feat(auth): improve otp delivery and verification flow

This commit is contained in:
2026-05-13 09:58:59 +03:30
parent be0619f5d9
commit 64a949e44f
12 changed files with 693 additions and 197 deletions

View File

@@ -1,29 +1,44 @@
import { authFetch, buildApiError, buildApiUrl } from './client';
// --- Auth Endpoints ---
const normalizeDigits = (value: string) =>
value
.replace(/[۰-۹]/g, (digit) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(digit)))
.replace(/[٠-٩]/g, (digit) => String("٠١٢٣٤٥٦٧٨٩".indexOf(digit)))
// --- Auth Endpoints ---
export const loginWithPassword = async (mobile: string, password: string) => {
const normalizedMobile = normalizeDigits(mobile)
const response = await authFetch('/api/users/login/', {
method: 'POST',
body: JSON.stringify({ mobile, password })
body: JSON.stringify({ mobile: normalizedMobile, password })
});
if (!response.ok) throw await buildApiError(response);
return response.json();
};
export const sendOtp = async (mobile: string, mode: string) => {
export interface SendOtpResponse {
detail: string
expires_in_seconds: number
expires_at?: string | null
}
export const sendOtp = async (mobile: string, mode: string): Promise<SendOtpResponse> => {
const normalizedMobile = normalizeDigits(mobile)
const response = await authFetch('/api/users/otp/send/', {
method: 'POST',
body: JSON.stringify({ mobile, mode })
body: JSON.stringify({ mobile: normalizedMobile, mode })
});
if (!response.ok) throw await buildApiError(response);
return response.json();
};
export const loginWithOtp = async (mobile: string, otp: string) => {
const normalizedMobile = normalizeDigits(mobile)
const normalizedOtp = normalizeDigits(otp)
const response = await authFetch('/api/users/otp/login/', {
method: 'POST',
body: JSON.stringify({ mobile, code: otp })
body: JSON.stringify({ mobile: normalizedMobile, code: normalizedOtp })
});
if (!response.ok) throw await buildApiError(response);
return response.json();
@@ -37,9 +52,18 @@ export const registerWithOtp = async (
first_name = "",
last_name = "",
) => {
const normalizedMobile = normalizeDigits(mobile)
const normalizedCode = normalizeDigits(code)
const response = await authFetch("/api/users/register/", {
method: "POST",
body: JSON.stringify({ mobile, code, password, re_password, first_name, last_name }),
body: JSON.stringify({
mobile: normalizedMobile,
code: normalizedCode,
password,
re_password,
first_name,
last_name,
}),
})
if (!response.ok) throw await buildApiError(response)
return response.json()
@@ -51,9 +75,16 @@ export const resetPasswordWithOtp = async (
password: string,
re_password: string,
) => {
const normalizedMobile = normalizeDigits(mobile)
const normalizedCode = normalizeDigits(code)
const response = await authFetch("/api/users/password/reset/", {
method: "POST",
body: JSON.stringify({ mobile, code, password, re_password }),
body: JSON.stringify({
mobile: normalizedMobile,
code: normalizedCode,
password,
re_password,
}),
})
if (!response.ok) throw await buildApiError(response)
return response.json()
@@ -107,9 +138,10 @@ export const completeGoogleOAuthSignup = async (
flow: string,
mobile: string,
): Promise<GoogleOAuthFlowResponse> => {
const normalizedMobile = normalizeDigits(mobile)
const response = await authFetch("/api/users/oauth/google/complete/", {
method: "POST",
body: JSON.stringify({ flow, mobile }),
body: JSON.stringify({ flow, mobile: normalizedMobile }),
});
if (!response.ok) throw await buildApiError(response);
return response.json();
@@ -128,9 +160,10 @@ export const verifyGoogleOAuthClaim = async (
flow: string,
code: string,
): Promise<GoogleOAuthFlowResponse> => {
const normalizedCode = normalizeDigits(code)
const response = await authFetch("/api/users/oauth/google/claim/verify/", {
method: "POST",
body: JSON.stringify({ flow, code }),
body: JSON.stringify({ flow, code: normalizedCode }),
});
if (!response.ok) throw await buildApiError(response);
return response.json();
@@ -193,9 +226,9 @@ export interface SearchedUser {
profile_picture: string | null;
}
export const searchUserByExactMobile = async (mobile: string): Promise<SearchedUser | null> => {
try {
const response = await authFetch(`/api/users/search/?mobile=${encodeURIComponent(mobile)}`);
export const searchUserByExactMobile = async (mobile: string): Promise<SearchedUser | null> => {
try {
const response = await authFetch(`/api/users/search/?mobile=${encodeURIComponent(normalizeDigits(mobile))}`);
if (!response.ok) return null; // Returns null on 404 or other errors
return await response.json();
} catch (error) {