From 0b6ce353fe8769ec00f9f062c66db525ba40d059 Mon Sep 17 00:00:00 2001 From: fengjiaqi Date: Sat, 8 Aug 2026 08:17:17 -0700 Subject: [PATCH] fix(profile): stop regenerating the access token when the dialog opens The access token dialog regenerated on mount: token is component state, so it is always empty on open and the effect always fired. Opening the dialog was therefore enough to replace the token server-side. That is not recoverable. AccessToken is json:"-" on the user model, so the value is returned once at creation and can never be read back, and the server overwrites the old one unconditionally. A user who only wanted to look at the card broke every integration still holding the old token. Drop the auto-generate effect, explain up front that the existing token cannot be shown again, and put the regeneration behind a destructive confirmation that spells out the consequence. --- .../dialogs/access-token-dialog.tsx | 171 +++++++++++------- web/src/i18n/locales/en.json | 6 +- web/src/i18n/locales/fr.json | 6 +- web/src/i18n/locales/ja.json | 6 +- web/src/i18n/locales/ru.json | 6 +- web/src/i18n/locales/vi.json | 6 +- web/src/i18n/locales/zh-TW.json | 6 +- web/src/i18n/locales/zh.json | 6 +- 8 files changed, 140 insertions(+), 73 deletions(-) diff --git a/web/src/features/profile/components/dialogs/access-token-dialog.tsx b/web/src/features/profile/components/dialogs/access-token-dialog.tsx index 550166caa158..b18940bb3a24 100644 --- a/web/src/features/profile/components/dialogs/access-token-dialog.tsx +++ b/web/src/features/profile/components/dialogs/access-token-dialog.tsx @@ -16,10 +16,11 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import { RefreshCw, Loader2 } from 'lucide-react' -import { useEffect } from 'react' +import { RefreshCw, Loader2, KeyRound } from 'lucide-react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +import { ConfirmDialog } from '@/components/confirm-dialog' import { CopyButton } from '@/components/copy-button' import { Dialog } from '@/components/dialog' import { Button } from '@/components/ui/button' @@ -43,76 +44,114 @@ export function AccessTokenDialog({ }: AccessTokenDialogProps) { const { t } = useTranslation() const { token, generating, generate } = useAccessToken() + const [confirmOpen, setConfirmOpen] = useState(false) - // Auto-generate token when dialog opens if no token exists + // Regenerating invalidates the previous token server-side and cannot be + // undone, so it must never happen as a side effect of opening this dialog. + // The confirmation state is reset on close so a reopened dialog starts idle. useEffect(() => { - if (open && !token) { - generate() + if (!open) { + setConfirmOpen(false) } - }, [open, token, generate]) + }, [open]) + + const handleConfirm = async () => { + setConfirmOpen(false) + await generate() + } return ( - - - - - } - > -
-
- -
- - + +
-

- {t('Use this token for API authentication')} -

