- createAuthMiddleware(): RS256 JWKS + HS256 fallback (parameterized) - createRequestContext(): productId validation + getUserId() - Fastify request type augmentation for jwtPayload - 15 tests passing
48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
/**
|
|
* Configurable request context helpers for Fastify product backends.
|
|
*
|
|
* Factory function creates getRequestProductId() and getUserId() bound to
|
|
* the provided product ID, eliminating hardcoded product IDs in each repo.
|
|
*/
|
|
import type { FastifyRequest } from 'fastify';
|
|
import { BadRequestError } from '@bytelyst/errors';
|
|
import type { RequestContextOptions } from './types.js';
|
|
|
|
export function createRequestContext(opts: RequestContextOptions) {
|
|
const { productId } = opts;
|
|
|
|
/**
|
|
* Extract productId from request. Validates against this backend's product ID.
|
|
* Falls back to the configured productId since this is a product-specific backend.
|
|
*/
|
|
function getRequestProductId(req: FastifyRequest): string {
|
|
// 1. From JWT
|
|
const jwtPid = req.jwtPayload?.productId;
|
|
if (jwtPid && jwtPid !== productId) {
|
|
throw new BadRequestError(`Invalid productId: expected ${productId}, got ${jwtPid}`);
|
|
}
|
|
|
|
// 2. From header
|
|
const header = req.headers['x-product-id'];
|
|
if (typeof header === 'string' && header.length > 0 && header !== productId) {
|
|
throw new BadRequestError(`Invalid productId: expected ${productId}, got ${header}`);
|
|
}
|
|
|
|
return productId;
|
|
}
|
|
|
|
/**
|
|
* Extract userId from the JWT payload on the request.
|
|
* Throws BadRequestError if no authenticated user is found.
|
|
*/
|
|
function getUserId(req: FastifyRequest): string {
|
|
const sub = req.jwtPayload?.sub;
|
|
if (!sub) {
|
|
throw new BadRequestError('Missing userId — request must be authenticated');
|
|
}
|
|
return sub;
|
|
}
|
|
|
|
return { getRequestProductId, getUserId };
|
|
}
|