odysseus-dev/odysseus · error · HTTPException

Not authenticated

Error message

Not authenticated

What it means

Raised as HTTP 401 by POST /change-password when _get_current_user(request) returns falsy, i.e. the request carries no valid session cookie. _get_current_user resolves the SESSION_COOKIE against the auth manager's session store; missing, expired, revoked, or unknown tokens all yield None. Password-change is a privileged operation, so an unauthenticated request is rejected before any validation.

Source

Thrown at routes/auth_routes.py:204

        # set merged with DEFAULT_PRIVILEGES.
        try:
            u = result.get("username")
            if u:
                result["privileges"] = auth_manager.get_privileges(u)
        except Exception:
            pass
        return result

    @router.get("/policy")
    async def auth_policy():
        """Return public auth policy constants for the frontend."""
        return auth_manager.policy()

    @router.post("/change-password")
    async def change_password(body: ChangePasswordRequest, request: Request):
        user = _get_current_user(request)
        if not user:
            raise HTTPException(401, "Not authenticated")
        if len(body.new_password) < PASSWORD_MIN_LENGTH:
            raise HTTPException(400, f"Password must be at least {PASSWORD_MIN_LENGTH} characters")
        current_token = request.cookies.get(SESSION_COOKIE)
        ok = await asyncio.to_thread(auth_manager.change_password, user, body.current_password, body.new_password)
        if not ok:
            raise HTTPException(400, "Current password is incorrect")
        await asyncio.to_thread(auth_manager.revoke_user_sessions, user, current_token)
        return {"ok": True}

    # ------------------------------------------------------------------
    # Two-factor authentication
    # ------------------------------------------------------------------

    @router.post("/2fa/setup")
    async def totp_setup(request: Request):
        """Generate a TOTP secret and return the QR code URI."""
        user = _get_current_user(request)
        if not user:

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Log in again to obtain a fresh session cookie, then retry the password change.
  2. Ensure the client sends cookies: fetch(url, {credentials: 'include'}) or keeps the cookie jar in curl/tests.
  3. If sessions were revoked (e.g. password changed on another device), re-authenticate on each device.
  4. Have the frontend detect 401 on this route and redirect to the login page with a return URL.

Example fix

// before
await fetch('/change-password', {method:'POST', body: JSON.stringify(payload)});
// after — include the session cookie and handle expiry
const res = await fetch('/change-password', {method:'POST', credentials:'include', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)});
if (res.status === 401) { location.href = '/login?next=/settings'; }
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the session is alive before showing the change-password form
const s = await fetch('/2fa/status', {credentials:'include'});
if (s.status === 401) { location.href = '/login?next=/settings'; return; }

Try / catch

catch (e) { if (e.status === 401) { saveDraft(); location.href = '/login?next=/settings'; } }

Prevention

When it happens

Trigger: Calling /change-password with no session cookie, with a session that expired (past TOKEN_TTL or browser session end), with a token revoked by another password change or logout-all, or with a cookie not sent because the request is cross-site and SameSite=lax blocks it.

Common situations: Session expired while the settings page sat open, cookie lost after password change elsewhere (sessions revoked), testing API endpoints with curl while forgetting the cookie jar, or a frontend fetch missing credentials: 'include'.

Understand the failure class

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/bde97a18a2a890a9. Report an issue: GitHub.