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
This commit is contained in:
+90
@@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ui/index.js — UI-слой: SSR через EJS, данные из /api/v1/*.
|
||||
*
|
||||
* createUiRouter({ auth, MOCK_USERS, authLimiter }) → Express Router.
|
||||
*
|
||||
* Монтируется в server.js ПОСЛЕ /api/v1/.
|
||||
*
|
||||
* Middleware:
|
||||
* 1. requireToken — если нет session.token → redirect /login
|
||||
* 2. resolveUser — декодирует payload из токена → req.user
|
||||
* (без проверки подписи; API валидирует при каждом запросе)
|
||||
*
|
||||
* Если API возвращает 401 → сессия уничтожается → redirect /login.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Router } = require('express');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const { createRouter: createAuthRouter } = require('./routes/auth');
|
||||
const { createRouter: createEntriesRouter } = require('./routes/entries');
|
||||
const { createRouter: createAdminRouter } = require('./routes/admin');
|
||||
const { createRouter: createExportRouter } = require('./routes/export');
|
||||
const api = require('./api-client');
|
||||
|
||||
const ADMIN_CLIENT_ID = process.env.ADMIN_CLIENT_ID || 'WZ01112';
|
||||
|
||||
function createUiRouter({ auth, MOCK_USERS, authLimiter }) {
|
||||
const router = Router();
|
||||
|
||||
// ── Публичные маршруты: /login, /login-token, /logout ────────────────────
|
||||
router.use('/', createAuthRouter({ auth, MOCK_USERS, authLimiter }));
|
||||
|
||||
// ── requireToken: все маршруты ниже требуют session.token ────────────────
|
||||
router.use((req, res, next) => {
|
||||
const token = api.token(req);
|
||||
if (!token) {
|
||||
return res.redirect('/login?returnTo=' + encodeURIComponent(req.originalUrl));
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// ── resolveUser: payload → req.user ──────────────────────────────────────
|
||||
// Подпись не проверяем здесь — API проверяет при каждом запросе.
|
||||
router.use((req, res, next) => {
|
||||
if (req.user) return next();
|
||||
try {
|
||||
const payload = jwt.decode(api.token(req));
|
||||
if (!payload) throw new Error('empty payload');
|
||||
const clientId = payload.ClientID || payload.clientId || payload.sub || '';
|
||||
req.user = {
|
||||
clientId,
|
||||
companyId: payload.company_id || payload.companyId || null,
|
||||
companyName: payload.company_name || payload.companyName || clientId,
|
||||
email: payload.email || clientId + '@unknown',
|
||||
isAdmin: clientId === ADMIN_CLIENT_ID,
|
||||
};
|
||||
} catch (e) {
|
||||
req.session.destroy(() =>
|
||||
res.redirect('/login?error=' + encodeURIComponent('Токен недействителен, войдите заново'))
|
||||
);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// ── Основные маршруты (все за requireToken + resolveUser) ─────────────────
|
||||
router.use('/', createEntriesRouter());
|
||||
router.use('/', createExportRouter());
|
||||
router.use('/', createAdminRouter());
|
||||
|
||||
// ── 404 ───────────────────────────────────────────────────────────────────
|
||||
router.use((req, res) => {
|
||||
res.status(404).render('error', { status: 404, message: 'Страница не найдена' });
|
||||
});
|
||||
|
||||
// ── Общий обработчик ошибок ───────────────────────────────────────────────
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
router.use((err, req, res, next) => {
|
||||
console.error('[UI] Error:', err);
|
||||
res.status(500).render('error', { status: 500, message: 'Внутренняя ошибка сервера' });
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
module.exports = { createUiRouter };
|
||||
Reference in New Issue
Block a user