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.
Files changed (69) hide show
  1. miso_client/__init__.py +523 -130
  2. miso_client/api/__init__.py +35 -0
  3. miso_client/api/auth_api.py +367 -0
  4. miso_client/api/logs_api.py +91 -0
  5. miso_client/api/permissions_api.py +88 -0
  6. miso_client/api/roles_api.py +88 -0
  7. miso_client/api/types/__init__.py +75 -0
  8. miso_client/api/types/auth_types.py +183 -0
  9. miso_client/api/types/logs_types.py +71 -0
  10. miso_client/api/types/permissions_types.py +31 -0
  11. miso_client/api/types/roles_types.py +31 -0
  12. miso_client/errors.py +30 -4
  13. miso_client/models/__init__.py +4 -0
  14. miso_client/models/config.py +275 -72
  15. miso_client/models/error_response.py +39 -0
  16. miso_client/models/filter.py +255 -0
  17. miso_client/models/pagination.py +44 -0
  18. miso_client/models/sort.py +25 -0
  19. miso_client/services/__init__.py +6 -5
  20. miso_client/services/auth.py +496 -87
  21. miso_client/services/cache.py +42 -41
  22. miso_client/services/encryption.py +18 -17
  23. miso_client/services/logger.py +467 -328
  24. miso_client/services/logger_chain.py +288 -0
  25. miso_client/services/permission.py +130 -67
  26. miso_client/services/redis.py +28 -23
  27. miso_client/services/role.py +145 -62
  28. miso_client/utils/__init__.py +3 -3
  29. miso_client/utils/audit_log_queue.py +222 -0
  30. miso_client/utils/auth_strategy.py +88 -0
  31. miso_client/utils/auth_utils.py +65 -0
  32. miso_client/utils/circuit_breaker.py +125 -0
  33. miso_client/utils/client_token_manager.py +244 -0
  34. miso_client/utils/config_loader.py +88 -17
  35. miso_client/utils/controller_url_resolver.py +80 -0
  36. miso_client/utils/data_masker.py +104 -33
  37. miso_client/utils/environment_token.py +126 -0
  38. miso_client/utils/error_utils.py +216 -0
  39. miso_client/utils/fastapi_endpoints.py +166 -0
  40. miso_client/utils/filter.py +364 -0
  41. miso_client/utils/filter_applier.py +143 -0
  42. miso_client/utils/filter_parser.py +110 -0
  43. miso_client/utils/flask_endpoints.py +169 -0
  44. miso_client/utils/http_client.py +494 -262
  45. miso_client/utils/http_client_logging.py +352 -0
  46. miso_client/utils/http_client_logging_helpers.py +197 -0
  47. miso_client/utils/http_client_query_helpers.py +138 -0
  48. miso_client/utils/http_error_handler.py +92 -0
  49. miso_client/utils/http_log_formatter.py +115 -0
  50. miso_client/utils/http_log_masker.py +203 -0
  51. miso_client/utils/internal_http_client.py +435 -0
  52. miso_client/utils/jwt_tools.py +125 -16
  53. miso_client/utils/logger_helpers.py +206 -0
  54. miso_client/utils/logging_helpers.py +70 -0
  55. miso_client/utils/origin_validator.py +128 -0
  56. miso_client/utils/pagination.py +275 -0
  57. miso_client/utils/request_context.py +285 -0
  58. miso_client/utils/sensitive_fields_loader.py +116 -0
  59. miso_client/utils/sort.py +116 -0
  60. miso_client/utils/token_utils.py +114 -0
  61. miso_client/utils/url_validator.py +66 -0
  62. miso_client/utils/user_token_refresh.py +245 -0
  63. miso_client-3.7.2.dist-info/METADATA +1021 -0
  64. miso_client-3.7.2.dist-info/RECORD +68 -0
  65. miso_client-0.1.0.dist-info/METADATA +0 -551
  66. miso_client-0.1.0.dist-info/RECORD +0 -23
  67. {miso_client-0.1.0.dist-info → miso_client-3.7.2.dist-info}/WHEEL +0 -0
  68. {miso_client-0.1.0.dist-info → miso_client-3.7.2.dist-info}/licenses/LICENSE +0 -0
  69. {miso_client-0.1.0.dist-info → miso_client-3.7.2.dist-info}/top_level.txt +0 -0
