feat: account merge flow — merge redirect, error handling, server-complete linking

- Добавлен merge_required + merge_token redirect в OAuthCallback и LinkTelegramCallback
- Server-complete OAuth linking для Mini App (внешний браузер)
- getErrorDetail: единый экстрактор ошибок из axios response
- LinkTelegramCallback: типизированный catch с getErrorDetail
- Удалён мёртвый экспорт getAndClearOAuthState
- CSRF валидация для Telegram и OAuth linking flows
- Типы ServerCompleteResponse, LinkCallbackResponse
This commit is contained in:
Fringg
2026-03-05 05:25:50 +03:00
parent 0d99ea0069
commit 2fc0759f89
10 changed files with 120 additions and 78 deletions

View File

@@ -7,6 +7,7 @@ import type {
MergeResponse,
OAuthProvider,
RegisterResponse,
ServerCompleteResponse,
TokenResponse,
User,
} from '../types';
@@ -260,8 +261,8 @@ export const authApi = {
code: string,
state: string,
deviceId?: string,
): Promise<LinkCallbackResponse & { provider?: string }> => {
const response = await apiClient.post<LinkCallbackResponse & { provider?: string }>(
): Promise<ServerCompleteResponse> => {
const response = await apiClient.post<ServerCompleteResponse>(
'/cabinet/auth/account/link/server-complete',
{
code,

View File

@@ -81,7 +81,7 @@ const AUTH_ENDPOINTS = [
function isAuthEndpoint(url: string | undefined): boolean {
if (!url) return false;
return AUTH_ENDPOINTS.some((endpoint) => url.includes(endpoint));
return AUTH_ENDPOINTS.some((endpoint) => url.startsWith(endpoint));
}
// Request interceptor - add auth token with expiration check
@@ -214,15 +214,10 @@ apiClient.interceptors.response.use(
// Если получили 401 и ещё не пробовали refresh (на случай если проверка exp не сработала)
if (error.response?.status === 401 && !originalRequest._retry) {
// Не обрабатываем 401 для авторизационных endpoints - пусть ошибка дойдет до компонента
const authEndpoints = [
'/cabinet/auth/email/login',
'/cabinet/auth/telegram',
'/cabinet/auth/telegram/widget',
];
const requestUrl = originalRequest.url || '';
const isAuthEndpoint = authEndpoints.some((endpoint) => requestUrl.includes(endpoint));
const isLoginEndpoint = isAuthEndpoint(requestUrl);
if (isAuthEndpoint) {
if (isLoginEndpoint) {
// Пробрасываем ошибку в компонент для показа сообщения пользователю
return Promise.reject(error);
}