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)