Skip to content

fix: auth hardening -- domain exceptions + null user_id safety (OPE-76, OPE-77)#259

Merged
DevanshuNEU merged 4 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:fix/auth-hardening
Feb 24, 2026
Merged

fix: auth hardening -- domain exceptions + null user_id safety (OPE-76, OPE-77)#259
DevanshuNEU merged 4 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:fix/auth-hardening

Conversation

@DevanshuNEU

@DevanshuNEU DevanshuNEU commented Feb 24, 2026

Copy link
Copy Markdown
Collaborator

What

Two auth improvements in one focused PR.

OPE-76: Domain exceptions

Auth service was tightly coupled to FastAPI by raising HTTPException directly. Services should raise domain exceptions; middleware translates them.

Before:

# services/auth.py
raise HTTPException(status_code=401, detail="Token expired")

After:

# services/auth.py
raise TokenExpiredError("Token expired")

# middleware/auth.py (catches and translates)
except TokenExpiredError:
    raise HTTPException(status_code=401, detail="Token expired")

New services/exceptions.py with clean hierarchy:

  • AuthenticationError (base)
    • TokenExpiredError
    • InvalidTokenError
    • TokenMissingClaimError
    • InvalidCredentialsError
    • SignupError
    • SessionError
    • UserIdRequiredError

OPE-77: Null user_id safety

API key users without user_id were hitting get_repo_or_404(repo_id, None) which passed None to the DB and returned confusing 404s.

Now get_repo_or_404 and verify_repo_access accept Optional[str] and return 401 when user_id is None with a clear message.

Changes

File Change
services/exceptions.py NEW -- domain exception hierarchy
services/auth.py Raises domain exceptions instead of HTTPException
middleware/auth.py Catches domain exceptions, translates to HTTP
dependencies.py Null guard on get_repo_or_404 and verify_repo_access
tests/test_auth_hardening.py NEW -- 5 tests for null safety + exception hierarchy

289 tests pass (284 existing + 5 new).

Closes OPE-76, OPE-77

Summary by CodeRabbit

  • Bug Fixes

    • Improved JWT token validation with clearer error messages for expired tokens, invalid claims, and authentication failures.
    • Enhanced API security by enforcing stricter user authentication requirements for sensitive operations.
  • Tests

    • Added comprehensive authentication hardening tests covering edge cases in user authentication and token verification.

…6, OPE-77)

OPE-76: Domain exceptions replace HTTPException in auth service.
- New services/exceptions.py with AuthenticationError hierarchy:
  TokenExpiredError, InvalidTokenError, TokenMissingClaimError,
  InvalidCredentialsError, SignupError, SessionError, UserIdRequiredError.
- auth.py raises domain exceptions, not HTTPException.
- middleware/auth.py catches domain exceptions and translates to HTTP.
  InvalidTokenError returns None (allows API key fallback).
  TokenExpiredError/TokenMissingClaimError raise 401 directly.

OPE-77: Null user_id safety.
- get_repo_or_404 and verify_repo_access accept Optional[str].
- None user_id returns 401 'User ID required' instead of confusing 404.
- Affects all API key auth users without user_id.

Tests: 5 new tests covering null safety + exception hierarchy.
289 tests pass (284 existing + 5 new).

Closes OPE-76, OPE-77
@vercel

vercel Bot commented Feb 24, 2026

Copy link
Copy Markdown

@DevanshuNEU is attempting to deploy a commit to the Dev's projects Team on Vercel.

A member of the Team first needs to authorize it.

@DevanshuNEU

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@DevanshuNEU has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 6 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 25edad1 and 38e0e40.

📒 Files selected for processing (4)
  • backend/dependencies.py
  • backend/middleware/auth.py
  • backend/services/auth.py
  • backend/tests/test_auth_hardening.py
📝 Walkthrough

Walkthrough

This PR introduces a custom exception hierarchy for authentication errors and refactors authentication validation layers to raise domain-specific exceptions instead of HTTPExceptions, while adding explicit null-user_id checks to prevent unauthorized access to repository resources.

Changes

