[eric] cycles: move OAuth flow state into a leaf module

This commit is contained in:
ciregenz
2026-05-23 07:05:25 -07:00
parent 7916babdcc
commit 26b9239c8a
3 changed files with 49 additions and 50 deletions
+25 -32
View File
@@ -11,6 +11,7 @@ import os
import httpx
from .process import NINE_ROUTER_API, NINE_ROUTER_PORT, NINE_ROUTER_V1
from backend.apps.oauth_state import _pending_oauth, _mark_oauth_completed
logger = logging.getLogger(__name__)
@@ -108,38 +109,30 @@ async def _start_codex_callback_listener(timeout: float = 300.0) -> asyncio.base
code = (q.get("code") or [""])[0]
state = (q.get("state") or [""])[0]
if code and state:
try:
from backend.main import _pending_oauth, _mark_oauth_completed
except Exception:
_pending_oauth = None
_mark_oauth_completed = None
if _pending_oauth is not None:
pending = _pending_oauth.pop(state, None)
if pending:
try:
await exchange_oauth(
pending["provider"],
code,
pending["redirect_uri"],
pending["code_verifier"],
state,
)
if _mark_oauth_completed is not None:
_mark_oauth_completed(state)
logger.info(
f"Codex callback: server-side exchange succeeded for state {state[:8]}..."
)
except Exception as e:
# Put the pending entry back so the
# frontend's msgHandler retry via
# /agents/subscriptions/exchange still
# has a shot. Safe because we only popped
# it a moment ago.
_pending_oauth[state] = pending
logger.debug(
f"Codex callback: server-side exchange failed ({e}); leaving for frontend retry"
)
pending = _pending_oauth.pop(state, None)
if pending:
try:
await exchange_oauth(
pending["provider"],
code,
pending["redirect_uri"],
pending["code_verifier"],
state,
)
_mark_oauth_completed(state)
logger.info(
f"Codex callback: server-side exchange succeeded for state {state[:8]}..."
)
except Exception as e:
# Put the pending entry back so the
# frontend's msgHandler retry via
# /agents/subscriptions/exchange still
# has a shot. Safe because we only popped
# it a moment ago.
_pending_oauth[state] = pending
logger.debug(
f"Codex callback: server-side exchange failed ({e}); leaving for frontend retry"
)
except Exception as e:
logger.debug(f"Codex callback listener pre-exchange error: {e}")
+18
View File
@@ -0,0 +1,18 @@
# In-memory store for pending OAuth flows (state -> {provider, code_verifier, redirect_uri})
_pending_oauth: dict[str, dict] = {}
# Recently-completed OAuth states so the /api/subscriptions/callback handler
# can distinguish a legitimate duplicate callback (browser prefetch, refresh,
# or Google redirect retry after a slow first response) from a truly stale
# request. Bounded FIFO, drops the oldest entries once it grows past
# _MAX_COMPLETED_OAUTH so it can't leak memory.
_completed_oauth: list[str] = []
_MAX_COMPLETED_OAUTH = 64
def _mark_oauth_completed(state: str) -> None:
if state in _completed_oauth:
return
_completed_oauth.append(state)
# Trim head if we've outgrown the bound
while len(_completed_oauth) > _MAX_COMPLETED_OAUTH:
_completed_oauth.pop(0)
+6 -18
View File
@@ -9,24 +9,12 @@ logger = logging.getLogger(__name__)
from fastapi.responses import JSONResponse, HTMLResponse
from fastapi import Request
# In-memory store for pending OAuth flows (state -> {provider, code_verifier, redirect_uri})
_pending_oauth: dict[str, dict] = {}
# Recently-completed OAuth states so the /api/subscriptions/callback handler
# can distinguish a legitimate duplicate callback (browser prefetch, refresh,
# or Google redirect retry after a slow first response) from a truly stale
# request. Bounded FIFO, drops the oldest entries once it grows past
# _MAX_COMPLETED_OAUTH so it can't leak memory.
_completed_oauth: list[str] = []
_MAX_COMPLETED_OAUTH = 64
def _mark_oauth_completed(state: str) -> None:
if state in _completed_oauth:
return
_completed_oauth.append(state)
# Trim head if we've outgrown the bound
while len(_completed_oauth) > _MAX_COMPLETED_OAUTH:
_completed_oauth.pop(0)
from backend.apps.oauth_state import (
_pending_oauth,
_completed_oauth,
_MAX_COMPLETED_OAUTH,
_mark_oauth_completed,
)
from backend.config.Apps import MainApp
from backend.apps.health.health import health
from backend.apps.agents.agents import agents