Something went wrong. Try again.
AtAuth
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162/** * Admin Routes * * Application registration and management endpoints. * Express 5 automatically forwards async errors to the error handler. */
import crypto from 'crypto';import { Router, Request, Response, NextFunction } from 'express';import { DatabaseService } from '../services/database.js';import { generateHmacSecret } from '../utils/hmac.js';import { httpError } from '../utils/errors.js';
/** * Constant-time string comparison to prevent timing attacks. */function secureCompare(a: string, b: string): boolean { const bufA = Buffer.from(a); const bufB = Buffer.from(b);
// Prevent length-based timing attacks by always comparing fixed-length hashes const hashA = crypto.createHash('sha256').update(bufA).digest(); const hashB = crypto.createHash('sha256').update(bufB).digest();
return crypto.timingSafeEqual(hashA, hashB);}
export function createAdminRoutes(db: DatabaseService, adminToken?: string): Router { const router = Router();
const requireAdmin = (req: Request, _res: Response, next: NextFunction) => { if (!adminToken) { throw httpError.forbidden('admin_disabled', 'Admin endpoints are disabled (set ADMIN_TOKEN)'); }
const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { throw httpError.unauthorized('missing_auth', 'Authorization header required'); }
const token = authHeader.substring(7); if (!secureCompare(token, adminToken)) { throw httpError.forbidden('invalid_token', 'Invalid admin token'); }
next(); };
/** * POST /admin/apps * Register a new application * * Body: * - id: Unique app identifier * - name: Display name * - token_ttl_seconds: Token lifetime (default 3600) * - callback_url: OAuth callback URL */ router.post('/apps', requireAdmin, async (req: Request, res: Response) => { const { id, name, token_ttl_seconds, callback_url } = req.body;
if (!id || !name) { throw httpError.badRequest('missing_params', 'id and name are required'); }
const existing = db.getApp(id); if (existing) { throw httpError.conflict('app_exists', `Application '${id}' already exists`); }
const hmac_secret = generateHmacSecret();
db.upsertApp({ id, name, hmac_secret, token_ttl_seconds: token_ttl_seconds || 3600, callback_url, });
res.status(201).json({ id, name, hmac_secret, token_ttl_seconds: token_ttl_seconds || 3600, callback_url, message: 'Application registered. Store the hmac_secret securely!', }); });
/** * GET /admin/apps/:id * Get application configuration (without secret) */ router.get('/apps/:id', requireAdmin, async (req: Request, res: Response) => { const app = db.getApp(req.params.id); if (!app) { throw httpError.notFound('app_not_found', `Application '${req.params.id}' not found`); }
res.json({ id: app.id, name: app.name, token_ttl_seconds: app.token_ttl_seconds, callback_url: app.callback_url, }); });
/** * PUT /admin/apps/:id * Update application configuration */ router.put('/apps/:id', requireAdmin, async (req: Request, res: Response) => { const existing = db.getApp(req.params.id); if (!existing) { throw httpError.notFound('app_not_found', `Application '${req.params.id}' not found`); }
const { name, token_ttl_seconds, callback_url, rotate_secret } = req.body;
const updated = { id: req.params.id, name: name || existing.name, hmac_secret: rotate_secret ? generateHmacSecret() : existing.hmac_secret, token_ttl_seconds: token_ttl_seconds || existing.token_ttl_seconds, callback_url: callback_url !== undefined ? callback_url : existing.callback_url, };
db.upsertApp(updated);
const response: Record<string, unknown> = { id: updated.id, name: updated.name, token_ttl_seconds: updated.token_ttl_seconds, callback_url: updated.callback_url, };
if (rotate_secret) { response.hmac_secret = updated.hmac_secret; response.message = 'Secret rotated. Update your backend configuration!'; }
res.json(response); });
/** * POST /admin/cleanup * Clean up expired sessions and OAuth states */ router.post('/cleanup', requireAdmin, async (_req: Request, res: Response) => { const statesDeleted = db.cleanupOldOAuthStates(); const sessionsDeleted = db.cleanupExpiredSessions();
res.json({ oauth_states_deleted: statesDeleted, sessions_deleted: sessionsDeleted, }); });
return router;}