130 lines
5.1 KiB
Python
130 lines
5.1 KiB
Python
"""Auth client for integration tests."""
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AuthClient:
|
|
"""Wraps authentication-related API calls.
|
|
|
|
Provides convenience methods for register, login, logout, and
|
|
session management. Automatically stores the token on the parent
|
|
SecuirdClient when login / register succeed.
|
|
"""
|
|
|
|
def __init__(self, client):
|
|
self._client = client
|
|
|
|
# ------------------------------------------------------------------
|
|
# Registration
|
|
# ------------------------------------------------------------------
|
|
def register(self, email: str, password: str, full_name: str | None = None) -> dict:
|
|
"""Register a new user and return the response payload.
|
|
|
|
Args:
|
|
email: User's email address.
|
|
password: Plain-text password (>= 8 chars).
|
|
full_name: Optional display name.
|
|
|
|
Returns:
|
|
API response dict containing ``user``, ``token``, ``expires_at``.
|
|
|
|
Raises:
|
|
ApiError: On validation failure or duplicate email.
|
|
"""
|
|
logger.info(f"[AuthClient] Registering user: email={email}")
|
|
payload = {"email": email, "password": password, "password_confirm": password}
|
|
if full_name:
|
|
payload["full_name"] = full_name
|
|
result = self._client.post("/auth/register", data=payload)
|
|
token = result.get("data", {}).get("token")
|
|
if token:
|
|
self._client.set_token(token)
|
|
logger.info(f"[AuthClient] Registration successful — token stored")
|
|
return result
|
|
|
|
# ------------------------------------------------------------------
|
|
# Login / Logout
|
|
# ------------------------------------------------------------------
|
|
def login(self, email: str, password: str, remember_me: bool = False) -> dict:
|
|
"""Authenticate with email and password.
|
|
|
|
Args:
|
|
email: Registered email address.
|
|
password: Plain-text password.
|
|
remember_me: Request a long-lived session.
|
|
|
|
Returns:
|
|
API response dict. If TOTP / WebAuthn is required the
|
|
response contains ``requires_totp`` or ``requires_webauthn``
|
|
instead of a token.
|
|
"""
|
|
logger.info(f"[AuthClient] Logging in: email={email}")
|
|
result = self._client.post(
|
|
"/auth/login",
|
|
data={"email": email, "password": password, "remember_me": remember_me},
|
|
)
|
|
token = result.get("data", {}).get("token")
|
|
if token:
|
|
self._client.set_token(token)
|
|
logger.info(f"[AuthClient] Login successful — token stored")
|
|
return result
|
|
|
|
def logout(self) -> dict:
|
|
"""Log out the current user and clear the stored token."""
|
|
logger.info("[AuthClient] Logging out")
|
|
result = self._client.post("/auth/logout")
|
|
self._client.clear_token()
|
|
return result
|
|
|
|
# ------------------------------------------------------------------
|
|
# Current user
|
|
# ------------------------------------------------------------------
|
|
def me(self) -> dict:
|
|
"""Return the current authenticated user's profile."""
|
|
return self._client.get("/auth/me")
|
|
|
|
# ------------------------------------------------------------------
|
|
# Sessions
|
|
# ------------------------------------------------------------------
|
|
def list_sessions(self) -> dict:
|
|
"""Return active sessions for the current user."""
|
|
return self._client.get("/auth/sessions")
|
|
|
|
def revoke_session(self, session_id: str) -> dict:
|
|
"""Revoke a specific session belonging to the current user."""
|
|
return self._client.delete(f"/auth/sessions/{session_id}")
|
|
|
|
def refresh_session(self) -> dict:
|
|
"""Extend the current session's idle window."""
|
|
return self._client.post("/auth/sessions/refresh")
|
|
|
|
# ------------------------------------------------------------------
|
|
# Password recovery
|
|
# ------------------------------------------------------------------
|
|
def forgot_password(self, email: str) -> dict:
|
|
"""Request a password-reset email."""
|
|
return self._client.post("/auth/forgot-password", data={"email": email})
|
|
|
|
def reset_password(self, token: str, new_password: str, new_password_confirm: str) -> dict:
|
|
"""Reset password using a token from the forgot-password flow."""
|
|
return self._client.post(
|
|
"/auth/reset-password",
|
|
data={
|
|
"token": token,
|
|
"password": new_password,
|
|
"password_confirm": new_password_confirm,
|
|
},
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Email verification
|
|
# ------------------------------------------------------------------
|
|
def verify_email(self, token: str) -> dict:
|
|
"""Verify an email address using the token sent by email."""
|
|
return self._client.post("/auth/verify-email", data={"token": token})
|
|
|
|
def resend_verification(self, email: str) -> dict:
|
|
"""Re-send the verification email."""
|
|
return self._client.post("/auth/resend-verification", data={"email": email})
|