feat: security audit fixes

This commit is contained in:
ZOMBIIIIIII
2026-05-13 00:17:32 +03:00
parent e87d178d71
commit 1498ed3431
31 changed files with 2198 additions and 339 deletions

View File

@@ -35,17 +35,50 @@ app.use(cookieParser());
app.use(traceMiddleware);
// ── PUBLIC endpoints ─────────────────────────────────────────────────────────
app.get('/api/health', (_req, res) => {
res.json({ success: true, data: { status: 'ok' } });
// H11 — /api/health with DB probe (не возвращает OK если DB down)
import { db } from './config/database';
app.get('/api/health', async (_req, res) => {
try {
await Promise.race([
db.raw('select 1'),
new Promise((_, reject) => setTimeout(() => reject(new Error('db-timeout')), 1000)),
]);
res.json({ success: true, data: { status: 'ok' } });
} catch (err: any) {
res.status(503).json({ success: false, error: 'db_unavailable' });
}
});
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
app.get('/api/docs/swagger.json', (_req, res) => {
// ── Глобальный rate limit на /api/* — ДО docs чтобы не было unauthenticated DoS на swagger.json
app.use('/api', globalLimiter);
// H1 — Swagger gated. В production требуется basic-auth ИЛИ NODE_ENV != production.
// JSON endpoint ОБЯЗАТЕЛЬНО до app.use('/api/docs', ...) — иначе swagger-ui-express
// перехватывает все /api/docs/* и возвращает HTML вместо JSON.
const docsGate = (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (process.env.NODE_ENV !== 'production' || process.env.SWAGGER_PUBLIC === 'true') {
return next();
}
// Production без SWAGGER_PUBLIC=true → require basic auth (operator credentials)
const auth = req.headers.authorization || '';
const expected = process.env.SWAGGER_BASIC_AUTH; // "user:pass"
if (!expected || !auth.startsWith('Basic ')) {
res.set('WWW-Authenticate', 'Basic realm="docs"');
res.status(401).json({ success: false, error: 'Docs auth required' });
return;
}
const decoded = Buffer.from(auth.slice(6), 'base64').toString('utf8');
if (decoded !== expected) {
res.set('WWW-Authenticate', 'Basic realm="docs"');
res.status(401).json({ success: false, error: 'Invalid docs credentials' });
return;
}
return next();
};
app.get('/api/docs/swagger.json', docsGate, (_req, res) => {
res.json(swaggerSpec);
});
// ── Глобальный rate limit на весь API после public endpoints ────────────────
app.use('/api', globalLimiter);
app.use('/api/docs', docsGate, swaggerUi.serve, swaggerUi.setup(swaggerSpec));
// ── PROTECTED endpoints (JWT + CSRF) ─────────────────────────────────────────
const protect = [authMiddleware, csrfMiddleware];