From 0135d28189678fb59c9b1e5a566c4e9022bf277a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Mon, 22 Jun 2026 19:22:42 +0400 Subject: [PATCH] =?UTF-8?q?v0.5.173:=20OIDC=20state=20=D0=B2=D1=81=D0=B5?= =?UTF-8?q?=D0=B3=D0=B4=D0=B0,=20CSRF=20=D1=81=D0=BC=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD,=20session.regenerate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- server.js | 6 +++++- src/routes/oidc.js | 17 +++++++++-------- v2/server.js | 6 +++--- v2/src/config/index.js | 2 +- v2/src/user/index.js | 3 ++- views/v2/user.ejs | 4 ++++ 7 files changed, 25 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 377987c..f4e2700 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.172", + "version": "0.5.173", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/server.js b/server.js index 7db97a8..55161c3 100644 --- a/server.js +++ b/server.js @@ -31,6 +31,7 @@ const { authLimiter } = require('./src/middleware/rateLimit'); const { createSessionMiddleware } = require('./src/middleware/session'); const { aggregateCIDRs } = require('./src/validators'); const { CSP_DIRECTIVES } = require('./src/middleware/csp'); +const { initCsrf } = require('./src/middleware/csrf'); const app = express(); const PORT = process.env.PORT || 3000; @@ -85,12 +86,15 @@ async function start() { const { createRouter: createOidcRouter } = require('./src/routes/oidc'); app.use('/', createOidcRouter({ auth })); + // ── CSRF — инициализация после сессий ───────────────────────────────── + const { generateCsrfToken, doubleCsrfProtection } = initCsrf(); + // ── V2 — основной код ───────────────────────────────────────────────── app.get('/', (req, res) => res.redirect('/v2/app')); // Редирект /export → /v2/export (для обратной совместимости) app.get('/export', (req, res) => res.redirect('/v2/export' + (req.originalUrl.includes('?') ? req.originalUrl.substring(req.originalUrl.indexOf('?')) : ''))); const { createV2Router } = require('./v2/server'); - app.use('/v2', createV2Router()); + app.use('/v2', createV2Router({ generateCsrfToken, doubleCsrfProtection })); // ── Общий обработчик ошибок ─────────────────────────────────────────────── // eslint-disable-next-line no-unused-vars diff --git a/src/routes/oidc.js b/src/routes/oidc.js index 6edd055..45038d8 100644 --- a/src/routes/oidc.js +++ b/src/routes/oidc.js @@ -32,15 +32,11 @@ function createRouter({ auth, doubleCsrfProtection, generateCsrfToken, authLimit router.get('/callback', async (req, res) => { const { code, state } = req.query; - // Проверка state — защита от CSRF в OAuth-потоке - if (process.env.NODE_ENV === 'production') { - if (req.session) delete req.session.oidcState; - } else { - if (!state || state !== req.session.oidcState) { - return res.status(403).send('Invalid state parameter'); - } - delete req.session.oidcState; + // Проверка state — защита от CSRF в OAuth-потоке (всегда) + if (!state || state !== req.session.oidcState) { + return res.status(403).send('Invalid state parameter'); } + delete req.session.oidcState; if (!code) { return res.status(400).send('Missing authorization code'); @@ -57,6 +53,11 @@ function createRouter({ auth, doubleCsrfProtection, generateCsrfToken, authLimit const payload = jwt.decode(accessToken); if (!payload) throw new Error('Failed to decode token'); + // Регенерируем ID сессии — защита от session fixation + await new Promise((resolve, reject) => { + req.session.regenerate(err => err ? reject(err) : resolve()); + }); + req.session.token = accessToken; req.session.idToken = tokenData.idToken || null; diff --git a/v2/server.js b/v2/server.js index 6d26288..09b1536 100644 --- a/v2/server.js +++ b/v2/server.js @@ -32,7 +32,7 @@ const { createAdminRouter } = require('./src/admin'); const { pool } = require('./src/db'); const { ensureSchema } = require('./src/db/schema'); -function createV2Router() { +function createV2Router({ generateCsrfToken, doubleCsrfProtection } = {}) { // Автосоздание таблиц v2_companies, v2_entries, v2_audit при старте ensureSchema(pool).catch(e => console.error('[v2:db] Schema error:', e.message)); @@ -102,10 +102,10 @@ function createV2Router() { }); // ── /v2/app — пользовательский CRUD (только с сессией) ────────────── - router.use('/app', enhanceImpersonation, resolveContext, createUserRouter()); + router.use('/app', enhanceImpersonation, resolveContext, doubleCsrfProtection || ((req,res,next)=>next()), createUserRouter({ generateCsrfToken })); // ── /v2/admin — админка: лимиты + аудит ────────────────────────── - router.use('/admin', enhanceImpersonation, resolveContext, createAdminRouter()); + router.use('/admin', enhanceImpersonation, resolveContext, doubleCsrfProtection || ((req,res,next)=>next()), createAdminRouter({ generateCsrfToken })); // ── GET /v2/ — редирект на /v2/app ────────────────────────────────── router.get('/', (req, res) => res.redirect('/v2/app')); diff --git a/v2/src/config/index.js b/v2/src/config/index.js index 2c17134..c8285cb 100644 --- a/v2/src/config/index.js +++ b/v2/src/config/index.js @@ -8,7 +8,7 @@ // ═══════════════════════════════════════════════════════════════════════════════ module.exports = { - version: '0.5.172', + version: '0.5.173', // ── IAM ────────────────────────────────────────────────────────────────── iamUrl: process.env.V2_IAM_URL || 'https://auth-api.ngcloud.ru/api/v1/auth/user', diff --git a/v2/src/user/index.js b/v2/src/user/index.js index 1c6e345..93c45de 100644 --- a/v2/src/user/index.js +++ b/v2/src/user/index.js @@ -19,7 +19,7 @@ const crud = require('../crud'); const q = require('../db/queries'); const config = require('../config'); -function createUserRouter() { +function createUserRouter({ generateCsrfToken } = {}) { const router = express.Router(); // ── GET / — список записей + форма ────────────────────────────────── @@ -84,6 +84,7 @@ function createUserRouter() { user: templateUser, includeDeleted, version: config.version, + csrfToken: generateCsrfToken ? generateCsrfToken(req, res) : '', }); } catch (e) { res.status(500).send('

Ошибка

' + e.message + '
Назад'); diff --git a/views/v2/user.ejs b/views/v2/user.ejs index 5ff35ca..db51508 100644 --- a/views/v2/user.ejs +++ b/views/v2/user.ejs @@ -188,6 +188,7 @@
Добавить адрес
+
@@ -233,6 +234,7 @@ <% if (!e.deleted_at) { %> + @@ -244,11 +246,13 @@ <% if (!e.deleted_at) { %> + <% } else { %>
+