crypticorn-utils 0.1.0rc1__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.
- crypticorn_utils-0.1.0rc1/LICENSE +15 -0
- crypticorn_utils-0.1.0rc1/MANIFEST.in +3 -0
- crypticorn_utils-0.1.0rc1/PKG-INFO +95 -0
- crypticorn_utils-0.1.0rc1/README.md +48 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/__init__.py +16 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/_migration.py +12 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/ansi_colors.py +41 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/auth.py +345 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/cli/__init__.py +4 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/cli/__main__.py +17 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/cli/init.py +127 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/cli/templates/__init__.py +0 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/cli/templates/auth.py +33 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/cli/version.py +8 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/decorators.py +38 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/enums.py +175 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/errors.py +915 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/exceptions.py +183 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/logging.py +130 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/metrics.py +32 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/middleware.py +125 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/mixins.py +68 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/openapi.py +10 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/pagination.py +286 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/router/admin_router.py +117 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/router/status_router.py +36 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/utils.py +93 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils/warnings.py +79 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils.egg-info/PKG-INFO +95 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils.egg-info/SOURCES.txt +43 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils.egg-info/dependency_links.txt +1 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils.egg-info/entry_points.txt +2 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils.egg-info/requires.txt +25 -0
- crypticorn_utils-0.1.0rc1/crypticorn_utils.egg-info/top_level.txt +1 -0
- crypticorn_utils-0.1.0rc1/pyproject.toml +69 -0
- crypticorn_utils-0.1.0rc1/requirements/dev.txt +10 -0
- crypticorn_utils-0.1.0rc1/requirements/main.txt +7 -0
- crypticorn_utils-0.1.0rc1/requirements/test.txt +7 -0
- crypticorn_utils-0.1.0rc1/setup.cfg +4 -0
- crypticorn_utils-0.1.0rc1/tests/test_auth_client.py +361 -0
- crypticorn_utils-0.1.0rc1/tests/test_common_routers.py +111 -0
- crypticorn_utils-0.1.0rc1/tests/test_config.py +22 -0
- crypticorn_utils-0.1.0rc1/tests/test_enums.py +44 -0
- crypticorn_utils-0.1.0rc1/tests/test_errors.py +63 -0
- crypticorn_utils-0.1.0rc1/tests/test_pagination.py +334 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
Copyright © 2025 Crypticorn
|
2
|
+
|
3
|
+
All rights reserved. This software and accompanying documentation files (collectively referred to as "the Software") are the proprietary property of Crypticorn. Any unauthorised reproduction, modification, integration, publication, distribution, sublicensing, sale, or enabling access to the Software without explicit written permission from Crypticorn is strictly prohibited, subject to the conditions outlined herein:
|
4
|
+
|
5
|
+
1. The aforementioned copyright notice and this permission notice must be included unaltered in all copies, or significant portions, of the Software.
|
6
|
+
|
7
|
+
2. The Software is provided "AS IS", without any warranty, whether express or implied. Crypticorn expressly disclaims any guarantees, including but not limited to those of satisfactory quality, fitness for a particular purpose, or non-infringement of third-party rights.
|
8
|
+
|
9
|
+
3. In no event shall Crypticorn, its authors, or copyright holders be held liable for any claim, damages, or other liability arising from or associated with the use of the Software or any other dealings involving the Software, to the extent that such liability can be limited or excluded by applicable law.
|
10
|
+
|
11
|
+
Changes to this agreement may be made without notice, and it is the user's responsibility to keep abreast of any changes. This agreement and its interpretation are subject to the laws of the Federal Republic of Germany.
|
12
|
+
|
13
|
+
If any provision of this license agreement is held to be invalid or unenforceable under applicable law, the remaining provisions will continue in full force and effect. Crypticorn's failure to enforce any provision of this agreement does not constitute a waiver of its right to do so in the future.
|
14
|
+
|
15
|
+
This agreement is written in English. In the event of any discrepancy between the English version and any translation, the English version shall prevail.
|
@@ -0,0 +1,95 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: crypticorn_utils
|
3
|
+
Version: 0.1.0rc1
|
4
|
+
Summary: Shared utilities for the Crypticorn APIs
|
5
|
+
Author-email: Crypticorn <timon@crypticorn.com>
|
6
|
+
License-Expression: MIT
|
7
|
+
Project-URL: Homepage, https://crypticorn.com
|
8
|
+
Project-URL: Documentation, https://docs.crypticorn.com
|
9
|
+
Project-URL: Dashboard, https://app.crypticorn.com
|
10
|
+
Classifier: Topic :: Scientific/Engineering
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: Operating System :: OS Independent
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
19
|
+
Classifier: Typing :: Typed
|
20
|
+
Requires-Python: >=3.9
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
License-File: LICENSE
|
23
|
+
Requires-Dist: fastapi<1.0.0,>=0.115.0
|
24
|
+
Requires-Dist: click<9.0.0,>=8.0.0
|
25
|
+
Requires-Dist: psutil<8.0.0,>=7.0.0
|
26
|
+
Requires-Dist: setuptools<81.0.0,>=80.0.0
|
27
|
+
Requires-Dist: strenum
|
28
|
+
Requires-Dist: prometheus-client<1.0.0,>=0.22.0
|
29
|
+
Provides-Extra: dev
|
30
|
+
Requires-Dist: build; extra == "dev"
|
31
|
+
Requires-Dist: twine; extra == "dev"
|
32
|
+
Requires-Dist: black; extra == "dev"
|
33
|
+
Requires-Dist: ruff; extra == "dev"
|
34
|
+
Requires-Dist: isort; extra == "dev"
|
35
|
+
Requires-Dist: mypy; extra == "dev"
|
36
|
+
Requires-Dist: openapi-generator-cli<8.0.0,>=7.12.0; extra == "dev"
|
37
|
+
Requires-Dist: python-semantic-release==9.21.0; extra == "dev"
|
38
|
+
Provides-Extra: test
|
39
|
+
Requires-Dist: pytest==8.3.5; extra == "test"
|
40
|
+
Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
|
41
|
+
Requires-Dist: pytest-cov==6.1.1; extra == "test"
|
42
|
+
Requires-Dist: python-dotenv==1.0.1; extra == "test"
|
43
|
+
Requires-Dist: PyJWT==2.10.0; extra == "test"
|
44
|
+
Requires-Dist: httpx>=0.27.0; extra == "test"
|
45
|
+
Requires-Dist: crypticorn>=2.17.0; extra == "test"
|
46
|
+
Dynamic: license-file
|
47
|
+
|
48
|
+
This module serves as a central place for providing utilities for our python backends.
|
49
|
+
|
50
|
+
- **Auth**: Authentication and authorization for APIs with API key and JWT bearer token support
|
51
|
+
- **Ansi Colors**: ANSI color codes for terminal text formatting and colorful console output
|
52
|
+
- **Decorators**: Utility decorators for model manipulation, including `partial_model` for optional fields
|
53
|
+
- **Enums**: Common enumerations for type safety and consistency
|
54
|
+
- **Errors**: Comprehensive error handling system with HTTP exceptions and error content structures
|
55
|
+
- **Exceptions**: Custom exception classes and error handling utilities
|
56
|
+
- **Logging**: Logging configuration and utilities for consistent formatting
|
57
|
+
- **Middleware**: API middleware components for request/response processing
|
58
|
+
- **Mixins**: Reusable functionality components for class mixing
|
59
|
+
- **Pagination**: Utilities for paginated API responses and cursor-based pagination
|
60
|
+
- **Router**: API routing utilities and components
|
61
|
+
- **Scopes**: Authorization scope definitions and management for API access control
|
62
|
+
- **Urls**: URL management utilities including base URLs, service endpoints, and API versioning
|
63
|
+
- **Utils**: General utility functions and helper methods
|
64
|
+
- **Warnings**: Warning handling and custom warning types
|
65
|
+
- **Metrics**: Shared metrics collection from APIs for visualization
|
66
|
+
|
67
|
+
# Changelog
|
68
|
+
|
69
|
+
<!-- changelog-insertion -->
|
70
|
+
|
71
|
+
## v0.1.0-rc.1 (2025-06-23)
|
72
|
+
|
73
|
+
### Documentation
|
74
|
+
|
75
|
+
- Add changelog
|
76
|
+
([`788f1f6`](https://github.com/crypticorn-ai/util-libraries/commit/788f1f670a8a50251401ebd1fc9ab7d2ca855a8d))
|
77
|
+
|
78
|
+
- Update Readme
|
79
|
+
([`d2b52cf`](https://github.com/crypticorn-ai/util-libraries/commit/d2b52cfe48de7a8b248ceefbc3bc7007ad21ea72))
|
80
|
+
|
81
|
+
### Features
|
82
|
+
|
83
|
+
- Initial release
|
84
|
+
([`4da5fe3`](https://github.com/crypticorn-ai/util-libraries/commit/4da5fe3d33abd31b3b35462e93052db0cde077c2))
|
85
|
+
|
86
|
+
|
87
|
+
## Unreleased
|
88
|
+
|
89
|
+
### Documentation
|
90
|
+
|
91
|
+
- Add changelog
|
92
|
+
([`788f1f6`](https://github.com/crypticorn-ai/util-libraries/commit/788f1f670a8a50251401ebd1fc9ab7d2ca855a8d))
|
93
|
+
|
94
|
+
- Update Readme
|
95
|
+
([`d2b52cf`](https://github.com/crypticorn-ai/util-libraries/commit/d2b52cfe48de7a8b248ceefbc3bc7007ad21ea72))
|
@@ -0,0 +1,48 @@
|
|
1
|
+
This module serves as a central place for providing utilities for our python backends.
|
2
|
+
|
3
|
+
- **Auth**: Authentication and authorization for APIs with API key and JWT bearer token support
|
4
|
+
- **Ansi Colors**: ANSI color codes for terminal text formatting and colorful console output
|
5
|
+
- **Decorators**: Utility decorators for model manipulation, including `partial_model` for optional fields
|
6
|
+
- **Enums**: Common enumerations for type safety and consistency
|
7
|
+
- **Errors**: Comprehensive error handling system with HTTP exceptions and error content structures
|
8
|
+
- **Exceptions**: Custom exception classes and error handling utilities
|
9
|
+
- **Logging**: Logging configuration and utilities for consistent formatting
|
10
|
+
- **Middleware**: API middleware components for request/response processing
|
11
|
+
- **Mixins**: Reusable functionality components for class mixing
|
12
|
+
- **Pagination**: Utilities for paginated API responses and cursor-based pagination
|
13
|
+
- **Router**: API routing utilities and components
|
14
|
+
- **Scopes**: Authorization scope definitions and management for API access control
|
15
|
+
- **Urls**: URL management utilities including base URLs, service endpoints, and API versioning
|
16
|
+
- **Utils**: General utility functions and helper methods
|
17
|
+
- **Warnings**: Warning handling and custom warning types
|
18
|
+
- **Metrics**: Shared metrics collection from APIs for visualization
|
19
|
+
|
20
|
+
# Changelog
|
21
|
+
|
22
|
+
<!-- changelog-insertion -->
|
23
|
+
|
24
|
+
## v0.1.0-rc.1 (2025-06-23)
|
25
|
+
|
26
|
+
### Documentation
|
27
|
+
|
28
|
+
- Add changelog
|
29
|
+
([`788f1f6`](https://github.com/crypticorn-ai/util-libraries/commit/788f1f670a8a50251401ebd1fc9ab7d2ca855a8d))
|
30
|
+
|
31
|
+
- Update Readme
|
32
|
+
([`d2b52cf`](https://github.com/crypticorn-ai/util-libraries/commit/d2b52cfe48de7a8b248ceefbc3bc7007ad21ea72))
|
33
|
+
|
34
|
+
### Features
|
35
|
+
|
36
|
+
- Initial release
|
37
|
+
([`4da5fe3`](https://github.com/crypticorn-ai/util-libraries/commit/4da5fe3d33abd31b3b35462e93052db0cde077c2))
|
38
|
+
|
39
|
+
|
40
|
+
## Unreleased
|
41
|
+
|
42
|
+
### Documentation
|
43
|
+
|
44
|
+
- Add changelog
|
45
|
+
([`788f1f6`](https://github.com/crypticorn-ai/util-libraries/commit/788f1f670a8a50251401ebd1fc9ab7d2ca855a8d))
|
46
|
+
|
47
|
+
- Update Readme
|
48
|
+
([`d2b52cf`](https://github.com/crypticorn-ai/util-libraries/commit/d2b52cfe48de7a8b248ceefbc3bc7007ad21ea72))
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from crypticorn_utils.ansi_colors import *
|
2
|
+
from crypticorn_utils.auth import *
|
3
|
+
from crypticorn_utils.decorators import *
|
4
|
+
from crypticorn_utils.enums import *
|
5
|
+
from crypticorn_utils.errors import *
|
6
|
+
from crypticorn_utils.exceptions import *
|
7
|
+
from crypticorn_utils.logging import *
|
8
|
+
from crypticorn_utils.metrics import *
|
9
|
+
from crypticorn_utils.middleware import *
|
10
|
+
from crypticorn_utils.mixins import *
|
11
|
+
from crypticorn_utils.openapi import *
|
12
|
+
from crypticorn_utils.pagination import *
|
13
|
+
from crypticorn_utils.router.admin_router import router as admin_router
|
14
|
+
from crypticorn_utils.router.status_router import router as status_router
|
15
|
+
from crypticorn_utils.utils import *
|
16
|
+
from crypticorn_utils.warnings import *
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This file is used to check if the crypticorn version is greater than 2.18
|
2
|
+
# This is to be compatible with this new utils library
|
3
|
+
|
4
|
+
import importlib.metadata
|
5
|
+
from importlib.metadata import PackageNotFoundError
|
6
|
+
|
7
|
+
try:
|
8
|
+
crypticorn_version = importlib.metadata.distribution("crypticorn").version
|
9
|
+
parts = crypticorn_version.split(".")
|
10
|
+
has_migrated = parts[0] >= "2" and parts[1] > "18"
|
11
|
+
except PackageNotFoundError:
|
12
|
+
has_migrated = False
|
@@ -0,0 +1,41 @@
|
|
1
|
+
try:
|
2
|
+
from enum import StrEnum
|
3
|
+
except ImportError:
|
4
|
+
from strenum import StrEnum
|
5
|
+
|
6
|
+
|
7
|
+
class AnsiColors(StrEnum):
|
8
|
+
"""Provides a collection of ANSI color codes for terminal text formatting, including regular, bright, and bold text colors. Useful for creating colorful and readable console output."""
|
9
|
+
|
10
|
+
# Regular Text Colors
|
11
|
+
BLACK = "\033[30m" # black
|
12
|
+
RED = "\033[31m" # red
|
13
|
+
GREEN = "\033[32m" # green
|
14
|
+
YELLOW = "\033[33m" # yellow
|
15
|
+
BLUE = "\033[34m" # blue
|
16
|
+
MAGENTA = "\033[35m" # magenta
|
17
|
+
CYAN = "\033[36m" # cyan
|
18
|
+
WHITE = "\033[37m" # white
|
19
|
+
|
20
|
+
# Bright Text Colors
|
21
|
+
BLACK_BRIGHT = "\033[90m" # black_bright
|
22
|
+
RED_BRIGHT = "\033[91m" # red_bright
|
23
|
+
GREEN_BRIGHT = "\033[92m" # green_bright
|
24
|
+
YELLOW_BRIGHT = "\033[93m" # yellow_bright
|
25
|
+
BLUE_BRIGHT = "\033[94m" # blue_bright
|
26
|
+
MAGENTA_BRIGHT = "\033[95m" # magenta_bright
|
27
|
+
CYAN_BRIGHT = "\033[96m" # cyan_bright
|
28
|
+
WHITE_BRIGHT = "\033[97m" # white_bright
|
29
|
+
|
30
|
+
# Bold Text Colors
|
31
|
+
BLACK_BOLD = "\033[1;30m" # black_bold
|
32
|
+
RED_BOLD = "\033[1;31m" # red_bold
|
33
|
+
GREEN_BOLD = "\033[1;32m" # green_bold
|
34
|
+
YELLOW_BOLD = "\033[1;33m" # yellow_bold
|
35
|
+
BLUE_BOLD = "\033[1;34m" # blue_bold
|
36
|
+
MAGENTA_BOLD = "\033[1;35m" # magenta_bold
|
37
|
+
CYAN_BOLD = "\033[1;36m" # cyan_bold
|
38
|
+
WHITE_BOLD = "\033[1;37m" # white_bold
|
39
|
+
|
40
|
+
# Reset Color
|
41
|
+
RESET = "\033[0m"
|
@@ -0,0 +1,345 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Union
|
3
|
+
|
4
|
+
from crypticorn.auth import AuthClient, Configuration, Verify200Response
|
5
|
+
from crypticorn.auth.client.exceptions import ApiException
|
6
|
+
from crypticorn_utils.enums import Scope, BaseUrl
|
7
|
+
from crypticorn_utils.exceptions import ApiError, ExceptionContent, HTTPException
|
8
|
+
from fastapi import Depends, Query
|
9
|
+
from fastapi.security import (
|
10
|
+
APIKeyHeader,
|
11
|
+
HTTPAuthorizationCredentials,
|
12
|
+
HTTPBasic,
|
13
|
+
HTTPBasicCredentials,
|
14
|
+
HTTPBearer,
|
15
|
+
SecurityScopes,
|
16
|
+
)
|
17
|
+
from typing_extensions import Annotated
|
18
|
+
|
19
|
+
AUTHENTICATE_HEADER = "WWW-Authenticate"
|
20
|
+
BEARER_AUTH_SCHEME = "Bearer"
|
21
|
+
APIKEY_AUTH_SCHEME = "X-API-Key"
|
22
|
+
BASIC_AUTH_SCHEME = "Basic"
|
23
|
+
|
24
|
+
# Auth Schemes
|
25
|
+
http_bearer = HTTPBearer(
|
26
|
+
bearerFormat="JWT",
|
27
|
+
auto_error=False,
|
28
|
+
description="The JWT to use for authentication.",
|
29
|
+
)
|
30
|
+
|
31
|
+
apikey_header = APIKeyHeader(
|
32
|
+
name=APIKEY_AUTH_SCHEME,
|
33
|
+
auto_error=False,
|
34
|
+
description="The API key to use for authentication.",
|
35
|
+
)
|
36
|
+
|
37
|
+
http_basic = HTTPBasic(
|
38
|
+
scheme_name=BASIC_AUTH_SCHEME,
|
39
|
+
auto_error=False,
|
40
|
+
description="The username and password to use for authentication.",
|
41
|
+
)
|
42
|
+
|
43
|
+
|
44
|
+
# Auth Handler
|
45
|
+
class AuthHandler:
|
46
|
+
"""
|
47
|
+
Middleware for verifying API requests. Verifies the validity of the authentication token, scopes, etc.
|
48
|
+
|
49
|
+
:param base_url: The base URL of the API.
|
50
|
+
:param api_version: The version of the API.
|
51
|
+
"""
|
52
|
+
|
53
|
+
def __init__(
|
54
|
+
self,
|
55
|
+
base_url: BaseUrl = BaseUrl.PROD,
|
56
|
+
):
|
57
|
+
self.url = f"{base_url}/v1/auth"
|
58
|
+
self.client = AuthClient(Configuration(host=self.url), is_sync=False)
|
59
|
+
|
60
|
+
async def _verify_api_key(self, api_key: str) -> Verify200Response:
|
61
|
+
"""
|
62
|
+
Verifies the API key.
|
63
|
+
"""
|
64
|
+
# self.client.config.api_key = {apikey_header.scheme_name: api_key}
|
65
|
+
return await self.client.login.verify_api_key(api_key)
|
66
|
+
|
67
|
+
async def _verify_bearer(
|
68
|
+
self, bearer: HTTPAuthorizationCredentials
|
69
|
+
) -> Verify200Response:
|
70
|
+
"""
|
71
|
+
Verifies the bearer token.
|
72
|
+
"""
|
73
|
+
self.client.config.access_token = bearer.credentials
|
74
|
+
return await self.client.login.verify()
|
75
|
+
|
76
|
+
async def _verify_basic(self, basic: HTTPBasicCredentials) -> Verify200Response:
|
77
|
+
"""
|
78
|
+
Verifies the basic authentication credentials.
|
79
|
+
"""
|
80
|
+
return await self.client.login.verify_basic_auth(basic.username, basic.password)
|
81
|
+
|
82
|
+
async def _validate_scopes(
|
83
|
+
self, api_scopes: list[Scope], user_scopes: list[Scope]
|
84
|
+
) -> bool:
|
85
|
+
"""
|
86
|
+
Checks if the required scopes are a subset of the user scopes.
|
87
|
+
"""
|
88
|
+
if not set(api_scopes).issubset(user_scopes):
|
89
|
+
raise HTTPException(
|
90
|
+
content=ExceptionContent(
|
91
|
+
error=ApiError.INSUFFICIENT_SCOPES,
|
92
|
+
message="Insufficient scopes to access this resource (required: "
|
93
|
+
+ ", ".join(api_scopes)
|
94
|
+
+ ")",
|
95
|
+
),
|
96
|
+
)
|
97
|
+
|
98
|
+
async def _extract_message(self, e: ApiException) -> str:
|
99
|
+
"""
|
100
|
+
Tries to extract the message from the body of the exception.
|
101
|
+
"""
|
102
|
+
try:
|
103
|
+
load = json.loads(e.body)
|
104
|
+
except (json.JSONDecodeError, TypeError):
|
105
|
+
return e.body
|
106
|
+
else:
|
107
|
+
common_keys = ["message"]
|
108
|
+
for key in common_keys:
|
109
|
+
if key in load:
|
110
|
+
return load[key]
|
111
|
+
return load
|
112
|
+
|
113
|
+
async def _handle_exception(self, e: Exception) -> HTTPException:
|
114
|
+
"""
|
115
|
+
Handles exceptions and returns a HTTPException with the appropriate status code and detail.
|
116
|
+
"""
|
117
|
+
if isinstance(e, ApiException):
|
118
|
+
# handle the TRPC Zod errors from auth-service
|
119
|
+
# Unfortunately, we cannot share the error messages defined in python/crypticorn/common/errors.py with the typescript client
|
120
|
+
message = await self._extract_message(e)
|
121
|
+
if message == "Invalid API key":
|
122
|
+
error = ApiError.INVALID_API_KEY
|
123
|
+
elif message == "API key expired":
|
124
|
+
error = ApiError.EXPIRED_API_KEY
|
125
|
+
elif message == "jwt expired":
|
126
|
+
error = ApiError.EXPIRED_BEARER
|
127
|
+
elif message == "Invalid basic authentication credentials":
|
128
|
+
error = ApiError.INVALID_BASIC_AUTH
|
129
|
+
else:
|
130
|
+
message = "Invalid bearer token"
|
131
|
+
error = (
|
132
|
+
ApiError.INVALID_BEARER
|
133
|
+
) # jwt malformed, jwt not active (https://www.npmjs.com/package/jsonwebtoken#errors--codes)
|
134
|
+
return HTTPException(
|
135
|
+
content=ExceptionContent(
|
136
|
+
error=error,
|
137
|
+
message=message,
|
138
|
+
),
|
139
|
+
)
|
140
|
+
elif isinstance(e, HTTPException):
|
141
|
+
return e
|
142
|
+
else:
|
143
|
+
return HTTPException(
|
144
|
+
content=ExceptionContent(
|
145
|
+
error=ApiError.UNKNOWN_ERROR,
|
146
|
+
message=str(e),
|
147
|
+
),
|
148
|
+
)
|
149
|
+
|
150
|
+
async def api_key_auth(
|
151
|
+
self,
|
152
|
+
api_key: Annotated[Union[str, None], Depends(apikey_header)] = None,
|
153
|
+
sec: SecurityScopes = SecurityScopes(),
|
154
|
+
) -> Verify200Response:
|
155
|
+
"""
|
156
|
+
Verifies the API key and checks the scopes.
|
157
|
+
Use this function if you only want to allow access via the API key.
|
158
|
+
This function is used for HTTP connections.
|
159
|
+
"""
|
160
|
+
try:
|
161
|
+
return await self.full_auth(
|
162
|
+
bearer=None, api_key=api_key, basic=None, sec=sec
|
163
|
+
)
|
164
|
+
except HTTPException as e:
|
165
|
+
raise HTTPException(
|
166
|
+
content=ExceptionContent(
|
167
|
+
error=ApiError.from_json(e.detail),
|
168
|
+
message=e.detail.get("message"),
|
169
|
+
),
|
170
|
+
headers={AUTHENTICATE_HEADER: APIKEY_AUTH_SCHEME},
|
171
|
+
)
|
172
|
+
|
173
|
+
async def bearer_auth(
|
174
|
+
self,
|
175
|
+
bearer: Annotated[
|
176
|
+
Union[HTTPAuthorizationCredentials, None],
|
177
|
+
Depends(http_bearer),
|
178
|
+
] = None,
|
179
|
+
sec: SecurityScopes = SecurityScopes(),
|
180
|
+
) -> Verify200Response:
|
181
|
+
"""
|
182
|
+
Verifies the bearer token and checks the scopes.
|
183
|
+
Use this function if you only want to allow access via the bearer token.
|
184
|
+
This function is used for HTTP connections.
|
185
|
+
"""
|
186
|
+
try:
|
187
|
+
return await self.full_auth(
|
188
|
+
bearer=bearer, api_key=None, basic=None, sec=sec
|
189
|
+
)
|
190
|
+
except HTTPException as e:
|
191
|
+
raise HTTPException(
|
192
|
+
content=ExceptionContent(
|
193
|
+
error=ApiError.from_json(e.detail),
|
194
|
+
message=e.detail.get("message"),
|
195
|
+
),
|
196
|
+
headers={AUTHENTICATE_HEADER: BEARER_AUTH_SCHEME},
|
197
|
+
)
|
198
|
+
|
199
|
+
async def basic_auth(
|
200
|
+
self,
|
201
|
+
credentials: Annotated[Union[HTTPBasicCredentials, None], Depends(http_basic)],
|
202
|
+
) -> Verify200Response:
|
203
|
+
"""
|
204
|
+
Verifies the basic authentication credentials. This authentication method should just be used for special cases like /admin/metrics, where JWT and API key authentication are not desired or not possible.
|
205
|
+
"""
|
206
|
+
try:
|
207
|
+
return await self.full_auth(
|
208
|
+
basic=credentials, bearer=None, api_key=None, sec=None
|
209
|
+
)
|
210
|
+
except HTTPException as e:
|
211
|
+
raise HTTPException(
|
212
|
+
content=ExceptionContent(
|
213
|
+
error=ApiError.from_json(e.detail),
|
214
|
+
message=e.detail.get("message"),
|
215
|
+
),
|
216
|
+
headers={AUTHENTICATE_HEADER: BASIC_AUTH_SCHEME},
|
217
|
+
)
|
218
|
+
|
219
|
+
async def combined_auth(
|
220
|
+
self,
|
221
|
+
bearer: Annotated[
|
222
|
+
Union[HTTPAuthorizationCredentials, None], Depends(http_bearer)
|
223
|
+
] = None,
|
224
|
+
api_key: Annotated[Union[str, None], Depends(apikey_header)] = None,
|
225
|
+
sec: SecurityScopes = SecurityScopes(),
|
226
|
+
) -> Verify200Response:
|
227
|
+
"""
|
228
|
+
Verifies the bearer token and/or API key and checks the scopes.
|
229
|
+
Returns early on the first successful verification, otherwise tries all available tokens.
|
230
|
+
Use this function if you want to allow access via either the bearer token or the API key.
|
231
|
+
This function is used for HTTP connections.
|
232
|
+
"""
|
233
|
+
try:
|
234
|
+
return await self.full_auth(
|
235
|
+
basic=None, bearer=bearer, api_key=api_key, sec=sec
|
236
|
+
)
|
237
|
+
except HTTPException as e:
|
238
|
+
raise HTTPException(
|
239
|
+
content=ExceptionContent(
|
240
|
+
error=ApiError.from_json(e.detail),
|
241
|
+
message=e.detail.get("message"),
|
242
|
+
),
|
243
|
+
headers={
|
244
|
+
AUTHENTICATE_HEADER: f"{BEARER_AUTH_SCHEME}, {APIKEY_AUTH_SCHEME}"
|
245
|
+
},
|
246
|
+
)
|
247
|
+
|
248
|
+
async def full_auth(
|
249
|
+
self,
|
250
|
+
basic: Annotated[Union[HTTPBasicCredentials, None], Depends(http_basic)] = None,
|
251
|
+
bearer: Annotated[
|
252
|
+
Union[HTTPAuthorizationCredentials, None], Depends(http_bearer)
|
253
|
+
] = None,
|
254
|
+
api_key: Annotated[Union[str, None], Depends(apikey_header)] = None,
|
255
|
+
sec: SecurityScopes = SecurityScopes(),
|
256
|
+
) -> Verify200Response:
|
257
|
+
"""
|
258
|
+
IMPORTANT: combined_auth is sufficient for most use cases. This function adds basic auth to the mix, which is needed for external services like prometheus, but is not recommended for internal use.
|
259
|
+
Verifies the bearer token, API key and basic authentication credentials and checks the scopes.
|
260
|
+
Returns early on the first successful verification, otherwise tries all available tokens.
|
261
|
+
Use this function if you want to allow access via either the bearer token, the API key or the basic authentication credentials.
|
262
|
+
This function is used for HTTP connections.
|
263
|
+
"""
|
264
|
+
tokens = [bearer, api_key, basic]
|
265
|
+
last_error = None
|
266
|
+
for token in tokens:
|
267
|
+
try:
|
268
|
+
if token is None:
|
269
|
+
continue
|
270
|
+
res = None
|
271
|
+
if isinstance(token, str):
|
272
|
+
res = await self._verify_api_key(token)
|
273
|
+
elif isinstance(token, HTTPAuthorizationCredentials):
|
274
|
+
res = await self._verify_bearer(token)
|
275
|
+
elif isinstance(token, HTTPBasicCredentials):
|
276
|
+
res = await self._verify_basic(token)
|
277
|
+
if res is None:
|
278
|
+
continue
|
279
|
+
if sec:
|
280
|
+
await self._validate_scopes(sec.scopes, res.scopes)
|
281
|
+
return res
|
282
|
+
|
283
|
+
except Exception as e:
|
284
|
+
last_error = await self._handle_exception(e)
|
285
|
+
continue
|
286
|
+
|
287
|
+
if last_error:
|
288
|
+
raise last_error
|
289
|
+
else:
|
290
|
+
raise HTTPException(
|
291
|
+
content=ExceptionContent(
|
292
|
+
error=ApiError.NO_CREDENTIALS,
|
293
|
+
message="No credentials provided. Check the WWW-Authenticate header for the available authentication methods.",
|
294
|
+
),
|
295
|
+
headers={
|
296
|
+
AUTHENTICATE_HEADER: f"{BEARER_AUTH_SCHEME}, {APIKEY_AUTH_SCHEME}, {BASIC_AUTH_SCHEME}"
|
297
|
+
},
|
298
|
+
)
|
299
|
+
|
300
|
+
async def ws_api_key_auth(
|
301
|
+
self,
|
302
|
+
api_key: Annotated[Union[str, None], Query()] = None,
|
303
|
+
sec: SecurityScopes = SecurityScopes(),
|
304
|
+
) -> Verify200Response:
|
305
|
+
"""
|
306
|
+
Verifies the API key and checks the scopes.
|
307
|
+
Use this function if you only want to allow access via the API key.
|
308
|
+
This function is used for WebSocket connections.
|
309
|
+
"""
|
310
|
+
return await self.api_key_auth(api_key=api_key, sec=sec)
|
311
|
+
|
312
|
+
async def ws_bearer_auth(
|
313
|
+
self,
|
314
|
+
bearer: Annotated[Union[str, None], Query()] = None,
|
315
|
+
sec: SecurityScopes = SecurityScopes(),
|
316
|
+
) -> Verify200Response:
|
317
|
+
"""
|
318
|
+
Verifies the bearer token and checks the scopes.
|
319
|
+
Use this function if you only want to allow access via the bearer token.
|
320
|
+
This function is used for WebSocket connections.
|
321
|
+
"""
|
322
|
+
credentials = (
|
323
|
+
HTTPAuthorizationCredentials(scheme="Bearer", credentials=bearer)
|
324
|
+
if bearer
|
325
|
+
else None
|
326
|
+
)
|
327
|
+
return await self.bearer_auth(bearer=credentials, sec=sec)
|
328
|
+
|
329
|
+
async def ws_combined_auth(
|
330
|
+
self,
|
331
|
+
bearer: Annotated[Union[str, None], Query()] = None,
|
332
|
+
api_key: Annotated[Union[str, None], Query()] = None,
|
333
|
+
sec: SecurityScopes = SecurityScopes(),
|
334
|
+
) -> Verify200Response:
|
335
|
+
"""
|
336
|
+
Verifies the bearer token and/or API key and checks the scopes.
|
337
|
+
Use this function if you want to allow access via either the bearer token or the API key.
|
338
|
+
This function is used for WebSocket connections.
|
339
|
+
"""
|
340
|
+
credentials = (
|
341
|
+
HTTPAuthorizationCredentials(scheme="Bearer", credentials=bearer)
|
342
|
+
if bearer
|
343
|
+
else None
|
344
|
+
)
|
345
|
+
return await self.combined_auth(bearer=credentials, api_key=api_key, sec=sec)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# crypticorn/cli.py
|
2
|
+
|
3
|
+
import click
|
4
|
+
from crypticorn.cli import init_group, version
|
5
|
+
|
6
|
+
|
7
|
+
@click.group()
|
8
|
+
def cli():
|
9
|
+
"""🧙 Crypticorn CLI — magic for our microservices."""
|
10
|
+
pass
|
11
|
+
|
12
|
+
|
13
|
+
cli.add_command(init_group, name="init")
|
14
|
+
cli.add_command(version, name="version")
|
15
|
+
|
16
|
+
if __name__ == "__main__":
|
17
|
+
cli()
|