samar m commited on
Commit
3cefbc3
·
1 Parent(s): 30e2128

fix: add JWT secret warning and missing security tests

Browse files
Files changed (2) hide show
  1. backend/auth/jwt.py +7 -0
  2. tests/test_jwt.py +32 -0
backend/auth/jwt.py CHANGED
@@ -1,6 +1,7 @@
1
  import os
2
  import secrets
3
  import hashlib
 
4
  from datetime import datetime, timedelta, timezone
5
  from jose import jwt, JWTError
6
 
@@ -8,6 +9,12 @@ _SECRET = os.getenv("JWT_SECRET", "dev-secret-key-32-chars-minimum!!")
8
  _ALGORITHM = "HS256"
9
  _ACCESS_EXPIRE_MINUTES = int(os.getenv("JWT_ACCESS_EXPIRE_MINUTES", "15"))
10
 
 
 
 
 
 
 
11
 
12
  def create_access_token(user_id: str, role: str, batch_id: str | None, email: str) -> str:
13
  exp = datetime.now(timezone.utc) + timedelta(minutes=_ACCESS_EXPIRE_MINUTES)
 
1
  import os
2
  import secrets
3
  import hashlib
4
+ import warnings
5
  from datetime import datetime, timedelta, timezone
6
  from jose import jwt, JWTError
7
 
 
9
  _ALGORITHM = "HS256"
10
  _ACCESS_EXPIRE_MINUTES = int(os.getenv("JWT_ACCESS_EXPIRE_MINUTES", "15"))
11
 
12
+ if _SECRET == "dev-secret-key-32-chars-minimum!!":
13
+ warnings.warn(
14
+ "JWT_SECRET is using the insecure development default. Set JWT_SECRET in your environment.",
15
+ stacklevel=1,
16
+ )
17
+
18
 
19
  def create_access_token(user_id: str, role: str, batch_id: str | None, email: str) -> str:
20
  exp = datetime.now(timezone.utc) + timedelta(minutes=_ACCESS_EXPIRE_MINUTES)
tests/test_jwt.py CHANGED
@@ -28,3 +28,35 @@ def test_refresh_token_hashes_consistently():
28
  import hashlib
29
  raw, hashed = generate_refresh_token()
30
  assert hashlib.sha256(raw.encode()).hexdigest() == hashed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  import hashlib
29
  raw, hashed = generate_refresh_token()
30
  assert hashlib.sha256(raw.encode()).hexdigest() == hashed
31
+
32
+
33
+ def test_expired_token_raises():
34
+ from datetime import datetime, timedelta, timezone
35
+ from jose import jwt as jose_jwt
36
+ import os
37
+ secret = os.getenv("JWT_SECRET", "dev-secret-key-32-chars-minimum!!")
38
+ expired_payload = {
39
+ "user_id": "u1", "role": "student", "batch_id": None, "email": "a@b.com",
40
+ "exp": datetime.now(timezone.utc) - timedelta(seconds=1),
41
+ }
42
+ expired_token = jose_jwt.encode(expired_payload, secret, algorithm="HS256")
43
+ with pytest.raises(ValueError):
44
+ verify_access_token(expired_token)
45
+
46
+
47
+ def test_wrong_secret_raises():
48
+ token = create_access_token("u1", "student", None, "a@b.com")
49
+ from jose import jwt as jose_jwt
50
+ # Tamper: re-sign with different secret
51
+ import os
52
+ wrong_secret = "wrong-secret-completely-different!!"
53
+ payload = jose_jwt.decode(token, os.getenv("JWT_SECRET", "dev-secret-key-32-chars-minimum!!"), algorithms=["HS256"])
54
+ tampered = jose_jwt.encode(payload, wrong_secret, algorithm="HS256")
55
+ with pytest.raises(ValueError):
56
+ verify_access_token(tampered)
57
+
58
+
59
+ def test_batch_id_none_roundtrip():
60
+ token = create_access_token("u1", "student", None, "a@b.com")
61
+ payload = verify_access_token(token)
62
+ assert payload["batch_id"] is None