clsplusplus 4.0.0__py3-none-any.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.
- clsplusplus/__init__.py +31 -0
- clsplusplus/api.py +1596 -0
- clsplusplus/auth.py +74 -0
- clsplusplus/cli.py +715 -0
- clsplusplus/client.py +462 -0
- clsplusplus/config.py +116 -0
- clsplusplus/cost_model.py +51 -0
- clsplusplus/demo_llm.py +133 -0
- clsplusplus/demo_llm_calls.py +100 -0
- clsplusplus/demo_local.py +515 -0
- clsplusplus/embeddings.py +52 -0
- clsplusplus/idempotency.py +66 -0
- clsplusplus/integration_service.py +256 -0
- clsplusplus/jwt_utils.py +39 -0
- clsplusplus/local_routes.py +781 -0
- clsplusplus/main.py +21 -0
- clsplusplus/memory_cycle.py +216 -0
- clsplusplus/memory_phase.py +3541 -0
- clsplusplus/memory_service.py +1323 -0
- clsplusplus/metrics.py +184 -0
- clsplusplus/middleware.py +325 -0
- clsplusplus/models.py +430 -0
- clsplusplus/permissions.py +54 -0
- clsplusplus/plasticity.py +148 -0
- clsplusplus/rate_limit.py +53 -0
- clsplusplus/rbac_service.py +86 -0
- clsplusplus/reconsolidation.py +71 -0
- clsplusplus/sleep_cycle.py +109 -0
- clsplusplus/stores/__init__.py +13 -0
- clsplusplus/stores/base.py +43 -0
- clsplusplus/stores/integration_store.py +648 -0
- clsplusplus/stores/l0_working_buffer.py +103 -0
- clsplusplus/stores/l1_indexing_store.py +427 -0
- clsplusplus/stores/l2_schema_graph.py +231 -0
- clsplusplus/stores/l3_deep_recess.py +182 -0
- clsplusplus/stores/l3_postgres.py +183 -0
- clsplusplus/stores/rbac_store.py +327 -0
- clsplusplus/stores/user_store.py +255 -0
- clsplusplus/stripe_service.py +136 -0
- clsplusplus/temporal.py +613 -0
- clsplusplus/test_suite.py +587 -0
- clsplusplus/tiers.py +109 -0
- clsplusplus/tracer.py +226 -0
- clsplusplus/usage.py +130 -0
- clsplusplus/user_embeddings.py +1636 -0
- clsplusplus/user_service.py +256 -0
- clsplusplus/webhook_dispatcher.py +229 -0
- clsplusplus-4.0.0.dist-info/METADATA +262 -0
- clsplusplus-4.0.0.dist-info/RECORD +53 -0
- clsplusplus-4.0.0.dist-info/WHEEL +5 -0
- clsplusplus-4.0.0.dist-info/entry_points.txt +2 -0
- clsplusplus-4.0.0.dist-info/licenses/LICENSE +201 -0
- clsplusplus-4.0.0.dist-info/top_level.txt +1 -0
clsplusplus/auth.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""CLS++ API authentication - secure, constant-time validation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import hmac
|
|
7
|
+
import re
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from clsplusplus.config import Settings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# API key format: cls_live_* or cls_test_* (min 32 chars total for entropy)
|
|
14
|
+
_API_KEY_PATTERN = re.compile(r"^cls_(?:live|test)_[a-zA-Z0-9]{24,}$")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _normalize_key(key: Optional[str]) -> Optional[str]:
|
|
18
|
+
"""Strip whitespace; return None if empty."""
|
|
19
|
+
if key is None:
|
|
20
|
+
return None
|
|
21
|
+
k = key.strip()
|
|
22
|
+
return k if k else None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _sha256_hex(s: str) -> str:
|
|
26
|
+
"""SHA-256 hex digest of string."""
|
|
27
|
+
return hashlib.sha256(s.encode("utf-8")).hexdigest()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_key_lookup(settings: Settings) -> dict[str, str]:
|
|
31
|
+
"""Build hash -> key lookup for constant-time validation."""
|
|
32
|
+
raw = getattr(settings, "api_keys", None) or ""
|
|
33
|
+
if not raw:
|
|
34
|
+
return {}
|
|
35
|
+
lookup: dict[str, str] = {}
|
|
36
|
+
for k in raw.split(","):
|
|
37
|
+
k = _normalize_key(k)
|
|
38
|
+
if k and _API_KEY_PATTERN.match(k):
|
|
39
|
+
lookup[_sha256_hex(k)] = k
|
|
40
|
+
return lookup
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def validate_api_key(key: Optional[str], settings: Optional[Settings] = None) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Validate API key using constant-time comparison.
|
|
46
|
+
Hash lookup + single hmac.compare_digest prevents timing attacks.
|
|
47
|
+
"""
|
|
48
|
+
if not key:
|
|
49
|
+
return False
|
|
50
|
+
key = _normalize_key(key)
|
|
51
|
+
if not key or not _API_KEY_PATTERN.match(key):
|
|
52
|
+
return False
|
|
53
|
+
settings = settings or Settings()
|
|
54
|
+
lookup = _get_key_lookup(settings)
|
|
55
|
+
if not lookup:
|
|
56
|
+
return False
|
|
57
|
+
key_hash = _sha256_hex(key)
|
|
58
|
+
stored = lookup.get(key_hash)
|
|
59
|
+
return stored is not None and hmac.compare_digest(key, stored)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def extract_bearer_token(auth_header: Optional[str]) -> Optional[str]:
|
|
63
|
+
"""Extract Bearer token from Authorization header. Returns None if invalid."""
|
|
64
|
+
if not auth_header or not isinstance(auth_header, str):
|
|
65
|
+
return None
|
|
66
|
+
parts = auth_header.strip().split()
|
|
67
|
+
if len(parts) != 2 or parts[0].lower() != "bearer":
|
|
68
|
+
return None
|
|
69
|
+
return parts[1] if parts[1] else None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_api_key_from_request(auth_header: Optional[str]) -> Optional[str]:
|
|
73
|
+
"""Extract and return API key from request; None if missing/invalid format."""
|
|
74
|
+
return extract_bearer_token(auth_header)
|