fix: add CLIProxy account reauthentication

This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-05-27 10:13:03 -04:00
1 parent d212c72ad8
commit 5f1976f69c
16 files changed
+332 -34

No files matched your search

@@ -29,7 +29,7 @@ import { Loader2, ExternalLink, User, Download, Copy, Check, ShieldAlert } from
import { useKiroImport } from '@/hooks/use-cliproxy';
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
import { applyDefaultPreset } from '@/lib/preset-utils';
import type { CliproxyProviderCatalog } from '@/lib/api-client';
import type { CliproxyProviderCatalog, OAuthAccount } from '@/lib/api-client';
import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card';
import { AntigravityResponsibilityChecklist } from '@/components/account/antigravity-responsibility-checklist';
import {
@@ -58,6 +58,8 @@ interface AddAccountDialogProps {
provider: string;
displayName: string;
catalog?: CliproxyProviderCatalog;
/** Existing account to reauthenticate instead of adding a new account. */
account?: OAuthAccount;
/** Whether this is the first account being added (shows different toast message) */
isFirstAccount?: boolean;
}
@@ -77,6 +79,7 @@ export function AddAccountDialog({
provider,
displayName,
catalog,
account,
isFirstAccount = false,
}: AddAccountDialogProps) {
const [nickname, setNickname] = useState('');
@@ -127,6 +130,8 @@ export function AddAccountDialog({
const gitlabBaseUrlTrimmed = gitlabBaseUrl.trim();
const gitlabPersonalAccessTokenTrimmed = gitlabPersonalAccessToken.trim();
const errorMessage = localError || authFlow.error;
const isReauth = Boolean(account);
const accountLabel = account?.email || account?.nickname || account?.id || displayName;
const fetchPowerUserModeState = useCallback(async (): Promise<boolean> => {
const response = await fetch('/api/settings/auth/antigravity-risk');
@@ -325,7 +330,8 @@ export function AddAccountDialog({
}
wasAuthenticatingRef.current = true;
authFlow.startAuth(provider, {
nickname: nicknameTrimmed || undefined,
accountId: account?.id,
nickname: account ? account.nickname : nicknameTrimmed || undefined,
kiroMethod: isKiro ? kiroAuthMethod : undefined,
kiroIDCStartUrl: isKiroIdc ? kiroIDCStartUrlTrimmed : undefined,
kiroIDCRegion: isKiroIdc && kiroIDCRegionTrimmed ? kiroIDCRegionTrimmed : undefined,
@@ -385,13 +391,19 @@ export function AddAccountDialog({
}}
>
<DialogHeader className="min-w-0 px-6 pt-6 pr-12">
<DialogTitle>{t('addAccountDialog.title', { displayName })}</DialogTitle>
<DialogTitle>
{isReauth
? `Reauthenticate ${displayName}`
: t('addAccountDialog.title', { displayName })}
</DialogTitle>
<DialogDescription>
{isKiro
? t('addAccountDialog.descKiro')
: isDeviceCode
? t('addAccountDialog.descDeviceCode')
: t('addAccountDialog.descOauth')}
{isReauth
? `Refresh credentials for ${accountLabel}.`
: isKiro
? t('addAccountDialog.descKiro')
: isDeviceCode
? t('addAccountDialog.descDeviceCode')
: t('addAccountDialog.descOauth')}
</DialogDescription>
</DialogHeader>
@@ -600,7 +612,16 @@ export function AddAccountDialog({
)}
{/* Nickname input - only show before auth starts */}
{!showAuthUI && (
{!showAuthUI && isReauth && (
<div className="rounded-md border bg-muted/30 px-3 py-2 text-sm">
<div className="font-medium">{accountLabel}</div>
<div className="text-xs text-muted-foreground">
This sign-in updates the selected account instead of creating another account.
</div>
</div>
)}
{!showAuthUI && !isReauth && (
<div className="space-y-2">
<Label htmlFor="nickname">{t('addAccountDialog.nicknameOptional')}</Label>
<div className="flex items-center gap-2">
@@ -784,7 +805,7 @@ export function AddAccountDialog({
}
>
<ExternalLink className="w-4 h-4 mr-2" />
{t('addAccountDialog.authenticate')}
{isReauth ? 'Reauthenticate' : t('addAccountDialog.authenticate')}
</Button>
)}
</div>
@@ -24,6 +24,7 @@ import {
MoreHorizontal,
Pause,
Play,
RefreshCw,
Star,
Trash2,
} from 'lucide-react';
@@ -87,6 +88,7 @@ export function AccountItem({
account,
onSetDefault,
onRemove,
onReauth,
onPauseToggle,
isRemoving,
isPausingAccount,
@@ -167,6 +169,12 @@ export function AccountItem({
Set as default
</DropdownMenuItem>
)}
{onReauth && (
<DropdownMenuItem onClick={onReauth}>
<RefreshCw className="w-4 h-4 mr-2" />
Reauthenticate
</DropdownMenuItem>
)}
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onClick={onRemove}
@@ -17,6 +17,7 @@ import { useTranslation } from 'react-i18next';
interface AccountsSectionProps {
accounts: OAuthAccount[];
onAddAccount: () => void;
onReauthAccount?: (account: OAuthAccount) => void;
onSetDefault: (accountId: string) => void;
onRemoveAccount: (accountId: string) => void;
onPauseToggle?: (accountId: string, paused: boolean) => void;
@@ -48,6 +49,7 @@ interface AccountsSectionProps {
export function AccountsSection({
accounts,
onAddAccount,
onReauthAccount,
onSetDefault,
onRemoveAccount,
onPauseToggle,
@@ -175,6 +177,7 @@ export function AccountsSection({
account={account}
onSetDefault={() => onSetDefault(account.id)}
onRemove={() => onRemoveAccount(account.id)}
onReauth={onReauthAccount ? () => onReauthAccount(account) : undefined}
onPauseToggle={
onPauseToggle ? (paused) => onPauseToggle(account.id, paused) : undefined
}
@@ -42,6 +42,7 @@ export function ProviderEditor({
defaultTarget,
topNotice,
onAddAccount,
onReauthAccount,
onSetDefault,
onRemoveAccount,
onPauseToggle,
@@ -282,6 +283,7 @@ export function ProviderEditor({
isDeletePending={deletePresetMutation.isPending}
accounts={accounts}
onAddAccount={onAddAccount}
onReauthAccount={onReauthAccount}
onSetDefault={onSetDefault}
onRemoveAccount={onRemoveAccount}
onPauseToggle={onPauseToggle}
@@ -40,6 +40,7 @@ interface ModelConfigTabProps {
isDeletePending?: boolean;
accounts: OAuthAccount[];
onAddAccount: () => void;
onReauthAccount?: (account: OAuthAccount) => void;
onSetDefault: (accountId: string) => void;
onRemoveAccount: (accountId: string) => void;
onPauseToggle?: (accountId: string, paused: boolean) => void;
@@ -82,6 +83,7 @@ export function ModelConfigTab({
isDeletePending,
accounts,
onAddAccount,
onReauthAccount,
onSetDefault,
onRemoveAccount,
onPauseToggle,
@@ -176,6 +178,7 @@ export function ModelConfigTab({
<AccountsSection
accounts={accounts}
onAddAccount={onAddAccount}
onReauthAccount={onReauthAccount}
onSetDefault={onSetDefault}
onRemoveAccount={onRemoveAccount}
onPauseToggle={onPauseToggle}
@@ -39,6 +39,7 @@ export interface ProviderEditorProps {
/** Optional contextual notice shown directly under the editor header */
topNotice?: ReactNode;
onAddAccount: () => void;
onReauthAccount?: (account: OAuthAccount) => void;
onSetDefault: (accountId: string) => void;
onRemoveAccount: (accountId: string) => void;
onPauseToggle?: (accountId: string, paused: boolean) => void;
@@ -63,6 +64,7 @@ export interface AccountItemProps {
account: OAuthAccount;
onSetDefault: () => void;
onRemove: () => void;
onReauth?: () => void;
onPauseToggle?: (paused: boolean) => void;
/** Solo mode: activate this account, pause all others */
onSoloMode?: () => void;
@@ -16,7 +16,7 @@ import {
usePauseAccount,
useResumeAccount,
} from '@/hooks/use-cliproxy';
import { Activity, CheckCircle2, XCircle, Radio } from 'lucide-react';
import { Activity, AlertTriangle, CheckCircle2, XCircle, Radio } from 'lucide-react';
import { useAuthMonitorData } from './hooks';
import { LivePulse } from './components/live-pulse';
@@ -86,6 +86,8 @@ export function AuthMonitor() {
const displayedSuccessRate = selectedProviderData
? getSuccessRate(selectedProviderData.successCount, selectedProviderData.failureCount)
: overallSuccessRate;
const showHighFailureGuidance =
displayedAccountCount >= 5 && displayedFailure >= 20 && displayedSuccessRate < 50;
const handlePauseToggle = (accountIds: string[], paused: boolean) => {
if (
@@ -204,6 +206,21 @@ export function AuthMonitor() {
/>
</div>
{showHighFailureGuidance && (
<div className="flex items-start gap-3 border-b border-amber-500/30 bg-amber-500/10 px-4 py-3 text-xs">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
<div className="space-y-1">
<p className="font-semibold text-foreground">High CLIProxy failure rate detected</p>
<p className="text-muted-foreground">
Select the affected provider, pause failing accounts, then check{' '}
<code className="rounded bg-muted px-1 py-0.5 font-mono">ccs cliproxy routing</code>.
For large Codex pools, prefer a smaller healthy active set and attach CLIProxy logs if
failures continue.
</p>
</div>
</div>
)}
{/* Flow Visualization */}
<div className="relative overflow-hidden">
{selectedProviderData ? (