/** * Authentication API Service * * Handles all authentication-related API calls: * - Pre-login (password verification + send verification code) * - Resend verification code * - Verify login (code verification + get tokens) * - Google login */ import { ApiResponse, ApisauceInstance, create } from "apisauce" import Config from "@/config" import { getGeneralApiProblem, GeneralApiProblem } from "./apiProblem" import type { BindNewEmailRequest, BindNewEmailResponse, ChangePasswordRequest, ChangePasswordResponse, ForgotPasswordRequest, ForgotPasswordResponse, GetLoginHistoryParams, GetLoginHistoryResponse, GetProfileResponse, GetSessionCountResponse, GetSessionsParams, GetSessionsResponse, GoogleLoginRequest, GoogleLoginResponse, LogoutResponse, PreLoginRequest, PreLoginResponse, PreRegisterRequest, PreRegisterResponse, RefreshTokenRequest, RefreshTokenResponse, ResendCodeRequest, ResendCodeResponse, ResendForgotPasswordRequest, ResendForgotPasswordResponse, ResendRegisterCodeRequest, ResendRegisterCodeResponse, ResetPasswordRequest, ResetPasswordResponse, RevokeOtherSessionsResponse, RevokeSessionResponse, SendEmailCodeRequest, SendEmailCodeResponse, UpdateProfileRequest, UpdateProfileResponse, VerifyCurrentEmailRequest, VerifyCurrentEmailResponse, VerifyLoginRequest, VerifyLoginResponse, VerifyRegisterRequest, VerifyRegisterResponse, } from "./authTypes" const AUTH_API_CONFIG = { url: Config.AUTH_API_URL || "https://auth.upay01.com", timeout: 15000, } /** * Auth API class for handling authentication requests */ export class AuthApi { apisauce: ApisauceInstance constructor() { this.apisauce = create({ baseURL: AUTH_API_CONFIG.url, timeout: AUTH_API_CONFIG.timeout, headers: { "Accept": "application/json", "Content-Type": "application/json", }, }) } /** * Pre-login: Verify password and send verification code */ async preLogin( email: string, password: string, language?: string, ): Promise<{ kind: "ok"; data: PreLoginResponse } | GeneralApiProblem> { const payload: PreLoginRequest = { email, password } if (language) payload.language = language const response: ApiResponse = await this.apisauce.post( "/api/auth/pre-login", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth preLogin error: ${e.message}`) } return { kind: "bad-data" } } } /** * Resend verification code */ async resendCode( email: string, language?: string, ): Promise<{ kind: "ok"; data: ResendCodeResponse } | GeneralApiProblem> { const payload: ResendCodeRequest = { email } if (language) payload.language = language const response: ApiResponse = await this.apisauce.post( "/api/auth/pre-login/resend", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth resendCode error: ${e.message}`) } return { kind: "bad-data" } } } /** * Verify login: Verify code and get tokens */ async verifyLogin( email: string, code: string, ): Promise<{ kind: "ok"; data: VerifyLoginResponse } | GeneralApiProblem> { const payload: VerifyLoginRequest = { email, code } const response: ApiResponse = await this.apisauce.post( "/api/auth/login/verify", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth verifyLogin error: ${e.message}`) } return { kind: "bad-data" } } } /** * Google login */ async googleLogin( idToken: string, referralCode?: string, ): Promise<{ kind: "ok"; data: GoogleLoginResponse } | GeneralApiProblem> { const payload: GoogleLoginRequest = { idToken } if (referralCode) payload.referralCode = referralCode const response: ApiResponse = await this.apisauce.post( "/api/auth/google-login", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth googleLogin error: ${e.message}`) } return { kind: "bad-data" } } } /** * Pre-register: Validate data and send verification code */ async preRegister( username: string, password: string, email: string, nickname?: string, referralCode?: string, language?: string, ): Promise<{ kind: "ok"; data: PreRegisterResponse } | GeneralApiProblem> { const payload: PreRegisterRequest = { username, password, email } if (nickname) payload.nickname = nickname if (referralCode) payload.referralCode = referralCode if (language) payload.language = language const response: ApiResponse = await this.apisauce.post( "/api/auth/pre-register", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth preRegister error: ${e.message}`) } return { kind: "bad-data" } } } /** * Resend register verification code */ async resendRegisterCode( email: string, language?: string, ): Promise<{ kind: "ok"; data: ResendRegisterCodeResponse } | GeneralApiProblem> { const payload: ResendRegisterCodeRequest = { email } if (language) payload.language = language const response: ApiResponse = await this.apisauce.post( "/api/auth/pre-register/resend", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth resendRegisterCode error: ${e.message}`) } return { kind: "bad-data" } } } /** * Verify register: Verify code and create user */ async verifyRegister( email: string, code: string, ): Promise<{ kind: "ok"; data: VerifyRegisterResponse } | GeneralApiProblem> { const payload: VerifyRegisterRequest = { email, code } const response: ApiResponse = await this.apisauce.post( "/api/auth/register/verify", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth verifyRegister error: ${e.message}`) } return { kind: "bad-data" } } } /** * Forgot password: Send verification code */ async forgotPassword( email: string, language?: string, ): Promise<{ kind: "ok"; data: ForgotPasswordResponse } | GeneralApiProblem> { const payload: ForgotPasswordRequest = { email } if (language) payload.language = language const response: ApiResponse = await this.apisauce.post( "/api/auth/forgot-password", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth forgotPassword error: ${e.message}`) } return { kind: "bad-data" } } } /** * Resend forgot password verification code */ async resendForgotPasswordCode( email: string, language?: string, ): Promise<{ kind: "ok"; data: ResendForgotPasswordResponse } | GeneralApiProblem> { const payload: ResendForgotPasswordRequest = { email } if (language) payload.language = language const response: ApiResponse = await this.apisauce.post( "/api/auth/forgot-password/resend", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth resendForgotPasswordCode error: ${e.message}`) } return { kind: "bad-data" } } } /** * Reset password: Verify code and set new password */ async resetPassword( email: string, code: string, newPassword: string, ): Promise<{ kind: "ok"; data: ResetPasswordResponse } | GeneralApiProblem> { const payload: ResetPasswordRequest = { email, code, newPassword } const response: ApiResponse = await this.apisauce.post( "/api/auth/reset-password", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth resetPassword error: ${e.message}`) } return { kind: "bad-data" } } } /** * Logout: Revoke token */ async logoutApi( accessToken: string, ): Promise<{ kind: "ok"; data: LogoutResponse } | GeneralApiProblem> { const response: ApiResponse = await this.apisauce.post( "/api/auth/logout", {}, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth logout error: ${e.message}`) } return { kind: "bad-data" } } } // ============================================ // Profile APIs // ============================================ /** * Get user profile */ async getProfile( accessToken: string, ): Promise<{ kind: "ok"; data: GetProfileResponse } | GeneralApiProblem> { const response: ApiResponse = await this.apisauce.get( "/api/auth/profile", {}, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth getProfile error: ${e.message}`) } return { kind: "bad-data" } } } /** * Update user profile */ async updateProfile( accessToken: string, payload: UpdateProfileRequest, ): Promise<{ kind: "ok"; data: UpdateProfileResponse } | GeneralApiProblem> { const response: ApiResponse = await this.apisauce.put( "/api/auth/profile", payload, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth updateProfile error: ${e.message}`) } return { kind: "bad-data" } } } // ============================================ // Password APIs // ============================================ /** * Change password */ async changePassword( accessToken: string, payload: ChangePasswordRequest, ): Promise<{ kind: "ok"; data: ChangePasswordResponse } | GeneralApiProblem> { const response: ApiResponse = await this.apisauce.post( "/api/auth/change-password", payload, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth changePassword error: ${e.message}`) } return { kind: "bad-data" } } } // ============================================ // Token APIs // ============================================ /** * Refresh access token */ async refreshToken( refreshToken: string, ): Promise<{ kind: "ok"; data: RefreshTokenResponse } | GeneralApiProblem> { const payload: RefreshTokenRequest = { refreshToken } const response: ApiResponse = await this.apisauce.post( "/api/auth/refresh-token", payload, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth refreshToken error: ${e.message}`) } return { kind: "bad-data" } } } // ============================================ // Email APIs // ============================================ /** * Send email verification code (for changing email) */ async sendEmailCode( accessToken: string, email: string, language?: string, ): Promise<{ kind: "ok"; data: SendEmailCodeResponse } | GeneralApiProblem> { const payload: SendEmailCodeRequest = { email } if (language) payload.language = language const response: ApiResponse = await this.apisauce.post( "/api/email/send-verification-code", payload, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth sendEmailCode error: ${e.message}`) } return { kind: "bad-data" } } } /** * Verify current email (Step 1 of email change) */ async verifyCurrentEmail( accessToken: string, currentCode: string, ): Promise<{ kind: "ok"; data: VerifyCurrentEmailResponse } | GeneralApiProblem> { const payload: VerifyCurrentEmailRequest = { action: "verify-current", currentCode, } const response: ApiResponse = await this.apisauce.post( "/api/email/change", payload, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth verifyCurrentEmail error: ${e.message}`) } return { kind: "bad-data" } } } /** * Bind new email (Step 2 of email change) */ async bindNewEmail( accessToken: string, newEmail: string, newCode: string, ): Promise<{ kind: "ok"; data: BindNewEmailResponse } | GeneralApiProblem> { const payload: BindNewEmailRequest = { action: "bind-new", newEmail, newCode, } const response: ApiResponse = await this.apisauce.post( "/api/email/change", payload, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth bindNewEmail error: ${e.message}`) } return { kind: "bad-data" } } } // ============================================ // Session Management APIs // ============================================ /** * Get sessions/devices list */ async getSessions( accessToken: string, params?: GetSessionsParams, ): Promise<{ kind: "ok"; data: GetSessionsResponse } | GeneralApiProblem> { const response: ApiResponse = await this.apisauce.get( "/api/sessions", params, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth getSessions error: ${e.message}`) } return { kind: "bad-data" } } } /** * Get active session count */ async getSessionCount( accessToken: string, ): Promise<{ kind: "ok"; data: GetSessionCountResponse } | GeneralApiProblem> { const response: ApiResponse = await this.apisauce.get( "/api/sessions/count", {}, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth getSessionCount error: ${e.message}`) } return { kind: "bad-data" } } } /** * Get login history */ async getLoginHistory( accessToken: string, params?: GetLoginHistoryParams, ): Promise<{ kind: "ok"; data: GetLoginHistoryResponse } | GeneralApiProblem> { const response: ApiResponse = await this.apisauce.get( "/api/sessions/history", params, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth getLoginHistory error: ${e.message}`) } return { kind: "bad-data" } } } /** * Revoke a specific session (remote logout device) */ async revokeSession( accessToken: string, sessionId: string, ): Promise<{ kind: "ok"; data: RevokeSessionResponse } | GeneralApiProblem> { const response: ApiResponse = await this.apisauce.delete( `/api/sessions/${sessionId}`, {}, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth revokeSession error: ${e.message}`) } return { kind: "bad-data" } } } /** * Revoke all other sessions (logout all other devices) */ async revokeOtherSessions( accessToken: string, ): Promise<{ kind: "ok"; data: RevokeOtherSessionsResponse } | GeneralApiProblem> { const response: ApiResponse = await this.apisauce.post( "/api/sessions/revoke-others", {}, { headers: { Authorization: `Bearer ${accessToken}`, }, }, ) if (!response.ok) { const problem = getGeneralApiProblem(response) if (problem) return problem } try { const data = response.data if (!data) return { kind: "bad-data" } return { kind: "ok", data } } catch (e) { if (__DEV__ && e instanceof Error) { console.error(`Auth revokeOtherSessions error: ${e.message}`) } return { kind: "bad-data" } } } } // Singleton instance export const authApi = new AuthApi()