+ onClick={() => onOpenChange(false)} + > + {t('Close')} + + + + } + > +
+ {token ? ( +
+ +
+ + +
+

+ {t( + 'This token is shown only once. Store it now — it cannot be retrieved later.' + )} +

+
+ ) : ( +
+
+ +
+

+ {t( + 'For security reasons the existing token cannot be displayed again. Regenerating creates a new token and invalidates the current one immediately.' + )} +

+
+ )}
-
-
+ + + + ) } diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index b48096a7fcfa..185ea83ae80a 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -5248,6 +5248,10 @@ "Zero retention": "Zero retention", "Zhipu": "Zhipu", "Zhipu V4": "Zhipu V4", - "Zoom": "Zoom" + "Zoom": "Zoom", + "Regenerate access token?": "Regenerate access token?", + "This token is shown only once. Store it now — it cannot be retrieved later.": "This token is shown only once. Store it now — it cannot be retrieved later.", + "For security reasons the existing token cannot be displayed again. Regenerating creates a new token and invalidates the current one immediately.": "For security reasons the existing token cannot be displayed again. Regenerating creates a new token and invalidates the current one immediately.", + "The current token stops working immediately. Any integration still using it will fail until you update it with the new token.": "The current token stops working immediately. Any integration still using it will fail until you update it with the new token." } } diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index 638cf9abadd1..7ac507788f65 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -5248,6 +5248,10 @@ "Zero retention": "Aucune rétention", "Zhipu": "Zhipu", "Zhipu V4": "Zhipu V4", - "Zoom": "Zoom" + "Zoom": "Zoom", + "Regenerate access token?": "Régénérer le jeton d'accès ?", + "This token is shown only once. Store it now — it cannot be retrieved later.": "Ce jeton n'est affiché qu'une seule fois. Enregistrez-le maintenant, il ne pourra plus être récupéré ensuite.", + "For security reasons the existing token cannot be displayed again. Regenerating creates a new token and invalidates the current one immediately.": "Pour des raisons de sécurité, le jeton existant ne peut pas être affiché à nouveau. La régénération crée un nouveau jeton et invalide immédiatement l'actuel.", + "The current token stops working immediately. Any integration still using it will fail until you update it with the new token.": "Le jeton actuel cesse de fonctionner immédiatement. Toute intégration l'utilisant encore échouera jusqu'à ce que vous la mettiez à jour avec le nouveau jeton." } } diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index 3b9d4ce693cc..cff4ab3649b3 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -5248,6 +5248,10 @@ "Zero retention": "データ保持なし", "Zhipu": "Zhipu", "Zhipu V4": "Zhipu V 4", - "Zoom": "ズーム" + "Zoom": "ズーム", + "Regenerate access token?": "アクセストークンを再生成しますか?", + "This token is shown only once. Store it now — it cannot be retrieved later.": "このトークンは一度しか表示されません。今すぐ保存してください。後から取得することはできません。", + "For security reasons the existing token cannot be displayed again. Regenerating creates a new token and invalidates the current one immediately.": "セキュリティ上の理由により、既存のトークンを再表示することはできません。再生成すると新しいトークンが作成され、現在のトークンは直ちに無効になります。", + "The current token stops working immediately. Any integration still using it will fail until you update it with the new token.": "現在のトークンは直ちに使用できなくなります。それを使用している連携は、新しいトークンに更新するまですべて失敗します。" } } diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index 853dc874d463..044827bca232 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -5248,6 +5248,10 @@ "Zero retention": "Без хранения данных", "Zhipu": "Zhipu", "Zhipu V4": "Zhipu V4", - "Zoom": "Zoom" + "Zoom": "Zoom", + "Regenerate access token?": "Перегенерировать токен доступа?", + "This token is shown only once. Store it now — it cannot be retrieved later.": "Этот токен отображается только один раз. Сохраните его сейчас — позже получить его будет невозможно.", + "For security reasons the existing token cannot be displayed again. Regenerating creates a new token and invalidates the current one immediately.": "В целях безопасности существующий токен нельзя показать повторно. Перегенерация создаёт новый токен и немедленно делает текущий недействительным.", + "The current token stops working immediately. Any integration still using it will fail until you update it with the new token.": "Текущий токен немедленно перестанет работать. Все интеграции, которые его используют, будут завершаться ошибкой, пока вы не обновите их новым токеном." } } diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index cdc1a12626e1..335d469d05f2 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -5248,6 +5248,10 @@ "Zero retention": "Không lưu dữ liệu", "Zhipu": "Zhipu", "Zhipu V4": "Zhipu V4", - "Zoom": "Zoom" + "Zoom": "Zoom", + "Regenerate access token?": "Tạo lại token truy cập?", + "This token is shown only once. Store it now — it cannot be retrieved later.": "Token này chỉ hiển thị một lần. Hãy lưu lại ngay — sau này không thể xem lại được.", + "For security reasons the existing token cannot be displayed again. Regenerating creates a new token and invalidates the current one immediately.": "Vì lý do bảo mật, token hiện tại không thể hiển thị lại. Việc tạo lại sẽ sinh ra token mới và vô hiệu hóa ngay token hiện tại.", + "The current token stops working immediately. Any integration still using it will fail until you update it with the new token.": "Token hiện tại sẽ ngừng hoạt động ngay lập tức. Mọi tích hợp còn dùng nó sẽ lỗi cho đến khi bạn cập nhật sang token mới." } } diff --git a/web/src/i18n/locales/zh-TW.json b/web/src/i18n/locales/zh-TW.json index f8b5fafd8d2c..69ea8fc64f3a 100644 --- a/web/src/i18n/locales/zh-TW.json +++ b/web/src/i18n/locales/zh-TW.json @@ -5248,6 +5248,10 @@ "Zero retention": "零數據保留", "Zhipu": "智譜", "Zhipu V4": "智譜 V4", - "Zoom": "縮放" + "Zoom": "縮放", + "Regenerate access token?": "重新生成存取令牌?", + "This token is shown only once. Store it now — it cannot be retrieved later.": "此令牌僅顯示一次,請立即儲存,之後無法再次查看。", + "For security reasons the existing token cannot be displayed again. Regenerating creates a new token and invalidates the current one immediately.": "出於安全考量,現有令牌無法再次顯示。重新生成會建立新令牌,並立即使目前的令牌失效。", + "The current token stops working immediately. Any integration still using it will fail until you update it with the new token.": "目前的令牌會立即停止運作。仍在使用它的整合將全部失敗,直到你更新為新令牌。" } } diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 842991e714f0..1b22477ee942 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -5248,6 +5248,10 @@ "Zero retention": "零数据保留", "Zhipu": "智谱", "Zhipu V4": "智谱 V4", - "Zoom": "缩放" + "Zoom": "缩放", + "Regenerate access token?": "重新生成访问令牌?", + "This token is shown only once. Store it now — it cannot be retrieved later.": "此令牌仅显示一次,请立即保存,之后无法再次查看。", + "For security reasons the existing token cannot be displayed again. Regenerating creates a new token and invalidates the current one immediately.": "出于安全考虑,现有令牌无法再次显示。重新生成会创建新令牌,并立即使当前令牌失效。", + "The current token stops working immediately. Any integration still using it will fail until you update it with the new token.": "当前令牌会立即停止工作。仍在使用它的集成将全部失败,直到你更新为新令牌。" } }