dominus-sdk-python 2.0.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.
- dominus/__init__.py +47 -0
- dominus/config/__init__.py +24 -0
- dominus/config/endpoints.py +43 -0
- dominus/errors.py +201 -0
- dominus/helpers/__init__.py +2 -0
- dominus/helpers/auth.py +49 -0
- dominus/helpers/cache.py +192 -0
- dominus/helpers/core.py +603 -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 +1025 -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 +437 -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 +942 -0
- dominus_sdk_python-2.0.0.dist-info/METADATA +381 -0
- dominus_sdk_python-2.0.0.dist-info/RECORD +31 -0
- dominus_sdk_python-2.0.0.dist-info/WHEEL +5 -0
- dominus_sdk_python-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,437 @@
|
|
|
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: str
|
|
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: Tenant UUID to login to
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dict with user info, tenant info, and session_id
|
|
60
|
+
"""
|
|
61
|
+
return await self._client._request(
|
|
62
|
+
endpoint="/api/portal/auth/login",
|
|
63
|
+
body={
|
|
64
|
+
"username": username,
|
|
65
|
+
"password": password,
|
|
66
|
+
"tenant_id": tenant_id
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
async def login_client(
|
|
71
|
+
self,
|
|
72
|
+
client_id: str,
|
|
73
|
+
psk: str,
|
|
74
|
+
tenant_id: str
|
|
75
|
+
) -> Dict[str, Any]:
|
|
76
|
+
"""
|
|
77
|
+
Login service client with PSK.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
client_id: Client UUID
|
|
81
|
+
psk: Pre-shared key
|
|
82
|
+
tenant_id: Tenant UUID
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dict with access_token, token_type, expires_in, session_id
|
|
86
|
+
"""
|
|
87
|
+
return await self._client._request(
|
|
88
|
+
endpoint="/api/portal/auth/login-client",
|
|
89
|
+
body={
|
|
90
|
+
"client_id": client_id,
|
|
91
|
+
"psk": psk,
|
|
92
|
+
"tenant_id": tenant_id
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
async def logout(self) -> Dict[str, Any]:
|
|
97
|
+
"""End session and clear cookie."""
|
|
98
|
+
return await self._client._request(
|
|
99
|
+
endpoint="/api/portal/auth/logout",
|
|
100
|
+
body={}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
async def refresh(self) -> Dict[str, Any]:
|
|
104
|
+
"""Refresh JWT token using existing session."""
|
|
105
|
+
return await self._client._request(
|
|
106
|
+
endpoint="/api/portal/auth/refresh",
|
|
107
|
+
body={}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
async def me(self) -> Dict[str, Any]:
|
|
111
|
+
"""
|
|
112
|
+
Get current user/client info.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Dict with subject_type, user/client info, tenants, scopes, roles
|
|
116
|
+
"""
|
|
117
|
+
return await self._client._request(
|
|
118
|
+
endpoint="/api/portal/auth/me",
|
|
119
|
+
method="GET"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
async def switch_tenant(self, tenant_id: str) -> Dict[str, Any]:
|
|
123
|
+
"""
|
|
124
|
+
Switch active tenant context.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
tenant_id: Tenant UUID to switch to
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Dict with success status and new tenant info
|
|
131
|
+
"""
|
|
132
|
+
return await self._client._request(
|
|
133
|
+
endpoint="/api/portal/auth/switch-tenant",
|
|
134
|
+
body={"tenant_id": tenant_id}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# ========================================
|
|
138
|
+
# SECURITY
|
|
139
|
+
# ========================================
|
|
140
|
+
|
|
141
|
+
async def change_password(
|
|
142
|
+
self,
|
|
143
|
+
current_password: str,
|
|
144
|
+
new_password: str
|
|
145
|
+
) -> Dict[str, Any]:
|
|
146
|
+
"""
|
|
147
|
+
Change current user's password.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
current_password: Current password for verification
|
|
151
|
+
new_password: New password to set
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Dict with success status
|
|
155
|
+
"""
|
|
156
|
+
return await self._client._request(
|
|
157
|
+
endpoint="/api/portal/security/change-password",
|
|
158
|
+
body={
|
|
159
|
+
"current_password": current_password,
|
|
160
|
+
"new_password": new_password
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
async def request_password_reset(self, email: str) -> Dict[str, Any]:
|
|
165
|
+
"""
|
|
166
|
+
Request password reset email.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
email: User's email address
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Dict with success status (always true for security)
|
|
173
|
+
"""
|
|
174
|
+
return await self._client._request(
|
|
175
|
+
endpoint="/api/portal/security/request-reset",
|
|
176
|
+
body={"email": email}
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
async def confirm_password_reset(
|
|
180
|
+
self,
|
|
181
|
+
token: str,
|
|
182
|
+
new_password: str
|
|
183
|
+
) -> Dict[str, Any]:
|
|
184
|
+
"""
|
|
185
|
+
Confirm password reset with token.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
token: Reset token from email
|
|
189
|
+
new_password: New password to set
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Dict with success status
|
|
193
|
+
"""
|
|
194
|
+
return await self._client._request(
|
|
195
|
+
endpoint="/api/portal/security/confirm-reset",
|
|
196
|
+
body={"token": token, "new_password": new_password}
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
async def list_sessions(self) -> List[Dict[str, Any]]:
|
|
200
|
+
"""List all active sessions for current user."""
|
|
201
|
+
return await self._client._request(
|
|
202
|
+
endpoint="/api/portal/security/sessions",
|
|
203
|
+
method="GET"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
async def revoke_session(self, session_id: str) -> Dict[str, Any]:
|
|
207
|
+
"""Revoke a specific session."""
|
|
208
|
+
return await self._client._request(
|
|
209
|
+
endpoint=f"/api/portal/security/sessions/{session_id}",
|
|
210
|
+
method="DELETE"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
async def revoke_all_sessions(self) -> Dict[str, Any]:
|
|
214
|
+
"""Revoke all sessions except current."""
|
|
215
|
+
return await self._client._request(
|
|
216
|
+
endpoint="/api/portal/security/sessions/revoke-all",
|
|
217
|
+
body={}
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# ========================================
|
|
221
|
+
# PROFILE
|
|
222
|
+
# ========================================
|
|
223
|
+
|
|
224
|
+
async def get_profile(self) -> Dict[str, Any]:
|
|
225
|
+
"""Get current user's profile."""
|
|
226
|
+
return await self._client._request(
|
|
227
|
+
endpoint="/api/portal/profile",
|
|
228
|
+
method="GET"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
async def update_profile(
|
|
232
|
+
self,
|
|
233
|
+
display_name: Optional[str] = None,
|
|
234
|
+
avatar_url: Optional[str] = None,
|
|
235
|
+
bio: Optional[str] = None,
|
|
236
|
+
phone: Optional[str] = None,
|
|
237
|
+
extra: Optional[Dict[str, Any]] = None
|
|
238
|
+
) -> Dict[str, Any]:
|
|
239
|
+
"""
|
|
240
|
+
Update user profile.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
display_name: Display name
|
|
244
|
+
avatar_url: Avatar image URL
|
|
245
|
+
bio: User biography
|
|
246
|
+
phone: Phone number
|
|
247
|
+
extra: Additional metadata (JSONB)
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Updated profile
|
|
251
|
+
"""
|
|
252
|
+
body = {}
|
|
253
|
+
if display_name is not None:
|
|
254
|
+
body["display_name"] = display_name
|
|
255
|
+
if avatar_url is not None:
|
|
256
|
+
body["avatar_url"] = avatar_url
|
|
257
|
+
if bio is not None:
|
|
258
|
+
body["bio"] = bio
|
|
259
|
+
if phone is not None:
|
|
260
|
+
body["phone"] = phone
|
|
261
|
+
if extra is not None:
|
|
262
|
+
body["extra"] = extra
|
|
263
|
+
|
|
264
|
+
return await self._client._request(
|
|
265
|
+
endpoint="/api/portal/profile",
|
|
266
|
+
method="PUT",
|
|
267
|
+
body=body
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
async def get_preferences(self) -> Dict[str, Any]:
|
|
271
|
+
"""Get current user's preferences."""
|
|
272
|
+
return await self._client._request(
|
|
273
|
+
endpoint="/api/portal/profile/preferences",
|
|
274
|
+
method="GET"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
async def update_preferences(
|
|
278
|
+
self,
|
|
279
|
+
theme: Optional[str] = None,
|
|
280
|
+
language: Optional[str] = None,
|
|
281
|
+
timezone: Optional[str] = None,
|
|
282
|
+
sidebar_collapsed: Optional[bool] = None,
|
|
283
|
+
notifications_enabled: Optional[bool] = None,
|
|
284
|
+
email_notifications: Optional[bool] = None,
|
|
285
|
+
extra: Optional[Dict[str, Any]] = None
|
|
286
|
+
) -> Dict[str, Any]:
|
|
287
|
+
"""
|
|
288
|
+
Update user preferences.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
theme: UI theme (light/dark/system)
|
|
292
|
+
language: Preferred language code
|
|
293
|
+
timezone: Timezone (e.g., "America/New_York")
|
|
294
|
+
sidebar_collapsed: Sidebar state
|
|
295
|
+
notifications_enabled: Push notifications
|
|
296
|
+
email_notifications: Email notifications
|
|
297
|
+
extra: Additional preferences (JSONB)
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Updated preferences
|
|
301
|
+
"""
|
|
302
|
+
body = {}
|
|
303
|
+
if theme is not None:
|
|
304
|
+
body["theme"] = theme
|
|
305
|
+
if language is not None:
|
|
306
|
+
body["language"] = language
|
|
307
|
+
if timezone is not None:
|
|
308
|
+
body["timezone"] = timezone
|
|
309
|
+
if sidebar_collapsed is not None:
|
|
310
|
+
body["sidebar_collapsed"] = sidebar_collapsed
|
|
311
|
+
if notifications_enabled is not None:
|
|
312
|
+
body["notifications_enabled"] = notifications_enabled
|
|
313
|
+
if email_notifications is not None:
|
|
314
|
+
body["email_notifications"] = email_notifications
|
|
315
|
+
if extra is not None:
|
|
316
|
+
body["extra"] = extra
|
|
317
|
+
|
|
318
|
+
return await self._client._request(
|
|
319
|
+
endpoint="/api/portal/profile/preferences",
|
|
320
|
+
method="PUT",
|
|
321
|
+
body=body
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# ========================================
|
|
325
|
+
# NAVIGATION
|
|
326
|
+
# ========================================
|
|
327
|
+
|
|
328
|
+
async def get_navigation(self) -> Dict[str, Any]:
|
|
329
|
+
"""
|
|
330
|
+
Get navigation tree for current user's tenant.
|
|
331
|
+
|
|
332
|
+
Returns hierarchical nav structure with access-filtered items.
|
|
333
|
+
"""
|
|
334
|
+
return await self._client._request(
|
|
335
|
+
endpoint="/api/portal/nav/tree",
|
|
336
|
+
method="GET"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
async def check_page_access(self, path: str) -> Dict[str, Any]:
|
|
340
|
+
"""
|
|
341
|
+
Check if current user can access a page.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
path: Page path to check
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Dict with allowed (bool) and reason
|
|
348
|
+
"""
|
|
349
|
+
return await self._client._request(
|
|
350
|
+
endpoint="/api/portal/nav/check-access",
|
|
351
|
+
body={"path": path}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# ========================================
|
|
355
|
+
# REGISTRATION
|
|
356
|
+
# ========================================
|
|
357
|
+
|
|
358
|
+
async def register(
|
|
359
|
+
self,
|
|
360
|
+
username: str,
|
|
361
|
+
email: str,
|
|
362
|
+
password: str,
|
|
363
|
+
tenant_id: str
|
|
364
|
+
) -> Dict[str, Any]:
|
|
365
|
+
"""
|
|
366
|
+
Self-register new user.
|
|
367
|
+
|
|
368
|
+
Requires tenant to allow public registration.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
username: Desired username
|
|
372
|
+
email: Email address
|
|
373
|
+
password: Password
|
|
374
|
+
tenant_id: Tenant to register with
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Dict with user info and verification status
|
|
378
|
+
"""
|
|
379
|
+
return await self._client._request(
|
|
380
|
+
endpoint="/api/portal/register",
|
|
381
|
+
body={
|
|
382
|
+
"username": username,
|
|
383
|
+
"email": email,
|
|
384
|
+
"password": password,
|
|
385
|
+
"tenant_id": tenant_id
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
async def verify_email(self, token: str) -> Dict[str, Any]:
|
|
390
|
+
"""
|
|
391
|
+
Verify email with token.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
token: Verification token from email
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Dict with success status
|
|
398
|
+
"""
|
|
399
|
+
return await self._client._request(
|
|
400
|
+
endpoint="/api/portal/register/verify",
|
|
401
|
+
body={"token": token}
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
async def resend_verification(self, email: str) -> Dict[str, Any]:
|
|
405
|
+
"""
|
|
406
|
+
Resend verification email.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
email: User's email address
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Dict with success status
|
|
413
|
+
"""
|
|
414
|
+
return await self._client._request(
|
|
415
|
+
endpoint="/api/portal/register/resend-verification",
|
|
416
|
+
body={"email": email}
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
async def accept_invitation(
|
|
420
|
+
self,
|
|
421
|
+
token: str,
|
|
422
|
+
password: str
|
|
423
|
+
) -> Dict[str, Any]:
|
|
424
|
+
"""
|
|
425
|
+
Accept admin invitation and set password.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
token: Invitation token from email
|
|
429
|
+
password: Password to set
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
Dict with user info and login token
|
|
433
|
+
"""
|
|
434
|
+
return await self._client._request(
|
|
435
|
+
endpoint="/api/portal/register/accept-invitation",
|
|
436
|
+
body={"token": token, "password": password}
|
|
437
|
+
)
|