crypticorn 1.0.2rc4__py3-none-any.whl → 2.0.1__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.
- crypticorn/__init__.py +3 -3
- crypticorn/auth/__init__.py +2 -0
- crypticorn/auth/client/__init__.py +112 -0
- crypticorn/auth/client/api/__init__.py +8 -0
- crypticorn/auth/client/api/admin_api.py +522 -0
- crypticorn/auth/client/api/auth_api.py +2089 -0
- crypticorn/auth/client/api/service_api.py +309 -0
- crypticorn/auth/client/api/user_api.py +2540 -0
- crypticorn/auth/client/api/wallet_api.py +1698 -0
- crypticorn/auth/client/api_client.py +758 -0
- crypticorn/auth/client/api_response.py +20 -0
- crypticorn/auth/client/configuration.py +584 -0
- crypticorn/auth/client/exceptions.py +220 -0
- crypticorn/auth/client/models/__init__.py +91 -0
- crypticorn/auth/client/models/add_wallet200_response.py +86 -0
- crypticorn/auth/client/models/add_wallet_request.py +107 -0
- crypticorn/auth/client/models/authorize_user200_response.py +107 -0
- crypticorn/auth/client/models/authorize_user200_response_auth.py +101 -0
- crypticorn/auth/client/models/authorize_user_request.py +96 -0
- crypticorn/auth/client/models/create_user_request.py +114 -0
- crypticorn/auth/client/models/list_wallets200_response.py +137 -0
- crypticorn/auth/client/models/list_wallets200_response_balances_inner.py +115 -0
- crypticorn/auth/client/models/list_wallets200_response_balances_inner_sale_round.py +115 -0
- crypticorn/auth/client/models/list_wallets200_response_balances_inner_wallet.py +168 -0
- crypticorn/auth/client/models/list_wallets200_response_balances_inner_wallet_vesting_wallets_inner.py +191 -0
- crypticorn/auth/client/models/list_wallets200_response_data_inner.py +102 -0
- crypticorn/auth/client/models/list_wallets200_response_user_value.py +118 -0
- crypticorn/auth/client/models/logout_default_response.py +108 -0
- crypticorn/auth/client/models/logout_default_response_issues_inner.py +83 -0
- crypticorn/auth/client/models/refresh_token_info200_response.py +97 -0
- crypticorn/auth/client/models/refresh_token_info200_response_user_session.py +105 -0
- crypticorn/auth/client/models/resend_verification_email_request.py +84 -0
- crypticorn/auth/client/models/revoke_user_tokens_request.py +83 -0
- crypticorn/auth/client/models/rotate_tokens200_response.py +110 -0
- crypticorn/auth/client/models/token_info200_response.py +97 -0
- crypticorn/auth/client/models/unlink_wallet_request.py +83 -0
- crypticorn/auth/client/models/update_user_request.py +93 -0
- crypticorn/auth/client/models/user_reset_password_request.py +87 -0
- crypticorn/auth/client/models/user_set_password_request.py +89 -0
- crypticorn/auth/client/models/verify200_response.py +110 -0
- crypticorn/auth/client/models/verify_email200_response.py +107 -0
- crypticorn/auth/client/models/verify_email200_response_auth.py +101 -0
- crypticorn/auth/client/models/verify_email200_response_auth_auth.py +110 -0
- crypticorn/auth/client/models/verify_email_request.py +83 -0
- crypticorn/auth/client/models/verify_wallet_request.py +91 -0
- crypticorn/auth/client/models/wallet_verified200_response.py +83 -0
- crypticorn/auth/client/models/whoami200_response.py +104 -0
- crypticorn/auth/client/rest.py +195 -0
- crypticorn/auth/main.py +45 -0
- crypticorn/client.py +46 -8
- crypticorn/common/__init__.py +5 -0
- crypticorn/common/auth.py +43 -0
- crypticorn/common/auth_client.py +183 -0
- crypticorn/common/errors.py +432 -0
- crypticorn/common/scopes.py +36 -0
- crypticorn/common/urls.py +25 -0
- crypticorn/hive/__init__.py +2 -1
- crypticorn/hive/client/__init__.py +57 -0
- crypticorn/hive/client/api/__init__.py +6 -0
- crypticorn/hive/client/api/data_api.py +594 -0
- crypticorn/hive/client/api/models_api.py +1680 -0
- crypticorn/hive/client/api/status_api.py +263 -0
- crypticorn/hive/client/api_client.py +758 -0
- crypticorn/hive/client/api_response.py +20 -0
- crypticorn/hive/client/configuration.py +612 -0
- crypticorn/hive/client/exceptions.py +220 -0
- crypticorn/hive/client/models/__init__.py +38 -0
- crypticorn/hive/client/models/coins.py +44 -0
- crypticorn/hive/client/models/data_download_response.py +113 -0
- crypticorn/hive/client/models/data_info.py +115 -0
- crypticorn/hive/client/models/data_value_value_value_inner.py +154 -0
- crypticorn/hive/client/models/data_version.py +35 -0
- crypticorn/hive/client/models/download_links.py +91 -0
- crypticorn/hive/client/models/evaluation.py +86 -0
- crypticorn/hive/client/models/evaluation_response.py +85 -0
- crypticorn/hive/client/models/feature_size.py +36 -0
- crypticorn/hive/client/models/http_validation_error.py +99 -0
- crypticorn/hive/client/models/model.py +133 -0
- crypticorn/hive/client/models/model_create.py +93 -0
- crypticorn/hive/client/models/model_status.py +35 -0
- crypticorn/hive/client/models/model_update.py +83 -0
- crypticorn/hive/client/models/target.py +36 -0
- crypticorn/hive/client/models/target_type.py +35 -0
- crypticorn/hive/client/models/validation_error.py +105 -0
- crypticorn/hive/client/models/validation_error_loc_inner.py +159 -0
- crypticorn/hive/client/py.typed +0 -0
- crypticorn/hive/client/rest.py +195 -0
- crypticorn/hive/main.py +27 -100
- crypticorn/klines/client/__init__.py +21 -7
- crypticorn/klines/client/api/__init__.py +0 -1
- crypticorn/klines/client/api/funding_rates_api.py +90 -79
- crypticorn/klines/client/api/health_check_api.py +29 -45
- crypticorn/klines/client/api/ohlcv_data_api.py +104 -87
- crypticorn/klines/client/api/symbols_api.py +36 -54
- crypticorn/klines/client/api/udf_api.py +228 -352
- crypticorn/klines/client/api_client.py +106 -148
- crypticorn/klines/client/api_response.py +2 -3
- crypticorn/klines/client/configuration.py +64 -50
- crypticorn/klines/client/exceptions.py +20 -16
- crypticorn/klines/client/models/__init__.py +21 -7
- crypticorn/klines/client/models/base_response_health_check_response.py +21 -15
- crypticorn/klines/client/models/base_response_list_funding_rate_response.py +21 -15
- crypticorn/klines/client/models/base_response_list_str.py +16 -14
- crypticorn/klines/client/models/base_response_ohlcv_response.py +21 -15
- crypticorn/klines/client/models/error_response.py +23 -15
- crypticorn/klines/client/models/exchange.py +11 -11
- crypticorn/klines/client/models/funding_rate_response.py +11 -11
- crypticorn/klines/client/models/health_check_response.py +14 -12
- crypticorn/klines/client/models/history_error_response.py +11 -11
- crypticorn/klines/client/models/history_no_data_response.py +16 -16
- crypticorn/klines/client/models/history_success_response.py +16 -16
- crypticorn/klines/client/models/http_validation_error.py +14 -10
- crypticorn/klines/client/models/market.py +2 -4
- crypticorn/klines/client/models/ohlcv_response.py +22 -15
- crypticorn/klines/client/models/resolution.py +5 -7
- crypticorn/klines/client/models/response_get_history_udf_history_get.py +71 -22
- crypticorn/klines/client/models/search_symbol_response.py +22 -15
- crypticorn/klines/client/models/sort_direction.py +2 -4
- crypticorn/klines/client/models/symbol_group_response.py +5 -9
- crypticorn/klines/client/models/symbol_info_response.py +40 -24
- crypticorn/klines/client/models/symbol_type.py +5 -10
- crypticorn/klines/client/models/timeframe.py +5 -7
- crypticorn/klines/client/models/udf_config_response.py +60 -21
- crypticorn/klines/client/models/validation_error.py +19 -13
- crypticorn/klines/client/models/validation_error_loc_inner.py +32 -11
- crypticorn/klines/client/rest.py +30 -41
- crypticorn/klines/main.py +52 -15
- crypticorn/pay/__init__.py +2 -0
- crypticorn/pay/client/__init__.py +52 -0
- crypticorn/pay/client/api/__init__.py +7 -0
- crypticorn/pay/client/api/now_payments_api.py +813 -0
- crypticorn/pay/client/api/payments_api.py +799 -0
- crypticorn/pay/client/api/products_api.py +891 -0
- crypticorn/pay/client/api/status_api.py +260 -0
- crypticorn/pay/client/api_client.py +758 -0
- crypticorn/pay/client/api_response.py +20 -0
- crypticorn/pay/client/configuration.py +612 -0
- crypticorn/pay/client/exceptions.py +220 -0
- crypticorn/pay/client/models/__init__.py +32 -0
- crypticorn/pay/client/models/api_status_res.py +83 -0
- crypticorn/pay/client/models/combined_payment_history.py +101 -0
- crypticorn/pay/client/models/create_invoice_req.py +188 -0
- crypticorn/pay/client/models/create_invoice_res.py +188 -0
- crypticorn/pay/client/models/currency.py +165 -0
- crypticorn/pay/client/models/estimate_price_req.py +91 -0
- crypticorn/pay/client/models/estimate_price_res.py +102 -0
- crypticorn/pay/client/models/get_currencies_res.py +99 -0
- crypticorn/pay/client/models/get_payment_status_res.py +222 -0
- crypticorn/pay/client/models/get_payments_list_res.py +109 -0
- crypticorn/pay/client/models/http_validation_error.py +99 -0
- crypticorn/pay/client/models/min_amount_req.py +124 -0
- crypticorn/pay/client/models/min_amount_res.py +105 -0
- crypticorn/pay/client/models/now_api_status_res.py +83 -0
- crypticorn/pay/client/models/now_create_invoice_req.py +188 -0
- crypticorn/pay/client/models/now_create_invoice_res.py +188 -0
- crypticorn/pay/client/models/now_fee_structure.py +104 -0
- crypticorn/pay/client/models/now_payment_model.py +124 -0
- crypticorn/pay/client/models/now_payment_status.py +42 -0
- crypticorn/pay/client/models/now_webhook_payload.py +181 -0
- crypticorn/pay/client/models/payment.py +231 -0
- crypticorn/pay/client/models/payment_status.py +40 -0
- crypticorn/pay/client/models/product.py +87 -0
- crypticorn/pay/client/models/product_model.py +119 -0
- crypticorn/pay/client/models/product_subs_model.py +108 -0
- crypticorn/pay/client/models/services.py +34 -0
- crypticorn/pay/client/models/unified_payment_model.py +112 -0
- crypticorn/pay/client/models/validation_error.py +105 -0
- crypticorn/pay/client/models/validation_error_loc_inner.py +159 -0
- crypticorn/pay/client/py.typed +0 -0
- crypticorn/pay/client/rest.py +195 -0
- crypticorn/pay/main.py +35 -0
- crypticorn/trade/client/__init__.py +9 -4
- crypticorn/trade/client/api/__init__.py +0 -1
- crypticorn/trade/client/api/api_keys_api.py +203 -304
- crypticorn/trade/client/api/bots_api.py +177 -250
- crypticorn/trade/client/api/exchanges_api.py +38 -57
- crypticorn/trade/client/api/futures_trading_panel_api.py +223 -321
- crypticorn/trade/client/api/notifications_api.py +247 -364
- crypticorn/trade/client/api/orders_api.py +44 -63
- crypticorn/trade/client/api/status_api.py +35 -53
- crypticorn/trade/client/api/strategies_api.py +852 -64
- crypticorn/trade/client/api/trading_actions_api.py +126 -203
- crypticorn/trade/client/api_client.py +115 -154
- crypticorn/trade/client/api_response.py +2 -3
- crypticorn/trade/client/configuration.py +128 -90
- crypticorn/trade/client/exceptions.py +21 -17
- crypticorn/trade/client/models/__init__.py +9 -4
- crypticorn/trade/client/models/action_model.py +114 -50
- crypticorn/trade/client/models/api_error_identifier.py +60 -51
- crypticorn/trade/client/models/api_error_level.py +37 -0
- crypticorn/trade/client/models/api_error_type.py +37 -0
- crypticorn/trade/client/models/api_key_model.py +49 -28
- crypticorn/trade/client/models/bot_model.py +76 -31
- crypticorn/trade/client/models/bot_status.py +37 -0
- crypticorn/trade/client/models/exchange.py +3 -5
- crypticorn/trade/client/models/execution_ids.py +14 -14
- crypticorn/trade/client/models/futures_balance.py +39 -23
- crypticorn/trade/client/models/futures_trading_action.py +98 -46
- crypticorn/trade/client/models/http_validation_error.py +15 -11
- crypticorn/trade/client/models/margin_mode.py +3 -5
- crypticorn/trade/client/models/market_type.py +3 -5
- crypticorn/trade/client/models/notification_model.py +60 -27
- crypticorn/trade/client/models/notification_type.py +4 -6
- crypticorn/trade/client/models/order_model.py +125 -65
- crypticorn/trade/client/models/order_status.py +6 -8
- crypticorn/trade/client/models/post_futures_action.py +16 -12
- crypticorn/trade/client/models/strategy_exchange_info.py +11 -12
- crypticorn/trade/client/models/strategy_model.py +66 -27
- crypticorn/trade/client/models/strategy_model_input.py +160 -0
- crypticorn/trade/client/models/strategy_model_output.py +160 -0
- crypticorn/trade/client/models/tpsl.py +35 -21
- crypticorn/trade/client/models/trading_action_type.py +5 -7
- crypticorn/trade/client/models/update_notification.py +17 -13
- crypticorn/trade/client/models/validation_error.py +20 -14
- crypticorn/trade/client/models/validation_error_loc_inner.py +33 -12
- crypticorn/trade/client/rest.py +108 -170
- crypticorn/trade/main.py +26 -19
- crypticorn-2.0.1.dist-info/METADATA +92 -0
- crypticorn-2.0.1.dist-info/RECORD +226 -0
- {crypticorn-1.0.2rc4.dist-info → crypticorn-2.0.1.dist-info}/WHEEL +1 -1
- crypticorn/hive/utils.py +0 -109
- crypticorn/klines/test/test_base_response_health_check_response.py +0 -56
- crypticorn/klines/test/test_base_response_list_funding_rate_response.py +0 -59
- crypticorn/klines/test/test_base_response_list_str.py +0 -56
- crypticorn/klines/test/test_base_response_ohlcv_response.py +0 -72
- crypticorn/klines/test/test_error_response.py +0 -57
- crypticorn/klines/test/test_exchange.py +0 -56
- crypticorn/klines/test/test_funding_rate_response.py +0 -56
- crypticorn/klines/test/test_funding_rates_api.py +0 -38
- crypticorn/klines/test/test_health_check_api.py +0 -38
- crypticorn/klines/test/test_health_check_response.py +0 -52
- crypticorn/klines/test/test_history_error_response.py +0 -53
- crypticorn/klines/test/test_history_no_data_response.py +0 -69
- crypticorn/klines/test/test_history_success_response.py +0 -87
- crypticorn/klines/test/test_http_validation_error.py +0 -58
- crypticorn/klines/test/test_market.py +0 -33
- crypticorn/klines/test/test_ohlcv_data_api.py +0 -38
- crypticorn/klines/test/test_ohlcv_response.py +0 -86
- crypticorn/klines/test/test_resolution.py +0 -33
- crypticorn/klines/test/test_response_get_history_udf_history_get.py +0 -89
- crypticorn/klines/test/test_search_symbol_response.py +0 -62
- crypticorn/klines/test/test_sort_direction.py +0 -33
- crypticorn/klines/test/test_symbol_group_response.py +0 -53
- crypticorn/klines/test/test_symbol_info_response.py +0 -84
- crypticorn/klines/test/test_symbol_type.py +0 -54
- crypticorn/klines/test/test_symbols_api.py +0 -38
- crypticorn/klines/test/test_timeframe.py +0 -33
- crypticorn/klines/test/test_udf_api.py +0 -80
- crypticorn/klines/test/test_udf_config_response.py +0 -95
- crypticorn/klines/test/test_validation_error.py +0 -60
- crypticorn/klines/test/test_validation_error_loc_inner.py +0 -50
- crypticorn-1.0.2rc4.dist-info/LICENSE.md +0 -19
- crypticorn-1.0.2rc4.dist-info/METADATA +0 -40
- crypticorn-1.0.2rc4.dist-info/RECORD +0 -125
- /crypticorn/{klines/test/__init__.py → auth/client/py.typed} +0 -0
- {crypticorn-1.0.2rc4.dist-info → crypticorn-2.0.1.dist-info}/top_level.txt +0 -0
crypticorn/auth/main.py
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
from crypticorn.auth import (
|
2
|
+
ApiClient,
|
3
|
+
Configuration,
|
4
|
+
AdminApi,
|
5
|
+
ServiceApi,
|
6
|
+
UserApi,
|
7
|
+
WalletApi,
|
8
|
+
AuthApi,
|
9
|
+
)
|
10
|
+
from crypticorn.common import BaseURL, ApiVersion, Service, apikey_header as aph
|
11
|
+
|
12
|
+
|
13
|
+
class AuthClient:
|
14
|
+
"""
|
15
|
+
A client for interacting with the Crypticorn Auth API.
|
16
|
+
"""
|
17
|
+
|
18
|
+
def __init__(
|
19
|
+
self,
|
20
|
+
base_url: BaseURL,
|
21
|
+
api_version: ApiVersion,
|
22
|
+
api_key: str = None,
|
23
|
+
jwt: str = None,
|
24
|
+
):
|
25
|
+
self.host = f"{base_url.value}/{api_version.value}/{Service.AUTH.value}"
|
26
|
+
self.config = Configuration(
|
27
|
+
host=self.host,
|
28
|
+
access_token=jwt,
|
29
|
+
api_key={aph.scheme_name: api_key} if api_key else None,
|
30
|
+
api_key_prefix=({aph.scheme_name: aph.model.name} if api_key else None),
|
31
|
+
debug=True,
|
32
|
+
)
|
33
|
+
self.base_client = ApiClient(configuration=self.config)
|
34
|
+
# Instantiate all the endpoint clients
|
35
|
+
self.admin = AdminApi(self.base_client)
|
36
|
+
self.service = ServiceApi(self.base_client)
|
37
|
+
self.user = UserApi(self.base_client)
|
38
|
+
self.wallet = WalletApi(self.base_client)
|
39
|
+
self.login = AuthApi(self.base_client)
|
40
|
+
|
41
|
+
async def __aenter__(self):
|
42
|
+
return self
|
43
|
+
|
44
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
45
|
+
await self.base_client.close()
|
crypticorn/client.py
CHANGED
@@ -1,16 +1,54 @@
|
|
1
1
|
from crypticorn.hive import HiveClient
|
2
|
-
from crypticorn.trade import TradeClient
|
3
2
|
from crypticorn.klines import KlinesClient
|
3
|
+
from crypticorn.pay import PayClient
|
4
|
+
from crypticorn.trade import TradeClient
|
5
|
+
from crypticorn.common import BaseURL, ApiVersion
|
6
|
+
|
7
|
+
|
8
|
+
class ApiClient:
|
9
|
+
"""
|
10
|
+
The official client for interacting with the Crypticorn API.
|
11
|
+
|
12
|
+
It is consisting of multiple microservices covering the whole stack of the Crypticorn project.
|
13
|
+
"""
|
4
14
|
|
5
|
-
class CrypticornClient:
|
6
15
|
def __init__(
|
7
|
-
self,
|
16
|
+
self,
|
17
|
+
base_url: BaseURL = BaseURL.PROD,
|
18
|
+
api_key: str = None,
|
19
|
+
jwt: str = None,
|
20
|
+
hive_version: ApiVersion = ApiVersion.V1,
|
21
|
+
klines_version: ApiVersion = ApiVersion.V1,
|
22
|
+
pay_version: ApiVersion = ApiVersion.V1,
|
23
|
+
trade_version: ApiVersion = ApiVersion.V1,
|
24
|
+
auth_version: ApiVersion = ApiVersion.V1,
|
8
25
|
):
|
9
26
|
self.base_url = base_url
|
10
27
|
self.api_key = api_key
|
11
28
|
self.jwt = jwt
|
12
|
-
|
13
|
-
|
14
|
-
self.
|
15
|
-
self.
|
16
|
-
|
29
|
+
self.hive = HiveClient(base_url, hive_version, api_key, jwt)
|
30
|
+
self.trade = TradeClient(base_url, trade_version, api_key, jwt)
|
31
|
+
self.klines = KlinesClient(base_url, klines_version, api_key, jwt)
|
32
|
+
self.pay = PayClient(base_url, pay_version, api_key, jwt)
|
33
|
+
# currently not working due to circular import since the AUTH_Handler
|
34
|
+
# is also using the ApiClient
|
35
|
+
# self.auth = AuthClient(base_url, auth_version, api_key, jwt)
|
36
|
+
|
37
|
+
async def __aenter__(self):
|
38
|
+
return self
|
39
|
+
|
40
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
41
|
+
await self.close()
|
42
|
+
|
43
|
+
async def close(self):
|
44
|
+
"""Close all client sessions."""
|
45
|
+
clients = [
|
46
|
+
self.hive.base_client,
|
47
|
+
self.trade.base_client,
|
48
|
+
self.klines.base_client,
|
49
|
+
self.pay.base_client,
|
50
|
+
]
|
51
|
+
|
52
|
+
for client in clients:
|
53
|
+
if hasattr(client, "close"):
|
54
|
+
await client.close()
|
@@ -0,0 +1,43 @@
|
|
1
|
+
from typing import Optional, Callable, Any, Annotated
|
2
|
+
from typing_extensions import Doc
|
3
|
+
from enum import Enum
|
4
|
+
|
5
|
+
from fastapi import params
|
6
|
+
from fastapi.security import APIKeyHeader, HTTPBearer
|
7
|
+
|
8
|
+
from crypticorn.common import Scope
|
9
|
+
|
10
|
+
|
11
|
+
http_bearer = HTTPBearer(
|
12
|
+
bearerFormat="JWT",
|
13
|
+
auto_error=False,
|
14
|
+
description="The JWT to use for authentication.",
|
15
|
+
)
|
16
|
+
|
17
|
+
apikey_header = APIKeyHeader(
|
18
|
+
name="X-API-Key",
|
19
|
+
auto_error=False,
|
20
|
+
description="The API key to use for authentication.",
|
21
|
+
)
|
22
|
+
|
23
|
+
|
24
|
+
def Security( # noqa: N802
|
25
|
+
dependency: Annotated[
|
26
|
+
Optional[Callable[..., Any]], Doc("A dependable callable (like a function).")
|
27
|
+
] = None,
|
28
|
+
scopes: Annotated[
|
29
|
+
Optional[list[Scope]], # Optional[Sequence[Union[str, Scope]]],
|
30
|
+
Doc("OAuth2 scopes required for the *path operation*."),
|
31
|
+
] = None,
|
32
|
+
use_cache: Annotated[
|
33
|
+
bool, Doc("Whether to cache the dependency during a single request.")
|
34
|
+
] = True,
|
35
|
+
) -> Any:
|
36
|
+
# Convert Enum scopes to string
|
37
|
+
scopes_str = (
|
38
|
+
[s.value if isinstance(s, Enum) else s for s in scopes] if scopes else None
|
39
|
+
)
|
40
|
+
|
41
|
+
return params.Security(
|
42
|
+
dependency=dependency, scopes=scopes_str, use_cache=use_cache
|
43
|
+
)
|
@@ -0,0 +1,183 @@
|
|
1
|
+
from fastapi import Depends, HTTPException, status
|
2
|
+
from fastapi.security import HTTPAuthorizationCredentials
|
3
|
+
from typing_extensions import Annotated, Doc
|
4
|
+
import json
|
5
|
+
|
6
|
+
from crypticorn.auth import AuthClient, Verify200Response
|
7
|
+
from crypticorn.auth.client.exceptions import ApiException
|
8
|
+
from crypticorn.common import (
|
9
|
+
ApiError,
|
10
|
+
Scope,
|
11
|
+
ApiVersion,
|
12
|
+
BaseURL,
|
13
|
+
Domain,
|
14
|
+
apikey_header,
|
15
|
+
http_bearer,
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
class AuthHandler:
|
20
|
+
"""
|
21
|
+
Middleware for verifying API requests. Verifies the validity of the authentication token, allowed scopes, etc.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
base_url: Annotated[
|
27
|
+
BaseURL, Doc("The base URL for the auth service.")
|
28
|
+
] = BaseURL.PROD,
|
29
|
+
api_version: Annotated[
|
30
|
+
ApiVersion, Doc("The API version of the auth service.")
|
31
|
+
] = ApiVersion.V1,
|
32
|
+
whitelist: Annotated[
|
33
|
+
list[Domain],
|
34
|
+
Doc(
|
35
|
+
"The domains of which requests are allowed full access to the service."
|
36
|
+
),
|
37
|
+
] = [
|
38
|
+
Domain.PROD,
|
39
|
+
Domain.DEV,
|
40
|
+
], # TODO: decide whether this is needed, else omit
|
41
|
+
):
|
42
|
+
self.whitelist = whitelist
|
43
|
+
self.auth_client = AuthClient(base_url=base_url, api_version=api_version)
|
44
|
+
|
45
|
+
self.no_credentials_exception = HTTPException(
|
46
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
47
|
+
detail=ApiError.NO_CREDENTIALS.identifier,
|
48
|
+
)
|
49
|
+
|
50
|
+
async def _verify_api_key(self, api_key: str) -> None:
|
51
|
+
"""
|
52
|
+
Verifies the API key.
|
53
|
+
"""
|
54
|
+
# TODO: Implement in auth service
|
55
|
+
raise NotImplementedError("API key verification not implemented")
|
56
|
+
|
57
|
+
async def _verify_bearer(
|
58
|
+
self, bearer: HTTPAuthorizationCredentials
|
59
|
+
) -> Verify200Response:
|
60
|
+
"""
|
61
|
+
Verifies the bearer token.
|
62
|
+
"""
|
63
|
+
self.auth_client.config.access_token = bearer.credentials
|
64
|
+
return await self.auth_client.login.verify()
|
65
|
+
|
66
|
+
async def _validate_scopes(
|
67
|
+
self, api_scopes: list[Scope], user_scopes: list[Scope]
|
68
|
+
) -> bool:
|
69
|
+
"""
|
70
|
+
Checks if the user scopes are a subset of the API scopes.
|
71
|
+
"""
|
72
|
+
if not set(api_scopes).issubset(user_scopes):
|
73
|
+
raise HTTPException(
|
74
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
75
|
+
detail=ApiError.INSUFFICIENT_SCOPES.identifier,
|
76
|
+
)
|
77
|
+
|
78
|
+
async def _extract_message(self, e: ApiException) -> str:
|
79
|
+
'''
|
80
|
+
Tries to extract the message from the body of the exception.
|
81
|
+
'''
|
82
|
+
try:
|
83
|
+
load = json.loads(e.body)
|
84
|
+
except (json.JSONDecodeError, TypeError):
|
85
|
+
return e.body
|
86
|
+
else:
|
87
|
+
common_keys = ["message"]
|
88
|
+
for key in common_keys:
|
89
|
+
if key in load:
|
90
|
+
return load[key]
|
91
|
+
return load
|
92
|
+
|
93
|
+
async def _handle_exception(self, e: Exception) -> HTTPException:
|
94
|
+
'''
|
95
|
+
Handles exceptions and returns a HTTPException with the appropriate status code and detail.
|
96
|
+
'''
|
97
|
+
if isinstance(e, ApiException):
|
98
|
+
return HTTPException(
|
99
|
+
status_code=e.status,
|
100
|
+
detail=await self._extract_message(e),
|
101
|
+
)
|
102
|
+
elif isinstance(e, HTTPException):
|
103
|
+
return e
|
104
|
+
else:
|
105
|
+
return HTTPException(
|
106
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
107
|
+
)
|
108
|
+
|
109
|
+
async def api_key_auth(
|
110
|
+
self,
|
111
|
+
api_key: Annotated[str | None, Depends(apikey_header)] = None,
|
112
|
+
scopes: list[Scope] = [],
|
113
|
+
) -> Verify200Response:
|
114
|
+
"""
|
115
|
+
Verifies the API key and checks if the user scopes are a subset of the API scopes.
|
116
|
+
"""
|
117
|
+
try:
|
118
|
+
if not api_key:
|
119
|
+
raise self.no_credentials_exception
|
120
|
+
|
121
|
+
res = await self._verify_api_key(api_key)
|
122
|
+
await self._validate_scopes(scopes, [Scope.from_str(scope) for scope in res.scopes])
|
123
|
+
return res
|
124
|
+
except Exception as e:
|
125
|
+
raise await self._handle_exception(e)
|
126
|
+
|
127
|
+
async def bearer_auth(
|
128
|
+
self,
|
129
|
+
bearer: Annotated[
|
130
|
+
HTTPAuthorizationCredentials | None,
|
131
|
+
Depends(http_bearer),
|
132
|
+
] = None,
|
133
|
+
scopes: list[Scope] = [],
|
134
|
+
) -> Verify200Response:
|
135
|
+
"""
|
136
|
+
Verifies the bearer token and checks if the user scopes are a subset of the API scopes.
|
137
|
+
"""
|
138
|
+
if not bearer:
|
139
|
+
raise self.no_credentials_exception
|
140
|
+
|
141
|
+
try:
|
142
|
+
res = await self._verify_bearer(bearer)
|
143
|
+
await self._validate_scopes(scopes, [Scope.from_str(scope) for scope in res.scopes])
|
144
|
+
return res
|
145
|
+
except Exception as e:
|
146
|
+
raise await self._handle_exception(e)
|
147
|
+
|
148
|
+
|
149
|
+
async def combined_auth(
|
150
|
+
self,
|
151
|
+
bearer: Annotated[
|
152
|
+
HTTPAuthorizationCredentials | None, Depends(http_bearer)
|
153
|
+
] = None,
|
154
|
+
api_key: Annotated[str | None, Depends(apikey_header)] = None,
|
155
|
+
scopes: list[Scope] = [],
|
156
|
+
) -> Verify200Response:
|
157
|
+
"""
|
158
|
+
Verifies the bearer token and API key and checks if the user scopes are a subset of the API scopes.
|
159
|
+
Returns early on the first successful verification, otherwise tries all available tokens.
|
160
|
+
"""
|
161
|
+
tokens = [bearer, api_key]
|
162
|
+
|
163
|
+
last_error = None
|
164
|
+
for token in tokens:
|
165
|
+
try:
|
166
|
+
if token is None:
|
167
|
+
continue
|
168
|
+
res = None
|
169
|
+
if isinstance(token, str):
|
170
|
+
res = await self._verify_api_key(token)
|
171
|
+
elif isinstance(token, HTTPAuthorizationCredentials):
|
172
|
+
res = await self._verify_bearer(token)
|
173
|
+
if res is None:
|
174
|
+
continue
|
175
|
+
if scopes:
|
176
|
+
await self._validate_scopes(scopes, [Scope.from_str(scope) for scope in res.scopes])
|
177
|
+
return res
|
178
|
+
|
179
|
+
except Exception as e:
|
180
|
+
last_error = await self._handle_exception(e)
|
181
|
+
continue
|
182
|
+
|
183
|
+
raise last_error or self.no_credentials_exception
|