feat(platform-service): add getRequestProductId() + getRequestProductConfig() helpers
- New lib/request-context.ts with product validation against cache - Priority: JWT payload > X-Product-Id header > env var fallback - Rejects unknown or disabled products with 400 Bad Request - Augments FastifyRequest with jwtPayload type declaration - getRequestProductConfig() for modules needing product-specific values
This commit is contained in:
parent
755c16dbfb
commit
365061566a
71
services/platform-service/src/lib/request-context.ts
Normal file
71
services/platform-service/src/lib/request-context.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Request-level product context helpers.
|
||||
*
|
||||
* Extract and validate productId from every request:
|
||||
* 1. JWT token payload (authenticated requests)
|
||||
* 2. X-Product-Id header (unauthenticated requests)
|
||||
* 3. PRODUCT_ID env var fallback (backward compat during migration)
|
||||
*
|
||||
* Validates against the products registry — rejects unknown or disabled products.
|
||||
*/
|
||||
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { BadRequestError } from './errors.js';
|
||||
import { isValidProduct, getProduct } from '../modules/products/cache.js';
|
||||
import type { ProductDoc } from '../modules/products/types.js';
|
||||
|
||||
/** JWT payload shape attached to req by the onRequest hook (see Commit 3). */
|
||||
export interface JwtPayload {
|
||||
sub: string;
|
||||
email?: string;
|
||||
role?: string;
|
||||
productId?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
// Augment Fastify request to include parsed JWT payload
|
||||
declare module 'fastify' {
|
||||
interface FastifyRequest {
|
||||
jwtPayload?: JwtPayload;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and validate productId from the request.
|
||||
* Priority: JWT token > X-Product-Id header > env fallback (dev only).
|
||||
* Rejects unknown or disabled products.
|
||||
*/
|
||||
export function getRequestProductId(req: FastifyRequest): string {
|
||||
// 1. From JWT (set during login/register, attached by onRequest hook)
|
||||
let id = req.jwtPayload?.productId;
|
||||
|
||||
// 2. From header (unauthenticated requests like license activation)
|
||||
if (!id) {
|
||||
const header = req.headers['x-product-id'];
|
||||
if (typeof header === 'string' && header.length > 0) id = header;
|
||||
}
|
||||
|
||||
// 3. Fallback to env var (backward compat during migration, dev only)
|
||||
if (!id) {
|
||||
const envFallback = process.env.PRODUCT_ID;
|
||||
if (envFallback) id = envFallback;
|
||||
}
|
||||
|
||||
if (!id) throw new BadRequestError('productId is required (via JWT or X-Product-Id header)');
|
||||
|
||||
// Validate against product registry
|
||||
if (!isValidProduct(id)) throw new BadRequestError(`Unknown product: ${id}`);
|
||||
const product = getProduct(id)!;
|
||||
if (product.status === 'disabled') throw new BadRequestError(`Product ${id} is disabled`);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full product config for the current request's productId.
|
||||
* Useful when modules need product-specific values (trial days, device limits, etc.).
|
||||
*/
|
||||
export function getRequestProductConfig(req: FastifyRequest): ProductDoc {
|
||||
const id = getRequestProductId(req);
|
||||
return getProduct(id)!;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user