Cohort / File(s) Summary
Exception Framework
backend/services/exceptions.py
New module defining authentication exception hierarchy with base AuthenticationError and specialized subclasses (TokenExpiredError, InvalidTokenError, TokenMissingClaimError, InvalidCredentialsError, SignupError, SessionError, UserIdRequiredError).
JWT Validation & Auth Logic
backend/middleware/auth.py, backend/services/auth.py
Refactored JWT validation to raise custom exceptions on token expiration, missing claims, or invalid tokens; adds fallback logic to API key authentication when token is not JWT or is invalid; updated error handling blocks to throw domain exceptions.
Repository Access Control
backend/dependencies.py
Modified get_repo_or_404 and verify_repo_access to accept optional user_id with explicit null checks that raise HTTP 401; ensures fail-fast behavior on missing user credentials.
Authentication Tests
backend/tests/test_auth_hardening.py, backend/tests/test_jwt_local_decode.py
Added new test suite validating null-user_id handling and exception hierarchy; updated existing JWT tests to assert custom exceptions instead of HTTPExceptions.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Middleware as Middleware<br/>_validate_jwt
    participant Services as Services<br/>verify_jwt
    participant APIKey as API Key Auth<br/>(Fallback)
    participant API as API Handler
    participant DB as Database

    Client->>Middleware: Request with Authorization header
    Middleware->>Services: Validate JWT token
    
    alt Token is JWT
        Services->>Services: Decode & verify token
        alt Token valid
            Services-->>Middleware: AuthContext
            Middleware->>API: Request authorized
        else Token expired
            Services-->>Middleware: TokenExpiredError
            Middleware-->>Client: HTTP 401
        else Missing claim
            Services-->>Middleware: TokenMissingClaimError
            Middleware-->>Client: HTTP 401
        else Invalid token
            Services-->>Middleware: InvalidTokenError
            Middleware->>APIKey: Return None (fallback)
        end
    else Not a JWT token
        Services-->>Middleware: Return None
        Middleware->>APIKey: Try API key authentication
        APIKey->>DB: Validate API key
        APIKey-->>Middleware: AuthContext (or None)
    end
    
    alt AuthContext obtained
        Middleware->>API: Proceed with request
        API->>DB: Process request
        DB-->>API: Response
        API-->>Client: HTTP 200
    else No valid auth
        Middleware-->>Client: HTTP 401
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐰 Through tokens tangled, we find our way,
With exceptions clear, come what may,
No null user sneaks past our guard,
Each claim we check, no task too hard,
Auth hardened strong, the rabbits say! 🔐

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the two main improvements: domain exceptions and null user_id safety, with specific issue references (OPE-76, OPE-77), directly matching the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/middleware/auth.py (1)

194-204: ⚠️ Potential issue | 🟠 Major

get_current_user legacy function will now return 500 instead of 401 on JWT failures.

verify_jwt() no longer raises HTTPException — it raises domain exceptions (TokenExpiredError, InvalidTokenError, etc.). This legacy function has no exception handling, so domain exceptions will propagate unhandled to FastAPI's default handler, producing a 500 Internal Server Error instead of the expected 401.

If this function is still in use, it needs a try/except that translates domain exceptions to HTTPException(401), similar to _validate_jwt. If it's dead code, consider removing it.

Proposed fix (if still in use)
 async def get_current_user(
     credentials: HTTPAuthorizationCredentials = Depends(_bearer_required)
 ) -> dict:
     """
     [LEGACY] Get current user from JWT token
     
     Prefer using require_auth() for new code.
     """
