import { API_ENDPOINTS } from "@/lib/api_urls"; import type { RegisterInput, VerifyOtpInput, LoginInput, ResendOtpInput, ForgotPasswordInput, VerifyPasswordResetOtpInput, ResetPasswordInput, TokenRefreshInput, } from "@/lib/schema/auth"; import type { AuthResponse, ApiError, AuthTokens, User } from "@/lib/models/auth"; // Helper function to extract error message from API response function extractErrorMessage(error: ApiError): string { // Check for main error messages if (error.detail) { // Handle both string and array formats if (Array.isArray(error.detail)) { return error.detail.join(", "); } return String(error.detail); } if (error.message) { if (Array.isArray(error.message)) { return error.message.join(", "); } return String(error.message); } if (error.error) { if (Array.isArray(error.error)) { return error.error.join(", "); } return String(error.error); } // Check for field-specific errors (common in Django REST Framework) const fieldErrors: string[] = []; Object.keys(error).forEach((key) => { if (key !== "detail" && key !== "message" && key !== "error") { const fieldError = error[key]; if (Array.isArray(fieldError)) { fieldErrors.push(`${key}: ${fieldError.join(", ")}`); } else if (typeof fieldError === "string") { fieldErrors.push(`${key}: ${fieldError}`); } } }); if (fieldErrors.length > 0) { return fieldErrors.join(". "); } return "An error occurred"; } // Helper function to handle API responses async function handleResponse(response: Response): Promise { let data: any; try { data = await response.json(); } catch { // If response is not JSON, use status text throw new Error(response.statusText || "An error occurred"); } if (!response.ok) { const error: ApiError = data; const errorMessage = extractErrorMessage(error); throw new Error(errorMessage); } return data as T; } // Helper function to normalize auth response function normalizeAuthResponse(data: AuthResponse): AuthResponse { // Normalize tokens: if tokens are at root level, move them to tokens object if (data.access && data.refresh && !data.tokens) { data.tokens = { access: data.access, refresh: data.refresh, }; } // Normalize user: only map isVerified to is_verified if needed if (data.user) { const user = data.user as any; if (user.isVerified !== undefined && user.is_verified === undefined) { user.is_verified = user.isVerified; } data.user = user; } return data; } // Register a new user export async function registerUser(input: RegisterInput): Promise { const response = await fetch(API_ENDPOINTS.auth.register, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(input), }); // Handle response - check if it's a 500 error that might indicate OTP sending failure // but user registration might have succeeded if (!response.ok && response.status === 500) { try { const data = await response.json(); // If the error message mentions OTP or email sending, it might be a partial success const errorMessage = extractErrorMessage(data); if (errorMessage.toLowerCase().includes("otp") || errorMessage.toLowerCase().includes("email") || errorMessage.toLowerCase().includes("send") || errorMessage.toLowerCase().includes("ssl") || errorMessage.toLowerCase().includes("certificate")) { // Return a partial success response - user might be created, allow OTP resend // This allows the user to proceed to OTP verification and use resend OTP return { message: "User registered, but OTP email could not be sent. Please use resend OTP.", } as AuthResponse; } } catch { // If we can't parse the error, continue to normal error handling } } return handleResponse(response); } // Verify OTP export async function verifyOtp(input: VerifyOtpInput): Promise { const response = await fetch(API_ENDPOINTS.auth.verifyOtp, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(input), }); const data = await handleResponse(response); return normalizeAuthResponse(data); } // Login user export async function loginUser(input: LoginInput): Promise { const response = await fetch(API_ENDPOINTS.auth.login, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(input), }); const data = await handleResponse(response); return normalizeAuthResponse(data); } // Resend OTP export async function resendOtp(input: ResendOtpInput): Promise { const response = await fetch(API_ENDPOINTS.auth.resendOtp, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(input), }); return handleResponse(response); } // Forgot password export async function forgotPassword(input: ForgotPasswordInput): Promise { const response = await fetch(API_ENDPOINTS.auth.forgotPassword, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(input), }); return handleResponse(response); } // Verify password reset OTP export async function verifyPasswordResetOtp( input: VerifyPasswordResetOtpInput ): Promise { const response = await fetch(API_ENDPOINTS.auth.verifyPasswordResetOtp, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(input), }); return handleResponse(response); } // Reset password export async function resetPassword(input: ResetPasswordInput): Promise { const response = await fetch(API_ENDPOINTS.auth.resetPassword, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(input), }); return handleResponse(response); } // Refresh access token export async function refreshToken(input: TokenRefreshInput): Promise { const response = await fetch(API_ENDPOINTS.auth.tokenRefresh, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(input), }); return handleResponse(response); } // Decode JWT token to check expiration function decodeJWT(token: string): { exp?: number; [key: string]: any } | null { try { const parts = token.split("."); if (parts.length !== 3) return null; const payload = parts[1]; const decoded = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/"))); return decoded; } catch (error) { return null; } } // Check if token is expired export function isTokenExpired(token: string | null): boolean { if (!token) return true; const decoded = decodeJWT(token); if (!decoded || !decoded.exp) return true; // exp is in seconds, Date.now() is in milliseconds const expirationTime = decoded.exp * 1000; const currentTime = Date.now(); // Consider token expired if it expires within the next 5 seconds (buffer) return currentTime >= (expirationTime - 5000); } // Get stored tokens export function getStoredTokens(): { access: string | null; refresh: string | null } { if (typeof window === "undefined") { return { access: null, refresh: null }; } return { access: localStorage.getItem("auth_access_token"), refresh: localStorage.getItem("auth_refresh_token"), }; } // Check if user has valid authentication export function hasValidAuth(): boolean { const tokens = getStoredTokens(); if (!tokens.access) return false; return !isTokenExpired(tokens.access); } // Store tokens export function storeTokens(tokens: AuthTokens): void { if (typeof window === "undefined") return; localStorage.setItem("auth_access_token", tokens.access); localStorage.setItem("auth_refresh_token", tokens.refresh); // Also set cookies for middleware document.cookie = `auth_access_token=${tokens.access}; path=/; max-age=${7 * 24 * 60 * 60}; SameSite=Lax`; document.cookie = `auth_refresh_token=${tokens.refresh}; path=/; max-age=${30 * 24 * 60 * 60}; SameSite=Lax`; } // Store user export function storeUser(user: User): void { if (typeof window === "undefined") return; localStorage.setItem("auth_user", JSON.stringify(user)); document.cookie = `auth_user=${encodeURIComponent(JSON.stringify(user))}; path=/; max-age=${7 * 24 * 60 * 60}; SameSite=Lax`; } // Get stored user export function getStoredUser(): User | null { if (typeof window === "undefined") return null; const userStr = localStorage.getItem("auth_user"); if (!userStr) return null; try { return JSON.parse(userStr) as User; } catch { return null; } } // Clear auth data export function clearAuthData(): void { if (typeof window === "undefined") return; localStorage.removeItem("auth_access_token"); localStorage.removeItem("auth_refresh_token"); localStorage.removeItem("auth_user"); // Also clear cookies document.cookie = "auth_access_token=; path=/; max-age=0"; document.cookie = "auth_refresh_token=; path=/; max-age=0"; document.cookie = "auth_user=; path=/; max-age=0"; } // Get auth header for API requests export function getAuthHeader(): { Authorization: string } | {} { const tokens = getStoredTokens(); if (tokens.access && !isTokenExpired(tokens.access)) { return { Authorization: `Bearer ${tokens.access}` }; } return {}; } // Get all users (Admin only) export async function getAllUsers(): Promise { const tokens = getStoredTokens(); if (!tokens.access) { throw new Error("Authentication required."); } const response = await fetch(API_ENDPOINTS.auth.allUsers, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${tokens.access}`, }, }); const data = await response.json(); if (!response.ok) { const errorMessage = extractErrorMessage(data); throw new Error(errorMessage); } // Handle different response formats if (data.users) { return data.users; } if (Array.isArray(data)) { return data; } return []; }