Observed (production, 2026-10-01)
/finalize_login on campus-profile (development) returned Internal Server Error with:
File "campus_python/auth/v1/__init__.py", line 167, in finalize
target = self.sessions[auth_session.id].finalize()
File "campus_python/auth/v1/sessions.py", line 106, in finalize
del flask.session[cast(CampusSessions, self.parent)._session_key]
KeyError: '_session_id'
Root cause
CampusSessions.Session.finalize() assumes the flask session key is always present and uses a bare del (campus_python/auth/v1/sessions.py:106). The key can legitimately be absent — e.g. the browser's session cookie is lost/stripped between /login and the OAuth callback (observed on mobile Safari), or a replayed callback after push_context already cleaned the stale key.
Related smell in the same class (campus_python/auth/v1/sessions.py):
path = f"sessions/{PROVIDER}/" # trailing slash
@property
def _session_key(self) -> str:
provider = self.path.split("/")[-1] # -> "" (no rstrip!)
return f"{provider}_session_id" # -> "_session_id"
logins.py does this correctly (self.path.rstrip("/").split("/")[-1] → logins_login_id); sessions yields the odd _session_id.
Suggested fix
Cross-references
Observed (production, 2026-10-01)
/finalize_loginon campus-profile (development) returned Internal Server Error with:Root cause
CampusSessions.Session.finalize()assumes the flask session key is always present and uses a baredel(campus_python/auth/v1/sessions.py:106). The key can legitimately be absent — e.g. the browser's session cookie is lost/stripped between/loginand the OAuth callback (observed on mobile Safari), or a replayed callback afterpush_contextalready cleaned the stale key.Related smell in the same class (
campus_python/auth/v1/sessions.py):logins.pydoes this correctly (self.path.rstrip("/").split("/")[-1]→logins_login_id); sessions yields the odd_session_id.Suggested fix
flask.session.pop(key, None)instead ofdel(mirrors the intent of fix: make auth logout() best-effort and never fail local logout #58: local cleanup must never fail the flow)._session_keyderivation withlogins.py(note: existing live cookies carry_session_id; either migrate or read both keys).Cross-references
/finalize_login).logout()best-effort; finalize's local cleanup deserves the same treatment.