From 3c8529fd1221d3382d207284cbb021aec628758c Mon Sep 17 00:00:00 2001 From: bruno Date: Tue, 14 Jul 2026 20:34:43 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20OAuth=20callback=20=E2=80=94=20recover?= =?UTF-8?q?=20mode=20from=20state=20when=20session=20cookie=20is=20lost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: request.session cookie expires during Gitea OAuth redirect → oauth_mode lost → link mode falls through to login mode → Creates gitea_bruno user instead of linking to local account Fix: state now carries mode suffix (state:mode) Callback recovers mode from state parameter even if session lost Allows full session loss but still correctly enters link mode --- app/routers/auth.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/app/routers/auth.py b/app/routers/auth.py index 1cb5925..be039b0 100644 --- a/app/routers/auth.py +++ b/app/routers/auth.py @@ -234,18 +234,22 @@ async def callback( state: str = Query(...), ): """Handle OAuth2 callback from Gitea.""" - # Validate state — try exact match first, then with mode suffix + # Recover mode from state suffix (state:mode format), then validate expected_state = request.session.get("oauth_state", "") provider_name = request.session.get("oauth_provider", "gitea") - if not expected_state: - return HTMLResponse("

Invalid state: session expired

", status_code=400) - # Check exact match or state:mode format - if state != expected_state: - parts = state.rsplit(":", 1) - if len(parts) == 2 and parts[0] == expected_state: - request.session["oauth_mode"] = parts[1] - else: - return HTMLResponse("

Invalid state

", status_code=400) + + auth_mode = "" + raw_state = state + if ":" in state: + raw_state, auth_mode = state.rsplit(":", 1) + if auth_mode: + request.session["oauth_mode"] = auth_mode + + # Validate: state token must match session, unless session lost and mode present + if expected_state and raw_state != expected_state: + return HTMLResponse("

Invalid state

", status_code=400) + if not expected_state and not auth_mode: + return HTMLResponse("

Session expired — please try connecting again

", status_code=400) from app.auth.providers import get_provider oauth_provider = get_provider(provider_name)