GitHub Actions commited on
Commit
e673ce2
·
1 Parent(s): 0e9cbc7

🚀 Auto-deploy from GitHub

Browse files
app/api/v1/endpoints/auth.py CHANGED
@@ -11,6 +11,7 @@ import uuid
11
  import logging
12
  from ..schemas.auth_schemas import TokenRequest, TokenResponse, UserRegistrationRequest, UserResponse
13
  from ....core.config import settings
 
14
  from ....services.database import (
15
  get_user_by_username,
16
  get_user_by_email,
@@ -61,14 +62,14 @@ async def get_access_token(credentials: TokenRequest, request: Request):
61
  # Create session in database
62
  await create_user_session(user["id"], jti, expires_at)
63
 
64
- # Generate a temporary JWT token
65
  payload = {
66
- "sub": credentials.username,
67
- "user_id": user["id"],
68
- "jti": jti,
69
- "exp": expires_at,
70
- "iat": datetime.utcnow(),
71
- "type": "access_token"
72
  }
73
 
74
  secret_key = os.getenv("SECRET_KEY", "your-secret-key-change-this")
@@ -255,6 +256,14 @@ async def logout_user(request: Request, token: str = Depends(security)):
255
  )
256
  raise HTTPException(status_code=401, detail="Invalid token")
257
 
 
 
 
 
 
 
 
 
258
  async def validate_user_credentials(credentials: Dict[str, Any]) -> Dict[str, Any] | None:
259
  """
260
  Validate user credentials against your Supabase database
 
11
  import logging
12
  from ..schemas.auth_schemas import TokenRequest, TokenResponse, UserRegistrationRequest, UserResponse
13
  from ....core.config import settings
14
+ from ....core.auth import authenticate_request, require_current_user
15
  from ....services.database import (
16
  get_user_by_username,
17
  get_user_by_email,
 
62
  # Create session in database
63
  await create_user_session(user["id"], jti, expires_at)
64
 
65
+ # Generate a temporary JWT token with minimal payload
66
  payload = {
67
+ "sub": credentials.username, # Subject (username) - needed for user lookup
68
+ "jti": jti, # JWT ID for session tracking
69
+ "exp": expires_at, # Expiration time
70
+ "iat": datetime.utcnow(), # Issued at time
71
+ "type": "access_token" # Token type
72
+ # Note: user_id removed - will be looked up from database using username
73
  }
74
 
75
  secret_key = os.getenv("SECRET_KEY", "your-secret-key-change-this")
 
256
  )
257
  raise HTTPException(status_code=401, detail="Invalid token")
258
 
259
+ @router.get("/auth/me", response_model=UserResponse)
260
+ async def get_current_user_info(current_user: Dict[str, Any] = Depends(require_current_user)):
261
+ """
262
+ Get current authenticated user information
263
+ Requires valid JWT token authentication
264
+ """
265
+ return UserResponse(**current_user)
266
+
267
  async def validate_user_credentials(credentials: Dict[str, Any]) -> Dict[str, Any] | None:
268
  """
269
  Validate user credentials against your Supabase database
app/api/v1/endpoints/download.py CHANGED
@@ -3,7 +3,7 @@ from fastapi.responses import FileResponse
3
  from pathlib import Path
4
  from ....services.database import get_supabase_client # Corrected import
5
  from ....core.config import settings # Import settings
6
- from ....core.auth import verify_hf_token
7
  from supabase import Client
8
 
9
  router = APIRouter()
@@ -12,7 +12,7 @@ router = APIRouter()
12
  async def download_generated_image(
13
  card_id: str, # card_id from path
14
  supabase: Client = Depends(get_supabase_client),
15
- authenticated: bool = Depends(verify_hf_token)
16
  ):
17
  """
18
  Download a custom generated image using the card_id to find the image path from Supabase.
 
3
  from pathlib import Path
4
  from ....services.database import get_supabase_client # Corrected import
5
  from ....core.config import settings # Import settings
6
+ from ....core.auth import authenticate_request
7
  from supabase import Client
8
 
9
  router = APIRouter()
 
12
  async def download_generated_image(
13
  card_id: str, # card_id from path
14
  supabase: Client = Depends(get_supabase_client),
15
+ authenticated: bool = Depends(authenticate_request)
16
  ):
17
  """
18
  Download a custom generated image using the card_id to find the image path from Supabase.
