Re-add propagation chain for initial login
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 49s

Device cookies handle subsequent auth changes (logout/re-login),
but the initial login needs the chain to create grants on each app
and link them to device cookies. Dead apps skipped via health check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-23 13:28:03 +00:00
parent c277017ab6
commit 17581a7b75

View File

@@ -271,8 +271,52 @@ def register(url_prefix="/auth"):
# Fresh account session ID for grant tracking # Fresh account session ID for grant tracking
qsession[ACCOUNT_SESSION_KEY] = secrets.token_urlsafe(32) qsession[ACCOUNT_SESSION_KEY] = secrets.token_urlsafe(32)
# Propagate login to all client apps via OAuth chain
redirect_url = pop_login_redirect_target() redirect_url = pop_login_redirect_target()
return redirect(redirect_url, 303) qsession["sso_final"] = redirect_url
qsession["sso_chain"] = list(ALLOWED_CLIENTS)
return redirect(url_for("auth.propagate"), 303)
@auth_bp.get("/propagate")
@auth_bp.get("/propagate/")
async def propagate():
"""Chain through each client app's OAuth login to create grants
and set device cookies. Dead apps are skipped."""
import os, aiohttp
from urllib.parse import quote
chain = qsession.get("sso_chain", [])
final = qsession.get("sso_final", account_url("/"))
if not g.get("user"):
qsession.pop("sso_chain", None)
qsession.pop("sso_final", None)
return redirect(final)
comeback = account_url("/auth/propagate")
while chain:
next_app = chain.pop(0)
qsession["sso_chain"] = chain
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/") @auth_bp.post("/logout/")
async def logout(): async def logout():