v0.5.14 — вход по токену, JWKS в mock-режиме, production deploy docs

- POST /login-token: принимает реальный JWT (не только clientId)
- auth.js: verifyAnyToken — mock + JWKS (Keycloak Nubes) одновременно
- auth.js: JWKS предзагружается всегда, даже в mock-режиме
- devops-deploy.md: полная production-инструкция для DevOps
- .env.example: production-ready шаблон
- README.md: актуальный (v0.5.13, архитектура, тесты, ссылки)
- .gitignore: очищен от дубликатов
This commit is contained in:
2026-06-03 12:37:10 +03:00
parent 22beaa86a3
commit 38c7adf25d
7 changed files with 396 additions and 143 deletions
+42 -4
View File
@@ -67,23 +67,61 @@ function createRouter({ auth, MOCK_USERS, authLimiter }) {
// GET /login-token — редирект на /login (форма вставки токена там же)
router.get('/login-token', (req, res) => res.redirect('/login'));
// POST /login-token — ручной вход по clientId (отладка)
// POST /login-token — вход по Bearer JWT токену (реальному) или clientId (отладка)
router.post('/login-token', authLimiter, (req, res) => {
const { clientId, returnTo } = req.body;
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;
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(returnTo || '/');
}
// ── Вход по clientId (отладка, mock) ───────────────────────────────────
if (!clientId || !clientId.trim()) return res.redirect('/login?error=Введите+clientId');
const trimmed = clientId.trim();
const allClientIds = trimmed.split(',').map(s => s.trim()).filter(Boolean);
const activeClientId = allClientIds[0];
const token = auth.issueMockToken({
const mockToken = auth.issueMockToken({
clientId: trimmed,
companyId: '00000000-0000-0000-0000-000000000001',
companyName: activeClientId,
email: activeClientId + '@debug.local',
});
req.session.token = token;
req.session.token = mockToken;
req.session.user = {
clientId: trimmed,
allClientIds,