@@ -5,156 +5,565 @@ This module handles authentication operations including client token management,
5
5
  token validation, user information retrieval, and logout functionality.
6
6
  """
7
7
 
8
- from typing import Optional
9
- from ..models.config import UserInfo, AuthResult
8
+ import hashlib
9
+ import logging
10
+ import time
11
+ from typing import TYPE_CHECKING, Any, Dict, Optional, cast
12
+
13
+ from ..models.config import AuthResult, AuthStrategy, UserInfo
14
+ from ..services.cache import CacheService
10
15
  from ..services.redis import RedisService
16
+ from ..utils.error_utils import extract_correlation_id_from_error
11
17
  from ..utils.http_client import HttpClient
18
+ from ..utils.jwt_tools import decode_token
19
+
20
+ if TYPE_CHECKING:
21
+ from ..api import ApiClient
22
+
23
+ logger = logging.getLogger(__name__)
12
24
 
13
25
 
14
26
  class AuthService:
15
27
  """Authentication service for token validation and user management."""
16
-
17
- def __init__(self, http_client: HttpClient, redis: RedisService):
28
+
29
+ def __init__(
30
+ self,
31
+ http_client: HttpClient,
32
+ redis: RedisService,
33
+ cache: Optional[CacheService] = None,
34
+ api_client: Optional["ApiClient"] = None,
35
+ ):
18
36
  """
19
37
  Initialize authentication service.
20
-
38
+
21
39
  Args:
22
- http_client: HTTP client instance
40
+ http_client: HTTP client instance (for backward compatibility)
23
41
  redis: Redis service instance
42
+ cache: Optional cache service instance (for token validation caching)
43
+ api_client: Optional API client instance (for typed API calls)
24
44
  """
25
45
  self.config = http_client.config
26
46
  self.http_client = http_client
27
47
  self.redis = redis
28
-
48
+ self.cache = cache
49
+ self.api_client = api_client
50
+ self.validation_ttl = self.config.validation_ttl
51
+
52
+ def _get_token_cache_key(self, token: str) -> str:
53
+ """
54
+ Generate cache key for token validation using SHA-256 hash.
55
+
56
+ Uses token hash instead of full token for security.
57
+
58
+ Args:
59
+ token: JWT token string
60
+
61
+ Returns:
62
+ Cache key string in format: token_validation:{sha256_hash}
63
+ """
64
+ token_hash = hashlib.sha256(token.encode()).hexdigest()
65
+ return f"token_validation:{token_hash}"
66
+
67
+ def _get_cache_ttl_from_token(self, token: str) -> int:
68
+ """
69
+ Calculate smart TTL based on token expiration.
70
+
71
+ If token has expiration claim, cache until token_exp - 30s buffer.
72
+ Minimum: 60 seconds, Maximum: validation_ttl.
73
+
74
+ Args:
75
+ token: JWT token string
76
+
77
+ Returns:
78
+ TTL in seconds
79
+ """
80
+ try:
81
+ decoded = decode_token(token)
82
+ if decoded and "exp" in decoded:
83
+ token_exp = decoded["exp"]
84
+ if isinstance(token_exp, (int, float)):
85
+ now = time.time()
86
+ # Calculate TTL as token_exp - now - 30s buffer
87
+ ttl = int(token_exp - now - 30)
88
+ # Clamp between min (60s) and max (validation_ttl)
89
+ return max(60, min(ttl, self.validation_ttl))
90
+ except Exception:
91
+ # If token expiration cannot be determined, use default TTL
92
+ pass
93
+
94
+ return self.validation_ttl
95
+
29
96
  async def get_environment_token(self) -> str:
30
97
  """
