mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-11 19:26:56 +00:00
refactor(backend): extract rate limiters, body parsing, and request gates (phase 1) (#731)
Phase 1 of the index.ts monolith refactor. Extracts all non-auth middleware into focused modules. Routes and authMiddleware itself stay in index.ts for now; Phase 2 introduces createApp() and extracts authMiddleware. New modules: - middleware/rateLimiters.ts: globalApiLimiter, pollingLimiter, webhookTriggerLimiter, authRateLimiter, ssoRateLimiter, trivyInstallLimiter plus the hybrid rateLimitKeyGenerator and isNodeProxyRequest helper - middleware/jsonParser.ts: conditionalJsonParser that preserves the raw stream for remote-proxy forwarding (via helpers/proxyExemptPaths) - middleware/nodeContext.ts: nodeContextMiddleware - middleware/apiTokenScope.ts: enforceApiTokenScope + DEPLOY_ALLOWED_PATTERNS - middleware/authGate.ts: createAuthGate(authMiddleware) factory + auditLog. Factory takes authMiddleware as a dependency to avoid a circular import until Phase 2 extracts the auth module. - middleware/errorHandler.ts: central error handler that preserves err.status / err.expose from body-parser and other HTTP errors - helpers/routePatterns.ts: WEBHOOK_TRIGGER_RE shared by rateLimiters and authGate index.ts drops ~290 lines. Middleware registration order is unchanged. All 1278 tests pass. Code review fixes: typed ApiTokenScope in apiTokenScope.ts; replaced 2 em dashes with colons (Directive 18); added local CachedProxyFlagReq type alias for the node_proxy memoization cast; extracted deny() helper.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* Build the `/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).
|
||||
*
|
||||
* Takes `authMiddleware` as a dependency instead of importing it so this
|
||||
* file does not pin the monolith's auth lifecycle. Phase 2 extracts
|
||||
* `authMiddleware` into its own module.
|
||||
*/
|
||||
export function createAuthGate(authMiddleware: RequestHandler): RequestHandler {
|
||||
return (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();
|
||||
};
|
||||
Reference in New Issue
Block a user