Fix auth loop: use TimestampSigner + base64 to match Starlette SessionMiddleware
Browse files- app/auth.py +22 -21
app/auth.py
CHANGED
|
@@ -7,36 +7,37 @@ from fastapi import HTTPException, Request, status
|
|
| 7 |
from starlette.middleware.sessions import SessionMiddleware
|
| 8 |
from starlette.responses import JSONResponse, HTMLResponse
|
| 9 |
from starlette.types import ASGIApp, Receive, Scope, Send
|
| 10 |
-
from itsdangerous import
|
|
|
|
|
|
|
| 11 |
import secrets
|
| 12 |
import time
|
| 13 |
from typing import Optional, Callable
|
| 14 |
from app.config import settings
|
| 15 |
|
| 16 |
|
| 17 |
-
# Session serializer for decoding cookies in middleware
|
| 18 |
-
# Must match Starlette's SessionMiddleware exactly
|
| 19 |
-
def get_session_serializer():
|
| 20 |
-
"""Create a serializer matching SessionMiddleware's format exactly"""
|
| 21 |
-
# Starlette uses URLSafeSerializer (not URLSafeTimedSerializer)
|
| 22 |
-
# with these exact parameters
|
| 23 |
-
return URLSafeSerializer(
|
| 24 |
-
settings.SESSION_SECRET_KEY,
|
| 25 |
-
salt="cookie-session",
|
| 26 |
-
signer_kwargs={
|
| 27 |
-
"key_derivation": "hmac",
|
| 28 |
-
"digest_method": "sha1"
|
| 29 |
-
}
|
| 30 |
-
)
|
| 31 |
-
|
| 32 |
-
|
| 33 |
def decode_session_cookie(cookie_value: str) -> dict:
|
| 34 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
if not cookie_value:
|
| 36 |
return {}
|
| 37 |
try:
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
except (BadSignature, Exception) as e:
|
| 41 |
# Log error for debugging but don't crash
|
| 42 |
print(f"Session decode error: {e}")
|
|
@@ -377,4 +378,4 @@ def get_login_html(error: str = None) -> str:
|
|
| 377 |
document.getElementById('password').focus();
|
| 378 |
</script>
|
| 379 |
</body>
|
| 380 |
-
</html>'''
|
|
|
|
| 7 |
from starlette.middleware.sessions import SessionMiddleware
|
| 8 |
from starlette.responses import JSONResponse, HTMLResponse
|
| 9 |
from starlette.types import ASGIApp, Receive, Scope, Send
|
| 10 |
+
from itsdangerous import TimestampSigner, BadSignature
|
| 11 |
+
import base64
|
| 12 |
+
import json
|
| 13 |
import secrets
|
| 14 |
import time
|
| 15 |
from typing import Optional, Callable
|
| 16 |
from app.config import settings
|
| 17 |
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
def decode_session_cookie(cookie_value: str) -> dict:
|
| 20 |
+
"""
|
| 21 |
+
Decode session cookie value to dictionary.
|
| 22 |
+
Must match Starlette's SessionMiddleware encoding exactly:
|
| 23 |
+
1. TimestampSigner signs the data
|
| 24 |
+
2. Data is base64 encoded JSON
|
| 25 |
+
"""
|
| 26 |
if not cookie_value:
|
| 27 |
return {}
|
| 28 |
try:
|
| 29 |
+
# Create the same signer that SessionMiddleware uses
|
| 30 |
+
signer = TimestampSigner(
|
| 31 |
+
settings.SESSION_SECRET_KEY,
|
| 32 |
+
salt="cookie-session"
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# Unsign the cookie value
|
| 36 |
+
unsigned_value = signer.unsign(cookie_value)
|
| 37 |
+
|
| 38 |
+
# Decode base64 and parse JSON
|
| 39 |
+
decoded = base64.urlsafe_b64decode(unsigned_value)
|
| 40 |
+
return json.loads(decoded)
|
| 41 |
except (BadSignature, Exception) as e:
|
| 42 |
# Log error for debugging but don't crash
|
| 43 |
print(f"Session decode error: {e}")
|
|
|
|
| 378 |
document.getElementById('password').focus();
|
| 379 |
</script>
|
| 380 |
</body>
|
| 381 |
+
</html>'''
|