amfs-http-server 0.3.1__tar.gz → 0.3.3__tar.gz
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.
- {amfs_http_server-0.3.1 → amfs_http_server-0.3.3}/PKG-INFO +1 -1
- {amfs_http_server-0.3.1 → amfs_http_server-0.3.3}/pyproject.toml +1 -1
- {amfs_http_server-0.3.1 → amfs_http_server-0.3.3}/src/amfs_http/auth.py +22 -4
- {amfs_http_server-0.3.1 → amfs_http_server-0.3.3}/src/amfs_http/server.py +1009 -86
- {amfs_http_server-0.3.1 → amfs_http_server-0.3.3}/src/amfs_http/sse.py +43 -0
- amfs_http_server-0.3.3/src/amfs_http/tenant_middleware.py +86 -0
- amfs_http_server-0.3.1/src/amfs_http/tenant_middleware.py +0 -48
- {amfs_http_server-0.3.1 → amfs_http_server-0.3.3}/.gitignore +0 -0
- {amfs_http_server-0.3.1 → amfs_http_server-0.3.3}/src/amfs_http/__init__.py +0 -0
- {amfs_http_server-0.3.1 → amfs_http_server-0.3.3}/src/amfs_http/models.py +0 -0
- {amfs_http_server-0.3.1 → amfs_http_server-0.3.3}/src/amfs_http/pro_proxy.py +0 -0
|
@@ -5,6 +5,8 @@ import hashlib
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
7
|
import secrets
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
8
10
|
|
|
9
11
|
from fastapi import HTTPException, Security, status
|
|
10
12
|
from fastapi.security import APIKeyHeader
|
|
@@ -13,6 +15,10 @@ logger = logging.getLogger(__name__)
|
|
|
13
15
|
|
|
14
16
|
API_KEY_HEADER = APIKeyHeader(name="X-AMFS-API-Key", auto_error=False)
|
|
15
17
|
|
|
18
|
+
_AUTH_CACHE_TTL = 120.0
|
|
19
|
+
_auth_cache: dict[str, float] = {}
|
|
20
|
+
_auth_cache_lock = threading.Lock()
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
def get_api_keys() -> set[str]:
|
|
18
24
|
raw = os.environ.get("AMFS_API_KEYS", "")
|
|
@@ -25,12 +31,22 @@ def _check_db_key(api_key: str) -> bool:
|
|
|
25
31
|
"""Check if an API key exists in amfs_api_keys table (active keys only).
|
|
26
32
|
|
|
27
33
|
Keys are stored as SHA-256 hex digests, so we hash the incoming raw key
|
|
28
|
-
before comparing.
|
|
34
|
+
before comparing. Results are cached in-process for ``_AUTH_CACHE_TTL``
|
|
35
|
+
seconds to avoid opening a new DB connection on every request. Only
|
|
36
|
+
positive results are cached; revoked/invalid keys always hit the DB.
|
|
29
37
|
|
|
30
38
|
Uses SECURITY DEFINER functions to bypass RLS — this connection does not
|
|
31
39
|
set amfs.current_account_id, so direct table queries would return nothing
|
|
32
40
|
when FORCE ROW LEVEL SECURITY is enabled.
|
|
33
41
|
"""
|
|
42
|
+
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
|
|
43
|
+
now = time.monotonic()
|
|
44
|
+
|
|
45
|
+
with _auth_cache_lock:
|
|
46
|
+
cached_at = _auth_cache.get(key_hash)
|
|
47
|
+
if cached_at is not None and (now - cached_at) < _AUTH_CACHE_TTL:
|
|
48
|
+
return True
|
|
49
|
+
|
|
34
50
|
try:
|
|
35
51
|
import psycopg
|
|
36
52
|
from psycopg.rows import dict_row
|
|
@@ -39,8 +55,6 @@ def _check_db_key(api_key: str) -> bool:
|
|
|
39
55
|
if not dsn:
|
|
40
56
|
return False
|
|
41
57
|
|
|
42
|
-
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
|
|
43
|
-
|
|
44
58
|
with psycopg.connect(dsn, row_factory=dict_row) as conn:
|
|
45
59
|
row = conn.execute(
|
|
46
60
|
"SELECT * FROM amfs_authenticate_api_key(%s)",
|
|
@@ -51,7 +65,11 @@ def _check_db_key(api_key: str) -> bool:
|
|
|
51
65
|
"SELECT amfs_touch_api_key_usage(%s)",
|
|
52
66
|
(row["key_id"],),
|
|
53
67
|
)
|
|
54
|
-
|
|
68
|
+
if row is not None:
|
|
69
|
+
with _auth_cache_lock:
|
|
70
|
+
_auth_cache[key_hash] = now
|
|
71
|
+
return True
|
|
72
|
+
return False
|
|
55
73
|
except Exception:
|
|
56
74
|
logger.debug("DB API key check failed (table may not exist)", exc_info=True)
|
|
57
75
|
return False
|