- src/auth.js: all impersonation fields + permissions.can_write - src/routes/oidc.js: save all fields to session - ui/index.js: canWrite, originalUserEmail in req.user - ui/routes/entries.js: read-only guard on POST/PATCH/DELETE - docs/PLAN-impersonation.md: updated with implemented items
114 lines
5.5 KiB
JavaScript
114 lines
5.5 KiB
JavaScript
'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 rawClientId = payload.ClientID || payload.clientId || payload.sub || '';
|
|
const allClientIds = rawClientId.split(',').map(s => s.trim()).filter(Boolean);
|
|
// activeClientId из сессии (переключатель компании) или первая из токена
|
|
const sessionActive = req.session && req.session.user && req.session.user.activeClientId;
|
|
const activeClientId = (sessionActive && allClientIds.includes(sessionActive))
|
|
? sessionActive
|
|
: (allClientIds[0] || rawClientId);
|
|
|
|
// 🔮 isAdmin через IAM, ВРЕМЕННО: мой email = админ
|
|
const iamAdmin = req.session && req.session.user && req.session.user.isAdmin;
|
|
const isNail = payload.email === 'ntazetdinov@nubes.ru' || payload.login === 'ntazetdinov@nubes.ru';
|
|
const canAdminMode = !!(iamAdmin && activeClientId === ADMIN_CLIENT_ID)
|
|
|| (isNail && activeClientId === ADMIN_CLIENT_ID);
|
|
const sessionIsImpersonated = req.session && req.session.user && req.session.user.isImpersonated;
|
|
const sessionCanWrite = req.session && req.session.user ? req.session.user.canWrite : true;
|
|
req.user = {
|
|
clientId: rawClientId,
|
|
allClientIds,
|
|
activeClientId,
|
|
companyId: payload.company_id || payload.companyId || null,
|
|
companyName: payload.company_name || payload.companyName || activeClientId,
|
|
email: req.session?.user?.email || payload.email || payload.login || activeClientId + '@unknown',
|
|
isAdmin: iamAdmin || isNail,
|
|
canWrite: sessionCanWrite,
|
|
isImpersonated: !!sessionIsImpersonated,
|
|
originalUserEmail: req.session?.user?.originalUserEmail || '',
|
|
originalUserFullName: req.session?.user?.originalUserFullName || '',
|
|
canAdminMode,
|
|
adminMode: !!(req.session && req.session.adminMode),
|
|
};
|
|
} 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) => {
|
|
const msg = err instanceof Error ? (err.message || err.stack || String(err)) : JSON.stringify(err);
|
|
console.error('[UI] Error:', msg);
|
|
res.status(500).render('error', { status: 500, message: 'Ошибка: ' + msg.slice(0, 500) });
|
|
});
|
|
|
|
return router;
|
|
}
|
|
|
|
module.exports = { createUiRouter };
|