From 9c97e78aced572d540f227007a675d7d007666ac Mon Sep 17 00:00:00 2001 From: Seefs <40468931+seefs001@users.noreply.github.com> Date: Mon, 10 Aug 2026 13:12:59 +0800 Subject: [PATCH] fix(web): require confirmation before rotating access token (#6749) --- .../dialogs/access-token-dialog.tsx | 216 ++++++++++++------ .../profile/hooks/use-access-token.ts | 5 + web/src/i18n/locales/en.json | 8 + web/src/i18n/locales/fr.json | 8 + web/src/i18n/locales/ja.json | 8 + web/src/i18n/locales/ru.json | 8 + web/src/i18n/locales/vi.json | 8 + web/src/i18n/locales/zh-TW.json | 8 + web/src/i18n/locales/zh.json | 8 + 9 files changed, 209 insertions(+), 68 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 550166ca..c506cc95 100644 --- a/web/src/features/profile/components/dialogs/access-token-dialog.tsx +++ b/web/src/features/profile/components/dialogs/access-token-dialog.tsx @@ -16,13 +16,21 @@ 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 { KeyRound, Loader2, RefreshCw } from 'lucide-react' +import { 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' +import { + Empty, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from '@/components/ui/empty' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' @@ -42,77 +50,149 @@ export function AccessTokenDialog({ onOpenChange, }: AccessTokenDialogProps) { const { t } = useTranslation() - const { token, generating, generate } = useAccessToken() + const { token, generating, generate, clearToken } = useAccessToken() + const [confirmOpen, setConfirmOpen] = useState(false) - // Auto-generate token when dialog opens if no token exists - useEffect(() => { - if (open && !token) { - generate() + const handleOpenChange = (nextOpen: boolean) => { + if (generating) return + + if (!nextOpen) { + setConfirmOpen(false) + clearToken() } - }, [open, token, generate]) + onOpenChange(nextOpen) + } + + const handleGenerate = async () => { + if (await generate()) { + setConfirmOpen(false) + } + } return ( - - - - - } - > -
-
- -
- - + +
-

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

+ onClick={() => handleOpenChange(false)} + disabled={generating} + > + {t('Close')} + + + + } + > +
+ {token ? ( +
+ +
+ + +
+

+ {t( + "Save this token now. You won't be able to view it again after closing this dialog." + )} +

+
+ ) : ( + + + + + + {t('Access tokens are shown only once')} + + + {t( + 'For security, existing access tokens cannot be displayed. Regenerate only when you need a new token.' + )} + + + {t( + 'Regenerating immediately invalidates any existing token.' + )} + + + + )}
-
-
+ + + { + if (!generating) setConfirmOpen(nextOpen) + }} + title={t('Regenerate access token?')} + desc={ +
+

+ {t( + 'This will immediately invalidate your existing access token. Any applications or scripts using it will stop working.' + )} +

+

+ {t( + 'The new token will only be shown once. Copy it and store it securely.' + )} +

+
+ } + confirmText={ + generating ? ( + <> +