259 lines
9.2 KiB
TypeScript
259 lines
9.2 KiB
TypeScript
// ================================================================
|
|
// frontend/src/api/services/auth.ts
|
|
// ================================================================
|
|
/**
|
|
* Auth Service - Atomic Registration Architecture
|
|
*
|
|
* Backend API structure (3-tier architecture):
|
|
* - OPERATIONS: auth_operations.py, onboarding_progress.py
|
|
*
|
|
* Last Updated: 2025-01-14
|
|
* Status: Complete - SetupIntent-first registration flow with 3DS support
|
|
*/
|
|
import { apiClient } from '../client';
|
|
import {
|
|
UserRegistration,
|
|
UserLogin,
|
|
TokenResponse,
|
|
RefreshTokenRequest,
|
|
PasswordChange,
|
|
PasswordReset,
|
|
UserResponse,
|
|
UserUpdate,
|
|
TokenVerificationResponse,
|
|
AuthHealthResponse,
|
|
RegistrationStartResponse,
|
|
RegistrationCompletionResponse,
|
|
RegistrationVerification,
|
|
} from '../types/auth';
|
|
|
|
export class AuthService {
|
|
private readonly baseUrl = '/auth';
|
|
|
|
// User Profile (authenticated)
|
|
// Backend: services/auth/app/api/users.py
|
|
// ===================================================================
|
|
|
|
async getProfile(): Promise<UserResponse> {
|
|
// Get current user ID from auth store
|
|
const { useAuthStore } = await import('../../stores/auth.store');
|
|
const user = useAuthStore.getState().user;
|
|
|
|
if (!user?.id) {
|
|
throw new Error('User not authenticated or user ID not available');
|
|
}
|
|
|
|
return apiClient.get<UserResponse>(`${this.baseUrl}/users/${user.id}`);
|
|
}
|
|
|
|
async updateProfile(updateData: UserUpdate): Promise<UserResponse> {
|
|
// Get current user ID from auth store
|
|
const { useAuthStore } = await import('../../stores/auth.store');
|
|
const user = useAuthStore.getState().user;
|
|
|
|
if (!user?.id) {
|
|
throw new Error('User not authenticated or user ID not available');
|
|
}
|
|
|
|
return apiClient.put<UserResponse>(`${this.baseUrl}/users/${user.id}`, updateData);
|
|
}
|
|
|
|
// ATOMIC REGISTRATION: SetupIntent-First Approach
|
|
// These methods implement the secure registration flow with 3DS support
|
|
// ===================================================================
|
|
|
|
/**
|
|
* Start secure registration flow with SetupIntent-first approach
|
|
* This is the FIRST step in the atomic registration flow
|
|
* Backend: services/auth/app/api/auth_operations.py:start_registration()
|
|
*/
|
|
async startRegistration(userData: UserRegistration): Promise<RegistrationStartResponse> {
|
|
return apiClient.post<RegistrationStartResponse>(`${this.baseUrl}/start-registration`, userData);
|
|
}
|
|
|
|
/**
|
|
* Complete registration after 3DS verification
|
|
* This is the SECOND step in the atomic registration flow
|
|
* Backend: services/auth/app/api/auth_operations.py:complete_registration()
|
|
*/
|
|
async completeRegistration(verificationData: RegistrationVerification): Promise<RegistrationCompletionResponse> {
|
|
return apiClient.post<RegistrationCompletionResponse>(`${this.baseUrl}/complete-registration`, verificationData);
|
|
}
|
|
|
|
// ===================================================================
|
|
// OPERATIONS: Authentication
|
|
// Backend: services/auth/app/api/auth_operations.py
|
|
// ===================================================================
|
|
|
|
async login(loginData: UserLogin): Promise<TokenResponse> {
|
|
return apiClient.post<TokenResponse>(`${this.baseUrl}/login`, loginData);
|
|
}
|
|
|
|
async refreshToken(refreshToken: string): Promise<TokenResponse> {
|
|
const refreshData: RefreshTokenRequest = { refresh_token: refreshToken };
|
|
return apiClient.post<TokenResponse>(`${this.baseUrl}/refresh`, refreshData);
|
|
}
|
|
|
|
async verifyToken(token?: string): Promise<TokenVerificationResponse> {
|
|
// If token is provided, temporarily set it; otherwise use current token
|
|
const currentToken = apiClient.getAuthToken();
|
|
if (token && token !== currentToken) {
|
|
apiClient.setAuthToken(token);
|
|
}
|
|
|
|
const response = await apiClient.post<TokenVerificationResponse>(`${this.baseUrl}/verify`);
|
|
|
|
// Restore original token if we temporarily changed it
|
|
if (token && token !== currentToken) {
|
|
apiClient.setAuthToken(currentToken);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
async logout(refreshToken: string): Promise<{ message: string }> {
|
|
const refreshData: RefreshTokenRequest = { refresh_token: refreshToken };
|
|
return apiClient.post<{ message: string }>(`${this.baseUrl}/logout`, refreshData);
|
|
}
|
|
|
|
async changePassword(passwordData: PasswordChange): Promise<{ message: string }> {
|
|
return apiClient.post<{ message: string }>(`${this.baseUrl}/change-password`, passwordData);
|
|
}
|
|
|
|
async requestPasswordReset(email: string): Promise<{ message: string }> {
|
|
return apiClient.post<{ message: string }>(`${this.baseUrl}/password/reset-request`, { email });
|
|
}
|
|
|
|
async resetPasswordWithToken(token: string, newPassword: string): Promise<{ message: string }> {
|
|
return apiClient.post<{ message: string }>(`${this.baseUrl}/password/reset`, {
|
|
token,
|
|
new_password: newPassword
|
|
});
|
|
}
|
|
|
|
// ===================================================================
|
|
// OPERATIONS: Email Verification
|
|
// Backend: services/auth/app/api/auth_operations.py
|
|
// ===================================================================
|
|
|
|
async verifyEmail(
|
|
userId: string,
|
|
verificationToken: string
|
|
): Promise<{ message: string }> {
|
|
return apiClient.post<{ message: string }>(`${this.baseUrl}/verify-email`, {
|
|
user_id: userId,
|
|
verification_token: verificationToken,
|
|
});
|
|
}
|
|
|
|
// ===================================================================
|
|
// Account Management (self-service)
|
|
// Backend: services/auth/app/api/account_deletion.py
|
|
// ===================================================================
|
|
|
|
async deleteAccount(confirmEmail: string, password: string, reason?: string): Promise<{ message: string; deletion_date: string }> {
|
|
return apiClient.delete(`${this.baseUrl}/me/account`, {
|
|
data: {
|
|
confirm_email: confirmEmail,
|
|
password: password,
|
|
reason: reason || ''
|
|
}
|
|
});
|
|
}
|
|
|
|
async getAccountDeletionInfo(): Promise<Record<string, unknown>> {
|
|
return apiClient.get(`${this.baseUrl}/me/account/deletion-info`);
|
|
}
|
|
|
|
// ===================================================================
|
|
// GDPR Consent Management
|
|
// Backend: services/auth/app/api/consent.py
|
|
// ===================================================================
|
|
|
|
async recordConsent(consentData: {
|
|
terms_accepted: boolean;
|
|
privacy_accepted: boolean;
|
|
marketing_consent?: boolean;
|
|
analytics_consent?: boolean;
|
|
consent_method: string;
|
|
consent_version?: string;
|
|
}): Promise<Record<string, unknown>> {
|
|
return apiClient.post(`${this.baseUrl}/me/consent`, consentData);
|
|
}
|
|
|
|
async getCurrentConsent(): Promise<Record<string, unknown>> {
|
|
return apiClient.get(`${this.baseUrl}/me/consent/current`);
|
|
}
|
|
|
|
async getConsentHistory(): Promise<Record<string, unknown>[]> {
|
|
return apiClient.get(`${this.baseUrl}/me/consent/history`);
|
|
}
|
|
|
|
async updateConsent(consentData: {
|
|
terms_accepted: boolean;
|
|
privacy_accepted: boolean;
|
|
marketing_consent?: boolean;
|
|
analytics_consent?: boolean;
|
|
consent_method: string;
|
|
consent_version?: string;
|
|
}): Promise<Record<string, unknown>> {
|
|
return apiClient.put(`${this.baseUrl}/me/consent`, consentData);
|
|
}
|
|
|
|
async withdrawConsent(): Promise<{ message: string; withdrawn_count: number }> {
|
|
return apiClient.post(`${this.baseUrl}/me/consent/withdraw`);
|
|
}
|
|
|
|
// ===================================================================
|
|
// Data Export (GDPR)
|
|
// Backend: services/auth/app/api/data_export.py
|
|
// ===================================================================
|
|
|
|
async exportMyData(): Promise<Record<string, unknown>> {
|
|
return apiClient.get(`${this.baseUrl}/me/export`);
|
|
}
|
|
|
|
async getExportSummary(): Promise<Record<string, unknown>> {
|
|
return apiClient.get(`${this.baseUrl}/me/export/summary`);
|
|
}
|
|
|
|
// ===================================================================
|
|
// Onboarding Progress
|
|
// Backend: services/auth/app/api/onboarding_progress.py
|
|
// ===================================================================
|
|
|
|
async getOnboardingProgress(): Promise<Record<string, unknown>> {
|
|
return apiClient.get(`${this.baseUrl}/me/onboarding/progress`);
|
|
}
|
|
|
|
async updateOnboardingStep(stepName: string, completed: boolean, data?: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
return apiClient.put(`${this.baseUrl}/me/onboarding/step`, {
|
|
step_name: stepName,
|
|
completed: completed,
|
|
data: data
|
|
});
|
|
}
|
|
|
|
async getNextOnboardingStep(): Promise<{ step: string }> {
|
|
return apiClient.get(`${this.baseUrl}/me/onboarding/next-step`);
|
|
}
|
|
|
|
async canAccessOnboardingStep(stepName: string): Promise<{ can_access: boolean }> {
|
|
return apiClient.get(`${this.baseUrl}/me/onboarding/can-access/${stepName}`);
|
|
}
|
|
|
|
async completeOnboarding(): Promise<{ success: boolean; message: string }> {
|
|
return apiClient.post(`${this.baseUrl}/me/onboarding/complete`);
|
|
}
|
|
|
|
// ===================================================================
|
|
// Health Check
|
|
// ===================================================================
|
|
|
|
async healthCheck(): Promise<AuthHealthResponse> {
|
|
return apiClient.get<AuthHealthResponse>(`${this.baseUrl}/health`);
|
|
}
|
|
}
|
|
|
|
export const authService = new AuthService();
|