Files
ipwhitelist-app/server.js
T
naeel c85a438ba2 feat: UI-слой через /api/v1/* (ui/, views/ui-login.ejs, views/error.ejs)
- ui/api-client.js: HTTP-клиент к /api/v1/* (token из session.token)
- ui/routes/auth.js: /login (mock-пресеты), /login-token (Bearer paste), /logout
- ui/routes/entries.js: /, /add, /edit/:id, /delete/:id через API
- ui/routes/admin.js: /admin, /admin/limit/:id, /audit через API
- ui/routes/export.js: /export → проксирует /api/v1/entries/export
- ui/index.js: createUiRouter() — requireToken + resolveUser middleware
- views/ui-login.ejs: страница входа (mock + token)
- views/error.ejs: шаблон для 403/404/500

API расширения:
- GET /api/v1/entries теперь возвращает limit и used
- GET /api/v1/entries?company=<id> (admin): записи другой компании
- POST/PATCH/DELETE /api/v1/entries?company=<id> (admin): мутации для другой компании
- GET /api/v1/entries/export: агрегированный CIDR (перенесён из монолита)
- GET /api/v1/audit?company=<id>: журнал (перенесён из монолита)

server.js: заменены src/routes/* на ui/createUiRouter(), убран CSRF
2026-05-30 19:02:51 +03:00

112 lines
5.6 KiB
JavaScript

/**
* server.js — точка входа приложения.
*
* Отвечает только за:
* 1. Инициализацию Express и глобальных middleware (helmet, static, body-parser)
* 2. Инициализацию auth (initAuth) и CSRF (initCsrf)
* 3. Подключение роутеров из src/routes/ с передачей зависимостей
* 4. Старт HTTP-сервера
*
* Бизнес-логика — в src/routes/*.js
* DB-запросы — в src/queries.js
* Auth/JWT — в src/auth.js
* Валидация — в src/validators.js
* Конфиг/хелперы — в src/config.js
*/
'use strict';
const express = require('express');
const cookieParser = require('cookie-parser');
const path = require('path');
const helmet = require('helmet');
require('dotenv').config();
const { checkConnection } = require('./src/db');
const { initAuth } = require('./src/auth');
const q = require('./src/queries');
const { MOCK_USERS, APP_VERSION } = require('./src/config');
const { authLimiter } = require('./src/middleware/rateLimit');
const { createSessionMiddleware } = require('./src/middleware/session');
const { CSP_DIRECTIVES } = require('./src/middleware/csp');
const { createUiRouter } = require('./ui/index');
const app = express();
const PORT = process.env.PORT || 3000;
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Версия доступна во всех шаблонах как <%= appVersion %> без передачи вручную.
app.locals.appVersion = APP_VERSION;
// ── Безопасность HTTP-заголовков ───────────────────────────────────────────────
// helmet выставляет все security-заголовки одним вызовом, включая CSP.
// Директивы CSP вынесены в src/middleware/csp.js.
app.use(helmet({ contentSecurityPolicy: { directives: CSP_DIRECTIVES } }));
// За reverse-proxy (nginx/k8s ingress) — доверяем первому хопу для secure cookies.
if (process.env.NODE_ENV === 'production') app.set('trust proxy', 1);
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.urlencoded({ extended: true, limit: '32kb' }));
app.use(cookieParser());
async function start() {
const auth = await initAuth();
// ── Сессии ────────────────────────────────────────────────────────────────
const { pool } = require('./src/db');
app.use(createSessionMiddleware(pool));
// ── Fail-fast в production ────────────────────────────────────────────────
// Если секреты не заданы — приложение в проде немедленно падает.
// Дефолтные значения — только для локальной разработки.
if (process.env.NODE_ENV === 'production') {
const DEFAULT_SESSION = 'dev-session-secret-change-me';
if (!process.env.SESSION_SECRET || process.env.SESSION_SECRET === DEFAULT_SESSION) {
console.error('FATAL: SESSION_SECRET не задан или равен дефолту в production!');
process.exit(1);
}
}
// k8s liveness probe — простая проверка что сервер жив
app.get('/healthz', (req, res) => res.send('OK'));
// JWKS endpoint для валидации токенов внешними сервисами (только в mock-режиме)
if (auth.jwksHandler) app.get('/.well-known/jwks.json', auth.jwksHandler);
// ── REST API v1 (Bearer JWT, без сессий/CSRF) ─────────────────────────────
const { createApiRouter } = require('./src/api/index');
app.use('/api/v1', createApiRouter({ auth, q }));
// ── UI-слой: SSR через EJS, данные из /api/v1/* ───────────────────────────
// Монтируется ПОСЛЕ /api/v1/ — не перехватывает API-запросы.
// Хранит Bearer token в сессии, рендерит те же views/*.ejs.
app.use('/', createUiRouter({ auth, MOCK_USERS, authLimiter }));
// ── Общий обработчик ошибок ───────────────────────────────────────────────
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
console.error('Unhandled error:', err);
res.status(500).send('Внутренняя ошибка сервера');
});
// ── Старт сервера ─────────────────────────────────────────────────────────────
checkConnection()
.then(() => console.log('DB connected'))
.catch(e => console.error('DB not ready:', e.message));
return app;
}
// Запускаем сервер только если файл запущен напрямую (не через require)
if (require.main === module) {
start()
.then(a => a.listen(PORT, () => console.log('Server on port ' + PORT)))
.catch(e => { console.error('Startup error:', e); process.exit(1); });
}
module.exports = { start };