-    from services.auth import get_auth_service
-    auth_service = get_auth_service()
-    return auth_service.verify_jwt(credentials.credentials)
+    try:
+        from services.auth import get_auth_service
+        auth_service = get_auth_service()
+        return auth_service.verify_jwt(credentials.credentials)
+    except AuthenticationError as e:
+        raise HTTPException(status_code=401, detail=str(e))
#!/bin/bash
# Check if get_current_user is still referenced anywhere (excluding its own definition)
rg -n "get_current_user" --type=py -g '!backend/middleware/auth.py' -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/middleware/auth.py` around lines 194 - 204, The legacy
get_current_user function calls auth_service.verify_jwt but does not catch
domain exceptions (e.g., TokenExpiredError, InvalidTokenError), so these bubble
up as 500s; update get_current_user to wrap the verify_jwt call in a try/except
that catches the domain exceptions and raises
fastapi.HTTPException(status_code=401, detail="Invalid or expired token")
(mirroring the behavior in _validate_jwt), or if get_current_user is unused,
remove it after confirming no references.
🧹 Nitpick comments (4)
backend/services/exceptions.py (1)

1-49: Well-structured exception hierarchy.

Clean design that properly decouples business logic from the HTTP layer.

One observation: UserIdRequiredError (Line 47) is defined here but never raised anywhere in this PR — backend/dependencies.py raises HTTPException(status_code=401) directly instead. Consider either using UserIdRequiredError in dependencies.py (and catching it in the middleware/route layer) to stay consistent with the domain-exception pattern, or removing it if it's not planned for near-term use.

#!/bin/bash
# Check if UserIdRequiredError is actually used anywhere in the codebase
rg -n "UserIdRequiredError" --type=py -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/services/exceptions.py` around lines 1 - 49, The file defines
UserIdRequiredError but dependencies.py currently raises
HTTPException(status_code=401) directly; either remove the unused
UserIdRequiredError class or replace the direct HTTPException in
backend/dependencies.py with raising UserIdRequiredError so the middleware/route
layer can translate it consistently—locate the UserIdRequiredError class in
services/exceptions.py and the place in backend/dependencies.py where
HTTPException(status_code=401) is raised, then choose and apply one of the two
fixes and update tests/docs accordingly.
backend/tests/test_auth_hardening.py (1)

9-23: Integration test mostly verifies its own mocks.

The test patches verify_repo_access to raise HTTPException(401) and then asserts the response is 401. This validates route wiring but doesn't exercise the actual null-guard logic in dependencies.py. The real logic is covered by the unit tests at Lines 25–41, so this isn't a gap — just something to be aware of if you want to evolve this into a true integration test later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/test_auth_hardening.py` around lines 9 - 23, The test
test_search_with_null_user_id_returns_401 is currently asserting a 401 by
patching routes.search.verify_repo_access to raise HTTPException, so it only
verifies the mock, not the real null-guard in dependencies.py; remove or adjust
the patch of routes.search.verify_repo_access (leave routes.search.require_auth
mocked to return AuthContext(api_key_name="test-key", user_id=None)) so the
actual verify_repo_access implementation in dependencies.py runs and produces
the 401 when user_id is None, or alternatively change mock_verify to call
through to the real function instead of raising to exercise the real logic.
Ensure references remain to routes.search.require_auth,
routes.search.verify_repo_access, and dependencies.py when making the change.
backend/services/auth.py (2)

13-21: Unused imports: InvalidCredentialsError, SignupError, SessionError.

These three exceptions are imported but never raised or referenced in this file. signup, login, refresh_session, and logout still raise HTTPException directly.

Remove the unused imports now, or convert those methods to raise domain exceptions (and catch them at the route/middleware layer) as a follow-up.

Proposed fix
 from services.exceptions import (
     AuthenticationError,
     TokenExpiredError,
     InvalidTokenError,
     TokenMissingClaimError,
-    InvalidCredentialsError,
-    SignupError,
-    SessionError,
 )
#!/bin/bash
# Verify that InvalidCredentialsError, SignupError, SessionError are not referenced in auth.py
rg -n "InvalidCredentialsError|SignupError|SessionError" --type=py -g 'backend/services/auth.py'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/services/auth.py` around lines 13 - 21, The file imports three unused
exceptions (InvalidCredentialsError, SignupError, SessionError) — remove these
unused imports from the top-level import list in backend/services/auth.py to
clean up unused symbols; locate the import block that currently lists
AuthenticationError, TokenExpiredError, InvalidTokenError,
TokenMissingClaimError, InvalidCredentialsError, SignupError, SessionError and
delete only InvalidCredentialsError, SignupError, and SessionError (or
alternatively refactor signup, login, refresh_session, logout to raise those
domain exceptions and update route/middleware to catch them if you prefer that
pattern).

78-84: Consider exception chaining with raise ... from for debuggability.

The new domain exceptions discard the original PyJWT traceback. Using raise X from e preserves the cause chain, which is helpful when debugging token issues in logs.