31
98
  Get environment token using client credentials.
32
-
99
+
33
100
  This is called automatically by HttpClient, but can be called manually if needed.
34
-
101
+
35
102
  Returns:
36
103
  Client token string
37
-
104
+
38
105
  Raises:
39
106
  AuthenticationError: If token fetch fails
40
107
  """
41
108
  return await self.http_client.get_environment_token()
42
-
43
- def login(self, redirect_uri: str) -> str:
109
+
110
+ async def _check_cache_for_token(self, token: str) -> Optional[Dict[str, Any]]:
44
111
  """
45
- Initiate login flow by redirecting to controller.
46
-
47
- Returns the login URL for browser redirect or manual navigation.
48
- Backend will extract environment and application from client token.
49
-
112
+ Check cache for token validation result.
113
+
50
114
  Args:
51
- redirect_uri: URI to redirect to after successful login
52
-
115
+ token: JWT token to check
116
+
53
117
  Returns:
54
- Login URL string
118
+ Cached validation result if found, None otherwise
55
119
  """
56
- return f"{self.config.controller_url}/api/auth/login?redirect={redirect_uri}"
57
-
58
- async def validate_token(self, token: str) -> bool:
120
+ if not self.cache:
121
+ return None
122
+
123
+ cache_key = self._get_token_cache_key(token)
124
+ cached_result = await self.cache.get(cache_key)
125
+ if cached_result and isinstance(cached_result, dict):
126
+ logger.debug("Token validation cache hit")
127
+ return cast(Dict[str, Any], cached_result)
128
+
129
+ return None
130
+
131
+ async def _fetch_validation_from_api_client(
132
+ self, token: str, auth_strategy: Optional[AuthStrategy] = None
133
+ ) -> Dict[str, Any]:
59
134
  """
60
- Validate token with controller.
61
-
135
+ Fetch token validation using ApiClient.
136
+
62
137
  Args:
63
138
  token: JWT token to validate
64
-
139
+ auth_strategy: Optional authentication strategy
140
+
65
141
  Returns:
