dominus-sdk-python-staging 2.1.6__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.
- dominus/__init__.py +47 -0
- dominus/config/__init__.py +24 -0
- dominus/config/endpoints.py +39 -0
- dominus/errors.py +201 -0
- dominus/helpers/__init__.py +2 -0
- dominus/helpers/auth.py +23 -0
- dominus/helpers/cache.py +192 -0
- dominus/helpers/core.py +657 -0
- dominus/helpers/crypto.py +118 -0
- dominus/namespaces/__init__.py +26 -0
- dominus/namespaces/_deprecated_crossover.py +10 -0
- dominus/namespaces/_deprecated_sql.py +1341 -0
- dominus/namespaces/auth.py +1056 -0
- dominus/namespaces/courier.py +251 -0
- dominus/namespaces/db.py +267 -0
- dominus/namespaces/ddl.py +590 -0
- dominus/namespaces/files.py +299 -0
- dominus/namespaces/health.py +59 -0
- dominus/namespaces/logs.py +367 -0
- dominus/namespaces/open.py +72 -0
- dominus/namespaces/portal.py +504 -0
- dominus/namespaces/redis.py +357 -0
- dominus/namespaces/secrets.py +126 -0
- dominus/services/__init__.py +2 -0
- dominus/services/_deprecated_architect.py +323 -0
- dominus/services/_deprecated_sovereign.py +93 -0
- dominus/start.py +1034 -0
- dominus_sdk_python_staging-2.1.6.dist-info/METADATA +381 -0
- dominus_sdk_python_staging-2.1.6.dist-info/RECORD +31 -0
- dominus_sdk_python_staging-2.1.6.dist-info/WHEEL +5 -0
- dominus_sdk_python_staging-2.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Portal Namespace - User authentication and session orchestration.
|
|
3
|
+
|
|
4
|
+
Provides login, logout, session management, profile, and navigation access.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..start import Dominus
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PortalNamespace:
|
|
13
|
+
"""
|
|
14
|
+
User authentication and session namespace.
|
|
15
|
+
|
|
16
|
+
All portal operations go through /api/portal/* endpoints.
|
|
17
|
+
Orchestrates Guardian (user data) + Warden (JWT minting).
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
# User login
|
|
21
|
+
session = await dominus.portal.login(
|
|
22
|
+
username="john@example.com",
|
|
23
|
+
password="secret123",
|
|
24
|
+
tenant_id="tenant-uuid"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Get current user
|
|
28
|
+
me = await dominus.portal.me()
|
|
29
|
+
|
|
30
|
+
# Switch tenant
|
|
31
|
+
await dominus.portal.switch_tenant("other-tenant-uuid")
|
|
32
|
+
|
|
33
|
+
# Get navigation
|
|
34
|
+
nav = await dominus.portal.get_navigation()
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, client: "Dominus"):
|
|
38
|
+
self._client = client
|
|
39
|
+
|
|
40
|
+
# ========================================
|
|
41
|
+
# AUTHENTICATION
|
|
42
|
+
# ========================================
|
|
43
|
+
|
|
44
|
+
async def login(
|
|
45
|
+
self,
|
|
46
|
+
username: str,
|
|
47
|
+
password: str,
|
|
48
|
+
tenant_id: Optional[str] = None
|
|
49
|
+
) -> Dict[str, Any]:
|
|
50
|
+
"""
|
|
51
|
+
Login user with password.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
username: Username or email
|
|
55
|
+
password: User password
|
|
56
|
+
tenant_id: Optional tenant UUID/slug. If not provided, uses user's first available tenant.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dict with user info, active tenant, available tenants list, and session_id
|
|
60
|
+
"""
|
|
61
|
+
body: Dict[str, str] = {
|
|
62
|
+
"username": username,
|
|
63
|
+
"password": password
|
|
64
|
+
}
|
|
65
|
+
if tenant_id:
|
|
66
|
+
body["tenant_id"] = tenant_id
|
|
67
|
+
|
|
68
|
+
return await self._client._request(
|
|
69
|
+
endpoint="/api/portal/auth/login",
|
|
70
|
+
body=body
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
async def login_client(
|
|
74
|
+
self,
|
|
75
|
+
psk: str,
|
|
76
|
+
tenant_id: Optional[str] = None
|
|
77
|
+
) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Login service client with PSK.
|
|
80
|
+
|
|
81
|
+
Client is looked up by PSK directly - no client_id needed.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
psk: Pre-shared key
|
|
85
|
+
tenant_id: Optional tenant UUID. If not provided, uses client's first assigned tenant.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dict with access_token, token_type, expires_in, session_id
|
|
89
|
+
"""
|
|
90
|
+
body: Dict[str, str] = {"psk": psk}
|
|
91
|
+
if tenant_id:
|
|
92
|
+
body["tenant_id"] = tenant_id
|
|
93
|
+
return await self._client._request(
|
|
94
|
+
endpoint="/api/portal/auth/login-client",
|
|
95
|
+
body=body
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
async def logout(self, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
99
|
+
"""
|
|
100
|
+
End session and clear cookie.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
104
|
+
"""
|
|
105
|
+
return await self._client._request(
|
|
106
|
+
endpoint="/api/portal/auth/logout",
|
|
107
|
+
body={},
|
|
108
|
+
user_token=user_token
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
async def refresh(self, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
Refresh JWT token using existing session.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
117
|
+
"""
|
|
118
|
+
return await self._client._request(
|
|
119
|
+
endpoint="/api/portal/auth/refresh",
|
|
120
|
+
body={},
|
|
121
|
+
user_token=user_token
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
async def me(self, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
125
|
+
"""
|
|
126
|
+
Get current user/client info.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Dict with subject_type, user/client info, tenants, scopes, roles
|
|
133
|
+
"""
|
|
134
|
+
return await self._client._request(
|
|
135
|
+
endpoint="/api/portal/auth/me",
|
|
136
|
+
method="GET",
|
|
137
|
+
user_token=user_token
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
async def switch_tenant(self, tenant_id: str, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
141
|
+
"""
|
|
142
|
+
Switch active tenant context.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
tenant_id: Tenant UUID to switch to
|
|
146
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Dict with success status and new tenant info
|
|
150
|
+
"""
|
|
151
|
+
return await self._client._request(
|
|
152
|
+
endpoint="/api/portal/auth/switch-tenant",
|
|
153
|
+
body={"tenant_id": tenant_id},
|
|
154
|
+
user_token=user_token
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# ========================================
|
|
158
|
+
# SECURITY
|
|
159
|
+
# ========================================
|
|
160
|
+
|
|
161
|
+
async def change_password(
|
|
162
|
+
self,
|
|
163
|
+
current_password: str,
|
|
164
|
+
new_password: str,
|
|
165
|
+
user_token: Optional[str] = None
|
|
166
|
+
) -> Dict[str, Any]:
|
|
167
|
+
"""
|
|
168
|
+
Change current user's password.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
current_password: Current password for verification
|
|
172
|
+
new_password: New password to set
|
|
173
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Dict with success status
|
|
177
|
+
"""
|
|
178
|
+
return await self._client._request(
|
|
179
|
+
endpoint="/api/portal/security/change-password",
|
|
180
|
+
body={
|
|
181
|
+
"current_password": current_password,
|
|
182
|
+
"new_password": new_password
|
|
183
|
+
},
|
|
184
|
+
user_token=user_token
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
async def request_password_reset(self, email: str) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Request password reset email.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
email: User's email address
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Dict with success status (always true for security)
|
|
196
|
+
"""
|
|
197
|
+
return await self._client._request(
|
|
198
|
+
endpoint="/api/portal/security/request-reset",
|
|
199
|
+
body={"email": email}
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
async def confirm_password_reset(
|
|
203
|
+
self,
|
|
204
|
+
token: str,
|
|
205
|
+
new_password: str
|
|
206
|
+
) -> Dict[str, Any]:
|
|
207
|
+
"""
|
|
208
|
+
Confirm password reset with token.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
token: Reset token from email
|
|
212
|
+
new_password: New password to set
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Dict with success status
|
|
216
|
+
"""
|
|
217
|
+
return await self._client._request(
|
|
218
|
+
endpoint="/api/portal/security/confirm-reset",
|
|
219
|
+
body={"token": token, "new_password": new_password}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
async def list_sessions(self, user_token: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
223
|
+
"""
|
|
224
|
+
List all active sessions for current user.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
228
|
+
"""
|
|
229
|
+
return await self._client._request(
|
|
230
|
+
endpoint="/api/portal/security/sessions",
|
|
231
|
+
method="GET",
|
|
232
|
+
user_token=user_token
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
async def revoke_session(self, session_id: str, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
236
|
+
"""
|
|
237
|
+
Revoke a specific session.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
session_id: Session ID to revoke
|
|
241
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
242
|
+
"""
|
|
243
|
+
return await self._client._request(
|
|
244
|
+
endpoint=f"/api/portal/security/sessions/{session_id}",
|
|
245
|
+
method="DELETE",
|
|
246
|
+
user_token=user_token
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
async def revoke_all_sessions(self, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
250
|
+
"""
|
|
251
|
+
Revoke all sessions except current.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
255
|
+
"""
|
|
256
|
+
return await self._client._request(
|
|
257
|
+
endpoint="/api/portal/security/sessions/revoke-all",
|
|
258
|
+
body={},
|
|
259
|
+
user_token=user_token
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# ========================================
|
|
263
|
+
# PROFILE
|
|
264
|
+
# ========================================
|
|
265
|
+
|
|
266
|
+
async def get_profile(self, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
267
|
+
"""
|
|
268
|
+
Get current user's profile.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
272
|
+
"""
|
|
273
|
+
return await self._client._request(
|
|
274
|
+
endpoint="/api/portal/profile",
|
|
275
|
+
method="GET",
|
|
276
|
+
user_token=user_token
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
async def update_profile(
|
|
280
|
+
self,
|
|
281
|
+
display_name: Optional[str] = None,
|
|
282
|
+
avatar_url: Optional[str] = None,
|
|
283
|
+
bio: Optional[str] = None,
|
|
284
|
+
phone: Optional[str] = None,
|
|
285
|
+
extra: Optional[Dict[str, Any]] = None,
|
|
286
|
+
user_token: Optional[str] = None
|
|
287
|
+
) -> Dict[str, Any]:
|
|
288
|
+
"""
|
|
289
|
+
Update user profile.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
display_name: Display name
|
|
293
|
+
avatar_url: Avatar image URL
|
|
294
|
+
bio: User biography
|
|
295
|
+
phone: Phone number
|
|
296
|
+
extra: Additional metadata (JSONB)
|
|
297
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Updated profile
|
|
301
|
+
"""
|
|
302
|
+
body = {}
|
|
303
|
+
if display_name is not None:
|
|
304
|
+
body["display_name"] = display_name
|
|
305
|
+
if avatar_url is not None:
|
|
306
|
+
body["avatar_url"] = avatar_url
|
|
307
|
+
if bio is not None:
|
|
308
|
+
body["bio"] = bio
|
|
309
|
+
if phone is not None:
|
|
310
|
+
body["phone"] = phone
|
|
311
|
+
if extra is not None:
|
|
312
|
+
body["extra"] = extra
|
|
313
|
+
|
|
314
|
+
return await self._client._request(
|
|
315
|
+
endpoint="/api/portal/profile",
|
|
316
|
+
method="PUT",
|
|
317
|
+
body=body,
|
|
318
|
+
user_token=user_token
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
async def get_preferences(self, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
322
|
+
"""
|
|
323
|
+
Get current user's preferences.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
327
|
+
"""
|
|
328
|
+
return await self._client._request(
|
|
329
|
+
endpoint="/api/portal/profile/preferences",
|
|
330
|
+
method="GET",
|
|
331
|
+
user_token=user_token
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
async def update_preferences(
|
|
335
|
+
self,
|
|
336
|
+
theme: Optional[str] = None,
|
|
337
|
+
language: Optional[str] = None,
|
|
338
|
+
timezone: Optional[str] = None,
|
|
339
|
+
sidebar_collapsed: Optional[bool] = None,
|
|
340
|
+
notifications_enabled: Optional[bool] = None,
|
|
341
|
+
email_notifications: Optional[bool] = None,
|
|
342
|
+
extra: Optional[Dict[str, Any]] = None,
|
|
343
|
+
user_token: Optional[str] = None
|
|
344
|
+
) -> Dict[str, Any]:
|
|
345
|
+
"""
|
|
346
|
+
Update user preferences.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
theme: UI theme (light/dark/system)
|
|
350
|
+
language: Preferred language code
|
|
351
|
+
timezone: Timezone (e.g., "America/New_York")
|
|
352
|
+
sidebar_collapsed: Sidebar state
|
|
353
|
+
notifications_enabled: Push notifications
|
|
354
|
+
email_notifications: Email notifications
|
|
355
|
+
extra: Additional preferences (JSONB)
|
|
356
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Updated preferences
|
|
360
|
+
"""
|
|
361
|
+
body = {}
|
|
362
|
+
if theme is not None:
|
|
363
|
+
body["theme"] = theme
|
|
364
|
+
if language is not None:
|
|
365
|
+
body["language"] = language
|
|
366
|
+
if timezone is not None:
|
|
367
|
+
body["timezone"] = timezone
|
|
368
|
+
if sidebar_collapsed is not None:
|
|
369
|
+
body["sidebar_collapsed"] = sidebar_collapsed
|
|
370
|
+
if notifications_enabled is not None:
|
|
371
|
+
body["notifications_enabled"] = notifications_enabled
|
|
372
|
+
if email_notifications is not None:
|
|
373
|
+
body["email_notifications"] = email_notifications
|
|
374
|
+
if extra is not None:
|
|
375
|
+
body["extra"] = extra
|
|
376
|
+
|
|
377
|
+
return await self._client._request(
|
|
378
|
+
endpoint="/api/portal/profile/preferences",
|
|
379
|
+
method="PUT",
|
|
380
|
+
body=body,
|
|
381
|
+
user_token=user_token
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# ========================================
|
|
385
|
+
# NAVIGATION
|
|
386
|
+
# ========================================
|
|
387
|
+
|
|
388
|
+
async def get_navigation(self, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
389
|
+
"""
|
|
390
|
+
Get navigation tree for current user's tenant.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Hierarchical nav structure with access-filtered items.
|
|
397
|
+
"""
|
|
398
|
+
return await self._client._request(
|
|
399
|
+
endpoint="/api/portal/nav/tree",
|
|
400
|
+
method="GET",
|
|
401
|
+
user_token=user_token
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
async def check_page_access(self, path: str, user_token: Optional[str] = None) -> Dict[str, Any]:
|
|
405
|
+
"""
|
|
406
|
+
Check if current user can access a page.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
path: Page path to check
|
|
410
|
+
user_token: Optional user JWT for user-authenticated requests
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Dict with allowed (bool) and reason
|
|
414
|
+
"""
|
|
415
|
+
return await self._client._request(
|
|
416
|
+
endpoint="/api/portal/nav/check-access",
|
|
417
|
+
body={"path": path},
|
|
418
|
+
user_token=user_token
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# ========================================
|
|
422
|
+
# REGISTRATION
|
|
423
|
+
# ========================================
|
|
424
|
+
|
|
425
|
+
async def register(
|
|
426
|
+
self,
|
|
427
|
+
username: str,
|
|
428
|
+
email: str,
|
|
429
|
+
password: str,
|
|
430
|
+
tenant_id: str
|
|
431
|
+
) -> Dict[str, Any]:
|
|
432
|
+
"""
|
|
433
|
+
Self-register new user.
|
|
434
|
+
|
|
435
|
+
Requires tenant to allow public registration.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
username: Desired username
|
|
439
|
+
email: Email address
|
|
440
|
+
password: Password
|
|
441
|
+
tenant_id: Tenant to register with
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
Dict with user info and verification status
|
|
445
|
+
"""
|
|
446
|
+
return await self._client._request(
|
|
447
|
+
endpoint="/api/portal/register",
|
|
448
|
+
body={
|
|
449
|
+
"username": username,
|
|
450
|
+
"email": email,
|
|
451
|
+
"password": password,
|
|
452
|
+
"tenant_id": tenant_id
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
async def verify_email(self, token: str) -> Dict[str, Any]:
|
|
457
|
+
"""
|
|
458
|
+
Verify email with token.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
token: Verification token from email
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
Dict with success status
|
|
465
|
+
"""
|
|
466
|
+
return await self._client._request(
|
|
467
|
+
endpoint="/api/portal/register/verify",
|
|
468
|
+
body={"token": token}
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
async def resend_verification(self, email: str) -> Dict[str, Any]:
|
|
472
|
+
"""
|
|
473
|
+
Resend verification email.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
email: User's email address
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
Dict with success status
|
|
480
|
+
"""
|
|
481
|
+
return await self._client._request(
|
|
482
|
+
endpoint="/api/portal/register/resend-verification",
|
|
483
|
+
body={"email": email}
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
async def accept_invitation(
|
|
487
|
+
self,
|
|
488
|
+
token: str,
|
|
489
|
+
password: str
|
|
490
|
+
) -> Dict[str, Any]:
|
|
491
|
+
"""
|
|
492
|
+
Accept admin invitation and set password.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
token: Invitation token from email
|
|
496
|
+
password: Password to set
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
Dict with user info and login token
|
|
500
|
+
"""
|
|
501
|
+
return await self._client._request(
|
|
502
|
+
endpoint="/api/portal/register/accept-invitation",
|
|
503
|
+
body={"token": token, "password": password}
|
|
504
|
+
)
|