amfs-http-server 0.3.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amfs-http-server
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: AMFS HTTP/REST API server with SSE support
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amfs-http-server"
3
- version = "0.3.2"
3
+ version = "0.3.3"
4
4
  description = "AMFS HTTP/REST API server with SSE support"
5
5
  requires-python = ">=3.11"
6
6
  license = "Apache-2.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
- return row is not None
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