Proposed fix
-        except jwt.ExpiredSignatureError:
-            raise TokenExpiredError("Token expired")
-        except jwt.InvalidAudienceError:
-            raise InvalidTokenError("Invalid token audience")
+        except jwt.ExpiredSignatureError as e:
+            raise TokenExpiredError("Token expired") from e
+        except jwt.InvalidAudienceError as e:
+            raise InvalidTokenError("Invalid token audience") from e
         except jwt.InvalidTokenError as e:
             logger.debug("JWT decode failed", error=str(e))
-            raise InvalidTokenError("Invalid token")
+            raise InvalidTokenError("Invalid token") from e
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/services/auth.py` around lines 78 - 84, The custom domain exceptions
in backend/services/auth.py currently discard the original PyJWT tracebacks;
update the except handlers for jwt.ExpiredSignatureError,
jwt.InvalidAudienceError and jwt.InvalidTokenError (the latter currently bound
as e) to capture the original exception and re-raise using exception chaining
(e.g., raise TokenExpiredError("Token expired") from e and raise
InvalidTokenError("Invalid token audience") from e / raise
InvalidTokenError("Invalid token") from e) so the original cause is preserved
for debuggability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@backend/middleware/auth.py`:
- Around line 194-204: The legacy get_current_user function calls
auth_service.verify_jwt but does not catch domain exceptions (e.g.,
TokenExpiredError, InvalidTokenError), so these bubble up as 500s; update
get_current_user to wrap the verify_jwt call in a try/except that catches the
domain exceptions and raises fastapi.HTTPException(status_code=401,
detail="Invalid or expired token") (mirroring the behavior in _validate_jwt), or
if get_current_user is unused, remove it after confirming no references.

---

Nitpick comments:
In `@backend/services/auth.py`:
- Around line 13-21: The file imports three unused exceptions
(InvalidCredentialsError, SignupError, SessionError) — remove these unused
imports from the top-level import list in backend/services/auth.py to clean up
unused symbols; locate the import block that currently lists
AuthenticationError, TokenExpiredError, InvalidTokenError,
TokenMissingClaimError, InvalidCredentialsError, SignupError, SessionError and
delete only InvalidCredentialsError, SignupError, and SessionError (or
alternatively refactor signup, login, refresh_session, logout to raise those
domain exceptions and update route/middleware to catch them if you prefer that
pattern).
- Around line 78-84: The custom domain exceptions in backend/services/auth.py
currently discard the original PyJWT tracebacks; update the except handlers for
jwt.ExpiredSignatureError, jwt.InvalidAudienceError and jwt.InvalidTokenError
(the latter currently bound as e) to capture the original exception and re-raise
using exception chaining (e.g., raise TokenExpiredError("Token expired") from e
and raise InvalidTokenError("Invalid token audience") from e / raise
InvalidTokenError("Invalid token") from e) so the original cause is preserved
for debuggability.

In `@backend/services/exceptions.py`:
- Around line 1-49: The file defines UserIdRequiredError but dependencies.py
currently raises HTTPException(status_code=401) directly; either remove the
unused UserIdRequiredError class or replace the direct HTTPException in
backend/dependencies.py with raising UserIdRequiredError so the middleware/route
layer can translate it consistently—locate the UserIdRequiredError class in
services/exceptions.py and the place in backend/dependencies.py where
HTTPException(status_code=401) is raised, then choose and apply one of the two
fixes and update tests/docs accordingly.

In `@backend/tests/test_auth_hardening.py`:
- Around line 9-23: The test test_search_with_null_user_id_returns_401 is
currently asserting a 401 by patching routes.search.verify_repo_access to raise
HTTPException, so it only verifies the mock, not the real null-guard in
dependencies.py; remove or adjust the patch of routes.search.verify_repo_access
(leave routes.search.require_auth mocked to return
AuthContext(api_key_name="test-key", user_id=None)) so the actual
verify_repo_access implementation in dependencies.py runs and produces the 401
when user_id is None, or alternatively change mock_verify to call through to the
real function instead of raising to exercise the real logic. Ensure references
remain to routes.search.require_auth, routes.search.verify_repo_access, and
dependencies.py when making the change.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f211731 and 25edad1.

