{"record":{"id":"01afc69d34a9d337","repo":"odysseus-dev/odysseus","slug":"authentication-required-01afc6","errorCode":null,"errorMessage":"Authentication required","messagePattern":"Authentication required","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"warning","filePath":"routes/document/document_routes.py","lineNumber":452,"sourceCode":"                \"total\": total,\n                \"languages\": languages,\n                \"session_count\": session_count,\n            }\n        except Exception as e:\n            logger.error(f\"Failed to fetch document library: {e}\")\n            raise HTTPException(500, f\"Failed to fetch document library: {e}\")\n        finally:\n            db.close()\n\n    # ---- GET /api/documents/{session_id} ----\n    @router.get(\"/api/documents/{session_id}\")\n    async def list_documents(request: Request, session_id: str) -> List[Dict[str, Any]]:\n        user = get_current_user(request)\n        db = SessionLocal()\n        try:\n            if not user:\n                if not _auth_disabled():\n                    raise HTTPException(403, \"Authentication required\")\n            # v2 review HIGH-9: raise 403 explicitly when the caller\n            # can't see this session, instead of returning [] which the\n            # UI treats identically to \"no docs\" and silently masks\n            # auth failures.\n            _get_session_or_404(db, session_id, user)\n            q = db.query(Document).filter(\n                Document.session_id == session_id\n            )\n            if user:\n                q = q.filter(or_(Document.owner == user, Document.owner.is_(None)))\n            docs = q.order_by(Document.created_at.desc()).all()\n            return [_doc_to_dict(d) for d in docs]\n        finally:\n            db.close()\n\n    # ---- GET /api/document/{doc_id} ----\n    @router.get(\"/api/document/{doc_id}\")\n    async def get_document(request: Request, doc_id: str) -> Dict[str, Any]:","sourceCodeStart":434,"sourceCodeEnd":470,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L434-L470","documentation":"403 raised by GET /api/documents/{session_id} when get_current_user(request) returns no user and authentication is not disabled (i.e. the app is running with auth enabled). It is an explicit gate: anonymous callers may only list session documents when the deployment intentionally runs auth-free.","triggerScenarios":"Request arrives without valid auth credentials (missing/expired session cookie or token); auth enabled in config while the client assumed open access; a proxy strips the auth header/cookie; the user record was deleted so the token no longer resolves.","commonSituations":"Frontend dev server not forwarding cookies (different port/domain without CORS credentials); token expiry after a long idle period; environment flipped from AUTH_DISABLED=1 to auth-enabled between environments; logged-in user removed from the user store.","solutions":["Send valid credentials with the request (session cookie or auth header) as the app expects.","If this deployment is intentionally auth-free, enable the app's auth-disabled mode so _auth_disabled() returns true.","Check CORS/proxy config so cookies survive the hop (withCredentials, same-site settings).","Re-login to obtain a fresh token if the old one expired or the user was recreated."],"exampleFix":"// before\nfetch(`/api/documents/${sessionId}`)  // no credentials -> 403\n// after\nfetch(`/api/documents/${sessionId}`, { credentials: 'include' })","handlingStrategy":"validation","validationCode":"async function listDocs(sessionId) {\n  if (!auth.currentUser()) return redirect('/login?next=' + encodeURIComponent(location.pathname));\n  return api.get(`/api/documents/${sessionId}`, { credentials: 'include' });\n}","typeGuard":"function hasUser(): boolean { return Boolean(auth.currentUser()); }","tryCatchPattern":"try { await api.get(url); }\ncatch (e) { if (e.status === 403 && e.message === 'Authentication required') { await auth.refreshOrLogin(); return api.get(url, { credentials: 'include' }); } throw e; }","preventionTips":["Attach credentials to every API call","Configure CORS with credentials and correct origin","Handle token expiry with a re-login flow before the request, not after"],"tags":["authentication","http-403","fastapi","authorization"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}