import { jwtVerify } from 'jose'; import type { FastifyRequest } from 'fastify'; import { config, productId } from './config.js'; export interface AuthenticatedRequest extends FastifyRequest { authUserId?: string; authRole?: string; authEmail?: string; authProductId?: string; } export async function extractAuth(req: FastifyRequest): Promise<{ userId: string; role: string; email?: string; productId?: string } | null> { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { return null; } const token = authHeader.slice(7); try { const { payload } = await jwtVerify( token, new TextEncoder().encode(config.JWT_SECRET), { issuer: 'bytelyst-platform' } ); // Check if user has admin role for the devops product // If they have global admin role, they can access // Otherwise, check their per-product membership const globalRole = (payload.role as string) || 'user'; const currentProductId = payload.productId as string; const targetProductId = productId; // This dashboard's product ID let effectiveRole = globalRole; // If not global admin, check per-product membership if (globalRole !== 'admin' && payload.products) { const products = payload.products as Array<{ productId: string; role: string; plan: string }>; const devopsMembership = products.find(p => p.productId === targetProductId); if (devopsMembership && devopsMembership.role === 'admin') { effectiveRole = 'admin'; } } return { userId: payload.sub as string, role: effectiveRole, email: payload.email as string, productId: currentProductId, }; } catch { return null; } } export class AuthError extends Error { constructor(message: string, public statusCode: number = 401) { super(message); this.name = 'AuthError'; } } export class BadRequestError extends Error { constructor(message: string) { super(message); this.name = 'BadRequestError'; } } export function requireAdmin(req: FastifyRequest): { userId: string } { const authReq = req as AuthenticatedRequest; if (!authReq.authUserId || authReq.authRole !== 'admin') { throw new AuthError('Admin access required', 403); } return { userId: authReq.authUserId }; }