v0.5.158: logout без KC, 401=destroy+redirect SSO
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ipwhitelist",
|
||||
"version": "0.5.157",
|
||||
"version": "0.5.158",
|
||||
"description": "IP WhiteList microservice for cloud provider",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
||||
+32
-15
@@ -145,25 +145,42 @@ function createV2Router() {
|
||||
const { createExportRouter } = require('./src/export');
|
||||
router.use('/export', createExportRouter());
|
||||
|
||||
// ── GET /v2/logout — редирект на основной выход ─────────────────────
|
||||
// ── GET /v2/logout — выход только из whitelist (без KC SSO) ─────────
|
||||
router.get('/logout', (req, res) => {
|
||||
const ua = (req.headers['user-agent'] || '').toLowerCase();
|
||||
const isBrowser = ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari');
|
||||
if (isBrowser) {
|
||||
res.set('Content-Type', 'text/html; charset=utf-8');
|
||||
return res.send('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Выход</title>'
|
||||
+ '<style>body{font-family:sans-serif;background:#111;color:#eee;padding:40px;text-align:center}'
|
||||
+ 'h2{color:#4fc3f7}p{font-size:16px;margin:20px 0}'
|
||||
+ '.btn{display:inline-block;padding:12px 24px;margin:0 10px;border-radius:6px;text-decoration:none;font-size:16px}'
|
||||
+ '.btn-yes{background:#ef5350;color:#fff}.btn-no{background:#333;color:#eee}'
|
||||
+ '</style></head><body>'
|
||||
+ '<h2>Выход из приложения</h2>'
|
||||
+ '<p>Выход завершит сессию во всех сервисах Nubes (Keycloak SSO).<br>Вы уверены?</p>'
|
||||
+ '<a href="/logout" class="btn btn-yes">Да, выйти</a>'
|
||||
+ '<a href="/v2/app" class="btn btn-no">Отмена</a>'
|
||||
+ '</body></html>');
|
||||
if (!isBrowser) {
|
||||
req.session.destroy(() => res.redirect('/v2/login'));
|
||||
return;
|
||||
}
|
||||
res.redirect('/logout');
|
||||
|
||||
if (req.query.confirm === 'yes') {
|
||||
req.session.destroy(() => {
|
||||
res.set('Content-Type', 'text/html; charset=utf-8');
|
||||
res.send('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Выход</title>'
|
||||
+ '<style>body{font-family:sans-serif;background:#111;color:#eee;padding:60px;text-align:center}'
|
||||
+ 'h2{color:#4fc3f7}p{font-size:18px;margin:20px 0}a{color:#80cbc4;font-size:16px}'
|
||||
+ '</style></head><body>'
|
||||
+ '<h2>Вы вышли из сервиса</h2>'
|
||||
+ '<p>Сессия whitelist завершена.<br>Сессия в других сервисах Nubes сохранена.</p>'
|
||||
+ '<a href="/v2/app">Войти снова</a>'
|
||||
+ '</body></html>');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.set('Content-Type', 'text/html; charset=utf-8');
|
||||
res.send('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Выход</title>'
|
||||
+ '<style>body{font-family:sans-serif;background:#111;color:#eee;padding:40px;text-align:center}'
|
||||
+ 'h2{color:#4fc3f7}p{font-size:16px;margin:20px 0}'
|
||||
+ '.btn{display:inline-block;padding:12px 24px;margin:0 10px;border-radius:6px;text-decoration:none;font-size:16px}'
|
||||
+ '.btn-yes{background:#ef5350;color:#fff}.btn-no{background:#333;color:#eee}'
|
||||
+ '</style></head><body>'
|
||||
+ '<h2>Выход из сервиса</h2>'
|
||||
+ '<p>Вы будете выведены только из whitelist.<br>Сессия в других сервисах Nubes сохранится.</p>'
|
||||
+ '<a href="/v2/logout?confirm=yes" class="btn btn-yes">Выйти</a>'
|
||||
+ '<a href="/v2/app" class="btn btn-no">Отмена</a>'
|
||||
+ '</body></html>');
|
||||
});
|
||||
|
||||
return router;
|
||||
|
||||
+17
-7
@@ -1,18 +1,27 @@
|
||||
const https = require('https');
|
||||
const config = require('../config');
|
||||
|
||||
function resolveContext(req, res, next) {
|
||||
/**
|
||||
* Express middleware — извлекает контекст из сессии.
|
||||
* При 401 от IAM — чистит сессию и редиректит на логин (SSO).
|
||||
*/
|
||||
async function resolveContext(req, res, next) {
|
||||
const u = req.session && req.session.user;
|
||||
|
||||
// Нет сессии — нет доступа
|
||||
if (!u) return res.redirect('/v2/login');
|
||||
|
||||
// ── Проверка имперсонации через IAM ──────────────────────────────
|
||||
// Если есть токен — запрашиваем свежие данные, чтобы обнаружить имперсонацию
|
||||
// которая могла начаться после логина (ЛК сессия отдельно от whitelist).
|
||||
const token = req.session && req.session.token;
|
||||
if (token) {
|
||||
checkImpersonation(token, req, u).catch(() => { /* fallback — тихо */ });
|
||||
try {
|
||||
await checkImpersonation(token, req, u);
|
||||
} catch (e) {
|
||||
const msg = (e.message || '');
|
||||
// 401 — токен протух, чистим сессию → SSO даст свежий
|
||||
if (msg.startsWith('401')) {
|
||||
req.session.destroy(() => res.redirect('/v2/login'));
|
||||
return;
|
||||
}
|
||||
// Другие ошибки IAM — игнорируем, используем старые данные
|
||||
}
|
||||
}
|
||||
|
||||
applyContext(req, u);
|
||||
@@ -21,6 +30,7 @@ function resolveContext(req, res, next) {
|
||||
|
||||
/**
|
||||
* Запрашивает IAM и обновляет сессию если имперсонация изменилась.
|
||||
* Бросает ошибку при 401 — вызывающий обработает.
|
||||
*/
|
||||
async function checkImpersonation(token, req, u) {
|
||||
const url = config.iamApiBase + '/auth/user';
|
||||
|
||||
Reference in New Issue
Block a user