app/api/v1/endpoints/generate.py CHANGED
@@ -2,6 +2,7 @@ from fastapi import APIRouter, HTTPException, Depends
2
  from dotenv import load_dotenv
3
  from supabase import Client
4
  import uuid
 
5
  from ..schemas.card_schemas import CardGenerateRequest, CardGenerateResponse
6
  from ....core.generator import build_prompt # get_constellation wird hier nicht direkt verwendet
7
  from ....core.card_renderer import generate_card as render_card_sync # Umbenennen für Klarheit
@@ -10,7 +11,7 @@ from ....services.database import get_supabase_client, save_card
10
  from ....core.config import settings
11
  from ....core.model_loader import get_generator
12
  from ....core.constraints import generate_with_retry, check_constraints
13
- from ....core.auth import verify_hf_token
14
  from fastapi.concurrency import run_in_threadpool # Importieren
15
 
16
  load_dotenv()
@@ -28,12 +29,17 @@ async def generate_qr_code_async(*args, **kwargs):
28
  async def generate_endpoint(
29
  request: CardGenerateRequest,
30
  supabase: Client = Depends(get_supabase_client),
31
- authenticated: bool = Depends(verify_hf_token)
 
32
  ):
33
  try:
34
  lang = request.lang or "de"
35
  input_date_str = request.card_date.isoformat()
36
 
 
 
 
 
