Hamdy005 commited on
Commit
350f1f4
·
1 Parent(s): 18308b3

feat: implement centralized token extraction via Request headers and add debug logging to search route

Browse files
Files changed (2) hide show
  1. dependencies.py +49 -45
  2. materials/routes.py +13 -4
dependencies.py CHANGED
@@ -1,4 +1,4 @@
1
- from fastapi import Depends, HTTPException, Header, status
2
  from fastapi.security import OAuth2PasswordBearer
3
  from typing import Any, Optional
4
 
@@ -10,58 +10,39 @@ DEV_USER = {"id": DEV_USER_ID, "email": "dev@studymate.ai", "name": "Dev User"}
10
  oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
11
 
12
 
13
- async def get_current_user(
14
- token: Optional[str] = Depends(oauth2_scheme),
15
- x_auth_token: Optional[str] = Header(None),
16
- authorization: Optional[str] = Header(None),
17
- ) -> Any:
18
- auth_client = get_auth_supabase()
19
- client = get_supabase()
20
 
21
- supabase_token = x_auth_token
22
- if not supabase_token and authorization:
23
- supabase_token = authorization.replace("Bearer ", "").strip()
24
- elif not supabase_token and token:
25
- supabase_token = token
26
 
27
- # If the token is a Hugging Face token (starts with hf_), ignore it for user auth
28
- if supabase_token and supabase_token.startswith("hf_"):
29
- supabase_token = None
 
 
 
 
 
30
 
31
- # Dev mode: no Supabase configured
32
- if client is None:
33
- return DEV_USER
34
-
35
- # 3. Verify the token with Supabase
36
- if supabase_token:
37
- try:
38
- verify_client = auth_client if auth_client is not None else client
39
- response = verify_client.auth.get_user(supabase_token)
40
- user = getattr(response, "user", None) or response
41
- if user:
42
- return user
43
- except Exception:
44
- pass
45
-
46
- raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Not authenticated")
47
 
48
 
49
- async def get_current_user_id(
50
- x_auth_token: Optional[str] = Header(None),
51
- authorization: Optional[str] = Header(None),
52
- ) -> str:
53
- token = x_auth_token
54
-
55
- if not token and authorization:
56
- token = authorization.replace("Bearer ", "").strip()
57
-
58
- if token and token.startswith("hf_"):
59
- token = None
60
-
61
  client = get_supabase()
 
 
62
  if client is None:
63
  return DEV_USER_ID
64
 
 
 
65
  if not token:
66
  raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Not authenticated")
67
 
@@ -71,5 +52,28 @@ async def get_current_user_id(
71
  user = supabase.auth.get_user(token)
72
  user_obj = getattr(user, "user", None) or user
73
  return str(user_obj.id)
74
- except Exception:
75
  raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid or expired token")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import Depends, HTTPException, Header, status, Request
2
  from fastapi.security import OAuth2PasswordBearer
3
  from typing import Any, Optional
4
 
 
10
  oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
11
 
12
 
13
+ def _extract_token(request: Request) -> Optional[str]:
14
+ """
15
+ Extract Supabase JWT from headers — case-insensitive.
16
+ Priority: X-Auth-Token Authorization (skip HF tokens)
17
+ """
18
+ headers = {k.lower(): v for k, v in request.headers.items()}
 
19
 
20
+ # 1. Try X-Auth-Token first (our custom header)
21
+ token = headers.get("x-auth-token")
22
+ if token:
23
+ return token
 
24
 
25
+ # 2. Fallback to Authorization header
26
+ auth = headers.get("authorization", "")
27
+ if auth.startswith("Bearer "):
28
+ token = auth[len("Bearer "):].strip()
29
+ # Skip HF tokens — they're for space access, not user auth
30
+ if token.startswith("hf_"):
31
+ return None
32
+ return token
33
 
34
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
 
37
+ async def get_current_user_id(request: Request) -> str:
 
 
 
 
 
 
 
 
 
 
 
38
  client = get_supabase()
39
+
40
+ # Dev mode
41
  if client is None:
42
  return DEV_USER_ID
43
 
44
+ token = _extract_token(request)
45
+
46
  if not token:
47
  raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Not authenticated")
48
 
 
52
  user = supabase.auth.get_user(token)
53
  user_obj = getattr(user, "user", None) or user
54
  return str(user_obj.id)
55
+ except Exception as e:
56
  raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid or expired token")
57
+
58
+
59
+ async def get_current_user(request: Request) -> Any:
60
+ auth_client = get_auth_supabase()
61
+ client = get_supabase()
62
+
63
+ # Dev mode
64
+ if client is None:
65
+ return DEV_USER
66
+
67
+ token = _extract_token(request)
68
+
69
+ if token:
70
+ try:
71
+ verify_client = auth_client if auth_client is not None else client
72
+ response = verify_client.auth.get_user(token)
73
+ user = getattr(response, "user", None) or response
74
+ if user:
75
+ return user
76
+ except Exception:
77
+ pass
78
+
79
+ raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Not authenticated")
materials/routes.py CHANGED
@@ -2,7 +2,7 @@ import time
2
  import asyncio
3
  import logging
4
  import validators
5
- from fastapi import APIRouter, UploadFile, File, HTTPException, Depends, BackgroundTasks, Header
6
  from pydantic import BaseModel
7
  from postgrest.exceptions import APIError
8
 
@@ -243,18 +243,27 @@ class SearchRequest(BaseModel):
243
 
244
  @router.post("/search")
245
  async def search_materials(
 
246
  body: SearchRequest,
247
- user_id: str = Depends(get_current_user_id)
248
  ):
 
 
 
 
 
 
 
249
  supabase = get_supabase()
250
  if not supabase:
251
- return {"results": []}
252
-
253
  result = supabase.rpc(
254
  "search_materials_by_title",
255
  {"p_query": body.q, "p_user_id": user_id}
256
  ).execute()
257
 
 
258
  return {"results": result.data}
259
 
260
  @router.delete("/{material_id}")
 
2
  import asyncio
3
  import logging
4
  import validators
5
+ from fastapi import APIRouter, UploadFile, File, HTTPException, Depends, BackgroundTasks, Header, Request
6
  from pydantic import BaseModel
7
  from postgrest.exceptions import APIError
8
 
 
243
 
244
  @router.post("/search")
245
  async def search_materials(
246
+ request: Request,
247
  body: SearchRequest,
248
+ user_id: str = Depends(get_current_user_id),
249
  ):
250
+ # Debug logs
251
+ headers = {k.lower(): v for k, v in request.headers.items()}
252
+ logger.info(f"[search] user_id={user_id}")
253
+ logger.info(f"[search] x-auth-token present: {'x-auth-token' in headers}")
254
+ logger.info(f"[search] authorization present: {'authorization' in headers}")
255
+ logger.info(f"[search] query={body.q}")
256
+
257
  supabase = get_supabase()
258
  if not supabase:
259
+ return {"results": []}
260
+
261
  result = supabase.rpc(
262
  "search_materials_by_title",
263
  {"p_query": body.q, "p_user_id": user_id}
264
  ).execute()
265
 
266
+ logger.info(f"[search] RPC returned {len(result.data)} results: {result.data}")
267
  return {"results": result.data}
268
 
269
  @router.delete("/{material_id}")