fix: asyncio lock on callback to prevent concurrent duplicate code exchange
All checks were successful
Build and Deploy / deploy (push) Successful in 1m2s

This commit is contained in:
Henrik Jess Nielsen
2026-05-23 01:10:38 +02:00
parent c3bc6a48a0
commit 1471d0f67f

View File

@@ -40,7 +40,10 @@ def _session(request: Request) -> dict:
# Server-side token store — keeps JWTs OUT of the session cookie # Server-side token store — keeps JWTs OUT of the session cookie
# (cookie limit is 4KB; two JWTs alone are ~1.3KB before base64 overhead) # (cookie limit is 4KB; two JWTs alone are ~1.3KB before base64 overhead)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
import asyncio
_token_store: dict[str, dict] = {} # sid → {"app_token": str, "user_token": str} _token_store: dict[str, dict] = {} # sid → {"app_token": str, "user_token": str}
_callback_locks: dict[str, asyncio.Lock] = {} # sid → Lock (prevents concurrent code exchange)
def _get_sid(sess: dict) -> str: def _get_sid(sess: dict) -> str:
@@ -402,8 +405,10 @@ async def tink_callback(request: Request, code: Optional[str] = None,
print(f"[CALLBACK] Tink returned error: {error}") print(f"[CALLBACK] Tink returned error: {error}")
return RedirectResponse(f"/demo/step/3?error={error}") return RedirectResponse(f"/demo/step/3?error={error}")
if code: if code:
# Guard: if we already have a user_token for this session, the code was sid = sess.get("sid", "unknown")
# already exchanged (duplicate callback from Traefik during rolling deploy). if sid not in _callback_locks:
_callback_locks[sid] = asyncio.Lock()
async with _callback_locks[sid]:
if _load_token(sess, "user_token"): if _load_token(sess, "user_token"):
print(f"[CALLBACK] Already have user_token — skipping duplicate exchange") print(f"[CALLBACK] Already have user_token — skipping duplicate exchange")
return RedirectResponse("/demo/step/3?cb_success=1", status_code=303) return RedirectResponse("/demo/step/3?cb_success=1", status_code=303)