66
- True if token is valid, False otherwise
142
+ Validation result dictionary
67
143
  """
68
- try:
144
+ if not self.api_client:
145
+ raise ValueError("ApiClient is required for this method")
146
+ response = await self.api_client.auth.validate_token(token, auth_strategy=auth_strategy)
147
+ # Extract data from typed response
148
+ return {
149
+ "success": response.success,
150
+ "data": {
151
+ "authenticated": response.data.authenticated,
152
+ "user": response.data.user.model_dump() if response.data.user else None,
153
+ "expiresAt": response.data.expiresAt,
154
+ },
155
+ "timestamp": response.timestamp,
156
+ }
157
+
158
+ async def _fetch_validation_from_http_client(
159
+ self, token: str, auth_strategy: Optional[AuthStrategy] = None
160
+ ) -> Dict[str, Any]:
161
+ """
162
+ Fetch token validation using HttpClient (backward compatibility).
163
+
164
+ Args:
165
+ token: JWT token to validate
166
+ auth_strategy: Optional authentication strategy
167
+
168
+ Returns:
169
+ Validation result dictionary
170
+ """
171
+ if auth_strategy is not None:
69
172
  result = await self.http_client.authenticated_request(
70
173
  "POST",
71
- "/api/auth/validate", # Backend knows app/env from client token
72
- token
174
+ "/api/v1/auth/validate",
175
+ token,
176
+ {"token": token},
177
+ auth_strategy=auth_strategy,
73
178
  )
74
-
179
+ return result # type: ignore[no-any-return]
180
+
181
+ result = await self.http_client.authenticated_request(
182
+ "POST", "/api/v1/auth/validate", token, {"token": token}
183
+ )
184
+ return result # type: ignore[no-any-return]
185
+
186
+ async def _fetch_validation_from_api(
187
+ self, token: str, auth_strategy: Optional[AuthStrategy] = None
188
+ ) -> Dict[str, Any]:
189
+ """
190
+ Fetch token validation from API (ApiClient or HttpClient).
191
+
192
+ Args:
193
+ token: JWT token to validate
194
+ auth_strategy: Optional authentication strategy
195
+
196
+ Returns:
197
+ Validation result dictionary
198
+ """
199
+ if self.api_client:
200
+ return await self._fetch_validation_from_api_client(token, auth_strategy)
201
+ else:
202
+ return await self._fetch_validation_from_http_client(token, auth_strategy)
203
+
204
+ async def _cache_validation_result(self, token: str, result: Dict[str, Any]) -> None:
205
+ """
206
+ Cache successful validation results.
207
+
208
+ Args:
209
+ token: JWT token that was validated
210
+ result: Validation result dictionary
211
+ """
212
+ if not self.cache:
213
+ return
214
+
215
+ result_dict: Dict[str, Any] = result
216
+ if result_dict.get("data", {}).get("authenticated") is not True:
217
+ return
218
+
219
+ cache_key = self._get_token_cache_key(token)
220
+ ttl = self._get_cache_ttl_from_token(token)
221
+ try:
222
+ await self.cache.set(cache_key, result_dict, ttl)
223
+ logger.debug(f"Token validation cached with TTL: {ttl}s")
224
+ except Exception as error:
225
+ logger.warning("Failed to cache validation result", exc_info=error)
226
+
227
+ async def _validate_token_request(
228
+ self, token: str, auth_strategy: Optional[AuthStrategy] = None
229
+ ) -> Dict[str, Any]:
230
+ """
231
+ Helper method to call /api/v1/auth/validate endpoint with proper request body.
232
+
233
+ Checks cache before making HTTP request and caches successful validation results.
234
+
235
+ Args:
236
+ token: JWT token to validate
237
+ auth_strategy: Optional authentication strategy
238
+
239
+ Returns:
240
+ Validation result dictionary
241
+ """
242
+ # Check cache first
243
+ cached_result = await self._check_cache_for_token(token)
244
+ if cached_result:
245
+ return cached_result
246
+
247
+ # Cache miss - fetch from API
248
+ result = await self._fetch_validation_from_api(token, auth_strategy)
249
+
250
+ # Cache successful validation results
251
+ await self._cache_validation_result(token, result)
252
+
253
+ return result
254
+
255
+ async def login(self, redirect: str, state: Optional[str] = None) -> Dict[str, Any]:
256
+ """
257
+ Initiate login flow by calling the controller login endpoint.
258
+
259
+ This method calls GET /api/v1/auth/login with redirect and optional state parameters.
260
+ The controller returns a login URL that should be used to redirect the user to Keycloak.
261
+
262
+ Args:
263
+ redirect: Callback URL where Keycloak redirects after authentication (required)
264
+ state: Optional CSRF protection token (auto-generated by backend if omitted)
265
+
266
+ Returns:
267
+ Dictionary containing:
268
+ - success: True if successful
269
+ - data: Dictionary with loginUrl and state
270
+ - timestamp: Response timestamp
271
+
272
+ Example:
273
+ >>> response = await auth_service.login(
274
+ ... redirect="http://localhost:3000/auth/callback",
275
+ ... state="abc123"
276
+ ... )
277
+ >>> login_url = response["data"]["loginUrl"]
278
+ >>> state = response["data"]["state"]
279
+ """
280
+ try:
281
+ if self.api_client:
282
+ # Use ApiClient for typed API calls
283
+ response = await self.api_client.auth.login(redirect, state)
284
+ # Extract data from typed response
285
+ return {
286
+ "success": response.success,
287
+ "data": {
288
+ "loginUrl": response.data.loginUrl,
289
+ "state": state, # State is returned in response if provided
290
+ },
291
+ "timestamp": response.timestamp,
292
+ }
293
+ else:
294
+ # Fallback to HttpClient for backward compatibility
295
+ params = {"redirect": redirect}
296
+ if state:
297
+ params["state"] = state
298
+
299
+ response = await self.http_client.get("/api/v1/auth/login", params=params)
300
+ return response # type: ignore[no-any-return]
301
+ except Exception as error:
302
+ correlation_id = extract_correlation_id_from_error(error)
303
+ logger.error(
304
+ "Login failed",
305
+ exc_info=error,
306
+ extra={"correlationId": correlation_id} if correlation_id else None,
307
+ )
308
+ # Return empty dict on error per service method pattern
309
+ return {}
310
+
311
+ async def validate_token(
312
+ self, token: str, auth_strategy: Optional[AuthStrategy] = None
313
+ ) -> bool:
314
+ """
315
+ Validate token with controller.
316
+
317
+ If API_KEY is configured and token matches it, bypasses OAuth2 validation.
318
+
319
+ Args:
320
+ token: JWT token to validate (or API_KEY for testing)
321
+ auth_strategy: Optional authentication strategy
322
+
323
+ Returns:
324
+ True if token is valid, False otherwise
325
+ """
326
+ # Check API_KEY first (for testing)
327
+ if self.config.api_key and token == self.config.api_key:
328
+ return True
329
+
330
+ # Fall back to OAuth2 validation
331
+ try:
332
+ result = await self._validate_token_request(token, auth_strategy)
75
333
  auth_result = AuthResult(**result)
76
334
  return auth_result.authenticated
77
-
78
- except Exception:
79
- # Token validation failed, return false
335
+
336
+ except Exception as error:
337
+ correlation_id = extract_correlation_id_from_error(error)
338
+ logger.error(
339
+ "Token validation failed",
340
+ exc_info=error,
341
+ extra={"correlationId": correlation_id} if correlation_id else None,
342
+ )
80
343
  return False
81
-
82
- async def get_user(self, token: str) -> Optional[UserInfo]:
344
+
345
+ async def get_user(
346
+ self, token: str, auth_strategy: Optional[AuthStrategy] = None
347
+ ) -> Optional[UserInfo]:
83
348
  """