📒 Files selected for processing (6)
  • backend/dependencies.py
  • backend/middleware/auth.py
  • backend/services/auth.py
  • backend/services/exceptions.py
  • backend/tests/test_auth_hardening.py
  • backend/tests/test_jwt_local_decode.py

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces domain-specific authentication exceptions and hardens null user_id handling. Custom exception classes replace HTTPException-based errors in auth services, middleware adds granular exception handling, and dependency helpers now guard against missing user_id with 401 responses. New tests verify null user_id safety and exception inheritance.

Changes

Cohort / File(s) Summary
Exception Domain Module
backend/services/exceptions.py
Introduces 8 custom authentication exception classes (AuthenticationError, TokenExpiredError, InvalidTokenError, TokenMissingClaimError, InvalidCredentialsError, SignupError, SessionError, UserIdRequiredError) to decouple business logic from HTTP framework.
Auth Service Refactoring
backend/services/auth.py
Replaces HTTPException error handling with custom domain exceptions in _verify_local and _verify_via_api for token validation failures, expired tokens, missing claims, and API verification errors.
Middleware Exception Handling
backend/middleware/auth.py
Adds granular exception handling in _validate_jwt to catch TokenExpiredError, TokenMissingClaimError, InvalidTokenError, and AuthenticationError, raising HTTP 401 with descriptive error messages.
Dependency Guards
backend/dependencies.py
Makes user_id parameter optional in get_repo_or_404 and verify_repo_access, adding guards that raise HTTP 401 before repository lookups when user_id is falsy.
New Test Suite
backend/tests/test_auth_hardening.py
Introduces tests for null user_id safety verification and confirms custom exception inheritance hierarchy from AuthenticationError.
Updated JWT Tests
backend/tests/test_jwt_local_decode.py
Refactors JWT verification tests to expect custom domain exceptions (TokenExpiredError, InvalidTokenError, TokenMissingClaimError) instead of HTTPException.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Guard the gates with custom care,
Exceptions dance through layers fair,
No null user\_id shall pass this line,
Auth exceptions now align,
Domain logic, clean and bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: domain exceptions (OPE-76) and null user_id safety (OPE-77), which are clearly reflected in the changeset across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…xception chaining, test exercises real code

1. get_current_user (legacy, used by logout + get_user_info routes)
   now catches domain exceptions. Was letting TokenExpiredError etc
   bubble up as 500s after our domain exception migration.
2. Exception chaining: jwt.ExpiredSignatureError etc now raise with
   'from e' so original PyJWT traceback is preserved for debugging.
3. Removed unused imports (InvalidCredentialsError, SignupError,
   SessionError) from auth.py. Will re-add when signup/login
   are migrated to domain exceptions.
4. test_search_with_null_user_id: now exercises the REAL null guard
   in dependencies.py instead of mocking verify_repo_access to
   return the answer. Overrides require_auth to return AuthContext
   with user_id=None, lets real verify_repo_access catch it.

289 tests pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
backend/dependencies.py (1)

47-57: Consider checking user_id is None (vs falsy) to match intent precisely.

Line 52 uses if not user_id: which will also reject "". If “missing user id” truly means None, prefer an explicit is None check (and keep the message stable for clients).

Proposed tweak
-    if not user_id:
+    if user_id is None:
         raise HTTPException(status_code=401, detail="User ID required for this operation")

