auth 2.3.1__tar.gz → 2.4.1__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.
- {auth-2.3.1 → auth-2.4.1}/PKG-INFO +21 -3
- {auth-2.3.1 → auth-2.4.1}/README.md +18 -0
- {auth-2.3.1 → auth-2.4.1}/auth/__init__.py +6 -1
- auth-2.4.1/auth/api_keys.py +50 -0
- {auth-2.3.1 → auth-2.4.1}/auth/audit.py +4 -0
- auth-2.4.1/auth/client.py +595 -0
- {auth-2.3.1 → auth-2.4.1}/auth/docs_page.py +54 -5
- {auth-2.3.1 → auth-2.4.1}/auth/models/sql.py +74 -0
- {auth-2.3.1 → auth-2.4.1}/auth/routes.py +140 -1
- {auth-2.3.1 → auth-2.4.1}/auth/services/service.py +194 -25
- {auth-2.3.1 → auth-2.4.1}/auth/validation.py +17 -0
- {auth-2.3.1 → auth-2.4.1}/auth.egg-info/PKG-INFO +21 -3
- {auth-2.3.1 → auth-2.4.1}/auth.egg-info/SOURCES.txt +3 -1
- {auth-2.3.1 → auth-2.4.1}/auth.egg-info/requires.txt +2 -2
- {auth-2.3.1 → auth-2.4.1}/pyproject.toml +5 -5
- auth-2.4.1/tests/test_api_keys.py +353 -0
- auth-2.4.1/tests/test_api_keys_encryption.py +89 -0
- auth-2.4.1/tests/test_client.py +317 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_docs_page.py +11 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_key_rotation.py +68 -3
- auth-2.3.1/auth/client.py +0 -408
- auth-2.3.1/tests/test_client.py +0 -157
- auth-2.3.1/tests/test_migrations.py +0 -38
- {auth-2.3.1 → auth-2.4.1}/LICENSE +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/circuit_breaker.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/cmd/__init__.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/cmd/server.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/config.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/core/REST/__init__.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/core/REST/client.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/core/__init__.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/core/models/__init__.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/database.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/decorators.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/encryption.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/logging_config.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/main.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/response_format.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/sanitizer.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/server.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth/workflow_checker.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth.egg-info/dependency_links.txt +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth.egg-info/entry_points.txt +0 -0
- {auth-2.3.1 → auth-2.4.1}/auth.egg-info/top_level.txt +0 -0
- {auth-2.3.1 → auth-2.4.1}/setup.cfg +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_audit_integrity.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_client_rest.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_cmd_server.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_config.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_correctness_hardening.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_encryption.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_encryption_integration.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_flask.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_log_redaction.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_phase_a_hardening.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_reencryption.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_routes_errors.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_server.py +0 -0
- {auth-2.3.1 → auth-2.4.1}/tests/test_service_lifecycle.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: auth
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: Authorization for humans
|
|
5
5
|
Author-email: Farshid Ashouri <farsheed.ashouri@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -40,11 +40,11 @@ Requires-Dist: python-json-logger<4,>=3.1.0
|
|
|
40
40
|
Provides-Extra: ratelimit
|
|
41
41
|
Requires-Dist: flask-limiter>=3.0.0; extra == "ratelimit"
|
|
42
42
|
Provides-Extra: migrations
|
|
43
|
-
Requires-Dist:
|
|
43
|
+
Requires-Dist: migretti>=0.10.0; extra == "migrations"
|
|
44
44
|
Provides-Extra: dev
|
|
45
45
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
46
46
|
Requires-Dist: pytest-cov>=2.0; extra == "dev"
|
|
47
|
-
Requires-Dist:
|
|
47
|
+
Requires-Dist: migretti>=0.10.0; extra == "dev"
|
|
48
48
|
Requires-Dist: ruff>=0.0.260; extra == "dev"
|
|
49
49
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
50
50
|
Requires-Dist: types-requests; extra == "dev"
|
|
@@ -104,9 +104,27 @@ with Client(api_key=KEY, service_url="https://auth.rodmena.app") as c:
|
|
|
104
104
|
- **Two response shapes** — bare `{"result": ...}` and wrapped
|
|
105
105
|
`{"success", "data", ...}`. The API reference says which per endpoint.
|
|
106
106
|
- **Errors below 2xx are HTML**, not JSON. Branch on the status code first.
|
|
107
|
+
- **Python client: check `success` before reading `data`.** On transport
|
|
108
|
+
failure the client does not raise by default — it returns
|
|
109
|
+
`{"error", "success": False, "transport_error": True, "data": {...}}` where
|
|
110
|
+
`data` only echoes your inputs and does NOT contain the answer field
|
|
111
|
+
(`has_permission`, `count`, ...). Reading `data` blindly turns an outage into
|
|
112
|
+
a false "no". Pass `Client(..., raise_on_error=True)` to get an
|
|
113
|
+
`AuthTransportError` exception instead of the error dict.
|
|
107
114
|
- **Reuse one key.** A new key is a new empty namespace, not an error. Keep the
|
|
108
115
|
key out of source control, logs, and URLs — it is the only thing protecting
|
|
109
116
|
your data. Rotate it with `POST /api/keys/rotate` if it leaks.
|
|
117
|
+
- **Per-user API keys** (2.4.0): `/api/apikeys/user/<user>` (create/list),
|
|
118
|
+
`/api/apikeys/user/<user>/<key_id>` (revoke), `/api/apikeys/validate`. auth
|
|
119
|
+
mints `rak_...` secrets for *your users*, shows each exactly once, stores only
|
|
120
|
+
a hash, and validates them inside your namespace — an identity UI fronts the
|
|
121
|
+
lifecycle, backends validate then use the RBAC checks. Client methods:
|
|
122
|
+
`create_api_key`, `list_api_keys`, `revoke_api_key`, `validate_api_key`.
|
|
123
|
+
- **DEPRECATED — bare user strings.** Asserting a `<user>` that no validated API
|
|
124
|
+
key backs is scheduled for decommission: 2.5.0 adds opt-in per-tenant strict
|
|
125
|
+
mode, **3.0.0 makes key-backed users mandatory** (each phase ships only after
|
|
126
|
+
every consuming platform confirms). Migrate backends to
|
|
127
|
+
`validate → user → has_permission` now. Details: [docs/DEPRECATIONS.md](docs/DEPRECATIONS.md).
|
|
110
128
|
|
|
111
129
|
## Documentation
|
|
112
130
|
|
|
@@ -48,9 +48,27 @@ with Client(api_key=KEY, service_url="https://auth.rodmena.app") as c:
|
|
|
48
48
|
- **Two response shapes** — bare `{"result": ...}` and wrapped
|
|
49
49
|
`{"success", "data", ...}`. The API reference says which per endpoint.
|
|
50
50
|
- **Errors below 2xx are HTML**, not JSON. Branch on the status code first.
|
|
51
|
+
- **Python client: check `success` before reading `data`.** On transport
|
|
52
|
+
failure the client does not raise by default — it returns
|
|
53
|
+
`{"error", "success": False, "transport_error": True, "data": {...}}` where
|
|
54
|
+
`data` only echoes your inputs and does NOT contain the answer field
|
|
55
|
+
(`has_permission`, `count`, ...). Reading `data` blindly turns an outage into
|
|
56
|
+
a false "no". Pass `Client(..., raise_on_error=True)` to get an
|
|
57
|
+
`AuthTransportError` exception instead of the error dict.
|
|
51
58
|
- **Reuse one key.** A new key is a new empty namespace, not an error. Keep the
|
|
52
59
|
key out of source control, logs, and URLs — it is the only thing protecting
|
|
53
60
|
your data. Rotate it with `POST /api/keys/rotate` if it leaks.
|
|
61
|
+
- **Per-user API keys** (2.4.0): `/api/apikeys/user/<user>` (create/list),
|
|
62
|
+
`/api/apikeys/user/<user>/<key_id>` (revoke), `/api/apikeys/validate`. auth
|
|
63
|
+
mints `rak_...` secrets for *your users*, shows each exactly once, stores only
|
|
64
|
+
a hash, and validates them inside your namespace — an identity UI fronts the
|
|
65
|
+
lifecycle, backends validate then use the RBAC checks. Client methods:
|
|
66
|
+
`create_api_key`, `list_api_keys`, `revoke_api_key`, `validate_api_key`.
|
|
67
|
+
- **DEPRECATED — bare user strings.** Asserting a `<user>` that no validated API
|
|
68
|
+
key backs is scheduled for decommission: 2.5.0 adds opt-in per-tenant strict
|
|
69
|
+
mode, **3.0.0 makes key-backed users mandatory** (each phase ships only after
|
|
70
|
+
every consuming platform confirms). Migrate backends to
|
|
71
|
+
`validate → user → has_permission` now. Details: [docs/DEPRECATIONS.md](docs/DEPRECATIONS.md).
|
|
54
72
|
|
|
55
73
|
## Documentation
|
|
56
74
|
|
|
@@ -3,7 +3,11 @@ __author__ = "Farshid Ashouri"
|
|
|
3
3
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
-
from auth.client import
|
|
6
|
+
from auth.client import ( # Import the new client
|
|
7
|
+
AuthTransportError,
|
|
8
|
+
Client,
|
|
9
|
+
EnhancedAuthClient,
|
|
10
|
+
)
|
|
7
11
|
from auth.database import SessionLocal
|
|
8
12
|
from auth.services.service import AuthorizationService
|
|
9
13
|
|
|
@@ -94,6 +98,7 @@ class Authorization:
|
|
|
94
98
|
# Export the new client for users who want enhanced features
|
|
95
99
|
__all__ = [
|
|
96
100
|
"Authorization",
|
|
101
|
+
"AuthTransportError",
|
|
97
102
|
"Client",
|
|
98
103
|
"EnhancedAuthClient",
|
|
99
104
|
"SessionLocal",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Per-user API key generation and hashing (SPEC 0004).
|
|
2
|
+
|
|
3
|
+
Secrets are ``rak_`` + 43 base62 characters carrying the full 256 bits of
|
|
4
|
+
``secrets.token_bytes(32)``. Only the SHA-256 hex digest is stored; the raw
|
|
5
|
+
secret is returned once at creation and never persisted, logged, or audited.
|
|
6
|
+
The digest deliberately excludes the tenant and any server pepper: at this
|
|
7
|
+
entropy an offline attack on a leaked hash is not a threat, and neither a
|
|
8
|
+
client-key rotation nor a pepper change may invalidate issued keys.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import re
|
|
13
|
+
import secrets
|
|
14
|
+
import uuid
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
|
|
17
|
+
API_KEY_PREFIX = "rak_"
|
|
18
|
+
API_KEY_PATTERN = re.compile(r"^rak_[0-9A-Za-z]{43}$")
|
|
19
|
+
|
|
20
|
+
# "rak_" + first 8 payload chars — safe to store and display in listings.
|
|
21
|
+
KEY_PREFIX_LEN = 12
|
|
22
|
+
|
|
23
|
+
_PAYLOAD_LEN = 43
|
|
24
|
+
_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _base62(data: bytes) -> str:
|
|
28
|
+
number = int.from_bytes(data, "big")
|
|
29
|
+
chars = []
|
|
30
|
+
while number:
|
|
31
|
+
number, rem = divmod(number, 62)
|
|
32
|
+
chars.append(_ALPHABET[rem])
|
|
33
|
+
return "".join(reversed(chars)) or _ALPHABET[0]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def generate_api_key() -> Tuple[str, str, str, str]:
|
|
37
|
+
"""Mint a fresh key: returns ``(secret, key_id, key_hash, key_prefix)``.
|
|
38
|
+
|
|
39
|
+
``secret`` is shown to the caller exactly once; ``key_id`` is the public
|
|
40
|
+
UUID4 handle used in revoke paths; ``key_hash``/``key_prefix`` are what
|
|
41
|
+
gets stored.
|
|
42
|
+
"""
|
|
43
|
+
payload = _base62(secrets.token_bytes(32)).rjust(_PAYLOAD_LEN, _ALPHABET[0])
|
|
44
|
+
secret = API_KEY_PREFIX + payload
|
|
45
|
+
return secret, str(uuid.uuid4()), hash_api_key(secret), secret[:KEY_PREFIX_LEN]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def hash_api_key(secret: str) -> str:
|
|
49
|
+
"""SHA-256 hex digest of the full secret string (equality-queryable)."""
|
|
50
|
+
return hashlib.sha256(secret.encode()).hexdigest()
|
|
@@ -38,6 +38,10 @@ class AuditAction(Enum):
|
|
|
38
38
|
LIST_MEMBERSHIPS = "LIST_MEMBERSHIPS"
|
|
39
39
|
USER_PERMISSIONS = "USER_PERMISSIONS"
|
|
40
40
|
ROTATE_KEY = "ROTATE_KEY"
|
|
41
|
+
CREATE_API_KEY = "CREATE_API_KEY"
|
|
42
|
+
LIST_API_KEYS = "LIST_API_KEYS"
|
|
43
|
+
REVOKE_API_KEY = "REVOKE_API_KEY"
|
|
44
|
+
VALIDATE_API_KEY = "VALIDATE_API_KEY"
|
|
41
45
|
|
|
42
46
|
|
|
43
47
|
class AuditLog(Base):
|