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:
parent
23ae6a602c
commit
3321d2e266
2 changed files with 35 additions and 1 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue