refactor: шаг 3 — CSRF-инициализация вынесена в src/middleware/csrf.js

This commit is contained in:
2026-05-30 13:37:08 +03:00
parent 35210291d0
commit b25f346d97
2 changed files with 65 additions and 16 deletions
+58
View File
@@ -0,0 +1,58 @@
/**
* src/middleware/csrf.js — инициализация CSRF-защиты.
*
* Используется паттерн «double-submit cookie»:
* 1. Сервер генерирует подписанный CSRF-токен и кладёт его в cookie.
* 2. Шаблон (EJS) вставляет токен в скрытое поле формы (<input name="_csrf">).
* 3. При POST-запросе doubleCsrfProtection сравнивает cookie и поле формы.
* 4. Если они не совпадают или токен протух — 403 Forbidden.
*
* Почему не стандартный csurf:
* csurf устарел (deprecated), csrf-csrf — его современная замена для Express 5+.
*
* Функция initCsrf() возвращает два объекта:
* - doubleCsrfProtection — middleware, вешается на POST-роуты
* - generateCsrfToken(req, res) — вызывается в GET-роутах для получения токена
* и передачи его в шаблон через csrfToken.
*/
'use strict';
const { doubleCsrf } = require('csrf-csrf');
/**
* Создаёт и возвращает CSRF middleware и генератор токенов.
*
* Вызывается один раз при старте приложения (в server.js → start()).
* Параметры берутся из process.env:
* - CSRF_SECRET: секрет для HMAC подписи. В dev используется дефолтное
* значение — в продакшене ОБЯЗАТЕЛЬНО задать через env!
* - NODE_ENV: если 'production' — cookie выставляется с флагом secure (HTTPS only).
*
* @returns {{ doubleCsrfProtection: Function, generateCsrfToken: Function }}
*/
function initCsrf() {
const { doubleCsrfProtection, generateCsrfToken } = doubleCsrf({
// Секрет для HMAC-подписи токена.
// ⚠️ В продакшене задать 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 || '',
cookieOptions: {
// secure: true → cookie передаётся только по HTTPS. Важно в продакшене.
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax', // lax: cookie отправляется при навигации, но не из iframe
httpOnly: true, // cookie недоступна из JS (защита от XSS-кражи CSRF-токена)
},
size: 64, // длина случайной части токена в байтах (достаточно для безопасности)
});
return { doubleCsrfProtection, generateCsrfToken };
}
module.exports = { initCsrf };