miso-client 0.1.0__py3-none-any.whl → 3.7.2__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.
- miso_client/__init__.py +523 -130
- miso_client/api/__init__.py +35 -0
- miso_client/api/auth_api.py +367 -0
- miso_client/api/logs_api.py +91 -0
- miso_client/api/permissions_api.py +88 -0
- miso_client/api/roles_api.py +88 -0
- miso_client/api/types/__init__.py +75 -0
- miso_client/api/types/auth_types.py +183 -0
- miso_client/api/types/logs_types.py +71 -0
- miso_client/api/types/permissions_types.py +31 -0
- miso_client/api/types/roles_types.py +31 -0
- miso_client/errors.py +30 -4
- miso_client/models/__init__.py +4 -0
- miso_client/models/config.py +275 -72
- miso_client/models/error_response.py +39 -0
- miso_client/models/filter.py +255 -0
- miso_client/models/pagination.py +44 -0
- miso_client/models/sort.py +25 -0
- miso_client/services/__init__.py +6 -5
- miso_client/services/auth.py +496 -87
- miso_client/services/cache.py +42 -41
- miso_client/services/encryption.py +18 -17
- miso_client/services/logger.py +467 -328
- miso_client/services/logger_chain.py +288 -0
- miso_client/services/permission.py +130 -67
- miso_client/services/redis.py +28 -23
- miso_client/services/role.py +145 -62
- miso_client/utils/__init__.py +3 -3
- miso_client/utils/audit_log_queue.py +222 -0
- miso_client/utils/auth_strategy.py +88 -0
- miso_client/utils/auth_utils.py +65 -0
- miso_client/utils/circuit_breaker.py +125 -0
- miso_client/utils/client_token_manager.py +244 -0
- miso_client/utils/config_loader.py +88 -17
- miso_client/utils/controller_url_resolver.py +80 -0
- miso_client/utils/data_masker.py +104 -33
- miso_client/utils/environment_token.py +126 -0
- miso_client/utils/error_utils.py +216 -0
- miso_client/utils/fastapi_endpoints.py +166 -0
- miso_client/utils/filter.py +364 -0
- miso_client/utils/filter_applier.py +143 -0
- miso_client/utils/filter_parser.py +110 -0
- miso_client/utils/flask_endpoints.py +169 -0
- miso_client/utils/http_client.py +494 -262
- miso_client/utils/http_client_logging.py +352 -0
- miso_client/utils/http_client_logging_helpers.py +197 -0
- miso_client/utils/http_client_query_helpers.py +138 -0
- miso_client/utils/http_error_handler.py +92 -0
- miso_client/utils/http_log_formatter.py +115 -0
- miso_client/utils/http_log_masker.py +203 -0
- miso_client/utils/internal_http_client.py +435 -0
- miso_client/utils/jwt_tools.py +125 -16
- miso_client/utils/logger_helpers.py +206 -0
- miso_client/utils/logging_helpers.py +70 -0
- miso_client/utils/origin_validator.py +128 -0
- miso_client/utils/pagination.py +275 -0
- miso_client/utils/request_context.py +285 -0
- miso_client/utils/sensitive_fields_loader.py +116 -0
- miso_client/utils/sort.py +116 -0
- miso_client/utils/token_utils.py +114 -0
- miso_client/utils/url_validator.py +66 -0
- miso_client/utils/user_token_refresh.py +245 -0
- miso_client-3.7.2.dist-info/METADATA +1021 -0
- miso_client-3.7.2.dist-info/RECORD +68 -0
- miso_client-0.1.0.dist-info/METADATA +0 -551
- miso_client-0.1.0.dist-info/RECORD +0 -23
- {miso_client-0.1.0.dist-info → miso_client-3.7.2.dist-info}/WHEEL +0 -0
- {miso_client-0.1.0.dist-info → miso_client-3.7.2.dist-info}/licenses/LICENSE +0 -0
- {miso_client-0.1.0.dist-info → miso_client-3.7.2.dist-info}/top_level.txt +0 -0
miso_client/__init__.py
CHANGED
|
@@ -5,38 +5,96 @@ This package provides a reusable client SDK for integrating with the Miso Contro
|
|
|
5
5
|
for authentication, role-based access control, permission management, and logging.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import asyncio
|
|
9
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
9
10
|
|
|
11
|
+
from .errors import (
|
|
12
|
+
AuthenticationError,
|
|
13
|
+
AuthorizationError,
|
|
14
|
+
ConfigurationError,
|
|
15
|
+
ConnectionError,
|
|
16
|
+
MisoClientError,
|
|
17
|
+
)
|
|
10
18
|
from .models.config import (
|
|
11
|
-
RedisConfig,
|
|
12
|
-
MisoClientConfig,
|
|
13
|
-
UserInfo,
|
|
14
19
|
AuthResult,
|
|
20
|
+
AuthStrategy,
|
|
21
|
+
CircuitBreakerConfig,
|
|
22
|
+
ClientLoggingOptions,
|
|
23
|
+
ClientTokenEndpointOptions,
|
|
24
|
+
ClientTokenEndpointResponse,
|
|
25
|
+
ClientTokenResponse,
|
|
26
|
+
DataClientConfigResponse,
|
|
15
27
|
LogEntry,
|
|
16
|
-
|
|
28
|
+
MisoClientConfig,
|
|
17
29
|
PermissionResult,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
30
|
+
RedisConfig,
|
|
31
|
+
RoleResult,
|
|
32
|
+
UserInfo,
|
|
21
33
|
)
|
|
34
|
+
from .models.error_response import ErrorResponse
|
|
35
|
+
from .models.filter import (
|
|
36
|
+
FilterBuilder,
|
|
37
|
+
FilterGroup,
|
|
38
|
+
FilterOperator,
|
|
39
|
+
FilterOption,
|
|
40
|
+
FilterQuery,
|
|
41
|
+
JsonFilter,
|
|
42
|
+
)
|
|
43
|
+
from .models.pagination import Meta, PaginatedListResponse
|
|
44
|
+
from .models.sort import SortOption
|
|
22
45
|
from .services.auth import AuthService
|
|
23
|
-
from .services.
|
|
46
|
+
from .services.cache import CacheService
|
|
47
|
+
from .services.encryption import EncryptionService
|
|
48
|
+
from .services.logger import LoggerService
|
|
49
|
+
from .services.logger_chain import LoggerChain
|
|
24
50
|
from .services.permission import PermissionService
|
|
25
|
-
from .services.logger import LoggerService, LoggerChain
|
|
26
51
|
from .services.redis import RedisService
|
|
27
|
-
from .services.
|
|
28
|
-
from .
|
|
29
|
-
from .utils.http_client import HttpClient
|
|
52
|
+
from .services.role import RoleService
|
|
53
|
+
from .utils.audit_log_queue import AuditLogQueue
|
|
30
54
|
from .utils.config_loader import load_config
|
|
31
|
-
from .
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
55
|
+
from .utils.controller_url_resolver import is_browser, resolve_controller_url
|
|
56
|
+
from .utils.environment_token import get_environment_token
|
|
57
|
+
from .utils.error_utils import (
|
|
58
|
+
ApiErrorException,
|
|
59
|
+
handle_api_error_snake_case,
|
|
60
|
+
handleApiError,
|
|
61
|
+
transform_error_to_snake_case,
|
|
62
|
+
transformError,
|
|
63
|
+
)
|
|
64
|
+
from .utils.fastapi_endpoints import create_fastapi_client_token_endpoint
|
|
65
|
+
from .utils.filter import (
|
|
66
|
+
apply_filters,
|
|
67
|
+
build_query_string,
|
|
68
|
+
filter_query_to_json,
|
|
69
|
+
json_filter_to_query_string,
|
|
70
|
+
json_to_filter_query,
|
|
71
|
+
parse_filter_params,
|
|
72
|
+
query_string_to_json_filter,
|
|
73
|
+
validate_filter_option,
|
|
74
|
+
validate_json_filter,
|
|
75
|
+
)
|
|
76
|
+
from .utils.flask_endpoints import create_flask_client_token_endpoint
|
|
77
|
+
from .utils.http_client import HttpClient
|
|
78
|
+
from .utils.internal_http_client import InternalHttpClient
|
|
79
|
+
from .utils.jwt_tools import extract_user_id
|
|
80
|
+
from .utils.logging_helpers import extract_logging_context
|
|
81
|
+
from .utils.origin_validator import validate_origin
|
|
82
|
+
from .utils.pagination import (
|
|
83
|
+
apply_pagination_to_array,
|
|
84
|
+
applyPaginationToArray,
|
|
85
|
+
create_meta_object,
|
|
86
|
+
create_paginated_list_response,
|
|
87
|
+
createMetaObject,
|
|
88
|
+
createPaginatedListResponse,
|
|
89
|
+
parse_pagination_params,
|
|
90
|
+
parsePaginationParams,
|
|
37
91
|
)
|
|
92
|
+
from .utils.request_context import RequestContext, extract_request_context
|
|
93
|
+
from .utils.sort import build_sort_string, parse_sort_params
|
|
94
|
+
from .utils.token_utils import extract_client_token_info
|
|
95
|
+
from .utils.url_validator import validate_url
|
|
38
96
|
|
|
39
|
-
__version__ = "
|
|
97
|
+
__version__ = "3.7.2"
|
|
40
98
|
__author__ = "AI Fabrix Team"
|
|
41
99
|
__license__ = "MIT"
|
|
42
100
|
|
|
@@ -44,38 +102,77 @@ __license__ = "MIT"
|
|
|
44
102
|
class MisoClient:
|
|
45
103
|
"""
|
|
46
104
|
Main MisoClient SDK class for authentication, authorization, and logging.
|
|
47
|
-
|
|
105
|
+
|
|
48
106
|
This client provides a unified interface for:
|
|
49
107
|
- Token validation and user management
|
|
50
108
|
- Role-based access control
|
|
51
109
|
- Permission management
|
|
52
110
|
- Application logging with Redis caching
|
|
53
111
|
"""
|
|
54
|
-
|
|
112
|
+
|
|
55
113
|
def __init__(self, config: MisoClientConfig):
|
|
56
114
|
"""
|
|
57
115
|
Initialize MisoClient with configuration.
|
|
58
|
-
|
|
116
|
+
|
|
59
117
|
Args:
|
|
60
118
|
config: MisoClient configuration including controller URL, client credentials, etc.
|
|
61
119
|
"""
|
|
62
120
|
self.config = config
|
|
63
|
-
|
|
121
|
+
|
|
122
|
+
# Create InternalHttpClient first (pure HTTP functionality, no logging)
|
|
123
|
+
self._internal_http_client = InternalHttpClient(config)
|
|
124
|
+
|
|
125
|
+
# Create Redis service
|
|
64
126
|
self.redis = RedisService(config.redis)
|
|
127
|
+
|
|
128
|
+
# Create LoggerService with InternalHttpClient (to avoid circular dependency)
|
|
129
|
+
# LoggerService uses InternalHttpClient for sending logs to prevent audit loops
|
|
130
|
+
self.logger = LoggerService(self._internal_http_client, self.redis)
|
|
131
|
+
|
|
132
|
+
# Create public HttpClient wrapping InternalHttpClient with logger
|
|
133
|
+
# This HttpClient adds automatic ISO 27001 compliant audit and debug logging
|
|
134
|
+
self.http_client = HttpClient(config, self.logger)
|
|
135
|
+
|
|
136
|
+
# Create ApiClient for typed API calls (import here to avoid circular import)
|
|
137
|
+
from .api import ApiClient
|
|
138
|
+
|
|
139
|
+
self.api_client = ApiClient(self.http_client)
|
|
140
|
+
|
|
141
|
+
# Update LoggerService with http_client and api_client for audit log queue (if needed)
|
|
142
|
+
# This is safe because http_client is already created and logger is already set
|
|
143
|
+
if config.audit and (
|
|
144
|
+
config.audit.batchSize is not None or config.audit.batchInterval is not None
|
|
145
|
+
):
|
|
146
|
+
self.logger.audit_log_queue = AuditLogQueue(self.http_client, self.redis, config)
|
|
147
|
+
|
|
148
|
+
# Update LoggerService with api_client (optional, for typed API calls)
|
|
149
|
+
# Note: LoggerService primarily uses InternalHttpClient to avoid circular dependency
|
|
150
|
+
# ApiClient is provided as optional fallback
|
|
151
|
+
self.logger.api_client = self.api_client
|
|
152
|
+
|
|
65
153
|
# Cache service (uses Redis if available, falls back to in-memory)
|
|
66
154
|
self.cache = CacheService(self.redis)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
self.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.
|
|
155
|
+
|
|
156
|
+
# Services use ApiClient for typed API calls (with HttpClient fallback for backward compatibility)
|
|
157
|
+
self.auth = AuthService(self.http_client, self.redis, self.cache, self.api_client)
|
|
158
|
+
# Set auth_service on refresh manager for refresh endpoint calls
|
|
159
|
+
self.http_client.set_auth_service_for_refresh(self.auth)
|
|
160
|
+
self.roles = RoleService(self.http_client, self.cache, self.api_client)
|
|
161
|
+
self.permissions = PermissionService(self.http_client, self.cache, self.api_client)
|
|
162
|
+
|
|
163
|
+
# Encryption service (optional - only initialized if ENCRYPTION_KEY is configured)
|
|
164
|
+
self.encryption: Optional[EncryptionService]
|
|
165
|
+
try:
|
|
166
|
+
self.encryption = EncryptionService()
|
|
167
|
+
except ConfigurationError:
|
|
168
|
+
# ENCRYPTION_KEY not configured or invalid - encryption service unavailable
|
|
169
|
+
self.encryption = None
|
|
73
170
|
self.initialized = False
|
|
74
171
|
|
|
75
172
|
async def initialize(self) -> None:
|
|
76
173
|
"""
|
|
77
174
|
Initialize the client (connect to Redis if configured).
|
|
78
|
-
|
|
175
|
+
|
|
79
176
|
This method should be called before using the client. It will attempt
|
|
80
177
|
to connect to Redis if configured, but will gracefully fall back to
|
|
81
178
|
controller-only mode if Redis is unavailable.
|
|
@@ -105,16 +202,18 @@ class MisoClient:
|
|
|
105
202
|
def get_token(self, req: dict) -> str | None:
|
|
106
203
|
"""
|
|
107
204
|
Extract Bearer token from request headers.
|
|
108
|
-
|
|
205
|
+
|
|
109
206
|
Supports common request object patterns (dict with headers).
|
|
110
|
-
|
|
207
|
+
|
|
111
208
|
Args:
|
|
112
209
|
req: Request object with headers dict containing 'authorization' key
|
|
113
|
-
|
|
210
|
+
|
|
114
211
|
Returns:
|
|
115
212
|
Bearer token string or None if not found
|
|
116
213
|
"""
|
|
117
|
-
headers_obj =
|
|
214
|
+
headers_obj = (
|
|
215
|
+
req.get("headers", {}) if isinstance(req, dict) else getattr(req, "headers", {})
|
|
216
|
+
)
|
|
118
217
|
headers: dict[str, Any] = headers_obj if isinstance(headers_obj, dict) else {}
|
|
119
218
|
auth_value = headers.get("authorization") or headers.get("Authorization")
|
|
120
219
|
if not isinstance(auth_value, str):
|
|
@@ -123,223 +222,375 @@ class MisoClient:
|
|
|
123
222
|
# Support "Bearer <token>" format
|
|
124
223
|
if auth_value.startswith("Bearer "):
|
|
125
224
|
return auth_value[7:]
|
|
126
|
-
|
|
225
|
+
|
|
127
226
|
# If no Bearer prefix, assume the whole header is the token
|
|
128
227
|
return auth_value
|
|
129
228
|
|
|
130
229
|
async def get_environment_token(self) -> str:
|
|
131
230
|
"""
|
|
132
231
|
Get environment token using client credentials.
|
|
133
|
-
|
|
232
|
+
|
|
134
233
|
This is called automatically by HttpClient but can be called manually.
|
|
135
|
-
|
|
234
|
+
|
|
136
235
|
Returns:
|
|
137
236
|
Client token string
|
|
138
237
|
"""
|
|
139
238
|
return await self.auth.get_environment_token()
|
|
140
239
|
|
|
141
|
-
def login(self,
|
|
240
|
+
async def login(self, redirect: str, state: Optional[str] = None) -> Dict[str, Any]:
|
|
142
241
|
"""
|
|
143
|
-
Initiate login flow by
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
242
|
+
Initiate login flow by calling the controller login endpoint.
|
|
243
|
+
|
|
244
|
+
This method calls GET /api/v1/auth/login with redirect and optional state parameters.
|
|
245
|
+
The controller returns a login URL that should be used to redirect the user to Keycloak.
|
|
246
|
+
|
|
147
247
|
Args:
|
|
148
|
-
|
|
149
|
-
|
|
248
|
+
redirect: Callback URL where Keycloak redirects after authentication (required)
|
|
249
|
+
state: Optional CSRF protection token (auto-generated by backend if omitted)
|
|
250
|
+
|
|
150
251
|
Returns:
|
|
151
|
-
|
|
252
|
+
Dictionary containing:
|
|
253
|
+
- success: True if successful
|
|
254
|
+
- data: Dictionary with loginUrl and state
|
|
255
|
+
- timestamp: Response timestamp
|
|
256
|
+
|
|
257
|
+
Example:
|
|
258
|
+
>>> response = await client.login(
|
|
259
|
+
... redirect="http://localhost:3000/auth/callback",
|
|
260
|
+
... state="abc123"
|
|
261
|
+
... )
|
|
262
|
+
>>> login_url = response["data"]["loginUrl"]
|
|
263
|
+
>>> state = response["data"]["state"]
|
|
152
264
|
"""
|
|
153
|
-
return self.auth.login(
|
|
265
|
+
return await self.auth.login(redirect, state)
|
|
154
266
|
|
|
155
|
-
async def validate_token(
|
|
267
|
+
async def validate_token(
|
|
268
|
+
self, token: str, auth_strategy: Optional[AuthStrategy] = None
|
|
269
|
+
) -> bool:
|
|
156
270
|
"""
|
|
157
271
|
Validate token with controller.
|
|
158
|
-
|
|
272
|
+
|
|
159
273
|
Args:
|
|
160
274
|
token: JWT token to validate
|
|
161
|
-
|
|
275
|
+
auth_strategy: Optional authentication strategy
|
|
276
|
+
|
|
162
277
|
Returns:
|
|
163
278
|
True if token is valid, False otherwise
|
|
164
279
|
"""
|
|
165
|
-
return await self.auth.validate_token(token)
|
|
280
|
+
return await self.auth.validate_token(token, auth_strategy=auth_strategy)
|
|
166
281
|
|
|
167
|
-
async def get_user(
|
|
282
|
+
async def get_user(
|
|
283
|
+
self, token: str, auth_strategy: Optional[AuthStrategy] = None
|
|
284
|
+
) -> UserInfo | None:
|
|
168
285
|
"""
|
|
169
286
|
Get user information from token.
|
|
170
|
-
|
|
287
|
+
|
|
171
288
|
Args:
|
|
172
289
|
token: JWT token
|
|
173
|
-
|
|
290
|
+
auth_strategy: Optional authentication strategy
|
|
291
|
+
|
|
174
292
|
Returns:
|
|
175
293
|
UserInfo if token is valid, None otherwise
|
|
176
294
|
"""
|
|
177
|
-
return await self.auth.get_user(token)
|
|
295
|
+
return await self.auth.get_user(token, auth_strategy=auth_strategy)
|
|
178
296
|
|
|
179
|
-
async def get_user_info(
|
|
297
|
+
async def get_user_info(
|
|
298
|
+
self, token: str, auth_strategy: Optional[AuthStrategy] = None
|
|
299
|
+
) -> UserInfo | None:
|
|
180
300
|
"""
|
|
181
|
-
Get user information from GET /api/auth/user endpoint.
|
|
182
|
-
|
|
301
|
+
Get user information from GET /api/v1/auth/user endpoint.
|
|
302
|
+
|
|
183
303
|
Args:
|
|
184
304
|
token: JWT token
|
|
185
|
-
|
|
305
|
+
auth_strategy: Optional authentication strategy
|
|
306
|
+
|
|
186
307
|
Returns:
|
|
187
308
|
UserInfo if token is valid, None otherwise
|
|
188
309
|
"""
|
|
189
|
-
return await self.auth.get_user_info(token)
|
|
310
|
+
return await self.auth.get_user_info(token, auth_strategy=auth_strategy)
|
|
190
311
|
|
|
191
|
-
async def is_authenticated(
|
|
312
|
+
async def is_authenticated(
|
|
313
|
+
self, token: str, auth_strategy: Optional[AuthStrategy] = None
|
|
314
|
+
) -> bool:
|
|
192
315
|
"""
|
|
193
316
|
Check if user is authenticated.
|
|
194
|
-
|
|
317
|
+
|
|
195
318
|
Args:
|
|
196
319
|
token: JWT token
|
|
197
|
-
|
|
320
|
+
auth_strategy: Optional authentication strategy
|
|
321
|
+
|
|
198
322
|
Returns:
|
|
199
323
|
True if user is authenticated, False otherwise
|
|
200
324
|
"""
|
|
201
|
-
return await self.auth.is_authenticated(token)
|
|
325
|
+
return await self.auth.is_authenticated(token, auth_strategy=auth_strategy)
|
|
326
|
+
|
|
327
|
+
async def logout(self, token: str) -> Dict[str, Any]:
|
|
328
|
+
"""
|
|
329
|
+
Logout user by invalidating the access token.
|
|
330
|
+
|
|
331
|
+
This method calls POST /api/v1/auth/logout with the user's access token in the request body.
|
|
332
|
+
The token will be invalidated on the server side, and all local caches (roles, permissions, JWT)
|
|
333
|
+
will be cleared automatically. Refresh tokens and callbacks are also cleared.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
token: Access token to invalidate (required)
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Dictionary containing:
|
|
340
|
+
- success: True if successful
|
|
341
|
+
- message: Success message
|
|
342
|
+
- timestamp: Response timestamp
|
|
343
|
+
|
|
344
|
+
Example:
|
|
345
|
+
>>> response = await client.logout(token="jwt-token-here")
|
|
346
|
+
>>> if response.get("success"):
|
|
347
|
+
... print("Logout successful")
|
|
348
|
+
"""
|
|
349
|
+
# Extract user ID before logout
|
|
350
|
+
user_id = extract_user_id(token)
|
|
351
|
+
|
|
352
|
+
# Call AuthService logout (invalidates token on server)
|
|
353
|
+
response = await self.auth.logout(token)
|
|
354
|
+
|
|
355
|
+
# Clear refresh data for user
|
|
356
|
+
if user_id:
|
|
357
|
+
self.clear_user_token_refresh(user_id)
|
|
358
|
+
|
|
359
|
+
# Clear all caches after logout (even if logout failed, clear caches for security)
|
|
360
|
+
# Use asyncio.gather() for concurrent cache clearing
|
|
361
|
+
await asyncio.gather(
|
|
362
|
+
self.roles.clear_roles_cache(token),
|
|
363
|
+
self.permissions.clear_permissions_cache(token),
|
|
364
|
+
return_exceptions=True, # Don't fail if any cache clear fails
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return response
|
|
368
|
+
|
|
369
|
+
def register_user_token_refresh_callback(self, user_id: str, callback: Any) -> None:
|
|
370
|
+
"""
|
|
371
|
+
Register refresh callback for a user.
|
|
372
|
+
|
|
373
|
+
The callback will be called when the user's token needs to be refreshed.
|
|
374
|
+
The callback should be an async function that takes the old token and returns
|
|
375
|
+
the new token.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
user_id: User ID
|
|
379
|
+
callback: Async function that takes old token and returns new token
|
|
380
|
+
|
|
381
|
+
Example:
|
|
382
|
+
>>> async def refresh_token(old_token: str) -> str:
|
|
383
|
+
... # Call your refresh endpoint
|
|
384
|
+
... response = await your_auth_client.refresh(old_token)
|
|
385
|
+
... return response["access_token"]
|
|
386
|
+
>>>
|
|
387
|
+
>>> client.register_user_token_refresh_callback("user-123", refresh_token)
|
|
388
|
+
"""
|
|
389
|
+
self.http_client.register_user_token_refresh_callback(user_id, callback)
|
|
390
|
+
|
|
391
|
+
def register_user_refresh_token(self, user_id: str, refresh_token: str) -> None:
|
|
392
|
+
"""
|
|
393
|
+
Register refresh token for a user.
|
|
394
|
+
|
|
395
|
+
The SDK will use this refresh token to automatically refresh the user's
|
|
396
|
+
access token when it expires.
|
|
202
397
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
398
|
+
Args:
|
|
399
|
+
user_id: User ID
|
|
400
|
+
refresh_token: Refresh token string
|
|
401
|
+
|
|
402
|
+
Example:
|
|
403
|
+
>>> client.register_user_refresh_token("user-123", "refresh-token-abc")
|
|
404
|
+
"""
|
|
405
|
+
self.http_client.register_user_refresh_token(user_id, refresh_token)
|
|
406
|
+
|
|
407
|
+
def clear_user_token_refresh(self, user_id: str) -> None:
|
|
408
|
+
"""
|
|
409
|
+
Clear refresh callback and tokens for a user.
|
|
410
|
+
|
|
411
|
+
Useful when user logs out or refresh tokens are revoked.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
user_id: User ID
|
|
415
|
+
|
|
416
|
+
Example:
|
|
417
|
+
>>> client.clear_user_token_refresh("user-123")
|
|
418
|
+
"""
|
|
419
|
+
self.http_client._user_token_refresh.clear_user_tokens(user_id)
|
|
206
420
|
|
|
207
421
|
# ==================== AUTHORIZATION METHODS ====================
|
|
208
422
|
|
|
209
|
-
async def get_roles(
|
|
423
|
+
async def get_roles(
|
|
424
|
+
self, token: str, auth_strategy: Optional[AuthStrategy] = None
|
|
425
|
+
) -> list[str]:
|
|
210
426
|
"""
|
|
211
427
|
Get user roles (cached in Redis if available).
|
|
212
|
-
|
|
428
|
+
|
|
213
429
|
Args:
|
|
214
430
|
token: JWT token
|
|
215
|
-
|
|
431
|
+
auth_strategy: Optional authentication strategy
|
|
432
|
+
|
|
216
433
|
Returns:
|
|
217
434
|
List of user roles
|
|
218
435
|
"""
|
|
219
|
-
return await self.roles.get_roles(token)
|
|
436
|
+
return await self.roles.get_roles(token, auth_strategy=auth_strategy)
|
|
220
437
|
|
|
221
|
-
async def has_role(
|
|
438
|
+
async def has_role(
|
|
439
|
+
self, token: str, role: str, auth_strategy: Optional[AuthStrategy] = None
|
|
440
|
+
) -> bool:
|
|
222
441
|
"""
|
|
223
442
|
Check if user has specific role.
|
|
224
|
-
|
|
443
|
+
|
|
225
444
|
Args:
|
|
226
445
|
token: JWT token
|
|
227
446
|
role: Role to check
|
|
228
|
-
|
|
447
|
+
auth_strategy: Optional authentication strategy
|
|
448
|
+
|
|
229
449
|
Returns:
|
|
230
450
|
True if user has the role, False otherwise
|
|
231
451
|
"""
|
|
232
|
-
return await self.roles.has_role(token, role)
|
|
452
|
+
return await self.roles.has_role(token, role, auth_strategy=auth_strategy)
|
|
233
453
|
|
|
234
|
-
async def has_any_role(
|
|
454
|
+
async def has_any_role(
|
|
455
|
+
self, token: str, roles: list[str], auth_strategy: Optional[AuthStrategy] = None
|
|
456
|
+
) -> bool:
|
|
235
457
|
"""
|
|
236
458
|
Check if user has any of the specified roles.
|
|
237
|
-
|
|
459
|
+
|
|
238
460
|
Args:
|
|
239
461
|
token: JWT token
|
|
240
462
|
roles: List of roles to check
|
|
241
|
-
|
|
463
|
+
auth_strategy: Optional authentication strategy
|
|
464
|
+
|
|
242
465
|
Returns:
|
|
243
466
|
True if user has any of the roles, False otherwise
|
|
244
467
|
"""
|
|
245
|
-
return await self.roles.has_any_role(token, roles)
|
|
468
|
+
return await self.roles.has_any_role(token, roles, auth_strategy=auth_strategy)
|
|
246
469
|
|
|
247
|
-
async def has_all_roles(
|
|
470
|
+
async def has_all_roles(
|
|
471
|
+
self, token: str, roles: list[str], auth_strategy: Optional[AuthStrategy] = None
|
|
472
|
+
) -> bool:
|
|
248
473
|
"""
|
|
249
474
|
Check if user has all of the specified roles.
|
|
250
|
-
|
|
475
|
+
|
|
251
476
|
Args:
|
|
252
477
|
token: JWT token
|
|
253
478
|
roles: List of roles to check
|
|
254
|
-
|
|
479
|
+
auth_strategy: Optional authentication strategy
|
|
480
|
+
|
|
255
481
|
Returns:
|
|
256
482
|
True if user has all roles, False otherwise
|
|
257
483
|
"""
|
|
258
|
-
return await self.roles.has_all_roles(token, roles)
|
|
484
|
+
return await self.roles.has_all_roles(token, roles, auth_strategy=auth_strategy)
|
|
259
485
|
|
|
260
|
-
async def refresh_roles(
|
|
486
|
+
async def refresh_roles(
|
|
487
|
+
self, token: str, auth_strategy: Optional[AuthStrategy] = None
|
|
488
|
+
) -> list[str]:
|
|
261
489
|
"""
|
|
262
490
|
Force refresh roles from controller (bypass cache).
|
|
263
|
-
|
|
491
|
+
|
|
264
492
|
Args:
|
|
265
493
|
token: JWT token
|
|
266
|
-
|
|
494
|
+
auth_strategy: Optional authentication strategy
|
|
495
|
+
|
|
267
496
|
Returns:
|
|
268
497
|
Fresh list of user roles
|
|
269
498
|
"""
|
|
270
|
-
return await self.roles.refresh_roles(token)
|
|
499
|
+
return await self.roles.refresh_roles(token, auth_strategy=auth_strategy)
|
|
271
500
|
|
|
272
|
-
async def get_permissions(
|
|
501
|
+
async def get_permissions(
|
|
502
|
+
self, token: str, auth_strategy: Optional[AuthStrategy] = None
|
|
503
|
+
) -> list[str]:
|
|
273
504
|
"""
|
|
274
505
|
Get user permissions (cached in Redis if available).
|
|
275
|
-
|
|
506
|
+
|
|
276
507
|
Args:
|
|
277
508
|
token: JWT token
|
|
278
|
-
|
|
509
|
+
auth_strategy: Optional authentication strategy
|
|
510
|
+
|
|
279
511
|
Returns:
|
|
280
512
|
List of user permissions
|
|
281
513
|
"""
|
|
282
|
-
return await self.permissions.get_permissions(token)
|
|
514
|
+
return await self.permissions.get_permissions(token, auth_strategy=auth_strategy)
|
|
283
515
|
|
|
284
|
-
async def has_permission(
|
|
516
|
+
async def has_permission(
|
|
517
|
+
self, token: str, permission: str, auth_strategy: Optional[AuthStrategy] = None
|
|
518
|
+
) -> bool:
|
|
285
519
|
"""
|
|
286
520
|
Check if user has specific permission.
|
|
287
|
-
|
|
521
|
+
|
|
288
522
|
Args:
|
|
289
523
|
token: JWT token
|
|
290
524
|
permission: Permission to check
|
|
291
|
-
|
|
525
|
+
auth_strategy: Optional authentication strategy
|
|
526
|
+
|
|
292
527
|
Returns:
|
|
293
528
|
True if user has the permission, False otherwise
|
|
294
529
|
"""
|
|
295
|
-
return await self.permissions.has_permission(token, permission)
|
|
530
|
+
return await self.permissions.has_permission(token, permission, auth_strategy=auth_strategy)
|
|
296
531
|
|
|
297
|
-
async def has_any_permission(
|
|
532
|
+
async def has_any_permission(
|
|
533
|
+
self, token: str, permissions: list[str], auth_strategy: Optional[AuthStrategy] = None
|
|
534
|
+
) -> bool:
|
|
298
535
|
"""
|
|
299
536
|
Check if user has any of the specified permissions.
|
|
300
|
-
|
|
537
|
+
|
|
301
538
|
Args:
|
|
302
539
|
token: JWT token
|
|
303
540
|
permissions: List of permissions to check
|
|
304
|
-
|
|
541
|
+
auth_strategy: Optional authentication strategy
|
|
542
|
+
|
|
305
543
|
Returns:
|
|
306
544
|
True if user has any of the permissions, False otherwise
|
|
307
545
|
"""
|
|
308
|
-
return await self.permissions.has_any_permission(
|
|
546
|
+
return await self.permissions.has_any_permission(
|
|
547
|
+
token, permissions, auth_strategy=auth_strategy
|
|
548
|
+
)
|
|
309
549
|
|
|
310
|
-
async def has_all_permissions(
|
|
550
|
+
async def has_all_permissions(
|
|
551
|
+
self, token: str, permissions: list[str], auth_strategy: Optional[AuthStrategy] = None
|
|
552
|
+
) -> bool:
|
|
311
553
|
"""
|
|
312
554
|
Check if user has all of the specified permissions.
|
|
313
|
-
|
|
555
|
+
|
|
314
556
|
Args:
|
|
315
557
|
token: JWT token
|
|
316
558
|
permissions: List of permissions to check
|
|
317
|
-
|
|
559
|
+
auth_strategy: Optional authentication strategy
|
|
560
|
+
|
|
318
561
|
Returns:
|
|
319
562
|
True if user has all permissions, False otherwise
|
|
320
563
|
"""
|
|
321
|
-
return await self.permissions.has_all_permissions(
|
|
564
|
+
return await self.permissions.has_all_permissions(
|
|
565
|
+
token, permissions, auth_strategy=auth_strategy
|
|
566
|
+
)
|
|
322
567
|
|
|
323
|
-
async def refresh_permissions(
|
|
568
|
+
async def refresh_permissions(
|
|
569
|
+
self, token: str, auth_strategy: Optional[AuthStrategy] = None
|
|
570
|
+
) -> list[str]:
|
|
324
571
|
"""
|
|
325
572
|
Force refresh permissions from controller (bypass cache).
|
|
326
|
-
|
|
573
|
+
|
|
327
574
|
Args:
|
|
328
575
|
token: JWT token
|
|
329
|
-
|
|
576
|
+
auth_strategy: Optional authentication strategy
|
|
577
|
+
|
|
330
578
|
Returns:
|
|
331
579
|
Fresh list of user permissions
|
|
332
580
|
"""
|
|
333
|
-
return await self.permissions.refresh_permissions(token)
|
|
581
|
+
return await self.permissions.refresh_permissions(token, auth_strategy=auth_strategy)
|
|
334
582
|
|
|
335
|
-
async def clear_permissions_cache(
|
|
583
|
+
async def clear_permissions_cache(
|
|
584
|
+
self, token: str, auth_strategy: Optional[AuthStrategy] = None
|
|
585
|
+
) -> None:
|
|
336
586
|
"""
|
|
337
587
|
Clear cached permissions for a user.
|
|
338
|
-
|
|
588
|
+
|
|
339
589
|
Args:
|
|
340
590
|
token: JWT token
|
|
591
|
+
auth_strategy: Optional authentication strategy
|
|
341
592
|
"""
|
|
342
|
-
return await self.permissions.clear_permissions_cache(token)
|
|
593
|
+
return await self.permissions.clear_permissions_cache(token, auth_strategy=auth_strategy)
|
|
343
594
|
|
|
344
595
|
# ==================== LOGGING METHODS ====================
|
|
345
596
|
|
|
@@ -347,7 +598,7 @@ class MisoClient:
|
|
|
347
598
|
def log(self) -> LoggerService:
|
|
348
599
|
"""
|
|
349
600
|
Get logger service for application logging.
|
|
350
|
-
|
|
601
|
+
|
|
351
602
|
Returns:
|
|
352
603
|
LoggerService instance
|
|
353
604
|
"""
|
|
@@ -358,29 +609,45 @@ class MisoClient:
|
|
|
358
609
|
def encrypt(self, plaintext: str) -> str:
|
|
359
610
|
"""
|
|
360
611
|
Encrypt sensitive data.
|
|
361
|
-
|
|
612
|
+
|
|
362
613
|
Convenience method that delegates to encryption service.
|
|
363
|
-
|
|
614
|
+
|
|
364
615
|
Args:
|
|
365
616
|
plaintext: Plain text string to encrypt
|
|
366
|
-
|
|
617
|
+
|
|
367
618
|
Returns:
|
|
368
619
|
Base64-encoded encrypted string
|
|
620
|
+
|
|
621
|
+
Raises:
|
|
622
|
+
ConfigurationError: If encryption service is not available (ENCRYPTION_KEY not configured)
|
|
369
623
|
"""
|
|
624
|
+
if self.encryption is None:
|
|
625
|
+
raise ConfigurationError(
|
|
626
|
+
"Encryption service is not available. Set ENCRYPTION_KEY environment variable "
|
|
627
|
+
"to enable encryption functionality."
|
|
628
|
+
)
|
|
370
629
|
return self.encryption.encrypt(plaintext)
|
|
371
630
|
|
|
372
631
|
def decrypt(self, encrypted_text: str) -> str:
|
|
373
632
|
"""
|
|
374
633
|
Decrypt sensitive data.
|
|
375
|
-
|
|
634
|
+
|
|
376
635
|
Convenience method that delegates to encryption service.
|
|
377
|
-
|
|
636
|
+
|
|
378
637
|
Args:
|
|
379
638
|
encrypted_text: Base64-encoded encrypted string
|
|
380
|
-
|
|
639
|
+
|
|
381
640
|
Returns:
|
|
382
641
|
Decrypted plain text string
|
|
642
|
+
|
|
643
|
+
Raises:
|
|
644
|
+
ConfigurationError: If encryption service is not available (ENCRYPTION_KEY not configured)
|
|
383
645
|
"""
|
|
646
|
+
if self.encryption is None:
|
|
647
|
+
raise ConfigurationError(
|
|
648
|
+
"Encryption service is not available. Set ENCRYPTION_KEY environment variable "
|
|
649
|
+
"to enable encryption functionality."
|
|
650
|
+
)
|
|
384
651
|
return self.encryption.decrypt(encrypted_text)
|
|
385
652
|
|
|
386
653
|
# ==================== CACHING METHODS ====================
|
|
@@ -388,12 +655,12 @@ class MisoClient:
|
|
|
388
655
|
async def cache_get(self, key: str) -> Optional[Any]:
|
|
389
656
|
"""
|
|
390
657
|
Get cached value.
|
|
391
|
-
|
|
658
|
+
|
|
392
659
|
Convenience method that delegates to cache service.
|
|
393
|
-
|
|
660
|
+
|
|
394
661
|
Args:
|
|
395
662
|
key: Cache key
|
|
396
|
-
|
|
663
|
+
|
|
397
664
|
Returns:
|
|
398
665
|
Cached value if found, None otherwise
|
|
399
666
|
"""
|
|
@@ -402,14 +669,14 @@ class MisoClient:
|
|
|
402
669
|
async def cache_set(self, key: str, value: Any, ttl: int) -> bool:
|
|
403
670
|
"""
|
|
404
671
|
Set cached value with TTL.
|
|
405
|
-
|
|
672
|
+
|
|
406
673
|
Convenience method that delegates to cache service.
|
|
407
|
-
|
|
674
|
+
|
|
408
675
|
Args:
|
|
409
676
|
key: Cache key
|
|
410
677
|
value: Value to cache
|
|
411
678
|
ttl: Time to live in seconds
|
|
412
|
-
|
|
679
|
+
|
|
413
680
|
Returns:
|
|
414
681
|
True if successful, False otherwise
|
|
415
682
|
"""
|
|
@@ -418,12 +685,12 @@ class MisoClient:
|
|
|
418
685
|
async def cache_delete(self, key: str) -> bool:
|
|
419
686
|
"""
|
|
420
687
|
Delete cached value.
|
|
421
|
-
|
|
688
|
+
|
|
422
689
|
Convenience method that delegates to cache service.
|
|
423
|
-
|
|
690
|
+
|
|
424
691
|
Args:
|
|
425
692
|
key: Cache key
|
|
426
|
-
|
|
693
|
+
|
|
427
694
|
Returns:
|
|
428
695
|
True if deleted, False otherwise
|
|
429
696
|
"""
|
|
@@ -432,7 +699,7 @@ class MisoClient:
|
|
|
432
699
|
async def cache_clear(self) -> None:
|
|
433
700
|
"""
|
|
434
701
|
Clear all cached values.
|
|
435
|
-
|
|
702
|
+
|
|
436
703
|
Convenience method that delegates to cache service.
|
|
437
704
|
"""
|
|
438
705
|
await self.cache.clear()
|
|
@@ -442,7 +709,7 @@ class MisoClient:
|
|
|
442
709
|
def get_config(self) -> MisoClientConfig:
|
|
443
710
|
"""
|
|
444
711
|
Get current configuration.
|
|
445
|
-
|
|
712
|
+
|
|
446
713
|
Returns:
|
|
447
714
|
Copy of current configuration
|
|
448
715
|
"""
|
|
@@ -451,12 +718,75 @@ class MisoClient:
|
|
|
451
718
|
def is_redis_connected(self) -> bool:
|
|
452
719
|
"""
|
|
453
720
|
Check if Redis is connected.
|
|
454
|
-
|
|
721
|
+
|
|
455
722
|
Returns:
|
|
456
723
|
True if Redis is connected, False otherwise
|
|
457
724
|
"""
|
|
458
725
|
return self.redis.is_connected()
|
|
459
726
|
|
|
727
|
+
# ==================== AUTHENTICATION STRATEGY METHODS ====================
|
|
728
|
+
|
|
729
|
+
def create_auth_strategy(
|
|
730
|
+
self,
|
|
731
|
+
methods: List[Literal["bearer", "client-token", "client-credentials", "api-key"]],
|
|
732
|
+
bearer_token: Optional[str] = None,
|
|
733
|
+
api_key: Optional[str] = None,
|
|
734
|
+
) -> AuthStrategy:
|
|
735
|
+
"""
|
|
736
|
+
Create an authentication strategy object.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
methods: List of authentication methods in priority order
|
|
740
|
+
bearer_token: Optional bearer token for bearer auth
|
|
741
|
+
api_key: Optional API key for api-key auth
|
|
742
|
+
|
|
743
|
+
Returns:
|
|
744
|
+
AuthStrategy instance
|
|
745
|
+
|
|
746
|
+
Example:
|
|
747
|
+
>>> strategy = client.create_auth_strategy(
|
|
748
|
+
... ['api-key'],
|
|
749
|
+
... bearer_token=None,
|
|
750
|
+
... api_key='your-api-key-here'
|
|
751
|
+
... )
|
|
752
|
+
"""
|
|
753
|
+
return AuthStrategy(methods=methods, bearerToken=bearer_token, apiKey=api_key)
|
|
754
|
+
|
|
755
|
+
async def request_with_auth_strategy(
|
|
756
|
+
self,
|
|
757
|
+
method: Literal["GET", "POST", "PUT", "DELETE"],
|
|
758
|
+
url: str,
|
|
759
|
+
auth_strategy: AuthStrategy,
|
|
760
|
+
data: Optional[Dict[str, Any]] = None,
|
|
761
|
+
**kwargs,
|
|
762
|
+
) -> Any:
|
|
763
|
+
"""
|
|
764
|
+
Make request with authentication strategy (priority-based fallback).
|
|
765
|
+
|
|
766
|
+
Tries authentication methods in priority order until one succeeds.
|
|
767
|
+
If a method returns 401, automatically tries the next method in the strategy.
|
|
768
|
+
|
|
769
|
+
Args:
|
|
770
|
+
method: HTTP method
|
|
771
|
+
url: Request URL
|
|
772
|
+
auth_strategy: Authentication strategy configuration
|
|
773
|
+
data: Request data (for POST/PUT)
|
|
774
|
+
**kwargs: Additional httpx request parameters
|
|
775
|
+
|
|
776
|
+
Returns:
|
|
777
|
+
Response data (JSON parsed)
|
|
778
|
+
|
|
779
|
+
Raises:
|
|
780
|
+
MisoClientError: If all authentication methods fail
|
|
781
|
+
|
|
782
|
+
Example:
|
|
783
|
+
>>> strategy = client.create_auth_strategy(['api-key'], api_key='your-key')
|
|
784
|
+
>>> response = await client.request_with_auth_strategy('GET', '/api/data', strategy)
|
|
785
|
+
"""
|
|
786
|
+
return await self.http_client.request_with_auth_strategy(
|
|
787
|
+
method, url, auth_strategy, data, **kwargs
|
|
788
|
+
)
|
|
789
|
+
|
|
460
790
|
|
|
461
791
|
# Export types
|
|
462
792
|
__all__ = [
|
|
@@ -465,12 +795,60 @@ __all__ = [
|
|
|
465
795
|
"MisoClientConfig",
|
|
466
796
|
"UserInfo",
|
|
467
797
|
"AuthResult",
|
|
798
|
+
"AuthStrategy",
|
|
468
799
|
"LogEntry",
|
|
469
800
|
"RoleResult",
|
|
470
801
|
"PermissionResult",
|
|
471
802
|
"ClientTokenResponse",
|
|
472
|
-
"
|
|
803
|
+
"ClientTokenEndpointResponse",
|
|
804
|
+
"ClientTokenEndpointOptions",
|
|
805
|
+
"DataClientConfigResponse",
|
|
806
|
+
"CircuitBreakerConfig",
|
|
473
807
|
"ClientLoggingOptions",
|
|
808
|
+
"ErrorResponse",
|
|
809
|
+
# Pagination models
|
|
810
|
+
"Meta",
|
|
811
|
+
"PaginatedListResponse",
|
|
812
|
+
# Filter models
|
|
813
|
+
"FilterOperator",
|
|
814
|
+
"FilterOption",
|
|
815
|
+
"FilterQuery",
|
|
816
|
+
"FilterBuilder",
|
|
817
|
+
"JsonFilter",
|
|
818
|
+
"FilterGroup",
|
|
819
|
+
# Sort models
|
|
820
|
+
"SortOption",
|
|
821
|
+
# Pagination utilities (camelCase)
|
|
822
|
+
"parsePaginationParams",
|
|
823
|
+
"createMetaObject",
|
|
824
|
+
"applyPaginationToArray",
|
|
825
|
+
"createPaginatedListResponse",
|
|
826
|
+
# Pagination utilities (legacy snake_case aliases)
|
|
827
|
+
"parse_pagination_params",
|
|
828
|
+
"create_meta_object",
|
|
829
|
+
"apply_pagination_to_array",
|
|
830
|
+
"create_paginated_list_response",
|
|
831
|
+
# Filter utilities
|
|
832
|
+
"parse_filter_params",
|
|
833
|
+
"build_query_string",
|
|
834
|
+
"apply_filters",
|
|
835
|
+
"filter_query_to_json",
|
|
836
|
+
"json_to_filter_query",
|
|
837
|
+
"json_filter_to_query_string",
|
|
838
|
+
"query_string_to_json_filter",
|
|
839
|
+
"validate_filter_option",
|
|
840
|
+
"validate_json_filter",
|
|
841
|
+
# Sort utilities
|
|
842
|
+
"parse_sort_params",
|
|
843
|
+
"build_sort_string",
|
|
844
|
+
# Error utilities (camelCase)
|
|
845
|
+
"transformError",
|
|
846
|
+
"handleApiError",
|
|
847
|
+
"ApiErrorException",
|
|
848
|
+
# Error utilities (legacy snake_case aliases)
|
|
849
|
+
"transform_error_to_snake_case",
|
|
850
|
+
"handle_api_error_snake_case",
|
|
851
|
+
# Services
|
|
474
852
|
"AuthService",
|
|
475
853
|
"RoleService",
|
|
476
854
|
"PermissionService",
|
|
@@ -480,10 +858,25 @@ __all__ = [
|
|
|
480
858
|
"EncryptionService",
|
|
481
859
|
"CacheService",
|
|
482
860
|
"HttpClient",
|
|
861
|
+
"AuditLogQueue",
|
|
483
862
|
"load_config",
|
|
484
863
|
"MisoClientError",
|
|
485
864
|
"AuthenticationError",
|
|
486
865
|
"AuthorizationError",
|
|
487
866
|
"ConnectionError",
|
|
488
867
|
"ConfigurationError",
|
|
868
|
+
# Server-side utilities
|
|
869
|
+
"get_environment_token",
|
|
870
|
+
"validate_origin",
|
|
871
|
+
"extract_client_token_info",
|
|
872
|
+
"validate_url",
|
|
873
|
+
"resolve_controller_url",
|
|
874
|
+
"is_browser",
|
|
875
|
+
"create_flask_client_token_endpoint",
|
|
876
|
+
"create_fastapi_client_token_endpoint",
|
|
877
|
+
# Request context utilities
|
|
878
|
+
"extract_request_context",
|
|
879
|
+
"RequestContext",
|
|
880
|
+
# Logging utilities
|
|
881
|
+
"extract_logging_context",
|
|
489
882
|
]
|