From 1cd11b9a2d1903763d42e9277e1ebe6bb6464716 Mon Sep 17 00:00:00 2001 From: giles Date: Mon, 23 Feb 2026 12:41:15 +0000 Subject: [PATCH] Skip dead apps in login propagation chain Health-check each app via internal URL before redirecting. Dead apps are silently skipped so the chain doesn't break. Co-Authored-By: Claude Opus 4.6 --- bp/auth/routes.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/bp/auth/routes.py b/bp/auth/routes.py index 09b3266..a497bb2 100644 --- a/bp/auth/routes.py +++ b/bp/auth/routes.py @@ -242,22 +242,44 @@ def register(url_prefix="/auth"): """Chain through each client app's OAuth login to propagate the account session. Each app does its OAuth flow (instant since account is already logged in) then redirects back here. When - the chain is empty, redirect to the original target.""" + the chain is empty, redirect to the original target. + Dead apps are skipped via internal health check.""" + import os, aiohttp + from urllib.parse import quote + chain = qsession.get("sso_chain", []) final = qsession.get("sso_final", account_url("/")) - if not chain or not g.get("user"): + if not g.get("user"): qsession.pop("sso_chain", None) qsession.pop("sso_final", None) return redirect(final) - next_app = chain.pop(0) - qsession["sso_chain"] = chain - - from urllib.parse import quote comeback = account_url("/auth/propagate") - login_url = app_url(next_app, f"/auth/login?next={quote(comeback, safe='')}") - return redirect(login_url) + + while chain: + next_app = chain.pop(0) + qsession["sso_chain"] = chain + + # Health check via internal URL before redirecting + internal = (os.getenv(f"INTERNAL_URL_{next_app.upper()}") or f"http://{next_app}:8000").rstrip("/") + try: + async with aiohttp.ClientSession() as http: + async with http.head( + internal, + timeout=aiohttp.ClientTimeout(total=2), + allow_redirects=True, + ) as resp: + if resp.status < 500: + login = app_url(next_app, f"/auth/login?next={quote(comeback, safe='')}") + return redirect(login) + except Exception: + current_app.logger.warning("[propagate] skipping dead app: %s", next_app) + continue + + qsession.pop("sso_chain", None) + qsession.pop("sso_final", None) + return redirect(final) @auth_bp.post("/logout/") async def logout():