- Add docker-compose.yml following trading web pattern - Update web Dockerfile to use multi-stage build with metadata - Add build metadata (commit SHA, branch, timestamp, author, message) - Rewrite deploy.sh to use docker compose with build metadata - Add hotcopy deployment script for quick updates - Add comprehensive backend API with deployment orchestration - Add health checks, service management, and monitoring endpoints - Add CI/CD workflow configuration - Add deployment documentation and guides Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
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 };
|
|
}
|