security: fix 7 vulnerabilities from Opus code review

- [A01] /export: mount after auth.middleware, filter by company for non-admin
- [A02] OIDC: verify issuer, audience, select key by kid; preload JWKS at startup
- [A07] session fixation: session.regenerate() in POST /login, GET /callback, POST /dev-login
- [A10] open redirect: safeReturn() helper validates returnTo (blocks //evil.com, https:// etc)
- [A05] CSRF: getSessionIdentifier uses req.sessionID instead of dead req.cookies.jwt
- [A05] fail-fast: reject default SESSION_SECRET / CSRF_SECRET in NODE_ENV=production
- [A05] DEV_MODE=true blocked in production

Tests: 68 PASS, 0 FAIL (was 50, added 18 new security-focused tests)
This commit is contained in:
2026-05-30 15:25:57 +03:00
parent ed6e9c4464
commit a5170a062c
6 changed files with 249 additions and 42 deletions
+4 -4
View File
@@ -37,10 +37,10 @@ function initCsrf() {
// ⚠️ В продакшене задать CSRF_SECRET в .env или через k8s Secret.
getSecret: () => process.env.CSRF_SECRET || 'dev-csrf-secret-change-in-prod',
// Идентификатор сессии — привязывает CSRF-токен к конкретному пользователю.
// Используем JWT-cookie: если его нет (не залогинен) — пустая строка,
// что допустимо для формы /login (она сама создаёт сессию).
getSessionIdentifier: (req) => req.cookies.jwt || '',
// Идентификатор сессии — привязывает CSRF-токен к конкретной сессии пользователя.
// Используем req.sessionID (выставляется express-session до вызова CSRF middleware).
// Если сессии нет — пустая строка (допустимо для /login до установки сессии).
getSessionIdentifier: (req) => req.sessionID || '',
cookieOptions: {
// secure: true → cookie передаётся только по HTTPS. Важно в продакшене.