You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As of frontend 14dbb73 (#36), apiRequest clears the session and redirects to /login on any non-exempt 401 or 403. That conflates two different answers: "your session is dead" and "you may not do this thing". RoleChecker denials ("Operation not permitted"), inactive users, and the new creator-or-admin check on annotation deletes (#214, backend PR #49) are all healthy-session 403s — and each one now logs the operator out mid-task instead of showing an inline error.
Correction (2026-07-22): an earlier version of this issue claimed the 403 teardown branch was "necessary today" because FastAPI's auto-error HTTPBearer surfaces a missing or malformed Authorization header as 403. Verified empirically against the pinned stack (FastAPI 0.139.0, per #48): missing and malformed headers both return 401, not 403 — the 403-on-missing-credentials behaviour belongs to older FastAPI versions. On our stack the 403 branch protects nothing; it only causes the spurious logouts.
Evidence:frontend/src/lib/api.ts:86-96 (401 or 403 → clearSession() + goto('/login')); backend/app/api/auth.py (RoleChecker → 403 with a live session); #214's 403 on the annotations DELETE; probe against the dev backend: no Authorization header → 401, malformed header → 401 on the auto-error bearer.
Fix (a coordinated pair; landing order immaterial on the pinned stack, since every missing/invalid-credential path already returns 401):
Frontend — the load-bearing half (PR Wiring #38): restrict the teardown branch to 401; 403 falls through to a plain catchable error that components display inline (the annotation panel already renders these).
Backend — hardening (PR Authentication & secrets (backend) #50): replace the auto-error bearer with an optional bearer plus explicit HTTPException(401, "Not authenticated"), so the status is chosen by our code rather than a framework default that has varied across FastAPI versions.
Acceptance. An expired, invalid, or missing token still redirects to login from any screen; a forbidden action (role denial, creator-or-admin denial) shows an inline error with the session intact; backend PR #49 (#214) unblocks.
As of frontend
14dbb73(#36),apiRequestclears the session and redirects to/loginon any non-exempt 401 or 403. That conflates two different answers: "your session is dead" and "you may not do this thing".RoleCheckerdenials ("Operation not permitted"), inactive users, and the new creator-or-admin check on annotation deletes (#214, backend PR #49) are all healthy-session 403s — and each one now logs the operator out mid-task instead of showing an inline error.Correction (2026-07-22): an earlier version of this issue claimed the 403 teardown branch was "necessary today" because FastAPI's auto-error
HTTPBearersurfaces a missing or malformed Authorization header as 403. Verified empirically against the pinned stack (FastAPI 0.139.0, per #48): missing and malformed headers both return 401, not 403 — the 403-on-missing-credentials behaviour belongs to older FastAPI versions. On our stack the 403 branch protects nothing; it only causes the spurious logouts.Evidence:
frontend/src/lib/api.ts:86-96(401 or 403 →clearSession()+goto('/login'));backend/app/api/auth.py(RoleChecker→ 403 with a live session); #214's 403 on the annotations DELETE; probe against the dev backend: no Authorization header → 401, malformed header → 401 on the auto-error bearer.Fix (a coordinated pair; landing order immaterial on the pinned stack, since every missing/invalid-credential path already returns 401):
HTTPException(401, "Not authenticated"), so the status is chosen by our code rather than a framework default that has varied across FastAPI versions.Acceptance. An expired, invalid, or missing token still redirects to login from any screen; a forbidden action (role denial, creator-or-admin denial) shows an inline error with the session intact; backend PR #49 (#214) unblocks.