Files
ipwhitelist-app/v1_legacy/ui/routes/auth.js
T

192 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 };