security: reduce JWT expiry to 24h and add jti claim

Tokens previously lasted 30 days with no revocation path. Expiry is now
24 hours and every token carries a unique jti (UUID hex) to support future
revocation checks.
This commit is contained in:
ZdenekSrotyr 2026-04-09 06:57:23 +02:00
parent 23ae6a602c
commit 3321d2e266
2 changed files with 35 additions and 1 deletions

View file

@ -1,6 +1,7 @@
"""JWT token creation and verification for API auth."""
import os
import uuid
from datetime import datetime, timedelta, timezone
from typing import Optional
@ -24,7 +25,7 @@ elif len(SECRET_KEY) < 32 and os.environ.get("TESTING", "").lower() not in ("1",
)
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_HOURS = 24 * 30 # 30 days
ACCESS_TOKEN_EXPIRE_HOURS = 24 # 24 hours
def create_access_token(
@ -42,6 +43,7 @@ def create_access_token(
"role": role,
"exp": expire,
"iat": datetime.now(timezone.utc),
"jti": uuid.uuid4().hex,
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

View file

@ -111,6 +111,18 @@ class TestScriptSandbox:
# Should still run but without access to dangerous modules
assert resp.status_code == 200
def test_sandbox_cannot_import_httpx(self, client):
"""httpx must be blocked — either by pattern check (400) or
ModuleNotFoundError at runtime due to stripped VIRTUAL_ENV/PYTHONPATH (200 with non-zero exit)."""
c, token = client
resp = c.post("/api/scripts/run", json={
"source": "import httpx\nprint('pwned')",
}, headers=_headers(token))
# Static pattern check should reject it outright
assert resp.status_code == 400 or (
resp.status_code == 200 and resp.json()["exit_code"] != 0
)
# ---- SQL Query Security ----
@ -213,6 +225,26 @@ class TestAuthSecurity:
assert resp.status_code == 401
# ---- JWT Claims ----
class TestJwtClaims:
def test_jwt_contains_jti_claim(self):
"""Token payload must include a jti claim with at least 16 hex chars."""
os.environ.setdefault("TESTING", "1")
from app.auth.jwt import create_access_token, verify_token
token = create_access_token("u1", "user@test.com", "analyst")
payload = verify_token(token)
assert payload is not None
assert "jti" in payload
assert len(payload["jti"]) >= 16
def test_jwt_expiry_is_24_hours(self):
"""ACCESS_TOKEN_EXPIRE_HOURS must be 24 (not 30*24)."""
os.environ.setdefault("TESTING", "1")
from app.auth import jwt as jwt_module
assert jwt_module.ACCESS_TOKEN_EXPIRE_HOURS == 24
# ---- JWT Secret Hardening ----
class TestJwtSecretHardening: