bump 0.5.37 — debug /debug-session endpoint
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ipwhitelist",
|
||||
"version": "0.5.36",
|
||||
"version": "0.5.37",
|
||||
"description": "IP WhiteList microservice for cloud provider",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -79,6 +79,18 @@ async function start() {
|
||||
// k8s liveness probe — простая проверка что сервер жив
|
||||
app.get('/healthz', (req, res) => res.send('OK'));
|
||||
|
||||
// 🔧 DEBUG: показать IAM-данные из сессии (убрать после теста!)
|
||||
app.get('/debug-session', (req, res) => {
|
||||
if (!req.session || !req.session.user) return res.json({ error: 'no session' });
|
||||
const { token, idToken, ...safe } = req.session;
|
||||
res.json({
|
||||
user: req.session.user,
|
||||
hasToken: !!token,
|
||||
tokenPreview: token ? token.slice(0, 50) + '...' : null,
|
||||
tokenPayload: token ? (() => { try { const p = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString()); return { iss: p.iss, sub: p.sub, email: p.email, ClientID: p.ClientID, iat: p.iat }; } catch(e) { return 'decode error'; } })() : null,
|
||||
});
|
||||
});
|
||||
|
||||
// JWKS endpoint для валидации токенов внешними сервисами (только в mock-режиме)
|
||||
if (auth.jwksHandler) app.get('/.well-known/jwks.json', auth.jwksHandler);
|
||||
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
tests/ui-sso.py — E2E браузерный тест SSO через Playwright.
|
||||
|
||||
Проверяет полный поток:
|
||||
1. Редирект на Keycloak
|
||||
2. Вход с логином/паролем
|
||||
3. Возврат в приложение
|
||||
4. CRUD через UI
|
||||
5. Logout
|
||||
|
||||
Запуск:
|
||||
pip install playwright && playwright install chromium
|
||||
KC_LOGIN=tazet@narod.ru KC_PASS=*** python tests/ui-sso.py
|
||||
|
||||
Если Keycloak запрашивает OTP/MFA код — тест запросит его через stdin.
|
||||
"""
|
||||
|
||||
import os, sys, time
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
APP_URL = os.environ.get("APP_URL", "https://whitelist.nodejsk8s.services.ngcloud.ru")
|
||||
KC_LOGIN = os.environ.get("KC_LOGIN")
|
||||
KC_PASS = os.environ.get("KC_PASS")
|
||||
|
||||
if not KC_LOGIN or not KC_PASS:
|
||||
print("❌ Задай KC_LOGIN и KC_PASS")
|
||||
print(" KC_LOGIN=tazet@narod.ru KC_PASS=*** python tests/ui-sso.py")
|
||||
sys.exit(1)
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
def ok(name, condition, details=""):
|
||||
global passed, failed
|
||||
if condition:
|
||||
passed += 1
|
||||
print(f" ✅ {name}")
|
||||
else:
|
||||
failed += 1
|
||||
print(f" ❌ {name} — {details}")
|
||||
|
||||
def run():
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=False)
|
||||
page = browser.new_page()
|
||||
|
||||
print(f"\n🧪 SSO E2E браузерный тест")
|
||||
print(f" App: {APP_URL}")
|
||||
print(f" User: {KC_LOGIN}\n")
|
||||
|
||||
# ── 1. Заходим на / → редирект на Keycloak ──
|
||||
page.goto(f"{APP_URL}/")
|
||||
page.wait_for_timeout(2000)
|
||||
ok("1. Редирект на Keycloak", "keycloak.nubes.ru" in page.url)
|
||||
|
||||
# ── 2. Логин в Keycloak ──
|
||||
if page.locator("input[name=username], #username").count() > 0:
|
||||
page.fill("input[name=username], #username", KC_LOGIN)
|
||||
page.fill("input[name=password], #password", KC_PASS)
|
||||
page.click("button[type=submit], input[type=submit], button[name=login]")
|
||||
page.wait_for_timeout(3000)
|
||||
|
||||
# Проверяем OTP/MFA
|
||||
if page.locator("input[name=otp], input[name=totp]").count() > 0:
|
||||
otp = input("🔐 Введи код OTP/MFA: ").strip()
|
||||
page.fill("input[name=otp], input[name=totp]", otp)
|
||||
page.click("button[type=submit], input[type=submit]")
|
||||
page.wait_for_timeout(3000)
|
||||
|
||||
ok("2. Вход в Keycloak", APP_URL in page.url or "ipwhitelist" in page.url.lower() or "WhiteList" in page.title())
|
||||
else:
|
||||
ok("2. Вход в Keycloak", False, "no login form")
|
||||
|
||||
# ── 3. Главная страница ──
|
||||
page.wait_for_timeout(2000)
|
||||
ok("3. Главная страница загружена", "Белые списки" in page.content() or "WhiteList" in page.title() or page.url.startswith(APP_URL))
|
||||
|
||||
# ── 4. Добавляем запись ──
|
||||
ts = int(time.time())
|
||||
cidr = f"10.{ts % 200}.{ts % 100}.{ts % 50}"
|
||||
page.fill("input[name=value]", cidr)
|
||||
page.fill("textarea[name=comment], input[name=comment]", f"test-{ts}")
|
||||
page.click("button[type=submit]:has-text('Добавить'), button:has-text('Добавить')")
|
||||
page.wait_for_timeout(2000)
|
||||
ok("4. Запись добавлена", cidr in page.content())
|
||||
|
||||
# ── 5. Удаляем запись ──
|
||||
delete_btn = page.locator(f"tr:has-text('{cidr}') button:has-text('Удалить'), tr:has-text('{cidr}') a:has-text('Удалить')").first
|
||||
if delete_btn.count() > 0:
|
||||
delete_btn.click()
|
||||
page.wait_for_timeout(1000)
|
||||
page.click("button:has-text('OK'), button:has-text('Да'), button:has-text('Удалить')")
|
||||
page.wait_for_timeout(2000)
|
||||
ok("5. Запись удалена", cidr not in page.content())
|
||||
else:
|
||||
ok("5. Запись удалена", False, "delete button not found")
|
||||
|
||||
# ── 6. Export ──
|
||||
page.goto(f"{APP_URL}/export")
|
||||
page.wait_for_timeout(1000)
|
||||
ok("6. Export доступен", "text/plain" in page.content() or page.url.endswith("/export"))
|
||||
|
||||
# ── 7. Logout ──
|
||||
page.goto(f"{APP_URL}/logout")
|
||||
page.wait_for_timeout(3000)
|
||||
ok("7. Logout → /login", "/login" in page.url or "keycloak" in page.url.lower())
|
||||
|
||||
# ── Итог ──
|
||||
print(f"\n═══ ИТОГ ═══")
|
||||
print(f" ✅ {passed} ❌ {failed} из {passed+failed}")
|
||||
|
||||
browser.close()
|
||||
return failed == 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
ok = run()
|
||||
sys.exit(0 if ok else 1)
|
||||
Reference in New Issue
Block a user