As per coding guidelines, PEP 8 style, max 120 char lines in Python backend.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/dependencies.py` around lines 47 - 57, Replace the falsy check in
get_repo_or_404 with an explicit None check: change the conditional to test "if
user_id is None:" (rather than "if not user_id:") so empty-string user IDs are
not treated as missing; keep the HTTPException message unchanged and leave repo
lookup via repo_manager.get_repo_for_user(repo_id, user_id) as-is; ensure the
modified line follows PEP8 (≤120 chars) and update the docstring if necessary to
reflect that only None denotes a missing user_id.
backend/tests/test_auth_hardening.py (1)

47-69: Prefer normal imports over __import__ for readability.

Line 51’s __import__('fastapi').HTTPException works, but is unconventional; a simple from fastapi import HTTPException (or import fastapi) keeps it clearer.

Suggested adjustment
-        assert not issubclass(TokenExpiredError, __import__('fastapi').HTTPException)
+        from fastapi import HTTPException
+        assert not issubclass(TokenExpiredError, HTTPException)

As per coding guidelines, PEP 8 style, max 120 char lines in Python backend.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/test_auth_hardening.py` around lines 47 - 69, In
test_expired_token_raises_domain_exception replace the dynamic
__import__('fastapi').HTTPException usage with an explicit import (e.g., add
"from fastapi import HTTPException" at the top of the test or inside the test)
and then compare TokenExpiredError against HTTPException; update the
test_expired_token_raises_domain_exception function to use HTTPException for
clarity and ensure the changed lines respect PEP8 line-length limits.
backend/services/auth.py (1)

57-85: Name collision risk: jwt.InvalidTokenError vs your services.exceptions.InvalidTokenError.

In _verify_local, you catch jwt.InvalidTokenError and raise your own InvalidTokenError. This is correct but easy to misread. Consider aliasing the domain exception on import for clarity.

