test: add comprehensive integration test suite for IAM platform
Add 162 integration tests covering authentication flows, TOTP MFA, SSH key/certificate management, organization workflows, multi-org access, self-service features, admin operations, authorization, security edge cases, department/principal management, CA management, policy compliance, WebAuthn passkeys, and ZeroTier network access. Includes: - Reusable API client library with session management - Test fixtures for users, organizations, memberships, and CAs - Helper functions for SSH key generation and verification - Documentation for running and writing tests Also update test configuration to disable conflicting maas plugins and configure WebAuthn/session settings for localhost testing.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""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}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 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})
|
||||
Reference in New Issue
Block a user