import type { FastifyInstance } from 'fastify'; export interface AccessTokenPayload { sub: string; // user id email: string; isSuperAdmin: boolean; } export interface RefreshTokenPayload { sub: string; // user id type: 'refresh'; } const ACCESS_TOKEN_EXPIRY = '15m'; const REFRESH_TOKEN_EXPIRY = '7d'; type JwtSign = (payload: object, options?: { expiresIn?: string }) => string; type JwtVerify = (token: string) => unknown; /** * The parts of the JWT decoration we actually call. * * @fastify/jwt decorates the instance at runtime and the refresh namespace is * registered by our own auth plugin, so neither appears in FastifyInstance's * type. Describing the shape here keeps the call sites type-checked instead of * casting the instance to `any`, which switches checking off entirely. */ interface JwtDecoratedInstance { jwt?: { sign?: JwtSign; verify?: JwtVerify; refresh?: { sign?: JwtSign; verify?: JwtVerify }; jwtRefresh?: { sign?: JwtSign; verify?: JwtVerify }; }; } /** The decorated JWT namespace, or undefined when the plugin is not loaded. */ export function getJwt(app: FastifyInstance): JwtDecoratedInstance['jwt'] { return (app as unknown as JwtDecoratedInstance).jwt; } export function signAccessToken(app: FastifyInstance, payload: AccessTokenPayload): string { const signer = getJwt(app)?.sign; if (typeof signer !== 'function') { throw new Error('JWT signer is not configured'); } return signer(payload, { expiresIn: ACCESS_TOKEN_EXPIRY }); } export function signRefreshToken(app: FastifyInstance, payload: RefreshTokenPayload): string { const jwt = getJwt(app); const signer = jwt?.refresh?.sign ?? jwt?.jwtRefresh?.sign; if (typeof signer !== 'function') { throw new Error('Refresh JWT signer is not configured'); } return signer(payload, { expiresIn: REFRESH_TOKEN_EXPIRY }); } export function verifyRefreshToken(app: FastifyInstance, token: string): RefreshTokenPayload { const jwt = getJwt(app); const verifier = jwt?.refresh?.verify ?? jwt?.jwtRefresh?.verify; if (typeof verifier !== 'function') { throw new Error('Refresh JWT verifier is not configured'); } return verifier(token) as RefreshTokenPayload; }