Spaces:
Running
Running
File size: 3,070 Bytes
bbe01fe | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | # backend/tests/test_jwt_auth.py
# Tests JWT verification — the only security gate between the internet and the chat endpoint.
# Every path through verify_jwt() is covered.
import time
import pytest
from fastapi import HTTPException
from fastapi.security import HTTPAuthorizationCredentials
from jose import jwt
from unittest.mock import patch
from tests.conftest import TEST_JWT_SECRET, TEST_ALGORITHM, make_jwt
def _make_credentials(token: str) -> HTTPAuthorizationCredentials:
return HTTPAuthorizationCredentials(scheme="Bearer", credentials=token)
def _verify(token: str):
"""Call verify_jwt synchronously (it's not async)."""
from app.security.jwt_auth import verify_jwt
return verify_jwt(_make_credentials(token))
class TestVerifyJWT:
def test_valid_token_passes(self, valid_token):
payload = _verify(valid_token)
assert payload["sub"] == "test-user"
def test_expired_token_rejected(self, expired_token):
with pytest.raises(HTTPException) as exc_info:
_verify(expired_token)
assert exc_info.value.status_code == 401
assert "expired" in exc_info.value.detail.lower()
def test_wrong_secret_rejected(self, wrong_secret_token):
with pytest.raises(HTTPException) as exc_info:
_verify(wrong_secret_token)
assert exc_info.value.status_code == 401
def test_malformed_token_rejected(self):
with pytest.raises(HTTPException) as exc_info:
_verify("not.a.jwt")
assert exc_info.value.status_code == 401
def test_empty_token_rejected(self):
with pytest.raises(HTTPException):
_verify("")
def test_algorithm_none_attack_rejected(self):
# Attacker crafts a token with alg=none to bypass signature verification.
# jose refuses to decode alg=none tokens by default — this test confirms it.
payload = {"sub": "attacker", "exp": int(time.time()) + 3600}
# We can't easily craft an alg=none token with jose, but we can verify
# that a tampered token (modified signature) is rejected.
valid = make_jwt()
tampered = valid[: valid.rfind(".")] + ".invalidsignature"
with pytest.raises(HTTPException) as exc_info:
_verify(tampered)
assert exc_info.value.status_code == 401
def test_missing_jwt_secret_raises_500(self):
# If JWT_SECRET is not configured on the server, the endpoint returns 500
# rather than accidentally accepting all tokens.
from app.core.config import get_settings, Settings
settings = get_settings()
original = settings.JWT_SECRET
settings.JWT_SECRET = None
try:
with pytest.raises(HTTPException) as exc_info:
_verify(make_jwt())
assert exc_info.value.status_code == 500
finally:
settings.JWT_SECRET = original
def test_token_payload_fields_preserved(self):
token = make_jwt(role="guest")
payload = _verify(token)
assert payload.get("role") == "guest"
|