fix(auth): honor CODEFRAME_AUTH_REQUIRED in WebSocket auth (#676)#693
Conversation
REST auth honored the no-auth opt-out (require_auth returns a synthetic local principal) but the terminal and session-chat WebSockets always demanded a ?token= JWT and closed the socket, so the sessions UI and embedded terminal were unusable when CODEFRAME_AUTH_REQUIRED=false. Collapse both near-identical WS auth helpers into a single shared authenticate_websocket() in auth/dependencies.py that consults auth_required() first — the same source of truth REST uses — so the two surfaces can no longer drift. In no-auth mode it returns (True, None); otherwise it validates the JWT and closes with the caller's code (4001 terminal / 1008 chat). Terminal ownership is skipped when user_id is None and the per-user terminal counter buckets no-auth terminals together. Tests: shared-helper unit tests, terminal + chat connect without a token in no-auth mode, and both still reject missing/invalid tokens when auth is enabled. Closes #676
|
Warning Review limit reached
More reviews will be available in 1 hour, 18 minutes, and 58 seconds. Learn how PR review limits work. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Code Review - fix(auth): honor CODEFRAME_AUTH_REQUIRED in WebSocket auth (related to #676)Summary: This is a clean, well-scoped fix. It addresses a real usability bug (WS connections unusable in no-auth local-dev mode), eliminates duplicated JWT logic, and establishes a single source of truth for WS auth that cannot drift from REST again. The approach is solid. What Works Well
One Implementation Concern: Lazy ImportsThe import jwt as pyjwt
from sqlalchemy import select
from codeframe.auth import manager
from codeframe.auth.manager import JWT_ALGORITHM, JWT_AUDIENCE, get_async_session_makerThe accompanying comment says: "Read manager.SECRET live: it may be refreshed from .env at server startup (after import), so binding the value at import would stale it." That comment is correct, but it only justifies using Recommendation: move all four imports to the top of This is a minor style point, not a correctness issue. Minor Notes
VerdictApprove with the lazy-import cleanup as a suggestion (not a blocker). The fix is correct, well-tested, and follows the project's existing auth patterns. The structural improvement (single source of truth) is a clear win over the pre-existing duplicated logic. |
|
Thanks for the review. On the lazy-import suggestion — I kept the imports deferred inside Also flagging for the record: the CodeRabbit check reported Docs synced: QUICKSTART no longer claims |
|
Follow-up on lazy imports (response to frankbria's note): That's a fair point — I missed that No outstanding blockers. My approval stands — the fix is correct, well-tested, and the structural improvement (single source of truth) is solid. |
Summary
Fixes #676.
CODEFRAME_AUTH_REQUIRED=false(the documented local no-auth opt-out) disabled REST auth but the terminal and session-chat WebSockets still demanded a?token=JWT and closed the connection — so the sessions UI and embedded terminal were unusable in no-auth mode. The two WS auth helpers also duplicated the JWT-validation logic, which is how they drifted from REST in the first place.Approach (issue's preferred option 1)
Collapse both near-identical WS auth helpers into a single shared
authenticate_websocket()incodeframe/auth/dependencies.pythat consultsauth_required()first — the same source of truthrequire_auth()uses for REST:(True, None)without requiring a token, mirroring REST's synthetic local principal (user_id=None).?token=JWT exactly as before (decode → subject → active DB user) and closes with the caller's existing code (4001terminal /1008chat) on any failure.The per-router
_authenticate_websocketwrappers remain (now thin delegates) and just pass their close code. Downstream, the terminal ownership check is skipped whenuser_id is None, and the per-user terminal counter buckets no-auth terminals together.Acceptance criteria
auth_required()); duplicated JWT logic removed.Testing
tests/auth/test_websocket_auth.py(new): shared-helper unit tests — no-auth pass-through(True, None), auth-on missing/invalid token closes with the caller's code.tests/ui/test_terminal_ws.py: added no-auth connect test; existing auth-rejection tests pinned toCODEFRAME_AUTH_REQUIRED=true; mock contract updated to(ok, user_id).tests/api/test_session_chat_ws.py: added no-auth connect test; rejection tests pinned to auth-on.tests/auth/,test_v2_auth_enforcement.py,test_security_config.pygreen; ruff clean.Cross-family review:
codex review --base mainfound no introduced bugs.Known limitations
Nonebucket), and session ownership is not enforced — symmetric with REST, which has no identity to enforce when auth is disabled.