From b3b07c322de5ce48db83661a020dfe96be5b2237 Mon Sep 17 00:00:00 2001
From: kewldan <40865857+kewldan@users.noreply.github.com>
Date: Thu, 23 Jul 2026 04:09:06 +0300
Subject: [PATCH] =?UTF-8?q?fix(auth):=20=D1=80=D0=B0=D0=B7=D0=BB=D0=B8?=
=?UTF-8?q?=D1=87=D0=B0=D1=82=D1=8C=20=D1=83=D1=81=D1=82=D0=B0=D1=80=D0=B5?=
=?UTF-8?q?=D0=B2=D1=88=D0=B5=D0=B3=D0=BE=20=D0=B1=D0=BE=D1=82=D0=B0=20?=
=?UTF-8?q?=D0=B8=20=D0=BD=D0=B5=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5?=
=?UTF-8?q?=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BD=D0=B0=20=D1=8D=D0=BA?=
=?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD=D0=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 4 +-
src/api/branding.ts | 6 ++-
src/components/TelegramLoginButton.tsx | 11 +++--
src/locales/en.json | 1 +
src/locales/fa.json | 1 +
src/locales/ru.json | 1 +
src/locales/zh.json | 1 +
src/utils/api-error.test.ts | 59 ++++++++++++++++++++++++++
src/utils/api-error.ts | 10 +++++
9 files changed, 87 insertions(+), 7 deletions(-)
create mode 100644 src/utils/api-error.test.ts
diff --git a/README.md b/README.md
index cc89889..b366a9e 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
# Bedolaga Cabinet - Web Interface
-Веб-интерфейс личного кабинета для VPN бота на базе [Remnawave Bedolaga Telegram Bot V3.0.0+](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot).
+Веб-интерфейс личного кабинета для VPN бота на базе [Remnawave Bedolaga Telegram Bot v3.33.0+](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot).
React + Vite + TypeScript | Авторизация через Telegram | Мультиязычность (EN/RU) | Адаптивный дизайн
## Требования
- Docker и Docker Compose
-- Запущенный backend бота с включенным Cabinet API
+- Запущенный backend бота с включенным Cabinet API, версия **v3.33.0 или новее** — авторизация кабинета использует deep-link эндпоинты (`/cabinet/auth/deeplink/*`), которых нет в более старых версиях. На старом боте кабинет не сможет авторизовать пользователей (залоченные теги вроде `:3` не подходят — используйте `:latest` или конкретный тег ≥ 3.33.0)
- Обратный прокси (Caddy / Nginx / Traefik)
## Архитектура
diff --git a/src/api/branding.ts b/src/api/branding.ts
index e57a130..72d6ffc 100644
--- a/src/api/branding.ts
+++ b/src/api/branding.ts
@@ -1,4 +1,5 @@
import apiClient from './client';
+import { isEndpointMissingError } from '../utils/api-error';
import type { AnimationConfig } from '@/components/ui/backgrounds/types';
import { DEFAULT_ANIMATION_CONFIG } from '@/components/ui/backgrounds/types';
@@ -36,6 +37,8 @@ export interface TelegramWidgetConfig {
request_access: boolean;
oidc_enabled: boolean;
oidc_client_id: string;
+ /** The bot answered 404 on the config route — build older than v3.24.0. */
+ endpoint_missing?: boolean;
}
export interface OfflineConvGoal {
@@ -321,7 +324,7 @@ export const brandingApi = {
'/cabinet/branding/telegram-widget',
);
return response.data;
- } catch {
+ } catch (err) {
return {
bot_username: import.meta.env.VITE_TELEGRAM_BOT_USERNAME || '',
size: 'large',
@@ -330,6 +333,7 @@ export const brandingApi = {
request_access: true,
oidc_enabled: false,
oidc_client_id: '',
+ endpoint_missing: isEndpointMissingError(err),
};
}
},
diff --git a/src/components/TelegramLoginButton.tsx b/src/components/TelegramLoginButton.tsx
index c31df2c..15dc748 100644
--- a/src/components/TelegramLoginButton.tsx
+++ b/src/components/TelegramLoginButton.tsx
@@ -9,6 +9,7 @@ import { useAuthStore } from '../store/auth';
import { useNavigate } from 'react-router';
import { getPendingCampaignSlug } from '../utils/campaign';
import { copyToClipboard } from '../utils/clipboard';
+import { isEndpointMissingError } from '../utils/api-error';
interface TelegramLoginButtonProps {
referralCode?: string;
@@ -159,7 +160,6 @@ export default function TelegramLoginButton({ referralCode }: TelegramLoginButto
isOIDC,
widgetConfig?.oidc_client_id,
widgetConfig?.request_access,
- t,
scriptLoaded,
handleScriptFailed,
]);
@@ -330,8 +330,9 @@ export default function TelegramLoginButton({ referralCode }: TelegramLoginButto
},
(expires_in || 300) * 1000,
);
- } catch {
- setDeepLinkError(t('common.error'));
+ } catch (err) {
+ // 404 = the deep-link auth routes don't exist on this bot build (< v3.33.0)
+ setDeepLinkError(t(isEndpointMissingError(err) ? 'auth.botOutdated' : 'common.error'));
}
}, [botUsername, loginWithDeepLink, navigate, t]);
@@ -421,9 +422,11 @@ export default function TelegramLoginButton({ referralCode }: TelegramLoginButto
}, [deepLinkPolling, deepLinkToken, loginWithDeepLink, navigate, t]);
if (!botUsername || botUsername === 'your_bot') {
+ // 404 on the config route = the bot build predates the cabinet endpoints,
+ // which reads as "not configured" but is actually a version mismatch (#345).
return (
- {t('auth.telegramNotConfigured')}
+ {t(widgetConfig?.endpoint_missing ? 'auth.botOutdated' : 'auth.telegramNotConfigured')}
);
}
diff --git a/src/locales/en.json b/src/locales/en.json
index 774cbb4..b1cd47f 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -189,6 +189,7 @@
"telegramRetryFailed": "Authorization failed. Close the app and try again.",
"telegramReopenHint": "If the problem persists, close and reopen the app",
"telegramNotConfigured": "Telegram bot is not configured",
+ "botOutdated": "The bot version is not compatible with the cabinet. Update the bot to v3.33.0 or newer.",
"telegramUnavailable": "Telegram login is temporarily unavailable",
"authenticating": "Authenticating...",
"orOpenInApp": "Or open the bot in the app",
diff --git a/src/locales/fa.json b/src/locales/fa.json
index 518fe03..71747b1 100644
--- a/src/locales/fa.json
+++ b/src/locales/fa.json
@@ -181,6 +181,7 @@
"telegramRetryFailed": "تایید هویت ناموفق بود. برنامه را ببندید و دوباره باز کنید.",
"telegramReopenHint": "اگر مشکل ادامه دارد، برنامه را ببندید و دوباره باز کنید",
"telegramNotConfigured": "ربات تلگرام پیکربندی نشده",
+ "botOutdated": "نسخهٔ ربات با کابینت سازگار نیست. ربات را به v3.33.0 یا جدیدتر بهروزرسانی کنید.",
"telegramUnavailable": "ورود از طریق تلگرام موقتاً در دسترس نیست",
"authenticating": "در حال تایید هویت...",
"orOpenInApp": "یا ربات را در برنامه باز کنید",
diff --git a/src/locales/ru.json b/src/locales/ru.json
index 5424442..f225028 100644
--- a/src/locales/ru.json
+++ b/src/locales/ru.json
@@ -192,6 +192,7 @@
"telegramRetryFailed": "Авторизация не удалась. Закройте приложение и откройте заново.",
"telegramReopenHint": "Если проблема повторяется, закройте и откройте приложение заново",
"telegramNotConfigured": "Telegram бот не настроен",
+ "botOutdated": "Версия бота несовместима с кабинетом. Обновите бота до v3.33.0 или новее.",
"telegramUnavailable": "Вход через Telegram временно недоступен",
"authenticating": "Авторизация...",
"orOpenInApp": "Или откройте бота в приложении",
diff --git a/src/locales/zh.json b/src/locales/zh.json
index d358bb2..f53e0b7 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -181,6 +181,7 @@
"telegramRetryFailed": "授权失败。请关闭应用后重新打开。",
"telegramReopenHint": "如果问题持续存在,请关闭并重新打开应用",
"telegramNotConfigured": "Telegram机器人未配置",
+ "botOutdated": "机器人版本与后台不兼容,请将机器人更新至 v3.33.0 或更高版本。",
"telegramUnavailable": "Telegram登录暂时不可用",
"authenticating": "正在验证...",
"orOpenInApp": "或在应用中打开机器人",
diff --git a/src/utils/api-error.test.ts b/src/utils/api-error.test.ts
new file mode 100644
index 0000000..bb6c5bb
--- /dev/null
+++ b/src/utils/api-error.test.ts
@@ -0,0 +1,59 @@
+import { AxiosError, AxiosHeaders } from 'axios';
+import { describe, expect, it } from 'vitest';
+import { getApiErrorMessage, isEndpointMissingError } from './api-error';
+
+function axiosErrorWithStatus(status: number, detail?: unknown): AxiosError {
+ const headers = new AxiosHeaders();
+ const config = { headers };
+ return new AxiosError(
+ 'Request failed',
+ 'ERR_BAD_REQUEST',
+ config,
+ {},
+ {
+ status,
+ statusText: '',
+ headers,
+ config,
+ data: detail === undefined ? {} : { detail },
+ },
+ );
+}
+
+describe('isEndpointMissingError', () => {
+ it('returns true for an axios 404', () => {
+ expect(isEndpointMissingError(axiosErrorWithStatus(404))).toBe(true);
+ });
+
+ it('returns false for other axios statuses', () => {
+ expect(isEndpointMissingError(axiosErrorWithStatus(403))).toBe(false);
+ expect(isEndpointMissingError(axiosErrorWithStatus(500))).toBe(false);
+ });
+
+ it('returns false for non-axios errors', () => {
+ expect(isEndpointMissingError(new Error('boom'))).toBe(false);
+ expect(isEndpointMissingError(undefined)).toBe(false);
+ expect(isEndpointMissingError(null)).toBe(false);
+ });
+});
+
+describe('getApiErrorMessage', () => {
+ it('returns string detail from an axios error', () => {
+ expect(getApiErrorMessage(axiosErrorWithStatus(400, 'Code must not be empty'), 'fb')).toBe(
+ 'Code must not be empty',
+ );
+ });
+
+ it('joins pydantic validation errors', () => {
+ const err = axiosErrorWithStatus(422, [
+ { loc: ['body', 'name'], msg: 'field required' },
+ { loc: ['body', 'days'], msg: 'must be positive' },
+ ]);
+ expect(getApiErrorMessage(err, 'fb')).toBe('name: field required; days: must be positive');
+ });
+
+ it('falls back for non-axios errors and missing detail', () => {
+ expect(getApiErrorMessage(new Error('boom'), 'fb')).toBe('fb');
+ expect(getApiErrorMessage(axiosErrorWithStatus(500), 'fb')).toBe('fb');
+ });
+});
diff --git a/src/utils/api-error.ts b/src/utils/api-error.ts
index b33e2ff..8e9f850 100644
--- a/src/utils/api-error.ts
+++ b/src/utils/api-error.ts
@@ -1,5 +1,15 @@
import axios from 'axios';
+/**
+ * True when the backend answered 404 on the route itself — for cabinet
+ * endpoints that means the bot build is too old to have them (e.g. the
+ * deep-link auth routes exist only since bot v3.33.0), as opposed to a
+ * missing entity inside a handler.
+ */
+export function isEndpointMissingError(err: unknown): boolean {
+ return axios.isAxiosError(err) && err.response?.status === 404;
+}
+
export function getApiErrorMessage(err: unknown, fallback: string): string {
if (axios.isAxiosError(err)) {
const detail = err.response?.data?.detail;