/** * Admin/ops API (protected). Policies, key rotation, circuit-breaker. * Auth: ADMIN_API_KEY (x-admin-key or admin_key query) or JWT later. * Audit: sends to dbis_core central audit when DBIS_CENTRAL_URL + ADMIN_CENTRAL_API_KEY set. */ import { Router, Request, Response, NextFunction } from 'express'; import { setCircuitBreaker } from './observability.js'; import { appendCentralAudit } from './central-audit.js'; const router: Router = Router(); const ADMIN_API_KEY = process.env.ADMIN_API_KEY; let policies: Record = {}; let lastKeyRotationAt: Date | null = null; function getAdminSubject(req: Request): string { return (req.headers['x-admin-subject'] as string) || 'multi-chain-execution'; } function adminAuth(req: Request, res: Response, next: NextFunction): void { if (!ADMIN_API_KEY) { next(); return; } const key = req.headers['x-admin-key'] ?? req.query.admin_key; if (key !== ADMIN_API_KEY) { res.status(401).json({ error: 'Unauthorized' }); return; } next(); } router.use(adminAuth); router.post('/v1/admin/policies', (req: Request, res: Response) => { const body = req.body as Record; if (body && typeof body === 'object') { policies = { ...policies, ...body }; } appendCentralAudit({ employeeId: getAdminSubject(req), action: 'update_policies', permission: 'admin:action', resourceType: 'policies', metadata: body, ipAddress: req.ip || (req.headers['x-forwarded-for'] as string)?.split(',')[0], userAgent: req.get('user-agent') ?? undefined, }).catch(() => {}); res.status(200).json({ message: 'Policy update accepted', policies }); }); router.get('/v1/admin/policies', (_req: Request, res: Response) => { res.status(200).json({ policies }); }); router.post('/v1/admin/keys/rotate', (req: Request, res: Response) => { lastKeyRotationAt = new Date(); appendCentralAudit({ employeeId: getAdminSubject(req), action: 'keys_rotate', permission: 'admin:action', resourceType: 'keys', ipAddress: req.ip || (req.headers['x-forwarded-for'] as string)?.split(',')[0], userAgent: req.get('user-agent') ?? undefined, }).catch(() => {}); res.status(200).json({ message: 'Key rotation initiated', rotated_at: lastKeyRotationAt.toISOString(), }); }); router.get('/v1/admin/keys/status', (_req: Request, res: Response) => { res.status(200).json({ last_rotation: lastKeyRotationAt?.toISOString() ?? null, }); }); router.post('/v1/admin/circuit-breaker/on', (req: Request, res: Response) => { setCircuitBreaker(true); appendCentralAudit({ employeeId: getAdminSubject(req), action: 'circuit_breaker_on', permission: 'admin:action', resourceType: 'circuit_breaker', ipAddress: req.ip || (req.headers['x-forwarded-for'] as string)?.split(',')[0], userAgent: req.get('user-agent') ?? undefined, }).catch(() => {}); res.status(200).json({ message: 'Circuit breaker forced open' }); }); router.post('/v1/admin/circuit-breaker/off', (req: Request, res: Response) => { setCircuitBreaker(false); appendCentralAudit({ employeeId: getAdminSubject(req), action: 'circuit_breaker_off', permission: 'admin:action', resourceType: 'circuit_breaker', ipAddress: req.ip || (req.headers['x-forwarded-for'] as string)?.split(',')[0], userAgent: req.get('user-agent') ?? undefined, }).catch(() => {}); res.status(200).json({ message: 'Circuit breaker forced closed' }); }); export default router;