miso-client 0.1.0__py3-none-any.whl → 0.4.0__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.
Potentially problematic release.
This version of miso-client might be problematic. Click here for more details.
- miso_client/__init__.py +104 -84
- miso_client/errors.py +30 -4
- miso_client/models/__init__.py +4 -0
- miso_client/models/config.py +56 -35
- miso_client/models/error_response.py +41 -0
- miso_client/services/__init__.py +5 -5
- miso_client/services/auth.py +65 -48
- miso_client/services/cache.py +42 -41
- miso_client/services/encryption.py +18 -17
- miso_client/services/logger.py +115 -100
- miso_client/services/permission.py +27 -36
- miso_client/services/redis.py +17 -15
- miso_client/services/role.py +25 -36
- miso_client/utils/__init__.py +3 -3
- miso_client/utils/config_loader.py +24 -16
- miso_client/utils/data_masker.py +104 -33
- miso_client/utils/http_client.py +462 -254
- miso_client/utils/internal_http_client.py +471 -0
- miso_client/utils/jwt_tools.py +14 -17
- miso_client/utils/sensitive_fields_loader.py +116 -0
- {miso_client-0.1.0.dist-info → miso_client-0.4.0.dist-info}/METADATA +165 -3
- miso_client-0.4.0.dist-info/RECORD +26 -0
- miso_client-0.1.0.dist-info/RECORD +0 -23
- {miso_client-0.1.0.dist-info → miso_client-0.4.0.dist-info}/WHEEL +0 -0
- {miso_client-0.1.0.dist-info → miso_client-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {miso_client-0.1.0.dist-info → miso_client-0.4.0.dist-info}/top_level.txt +0 -0
miso_client/models/config.py
CHANGED
|
@@ -5,13 +5,14 @@ This module contains Pydantic models that define the configuration structure
|
|
|
5
5
|
and data types used throughout the MisoClient SDK.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
9
|
+
|
|
9
10
|
from pydantic import BaseModel, Field
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class RedisConfig(BaseModel):
|
|
13
14
|
"""Redis connection configuration."""
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
host: str = Field(..., description="Redis host")
|
|
16
17
|
port: int = Field(default=6379, description="Redis port")
|
|
17
18
|
password: Optional[str] = Field(default=None, description="Redis password")
|
|
@@ -21,38 +22,42 @@ class RedisConfig(BaseModel):
|
|
|
21
22
|
|
|
22
23
|
class MisoClientConfig(BaseModel):
|
|
23
24
|
"""Main MisoClient configuration.
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
Required fields:
|
|
26
27
|
- controller_url: Miso Controller base URL
|
|
27
28
|
- client_id: Client identifier for authentication
|
|
28
29
|
- client_secret: Client secret for authentication
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
Optional fields:
|
|
31
32
|
- redis: Redis configuration for caching
|
|
32
33
|
- log_level: Logging level (debug, info, warn, error)
|
|
33
34
|
- cache: Cache TTL settings for roles and permissions
|
|
35
|
+
- api_key: API key for testing (bypasses OAuth2 authentication)
|
|
34
36
|
"""
|
|
35
|
-
|
|
37
|
+
|
|
36
38
|
controller_url: str = Field(..., description="Miso Controller base URL")
|
|
37
39
|
client_id: str = Field(..., description="Client identifier for authentication")
|
|
38
40
|
client_secret: str = Field(..., description="Client secret for authentication")
|
|
39
41
|
redis: Optional[RedisConfig] = Field(default=None, description="Optional Redis configuration")
|
|
40
42
|
log_level: Literal["debug", "info", "warn", "error"] = Field(
|
|
41
|
-
default="info",
|
|
42
|
-
description="Log level"
|
|
43
|
+
default="info", description="Log level"
|
|
43
44
|
)
|
|
44
45
|
cache: Optional[Dict[str, int]] = Field(
|
|
45
46
|
default=None,
|
|
46
|
-
description="Cache TTL settings: permission_ttl, role_ttl (default: 900 seconds)"
|
|
47
|
+
description="Cache TTL settings: permission_ttl, role_ttl (default: 900 seconds)",
|
|
47
48
|
)
|
|
48
|
-
|
|
49
|
+
api_key: Optional[str] = Field(
|
|
50
|
+
default=None,
|
|
51
|
+
description="API key for testing - when set, bearer tokens matching this key bypass OAuth2 validation",
|
|
52
|
+
)
|
|
53
|
+
|
|
49
54
|
@property
|
|
50
55
|
def role_ttl(self) -> int:
|
|
51
56
|
"""Get role cache TTL in seconds."""
|
|
52
57
|
if self.cache and "role_ttl" in self.cache:
|
|
53
58
|
return self.cache["role_ttl"]
|
|
54
59
|
return self.cache.get("roleTTL", 900) if self.cache else 900 # 15 minutes default
|
|
55
|
-
|
|
60
|
+
|
|
56
61
|
@property
|
|
57
62
|
def permission_ttl(self) -> int:
|
|
58
63
|
"""Get permission cache TTL in seconds."""
|
|
@@ -63,21 +68,21 @@ class MisoClientConfig(BaseModel):
|
|
|
63
68
|
|
|
64
69
|
class UserInfo(BaseModel):
|
|
65
70
|
"""User information from token validation."""
|
|
66
|
-
|
|
71
|
+
|
|
67
72
|
id: str = Field(..., description="User ID")
|
|
68
73
|
username: str = Field(..., description="Username")
|
|
69
74
|
email: Optional[str] = Field(default=None, description="User email")
|
|
70
75
|
firstName: Optional[str] = Field(default=None, alias="first_name", description="First name")
|
|
71
76
|
lastName: Optional[str] = Field(default=None, alias="last_name", description="Last name")
|
|
72
77
|
roles: Optional[List[str]] = Field(default=None, description="User roles")
|
|
73
|
-
|
|
78
|
+
|
|
74
79
|
class Config:
|
|
75
80
|
populate_by_name = True # Allow both snake_case and camelCase
|
|
76
81
|
|
|
77
82
|
|
|
78
83
|
class AuthResult(BaseModel):
|
|
79
84
|
"""Authentication result."""
|
|
80
|
-
|
|
85
|
+
|
|
81
86
|
authenticated: bool = Field(..., description="Whether authentication was successful")
|
|
82
87
|
user: Optional[UserInfo] = Field(default=None, description="User information if authenticated")
|
|
83
88
|
error: Optional[str] = Field(default=None, description="Error message if authentication failed")
|
|
@@ -85,90 +90,106 @@ class AuthResult(BaseModel):
|
|
|
85
90
|
|
|
86
91
|
class LogEntry(BaseModel):
|
|
87
92
|
"""Log entry structure."""
|
|
88
|
-
|
|
93
|
+
|
|
89
94
|
timestamp: str = Field(..., description="ISO timestamp")
|
|
90
95
|
level: Literal["error", "audit", "info", "debug"] = Field(..., description="Log level")
|
|
91
96
|
environment: str = Field(..., description="Environment name (extracted by backend)")
|
|
92
97
|
application: str = Field(..., description="Application identifier (clientId)")
|
|
93
|
-
applicationId: Optional[str] = Field(
|
|
98
|
+
applicationId: Optional[str] = Field(
|
|
99
|
+
default=None, alias="application_id", description="Application ID"
|
|
100
|
+
)
|
|
94
101
|
userId: Optional[str] = Field(default=None, alias="user_id", description="User ID if available")
|
|
95
102
|
message: str = Field(..., description="Log message")
|
|
96
103
|
context: Optional[Dict[str, Any]] = Field(default=None, description="Additional context")
|
|
97
|
-
correlationId: Optional[str] = Field(
|
|
104
|
+
correlationId: Optional[str] = Field(
|
|
105
|
+
default=None, alias="correlation_id", description="Correlation ID for tracing"
|
|
106
|
+
)
|
|
98
107
|
requestId: Optional[str] = Field(default=None, alias="request_id", description="Request ID")
|
|
99
108
|
sessionId: Optional[str] = Field(default=None, alias="session_id", description="Session ID")
|
|
100
|
-
stackTrace: Optional[str] = Field(
|
|
109
|
+
stackTrace: Optional[str] = Field(
|
|
110
|
+
default=None, alias="stack_trace", description="Stack trace for errors"
|
|
111
|
+
)
|
|
101
112
|
ipAddress: Optional[str] = Field(default=None, alias="ip_address", description="IP address")
|
|
102
113
|
userAgent: Optional[str] = Field(default=None, alias="user_agent", description="User agent")
|
|
103
114
|
hostname: Optional[str] = Field(default=None, description="Hostname")
|
|
104
|
-
|
|
115
|
+
|
|
105
116
|
class Config:
|
|
106
117
|
populate_by_name = True
|
|
107
118
|
|
|
108
119
|
|
|
109
120
|
class RoleResult(BaseModel):
|
|
110
121
|
"""Role query result."""
|
|
111
|
-
|
|
122
|
+
|
|
112
123
|
userId: str = Field(..., alias="user_id", description="User ID")
|
|
113
124
|
roles: List[str] = Field(..., description="List of user roles")
|
|
114
125
|
environment: str = Field(..., description="Environment name")
|
|
115
126
|
application: str = Field(..., description="Application name")
|
|
116
|
-
|
|
127
|
+
|
|
117
128
|
class Config:
|
|
118
129
|
populate_by_name = True
|
|
119
130
|
|
|
120
131
|
|
|
121
132
|
class PermissionResult(BaseModel):
|
|
122
133
|
"""Permission query result."""
|
|
123
|
-
|
|
134
|
+
|
|
124
135
|
userId: str = Field(..., alias="user_id", description="User ID")
|
|
125
136
|
permissions: List[str] = Field(..., description="List of user permissions")
|
|
126
137
|
environment: str = Field(..., description="Environment name")
|
|
127
138
|
application: str = Field(..., description="Application name")
|
|
128
|
-
|
|
139
|
+
|
|
129
140
|
class Config:
|
|
130
141
|
populate_by_name = True
|
|
131
142
|
|
|
132
143
|
|
|
133
144
|
class ClientTokenResponse(BaseModel):
|
|
134
145
|
"""Client token response."""
|
|
135
|
-
|
|
146
|
+
|
|
136
147
|
success: bool = Field(..., description="Whether token request was successful")
|
|
137
148
|
token: str = Field(..., description="Client token")
|
|
138
149
|
expiresIn: int = Field(..., alias="expires_in", description="Token expiration in seconds")
|
|
139
150
|
expiresAt: str = Field(..., alias="expires_at", description="Token expiration ISO timestamp")
|
|
140
|
-
|
|
151
|
+
|
|
141
152
|
class Config:
|
|
142
153
|
populate_by_name = True
|
|
143
154
|
|
|
144
155
|
|
|
145
156
|
class PerformanceMetrics(BaseModel):
|
|
146
157
|
"""Performance metrics for logging."""
|
|
147
|
-
|
|
158
|
+
|
|
148
159
|
startTime: int = Field(..., alias="start_time", description="Start time in milliseconds")
|
|
149
|
-
endTime: Optional[int] = Field(
|
|
160
|
+
endTime: Optional[int] = Field(
|
|
161
|
+
default=None, alias="end_time", description="End time in milliseconds"
|
|
162
|
+
)
|
|
150
163
|
duration: Optional[int] = Field(default=None, description="Duration in milliseconds")
|
|
151
164
|
memoryUsage: Optional[Dict[str, int]] = Field(
|
|
152
|
-
default=None,
|
|
165
|
+
default=None,
|
|
153
166
|
alias="memory_usage",
|
|
154
|
-
description="Memory usage metrics (rss, heapTotal, heapUsed, external, arrayBuffers)"
|
|
167
|
+
description="Memory usage metrics (rss, heapTotal, heapUsed, external, arrayBuffers)",
|
|
155
168
|
)
|
|
156
|
-
|
|
169
|
+
|
|
157
170
|
class Config:
|
|
158
171
|
populate_by_name = True
|
|
159
172
|
|
|
160
173
|
|
|
161
174
|
class ClientLoggingOptions(BaseModel):
|
|
162
175
|
"""Options for client logging."""
|
|
163
|
-
|
|
164
|
-
applicationId: Optional[str] = Field(
|
|
176
|
+
|
|
177
|
+
applicationId: Optional[str] = Field(
|
|
178
|
+
default=None, alias="application_id", description="Application ID"
|
|
179
|
+
)
|
|
165
180
|
userId: Optional[str] = Field(default=None, alias="user_id", description="User ID")
|
|
166
|
-
correlationId: Optional[str] = Field(
|
|
181
|
+
correlationId: Optional[str] = Field(
|
|
182
|
+
default=None, alias="correlation_id", description="Correlation ID"
|
|
183
|
+
)
|
|
167
184
|
requestId: Optional[str] = Field(default=None, alias="request_id", description="Request ID")
|
|
168
185
|
sessionId: Optional[str] = Field(default=None, alias="session_id", description="Session ID")
|
|
169
186
|
token: Optional[str] = Field(default=None, description="JWT token for context extraction")
|
|
170
|
-
maskSensitiveData: Optional[bool] = Field(
|
|
171
|
-
|
|
172
|
-
|
|
187
|
+
maskSensitiveData: Optional[bool] = Field(
|
|
188
|
+
default=None, alias="mask_sensitive_data", description="Enable data masking"
|
|
189
|
+
)
|
|
190
|
+
performanceMetrics: Optional[bool] = Field(
|
|
191
|
+
default=None, alias="performance_metrics", description="Include performance metrics"
|
|
192
|
+
)
|
|
193
|
+
|
|
173
194
|
class Config:
|
|
174
195
|
populate_by_name = True
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Structured error response model following RFC 7807-style format.
|
|
3
|
+
|
|
4
|
+
This module provides a generic error response interface that can be used
|
|
5
|
+
across different applications for consistent error handling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ErrorResponse(BaseModel):
|
|
14
|
+
"""
|
|
15
|
+
Structured error response following RFC 7807-style format.
|
|
16
|
+
|
|
17
|
+
This model represents a standardized error response structure that includes:
|
|
18
|
+
- Multiple error messages
|
|
19
|
+
- Error type identifier
|
|
20
|
+
- Human-readable title
|
|
21
|
+
- HTTP status code
|
|
22
|
+
- Request instance URI (optional)
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
{
|
|
26
|
+
"errors": ["Error message 1", "Error message 2"],
|
|
27
|
+
"type": "/Errors/Bad Input",
|
|
28
|
+
"title": "Bad Request",
|
|
29
|
+
"statusCode": 400,
|
|
30
|
+
"instance": "/OpenApi/rest/Xzy"
|
|
31
|
+
}
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
errors: List[str] = Field(..., description="List of error messages")
|
|
35
|
+
type: str = Field(..., description="Error type URI (e.g., '/Errors/Bad Input')")
|
|
36
|
+
title: str = Field(..., description="Human-readable error title")
|
|
37
|
+
statusCode: int = Field(..., alias="status_code", description="HTTP status code")
|
|
38
|
+
instance: Optional[str] = Field(default=None, description="Request instance URI")
|
|
39
|
+
|
|
40
|
+
class Config:
|
|
41
|
+
populate_by_name = True # Allow both camelCase and snake_case
|
miso_client/services/__init__.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Service implementations for MisoClient SDK."""
|
|
2
2
|
|
|
3
3
|
from .auth import AuthService
|
|
4
|
-
from .
|
|
4
|
+
from .cache import CacheService
|
|
5
|
+
from .encryption import EncryptionService
|
|
6
|
+
from .logger import LoggerChain, LoggerService
|
|
5
7
|
from .permission import PermissionService
|
|
6
|
-
from .logger import LoggerService, LoggerChain
|
|
7
8
|
from .redis import RedisService
|
|
8
|
-
from .
|
|
9
|
-
from .cache import CacheService
|
|
9
|
+
from .role import RoleService
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
12
|
"AuthService",
|
|
@@ -17,4 +17,4 @@ __all__ = [
|
|
|
17
17
|
"RedisService",
|
|
18
18
|
"EncryptionService",
|
|
19
19
|
"CacheService",
|
|
20
|
-
]
|
|
20
|
+
]
|
miso_client/services/auth.py
CHANGED
|
@@ -6,18 +6,19 @@ token validation, user information retrieval, and logout functionality.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from typing import Optional
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
from ..models.config import AuthResult, UserInfo
|
|
10
11
|
from ..services.redis import RedisService
|
|
11
12
|
from ..utils.http_client import HttpClient
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class AuthService:
|
|
15
16
|
"""Authentication service for token validation and user management."""
|
|
16
|
-
|
|
17
|
+
|
|
17
18
|
def __init__(self, http_client: HttpClient, redis: RedisService):
|
|
18
19
|
"""
|
|
19
20
|
Initialize authentication service.
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
Args:
|
|
22
23
|
http_client: HTTP client instance
|
|
23
24
|
redis: Redis service instance
|
|
@@ -25,117 +26,132 @@ class AuthService:
|
|
|
25
26
|
self.config = http_client.config
|
|
26
27
|
self.http_client = http_client
|
|
27
28
|
self.redis = redis
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
async def get_environment_token(self) -> str:
|
|
30
31
|
"""
|
|
31
32
|
Get environment token using client credentials.
|
|
32
|
-
|
|
33
|
+
|
|
33
34
|
This is called automatically by HttpClient, but can be called manually if needed.
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
Returns:
|
|
36
37
|
Client token string
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
Raises:
|
|
39
40
|
AuthenticationError: If token fetch fails
|
|
40
41
|
"""
|
|
41
42
|
return await self.http_client.get_environment_token()
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
def login(self, redirect_uri: str) -> str:
|
|
44
45
|
"""
|
|
45
46
|
Initiate login flow by redirecting to controller.
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
Returns the login URL for browser redirect or manual navigation.
|
|
48
49
|
Backend will extract environment and application from client token.
|
|
49
|
-
|
|
50
|
+
|
|
50
51
|
Args:
|
|
51
52
|
redirect_uri: URI to redirect to after successful login
|
|
52
|
-
|
|
53
|
+
|
|
53
54
|
Returns:
|
|
54
55
|
Login URL string
|
|
55
56
|
"""
|
|
56
57
|
return f"{self.config.controller_url}/api/auth/login?redirect={redirect_uri}"
|
|
57
|
-
|
|
58
|
+
|
|
58
59
|
async def validate_token(self, token: str) -> bool:
|
|
59
60
|
"""
|
|
60
61
|
Validate token with controller.
|
|
61
|
-
|
|
62
|
+
|
|
63
|
+
If API_KEY is configured and token matches it, bypasses OAuth2 validation.
|
|
64
|
+
|
|
62
65
|
Args:
|
|
63
|
-
token: JWT token to validate
|
|
64
|
-
|
|
66
|
+
token: JWT token to validate (or API_KEY for testing)
|
|
67
|
+
|
|
65
68
|
Returns:
|
|
66
69
|
True if token is valid, False otherwise
|
|
67
70
|
"""
|
|
71
|
+
# Check API_KEY first (for testing)
|
|
72
|
+
if self.config.api_key and token == self.config.api_key:
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
# Fall back to OAuth2 validation
|
|
68
76
|
try:
|
|
69
77
|
result = await self.http_client.authenticated_request(
|
|
70
|
-
"POST",
|
|
71
|
-
"/api/auth/validate", # Backend knows app/env from client token
|
|
72
|
-
token
|
|
78
|
+
"POST", "/api/auth/validate", token # Backend knows app/env from client token
|
|
73
79
|
)
|
|
74
|
-
|
|
80
|
+
|
|
75
81
|
auth_result = AuthResult(**result)
|
|
76
82
|
return auth_result.authenticated
|
|
77
|
-
|
|
83
|
+
|
|
78
84
|
except Exception:
|
|
79
85
|
# Token validation failed, return false
|
|
80
86
|
return False
|
|
81
|
-
|
|
87
|
+
|
|
82
88
|
async def get_user(self, token: str) -> Optional[UserInfo]:
|
|
83
89
|
"""
|
|
84
90
|
Get user information from token.
|
|
85
|
-
|
|
91
|
+
|
|
92
|
+
If API_KEY is configured and token matches it, returns None (no user info for API key auth).
|
|
93
|
+
|
|
86
94
|
Args:
|
|
87
|
-
token: JWT token
|
|
88
|
-
|
|
95
|
+
token: JWT token (or API_KEY for testing)
|
|
96
|
+
|
|
89
97
|
Returns:
|
|
90
98
|
UserInfo if token is valid, None otherwise
|
|
91
99
|
"""
|
|
100
|
+
# Check API_KEY first (for testing)
|
|
101
|
+
if self.config.api_key and token == self.config.api_key:
|
|
102
|
+
# API key authentication doesn't provide user info
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
# Fall back to OAuth2 validation
|
|
92
106
|
try:
|
|
93
107
|
result = await self.http_client.authenticated_request(
|
|
94
|
-
"POST",
|
|
95
|
-
"/api/auth/validate",
|
|
96
|
-
token
|
|
108
|
+
"POST", "/api/auth/validate", token
|
|
97
109
|
)
|
|
98
|
-
|
|
110
|
+
|
|
99
111
|
auth_result = AuthResult(**result)
|
|
100
|
-
|
|
112
|
+
|
|
101
113
|
if auth_result.authenticated and auth_result.user:
|
|
102
114
|
return auth_result.user
|
|
103
|
-
|
|
115
|
+
|
|
104
116
|
return None
|
|
105
|
-
|
|
117
|
+
|
|
106
118
|
except Exception:
|
|
107
119
|
# Failed to get user info, return null
|
|
108
120
|
return None
|
|
109
|
-
|
|
121
|
+
|
|
110
122
|
async def get_user_info(self, token: str) -> Optional[UserInfo]:
|
|
111
123
|
"""
|
|
112
124
|
Get user information from GET /api/auth/user endpoint.
|
|
113
|
-
|
|
125
|
+
|
|
126
|
+
If API_KEY is configured and token matches it, returns None (no user info for API key auth).
|
|
127
|
+
|
|
114
128
|
Args:
|
|
115
|
-
token: JWT token
|
|
116
|
-
|
|
129
|
+
token: JWT token (or API_KEY for testing)
|
|
130
|
+
|
|
117
131
|
Returns:
|
|
118
132
|
UserInfo if token is valid, None otherwise
|
|
119
133
|
"""
|
|
134
|
+
# Check API_KEY first (for testing)
|
|
135
|
+
if self.config.api_key and token == self.config.api_key:
|
|
136
|
+
# API key authentication doesn't provide user info
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
# Fall back to OAuth2 validation
|
|
120
140
|
try:
|
|
121
|
-
user_data = await self.http_client.authenticated_request(
|
|
122
|
-
|
|
123
|
-
"/api/auth/user",
|
|
124
|
-
token
|
|
125
|
-
)
|
|
126
|
-
|
|
141
|
+
user_data = await self.http_client.authenticated_request("GET", "/api/auth/user", token)
|
|
142
|
+
|
|
127
143
|
return UserInfo(**user_data)
|
|
128
|
-
|
|
144
|
+
|
|
129
145
|
except Exception:
|
|
130
146
|
# Failed to get user info, return None
|
|
131
147
|
return None
|
|
132
|
-
|
|
148
|
+
|
|
133
149
|
async def logout(self) -> None:
|
|
134
150
|
"""
|
|
135
151
|
Logout user.
|
|
136
|
-
|
|
152
|
+
|
|
137
153
|
Backend extracts app/env from client token (no body needed).
|
|
138
|
-
|
|
154
|
+
|
|
139
155
|
Raises:
|
|
140
156
|
MisoClientError: If logout fails
|
|
141
157
|
"""
|
|
@@ -145,15 +161,16 @@ class AuthService:
|
|
|
145
161
|
except Exception as e:
|
|
146
162
|
# Logout failed, re-raise error for application to handle
|
|
147
163
|
from ..errors import MisoClientError
|
|
164
|
+
|
|
148
165
|
raise MisoClientError(f"Logout failed: {str(e)}")
|
|
149
|
-
|
|
166
|
+
|
|
150
167
|
async def is_authenticated(self, token: str) -> bool:
|
|
151
168
|
"""
|
|
152
169
|
Check if user is authenticated (has valid token).
|
|
153
|
-
|
|
170
|
+
|
|
154
171
|
Args:
|
|
155
172
|
token: JWT token
|
|
156
|
-
|
|
173
|
+
|
|
157
174
|
Returns:
|
|
158
175
|
True if user is authenticated, False otherwise
|
|
159
176
|
"""
|