vectorplasticity commited on
Commit
6adb94f
·
verified ·
1 Parent(s): 597333b

Update via HuggingFace Tool

Browse files
Files changed (1) hide show
  1. app/auth.py +32 -17
app/auth.py CHANGED
@@ -7,7 +7,7 @@ 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 URLSafeTimedSerializer, BadSignature
11
  import secrets
12
  import time
13
  from typing import Optional, Callable
@@ -15,12 +15,18 @@ from app.config import settings
15
 
16
 
17
  # Session serializer for decoding cookies in middleware
 
18
  def get_session_serializer():
19
- """Create a serializer matching SessionMiddleware's format"""
20
- return URLSafeTimedSerializer(
 
 
21
  settings.SESSION_SECRET_KEY,
22
  salt="cookie-session",
23
- signer_kwargs={"key_derivation": "hmac", "digest_method": "sha1"}
 
 
 
24
  )
25
 
26
 
@@ -30,9 +36,10 @@ def decode_session_cookie(cookie_value: str) -> dict:
30
  return {}
31
  try:
32
  serializer = get_session_serializer()
33
- # max_age matches the session max_age
34
- return serializer.loads(cookie_value, max_age=settings.SESSION_EXPIRE_HOURS * 3600)
35
- except (BadSignature, Exception):
 
36
  return {}
37
 
38
 
@@ -87,9 +94,11 @@ class AuthMiddleware:
87
 
88
  IMPORTANT: This middleware decodes the session cookie directly because
89
  SessionMiddleware hasn't run yet when this middleware executes.
 
 
90
  """
91
 
92
- # Paths that don't require authentication
93
  PUBLIC_PATHS = [
94
  "/login",
95
  "/api/auth/login",
@@ -121,7 +130,12 @@ class AuthMiddleware:
121
  await self.app(scope, receive, send)
122
  return
123
 
124
- # Allow public paths (but NOT root "/")
 
 
 
 
 
125
  if path in self.PUBLIC_PATHS:
126
  await self.app(scope, receive, send)
127
  return
@@ -151,13 +165,13 @@ class AuthMiddleware:
151
  await self.app(scope, receive, send)
152
  return
153
 
154
- # Unauthorized - redirect to login for HTML requests
155
  accept = headers.get(b"accept", b"").decode("utf-8", errors="ignore")
156
 
157
  # Check if this is an API request or browser request
158
  is_api_request = path.startswith("/api/") or "application/json" in accept
159
 
160
- if is_api_request and not "text/html" in accept:
161
  # API request - return JSON error
162
  response = JSONResponse(
163
  {"detail": "Authentication required", "login_url": "/login"},
@@ -228,7 +242,7 @@ def get_login_html(error: str = None) -> str:
228
  }}
229
  .login-icon {{
230
  font-size: 3rem;
231
- margin-bottom: 20px;
232
  display: block;
233
  text-align: center;
234
  }}
@@ -334,17 +348,18 @@ def get_login_html(error: str = None) -> str:
334
 
335
  const response = await fetch('/api/auth/login', {{
336
  method: 'POST',
337
- body: formData
 
338
  }});
339
 
340
  const data = await response.json();
341
 
342
  if (response.ok && data.success) {{
343
  btnText.textContent = 'Success!';
344
- // Wait a moment for cookie to be set, then redirect
345
  setTimeout(() => {{
346
- window.location.href = '/';
347
- }}, 100);
348
  }} else {{
349
  throw new Error(data.detail || 'Authentication failed');
350
  }}
@@ -362,4 +377,4 @@ def get_login_html(error: str = None) -> str:
362
  document.getElementById('password').focus();
363
  </script>
364
  </body>
365
- </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 URLSafeSerializer, BadSignature
11
  import secrets
12
  import time
13
  from typing import Optional, Callable
 
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
 
 
36
  return {}
37
  try:
38
  serializer = get_session_serializer()
39
+ return serializer.loads(cookie_value)
40
+ except (BadSignature, Exception) as e:
41
+ # Log error for debugging but don't crash
42
+ print(f"Session decode error: {e}")
43
  return {}
44
 
45
 
 
94
 
95
  IMPORTANT: This middleware decodes the session cookie directly because
96
  SessionMiddleware hasn't run yet when this middleware executes.
97
+ Starlette middleware runs in REVERSE order - so AuthMiddleware (added first)
98
+ runs BEFORE SessionMiddleware (added second).
99
  """
100
 
101
+ # Paths that don't require authentication (exact match)
102
  PUBLIC_PATHS = [
103
  "/login",
104
  "/api/auth/login",
 
130
  await self.app(scope, receive, send)
131
  return
132
 
133
+ # Allow root path "/" - the dashboard handles its own auth check
134
+ if path == "/":
135
+ await self.app(scope, receive, send)
136
+ return
137
+
138
+ # Allow public paths
139
  if path in self.PUBLIC_PATHS:
140
  await self.app(scope, receive, send)
141
  return
 
165
  await self.app(scope, receive, send)
166
  return
167
 
168
+ # Unauthorized - return appropriate response
169
  accept = headers.get(b"accept", b"").decode("utf-8", errors="ignore")
170
 
171
  # Check if this is an API request or browser request
172
  is_api_request = path.startswith("/api/") or "application/json" in accept
173
 
174
+ if is_api_request and "text/html" not in accept:
175
  # API request - return JSON error
176
  response = JSONResponse(
177
  {"detail": "Authentication required", "login_url": "/login"},
 
242
  }}
243
  .login-icon {{
244
  font-size: 3rem;
245
+ margin-bottom: 20px;
246
  display: block;
247
  text-align: center;
248
  }}
 
348
 
349
  const response = await fetch('/api/auth/login', {{
350
  method: 'POST',
351
+ body: formData,
352
+ credentials: 'same-origin'
353
  }});
354
 
355
  const data = await response.json();
356
 
357
  if (response.ok && data.success) {{
358
  btnText.textContent = 'Success!';
359
+ // Wait for cookie to be fully set, then redirect
360
  setTimeout(() => {{
361
+ window.location.replace('/');
362
+ }}, 200);
363
  }} else {{
364
  throw new Error(data.detail || 'Authentication failed');
365
  }}
 
377
  document.getElementById('password').focus();
378
  </script>
379
  </body>
380
+ </html>'''