Spaces:
Sleeping
Sleeping
File size: 4,163 Bytes
62516b8 | 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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | from __future__ import annotations
import asyncio
from datetime import datetime, timedelta, timezone
import jwt
import pytest
from fastapi import HTTPException
from src.auth.jwt import decode_and_verify_jwt, require_jwt
from src.config import settings
def _build_token(
*,
secret: str,
iss: str,
aud: str,
sub: str = "client:test-client",
expires_in_seconds: int = 300,
scope: str = "material:write lkpd:write lkpd:read",
) -> str:
now = datetime.now(timezone.utc)
payload = {
"iss": iss,
"aud": aud,
"sub": sub,
"iat": int(now.timestamp()),
"exp": int((now + timedelta(seconds=expires_in_seconds)).timestamp()),
"scope": scope,
}
return jwt.encode(payload, secret, algorithm="HS256")
def _set_jwt_settings(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(settings, "jwt_enabled", True)
monkeypatch.setattr(settings, "jwt_secret", "x" * 32)
monkeypatch.setattr(settings, "jwt_issuer", "my-backend")
monkeypatch.setattr(settings, "jwt_audience", "rtm-class-ai")
monkeypatch.setattr(settings, "jwt_clock_skew_seconds", 0)
monkeypatch.setattr(settings, "jwt_denylist_enabled", False)
def test_decode_and_verify_jwt_valid_token(monkeypatch: pytest.MonkeyPatch) -> None:
_set_jwt_settings(monkeypatch)
token = _build_token(
secret=settings.jwt_secret,
iss=settings.jwt_issuer,
aud=settings.jwt_audience,
)
payload = decode_and_verify_jwt(token)
assert payload["iss"] == settings.jwt_issuer
assert payload["aud"] == settings.jwt_audience
assert payload["sub"] == "client:test-client"
def test_decode_and_verify_jwt_wrong_issuer(monkeypatch: pytest.MonkeyPatch) -> None:
_set_jwt_settings(monkeypatch)
token = _build_token(
secret=settings.jwt_secret,
iss="not-backend",
aud=settings.jwt_audience,
)
with pytest.raises(HTTPException) as exc_info:
decode_and_verify_jwt(token)
assert exc_info.value.status_code == 401
assert exc_info.value.detail == "Invalid token issuer."
def test_decode_and_verify_jwt_wrong_audience(monkeypatch: pytest.MonkeyPatch) -> None:
_set_jwt_settings(monkeypatch)
token = _build_token(
secret=settings.jwt_secret,
iss=settings.jwt_issuer,
aud="other-service",
)
with pytest.raises(HTTPException) as exc_info:
decode_and_verify_jwt(token)
assert exc_info.value.status_code == 401
assert exc_info.value.detail == "Invalid token audience."
def test_decode_and_verify_jwt_expired_token(monkeypatch: pytest.MonkeyPatch) -> None:
_set_jwt_settings(monkeypatch)
token = _build_token(
secret=settings.jwt_secret,
iss=settings.jwt_issuer,
aud=settings.jwt_audience,
expires_in_seconds=-1,
)
with pytest.raises(HTTPException) as exc_info:
decode_and_verify_jwt(token)
assert exc_info.value.status_code == 401
assert exc_info.value.detail == "Invalid or expired token."
def test_require_jwt_missing_scope(monkeypatch: pytest.MonkeyPatch) -> None:
_set_jwt_settings(monkeypatch)
token = _build_token(
secret=settings.jwt_secret,
iss=settings.jwt_issuer,
aud=settings.jwt_audience,
scope="lkpd:read",
)
dependency = require_jwt(["material:write"])
with pytest.raises(HTTPException) as exc_info:
asyncio.run(dependency(authorization=f"Bearer {token}"))
assert exc_info.value.status_code == 403
assert exc_info.value.detail == "Insufficient scope."
def test_require_jwt_invalid_subject(monkeypatch: pytest.MonkeyPatch) -> None:
_set_jwt_settings(monkeypatch)
token = _build_token(
secret=settings.jwt_secret,
iss=settings.jwt_issuer,
aud=settings.jwt_audience,
sub="service:backend",
)
dependency = require_jwt(["material:write"])
with pytest.raises(HTTPException) as exc_info:
asyncio.run(dependency(authorization=f"Bearer {token}"))
assert exc_info.value.status_code == 401
assert exc_info.value.detail == "Invalid token subject."
|