46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
import { jwtVerify } from 'jose';
|
|
import { UnauthorizedError, ForbiddenError } from '@bytelyst/errors';
|
|
|
|
export type Role = 'viewer' | 'admin' | 'super_admin';
|
|
|
|
const JWT_ISSUER = 'bytelyst-platform';
|
|
|
|
export interface JwtPayload {
|
|
sub: string;
|
|
role?: Role;
|
|
productId?: string;
|
|
iat?: number;
|
|
exp?: number;
|
|
}
|
|
|
|
/** Minimal request shape used for auth checks (works with FastifyRequest or McpToolRequest) */
|
|
export interface AuthRequest {
|
|
headers: { authorization?: string | undefined };
|
|
jwtPayload?: JwtPayload;
|
|
}
|
|
|
|
declare module 'fastify' {
|
|
interface FastifyRequest {
|
|
jwtPayload?: JwtPayload;
|
|
}
|
|
}
|
|
|
|
export async function verifyJwtToken(token: string, secret: Uint8Array): Promise<JwtPayload> {
|
|
const { payload } = await jwtVerify(token, secret, { issuer: JWT_ISSUER });
|
|
return payload as JwtPayload;
|
|
}
|
|
|
|
export function requireAuth(req: AuthRequest): JwtPayload {
|
|
if (!req.jwtPayload?.sub) throw new UnauthorizedError('Authentication required');
|
|
return req.jwtPayload as JwtPayload;
|
|
}
|
|
|
|
export function requireRole(req: AuthRequest, minRole: Role): JwtPayload {
|
|
const payload = requireAuth(req);
|
|
const order: Role[] = ['viewer', 'admin', 'super_admin'];
|
|
const userLevel = order.indexOf(payload.role ?? 'viewer');
|
|
const requiredLevel = order.indexOf(minRole);
|
|
if (userLevel < requiredLevel) throw new ForbiddenError(`Role '${minRole}' required`);
|
|
return payload;
|
|
}
|