fix: encode OAuth mode in state parameter — survives session loss
Before: oauth_mode stored only in request.session cookie
→ Lost if session expires during Gitea OAuth redirect
→ Link mode falls through to login mode → creates gitea_bruno user
After: state format: <random>:<mode> (e.g., abc123:link)
→ Mode survives session cookie loss
→ Link mode correctly links to current local user
This commit is contained in:
+13
-4
@@ -117,11 +117,13 @@ async def login(request: Request, provider: str = Query("gitea")):
|
||||
request.session["oauth_provider"] = provider
|
||||
# Link mode: connect OAuth to current local account instead of creating new user
|
||||
mode = request.query_params.get("mode", "")
|
||||
# Encode auth mode in state to survive session loss during OAuth redirect
|
||||
signed_state = f"{state}:{mode}" if mode else state
|
||||
request.session["oauth_mode"] = mode
|
||||
# Dynamic redirect URI based on incoming Host header
|
||||
host = request.headers.get("host", "localhost:8080")
|
||||
dynamic_redirect_uri = f"http://{host}/auth/callback"
|
||||
auth_url = oauth_provider.get_authorize_url(state, redirect_uri=dynamic_redirect_uri, force_login=(mode == "link"))
|
||||
auth_url = oauth_provider.get_authorize_url(signed_state, redirect_uri=dynamic_redirect_uri, force_login=(mode == "link"))
|
||||
return RedirectResponse(url=auth_url, status_code=302)
|
||||
|
||||
|
||||
@@ -232,11 +234,18 @@ async def callback(
|
||||
state: str = Query(...),
|
||||
):
|
||||
"""Handle OAuth2 callback from Gitea."""
|
||||
# Validate state
|
||||
# Validate state — try exact match first, then with mode suffix
|
||||
expected_state = request.session.get("oauth_state", "")
|
||||
provider_name = request.session.get("oauth_provider", "gitea")
|
||||
if not expected_state or state != expected_state:
|
||||
return HTMLResponse("<h1>Invalid state</h1>", status_code=400)
|
||||
if not expected_state:
|
||||
return HTMLResponse("<h1>Invalid state: session expired</h1>", 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("<h1>Invalid state</h1>", status_code=400)
|
||||
|
||||
from app.auth.providers import get_provider
|
||||
oauth_provider = get_provider(provider_name)
|
||||
|
||||
Reference in New Issue
Block a user