dominusnode 1.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.
- dominusnode/__init__.py +151 -0
- dominusnode/admin.py +252 -0
- dominusnode/agent_wallet.py +240 -0
- dominusnode/auth.py +271 -0
- dominusnode/client.py +457 -0
- dominusnode/constants.py +18 -0
- dominusnode/errors.py +91 -0
- dominusnode/http_client.py +435 -0
- dominusnode/keys.py +88 -0
- dominusnode/plans.py +93 -0
- dominusnode/proxy.py +248 -0
- dominusnode/py.typed +0 -0
- dominusnode/sessions.py +55 -0
- dominusnode/slots.py +60 -0
- dominusnode/teams.py +339 -0
- dominusnode/token_manager.py +235 -0
- dominusnode/types.py +521 -0
- dominusnode/usage.py +230 -0
- dominusnode/wallet.py +189 -0
- dominusnode/wallet_auth.py +241 -0
- dominusnode/x402.py +88 -0
- dominusnode-1.0.0.dist-info/LICENSE +21 -0
- dominusnode-1.0.0.dist-info/METADATA +13 -0
- dominusnode-1.0.0.dist-info/RECORD +26 -0
- dominusnode-1.0.0.dist-info/WHEEL +5 -0
- dominusnode-1.0.0.dist-info/top_level.txt +1 -0
dominusnode/auth.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Auth resource -- registration, login, MFA, token refresh, and password management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from .http_client import AsyncHttpClient, SyncHttpClient
|
|
8
|
+
from .token_manager import AsyncTokenManager, TokenManager
|
|
9
|
+
from .types import LoginResult, MfaSetup, MfaStatus, User
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _parse_login_result(data: dict) -> LoginResult:
|
|
13
|
+
"""Parse a login/register response into a LoginResult."""
|
|
14
|
+
if data.get("mfaRequired"):
|
|
15
|
+
return LoginResult(
|
|
16
|
+
mfa_required=True,
|
|
17
|
+
mfa_challenge_token=data.get("mfaChallengeToken"),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
user_data = data.get("user")
|
|
21
|
+
user = None
|
|
22
|
+
if user_data:
|
|
23
|
+
user = User(
|
|
24
|
+
id=user_data["id"],
|
|
25
|
+
email=user_data["email"],
|
|
26
|
+
created_at=str(user_data.get("created_at", "")),
|
|
27
|
+
is_admin=user_data.get("is_admin", False),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return LoginResult(
|
|
31
|
+
user=user,
|
|
32
|
+
token=data.get("token"),
|
|
33
|
+
refresh_token=data.get("refreshToken"),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
38
|
+
# Sync
|
|
39
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AuthResource:
|
|
43
|
+
"""Synchronous auth operations."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, http: SyncHttpClient, tokens: TokenManager) -> None:
|
|
46
|
+
self._http = http
|
|
47
|
+
self._tokens = tokens
|
|
48
|
+
|
|
49
|
+
def register(self, email: str, password: str) -> LoginResult:
|
|
50
|
+
"""Create a new account and return tokens."""
|
|
51
|
+
data = self._http.post("/api/auth/register", json={"email": email, "password": password})
|
|
52
|
+
result = _parse_login_result(data)
|
|
53
|
+
if result.token and result.refresh_token:
|
|
54
|
+
self._tokens.set_tokens(result.token, result.refresh_token)
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
def login(self, email: str, password: str) -> LoginResult:
|
|
58
|
+
"""Authenticate with email/password. May require MFA."""
|
|
59
|
+
data = self._http.post("/api/auth/login", json={"email": email, "password": password})
|
|
60
|
+
result = _parse_login_result(data)
|
|
61
|
+
if result.token and result.refresh_token:
|
|
62
|
+
self._tokens.set_tokens(result.token, result.refresh_token)
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
def verify_mfa(
|
|
66
|
+
self,
|
|
67
|
+
code: str,
|
|
68
|
+
*,
|
|
69
|
+
mfa_challenge_token: Optional[str] = None,
|
|
70
|
+
is_backup_code: bool = False,
|
|
71
|
+
) -> LoginResult:
|
|
72
|
+
"""Complete MFA challenge with TOTP code or backup code."""
|
|
73
|
+
payload: dict = {"code": code, "isBackupCode": is_backup_code}
|
|
74
|
+
if mfa_challenge_token:
|
|
75
|
+
payload["mfaChallengeToken"] = mfa_challenge_token
|
|
76
|
+
data = self._http.post("/api/auth/mfa/verify", json=payload)
|
|
77
|
+
result = _parse_login_result(data)
|
|
78
|
+
if result.token and result.refresh_token:
|
|
79
|
+
self._tokens.set_tokens(result.token, result.refresh_token)
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
def refresh(self, refresh_token: Optional[str] = None) -> tuple[str, str]:
|
|
83
|
+
"""Exchange refresh token for new token pair.
|
|
84
|
+
|
|
85
|
+
Returns (access_token, refresh_token) tuple.
|
|
86
|
+
"""
|
|
87
|
+
rt = refresh_token or self._tokens.refresh_token
|
|
88
|
+
if not rt:
|
|
89
|
+
raise ValueError("No refresh token available")
|
|
90
|
+
data = self._http.post("/api/auth/refresh", json={"refreshToken": rt})
|
|
91
|
+
access = data["token"]
|
|
92
|
+
new_refresh = data["refreshToken"]
|
|
93
|
+
self._tokens.set_tokens(access, new_refresh)
|
|
94
|
+
return (access, new_refresh)
|
|
95
|
+
|
|
96
|
+
def logout(self) -> None:
|
|
97
|
+
"""Revoke all refresh tokens for the current user."""
|
|
98
|
+
self._http.post("/api/auth/logout")
|
|
99
|
+
self._tokens.clear()
|
|
100
|
+
|
|
101
|
+
def me(self) -> User:
|
|
102
|
+
"""Get current authenticated user."""
|
|
103
|
+
data = self._http.get("/api/auth/me")
|
|
104
|
+
u = data["user"]
|
|
105
|
+
return User(
|
|
106
|
+
id=u["id"],
|
|
107
|
+
email=u["email"],
|
|
108
|
+
created_at=str(u.get("created_at", "")),
|
|
109
|
+
is_admin=u.get("is_admin", False),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def change_password(self, current_password: str, new_password: str) -> None:
|
|
113
|
+
"""Change the current user's password."""
|
|
114
|
+
self._http.post(
|
|
115
|
+
"/api/auth/change-password",
|
|
116
|
+
json={"currentPassword": current_password, "newPassword": new_password},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def verify_key(self, api_key: str) -> LoginResult:
|
|
120
|
+
"""Verify an API key and get JWT tokens."""
|
|
121
|
+
data = self._http.post("/api/auth/verify-key", json={"apiKey": api_key})
|
|
122
|
+
result = LoginResult(
|
|
123
|
+
token=data.get("token"),
|
|
124
|
+
refresh_token=data.get("refreshToken"),
|
|
125
|
+
user=User(
|
|
126
|
+
id=data.get("userId", ""),
|
|
127
|
+
email=data.get("email", ""),
|
|
128
|
+
created_at="",
|
|
129
|
+
) if data.get("userId") else None,
|
|
130
|
+
)
|
|
131
|
+
if result.token and result.refresh_token:
|
|
132
|
+
self._tokens.set_tokens(result.token, result.refresh_token)
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
def mfa_setup(self) -> MfaSetup:
|
|
136
|
+
"""Generate TOTP secret and backup codes for MFA setup."""
|
|
137
|
+
data = self._http.post("/api/auth/mfa/setup")
|
|
138
|
+
return MfaSetup(
|
|
139
|
+
secret=data["secret"],
|
|
140
|
+
otpauth_uri=data["otpauthUri"],
|
|
141
|
+
backup_codes=data.get("backupCodes", []),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def mfa_enable(self, code: str) -> bool:
|
|
145
|
+
"""Verify initial TOTP code to activate MFA."""
|
|
146
|
+
data = self._http.post("/api/auth/mfa/enable", json={"code": code})
|
|
147
|
+
return data.get("enabled", False)
|
|
148
|
+
|
|
149
|
+
def mfa_disable(self, password: str, code: str) -> bool:
|
|
150
|
+
"""Disable MFA (requires password + TOTP/backup code)."""
|
|
151
|
+
data = self._http.post("/api/auth/mfa/disable", json={"password": password, "code": code})
|
|
152
|
+
return not data.get("enabled", True)
|
|
153
|
+
|
|
154
|
+
def mfa_status(self) -> MfaStatus:
|
|
155
|
+
"""Check current MFA status."""
|
|
156
|
+
data = self._http.get("/api/auth/mfa/status")
|
|
157
|
+
return MfaStatus(
|
|
158
|
+
enabled=data["enabled"],
|
|
159
|
+
backup_codes_remaining=data.get("backupCodesRemaining", 0),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
164
|
+
# Async
|
|
165
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class AsyncAuthResource:
|
|
169
|
+
"""Asynchronous auth operations."""
|
|
170
|
+
|
|
171
|
+
def __init__(self, http: AsyncHttpClient, tokens: AsyncTokenManager) -> None:
|
|
172
|
+
self._http = http
|
|
173
|
+
self._tokens = tokens
|
|
174
|
+
|
|
175
|
+
async def register(self, email: str, password: str) -> LoginResult:
|
|
176
|
+
data = await self._http.post("/api/auth/register", json={"email": email, "password": password})
|
|
177
|
+
result = _parse_login_result(data)
|
|
178
|
+
if result.token and result.refresh_token:
|
|
179
|
+
self._tokens.set_tokens(result.token, result.refresh_token)
|
|
180
|
+
return result
|
|
181
|
+
|
|
182
|
+
async def login(self, email: str, password: str) -> LoginResult:
|
|
183
|
+
data = await self._http.post("/api/auth/login", json={"email": email, "password": password})
|
|
184
|
+
result = _parse_login_result(data)
|
|
185
|
+
if result.token and result.refresh_token:
|
|
186
|
+
self._tokens.set_tokens(result.token, result.refresh_token)
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
async def verify_mfa(
|
|
190
|
+
self,
|
|
191
|
+
code: str,
|
|
192
|
+
*,
|
|
193
|
+
mfa_challenge_token: Optional[str] = None,
|
|
194
|
+
is_backup_code: bool = False,
|
|
195
|
+
) -> LoginResult:
|
|
196
|
+
payload: dict = {"code": code, "isBackupCode": is_backup_code}
|
|
197
|
+
if mfa_challenge_token:
|
|
198
|
+
payload["mfaChallengeToken"] = mfa_challenge_token
|
|
199
|
+
data = await self._http.post("/api/auth/mfa/verify", json=payload)
|
|
200
|
+
result = _parse_login_result(data)
|
|
201
|
+
if result.token and result.refresh_token:
|
|
202
|
+
self._tokens.set_tokens(result.token, result.refresh_token)
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
async def refresh(self, refresh_token: Optional[str] = None) -> tuple[str, str]:
|
|
206
|
+
rt = refresh_token or self._tokens.refresh_token
|
|
207
|
+
if not rt:
|
|
208
|
+
raise ValueError("No refresh token available")
|
|
209
|
+
data = await self._http.post("/api/auth/refresh", json={"refreshToken": rt})
|
|
210
|
+
access = data["token"]
|
|
211
|
+
new_refresh = data["refreshToken"]
|
|
212
|
+
self._tokens.set_tokens(access, new_refresh)
|
|
213
|
+
return (access, new_refresh)
|
|
214
|
+
|
|
215
|
+
async def logout(self) -> None:
|
|
216
|
+
await self._http.post("/api/auth/logout")
|
|
217
|
+
self._tokens.clear()
|
|
218
|
+
|
|
219
|
+
async def me(self) -> User:
|
|
220
|
+
data = await self._http.get("/api/auth/me")
|
|
221
|
+
u = data["user"]
|
|
222
|
+
return User(
|
|
223
|
+
id=u["id"],
|
|
224
|
+
email=u["email"],
|
|
225
|
+
created_at=str(u.get("created_at", "")),
|
|
226
|
+
is_admin=u.get("is_admin", False),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
async def change_password(self, current_password: str, new_password: str) -> None:
|
|
230
|
+
await self._http.post(
|
|
231
|
+
"/api/auth/change-password",
|
|
232
|
+
json={"currentPassword": current_password, "newPassword": new_password},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
async def verify_key(self, api_key: str) -> LoginResult:
|
|
236
|
+
data = await self._http.post("/api/auth/verify-key", json={"apiKey": api_key})
|
|
237
|
+
result = LoginResult(
|
|
238
|
+
token=data.get("token"),
|
|
239
|
+
refresh_token=data.get("refreshToken"),
|
|
240
|
+
user=User(
|
|
241
|
+
id=data.get("userId", ""),
|
|
242
|
+
email=data.get("email", ""),
|
|
243
|
+
created_at="",
|
|
244
|
+
) if data.get("userId") else None,
|
|
245
|
+
)
|
|
246
|
+
if result.token and result.refresh_token:
|
|
247
|
+
self._tokens.set_tokens(result.token, result.refresh_token)
|
|
248
|
+
return result
|
|
249
|
+
|
|
250
|
+
async def mfa_setup(self) -> MfaSetup:
|
|
251
|
+
data = await self._http.post("/api/auth/mfa/setup")
|
|
252
|
+
return MfaSetup(
|
|
253
|
+
secret=data["secret"],
|
|
254
|
+
otpauth_uri=data["otpauthUri"],
|
|
255
|
+
backup_codes=data.get("backupCodes", []),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
async def mfa_enable(self, code: str) -> bool:
|
|
259
|
+
data = await self._http.post("/api/auth/mfa/enable", json={"code": code})
|
|
260
|
+
return data.get("enabled", False)
|
|
261
|
+
|
|
262
|
+
async def mfa_disable(self, password: str, code: str) -> bool:
|
|
263
|
+
data = await self._http.post("/api/auth/mfa/disable", json={"password": password, "code": code})
|
|
264
|
+
return not data.get("enabled", True)
|
|
265
|
+
|
|
266
|
+
async def mfa_status(self) -> MfaStatus:
|
|
267
|
+
data = await self._http.get("/api/auth/mfa/status")
|
|
268
|
+
return MfaStatus(
|
|
269
|
+
enabled=data["enabled"],
|
|
270
|
+
backup_codes_remaining=data.get("backupCodesRemaining", 0),
|
|
271
|
+
)
|