chore: delete UI middleware (csrf, session, csp) - not needed in API
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* src/middleware/csp.js — Content Security Policy директивы.
|
||||
*
|
||||
* Экспортирует объект CSP_DIRECTIVES для передачи в helmet({ contentSecurityPolicy }).
|
||||
* Так CSP применяется один раз вместе с остальными заголовками helmet.
|
||||
*
|
||||
* Текущее ограничение: EJS-шаблоны используют inline <style>, <script> и
|
||||
* атрибуты onclick/style= — поэтому 'unsafe-inline' пока необходим.
|
||||
* Это всё равно лучше отключённого CSP: блокируются внешние ресурсы,
|
||||
* фреймирование, object/embed, переопределение base.
|
||||
*
|
||||
* Что блокирует эта политика:
|
||||
* - Загрузку скриптов/стилей с внешних доменов
|
||||
* - Фреймирование страниц (clickjacking) — frame-ancestors 'none'
|
||||
* - <object>, <embed>, <applet> — object-src 'none'
|
||||
* - Переопределение <base href> — base-uri 'self'
|
||||
* - POST форм на внешние домены — form-action 'self'
|
||||
*
|
||||
* TODO (при переносе стилей в .css и скриптов в .js файлы):
|
||||
* убрать 'unsafe-inline', добавить nonce или hash для оставшихся inline-блоков.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const CSP_DIRECTIVES = {
|
||||
defaultSrc: ["'self'"],
|
||||
// 'unsafe-inline' нужен пока стили и скрипты inline в EJS.
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", 'data:'], // data: для SVG-иконок
|
||||
fontSrc: ["'self'"],
|
||||
connectSrc: ["'self'"],
|
||||
objectSrc: ["'none'"], // блок <object>/<embed>/<applet>
|
||||
baseUri: ["'self'"], // блок переопределения <base>
|
||||
formAction: ["'self'"], // POST только на свой домен
|
||||
frameAncestors: ["'none'"], // clickjacking protection
|
||||
};
|
||||
|
||||
module.exports = { CSP_DIRECTIVES };
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* 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-токен к конкретной сессии пользователя.
|
||||
// Используем req.sessionID (выставляется express-session до вызова CSRF middleware).
|
||||
// Если сессии нет — пустая строка (допустимо для /login до установки сессии).
|
||||
getSessionIdentifier: (req) => req.sessionID || '',
|
||||
|
||||
// ⚠️ csrf-csrf v4 по умолчанию читает токен из заголовка x-csrf-token.
|
||||
// Наши формы (EJS) отправляют его в теле POST как поле _csrf.
|
||||
// Без этой строки токен не находится → 403 на каждый POST.
|
||||
getCsrfTokenFromRequest: (req) => req.body && req.body._csrf,
|
||||
|
||||
// Имя cookie без префикса __Host- (дефолт __Host-psifi.x-csrf-token
|
||||
// требует Secure-флаг; при NODE_ENV≠production secure:false → cookie
|
||||
// отклоняется браузером по HTTPS и валидация падает).
|
||||
cookieName: 'csrf-token',
|
||||
|
||||
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 };
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* src/middleware/session.js — конфигурация express-session.
|
||||
*
|
||||
* Стор выбирается по наличию пула БД:
|
||||
* - pool передан → connect-pg-simple (PG): сессии переживают рестарт, работают
|
||||
* на нескольких подах.
|
||||
* - pool не передан → MemoryStore: только для юнит-тестов (не требует БД).
|
||||
*
|
||||
* Таблица session создаётся автоматически connect-pg-simple при первом старте
|
||||
* (createTableIfMissing: true). SQL-схема совместима с PostgreSQL 9.6+.
|
||||
*
|
||||
* Экспортирует одну функцию: createSessionMiddleware(pool) → express middleware.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const session = require('express-session');
|
||||
const ConnectPgSimple = require('connect-pg-simple');
|
||||
|
||||
const SESSION_MAX_AGE = 8 * 60 * 60 * 1000; // 8 часов, в мс
|
||||
|
||||
/**
|
||||
* Создаёт и возвращает сконфигурированный express-session middleware.
|
||||
* @param {import('pg').Pool|null} pool — pg Pool; null → MemoryStore (тесты)
|
||||
* @returns {import('express').RequestHandler}
|
||||
*/
|
||||
function createSessionMiddleware(pool) {
|
||||
const store = pool ? buildPgStore(pool) : undefined; // undefined → MemoryStore
|
||||
|
||||
return session({
|
||||
store,
|
||||
secret: process.env.SESSION_SECRET || 'dev-session-secret-change-me',
|
||||
resave: false,
|
||||
// true: сессия сохраняется даже пустой (нужно чтобы sessionID не менялся
|
||||
// между GET /login и POST /login — иначе CSRF-токен привязанный к sessionID
|
||||
// из GET не совпадёт с sessionID на POST и будет 403).
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production', // HTTPS only в проде
|
||||
sameSite: 'lax',
|
||||
maxAge: SESSION_MAX_AGE,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт connect-pg-simple Store поверх переданного пула.
|
||||
* Таблица `session` создаётся автоматически если отсутствует.
|
||||
* @param {import('pg').Pool} pool
|
||||
* @returns {session.Store}
|
||||
*/
|
||||
function buildPgStore(pool) {
|
||||
const PgSession = ConnectPgSimple(session);
|
||||
return new PgSession({
|
||||
pool,
|
||||
createTableIfMissing: true,
|
||||
// Автоочистка протухших сессий раз в час (прунинг не блокирует запросы).
|
||||
pruneSessionInterval: 60 * 60, // секунды
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { createSessionMiddleware };
|
||||
Reference in New Issue
Block a user