v0.5.166: /export→/v2/export редирект, экспорт только админам
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ui/routes/auth.js — /login, /logout для UI-слоя.
|
||||
*
|
||||
* Два режима:
|
||||
* Mock (DEV_MODE=true) — страница с пресетами MOCK_USERS.
|
||||
* Выбранный пользователь → генерируем mock Bearer token → session.token.
|
||||
*
|
||||
* Token (всегда доступен) — форма вставки реального Bearer токена от auth-api/KC.
|
||||
* Вставленный токен → session.token (без проверки подписи здесь,
|
||||
* API сам проверит при первом запросе).
|
||||
*
|
||||
* /logout — уничтожает сессию.
|
||||
*/
|
||||
|
||||
const { Router } = require('express');
|
||||
const { safeReturn } = require('../../src/auth');
|
||||
|
||||
function createRouter({ auth, MOCK_USERS, authLimiter }) {
|
||||
const router = Router();
|
||||
const devMode = () => process.env.DEV_MODE === 'true';
|
||||
|
||||
// GET /login
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session && req.session.token) return res.redirect('/');
|
||||
res.render('ui-login', {
|
||||
devMode: devMode(),
|
||||
MOCK_USERS: devMode() ? MOCK_USERS : [],
|
||||
error: req.query.error || null,
|
||||
returnTo: req.query.returnTo || '/',
|
||||
appVersion: require('../../package.json').version,
|
||||
});
|
||||
});
|
||||
|
||||
// POST /login — mock user
|
||||
router.post('/login', authLimiter, (req, res) => {
|
||||
const { clientId, returnTo } = req.body;
|
||||
if (!clientId) return res.redirect('/login?error=Выберите+пользователя');
|
||||
|
||||
const user = MOCK_USERS.find(u => u.clientId === clientId);
|
||||
if (!user) return res.redirect('/login?error=Пользователь+не+найден');
|
||||
|
||||
// Разбор clientId с запятой (мульти-компания)
|
||||
const allClientIds = user.clientId.split(',').map(s => s.trim()).filter(Boolean);
|
||||
const activeClientId = allClientIds[0] || user.clientId;
|
||||
|
||||
const token = auth.issueMockToken({
|
||||
clientId: user.clientId,
|
||||
companyId: user.companyId || '00000000-0000-0000-0000-000000000001',
|
||||
companyName: user.companyName || user.clientId,
|
||||
email: user.email || user.clientId + '@mock.local',
|
||||
});
|
||||
|
||||
req.session.token = token;
|
||||
req.session.user = {
|
||||
clientId: user.clientId,
|
||||
allClientIds,
|
||||
activeClientId,
|
||||
email: user.email || user.clientId + '@mock.local',
|
||||
companyId: user.companyId || '00000000-0000-0000-0000-000000000001',
|
||||
companyName: user.companyName || user.clientId,
|
||||
isAdmin: user.isAdmin || user.role === 'admin',
|
||||
};
|
||||
res.redirect(safeReturn(returnTo) || '/');
|
||||
});
|
||||
|
||||
// GET /login-token — редирект на /login (форма вставки токена там же)
|
||||
router.get('/login-token', (req, res) => res.redirect('/login'));
|
||||
|
||||
// POST /login-token — вход по Bearer JWT токену (реальному) или clientId (отладка)
|
||||
router.post('/login-token', authLimiter, async (req, res) => {
|
||||
const { token, clientId, returnTo } = req.body;
|
||||
|
||||
// ── Вход по реальному JWT токену ──────────────────────────────────────
|
||||
if (token && token.trim()) {
|
||||
const raw = token.trim().replace(/^Bearer\s+/i, '');
|
||||
// Минимальная проверка: это JWT (3 части, разделённые точками)
|
||||
const parts = raw.split('.');
|
||||
if (parts.length !== 3) {
|
||||
return res.redirect('/login?error=Невалидный+JWT+токен');
|
||||
}
|
||||
// Декодируем payload (без проверки подписи — API проверит)
|
||||
let payload;
|
||||
try {
|
||||
const buf = Buffer.from(parts[1], 'base64url');
|
||||
payload = JSON.parse(buf.toString());
|
||||
if (!payload || typeof payload !== 'object') throw new Error('empty');
|
||||
} catch (_) {
|
||||
return res.redirect('/login?error=Невалидный+payload+токена');
|
||||
}
|
||||
|
||||
// Извлекаем данные пользователя из payload
|
||||
const rawClientId = payload.ClientID || payload.client_id || payload.preferred_username || payload.sub || '';
|
||||
const allClientIds = rawClientId.split(',').map(s => s.trim()).filter(Boolean);
|
||||
const activeClientId = allClientIds[0] || rawClientId;
|
||||
|
||||
req.session.token = raw;
|
||||
|
||||
// Пытаемся обогатить через IAM API (только в OIDC-режиме)
|
||||
if (auth.isOidc) {
|
||||
try {
|
||||
const iamData = await auth.fetchIamUser(raw);
|
||||
req.session.user = {
|
||||
email: iamData.email,
|
||||
clientId: iamData.clientId,
|
||||
allClientIds: iamData.allClientIds,
|
||||
activeClientId: iamData.clientId,
|
||||
activeProfileId: iamData.activeProfileId,
|
||||
companyId: iamData.companyId,
|
||||
companyName: iamData.companyName,
|
||||
isAdmin: iamData.isAdmin,
|
||||
fio: iamData.fio,
|
||||
profiles: iamData.profiles,
|
||||
};
|
||||
console.log('[login-token] IAM enrichment OK:', iamData.email, iamData.clientId);
|
||||
} catch (iamErr) {
|
||||
// IAM недоступен — fallback из JWT
|
||||
console.warn('[login-token] IAM enrichment failed, using JWT fallback:', iamErr.message);
|
||||
}
|
||||
}
|
||||
// Fallback (всегда): базовый user из JWT
|
||||
if (!req.session.user) {
|
||||
req.session.user = {
|
||||
clientId: rawClientId,
|
||||
allClientIds,
|
||||
activeClientId,
|
||||
email: payload.email || payload.login || activeClientId + '@unknown',
|
||||
companyId: payload.company_id || payload.companyId || null,
|
||||
companyName: payload.company_name || payload.companyName || activeClientId,
|
||||
isAdmin: activeClientId === (process.env.ADMIN_CLIENT_ID || 'WZ01112'),
|
||||
};
|
||||
}
|
||||
return res.redirect(safeReturn(returnTo) || '/');
|
||||
}
|
||||
|
||||
// ── Вход по clientId (отладка, mock) ───────────────────────────────────
|
||||
if (!clientId || !clientId.trim()) return res.redirect('/login?error=Введите+clientId');
|
||||
|
||||
// В OIDC-режиме mock-токен бессмысленен — редиректим на KC
|
||||
if (auth.isOidc) {
|
||||
return res.redirect('/login?error=Вход+по+clientId+недоступен+в+SSO-режиме.+Войдите+через+Keycloak.');
|
||||
}
|
||||
|
||||
const trimmed = clientId.trim();
|
||||
const allClientIds = trimmed.split(',').map(s => s.trim()).filter(Boolean);
|
||||
const activeClientId = allClientIds[0];
|
||||
|
||||
const mockToken = auth.issueMockToken({
|
||||
clientId: trimmed,
|
||||
companyId: '00000000-0000-0000-0000-000000000001',
|
||||
companyName: activeClientId,
|
||||
email: activeClientId + '@debug.local',
|
||||
});
|
||||
|
||||
req.session.token = mockToken;
|
||||
req.session.user = {
|
||||
clientId: trimmed,
|
||||
allClientIds,
|
||||
activeClientId,
|
||||
email: activeClientId + '@debug.local',
|
||||
companyId: '00000000-0000-0000-0000-000000000001',
|
||||
companyName: activeClientId,
|
||||
isAdmin: activeClientId === (process.env.ADMIN_CLIENT_ID || 'WZ01112'),
|
||||
};
|
||||
res.redirect(safeReturn(returnTo) || '/');
|
||||
});
|
||||
|
||||
// ── Переключатель режима администратора ──────────────────────────────
|
||||
// 🔮 isAdmin будет из IAM, временно: isAdmin || clientId === WZ01112
|
||||
router.post('/toggle-admin', (req, res) => {
|
||||
const canAdmin = req.session.user && (
|
||||
req.session.user.isAdmin ||
|
||||
req.session.user.clientId === (process.env.ADMIN_CLIENT_ID || 'WZ01112')
|
||||
);
|
||||
if (!canAdmin) return res.status(403).send('Forbidden');
|
||||
|
||||
req.session.adminMode = !req.session.adminMode;
|
||||
// Редирект туда же откуда пришли, или на главную
|
||||
res.redirect(req.get('Referer') || '/');
|
||||
});
|
||||
|
||||
// GET /logout
|
||||
router.get('/logout', (req, res) => {
|
||||
req.session.destroy(() => res.redirect('/login'));
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
module.exports = { createRouter };
|
||||
Reference in New Issue
Block a user