84
349
  Get user information from token.
85
-
350
+
351
+ If API_KEY is configured and token matches it, returns None (no user info for API key auth).
352
+
86
353
  Args:
87
- token: JWT token
88
-
354
+ token: JWT token (or API_KEY for testing)
355
+ auth_strategy: Optional authentication strategy
356
+
89
357
  Returns:
90
358
  UserInfo if token is valid, None otherwise
91
359
  """
360
+ # Check API_KEY first (for testing)
361
+ if self.config.api_key and token == self.config.api_key:
362
+ # API key authentication doesn't provide user info
363
+ return None
364
+
365
+ # Fall back to OAuth2 validation
92
366
  try:
93
- result = await self.http_client.authenticated_request(
94
- "POST",
95
- "/api/auth/validate",
96
- token
97
- )
98
-
99
- auth_result = AuthResult(**result)
100
-
101
- if auth_result.authenticated and auth_result.user:
102
- return auth_result.user
103
-
367
+ result = await self._validate_token_request(token, auth_strategy)
368
+ # _validate_token_request returns dict with "data" key
369
+ authenticated = result.get("data", {}).get("authenticated", False)
370
+ user_data = result.get("data", {}).get("user")
371
+
372
+ if authenticated and user_data:
373
+ return UserInfo(**user_data)
374
+
104
375
  return None
105
-
106
- except Exception:
107
- # Failed to get user info, return null
376
+
377
+ except Exception as error:
378
+ correlation_id = extract_correlation_id_from_error(error)
379
+ logger.error(
380
+ "Failed to get user info",
381
+ exc_info=error,
382
+ extra={"correlationId": correlation_id} if correlation_id else None,
383
+ )
108
384
  return None
109
-
110
- async def get_user_info(self, token: str) -> Optional[UserInfo]:
385
+
386
+ async def get_user_info(
387
+ self, token: str, auth_strategy: Optional[AuthStrategy] = None
388
+ ) -> Optional[UserInfo]:
111
389
  """
