diff --git a/navi/api/websocket.py b/navi/api/websocket.py index b84da02..eef286d 100644 --- a/navi/api/websocket.py +++ b/navi/api/websocket.py @@ -194,19 +194,27 @@ websocket: WebSocket, user: Annotated[User | None, Depends(get_current_user_ws)] = None, ) -> None: + log.info("ws.handler_enter", session_id=session_id, user_id=user.id if user else None) session_store = get_session_store() session = await session_store.get(session_id) if session is None: + log.warning("ws.session_not_found", session_id=session_id) await websocket.close(code=4004, reason="Session not found") return + log.info("ws.session_found", session_id=session_id, session_user_id=session.user_id) + if user is not None: try: check_session_access(session, user) + log.info("ws.access_granted", session_id=session_id, user_id=user.id) except Exception: + log.warning("ws.access_denied", session_id=session_id, user_id=user.id) await websocket.close(code=4003, reason="Access denied") return + else: + log.info("ws.anonymous_user", session_id=session_id) await websocket.accept() log.info("ws.connected", session_id=session_id) diff --git a/navi/auth/deps.py b/navi/auth/deps.py index fff4936..f485008 100644 --- a/navi/auth/deps.py +++ b/navi/auth/deps.py @@ -23,34 +23,45 @@ crashing WebSocket upgrades or other dependency-injected paths. """ try: + log.debug("auth.resolve_start", conn_type=type(conn).__name__) if conn is None: + log.debug("auth.resolve_no_conn") return None # Return cached user if already resolved this request if hasattr(conn.state, "user") and conn.state.user is not None: + log.debug("auth.resolve_cached", user_id=conn.state.user.id) return conn.state.user # Auth not configured — treat as anonymous if not settings.gnauth_client_id or not settings.gnauth_client_secret: + log.debug("auth.resolve_not_configured") return None cookie_name = settings.navi_auth_cookie_name session_id = conn.cookies.get(cookie_name) if not session_id: + log.debug("auth.resolve_no_cookie", cookie_name=cookie_name) return None + log.debug("auth.resolve_cookie_found", session_id=session_id[:8]) + # Look up the auth session in DB try: from navi.api.deps import get_session_store except Exception: # Avoid circular import during early bootstrap + log.debug("auth.resolve_store_import_failed") return None store = get_session_store() row = await _get_auth_session(store, session_id) if row is None: + log.debug("auth.resolve_session_not_found", session_id=session_id[:8]) return None + log.debug("auth.resolve_session_found", user_id=row["user_id"]) + encryptor = get_encryptor() access_token = encryptor.decrypt(row["access_token_enc"]) expires_at = row["expires_at"] @@ -85,6 +96,8 @@ log.warning("auth.fetch_user_failed", session_id=session_id[:8]) return None + log.debug("auth.resolve_user_fetched", user_id=auth_user.user_id) + # Determine role from client-level role_ids role = "user" permissions: list[str] = [] @@ -110,6 +123,7 @@ # Update last_used_at await _touch_auth_session(store, session_id) conn.state.user = user + log.debug("auth.resolve_success", user_id=user.id) return user except Exception: # Any unexpected failure during auth resolution should not crash the diff --git a/navi/core/pg_session_store.py b/navi/core/pg_session_store.py index 8b89351..dcbe4a5 100644 --- a/navi/core/pg_session_store.py +++ b/navi/core/pg_session_store.py @@ -121,7 +121,7 @@ if not is_admin and user_id is not None: rows = await conn.fetch( "SELECT id, profile_id, user_id, messages, context, pinned, created_at, last_active, context_token_count, name, planning_logs " - "FROM sessions WHERE user_id = $1 OR user_id IS NULL ORDER BY pinned DESC, last_active DESC", + "FROM sessions WHERE user_id = $1 ORDER BY pinned DESC, last_active DESC", user_id, ) else: @@ -153,7 +153,7 @@ return f"${param_idx}" if not is_admin and user_id is not None: - conditions.append(f"(user_id = {add_param(user_id)} OR user_id IS NULL)") + conditions.append(f"user_id = {add_param(user_id)}") if profile_id: conditions.append(f"profile_id = {add_param(profile_id)}")