keyrunes-python-sdk 0.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.
- keyrunes_python_sdk-0.0.1.dist-info/LICENSE +661 -0
- keyrunes_python_sdk-0.0.1.dist-info/METADATA +880 -0
- keyrunes_python_sdk-0.0.1.dist-info/RECORD +10 -0
- keyrunes_python_sdk-0.0.1.dist-info/WHEEL +4 -0
- keyrunes_sdk/__init__.py +52 -0
- keyrunes_sdk/client.py +565 -0
- keyrunes_sdk/config.py +203 -0
- keyrunes_sdk/decorators.py +248 -0
- keyrunes_sdk/exceptions.py +43 -0
- keyrunes_sdk/models.py +100 -0
keyrunes_sdk/config.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Global configuration and client management for Keyrunes SDK."""
|
|
2
|
+
|
|
3
|
+
from threading import Lock
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from keyrunes_sdk.client import KeyrunesClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _GlobalConfig:
|
|
10
|
+
"""
|
|
11
|
+
Thread-safe global configuration for Keyrunes SDK.
|
|
12
|
+
|
|
13
|
+
This class manages a global KeyrunesClient instance that can be configured
|
|
14
|
+
once and used throughout the application without passing it explicitly
|
|
15
|
+
to decorators and functions.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> from keyrunes_sdk import configure, require_group
|
|
19
|
+
>>>
|
|
20
|
+
>>> # Configure once at application startup
|
|
21
|
+
>>> configure(base_url="https://keyrunes.example.com")
|
|
22
|
+
>>>
|
|
23
|
+
>>> # Use decorators without passing client
|
|
24
|
+
>>> @require_group("admins")
|
|
25
|
+
... def delete_user(user_id: str):
|
|
26
|
+
... pass
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
"""Initialize global configuration."""
|
|
31
|
+
self._client: Optional[KeyrunesClient] = None
|
|
32
|
+
self._lock = Lock()
|
|
33
|
+
|
|
34
|
+
def set_client(self, client: KeyrunesClient) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Set the global client instance.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
client: KeyrunesClient instance to use globally
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> from keyrunes_sdk import get_config
|
|
43
|
+
>>> from keyrunes_sdk.client import KeyrunesClient
|
|
44
|
+
>>>
|
|
45
|
+
>>> client = KeyrunesClient("https://keyrunes.example.com")
|
|
46
|
+
>>> get_config().set_client(client)
|
|
47
|
+
"""
|
|
48
|
+
with self._lock:
|
|
49
|
+
self._client = client
|
|
50
|
+
|
|
51
|
+
def get_client(self) -> Optional[KeyrunesClient]:
|
|
52
|
+
"""
|
|
53
|
+
Get the global client instance.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Global KeyrunesClient instance or None if not configured
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
>>> from keyrunes_sdk import get_config
|
|
60
|
+
>>>
|
|
61
|
+
>>> client = get_config().get_client()
|
|
62
|
+
>>> if client:
|
|
63
|
+
... user = client.get_current_user()
|
|
64
|
+
"""
|
|
65
|
+
with self._lock:
|
|
66
|
+
return self._client
|
|
67
|
+
|
|
68
|
+
def configure(
|
|
69
|
+
self,
|
|
70
|
+
base_url: str,
|
|
71
|
+
api_key: Optional[str] = None,
|
|
72
|
+
timeout: int = 30,
|
|
73
|
+
) -> KeyrunesClient:
|
|
74
|
+
"""
|
|
75
|
+
Configure and create global client instance.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
base_url: Base URL of the Keyrunes API
|
|
79
|
+
api_key: Optional API key for authentication
|
|
80
|
+
timeout: Request timeout in seconds (default: 30)
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Configured KeyrunesClient instance
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
>>> from keyrunes_sdk import get_config
|
|
87
|
+
>>>
|
|
88
|
+
>>> client = get_config().configure(
|
|
89
|
+
... base_url="https://keyrunes.example.com",
|
|
90
|
+
... api_key="my-api-key"
|
|
91
|
+
... )
|
|
92
|
+
"""
|
|
93
|
+
client = KeyrunesClient(
|
|
94
|
+
base_url=base_url, api_key=api_key, timeout=timeout
|
|
95
|
+
)
|
|
96
|
+
self.set_client(client)
|
|
97
|
+
return client
|
|
98
|
+
|
|
99
|
+
def clear(self) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Clear the global client instance.
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
>>> from keyrunes_sdk import get_config
|
|
105
|
+
>>>
|
|
106
|
+
>>> get_config().clear()
|
|
107
|
+
"""
|
|
108
|
+
with self._lock:
|
|
109
|
+
if self._client:
|
|
110
|
+
self._client.close()
|
|
111
|
+
self._client = None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
_config = _GlobalConfig()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_config() -> _GlobalConfig:
|
|
118
|
+
"""
|
|
119
|
+
Get the global configuration instance.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Global configuration instance
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> from keyrunes_sdk import get_config
|
|
126
|
+
>>>
|
|
127
|
+
>>> config = get_config()
|
|
128
|
+
>>> config.configure(base_url="https://keyrunes.example.com")
|
|
129
|
+
"""
|
|
130
|
+
return _config
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def configure(
|
|
134
|
+
base_url: str,
|
|
135
|
+
api_key: Optional[str] = None,
|
|
136
|
+
timeout: int = 30,
|
|
137
|
+
) -> KeyrunesClient:
|
|
138
|
+
"""
|
|
139
|
+
Configure the global Keyrunes client.
|
|
140
|
+
|
|
141
|
+
This is a convenience function that configures the global client instance.
|
|
142
|
+
After calling this function, decorators will automatically use this client
|
|
143
|
+
without needing to pass it explicitly.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
base_url: Base URL of the Keyrunes API
|
|
147
|
+
api_key: Optional API key for authentication
|
|
148
|
+
timeout: Request timeout in seconds (default: 30)
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Configured KeyrunesClient instance
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
>>> from keyrunes_sdk import configure, require_group
|
|
155
|
+
>>>
|
|
156
|
+
>>> # Configure once at app startup
|
|
157
|
+
>>> client = configure("https://keyrunes.example.com")
|
|
158
|
+
>>> client.login("user@example.com", "password")
|
|
159
|
+
>>>
|
|
160
|
+
>>> # Now decorators work without passing client
|
|
161
|
+
>>> @require_group("admins")
|
|
162
|
+
... def admin_only_function(user_id: str):
|
|
163
|
+
... print(f"Admin function for {user_id}")
|
|
164
|
+
>>>
|
|
165
|
+
>>> admin_only_function(user_id="user123")
|
|
166
|
+
"""
|
|
167
|
+
return _config.configure(
|
|
168
|
+
base_url=base_url,
|
|
169
|
+
api_key=api_key,
|
|
170
|
+
timeout=timeout,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_global_client() -> Optional[KeyrunesClient]:
|
|
175
|
+
"""
|
|
176
|
+
Get the global client instance.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Global KeyrunesClient instance or None if not configured
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
RuntimeError: If client is not configured
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
>>> from keyrunes_sdk import configure, get_global_client
|
|
186
|
+
>>>
|
|
187
|
+
>>> configure("https://keyrunes.example.com")
|
|
188
|
+
>>> client = get_global_client()
|
|
189
|
+
>>> user = client.get_current_user()
|
|
190
|
+
"""
|
|
191
|
+
return _config.get_client()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def clear_global_client() -> None:
|
|
195
|
+
"""
|
|
196
|
+
Clear the global client instance.
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
>>> from keyrunes_sdk import clear_global_client
|
|
200
|
+
>>>
|
|
201
|
+
>>> clear_global_client()
|
|
202
|
+
"""
|
|
203
|
+
_config.clear()
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Decorators for Keyrunes authorization."""
|
|
2
|
+
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Any, Callable, Optional
|
|
5
|
+
|
|
6
|
+
from keyrunes_sdk.client import KeyrunesClient
|
|
7
|
+
from keyrunes_sdk.exceptions import AuthorizationError, UserNotFoundError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _get_client(
|
|
11
|
+
client: Optional[KeyrunesClient], kwargs: dict
|
|
12
|
+
) -> KeyrunesClient:
|
|
13
|
+
"""
|
|
14
|
+
Get client from decorator, kwargs, or global config.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
client: Client passed to decorator
|
|
18
|
+
kwargs: Function kwargs that might contain 'client'
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
KeyrunesClient instance
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ValueError: If no client is available
|
|
25
|
+
"""
|
|
26
|
+
if client:
|
|
27
|
+
return client
|
|
28
|
+
|
|
29
|
+
if "client" in kwargs:
|
|
30
|
+
client_from_kwargs = kwargs["client"]
|
|
31
|
+
if isinstance(client_from_kwargs, KeyrunesClient):
|
|
32
|
+
return client_from_kwargs
|
|
33
|
+
|
|
34
|
+
from keyrunes_sdk.config import get_global_client
|
|
35
|
+
|
|
36
|
+
global_client = get_global_client()
|
|
37
|
+
if global_client is not None:
|
|
38
|
+
return global_client
|
|
39
|
+
|
|
40
|
+
raise ValueError(
|
|
41
|
+
"KeyrunesClient not provided. Either:\n"
|
|
42
|
+
"1. Pass 'client' parameter to decorator: "
|
|
43
|
+
"@require_group('admins', client=client)\n"
|
|
44
|
+
"2. Pass 'client' in function kwargs: "
|
|
45
|
+
"func(user_id='123', client=client)\n"
|
|
46
|
+
"3. Configure global client: "
|
|
47
|
+
"configure('https://keyrunes.example.com')"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def require_group(
|
|
52
|
+
*group_ids: str,
|
|
53
|
+
client: Optional[KeyrunesClient] = None,
|
|
54
|
+
user_id_param: str = "user_id",
|
|
55
|
+
all_groups: bool = False,
|
|
56
|
+
) -> Callable:
|
|
57
|
+
"""
|
|
58
|
+
Decorator to require user membership in one or more groups.
|
|
59
|
+
|
|
60
|
+
This decorator checks if a user belongs to the specified group(s) before
|
|
61
|
+
executing the decorated function. If the user doesn't have the required
|
|
62
|
+
group membership, an AuthorizationError is raised.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
*group_ids: One or more group IDs to check
|
|
66
|
+
client: KeyrunesClient instance
|
|
67
|
+
(if None, expects 'client' in kwargs)
|
|
68
|
+
user_id_param: Name of the parameter containing user_id
|
|
69
|
+
(default: 'user_id')
|
|
70
|
+
all_groups: If True, user must belong to ALL groups;
|
|
71
|
+
if False, ANY group (default: False)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Decorated function
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
AuthorizationError: If user doesn't have required group membership
|
|
78
|
+
ValueError: If client is not provided
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
>>> @require_group("admins")
|
|
82
|
+
... def delete_user(user_id: str, client: KeyrunesClient):
|
|
83
|
+
... # This function only executes if user is in 'admins' group
|
|
84
|
+
... pass
|
|
85
|
+
|
|
86
|
+
>>> @require_group("admins", "moderators", all_groups=False)
|
|
87
|
+
... def moderate_content(user_id: str, client: KeyrunesClient):
|
|
88
|
+
... # User needs to be in 'admins' OR 'moderators'
|
|
89
|
+
... pass
|
|
90
|
+
|
|
91
|
+
>>> @require_group("admins", "verified", all_groups=True)
|
|
92
|
+
... def sensitive_operation(user_id: str, client: KeyrunesClient):
|
|
93
|
+
... # User needs to be in BOTH 'admins' AND 'verified'
|
|
94
|
+
... pass
|
|
95
|
+
|
|
96
|
+
>>> # Using with a custom client
|
|
97
|
+
>>> my_client = KeyrunesClient("https://keyrunes.example.com")
|
|
98
|
+
>>> my_client.login("admin@example.com", "password")
|
|
99
|
+
>>>
|
|
100
|
+
>>> @require_group("admins", client=my_client)
|
|
101
|
+
... def admin_only_function(user_id: str):
|
|
102
|
+
... print(f"Admin function for user {user_id}")
|
|
103
|
+
>>>
|
|
104
|
+
>>> admin_only_function(user_id="user123")
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def decorator(func: Callable) -> Callable:
|
|
108
|
+
@wraps(func)
|
|
109
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
110
|
+
keyrunes_client = _get_client(client, kwargs)
|
|
111
|
+
|
|
112
|
+
user_id = kwargs.get(user_id_param)
|
|
113
|
+
|
|
114
|
+
if not user_id:
|
|
115
|
+
import inspect
|
|
116
|
+
|
|
117
|
+
sig = inspect.signature(func)
|
|
118
|
+
param_names = list(sig.parameters.keys())
|
|
119
|
+
|
|
120
|
+
if user_id_param in param_names:
|
|
121
|
+
param_index = param_names.index(user_id_param)
|
|
122
|
+
if param_index < len(args):
|
|
123
|
+
user_id = args[param_index]
|
|
124
|
+
|
|
125
|
+
if not user_id:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"User ID not found. Expected parameter "
|
|
128
|
+
f"'{user_id_param}' in function arguments."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if all_groups:
|
|
132
|
+
for group_id in group_ids:
|
|
133
|
+
if not keyrunes_client.has_group(user_id, group_id):
|
|
134
|
+
raise AuthorizationError(
|
|
135
|
+
f"User '{user_id}' does not have required "
|
|
136
|
+
f"group '{group_id}'. All groups required: "
|
|
137
|
+
f"{group_ids}"
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
has_any_group = False
|
|
141
|
+
for group_id in group_ids:
|
|
142
|
+
if keyrunes_client.has_group(user_id, group_id):
|
|
143
|
+
has_any_group = True
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
if not has_any_group:
|
|
147
|
+
raise AuthorizationError(
|
|
148
|
+
f"User '{user_id}' does not belong to any of "
|
|
149
|
+
f"the required groups: {group_ids}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return func(*args, **kwargs)
|
|
153
|
+
|
|
154
|
+
return wrapper
|
|
155
|
+
|
|
156
|
+
return decorator
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def require_admin(
|
|
160
|
+
client: Optional[KeyrunesClient] = None,
|
|
161
|
+
user_id_param: str = "user_id",
|
|
162
|
+
) -> Callable:
|
|
163
|
+
"""
|
|
164
|
+
Decorator to require admin privileges.
|
|
165
|
+
|
|
166
|
+
Convenience decorator that checks if user has admin flag set.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
client: KeyrunesClient instance
|
|
170
|
+
(if None, expects 'client' in kwargs)
|
|
171
|
+
user_id_param: Name of the parameter containing user_id
|
|
172
|
+
(default: 'user_id')
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Decorated function
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
AuthorizationError: If user doesn't have admin privileges
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
>>> @require_admin()
|
|
182
|
+
... def system_config(user_id: str, client: KeyrunesClient):
|
|
183
|
+
... # Only admins can execute this
|
|
184
|
+
... pass
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def decorator(func: Callable) -> Callable:
|
|
188
|
+
@wraps(func)
|
|
189
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
190
|
+
keyrunes_client = _get_client(client, kwargs)
|
|
191
|
+
|
|
192
|
+
user_id = kwargs.get(user_id_param)
|
|
193
|
+
if not user_id:
|
|
194
|
+
import inspect
|
|
195
|
+
|
|
196
|
+
sig = inspect.signature(func)
|
|
197
|
+
param_names = list(sig.parameters.keys())
|
|
198
|
+
|
|
199
|
+
if user_id_param in param_names:
|
|
200
|
+
param_index = param_names.index(user_id_param)
|
|
201
|
+
if param_index < len(args):
|
|
202
|
+
user_id = args[param_index]
|
|
203
|
+
|
|
204
|
+
if not user_id:
|
|
205
|
+
raise ValueError(
|
|
206
|
+
f"User ID not found in parameter '{user_id_param}'"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
token_user_id = (
|
|
210
|
+
str(keyrunes_client._token_data.get("sub", ""))
|
|
211
|
+
if keyrunes_client._token_data
|
|
212
|
+
else None
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if token_user_id and str(user_id) == token_user_id:
|
|
216
|
+
groups = (
|
|
217
|
+
keyrunes_client._token_data.get("groups", [])
|
|
218
|
+
if keyrunes_client._token_data
|
|
219
|
+
else []
|
|
220
|
+
)
|
|
221
|
+
is_admin = (
|
|
222
|
+
"admins" in groups
|
|
223
|
+
or "superadmin" in groups
|
|
224
|
+
or any("admin" in str(g).lower() for g in groups)
|
|
225
|
+
)
|
|
226
|
+
if not is_admin:
|
|
227
|
+
raise AuthorizationError(
|
|
228
|
+
f"User '{user_id}' does not have admin privileges."
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
try:
|
|
232
|
+
user = keyrunes_client.get_user(user_id)
|
|
233
|
+
if not user.is_admin:
|
|
234
|
+
raise AuthorizationError(
|
|
235
|
+
f"User '{user_id}' does not have admin "
|
|
236
|
+
f"privileges."
|
|
237
|
+
)
|
|
238
|
+
except UserNotFoundError:
|
|
239
|
+
raise AuthorizationError(
|
|
240
|
+
f"User '{user_id}' not found or does not have "
|
|
241
|
+
f"admin privileges."
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return func(*args, **kwargs)
|
|
245
|
+
|
|
246
|
+
return wrapper
|
|
247
|
+
|
|
248
|
+
return decorator
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Custom exceptions for Keyrunes SDK."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class KeyrunesError(Exception):
|
|
5
|
+
"""Base exception for all Keyrunes SDK errors."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AuthenticationError(KeyrunesError):
|
|
11
|
+
"""Raised when authentication fails."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuthorizationError(KeyrunesError):
|
|
17
|
+
"""Raised when authorization fails."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GroupNotFoundError(KeyrunesError):
|
|
23
|
+
"""Raised when a group is not found."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UserNotFoundError(KeyrunesError):
|
|
29
|
+
"""Raised when a user is not found."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InvalidTokenError(KeyrunesError):
|
|
35
|
+
"""Raised when token is invalid or expired."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class NetworkError(KeyrunesError):
|
|
41
|
+
"""Raised when network request fails."""
|
|
42
|
+
|
|
43
|
+
pass
|
keyrunes_sdk/models.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Data models for Keyrunes SDK."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, EmailStr, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class User(BaseModel):
|
|
10
|
+
"""User model."""
|
|
11
|
+
|
|
12
|
+
id: str = Field(..., description="User ID")
|
|
13
|
+
username: str = Field(..., description="Username")
|
|
14
|
+
email: EmailStr = Field(..., description="User email")
|
|
15
|
+
groups: List[str] = Field(
|
|
16
|
+
default_factory=list, description="List of group IDs"
|
|
17
|
+
)
|
|
18
|
+
attributes: Dict[str, Any] = Field(
|
|
19
|
+
default_factory=dict, description="User attributes"
|
|
20
|
+
)
|
|
21
|
+
created_at: Optional[datetime] = Field(
|
|
22
|
+
None, description="Creation timestamp"
|
|
23
|
+
)
|
|
24
|
+
updated_at: Optional[datetime] = Field(
|
|
25
|
+
None, description="Last update timestamp"
|
|
26
|
+
)
|
|
27
|
+
is_active: bool = Field(True, description="Whether user is active")
|
|
28
|
+
is_admin: bool = Field(
|
|
29
|
+
False, description="Whether user has admin privileges"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Group(BaseModel):
|
|
34
|
+
"""Group model."""
|
|
35
|
+
|
|
36
|
+
id: str = Field(..., description="Group ID")
|
|
37
|
+
name: str = Field(..., description="Group name")
|
|
38
|
+
description: Optional[str] = Field(None, description="Group description")
|
|
39
|
+
permissions: List[str] = Field(
|
|
40
|
+
default_factory=list, description="List of permissions"
|
|
41
|
+
)
|
|
42
|
+
created_at: Optional[datetime] = Field(
|
|
43
|
+
None, description="Creation timestamp"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Token(BaseModel):
|
|
48
|
+
"""Authentication token model."""
|
|
49
|
+
|
|
50
|
+
access_token: str = Field(..., description="JWT access token")
|
|
51
|
+
token_type: str = Field("bearer", description="Token type")
|
|
52
|
+
expires_in: Optional[int] = Field(
|
|
53
|
+
None, description="Token expiration in seconds"
|
|
54
|
+
)
|
|
55
|
+
refresh_token: Optional[str] = Field(None, description="Refresh token")
|
|
56
|
+
user: Optional[User] = Field(None, description="User information")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class UserRegistration(BaseModel):
|
|
60
|
+
"""User registration data."""
|
|
61
|
+
|
|
62
|
+
username: str = Field(
|
|
63
|
+
..., min_length=3, max_length=50, description="Username"
|
|
64
|
+
)
|
|
65
|
+
email: EmailStr = Field(..., description="User email")
|
|
66
|
+
password: str = Field(..., min_length=8, description="Password")
|
|
67
|
+
attributes: Dict[str, Any] = Field(
|
|
68
|
+
default_factory=dict, description="Additional attributes"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AdminRegistration(UserRegistration):
|
|
73
|
+
"""Admin registration data (extends UserRegistration)."""
|
|
74
|
+
|
|
75
|
+
admin_key: str = Field(..., description="Admin registration key")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class LoginCredentials(BaseModel):
|
|
79
|
+
"""Login credentials."""
|
|
80
|
+
|
|
81
|
+
identity: str = Field(..., description="Username or email")
|
|
82
|
+
password: str = Field(..., description="Password")
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def from_username(cls, username: str, password: str) -> "LoginCredentials":
|
|
86
|
+
"""Create from username parameter (for backward compatibility)."""
|
|
87
|
+
return cls(identity=username, password=password)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class GroupCheck(BaseModel):
|
|
91
|
+
"""Group membership check result."""
|
|
92
|
+
|
|
93
|
+
user_id: str = Field(..., description="User ID")
|
|
94
|
+
group_id: str = Field(..., description="Group ID")
|
|
95
|
+
has_access: bool = Field(
|
|
96
|
+
..., description="Whether user has access to group"
|
|
97
|
+
)
|
|
98
|
+
checked_at: datetime = Field(
|
|
99
|
+
default_factory=datetime.utcnow, description="Check timestamp"
|
|
100
|
+
)
|