112
- Get user information from GET /api/auth/user endpoint.
113
-
390
+ Get user information from GET /api/v1/auth/user endpoint.
391
+
392
+ If API_KEY is configured and token matches it, returns None (no user info for API key auth).
393
+
114
394
  Args:
115
- token: JWT token
116
-
395
+ token: JWT token (or API_KEY for testing)
396
+ auth_strategy: Optional authentication strategy
397
+
117
398
  Returns:
118
399
  UserInfo if token is valid, None otherwise
119
400
  """
401
+ # Check API_KEY first (for testing)
402
+ if self.config.api_key and token == self.config.api_key:
403
+ # API key authentication doesn't provide user info
404
+ return None
405
+
406
+ # Fall back to OAuth2 validation
120
407
  try:
121
- user_data = await self.http_client.authenticated_request(
122
- "GET",
123
- "/api/auth/user",
124
- token
408
+ if self.api_client:
409
+ # Use ApiClient for typed API calls
410
+ response = await self.api_client.auth.get_user(token, auth_strategy=auth_strategy)
411
+ # Extract user from typed response
412
+ return response.data.user
413
+ else:
414
+ # Fallback to HttpClient for backward compatibility
415
+ if auth_strategy is not None:
416
+ user_data = await self.http_client.authenticated_request(
417
+ "GET", "/api/v1/auth/user", token, auth_strategy=auth_strategy
418
+ )
419
+ else:
420
+ user_data = await self.http_client.authenticated_request(
421
+ "GET", "/api/v1/auth/user", token
422
+ )
423
+
424
+ return UserInfo(**user_data)
425
+
426
+ except Exception as error:
427
+ correlation_id = extract_correlation_id_from_error(error)
428
+ logger.error(
429
+ "Failed to get user info",
430
+ exc_info=error,
431
+ extra={"correlationId": correlation_id} if correlation_id else None,
125
432
  )
126
-
127
- return UserInfo(**user_data)
128
-
129
- except Exception:
130
- # Failed to get user info, return None
131
433
  return None
132
-
133
- async def logout(self) -> None:
434
+
435
+ async def logout(self, token: str) -> Dict[str, Any]:
134
436
  """
