Add prompt=none to OAuth authorize, remove propagation chain
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 53s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 53s
Account's authorize endpoint now supports prompt=none: returns error=login_required redirect when user isn't logged in instead of bouncing to interactive login. Removed /propagate endpoint since client apps now detect auth state via prompt=none handshake. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,7 @@ def register(url_prefix="/auth"):
|
|||||||
redirect_uri = request.args.get("redirect_uri", "")
|
redirect_uri = request.args.get("redirect_uri", "")
|
||||||
state = request.args.get("state", "")
|
state = request.args.get("state", "")
|
||||||
device_id = request.args.get("device_id", "")
|
device_id = request.args.get("device_id", "")
|
||||||
|
prompt = request.args.get("prompt", "")
|
||||||
|
|
||||||
if client_id not in ALLOWED_CLIENTS:
|
if client_id not in ALLOWED_CLIENTS:
|
||||||
return "Invalid client_id", 400
|
return "Invalid client_id", 400
|
||||||
@@ -66,8 +67,12 @@ def register(url_prefix="/auth"):
|
|||||||
if redirect_uri != expected_redirect:
|
if redirect_uri != expected_redirect:
|
||||||
return "Invalid redirect_uri", 400
|
return "Invalid redirect_uri", 400
|
||||||
|
|
||||||
# Not logged in — bounce to magic link login, then back here
|
# Not logged in
|
||||||
if not g.get("user"):
|
if not g.get("user"):
|
||||||
|
if prompt == "none":
|
||||||
|
# Silent check — no interactive login, return error
|
||||||
|
sep = "&" if "?" in redirect_uri else "?"
|
||||||
|
return redirect(f"{redirect_uri}{sep}error=login_required&state={state}")
|
||||||
authorize_path = request.full_path
|
authorize_path = request.full_path
|
||||||
store_login_redirect_target()
|
store_login_redirect_target()
|
||||||
return redirect(url_for("auth.login_form", next=authorize_path))
|
return redirect(url_for("auth.login_form", next=authorize_path))
|
||||||
@@ -271,52 +276,8 @@ 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()
|
||||||
qsession["sso_final"] = redirect_url
|
return redirect(redirect_url, 303)
|
||||||
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():
|
||||||
|
|||||||
Reference in New Issue
Block a user