-
Notifications
You must be signed in to change notification settings - Fork 5
perf: switch JWT verification from network call to local decode (OPE-75) #253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
DevanshuNEU
merged 3 commits into
OpenCodeIntel:main
from
DevanshuNEU:fix/jwt-local-decode
Feb 22, 2026
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
57122e5
perf: switch JWT verification from Supabase API call to local decode
DevanshuNEU c570d42
fix: review findings -- null metadata coalescing, case-insensitive Be…
DevanshuNEU 3360290
fix: add 30s leeway for clock skew, update expired token test
DevanshuNEU File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| """Tests for JWT local decode (OPE-75).""" | ||
| import pytest | ||
| import jwt as pyjwt | ||
| import time | ||
| from unittest.mock import patch, MagicMock | ||
|
|
||
|
|
||
| JWT_SECRET = "test-jwt-secret-for-unit-tests" | ||
|
|
||
|
|
||
| def _make_token(payload: dict, secret: str = JWT_SECRET) -> str: | ||
| """Create a signed JWT for testing.""" | ||
| defaults = { | ||
| "aud": "authenticated", | ||
| "exp": int(time.time()) + 3600, | ||
| "iat": int(time.time()), | ||
| "role": "authenticated", | ||
| } | ||
| return pyjwt.encode({**defaults, **payload}, secret, algorithm="HS256") | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def auth_service(): | ||
| """Create auth service with local JWT secret configured.""" | ||
| with patch("services.auth.create_client") as mock_client: | ||
| mock_client.return_value = MagicMock() | ||
| with patch.dict("os.environ", { | ||
| "SUPABASE_URL": "https://test.supabase.co", | ||
| "SUPABASE_ANON_KEY": "test-key", | ||
| "SUPABASE_JWT_SECRET": JWT_SECRET, | ||
| }): | ||
| from services.auth import SupabaseAuthService | ||
| yield SupabaseAuthService() | ||
|
|
||
|
|
||
| class TestLocalJWTDecode: | ||
| """Verify local JWT decode works without network calls.""" | ||
|
|
||
| def test_valid_token_returns_user_data(self, auth_service): | ||
| token = _make_token({"sub": "user-123", "email": "dev@test.com", "user_metadata": {"tier": "pro"}}) | ||
| result = auth_service.verify_jwt(token) | ||
|
|
||
| assert result["user_id"] == "user-123" | ||
| assert result["email"] == "dev@test.com" | ||
| assert result["metadata"]["tier"] == "pro" | ||
|
|
||
| def test_bearer_prefix_stripped(self, auth_service): | ||
| token = _make_token({"sub": "user-456", "email": "a@b.com"}) | ||
| result = auth_service.verify_jwt(f"Bearer {token}") | ||
|
|
||
| assert result["user_id"] == "user-456" | ||
|
|
||
| def test_expired_token_raises_401(self, auth_service): | ||
| token = _make_token({"sub": "user-789", "exp": int(time.time()) - 60}) | ||
|
|
||
| from fastapi import HTTPException | ||
| with pytest.raises(HTTPException) as exc: | ||
| auth_service.verify_jwt(token) | ||
| assert exc.value.status_code == 401 | ||
| assert "expired" in exc.value.detail.lower() | ||
|
|
||
| def test_wrong_secret_raises_401(self, auth_service): | ||
| token = _make_token({"sub": "user-000"}, secret="wrong-secret") | ||
|
|
||
| from fastapi import HTTPException | ||
| with pytest.raises(HTTPException) as exc: | ||
| auth_service.verify_jwt(token) | ||
| assert exc.value.status_code == 401 | ||
|
|
||
| def test_missing_sub_claim_raises_401(self, auth_service): | ||
| token = _make_token({"email": "no-sub@test.com"}) | ||
|
|
||
| from fastapi import HTTPException | ||
| with pytest.raises(HTTPException) as exc: | ||
| auth_service.verify_jwt(token) | ||
| assert exc.value.status_code == 401 | ||
| assert "subject" in exc.value.detail.lower() | ||
|
|
||
| def test_wrong_audience_raises_401(self, auth_service): | ||
| payload = {"sub": "user-aud", "aud": "wrong-audience", "exp": int(time.time()) + 3600} | ||
| token = pyjwt.encode(payload, JWT_SECRET, algorithm="HS256") | ||
|
|
||
| from fastapi import HTTPException | ||
| with pytest.raises(HTTPException) as exc: | ||
| auth_service.verify_jwt(token) | ||
| assert exc.value.status_code == 401 | ||
|
|
||
| def test_no_network_call_made(self, auth_service): | ||
| """The whole point of OPE-75: verify_jwt should NOT hit the network.""" | ||
| token = _make_token({"sub": "user-net", "email": "net@test.com"}) | ||
|
|
||
| auth_service.verify_jwt(token) | ||
|
|
||
| # get_user should never be called when jwt_secret is available | ||
| auth_service.client.auth.get_user.assert_not_called() | ||
|
|
||
| def test_metadata_defaults_to_empty_dict(self, auth_service): | ||
| token = _make_token({"sub": "user-no-meta", "email": "x@y.com"}) | ||
| result = auth_service.verify_jwt(token) | ||
|
|
||
| assert result["metadata"] == {} | ||
|
|
||
| def test_metadata_null_coalesced_to_empty_dict(self, auth_service): | ||
| """user_metadata can be explicitly null in Supabase JWTs.""" | ||
| token = _make_token({"sub": "user-null-meta", "user_metadata": None}) | ||
| result = auth_service.verify_jwt(token) | ||
|
|
||
| assert result["metadata"] == {} | ||
|
|
||
| def test_bearer_prefix_case_insensitive(self, auth_service): | ||
| token = _make_token({"sub": "user-case"}) | ||
| for prefix in ["Bearer ", "bearer ", "BEARER "]: | ||
| result = auth_service.verify_jwt(f"{prefix}{token}") | ||
| assert result["user_id"] == "user-case" | ||
|
|
||
|
|
||
| class TestAPIFallback: | ||
| """When JWT secret is not configured, fall back to Supabase API.""" | ||
|
|
||
| def test_falls_back_to_api_when_no_secret(self): | ||
| with patch("services.auth.create_client") as mock_client: | ||
| client = MagicMock() | ||
| user = MagicMock() | ||
| user.id = "api-user-123" | ||
| user.email = "api@test.com" | ||
| user.user_metadata = {"tier": "free"} | ||
| response = MagicMock() | ||
| response.user = user | ||
| client.auth.get_user.return_value = response | ||
| mock_client.return_value = client | ||
|
|
||
| with patch.dict("os.environ", { | ||
| "SUPABASE_URL": "https://test.supabase.co", | ||
| "SUPABASE_ANON_KEY": "test-key", | ||
| "SUPABASE_JWT_SECRET": "", | ||
| }): | ||
| from services.auth import SupabaseAuthService | ||
| service = SupabaseAuthService() | ||
| result = service.verify_jwt("some-token") | ||
|
|
||
| assert result["user_id"] == "api-user-123" | ||
| client.auth.get_user.assert_called_once_with("some-token") |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.