mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
When the Telegram OIDC widget fails to load (ad blockers, DNS, CSP), the deep link polling fallback caused an infinite page reload: 1. pollDeepLinkToken received 202 (pending) which axios treated as success 2. loginWithDeepLink stored undefined tokens with isAuthenticated=true 3. checkAdminStatus() fired with invalid token → 401 4. Response interceptor called safeRedirectToLogin() → page reload → loop Fixes: - pollDeepLinkToken: validateStatus rejects non-200 (202 now caught in poll loop) - AUTH_ENDPOINTS: add /cabinet/auth/deeplink/ and /cabinet/auth/login/auto - safeRedirectToLogin: guard against redirect when already on /login - loginWithDeepLink: validate response tokens before storing - setTokens: validate tokens in store method (protects AutoLogin, VerifyEmail, etc.)
328 lines
9.3 KiB
TypeScript
328 lines
9.3 KiB
TypeScript
import apiClient from './client';
|
|
import type {
|
|
AuthResponse,
|
|
LinkCallbackResponse,
|
|
LinkedProvidersResponse,
|
|
MergePreviewResponse,
|
|
MergeResponse,
|
|
OAuthProvider,
|
|
RegisterResponse,
|
|
ServerCompleteResponse,
|
|
TokenResponse,
|
|
User,
|
|
} from '../types';
|
|
|
|
export const authApi = {
|
|
loginTelegram: async (
|
|
initData: string,
|
|
campaignSlug?: string | null,
|
|
referralCode?: string | null,
|
|
): Promise<AuthResponse> => {
|
|
const response = await apiClient.post<AuthResponse>('/cabinet/auth/telegram', {
|
|
init_data: initData,
|
|
campaign_slug: campaignSlug || undefined,
|
|
referral_code: referralCode || undefined,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
loginTelegramWidget: async (
|
|
data: {
|
|
id: number;
|
|
first_name: string;
|
|
last_name?: string;
|
|
username?: string;
|
|
photo_url?: string;
|
|
auth_date: number;
|
|
hash: string;
|
|
},
|
|
campaignSlug?: string | null,
|
|
referralCode?: string | null,
|
|
): Promise<AuthResponse> => {
|
|
const response = await apiClient.post<AuthResponse>('/cabinet/auth/telegram/widget', {
|
|
...data,
|
|
campaign_slug: campaignSlug || undefined,
|
|
referral_code: referralCode || undefined,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
loginTelegramOIDC: async (
|
|
idToken: string,
|
|
campaignSlug?: string | null,
|
|
referralCode?: string | null,
|
|
): Promise<AuthResponse> => {
|
|
const response = await apiClient.post<AuthResponse>('/cabinet/auth/telegram/oidc', {
|
|
id_token: idToken,
|
|
campaign_slug: campaignSlug || undefined,
|
|
referral_code: referralCode || undefined,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
loginEmail: async (
|
|
email: string,
|
|
password: string,
|
|
campaignSlug?: string | null,
|
|
referralCode?: string | null,
|
|
): Promise<AuthResponse> => {
|
|
const response = await apiClient.post<AuthResponse>('/cabinet/auth/email/login', {
|
|
email,
|
|
password,
|
|
campaign_slug: campaignSlug || undefined,
|
|
referral_code: referralCode || undefined,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
registerEmail: async (
|
|
email: string,
|
|
password: string,
|
|
): Promise<{ message: string; email: string }> => {
|
|
const response = await apiClient.post('/cabinet/auth/email/register', {
|
|
email,
|
|
password,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
registerEmailStandalone: async (data: {
|
|
email: string;
|
|
password: string;
|
|
first_name?: string;
|
|
language?: string;
|
|
referral_code?: string;
|
|
}): Promise<RegisterResponse> => {
|
|
const response = await apiClient.post<RegisterResponse>(
|
|
'/cabinet/auth/email/register/standalone',
|
|
data,
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
verifyEmail: async (token: string, campaignSlug?: string | null): Promise<AuthResponse> => {
|
|
const response = await apiClient.post<AuthResponse>('/cabinet/auth/email/verify', {
|
|
token,
|
|
campaign_slug: campaignSlug || undefined,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
resendVerification: async (): Promise<{ message: string }> => {
|
|
const response = await apiClient.post('/cabinet/auth/email/resend');
|
|
return response.data;
|
|
},
|
|
|
|
refreshToken: async (refreshToken: string): Promise<TokenResponse> => {
|
|
const response = await apiClient.post<TokenResponse>('/cabinet/auth/refresh', {
|
|
refresh_token: refreshToken,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
logout: async (refreshToken: string): Promise<void> => {
|
|
await apiClient.post('/cabinet/auth/logout', {
|
|
refresh_token: refreshToken,
|
|
});
|
|
},
|
|
|
|
forgotPassword: async (email: string): Promise<{ message: string }> => {
|
|
const response = await apiClient.post('/cabinet/auth/password/forgot', { email });
|
|
return response.data;
|
|
},
|
|
|
|
resetPassword: async (token: string, password: string): Promise<{ message: string }> => {
|
|
const response = await apiClient.post('/cabinet/auth/password/reset', {
|
|
token,
|
|
password,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
getMe: async (): Promise<User> => {
|
|
const response = await apiClient.get<User>('/cabinet/auth/me');
|
|
return response.data;
|
|
},
|
|
|
|
requestEmailChange: async (
|
|
newEmail: string,
|
|
): Promise<{ message: string; new_email: string; expires_in_minutes: number }> => {
|
|
const response = await apiClient.post('/cabinet/auth/email/change', {
|
|
new_email: newEmail,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
verifyEmailChange: async (code: string): Promise<{ message: string; email: string }> => {
|
|
const response = await apiClient.post('/cabinet/auth/email/change/verify', {
|
|
code,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
getOAuthProviders: async (): Promise<{ providers: OAuthProvider[] }> => {
|
|
const response = await apiClient.get<{ providers: OAuthProvider[] }>(
|
|
'/cabinet/auth/oauth/providers',
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
getOAuthAuthorizeUrl: async (
|
|
provider: string,
|
|
): Promise<{ authorize_url: string; state: string }> => {
|
|
const response = await apiClient.get<{ authorize_url: string; state: string }>(
|
|
`/cabinet/auth/oauth/${encodeURIComponent(provider)}/authorize`,
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
oauthCallback: async (
|
|
provider: string,
|
|
code: string,
|
|
state: string,
|
|
deviceId?: string | null,
|
|
campaignSlug?: string | null,
|
|
referralCode?: string | null,
|
|
): Promise<AuthResponse> => {
|
|
const response = await apiClient.post<AuthResponse>(
|
|
`/cabinet/auth/oauth/${encodeURIComponent(provider)}/callback`,
|
|
{
|
|
code,
|
|
state,
|
|
device_id: deviceId || undefined,
|
|
campaign_slug: campaignSlug || undefined,
|
|
referral_code: referralCode || undefined,
|
|
},
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
getLinkedProviders: async (): Promise<LinkedProvidersResponse> => {
|
|
const response = await apiClient.get<LinkedProvidersResponse>(
|
|
'/cabinet/auth/account/linked-providers',
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
linkProviderInit: async (provider: string): Promise<{ authorize_url: string; state: string }> => {
|
|
const response = await apiClient.get<{ authorize_url: string; state: string }>(
|
|
`/cabinet/auth/account/link/${encodeURIComponent(provider)}/init`,
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
linkProviderCallback: async (
|
|
provider: string,
|
|
code: string,
|
|
state: string,
|
|
deviceId?: string,
|
|
): Promise<LinkCallbackResponse> => {
|
|
const response = await apiClient.post<LinkCallbackResponse>(
|
|
`/cabinet/auth/account/link/${encodeURIComponent(provider)}/callback`,
|
|
{
|
|
code,
|
|
state,
|
|
device_id: deviceId,
|
|
},
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
linkTelegram: async (
|
|
data:
|
|
| { init_data: string }
|
|
| { id_token: string }
|
|
| {
|
|
id: number;
|
|
first_name: string;
|
|
last_name?: string;
|
|
username?: string;
|
|
photo_url?: string;
|
|
auth_date: number;
|
|
hash: string;
|
|
},
|
|
): Promise<LinkCallbackResponse> => {
|
|
const response = await apiClient.post<LinkCallbackResponse>(
|
|
'/cabinet/auth/account/link/telegram',
|
|
data,
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
linkServerComplete: async (
|
|
code: string,
|
|
state: string,
|
|
deviceId?: string,
|
|
): Promise<ServerCompleteResponse> => {
|
|
const response = await apiClient.post<ServerCompleteResponse>(
|
|
'/cabinet/auth/account/link/server-complete',
|
|
{
|
|
code,
|
|
state,
|
|
device_id: deviceId,
|
|
},
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
unlinkProvider: async (provider: string): Promise<{ success: boolean }> => {
|
|
const response = await apiClient.post<{ success: boolean }>(
|
|
`/cabinet/auth/account/unlink/${encodeURIComponent(provider)}`,
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
autoLogin: async (token: string): Promise<AuthResponse> => {
|
|
const response = await apiClient.post<AuthResponse>('/cabinet/auth/login/auto', { token });
|
|
return response.data;
|
|
},
|
|
|
|
requestDeepLinkToken: async (): Promise<{
|
|
token: string;
|
|
bot_username: string;
|
|
expires_in: number;
|
|
}> => {
|
|
const response = await apiClient.post<{
|
|
token: string;
|
|
bot_username: string;
|
|
expires_in: number;
|
|
}>('/cabinet/auth/deeplink/request');
|
|
return response.data;
|
|
},
|
|
|
|
pollDeepLinkToken: async (token: string): Promise<AuthResponse> => {
|
|
// validateStatus: only treat 200 as success.
|
|
// Server returns 202 for "pending" and 410 for "expired" —
|
|
// these must reject so the polling catch-block can handle them.
|
|
// Without this, axios resolves on 202 (it's 2xx), causing
|
|
// loginWithDeepLink to set undefined tokens + isAuthenticated=true,
|
|
// which triggers checkAdminStatus() → 401 → safeRedirectToLogin() → infinite reload.
|
|
const response = await apiClient.post<AuthResponse>(
|
|
'/cabinet/auth/deeplink/poll',
|
|
{ token },
|
|
{ validateStatus: (status) => status === 200 },
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
getMergePreview: async (mergeToken: string): Promise<MergePreviewResponse> => {
|
|
const response = await apiClient.get<MergePreviewResponse>(
|
|
`/cabinet/auth/merge/${encodeURIComponent(mergeToken)}`,
|
|
);
|
|
return response.data;
|
|
},
|
|
|
|
executeMerge: async (
|
|
mergeToken: string,
|
|
keepSubscriptionFrom: number,
|
|
): Promise<MergeResponse> => {
|
|
const response = await apiClient.post<MergeResponse>(
|
|
`/cabinet/auth/merge/${encodeURIComponent(mergeToken)}`,
|
|
{
|
|
keep_subscription_from: keepSubscriptionFrom,
|
|
},
|
|
);
|
|
return response.data;
|
|
},
|
|
};
|