security(07.3-02): ES256 signing — swap 4 JWT sites + config keys + promote tests

- backend/config.py: add refresh_token_expire_hours=16, jwt_private_key, jwt_public_key
- backend/services/auth.py: add import base64; all 4 JWT sites use ES256 with inline PEM decode; zero HS256/secret_key references
- backend/tests/test_auth_es256.py: promote ES256-01..03 + CFG-01 tests from xfail to passing
This commit is contained in:
curo1305
2026-06-06 16:59:38 +02:00
parent 1eb1d59c8d
commit fd3f611546
3 changed files with 53 additions and 14 deletions
+40 -10
View File
@@ -36,21 +36,47 @@ def es256_keys(monkeypatch):
monkeypatch.setattr("config.settings.jwt_public_key", pub, raising=False)
# ── ES256 algorithm stubs ─────────────────────────────────────────────────────
# ── ES256 algorithm tests ─────────────────────────────────────────────────────
@pytest.mark.xfail(strict=True, reason="ES256-01: not yet implemented")
def test_access_token_uses_es256():
pytest.xfail("not yet implemented")
"""ES256-01: access token header must declare alg=ES256."""
from services.auth import create_access_token
token = create_access_token("u1", "user")
# Decode the header (first segment of the JWT)
header_b64 = token.split(".")[0]
# Add padding to make it valid base64
padding = "=" * (4 - len(header_b64) % 4)
header = json.loads(base64.urlsafe_b64decode(header_b64 + padding))
assert header["alg"] == "ES256"
@pytest.mark.xfail(strict=True, reason="ES256-02: not yet implemented")
def test_hs256_token_rejected():
pytest.xfail("not yet implemented")
"""ES256-02: an HS256 token must raise ValueError when decoded."""
import jwt as _jwt
from services.auth import decode_access_token
hs256_token = _jwt.encode(
{
"sub": "u1",
"typ": "access",
"exp": int(time.time()) + 60,
"iat": int(time.time()),
},
"any-hs256-secret",
algorithm="HS256",
)
with pytest.raises(ValueError):
decode_access_token(hs256_token)
@pytest.mark.xfail(strict=True, reason="ES256-03: not yet implemented")
def test_reset_token_uses_es256():
pytest.xfail("not yet implemented")
"""ES256-03: password-reset token header must declare alg=ES256 and round-trips."""
from services.auth import create_password_reset_token, decode_password_reset_token
token = create_password_reset_token("u1")
header_b64 = token.split(".")[0]
padding = "=" * (4 - len(header_b64) % 4)
header = json.loads(base64.urlsafe_b64decode(header_b64 + padding))
assert header["alg"] == "ES256"
assert decode_password_reset_token(token) == "u1"
# ── Startup token rotation stubs ──────────────────────────────────────────────
@@ -87,8 +113,12 @@ async def test_remember_me_cookie_max_age(async_client, auth_user):
pytest.xfail("not yet implemented")
# ── Config field stubs ────────────────────────────────────────────────────────
# ── Config field tests ────────────────────────────────────────────────────────
@pytest.mark.xfail(strict=True, reason="CFG-01: not yet implemented")
def test_settings_has_jwt_keys():
pytest.xfail("not yet implemented")
"""CFG-01: Settings must expose jwt_private_key, jwt_public_key, and refresh_token_expire_hours=16."""
from config import settings
assert hasattr(settings, "jwt_private_key")
assert hasattr(settings, "jwt_public_key")
assert hasattr(settings, "refresh_token_expire_hours")
assert settings.refresh_token_expire_hours == 16