Example refactor
-from services.exceptions import (
+from services.exceptions import (
     AuthenticationError,
     TokenExpiredError,
-    InvalidTokenError,
+    InvalidTokenError as AuthInvalidTokenError,
     TokenMissingClaimError,
     InvalidCredentialsError,
     SignupError,
     SessionError,
 )
@@
-        except jwt.InvalidTokenError as e:
+        except jwt.InvalidTokenError as e:
             logger.debug("JWT decode failed", error=str(e))
-            raise InvalidTokenError("Invalid token")
+            raise AuthInvalidTokenError("Invalid token")

As per coding guidelines, PEP 8 style, max 120 char lines in Python backend.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/services/auth.py` around lines 57 - 85, Rename or alias the external
PyJWT exception at import (e.g., from jwt import InvalidTokenError as
PyJWTInvalidTokenError) and update the _verify_local exception handlers to catch
PyJWTInvalidTokenError (and other jwt.* exceptions) while still raising your
domain InvalidTokenError from services.exceptions; also update the logger.debug
call in _verify_local to use a keyword-safe message (e.g., logger.debug("JWT
decode failed: %s", str(e))) and wrap long lines to comply with PEP8 max 120
chars.
backend/middleware/auth.py (1)

68-95: Don’t silently swallow unexpected JWT verification failures without at least logging.

Line 93-94 (except Exception: return None) can convert genuine server-side issues (e.g., misconfig, supabase outage) into “auth fallback” behavior, which likely becomes a 401 later. That makes incidents harder to detect and can mislead clients.

Recommendation: log + rethrow for truly unexpected exceptions (or at minimum log at warning/error and keep fallback only for the specific “non-JWT”/invalid-token cases you intend).

As per coding guidelines, Use async/await for I/O operations in Python backend.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/middleware/auth.py` around lines 68 - 95, The _validate_jwt function
currently swallows unexpected exceptions (the bare except Exception: return
None), which hides server-side failures and prevents proper observability;
update _validate_jwt to be async, await auth_service.verify_jwt (since
verify_jwt is I/O), introduce a module-level logger, and in the broad exception
path log the full exception with context (e.g., include token or user info
redacted) and re-raise an HTTPException (500) or the original exception instead
of returning None; keep the specific InvalidTokenError path as the only case
that returns None to allow API-key fallback and preserve existing
TokenExpiredError/TokenMissingClaimError/AuthenticationError handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/services/auth.py`:
- Around line 13-21: The signup/login/refresh_session/logout functions in
auth.py currently raise HTTPException (mixing HTTP layer into the service);
update these methods to raise the domain exceptions already imported (e.g.,
InvalidCredentialsError, SignupError, SessionError, TokenExpiredError,
InvalidTokenError, TokenMissingClaimError) instead of HTTPException, and ensure
any HTTP-specific mapping is moved to the route layer or middleware so
routes/middleware translate those domain exceptions into
HTTPException/responses; locate and change the raise sites inside signup, login,
refresh_session and logout to throw the appropriate imported domain exception
types and keep the service layer free of FastAPI errors.
- Around line 86-104: The synchronous network call in _verify_via_api is
blocking the event loop; make _verify_via_api async, wrap the blocking call
self.client.auth.get_user in fastapi.concurrency.run_in_threadpool and await it,
preserve the existing checks/errors (raise InvalidTokenError when response.user
is falsy and re-raise AuthenticationError), and chain the original exception
when raising AuthenticationError (raise ... from e); then make verify_jwt async
so it can await _verify_via_api and update any callers (e.g., require_auth) to
await verify_jwt to keep the async context consistent.

---

Nitpick comments:
In `@backend/dependencies.py`:
- Around line 47-57: Replace the falsy check in get_repo_or_404 with an explicit
None check: change the conditional to test "if user_id is None:" (rather than
"if not user_id:") so empty-string user IDs are not treated as missing; keep the
HTTPException message unchanged and leave repo lookup via
repo_manager.get_repo_for_user(repo_id, user_id) as-is; ensure the modified line
follows PEP8 (≤120 chars) and update the docstring if necessary to reflect that
only None denotes a missing user_id.

In `@backend/middleware/auth.py`:
- Around line 68-95: The _validate_jwt function currently swallows unexpected
exceptions (the bare except Exception: return None), which hides server-side
failures and prevents proper observability; update _validate_jwt to be async,
await auth_service.verify_jwt (since verify_jwt is I/O), introduce a
module-level logger, and in the broad exception path log the full exception with
context (e.g., include token or user info redacted) and re-raise an
HTTPException (500) or the original exception instead of returning None; keep
the specific InvalidTokenError path as the only case that returns None to allow
API-key fallback and preserve existing
TokenExpiredError/TokenMissingClaimError/AuthenticationError handling.

In `@backend/services/auth.py`:
- Around line 57-85: Rename or alias the external PyJWT exception at import
(e.g., from jwt import InvalidTokenError as PyJWTInvalidTokenError) and update
the _verify_local exception handlers to catch PyJWTInvalidTokenError (and other
jwt.* exceptions) while still raising your domain InvalidTokenError from
services.exceptions; also update the logger.debug call in _verify_local to use a
keyword-safe message (e.g., logger.debug("JWT decode failed: %s", str(e))) and
wrap long lines to comply with PEP8 max 120 chars.

In `@backend/tests/test_auth_hardening.py`:
- Around line 47-69: In test_expired_token_raises_domain_exception replace the
dynamic __import__('fastapi').HTTPException usage with an explicit import (e.g.,
add "from fastapi import HTTPException" at the top of the test or inside the
test) and then compare TokenExpiredError against HTTPException; update the
test_expired_token_raises_domain_exception function to use HTTPException for
clarity and ensure the changed lines respect PEP8 line-length limits.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f211731 and 25edad1.

📒 Files selected for processing (6)
  • backend/dependencies.py
  • backend/middleware/auth.py
  • backend/services/auth.py
  • backend/services/exceptions.py
  • backend/tests/test_auth_hardening.py
  • backend/tests/test_jwt_local_decode.py

Comment thread backend/services/auth.py
Comment thread backend/services/auth.py
1. 'import jwt as pyjwt' to avoid name collision with our
   services.exceptions.InvalidTokenError. Now pyjwt.InvalidTokenError
   vs InvalidTokenError is unambiguous.
2. 'if user_id is None' instead of 'if not user_id' in dependencies.py.
   Empty string user_ids (unlikely but possible) no longer rejected.
3. Replaced __import__('fastapi').HTTPException hack in test with
   proper import.

Skipped: signup/login HTTPException migration (scope creep, separate
PR), async _verify_via_api (6th time raised), _validate_jwt bare
except (intentional for API key fallback).

289 tests pass.
Self-audit caught: _verify_local had 'from e' on all 3 except
handlers, but _verify_via_api's catch-all was missing it. Original
Supabase error traceback was being lost on API fallback failures.

289 tests pass.
@vercel

vercel Bot commented Feb 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
opencodeintel Ignored Ignored Preview Feb 24, 2026 5:41pm

@DevanshuNEU DevanshuNEU merged commit 68ac966 into OpenCodeIntel:main Feb 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant