import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios' import type { RegisterRequest, UpdateAccountRequest, ChangePasswordRequest, ForgotPasswordRequest, ResetPasswordRequest, SendVerificationRequest, VerifyEmailRequest, AuthenticationAccount, PasswordLoginRequest, EmailTokenSendRequest, EmailTokenVerifyRequest, OTPSendRequest, OTPVerifyRequest, SMSSendRequest, SMSVerifyRequest, GenerateTokenRequest, RedeemTokenRequest, SSOLoginRequest, LoginResponse, RegisterResponse, LogoutResponse, GetMeResponse, UpdateMeResponse, DeleteMeResponse, ChangePasswordResponse, ForgotPasswordResponse, ResetPasswordResponse, VerifyResetTokenResponse, SendVerificationResponse, VerifyEmailResponse, RefreshSessionResponse, GetSessionsResponse, DeleteSessionResponse, DeleteAllSessionsResponse, CleanupSessionsResponse, GetMethodsResponse, GetAuthStatusResponse, SendEmailTokenResponse, VerifyEmailTokenResponse, SendOTPResponse, VerifyOTPResponse, SendSMSResponse, VerifySMSResponse, GenerateLoginTokenResponse, RedeemLoginTokenResponse, LegacySSOLoginResponse, SSOProvider, SSOInitiateRequest, SSOCallbackRequest, SSOLinkRequest, InitiateSSOResponse, CallbackSSOResponse, LinkSSOResponse, UnlinkSSOResponse, GetTenantsResponse, GetTenantResponse, CreateTenantResponse, UpdateTenantResponse, DeleteTenantResponse, GetTenantMembersResponse, AddTenantMemberResponse, UpdateTenantMemberResponse, DeleteTenantMemberResponse, GetTenantRolesResponse, CreateInvitationResponse, GetInvitationResponse, AcceptInvitationResponse, CreateTenantRequest, UpdateTenantRequest, AddMemberRequest, UpdateMemberRequest, CreateInvitationRequest, AcceptInvitationRequest, } from './types' import { createAxiosInstance } from './utils' export class AuthApi { private api: AxiosInstance private currentTenantId: string | null = null private externalAxiosInstances: Set = new Set() constructor(baseURL: string = '') { this.api = createAxiosInstance(baseURL) this.setupInterceptors() } /** * Register an external axios instance so that tenant headers * are automatically applied to it when setTenantId() is called. */ registerAxios(instance: AxiosInstance) { this.externalAxiosInstances.add(instance) // Apply current tenant immediately if already set if (this.currentTenantId !== null) { instance.defaults.headers.common['X-Tenant-ID'] = this.currentTenantId } } /** * Set the current tenant ID for multi-tenant requests. * Also updates the header on any externally registered axios instances. */ setTenantId(tenantId: string | null) { this.currentTenantId = tenantId // Update all registered external axios instances for (const instance of this.externalAxiosInstances) { if (tenantId !== null) { instance.defaults.headers.common['X-Tenant-ID'] = tenantId } else { delete instance.defaults.headers.common['X-Tenant-ID'] } } } /** * Get the current tenant ID */ getTenantId(): string | null { return this.currentTenantId } private setupInterceptors() { this.api.interceptors.request.use((config: InternalAxiosRequestConfig) => { // Handle password reset token from URL const urlParams = new URLSearchParams(window.location.search) const resetToken = urlParams.get('token') if (resetToken !== null) { config.headers['X-Reset-Token'] = resetToken } // Add tenant ID header if set if (this.currentTenantId !== null) { config.headers['X-Tenant-ID'] = this.currentTenantId } return config }) } // ============================================ // Authentication Methods // ============================================ /** * Get authentication status */ async getAuthStatus(): Promise { return this.api.get('authentication/status') } /** * Get available authentication methods */ async getAuthMethods(): Promise { return this.api.get('authentication/methods') } /** * Register a new account */ async register(data: RegisterRequest): Promise { return this.api.post('authentication/register', { ...data, email: data.email.toLowerCase(), }) } /** * Login with password */ async login(data: PasswordLoginRequest): Promise { return this.api.post('authentication/login/password', { ...data, email: data.email.toLowerCase(), }) } /** * Send email token to user */ async sendEmailToken(data: EmailTokenSendRequest): Promise { return this.api.post('authentication/login/email/send', data) } /** * Verify email token and login */ async verifyEmailToken(data: EmailTokenVerifyRequest): Promise { return this.api.post('authentication/login/email/verify', data) } /** * Send SMS verification code to phone number */ async sendSMS(data: SMSSendRequest): Promise { return this.api.post('authentication/login/sms/send', data) } /** * Verify SMS code and login */ async verifySMS(data: SMSVerifyRequest): Promise { return this.api.post('authentication/login/sms/verify', data) } /** * @deprecated Use sendSMS() instead */ async sendOTP(data: OTPSendRequest): Promise { return this.sendSMS(data) } /** * @deprecated Use verifySMS() instead */ async verifyOTP(data: OTPVerifyRequest): Promise { return this.verifySMS(data) } // ============================================ // Login Token Methods // ============================================ /** * Generate a one-time login token. * Omit target_identity_id to generate for yourself (e.g. desktop → mobile). * Provide target_identity_id to generate for another user (admin only). */ async generateLoginToken(data: GenerateTokenRequest = {}): Promise { return this.api.post('authentication/login-token/generate', data) } /** * Redeem a one-time login token (no authentication required). */ async redeemLoginToken(data: RedeemTokenRequest): Promise { return this.api.post('authentication/login-token/redeem', data) } /** * Check if a login token is still valid (no auth required). */ async getLoginTokenStatus(token: string): Promise { return this.api.get(`authentication/login-token/${token}/status`) } /** * Login with SSO provider (legacy endpoint without PKCE) */ async loginWithSSO(provider: SSOProvider, data: SSOLoginRequest): Promise { return this.api.post(`authentication/login/sso/${provider}`, data) } /** * Logout and clear session */ async logout(): Promise { return this.api.post('authentication/logout', {}) } /** * Refresh current session */ async refreshSession(): Promise { return this.api.post('authentication/refresh', {}) } // ============================================ // SSO Authentication Methods // ============================================ /** * Initiate SSO login flow * Returns authorization URL to redirect user to */ async initiateSSO(provider: SSOProvider, data: SSOInitiateRequest): Promise { return this.api.post(`authentication/sso/${provider}/initiate`, data) } /** * Complete SSO login after callback from provider */ async ssoCallback(provider: SSOProvider, data: SSOCallbackRequest): Promise { return this.api.post(`authentication/sso/${provider}/callback`, data) } /** * Link an SSO provider to existing account */ async linkSSOProvider(provider: SSOProvider, data: SSOLinkRequest): Promise { return this.api.post(`authentication/sso/${provider}/link`, data) } /** * Unlink an SSO provider from account */ async unlinkSSOProvider(provider: SSOProvider): Promise { return this.api.delete(`authentication/sso/${provider}/unlink`) } // ============================================ // Current User (Me) Methods // ============================================ /** * Get current user account info */ async getCurrentUser(): Promise { return this.api.get('authentication/me') } /** * Update current user profile */ async updateCurrentUser(data: UpdateAccountRequest): Promise { return this.api.patch('authentication/me', data) } /** * Delete current user account */ async deleteCurrentUser(): Promise { return this.api.delete('authentication/me') } // ============================================ // Password Management // ============================================ /** * Change password (requires current password) */ async changePassword(data: ChangePasswordRequest): Promise { return this.api.post('authentication/password/change', data) } /** * Initiate forgot password flow */ async forgotPassword(data: ForgotPasswordRequest): Promise { return this.api.post('authentication/password/forgot', { ...data, email: data.email.toLowerCase(), }) } /** * Verify password reset token */ async verifyResetToken(token: string): Promise { return this.api.get(`authentication/password/verify-reset-token/${token}`) } /** * Reset password with token */ async resetPassword(data: ResetPasswordRequest): Promise { return this.api.post('authentication/password/reset', data) } // ============================================ // Email Verification // ============================================ /** * Send email verification */ async sendVerification( data: SendVerificationRequest = {}, user?: AuthenticationAccount ): Promise { return this.api.post('authentication/verify/send', data, { params: user ? { user } : undefined, }) } /** * Verify email with token */ async verifyEmail(data: VerifyEmailRequest): Promise { return this.api.post('authentication/verify/email', data) } // ============================================ // Session Management // ============================================ /** * Get active sessions for current identity */ async getSessions(): Promise { return this.api.get('authentication/sessions') } /** * Revoke a specific session */ async revokeSession(sessionToken: string): Promise { return this.api.delete(`authentication/sessions/${sessionToken}`) } /** * Revoke all sessions for current identity */ async revokeAllSessions(): Promise { return this.api.delete('authentication/sessions') } /** * Cleanup expired sessions (admin) */ async cleanupSessions(): Promise { return this.api.post('authentication/cleanup-sessions', {}) } // ============================================ // Multi-Tenancy Methods // ============================================ /** * Get list of tenants the authenticated user belongs to */ async getTenants(): Promise { return this.api.get('tenants') } /** * Get a single tenant by ID or slug */ async getTenant(idOrSlug: string): Promise { return this.api.get(`tenants/${idOrSlug}`) } /** * Create a new tenant (caller is auto-added as admin) */ async createTenant(data: CreateTenantRequest): Promise { return this.api.post('tenants/', data) } /** * Update a tenant */ async updateTenant(tenantId: string, data: UpdateTenantRequest): Promise { return this.api.put(`tenants/${tenantId}`, data) } /** * Delete a tenant */ async deleteTenant(tenantId: string): Promise { return this.api.delete(`tenants/${tenantId}`) } // ============================================ // Tenant Member Methods // ============================================ /** * List members of a tenant */ async getTenantMembers(tenantId: string, status?: string): Promise { return this.api.get(`tenants/${tenantId}/members`, { params: status ? { status } : undefined }) } /** * Add a member to a tenant by identity_id */ async addTenantMember(tenantId: string, data: AddMemberRequest): Promise { return this.api.post(`tenants/${tenantId}/members`, data) } /** * Update a tenant member's roles/status/metadata */ async updateTenantMember(tenantId: string, identityId: string, data: UpdateMemberRequest): Promise { return this.api.put(`tenants/${tenantId}/members/${identityId}`, data) } /** * Activate a pending membership */ async activateTenantMember(tenantId: string, identityId: string): Promise { return this.api.post(`tenants/${tenantId}/members/${identityId}/activate`, {}) } /** * Remove a member from a tenant */ async removeTenantMember(tenantId: string, identityId: string): Promise { return this.api.delete(`tenants/${tenantId}/members/${identityId}`) } /** * List available roles */ async getTenantRoles(): Promise { return this.api.get('tenants/roles') } // ============================================ // Invitation Methods // ============================================ /** * Create an invitation for a new user to join a tenant * Requires X-Tenant-ID header (set via setTenantId) */ async createInvitation(data: CreateInvitationRequest): Promise { return this.api.post('invitations/', data) } /** * Get invitation metadata by token (no auth required) */ async getInvitation(token: string): Promise { return this.api.get(`invitations/${token}`) } /** * Accept an invitation — creates TenantMembership for the authenticated user */ async acceptInvitation(token: string, data?: AcceptInvitationRequest): Promise { return this.api.post(`invitations/${token}/accept`, data ?? {}) } }