Fastify
Um plugin que decora a requisição e hooks que barram a rota.
O Fastify prefere decorators e hooks a middleware, então a integração é um plugin pequeno em vez de um monte de funções. A página do Node explica o que ele chama por baixo.
Instalação
npm install @nulvless/nulvless-js fastify fastify-pluginO plugin
fastify-plugin impede o Fastify de encapsular os decorators, para que rotas
registradas em outro lugar continuem enxergando-os.
import fp from 'fastify-plugin';
import { createServerClient } from '@nulvless/nulvless-js';
import type { FastifyReply, FastifyRequest } from 'fastify';
export default fp(async (app) => {
const nulvless = createServerClient(
process.env.NULVLESS_URL!,
process.env.NULVLESS_SECRET!,
);
app.decorate('nulvless', nulvless);
app.decorateRequest('userId', null);
app.decorateRequest('session', null);
app.decorate('requireAuth', async (request: FastifyRequest, reply: FastifyReply) => {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) return reply.code(401).send();
const { data, error } = await nulvless.auth.verify(undefined, {
headers: { Authorization: `Bearer ${token}` },
});
if (error) return reply.code(401).send();
request.userId = data.user.id;
request.session = data.session;
});
app.decorate('requireRole', (roles: string[]) =>
async (request: FastifyRequest, reply: FastifyReply) => {
const token = request.headers.authorization?.replace('Bearer ', '');
const { error } = await nulvless.auth.verify(
{ resource: (request.params as { org: string }).org, roles },
{ headers: { Authorization: `Bearer ${token}` } },
);
if (error) return reply.code(403).send();
},
);
});Um hook que envia uma resposta encerra a requisição — retornar
reply.code(401).send() é como você impede o handler de rodar.
Declare os decorators
import type { NulvlessServerClient, Session } from '@nulvless/nulvless-js';
import type { FastifyReply, FastifyRequest } from 'fastify';
declare module 'fastify' {
interface FastifyInstance {
nulvless: NulvlessServerClient;
requireAuth: (request: FastifyRequest, reply: FastifyReply) => Promise<unknown>;
requireRole: (
roles: string[],
) => (request: FastifyRequest, reply: FastifyReply) => Promise<unknown>;
}
interface FastifyRequest {
userId: string | null;
session: Session | null;
}
}Barre as rotas
Anexe o hook por rota, e não globalmente, para que as rotas públicas continuem públicas.
import Fastify from 'fastify';
import nulvlessPlugin from './plugins/nulvless';
const app = Fastify();
await app.register(nulvlessPlugin);
app.get('/me', { preHandler: app.requireAuth }, async (request) => {
return { user: request.userId };
});
app.delete(
'/orgs/:org/keys/:id',
{ preHandler: app.requireRole(['owner', 'admin']) },
async (request, reply) => {
reply.code(204).send();
},
);
await app.listen({ port: 3000 });Aja em nome do usuário
Passe request.raw
Um FastifyRequest não é um IncomingMessage do Node — ele envolve um. Entregue
o request cru ao forwardUserHeaders, senão os headers não serão encontrados.
import { forwardUserHeaders } from '@nulvless/nulvless-js';
app.post('/orgs/:org/invites', { preHandler: app.requireAuth }, async (request, reply) => {
const body = request.body as { email: string; role_id: string };
const { data, error } = await app.nulvless.auth.grants.invite(
{ invites: [body], resource: (request.params as { org: string }).org },
{ headers: forwardUserHeaders(request.raw) },
);
if (error) return reply.code(400).send({ error });
return data;
});Exija uma confirmação recente
app.delete('/keys/:id', { preHandler: app.requireAuth }, async (request, reply) => {
const { error } = await app.nulvless.auth.sessions.revoke({
id: (request.params as { id: string }).id,
nonce: request.headers['nlv-sudo-nonce'] as string,
});
if (error) return reply.code(403).send({ error });
reply.code(204).send();
});Veja Modo sudo.