From 365061566ab6cc867a2113ce910a81954e07db92 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sun, 15 Feb 2026 14:14:17 -0800 Subject: [PATCH] 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 --- .../src/lib/request-context.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 services/platform-service/src/lib/request-context.ts diff --git a/services/platform-service/src/lib/request-context.ts b/services/platform-service/src/lib/request-context.ts new file mode 100644 index 00000000..8cad4ed7 --- /dev/null +++ b/services/platform-service/src/lib/request-context.ts @@ -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)!; +}