37
  card_prompt = build_prompt(
38
  lang=lang,
39
  card_date=input_date_str,
 
2
  from dotenv import load_dotenv
3
  from supabase import Client
4
  import uuid
5
+ from typing import Optional, Dict, Any
6
  from ..schemas.card_schemas import CardGenerateRequest, CardGenerateResponse
7
  from ....core.generator import build_prompt # get_constellation wird hier nicht direkt verwendet
8
  from ....core.card_renderer import generate_card as render_card_sync # Umbenennen für Klarheit
 
11
  from ....core.config import settings
12
  from ....core.model_loader import get_generator
13
  from ....core.constraints import generate_with_retry, check_constraints
14
+ from ....core.auth import authenticate_request, get_current_user
15
  from fastapi.concurrency import run_in_threadpool # Importieren
16
 
17
  load_dotenv()
 
29
  async def generate_endpoint(
30
  request: CardGenerateRequest,
31
  supabase: Client = Depends(get_supabase_client),
32
+ authenticated: bool = Depends(authenticate_request),
33
+ current_user: Optional[Dict[str, Any]] = Depends(get_current_user)
34
  ):
35
  try:
36
  lang = request.lang or "de"
37
  input_date_str = request.card_date.isoformat()
38
 
39
+ # Log user context if available
40
+ user_id = current_user["id"] if current_user else None
41
+ username = current_user["username"] if current_user else "anonymous"
42
+
43
  card_prompt = build_prompt(
44
  lang=lang,
45
  card_date=input_date_str,
app/api/v1/endpoints/health.py CHANGED
@@ -1,6 +1,6 @@
1
  from fastapi import APIRouter, HTTPException, Depends
2
  from ..schemas.health_schema import HealthResponse
3
- from ....core.auth import verify_hf_token
4
  import httpx
5
  import asyncio
6
  from typing import Optional
@@ -26,7 +26,7 @@ async def check_huggingface_space():
26
  return "unreachable"
27
 
28
  @router.get("/health", response_model=HealthResponse)
29
- async def health_check(authenticated: bool = Depends(verify_hf_token)):
30
  """
31
  Health check endpoint that verifies server status, model loading status and HuggingFace space availability
32
  """
 
1
  from fastapi import APIRouter, HTTPException, Depends
2
  from ..schemas.health_schema import HealthResponse
3
+ from ....core.auth import authenticate_request
4
  import httpx
5
  import asyncio
6
  from typing import Optional
 
26
  return "unreachable"
27
 
28
  @router.get("/health", response_model=HealthResponse)
29
+ async def health_check(authenticated: bool = Depends(authenticate_request)):
30
  """
31
  Health check endpoint that verifies server status, model loading status and HuggingFace space availability
32
  """
app/core/auth.py CHANGED
@@ -1,62 +1,89 @@
1
  """
2
- Authentication middleware for HuggingFace API token validation
 
3
  """
4
  from fastapi import HTTPException, status, Depends
5
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
6
- from typing import Optional
7
  import os
8
  import jwt
 
9
  from datetime import datetime
10
  from dotenv import load_dotenv
11
- from ..services.database import get_user_session
12
 
13
  load_dotenv()
14
 
15
  security = HTTPBearer(auto_error=False)
 
16
 
17
- def get_hf_api_key() -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  """Get HuggingFace API key from environment"""
19
- return os.getenv("HF_API_KEY", "")
20
 
21
- async def verify_hf_token(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)) -> bool:
22
  """
23
- Verify HuggingFace API token or JWT token
24
- Returns True if no authentication is required (public space) or if token is valid
 
 
 
 
 
 
 
 
 
25
  """
26
  expected_token = get_hf_api_key()
27
 
28
- # If no HF_API_KEY is set, allow public access
29
- if not expected_token:
30
- return True
31
-
32
- # If HF_API_KEY is set but no credentials provided, deny access
33
  if not credentials:
34
  raise HTTPException(
35
  status_code=status.HTTP_401_UNAUTHORIZED,
36
- detail="Authentication required. Please provide a valid HuggingFace API token.",
37
  headers={"WWW-Authenticate": "Bearer"},
38
  )
39
 
40
  token = credentials.credentials
41
 
42
- # Try to verify as HuggingFace API key first
43
- if token == expected_token:
44
  return True
45
 
46
- # Try to verify as JWT token (if you implement JWT auth)
47
  try:
48
- secret_key = os.getenv("SECRET_KEY", "your-secret-key")
49
- payload = jwt.decode(token, secret_key, algorithms=["HS256"])
50
-
51
- # Verify token hasn't expired
52
- if payload.get("exp") and datetime.fromtimestamp(payload["exp"]) < datetime.utcnow():
53
- raise jwt.ExpiredSignatureError("Token has expired")
 
 
 
 
54
 
55
  # Check if session is still valid (not revoked)
56
  jti = payload.get("jti")
57
  if jti:
58
  session = await get_user_session(jti)
59
  if not session:
 
60
  raise HTTPException(
61
  status_code=status.HTTP_401_UNAUTHORIZED,
62
  detail="Session has been revoked",
@@ -64,7 +91,18 @@ async def verify_hf_token(credentials: Optional[HTTPAuthorizationCredentials] =
64
  )
65
 
66
  return True
67
- except jwt.InvalidTokenError:
 
 
 
 
 
 
 
 
 
 
 
68
  pass
69
 
70
  # If neither verification method worked, deny access
@@ -76,33 +114,32 @@ async def verify_hf_token(credentials: Optional[HTTPAuthorizationCredentials] =
76
 
77
  async def optional_auth(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)) -> bool:
78
  """
79
- Optional authentication - doesn't raise errors if no token provided
80
- Used for endpoints that can work with or without authentication
 
81
  """
82
- expected_token = get_hf_api_key()
83
-
84
- # If no HF_API_KEY is set, allow public access
85
- if not expected_token:
86
- return True
87
-
88
- # If no credentials provided, still allow access (optional auth)
89
  if not credentials:
90
  return False
91
 
92
- # If credentials provided, verify them
93
  token = credentials.credentials
 
94
 
95
  # Check HF API key
96
- if token == expected_token:
97
  return True
98
 
99
  # Check JWT token
100
  try:
101
- secret_key = os.getenv("SECRET_KEY", "your-secret-key")
102
- payload = jwt.decode(token, secret_key, algorithms=["HS256"])
103
-
104
- if payload.get("exp") and datetime.fromtimestamp(payload["exp"]) < datetime.utcnow():
105
- return False
 
 
 
 
 
106
 
107
  # Check session validity
108
  jti = payload.get("jti")
@@ -113,3 +150,78 @@ async def optional_auth(credentials: Optional[HTTPAuthorizationCredentials] = De
113
  return True
114
  except jwt.InvalidTokenError:
115
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ Authentication middleware for JWT token validation with Supabase database integration.
3
+ Supports dual authentication: JWT tokens for users and HuggingFace API key for admin access.
4
  """
5
  from fastapi import HTTPException, status, Depends
6
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
7
+ from typing import Optional, Dict, Any
8
  import os
9
  import jwt
10
+ import logging
11
  from datetime import datetime
12
  from dotenv import load_dotenv
13
+ from ..services.database import get_user_session, get_user_by_username
14
 
15
  load_dotenv()
16
 
17
  security = HTTPBearer(auto_error=False)
18
+ logger = logging.getLogger(__name__)
19
 
20
+ def get_secret_key() -> str:
21
+ """Get JWT secret key from environment"""
22
+ secret_key = os.getenv("SECRET_KEY")
23
+ if not secret_key:
24
+ raise ValueError("SECRET_KEY environment variable not set. Cannot issue or verify JWTs.")
25
+ return secret_key
26
+
27
+ def get_jwt_issuer() -> Optional[str]:
28
+ """Get JWT issuer from environment"""
29
+ return os.getenv("JWT_ISSUER")
30
+
31
+ def get_jwt_audience() -> Optional[str]:
32
+ """Get JWT audience from environment"""
33
+ return os.getenv("JWT_AUDIENCE")
34
+
35
+ def get_hf_api_key() -> Optional[str]:
36
  """Get HuggingFace API key from environment"""
37
+ return os.getenv("HF_API_KEY")
38
 
39
+ async def authenticate_request(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)) -> bool:
40
  """
41
+ Primary authentication dependency for protected endpoints.
42
+ Implements dual authentication strategy:
43
+ 1. HuggingFace API key (admin bypass) - simple string comparison
44
+ 2. JWT token (user authentication) - cryptographic validation + session verification
45
+
46
+ For JWT tokens:
47
+ - Validates signature, expiration, audience, and issuer
48
+ - Checks session validity in Supabase database via 'jti' claim
49
+ - Rejects revoked sessions
50
+
51
+ Returns True if authentication succeeds, otherwise raises HTTPException.
52
  """
53
  expected_token = get_hf_api_key()
54
 
 
 
 
 
 
55
  if not credentials:
56
  raise HTTPException(
57
  status_code=status.HTTP_401_UNAUTHORIZED,
58
+ detail="Authentication required. Please provide a valid JWT token or HuggingFace API key.",
59
  headers={"WWW-Authenticate": "Bearer"},
60
  )
61
 
62
  token = credentials.credentials
63
 
64
+ # Check HuggingFace API key first (admin bypass - performance optimization)
65
+ if expected_token and token == expected_token:
66
  return True
67
 
68
+ # Validate JWT token with full session verification
69
  try:
70
+ secret_key = get_secret_key()
71
+ issuer = get_jwt_issuer()
72
+ audience = get_jwt_audience()
73
+ payload = jwt.decode(
74
+ token,
75
+ secret_key,
76
+ algorithms=["HS256"],
77
+ audience=audience,
78
+ issuer=issuer
79
+ )
80
 
81
  # Check if session is still valid (not revoked)
82
  jti = payload.get("jti")
83
  if jti:
84
  session = await get_user_session(jti)
85
  if not session:
86
+ logger.warning(f"JWT verification failed: Session has been revoked for jti: {jti}")
87
  raise HTTPException(
88
  status_code=status.HTTP_401_UNAUTHORIZED,
89
  detail="Session has been revoked",
 
91
  )
92
 
93
  return True
94
+ except jwt.ExpiredSignatureError:
95
+ logger.warning("JWT verification failed: Token has expired")
96
+ raise HTTPException(
97
+ status_code=status.HTTP_401_UNAUTHORIZED,
98
+ detail="Token has expired",
99
+ headers={"WWW-Authenticate": "Bearer"},
100
+ )
101
+ except jwt.InvalidTokenError as e:
102
+ logger.warning(f"JWT verification failed: Invalid token - {e}")
103
+ # Potential Issue: Broad exception handling. Catching InvalidTokenError is a safe default
104
+ # to avoid leaking error details, but it can make debugging harder.
105
+ # Consider logging the specific error here for internal monitoring.
106
  pass
107
 
108
  # If neither verification method worked, deny access
 
114
 
115
  async def optional_auth(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)) -> bool:
116
  """
117
+ Optional authentication - doesn't raise errors if no token provided.
118
+ Returns True if authentication is successful, False otherwise.
119
+ Used for endpoints that can work with or without authentication.
120
  """
 
 
 
 
 
 
 
121
  if not credentials:
122
  return False
123
 
 
124
  token = credentials.credentials
125
+ expected_token = get_hf_api_key()
126
 
127
  # Check HF API key
128
+ if expected_token and token == expected_token:
129
  return True
130
 
131
  # Check JWT token
132
  try:
133
+ secret_key = get_secret_key()
134
+ issuer = get_jwt_issuer()
135
+ audience = get_jwt_audience()
136
+ payload = jwt.decode(
137
+ token,
138
+ secret_key,
139
+ algorithms=["HS256"],
140
+ audience=audience,
141
+ issuer=issuer
142
+ )
143
 
144
  # Check session validity
145
  jti = payload.get("jti")
 
150
  return True
151
  except jwt.InvalidTokenError:
152
  return False
153
+
154
+ async def get_current_user(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)) -> Optional[Dict[str, Any]]:
155
+ """
156
+ Extract authenticated user data from JWT token.
157
+
158
+ Returns:
159
+ - User data dict if authenticated with valid JWT token
160
+ - None if using HuggingFace API key (no user context)
161
+ - None if not authenticated or invalid token
162
+
163
+ For JWT tokens:
164
+ - Validates token signature and session in Supabase
165
+ - Retrieves full user data from database using 'sub' claim (username)
166
+ """
167
+ if not credentials:
168
+ return None
169
+
170
+ token = credentials.credentials
171
+
172
+ # Check if it's an HF API key (these don't have user context)
173
+ expected_hf_token = get_hf_api_key()
174
+ if expected_hf_token and token == expected_hf_token:
175
+ return None # HF API key users don't have user context
176
+
177
+ # Try to decode JWT token
178
+ try:
179
+ secret_key = get_secret_key()
180
+ issuer = get_jwt_issuer()
181
+ audience = get_jwt_audience()
182
+ payload = jwt.decode(
183
+ token,
184
+ secret_key,
185
+ algorithms=["HS256"],
186
+ audience=audience,
187
+ issuer=issuer
188
+ )
189
+
190
+ # Check if session is still valid
191
+ jti = payload.get("jti")
192
+ if jti:
193
+ session = await get_user_session(jti)
194
+ if not session:
195
+ return None
196
+
197
+ # Get user data from database using username from token
198
+ username = payload.get("sub")
199
+ if username:
200
+ user = await get_user_by_username(username)
201
+ return user
202
+
203
+ return None
204
+ except jwt.InvalidTokenError:
205
+ return None
206
+
207
+ async def require_current_user(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)) -> Dict[str, Any]:
208
+ """
209
+ Extract authenticated user data from JWT token - mandatory authentication.
210
+
211
+ Use this dependency for endpoints that require user authentication.
212
+ Raises HTTPException if:
213
+ - No credentials provided
214
+ - Using HuggingFace API key (no user context)
215
+ - Invalid or expired JWT token
216
+ - Revoked session
217
+
218
+ Returns: User data dict from Supabase database
219
+ """
220
+ user = await get_current_user(credentials)
221
+ if not user:
222
+ raise HTTPException(
223
+ status_code=status.HTTP_401_UNAUTHORIZED,
224
+ detail="Authentication required. Please provide a valid JWT token.",
225
+ headers={"WWW-Authenticate": "Bearer"},
226
+ )
227
+ return user
docs/authentication.md ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Authentication System Documentation
2
+
3
+ ## Übersicht
4
+
5
+ Das Authentifizierungssystem unterstützt zwei Arten von Tokens:
6
+ 1. **HuggingFace API Keys** - für API-Zugriff ohne Benutzerkontext
7
+ 2. **JWT Tokens** - für authentifizierte Benutzer mit Benutzerkontext
8
+
9
+ ## Dependency Functions
10
+
11
+ ### 1. `verify_hf_token()`
12
+ - **Zweck**: Grundlegende Authentifizierung
13
+ - **Rückgabe**: `bool` - True wenn authentifiziert
14
+ - **Verwendung**: Für Endpoints, die Authentifizierung benötigen, aber keinen Benutzerkontext
15
+
16
+ ```python
17
+ @router.post("/some-endpoint")
18
+ async def endpoint(authenticated: bool = Depends(verify_hf_token)):
19
+ # Endpoint Logic
20
+ ```
21
+
22
+ ### 2. `get_current_user()`
23
+ - **Zweck**: Optionale Benutzer-Extraktion
24
+ - **Rückgabe**: `Optional[Dict[str, Any]]` - Benutzerdaten oder None
25
+ - **Verwendung**: Für Endpoints, die optional Benutzerkontext nutzen können
26
+
27
+ ```python
28
+ @router.post("/some-endpoint")
29
+ async def endpoint(
30
+ authenticated: bool = Depends(verify_hf_token),
31
+ current_user: Optional[Dict[str, Any]] = Depends(get_current_user)
32
+ ):
33
+ if current_user:
34
+ user_id = current_user["id"]
35
+ username = current_user["username"]
36
+ # Endpoint Logic
37
+ ```
38
+
39
+ ### 3. `require_current_user()`
40
+ - **Zweck**: Pflicht-Benutzer-Authentifizierung
41
+ - **Rückgabe**: `Dict[str, Any]` - Benutzerdaten (oder 401 Error)
42
+ - **Verwendung**: Für Endpoints, die definitiv einen authentifizierten Benutzer benötigen
43
+
44
+ ```python
45
+ @router.post("/user-only-endpoint")
46
+ async def endpoint(current_user: Dict[str, Any] = Depends(require_current_user)):
47
+ user_id = current_user["id"]
48
+ username = current_user["username"]
49
+ # Endpoint Logic
50
+ ```
51
+
52
+ ## Token-Payload
53
+
54
+ Das JWT Token enthält minimale Informationen:
55
+
56
+ ```json
57
+ {
58
+ "sub": "username", // Username für Datenbanksuche
59
+ "jti": "unique-session-id", // Session-Tracking
60
+ "exp": 1735689600, // Ablaufzeit
61
+ "iat": 1735603200, // Ausstellungszeit
62
+ "type": "access_token" // Token-Typ
63
+ }
64
+ ```
65
+
66
+ **Wichtig**: Die `user_id` wird nicht mehr im Token gespeichert, sondern zur Laufzeit aus der Datenbank geholt.
67
+
68
+ ## Neue Endpoints
69
+
70
+ ### GET `/api/v1/auth/me`
71
+ Gibt Informationen über den aktuell authentifizierten Benutzer zurück.
72
+
73
+ **Authentifizierung**: JWT Token erforderlich
74
+ **Response**: UserResponse-Schema
75
+
76
+ ```bash
77
+ curl -H "Authorization: Bearer <jwt_token>" \
78
+ http://localhost:8000/api/v1/auth/me
79
+ ```
80
+
81
+ ## Sicherheitsverbesserungen
82
+
83
+ 1. **Minimaler Token-Payload**: Sensible Daten werden nicht im Token gespeichert
84
+ 2. **Session-Tracking**: Jeder Token ist mit einer Session in der Datenbank verknüpft
85
+ 3. **Flexible Authentifizierung**: Verschiedene Dependency-Funktionen für verschiedene Anwendungsfälle
86
+ 4. **Token-Widerruf**: Sessions können serverseitig widerrufen werden
87
+
88
+ ## Best Practices
89
+
90
+ 1. **SECRET_KEY sicher setzen**:
91
+ ```bash
92
+ export SECRET_KEY=$(openssl rand -hex 32)
93
+ ```
94
+
95
+ 2. **Richtige Dependency wählen**:
96
+ - `verify_hf_token`: Grundlegende Auth ohne Benutzerkontext
97
+ - `get_current_user`: Optionaler Benutzerkontext
98
+ - `require_current_user`: Pflicht-Benutzerkontext
99
+
100
+ 3. **Benutzer-Logging**: Nutzen Sie `current_user` für Audit-Logs
101
+
102
+ ```python
103
+ if current_user:
104
+ logger.info(f"Action performed by user {current_user['username']} (ID: {current_user['id']})")
105
+ ```
tests/test_authentication.py CHANGED
@@ -120,7 +120,33 @@ def main():
120
  "lang": "en"
121
  },
122
  "should_succeed": False
123
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  ]
125
 
126
  results = []
 
120
  "lang": "en"
121
  },
122
  "should_succeed": False
123
+ },
124
+
125
+ # Test new user info endpoint
126
+ {
127
+ "name": "Get Current User (Valid JWT - after login)",
128
+ "endpoint": "/api/v1/auth/me",
129
+ "method": "GET",
130
+ "headers": {}, # Will be filled after login
131
+ "should_succeed": True,
132
+ "requires_jwt": True
133
+ },
134
+
135
+ {
136
+ "name": "Get Current User (No Auth)",
137
+ "endpoint": "/api/v1/auth/me",
138
+ "method": "GET",
139
+ "headers": no_auth_headers,
140
+ "should_succeed": False
141
+ },
142
+
143
+ {
144
+ "name": "Get Current User (HF API Key)",
145
+ "endpoint": "/api/v1/auth/me",
146
+ "method": "GET",
147
+ "headers": auth_headers,
148
+ "should_succeed": False # HF API key should not work for user endpoints
149
+ },
150
  ]
151
 
152
  results = []