Spaces:
Running
Running
File size: 5,325 Bytes
dc64ef1 37bcb58 ef8e0a7 b65231d 37bcb58 86a47ea 962a395 8c7a11b 962a395 8c7a11b b65231d dc64ef1 b65231d 350f1f4 ef8e0a7 350f1f4 8c7a11b 350f1f4 6a3fe66 350f1f4 f3c951d 350f1f4 b65231d 962a395 350f1f4 dc64ef1 350f1f4 ef8e0a7 6a3fe66 350f1f4 6a3fe66 37bcb58 ef8e0a7 37bcb58 f55b5b5 37bcb58 f55b5b5 37bcb58 f55b5b5 ef8e0a7 6a3fe66 dc64ef1 34d7bbf 6a3fe66 350f1f4 dc64ef1 350f1f4 ef8e0a7 37bcb58 f55b5b5 37bcb58 f55b5b5 37bcb58 f55b5b5 37bcb58 ef8e0a7 350f1f4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | import time
import jwt as pyjwt
from fastapi import HTTPException, status, Request
from fastapi.security import OAuth2PasswordBearer
from typing import Any, Optional
from src.config import settings
from src.database import get_supabase, get_auth_supabase
DEV_USER_ID = "00000000-0000-0000-0000-000000000001"
DEV_USER = {"id": DEV_USER_ID, "email": "dev@studymate.ai", "name": "Dev User"}
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
_TOKEN_CACHE: dict = {}
_TOKEN_CACHE_TTL = 300 # 5 minutes
def _verify_token_cached(client, token: str) -> Any:
now = time.time()
# Simple cleanup to prevent unbounded growth
if len(_TOKEN_CACHE) > 1000:
expired = [k for k, v in _TOKEN_CACHE.items() if now - v[1] > _TOKEN_CACHE_TTL]
for k in expired:
del _TOKEN_CACHE[k]
# Return cached user if valid
if token in _TOKEN_CACHE:
user, timestamp = _TOKEN_CACHE[token]
if now - timestamp < _TOKEN_CACHE_TTL:
return user
# Not cached or expired β fetch from Supabase
response = client.auth.get_user(token)
user = getattr(response, "user", None) or response
if user:
_TOKEN_CACHE[token] = (user, now)
return user
raise ValueError("Invalid token response")
def _extract_token(request: Request) -> Optional[str]:
"""
Extract JWT from headers β case-insensitive.
Priority: X-Auth-Token β Authorization (skip HF space tokens)
"""
headers = {k.lower(): v for k, v in request.headers.items()}
# 1. Try X-Auth-Token first (our custom header)
token = headers.get("x-auth-token")
if token:
return token
# 2. Fallback to Authorization header
auth = headers.get("authorization", "")
if auth.startswith("Bearer "):
token = auth[len("Bearer "):].strip()
# Skip HF tokens β they're for space access, not user auth
if token.startswith("hf_"):
return None
return token
return None
async def get_current_user_id(request: Request) -> str:
client = get_auth_supabase() or get_supabase()
# Dev mode β no Supabase configured
if client is None:
return DEV_USER_ID
token = _extract_token(request)
if not token:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Not authenticated")
# ββ Mode 1: our own stateless JWT ββββββββββββββββββββββββββββββββββββββββ
try:
from src.auth.jwt_utils import decode_access_token
payload = decode_access_token(token)
user_id = payload.get("sub")
if user_id:
return str(user_id)
except Exception:
pass
# ββ Mode 1b: Supabase JWT stateless fallback (avoids 403 network race) βββ
if settings.supabase_jwt_secret:
try:
payload = pyjwt.decode(
token,
settings.supabase_jwt_secret,
algorithms=["HS256", "HS384", "HS512"],
options={"verify_aud": False},
)
user_id = payload.get("sub")
if user_id:
return str(user_id)
except Exception:
pass
# ββ Mode 2: Supabase token (backwards-compat for Google OAuth sessions) ββ
try:
user = _verify_token_cached(client, token)
return str(user.id)
except Exception as e:
print(f"Token validation error: {e}")
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid or expired token")
async def get_current_user(request: Request) -> Any:
client = get_auth_supabase() or get_supabase()
# Dev mode
if client is None:
return DEV_USER
token = _extract_token(request)
if not token:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Not authenticated")
# ββ Mode 1: our own stateless JWT ββββββββββββββββββββββββββββββββββββββββ
try:
from src.auth.jwt_utils import decode_access_token
payload = decode_access_token(token)
user_id = payload.get("sub")
if user_id:
return {"id": user_id, "email": payload.get("email", "")}
except Exception:
pass
# ββ Mode 1b: Supabase JWT stateless fallback βββββββββββββββββββββββββββββ
if settings.supabase_jwt_secret:
try:
payload = pyjwt.decode(
token,
settings.supabase_jwt_secret,
algorithms=["HS256", "HS384", "HS512"],
options={"verify_aud": False},
)
user_id = payload.get("sub")
if user_id:
return {"id": str(user_id), "email": payload.get("email", "")}
except Exception:
pass
# ββ Mode 2: Supabase token (backwards compat) ββββββββββββββββββββββββββββ
try:
user = _verify_token_cached(client, token)
if user:
return user
except Exception as e:
print(f"Token validation error: {e}")
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Not authenticated") |