135
- Logout user.
136
-
137
- Backend extracts app/env from client token (no body needed).
138
-
139
- Raises:
140
- MisoClientError: If logout fails
437
+ Logout user by invalidating the access token.
438
+
439
+ This method calls POST /api/v1/auth/logout with the user's access token in the request body.
440
+ The token will be invalidated on the server side, and JWT token cache will be cleared automatically.
441
+
442
+ Args:
443
+ token: Access token to invalidate (required)
444
+
445
+ Returns:
446
+ Dictionary containing:
447
+ - success: True if successful
448
+ - message: Success message
449
+ - timestamp: Response timestamp
450
+
451
+ Example:
452
+ >>> response = await auth_service.logout(token="jwt-token-here")
453
+ >>> if response.get("success"):
454
+ ... print("Logout successful")
141
455
  """
142
456
  try:
143
- # Backend extracts app/env from client token
144
- await self.http_client.request("POST", "/api/auth/logout")
145
- except Exception as e:
146
- # Logout failed, re-raise error for application to handle
147
- from ..errors import MisoClientError
148
- raise MisoClientError(f"Logout failed: {str(e)}")
149
-
150
- async def is_authenticated(self, token: str) -> bool:
457
+ if self.api_client:
458
+ # Use ApiClient for typed API calls
459
+ response = await self.api_client.auth.logout(token)
460
+ # Extract data from typed response
461
+ result = {
462
+ "success": response.success,
463
+ "message": response.message,
464
+ "timestamp": response.timestamp,
465
+ }
466
+ else:
467
+ # Fallback to HttpClient for backward compatibility
468
+ result = await self.http_client.authenticated_request(
469
+ "POST", "/api/v1/auth/logout", token, {"token": token}
470
+ )
471
+ result = result # type: ignore[assignment]
472
+
473
+ # Clear JWT token cache after successful logout
474
+ try:
475
+ self.http_client.clear_user_token(token)
476
+ except Exception:
477
+ # Silently continue if cache clearing fails
478
+ pass
479
+
480
+ # Clear validation cache entry after successful logout
481
+ if self.cache:
482
+ try:
483
+ cache_key = self._get_token_cache_key(token)
484
+ await self.cache.delete(cache_key)
485
+ logger.debug("Token validation cache cleared on logout")
486
+ except Exception as error:
487
+ logger.warning("Failed to clear validation cache on logout", exc_info=error)
488
+
489
+ return result # type: ignore[no-any-return]
490
+ except Exception as error:
491
+ correlation_id = extract_correlation_id_from_error(error)
492
+ logger.error(
493
+ "Logout failed",
494
+ exc_info=error,
495
+ extra={"correlationId": correlation_id} if correlation_id else None,
496
+ )
497
+ # Return empty dict on error per service method pattern
498
+ return {}
499
+
500
+ async def refresh_user_token(self, refresh_token: str) -> Optional[Dict[str, Any]]:
501
+ """
502
+ Refresh user access token using refresh token.
503
+
504
+ The refresh endpoint uses the refresh token in the request body for authentication.
505
+ Client token is automatically sent via x-client-token header.
506
+
507
+ Args:
508
+ refresh_token: Refresh token string
509
+
510
+ Returns:
511
+ Dictionary containing:
512
+ - token: New access token
513
+ - refreshToken: New refresh token (if provided)
514
+ - expiresIn: Token expiration in seconds
515
+ None if refresh fails
516
+ """
517
+ try:
518
+ if self.api_client:
519
+ # Use ApiClient for typed API calls
520
+ response = await self.api_client.auth.refresh_token(refresh_token)
521
+ # Extract data from typed response
522
+ # Map accessToken to token for backward compatibility
523
+ return {
524
+ "success": response.success,
525
+ "data": {
526
+ "token": response.data.accessToken, # Map accessToken to token
527
+ "accessToken": response.data.accessToken,
528
+ "refreshToken": response.data.refreshToken,
529
+ "expiresIn": response.data.expiresIn,
530
+ },
531
+ "message": response.message,
532
+ "timestamp": response.timestamp,
533
+ }
534
+ else:
535
+ # Fallback to HttpClient for backward compatibility
536
+ # Uses request() (not authenticated_request()) since refresh token is the auth
537
+ response = await self.http_client.request(
538
+ "POST",
539
+ "/api/v1/auth/refresh",
540
+ {"refreshToken": refresh_token},
541
+ )
542
+
543
+ return response # type: ignore[no-any-return]
544
+ except Exception as error:
545
+ correlation_id = extract_correlation_id_from_error(error)
546
+ logger.error(
547
+ "Failed to refresh user token",
548
+ exc_info=error,
549
+ extra={"correlationId": correlation_id} if correlation_id else None,
550
+ )
551
+ return None
552
+
553
+ async def is_authenticated(
554
+ self, token: str, auth_strategy: Optional[AuthStrategy] = None
555
+ ) -> bool:
151
556
  """
152
557
  Check if user is authenticated (has valid token).
153
-
558
+
154
559
  Args:
155
560
  token: JWT token
156
-
561
+ auth_strategy: Optional authentication strategy
562
+
157
563
  Returns:
158
564
  True if user is authenticated, False otherwise
159
565
  """
160
- return await self.validate_token(token)
566
+ if auth_strategy is not None:
567
+ return await self.validate_token(token, auth_strategy=auth_strategy)
568
+ else:
569
+ return await self.validate_token(token)