Fastify
WIPWriting the auth routes on Fastify over auth.flows, guarding them with a preHandler, and binding the actor per handler
import { AuthError } from '@gentleduck/auth/core'
import { fastifyCaller, fastifyCsrf } from '@gentleduck/auth/server/fastify'
import { errorToHttp, executeIntents, jsonResponse, nodeHeadersToFetch } from '@gentleduck/auth/server/generic'
import Fastify from 'fastify'
const app = Fastify({ trustProxy: 1 })
app.setErrorHandler((err, _req, reply) => {
const { status, body } = errorToHttp(err)
reply.status(status).header('cache-control', 'no-store').send(body)
})
app.register(async (own) => {
own.addHook('preHandler', fastifyCsrf(auth))
own.post<{ Body: { providerId?: unknown; input?: unknown } }>('/auth/signin', async (req, reply) => {
const { providerId, input } = req.body ?? {}
if (typeof providerId !== 'string') throw new AuthError('AUTH_INVALID_PARAMETERS')
const headers = nodeHeadersToFetch(req.headers)
const { intents } = await auth.flows.signIn({
input: input ?? {},
providerId,
...fastifyCaller(req),
previousSid: auth.transport.extract({ headers }) ?? undefined,
})
return reply.send(executeIntents(intents))
})
own.post('/auth/signout', async (req, reply) => {
const sid = auth.transport.extract({ headers: nodeHeadersToFetch(req.headers) })
return reply.send(executeIntents(sid ? (await auth.flows.signOut(sid)).intents : auth.transport.revoke()))
})
own.get('/auth/session', async (req, reply) => {
const resolved = await auth.resolveSession({ headers: nodeHeadersToFetch(req.headers) }).orNull()
if (!resolved) return reply.send(jsonResponse(200, { session: null, identity: null }))
// `csrfHash` is server-side state; the browser holds the plaintext
const { csrfHash: _csrfHash, ...session } = resolved.session
return reply.send(jsonResponse(200, { session, identity: resolved.identity }))
})
})
- Fastify sends a Web
Responseas it is, soexecuteIntentsandjsonResponseserve as the intents writer. trustProxydecides which IPreq.ipanswers, and so what the session records. Set it to the proxies in front of you.- The provider routes are on the OAuth page. Register the callback outside the guarded plugin. Apple posts it as a form, which Fastify refuses with a 415 unless a parser for
application/x-www-form-urlencodedis registered.
Your own routes
import { fastifyWithActor } from '@gentleduck/auth/server/fastify'
app.register(async (api) => {
api.addHook('preHandler', fastifyCsrf(auth))
api.get(
'/api/me',
fastifyWithActor(auth, async (req, reply) => {
const resolved = await auth.resolveSession({ headers: nodeHeadersToFetch(req.headers) }).orNull()
if (!resolved) return reply.status(401).send({ error: 'unauthenticated' })
return resolved.identity
}),
)
})
Fastify composes no next, so the actor scope wraps each handler instead of sitting in front of them.