canina/frontend/application/lib/services/authService.ts
parsaaghayi bd49fbc4a0
Some checks failed
Deploy Canina / deploy (push) Successful in 2m25s
E2E Playwright Tests / Run Full E2E & Security Suites (push) Failing after 7s
fix(security): remediate audit vulnerabilities SEC-001 through SEC-023
2026-09-23 23:00:25 +03:30

200 lines
5.8 KiB
TypeScript

import api from './api';
export interface User {
id: string;
firstName: string;
lastName: string;
email: string;
mobile: string | null;
role: string;
walletBalance: number;
charityDonationTotal: number;
hasPassword?: boolean;
password?: string;
currentPassword?: string;
createdAt: string;
updatedAt: string;
pets?: unknown[];
orders?: unknown[];
}
export interface AuthResponse {
success: boolean;
data: {
user: User;
accessToken: string;
refreshToken?: string;
};
}
interface ApiErr {
response?: {
status?: number;
data?: {
message?: string | string[];
};
};
}
export class AuthService {
private static instance: AuthService;
private constructor() {}
public static getInstance(): AuthService {
if (!AuthService.instance) {
AuthService.instance = new AuthService();
}
return AuthService.instance;
}
/**
* Send SMS verification code (OTP)
*/
public async sendOtp(phoneNumber: string): Promise<{ success: boolean; message: string }> {
try {
const response = await api.post('/auth/send-otp', { phoneNumber });
return response.data;
} catch (error) {
const err = error as ApiErr;
const message = err.response?.data?.message || 'خطا در ارسال کد تایید';
throw new Error(Array.isArray(message) ? message[0] : message);
}
}
/**
* Verify SMS verification code and login/register
*/
public async verifyOtp(phoneNumber: string, code: string): Promise<AuthResponse> {
try {
const response = await api.post('/auth/verify-otp', { phoneNumber, code });
const { data } = response.data;
if (data?.accessToken) {
localStorage.setItem('accessToken', data.accessToken);
}
if (data?.refreshToken) {
localStorage.setItem('refreshToken', data.refreshToken);
}
return response.data;
} catch (error) {
const err = error as ApiErr;
const message = err.response?.data?.message || 'کد تایید نامعتبر است';
throw new Error(Array.isArray(message) ? message[0] : message);
}
}
/**
* Register with email/mobile and password
*/
public async register(data: Record<string, unknown>): Promise<AuthResponse> {
try {
const response = await api.post('/auth/register', data);
const { data: resData } = response.data;
if (resData?.accessToken) {
localStorage.setItem('accessToken', resData.accessToken);
}
if (resData?.refreshToken) {
localStorage.setItem('refreshToken', resData.refreshToken);
}
return response.data;
} catch (error) {
const err = error as ApiErr;
const message = err.response?.data?.message || 'خطا در ثبت‌نام';
throw new Error(Array.isArray(message) ? message[0] : message);
}
}
/**
* Login with mobile and password
*/
public async login(mobile: string, password: string): Promise<AuthResponse> {
try {
const response = await api.post('/auth/login', { mobile, password });
const { data } = response.data;
if (data?.accessToken) {
localStorage.setItem('accessToken', data.accessToken);
}
if (data?.refreshToken) {
localStorage.setItem('refreshToken', data.refreshToken);
}
return response.data;
} catch (error) {
const err = error as ApiErr;
const message = err.response?.data?.message || 'نام کاربری یا رمز عبور اشتباه است';
throw new Error(Array.isArray(message) ? message[0] : message);
}
}
/**
* Refresh auth tokens using the stored refresh token
*/
public async refreshAuthToken(): Promise<string | null> {
try {
const refreshToken = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
if (!refreshToken) return null;
const response = await api.post('/auth/refresh-token', { refreshToken }, { hideErrorToast: true } as unknown as import('axios').AxiosRequestConfig);
const { data } = response.data;
if (data?.accessToken) {
localStorage.setItem('accessToken', data.accessToken);
}
if (data?.refreshToken) {
localStorage.setItem('refreshToken', data.refreshToken);
}
return data?.accessToken || null;
} catch {
this.logout();
return null;
}
}
/**
* Get current user profile
*/
public async getProfile(): Promise<User | null> {
try {
const response = await api.get('/users/profile', { hideErrorToast: true } as unknown as import('axios').AxiosRequestConfig);
return response.data;
} catch (error) {
const err = error as ApiErr;
if (err.response?.status === 401 || err.response?.status === 403) {
return null;
}
const message = err.response?.data?.message || 'خطا در دریافت اطلاعات کاربری';
throw new Error(Array.isArray(message) ? message[0] : message);
}
}
/**
* Update current user profile
*/
public async updateProfile(profileData: Partial<User>): Promise<User> {
try {
const response = await api.patch('/users/profile', profileData);
return response.data;
} catch (error) {
const err = error as ApiErr;
const message = err.response?.data?.message || 'خطا در ویرایش اطلاعات کاربری';
throw new Error(Array.isArray(message) ? message[0] : message);
}
}
/**
* Logout user and clear tokens
*/
public logout(): void {
if (typeof window !== 'undefined') {
try {
const token = localStorage.getItem('accessToken');
if (token) {
api.post('/auth/logout').catch(() => {});
}
} catch {}
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
}
}
}
export const authService = AuthService.getInstance();