turboapi 0.4.12__cp314-cp314t-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- turboapi/__init__.py +24 -0
- turboapi/async_limiter.py +86 -0
- turboapi/async_pool.py +141 -0
- turboapi/decorators.py +69 -0
- turboapi/main_app.py +314 -0
- turboapi/middleware.py +342 -0
- turboapi/models.py +148 -0
- turboapi/request_handler.py +227 -0
- turboapi/routing.py +219 -0
- turboapi/rust_integration.py +335 -0
- turboapi/security.py +542 -0
- turboapi/server_integration.py +436 -0
- turboapi/turbonet.cpython-314t-darwin.so +0 -0
- turboapi/version_check.py +268 -0
- turboapi-0.4.12.dist-info/METADATA +31 -0
- turboapi-0.4.12.dist-info/RECORD +17 -0
- turboapi-0.4.12.dist-info/WHEEL +4 -0
turboapi/security.py
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI-compatible Security and Authentication for TurboAPI.
|
|
3
|
+
|
|
4
|
+
Includes:
|
|
5
|
+
- OAuth2 (Password Bearer, Authorization Code)
|
|
6
|
+
- HTTP Basic Authentication
|
|
7
|
+
- HTTP Bearer Authentication
|
|
8
|
+
- API Key Authentication (Header, Query, Cookie)
|
|
9
|
+
- Security scopes and dependencies
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Optional, List, Dict, Any, Callable
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
import secrets
|
|
15
|
+
import base64
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ============================================================================
|
|
19
|
+
# Base Security Classes
|
|
20
|
+
# ============================================================================
|
|
21
|
+
|
|
22
|
+
class SecurityBase:
|
|
23
|
+
"""Base class for all security schemes."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, *, scheme_name: Optional[str] = None, auto_error: bool = True):
|
|
26
|
+
self.scheme_name = scheme_name
|
|
27
|
+
self.auto_error = auto_error
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# OAuth2 Authentication
|
|
32
|
+
# ============================================================================
|
|
33
|
+
|
|
34
|
+
class OAuth2PasswordBearer(SecurityBase):
|
|
35
|
+
"""
|
|
36
|
+
OAuth2 password bearer token authentication.
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
40
|
+
|
|
41
|
+
@app.get("/users/me")
|
|
42
|
+
async def get_user(token: str = Depends(oauth2_scheme)):
|
|
43
|
+
return {"token": token}
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
tokenUrl: str,
|
|
49
|
+
scheme_name: Optional[str] = None,
|
|
50
|
+
scopes: Optional[Dict[str, str]] = None,
|
|
51
|
+
description: Optional[str] = None,
|
|
52
|
+
auto_error: bool = True,
|
|
53
|
+
):
|
|
54
|
+
super().__init__(scheme_name=scheme_name, auto_error=auto_error)
|
|
55
|
+
self.tokenUrl = tokenUrl
|
|
56
|
+
self.scopes = scopes or {}
|
|
57
|
+
self.description = description
|
|
58
|
+
self.model = {
|
|
59
|
+
"type": "oauth2",
|
|
60
|
+
"flows": {
|
|
61
|
+
"password": {
|
|
62
|
+
"tokenUrl": tokenUrl,
|
|
63
|
+
"scopes": self.scopes,
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def __call__(self, authorization: Optional[str] = None) -> Optional[str]:
|
|
69
|
+
"""Extract token from Authorization header."""
|
|
70
|
+
if not authorization:
|
|
71
|
+
if self.auto_error:
|
|
72
|
+
raise HTTPException(
|
|
73
|
+
status_code=401,
|
|
74
|
+
detail="Not authenticated",
|
|
75
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
76
|
+
)
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
scheme, _, token = authorization.partition(" ")
|
|
80
|
+
if scheme.lower() != "bearer":
|
|
81
|
+
if self.auto_error:
|
|
82
|
+
raise HTTPException(
|
|
83
|
+
status_code=401,
|
|
84
|
+
detail="Invalid authentication credentials",
|
|
85
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
86
|
+
)
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
return token
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class OAuth2PasswordRequestForm:
|
|
94
|
+
"""
|
|
95
|
+
OAuth2 password request form data.
|
|
96
|
+
|
|
97
|
+
Automatically parses form data for OAuth2 password flow.
|
|
98
|
+
"""
|
|
99
|
+
username: str
|
|
100
|
+
password: str
|
|
101
|
+
scope: str = ""
|
|
102
|
+
grant_type: Optional[str] = "password"
|
|
103
|
+
client_id: Optional[str] = None
|
|
104
|
+
client_secret: Optional[str] = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class OAuth2AuthorizationCodeBearer(SecurityBase):
|
|
108
|
+
"""
|
|
109
|
+
OAuth2 authorization code flow with bearer token.
|
|
110
|
+
|
|
111
|
+
Usage:
|
|
112
|
+
oauth2_scheme = OAuth2AuthorizationCodeBearer(
|
|
113
|
+
authorizationUrl="https://example.com/oauth/authorize",
|
|
114
|
+
tokenUrl="https://example.com/oauth/token"
|
|
115
|
+
)
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
authorizationUrl: str,
|
|
121
|
+
tokenUrl: str,
|
|
122
|
+
refreshUrl: Optional[str] = None,
|
|
123
|
+
scheme_name: Optional[str] = None,
|
|
124
|
+
scopes: Optional[Dict[str, str]] = None,
|
|
125
|
+
description: Optional[str] = None,
|
|
126
|
+
auto_error: bool = True,
|
|
127
|
+
):
|
|
128
|
+
super().__init__(scheme_name=scheme_name, auto_error=auto_error)
|
|
129
|
+
self.authorizationUrl = authorizationUrl
|
|
130
|
+
self.tokenUrl = tokenUrl
|
|
131
|
+
self.refreshUrl = refreshUrl
|
|
132
|
+
self.scopes = scopes or {}
|
|
133
|
+
self.description = description
|
|
134
|
+
self.model = {
|
|
135
|
+
"type": "oauth2",
|
|
136
|
+
"flows": {
|
|
137
|
+
"authorizationCode": {
|
|
138
|
+
"authorizationUrl": authorizationUrl,
|
|
139
|
+
"tokenUrl": tokenUrl,
|
|
140
|
+
"refreshUrl": refreshUrl,
|
|
141
|
+
"scopes": self.scopes,
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def __call__(self, authorization: Optional[str] = None) -> Optional[str]:
|
|
147
|
+
"""Extract token from Authorization header."""
|
|
148
|
+
if not authorization:
|
|
149
|
+
if self.auto_error:
|
|
150
|
+
raise HTTPException(
|
|
151
|
+
status_code=401,
|
|
152
|
+
detail="Not authenticated",
|
|
153
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
154
|
+
)
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
scheme, _, token = authorization.partition(" ")
|
|
158
|
+
if scheme.lower() != "bearer":
|
|
159
|
+
if self.auto_error:
|
|
160
|
+
raise HTTPException(
|
|
161
|
+
status_code=401,
|
|
162
|
+
detail="Invalid authentication credentials",
|
|
163
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
164
|
+
)
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
return token
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ============================================================================
|
|
171
|
+
# HTTP Basic Authentication
|
|
172
|
+
# ============================================================================
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class HTTPBasicCredentials:
|
|
176
|
+
"""HTTP Basic authentication credentials."""
|
|
177
|
+
username: str
|
|
178
|
+
password: str
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class HTTPBasic(SecurityBase):
|
|
182
|
+
"""
|
|
183
|
+
HTTP Basic authentication.
|
|
184
|
+
|
|
185
|
+
Usage:
|
|
186
|
+
security = HTTPBasic()
|
|
187
|
+
|
|
188
|
+
@app.get("/users/me")
|
|
189
|
+
def get_user(credentials: HTTPBasicCredentials = Depends(security)):
|
|
190
|
+
return {"username": credentials.username}
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(
|
|
194
|
+
self,
|
|
195
|
+
*,
|
|
196
|
+
scheme_name: Optional[str] = None,
|
|
197
|
+
realm: Optional[str] = None,
|
|
198
|
+
auto_error: bool = True,
|
|
199
|
+
):
|
|
200
|
+
super().__init__(scheme_name=scheme_name, auto_error=auto_error)
|
|
201
|
+
self.realm = realm
|
|
202
|
+
self.model = {"type": "http", "scheme": "basic"}
|
|
203
|
+
|
|
204
|
+
def __call__(self, authorization: Optional[str] = None) -> Optional[HTTPBasicCredentials]:
|
|
205
|
+
"""Extract and decode Basic auth credentials."""
|
|
206
|
+
if not authorization:
|
|
207
|
+
if self.auto_error:
|
|
208
|
+
raise HTTPException(
|
|
209
|
+
status_code=401,
|
|
210
|
+
detail="Not authenticated",
|
|
211
|
+
headers={"WWW-Authenticate": f'Basic realm="{self.realm}"' if self.realm else "Basic"},
|
|
212
|
+
)
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
scheme, _, credentials = authorization.partition(" ")
|
|
216
|
+
if scheme.lower() != "basic":
|
|
217
|
+
if self.auto_error:
|
|
218
|
+
raise HTTPException(
|
|
219
|
+
status_code=401,
|
|
220
|
+
detail="Invalid authentication credentials",
|
|
221
|
+
headers={"WWW-Authenticate": f'Basic realm="{self.realm}"' if self.realm else "Basic"},
|
|
222
|
+
)
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
decoded = base64.b64decode(credentials).decode("utf-8")
|
|
227
|
+
username, _, password = decoded.partition(":")
|
|
228
|
+
return HTTPBasicCredentials(username=username, password=password)
|
|
229
|
+
except Exception:
|
|
230
|
+
if self.auto_error:
|
|
231
|
+
raise HTTPException(
|
|
232
|
+
status_code=401,
|
|
233
|
+
detail="Invalid authentication credentials",
|
|
234
|
+
headers={"WWW-Authenticate": f'Basic realm="{self.realm}"' if self.realm else "Basic"},
|
|
235
|
+
)
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ============================================================================
|
|
240
|
+
# HTTP Bearer Authentication
|
|
241
|
+
# ============================================================================
|
|
242
|
+
|
|
243
|
+
@dataclass
|
|
244
|
+
class HTTPAuthorizationCredentials:
|
|
245
|
+
"""HTTP authorization credentials."""
|
|
246
|
+
scheme: str
|
|
247
|
+
credentials: str
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class HTTPBearer(SecurityBase):
|
|
251
|
+
"""
|
|
252
|
+
HTTP Bearer token authentication.
|
|
253
|
+
|
|
254
|
+
Usage:
|
|
255
|
+
security = HTTPBearer()
|
|
256
|
+
|
|
257
|
+
@app.get("/users/me")
|
|
258
|
+
def get_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
|
259
|
+
return {"token": credentials.credentials}
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(
|
|
263
|
+
self,
|
|
264
|
+
*,
|
|
265
|
+
scheme_name: Optional[str] = None,
|
|
266
|
+
auto_error: bool = True,
|
|
267
|
+
):
|
|
268
|
+
super().__init__(scheme_name=scheme_name, auto_error=auto_error)
|
|
269
|
+
self.model = {"type": "http", "scheme": "bearer"}
|
|
270
|
+
|
|
271
|
+
def __call__(self, authorization: Optional[str] = None) -> Optional[HTTPAuthorizationCredentials]:
|
|
272
|
+
"""Extract Bearer token."""
|
|
273
|
+
if not authorization:
|
|
274
|
+
if self.auto_error:
|
|
275
|
+
raise HTTPException(
|
|
276
|
+
status_code=401,
|
|
277
|
+
detail="Not authenticated",
|
|
278
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
279
|
+
)
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
scheme, _, credentials = authorization.partition(" ")
|
|
283
|
+
if scheme.lower() != "bearer":
|
|
284
|
+
if self.auto_error:
|
|
285
|
+
raise HTTPException(
|
|
286
|
+
status_code=401,
|
|
287
|
+
detail="Invalid authentication credentials",
|
|
288
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
289
|
+
)
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class HTTPDigest(SecurityBase):
|
|
296
|
+
"""
|
|
297
|
+
HTTP Digest authentication.
|
|
298
|
+
|
|
299
|
+
Usage:
|
|
300
|
+
security = HTTPDigest()
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
def __init__(
|
|
304
|
+
self,
|
|
305
|
+
*,
|
|
306
|
+
scheme_name: Optional[str] = None,
|
|
307
|
+
auto_error: bool = True,
|
|
308
|
+
):
|
|
309
|
+
super().__init__(scheme_name=scheme_name, auto_error=auto_error)
|
|
310
|
+
self.model = {"type": "http", "scheme": "digest"}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# ============================================================================
|
|
314
|
+
# API Key Authentication
|
|
315
|
+
# ============================================================================
|
|
316
|
+
|
|
317
|
+
class APIKeyBase(SecurityBase):
|
|
318
|
+
"""Base class for API key authentication."""
|
|
319
|
+
|
|
320
|
+
def __init__(
|
|
321
|
+
self,
|
|
322
|
+
*,
|
|
323
|
+
name: str,
|
|
324
|
+
scheme_name: Optional[str] = None,
|
|
325
|
+
description: Optional[str] = None,
|
|
326
|
+
auto_error: bool = True,
|
|
327
|
+
):
|
|
328
|
+
super().__init__(scheme_name=scheme_name, auto_error=auto_error)
|
|
329
|
+
self.name = name
|
|
330
|
+
self.description = description
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class APIKeyQuery(APIKeyBase):
|
|
334
|
+
"""
|
|
335
|
+
API Key authentication via query parameter.
|
|
336
|
+
|
|
337
|
+
Usage:
|
|
338
|
+
api_key = APIKeyQuery(name="api_key")
|
|
339
|
+
|
|
340
|
+
@app.get("/items")
|
|
341
|
+
def get_items(key: str = Depends(api_key)):
|
|
342
|
+
return {"api_key": key}
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
def __init__(
|
|
346
|
+
self,
|
|
347
|
+
*,
|
|
348
|
+
name: str,
|
|
349
|
+
scheme_name: Optional[str] = None,
|
|
350
|
+
description: Optional[str] = None,
|
|
351
|
+
auto_error: bool = True,
|
|
352
|
+
):
|
|
353
|
+
super().__init__(
|
|
354
|
+
name=name,
|
|
355
|
+
scheme_name=scheme_name,
|
|
356
|
+
description=description,
|
|
357
|
+
auto_error=auto_error,
|
|
358
|
+
)
|
|
359
|
+
self.model = {"type": "apiKey", "in": "query", "name": name}
|
|
360
|
+
|
|
361
|
+
def __call__(self, query_params: Optional[Dict[str, str]] = None) -> Optional[str]:
|
|
362
|
+
"""Extract API key from query parameters."""
|
|
363
|
+
if not query_params or self.name not in query_params:
|
|
364
|
+
if self.auto_error:
|
|
365
|
+
raise HTTPException(
|
|
366
|
+
status_code=403,
|
|
367
|
+
detail="Not authenticated",
|
|
368
|
+
)
|
|
369
|
+
return None
|
|
370
|
+
return query_params[self.name]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class APIKeyHeader(APIKeyBase):
|
|
374
|
+
"""
|
|
375
|
+
API Key authentication via HTTP header.
|
|
376
|
+
|
|
377
|
+
Usage:
|
|
378
|
+
api_key = APIKeyHeader(name="X-API-Key")
|
|
379
|
+
|
|
380
|
+
@app.get("/items")
|
|
381
|
+
def get_items(key: str = Depends(api_key)):
|
|
382
|
+
return {"api_key": key}
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
def __init__(
|
|
386
|
+
self,
|
|
387
|
+
*,
|
|
388
|
+
name: str,
|
|
389
|
+
scheme_name: Optional[str] = None,
|
|
390
|
+
description: Optional[str] = None,
|
|
391
|
+
auto_error: bool = True,
|
|
392
|
+
):
|
|
393
|
+
super().__init__(
|
|
394
|
+
name=name,
|
|
395
|
+
scheme_name=scheme_name,
|
|
396
|
+
description=description,
|
|
397
|
+
auto_error=auto_error,
|
|
398
|
+
)
|
|
399
|
+
self.model = {"type": "apiKey", "in": "header", "name": name}
|
|
400
|
+
|
|
401
|
+
def __call__(self, headers: Optional[Dict[str, str]] = None) -> Optional[str]:
|
|
402
|
+
"""Extract API key from headers."""
|
|
403
|
+
if not headers or self.name.lower() not in {k.lower(): v for k, v in headers.items()}:
|
|
404
|
+
if self.auto_error:
|
|
405
|
+
raise HTTPException(
|
|
406
|
+
status_code=403,
|
|
407
|
+
detail="Not authenticated",
|
|
408
|
+
)
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
# Case-insensitive header lookup
|
|
412
|
+
for key, value in headers.items():
|
|
413
|
+
if key.lower() == self.name.lower():
|
|
414
|
+
return value
|
|
415
|
+
return None
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class APIKeyCookie(APIKeyBase):
|
|
419
|
+
"""
|
|
420
|
+
API Key authentication via HTTP cookie.
|
|
421
|
+
|
|
422
|
+
Usage:
|
|
423
|
+
api_key = APIKeyCookie(name="session")
|
|
424
|
+
|
|
425
|
+
@app.get("/items")
|
|
426
|
+
def get_items(key: str = Depends(api_key)):
|
|
427
|
+
return {"session": key}
|
|
428
|
+
"""
|
|
429
|
+
|
|
430
|
+
def __init__(
|
|
431
|
+
self,
|
|
432
|
+
*,
|
|
433
|
+
name: str,
|
|
434
|
+
scheme_name: Optional[str] = None,
|
|
435
|
+
description: Optional[str] = None,
|
|
436
|
+
auto_error: bool = True,
|
|
437
|
+
):
|
|
438
|
+
super().__init__(
|
|
439
|
+
name=name,
|
|
440
|
+
scheme_name=scheme_name,
|
|
441
|
+
description=description,
|
|
442
|
+
auto_error=auto_error,
|
|
443
|
+
)
|
|
444
|
+
self.model = {"type": "apiKey", "in": "cookie", "name": name}
|
|
445
|
+
|
|
446
|
+
def __call__(self, cookies: Optional[Dict[str, str]] = None) -> Optional[str]:
|
|
447
|
+
"""Extract API key from cookies."""
|
|
448
|
+
if not cookies or self.name not in cookies:
|
|
449
|
+
if self.auto_error:
|
|
450
|
+
raise HTTPException(
|
|
451
|
+
status_code=403,
|
|
452
|
+
detail="Not authenticated",
|
|
453
|
+
)
|
|
454
|
+
return None
|
|
455
|
+
return cookies[self.name]
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
# ============================================================================
|
|
459
|
+
# Security Scopes
|
|
460
|
+
# ============================================================================
|
|
461
|
+
|
|
462
|
+
class SecurityScopes:
|
|
463
|
+
"""
|
|
464
|
+
Security scopes for OAuth2 and other scope-based auth.
|
|
465
|
+
|
|
466
|
+
Usage:
|
|
467
|
+
def get_current_user(
|
|
468
|
+
security_scopes: SecurityScopes,
|
|
469
|
+
token: str = Depends(oauth2_scheme)
|
|
470
|
+
):
|
|
471
|
+
if security_scopes.scopes:
|
|
472
|
+
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
|
|
473
|
+
else:
|
|
474
|
+
authenticate_value = "Bearer"
|
|
475
|
+
# Validate token and scopes...
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
def __init__(self, scopes: Optional[List[str]] = None):
|
|
479
|
+
self.scopes = scopes or []
|
|
480
|
+
self.scope_str = " ".join(self.scopes)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# ============================================================================
|
|
484
|
+
# Helper Functions
|
|
485
|
+
# ============================================================================
|
|
486
|
+
|
|
487
|
+
class HTTPException(Exception):
|
|
488
|
+
"""HTTP exception for authentication errors."""
|
|
489
|
+
|
|
490
|
+
def __init__(
|
|
491
|
+
self,
|
|
492
|
+
status_code: int,
|
|
493
|
+
detail: Any = None,
|
|
494
|
+
headers: Optional[Dict[str, str]] = None,
|
|
495
|
+
):
|
|
496
|
+
self.status_code = status_code
|
|
497
|
+
self.detail = detail
|
|
498
|
+
self.headers = headers
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
502
|
+
"""
|
|
503
|
+
Verify a password against a hash.
|
|
504
|
+
|
|
505
|
+
Note: This is a placeholder. Use a proper password hashing library like:
|
|
506
|
+
- passlib with bcrypt
|
|
507
|
+
- argon2-cffi
|
|
508
|
+
"""
|
|
509
|
+
# TODO: Implement with proper password hashing
|
|
510
|
+
return secrets.compare_digest(plain_password, hashed_password)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def get_password_hash(password: str) -> str:
|
|
514
|
+
"""
|
|
515
|
+
Hash a password.
|
|
516
|
+
|
|
517
|
+
Note: This is a placeholder. Use a proper password hashing library.
|
|
518
|
+
"""
|
|
519
|
+
# TODO: Implement with proper password hashing
|
|
520
|
+
return password # INSECURE - just for demo!
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
# ============================================================================
|
|
524
|
+
# Dependency Injection Helper
|
|
525
|
+
# ============================================================================
|
|
526
|
+
|
|
527
|
+
class Depends:
|
|
528
|
+
"""
|
|
529
|
+
Dependency injection marker (compatible with FastAPI).
|
|
530
|
+
|
|
531
|
+
Usage:
|
|
532
|
+
def get_current_user(token: str = Depends(oauth2_scheme)):
|
|
533
|
+
return decode_token(token)
|
|
534
|
+
|
|
535
|
+
@app.get("/users/me")
|
|
536
|
+
def read_users_me(user = Depends(get_current_user)):
|
|
537
|
+
return user
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
def __init__(self, dependency: Optional[Callable] = None, *, use_cache: bool = True):
|
|
541
|
+
self.dependency = dependency
|
|
542
|
+
self.use_cache = use_cache
|