bump 0.5.37 — debug /debug-session endpoint
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ipwhitelist",
|
"name": "ipwhitelist",
|
||||||
"version": "0.5.36",
|
"version": "0.5.37",
|
||||||
"description": "IP WhiteList microservice for cloud provider",
|
"description": "IP WhiteList microservice for cloud provider",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -79,6 +79,18 @@ async function start() {
|
|||||||
// k8s liveness probe — простая проверка что сервер жив
|
// k8s liveness probe — простая проверка что сервер жив
|
||||||
app.get('/healthz', (req, res) => res.send('OK'));
|
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-режиме)
|
// JWKS endpoint для валидации токенов внешними сервисами (только в mock-режиме)
|
||||||
if (auth.jwksHandler) app.get('/.well-known/jwks.json', auth.jwksHandler);
|
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