website/lib/actions/auth.ts

290 lines
7.7 KiB
TypeScript

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<T>(response: Response): Promise<T> {
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;
}
// Register a new user
export async function registerUser(input: RegisterInput): Promise<AuthResponse> {
const response = await fetch(API_ENDPOINTS.auth.register, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
return handleResponse<AuthResponse>(response);
}
// Verify OTP
export async function verifyOtp(input: VerifyOtpInput): Promise<AuthResponse> {
const response = await fetch(API_ENDPOINTS.auth.verifyOtp, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
const data = await handleResponse<AuthResponse>(response);
// Normalize response: 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: map isVerified to is_verified if needed
if (data.user) {
if (data.user.isVerified !== undefined && data.user.is_verified === undefined) {
data.user.is_verified = data.user.isVerified;
}
}
return data;
}
// Login user
export async function loginUser(input: LoginInput): Promise<AuthResponse> {
const response = await fetch(API_ENDPOINTS.auth.login, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
const data = await handleResponse<AuthResponse>(response);
// Normalize response: 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: map isVerified to is_verified if needed
if (data.user) {
if (data.user.isVerified !== undefined && data.user.is_verified === undefined) {
data.user.is_verified = data.user.isVerified;
}
}
return data;
}
// Resend OTP
export async function resendOtp(input: ResendOtpInput): Promise<AuthResponse> {
const response = await fetch(API_ENDPOINTS.auth.resendOtp, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
return handleResponse<AuthResponse>(response);
}
// Forgot password
export async function forgotPassword(input: ForgotPasswordInput): Promise<AuthResponse> {
const response = await fetch(API_ENDPOINTS.auth.forgotPassword, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
return handleResponse<AuthResponse>(response);
}
// Verify password reset OTP
export async function verifyPasswordResetOtp(
input: VerifyPasswordResetOtpInput
): Promise<AuthResponse> {
const response = await fetch(API_ENDPOINTS.auth.verifyPasswordResetOtp, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
return handleResponse<AuthResponse>(response);
}
// Reset password
export async function resetPassword(input: ResetPasswordInput): Promise<AuthResponse> {
const response = await fetch(API_ENDPOINTS.auth.resetPassword, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
return handleResponse<AuthResponse>(response);
}
// Refresh access token
export async function refreshToken(input: TokenRefreshInput): Promise<AuthTokens> {
const response = await fetch(API_ENDPOINTS.auth.tokenRefresh, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
return handleResponse<AuthTokens>(response);
}
// 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"),
};
}
// 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));
// Also set cookie for middleware
document.cookie = `auth_user=${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) {
return { Authorization: `Bearer ${tokens.access}` };
}
return {};
}