Files

106 lines
3.4 KiB
TypeScript
Raw Permalink Normal View History

/**
* 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<string, unknown> = {};
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<string, unknown>;
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;