Skip to content

Commit 939e79c

Browse files
committed
Updates
1 parent 9fc14b6 commit 939e79c

File tree

2 files changed

+208
-16
lines changed

2 files changed

+208
-16
lines changed

agixtsdk/__init__.py

Lines changed: 207 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,56 @@ def handle_error(self, error) -> str:
8989
print(f"Error: {error}")
9090
raise Exception(f"Unable to retrieve data. {error}")
9191

92-
def login(self, email, otp):
92+
def login(self, username: str, password: str, mfa_token: str = None):
93+
"""
94+
Login with username/password authentication.
95+
96+
Args:
97+
username: Username or email address
98+
password: User's password
99+
mfa_token: Optional TOTP code if MFA is enabled
100+
101+
Returns:
102+
JWT token on success, or response dict on failure
103+
"""
104+
payload = {
105+
"username": username,
106+
"password": password,
107+
}
108+
if mfa_token:
109+
payload["mfa_token"] = mfa_token
110+
93111
response = requests.post(
94112
f"{self.base_uri}/v1/login",
113+
json=payload,
114+
)
115+
if self.verbose:
116+
parse_response(response)
117+
118+
result = response.json()
119+
if response.status_code == 200:
120+
token = result.get("token")
121+
if token:
122+
self.headers = {"Authorization": token}
123+
if self.verbose:
124+
print(f"Logged in successfully")
125+
return token
126+
return result
127+
128+
def login_magic_link(self, email: str, otp: str):
129+
"""
130+
Legacy login with magic link (email + OTP token).
131+
Maintained for backward compatibility.
132+
133+
Args:
134+
email: User's email address
135+
otp: TOTP code from authenticator app
136+
137+
Returns:
138+
JWT token on success, or response dict on failure
139+
"""
140+
response = requests.post(
141+
f"{self.base_uri}/v1/login/magic-link",
95142
json={"email": email, "token": otp},
96143
)
97144
if self.verbose:
@@ -105,26 +152,171 @@ def login(self, email, otp):
105152
if self.verbose:
106153
print(f"Log in at {detail}")
107154
return token
155+
return response
156+
157+
def register_user(
158+
self,
159+
email: str,
160+
password: str,
161+
confirm_password: str,
162+
first_name: str = "",
163+
last_name: str = "",
164+
username: str = None,
165+
organization_name: str = "",
166+
):
167+
"""
168+
Register a new user with username/password authentication.
169+
170+
Args:
171+
email: User's email address
172+
password: User's password
173+
confirm_password: Password confirmation
174+
first_name: User's first name (optional)
175+
last_name: User's last name (optional)
176+
username: Desired username (optional, auto-generated from email if not provided)
177+
organization_name: Company/organization name (optional)
178+
179+
Returns:
180+
Response dict with user_id, username, token on success
181+
"""
182+
payload = {
183+
"email": email,
184+
"password": password,
185+
"confirm_password": confirm_password,
186+
"first_name": first_name,
187+
"last_name": last_name,
188+
}
189+
if username:
190+
payload["username"] = username
191+
if organization_name:
192+
payload["organization_name"] = organization_name
108193

109-
def register_user(self, email, first_name, last_name):
110-
login_response = requests.post(
194+
response = requests.post(
111195
f"{self.base_uri}/v1/user",
196+
json=payload,
197+
)
198+
if self.verbose:
199+
parse_response(response)
200+
201+
result = response.json()
202+
if response.status_code == 200:
203+
# Automatically set the token for subsequent requests
204+
token = result.get("token")
205+
if token:
206+
self.headers = {"Authorization": token}
207+
if self.verbose:
208+
print(f"Registered and logged in as {result.get('username')}")
209+
return result
210+
211+
def get_mfa_setup(self):
212+
"""
213+
Get MFA setup information including QR code URI.
214+
215+
Returns:
216+
Dict with provisioning_uri, secret, and mfa_enabled status
217+
"""
218+
response = requests.get(
219+
f"{self.base_uri}/v1/user/mfa/setup",
220+
headers=self.headers,
221+
)
222+
if self.verbose:
223+
parse_response(response)
224+
return response.json()
225+
226+
def enable_mfa(self, mfa_token: str):
227+
"""
228+
Enable MFA for the current user.
229+
230+
Args:
231+
mfa_token: TOTP code from authenticator app to verify setup
232+
233+
Returns:
234+
Response dict with success message
235+
"""
236+
response = requests.post(
237+
f"{self.base_uri}/v1/user/mfa/enable",
238+
headers=self.headers,
239+
json={"mfa_token": mfa_token},
240+
)
241+
if self.verbose:
242+
parse_response(response)
243+
return response.json()
244+
245+
def disable_mfa(self, password: str = None, mfa_token: str = None):
246+
"""
247+
Disable MFA for the current user.
248+
249+
Args:
250+
password: User's password (optional)
251+
mfa_token: Current TOTP code (optional)
252+
253+
Returns:
254+
Response dict with success message
255+
"""
256+
payload = {}
257+
if password:
258+
payload["password"] = password
259+
if mfa_token:
260+
payload["mfa_token"] = mfa_token
261+
262+
response = requests.post(
263+
f"{self.base_uri}/v1/user/mfa/disable",
264+
headers=self.headers,
265+
json=payload,
266+
)
267+
if self.verbose:
268+
parse_response(response)
269+
return response.json()
270+
271+
def change_password(
272+
self, current_password: str, new_password: str, confirm_password: str
273+
):
274+
"""
275+
Change the current user's password.
276+
277+
Args:
278+
current_password: Current password
279+
new_password: New password
280+
confirm_password: New password confirmation
281+
282+
Returns:
283+
Response dict with success message
284+
"""
285+
response = requests.post(
286+
f"{self.base_uri}/v1/user/password/change",
287+
headers=self.headers,
112288
json={
113-
"email": email,
114-
"first_name": first_name,
115-
"last_name": last_name,
289+
"current_password": current_password,
290+
"new_password": new_password,
291+
"confirm_password": confirm_password,
116292
},
117293
)
118294
if self.verbose:
119-
parse_response(login_response)
120-
response = login_response.json()
121-
if "otp_uri" in response:
122-
mfa_token = str(response["otp_uri"]).split("secret=")[1].split("&")[0]
123-
totp = pyotp.TOTP(mfa_token)
124-
self.login(email=email, otp=totp.now())
125-
return response["otp_uri"]
126-
else:
127-
return response
295+
parse_response(response)
296+
return response.json()
297+
298+
def set_password(self, new_password: str, confirm_password: str):
299+
"""
300+
Set a password for users who don't have one (migrating from magic link).
301+
302+
Args:
303+
new_password: New password
304+
confirm_password: New password confirmation
305+
306+
Returns:
307+
Response dict with success message and username
308+
"""
309+
response = requests.post(
310+
f"{self.base_uri}/v1/user/password/set",
311+
headers=self.headers,
312+
json={
313+
"new_password": new_password,
314+
"confirm_password": confirm_password,
315+
},
316+
)
317+
if self.verbose:
318+
parse_response(response)
319+
return response.json()
128320

129321
def user_exists(self, email):
130322
response = requests.get(f"{self.base_uri}/v1/user/exists?email={email}")

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setup(
1010
name="agixtsdk",
11-
version="0.0.80",
11+
version="0.0.81",
1212
description="The AGiXT SDK for Python.",
1313
long_description=long_description,
1414
long_description_content_type="text/markdown",

0 commit comments

Comments
 (0)