mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-05 16:37:46 +00:00
ca5a930c68
Phase 2 of the index.ts refactor. Pulls the auth middleware and session cookie issuers into their own module, and introduces the app.ts factory that owns the first nine steps of the canonical middleware pipeline. New modules: - middleware/auth.ts: authMiddleware, issueSessionCookie, issueMfaPendingCookie, clearMfaPendingCookie - app.ts: createApp() factory installing trust proxy, helmet, cors, compression, cookieParser, rate limiters, conditionalJsonParser, and nodeContextMiddleware. A header comment documents all 16 canonical middleware steps and where each currently lives. Changes: - middleware/authGate.ts: createAuthGate factory removed; authGate now imports authMiddleware directly (the factory existed only to avoid a circular import while authMiddleware lived in index.ts). - services/DatabaseService.ts: added API_TOKEN_SCOPE_TO_ROLE map so the auth middleware no longer inlines a stringly-typed record. - index.ts drops ~260 lines; auth routes, authGate, auditLog, apiTokenScope, remaining routes, static serving, and the error handler continue to be registered there until their respective phases. - vitest.config.ts bumps testTimeout to 30s and hookTimeout to 45s so fork-pool workers have enough headroom to ts-node-transform the growing module graph under CPU contention (64 workers each import the full Express stack in beforeAll). Code review fixes: use getErrorMessage() util in the auth catch block instead of inline cast; promote the scope-to-role map to a typed module-level constant.
62 lines
2.1 KiB
TypeScript
62 lines
2.1 KiB
TypeScript
import type { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
import { DatabaseService } from '../services/DatabaseService';
|
|
import { isDebugEnabled } from '../utils/debug';
|
|
import { getAuditSummary } from '../utils/audit-summaries';
|
|
import { WEBHOOK_TRIGGER_RE } from '../helpers/routePatterns';
|
|
import { authMiddleware } from './auth';
|
|
|
|
/**
|
|
* `/api/*` auth gate. Mounted at `/api`, so paths it sees are already
|
|
* stripped of the `/api` prefix. Exempts `/auth/*` (setup, login, SSO:
|
|
* handled by their own routes) and webhook triggers (authenticated via
|
|
* HMAC, not session).
|
|
*/
|
|
export const authGate: RequestHandler = (req: Request, res: Response, next: NextFunction): void => {
|
|
if (req.path.startsWith('/auth/') || WEBHOOK_TRIGGER_RE.test(req.path)) {
|
|
next();
|
|
return;
|
|
}
|
|
authMiddleware(req, res, next);
|
|
};
|
|
|
|
/**
|
|
* Audit-logging middleware. Records every mutating `/api/*` action for
|
|
* Admiral accountability. Mounted at `/api`. Uses `res.on('finish')` to
|
|
* capture the final status code.
|
|
*/
|
|
export const auditLog: RequestHandler = (req: Request, res: Response, next: NextFunction): void => {
|
|
if (!['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
const username = req.user?.username || 'unknown';
|
|
const nodeId = req.nodeId ?? null;
|
|
const forwarded = req.headers['x-forwarded-for'];
|
|
const xff = typeof forwarded === 'string' ? forwarded.split(',')[0].trim() : '';
|
|
const ip = req.ip || xff || '';
|
|
const apiPath = req.path;
|
|
|
|
res.on('finish', () => {
|
|
try {
|
|
if (isDebugEnabled()) {
|
|
console.log(`[Audit:diag] ${req.method} /api${apiPath} by=${username} status=${res.statusCode} node=${nodeId ?? 'local'} ip=${ip}`);
|
|
}
|
|
DatabaseService.getInstance().insertAuditLog({
|
|
timestamp: Date.now(),
|
|
username,
|
|
method: req.method,
|
|
path: `/api${apiPath}`,
|
|
status_code: res.statusCode,
|
|
node_id: nodeId,
|
|
ip_address: ip,
|
|
summary: getAuditSummary(req.method, apiPath),
|
|
});
|
|
} catch (err) {
|
|
console.error('[Audit] Failed to write audit log:', err);
|
|
}
|
|
});
|
|
|
|
next();
|
|
};
|