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)