fix(cliproxy): strengthen account safety warning and redesign alert UI

This commit is contained in:
Tam Nhu Tran
2026-02-24 12:01:07 +07:00
parent e4782c8393
commit 00495b6675
5 changed files with 181 additions and 38 deletions
+40 -12
View File
@@ -165,9 +165,18 @@ export function warnCrossProviderDuplicates(provider: CLIProxyProvider): boolean
console.error('');
console.error(warn('Account safety: cross-provider duplicate detected'));
console.error(' Same Google account across providers risks account bans (ref: #509).');
console.error(
' If provider requests start returning 403/Forbidden, treat it as a possible ban.'
' Same Google account across "ccs gemini" + "ccs agy" is a known suspension/ban risk (ref: #509).'
);
console.error(' This risk applies to both CLI sessions and accounts added from "ccs config".');
console.error(
' If provider requests start returning 403/Forbidden, treat it as a possible account disable/ban.'
);
console.error(
' If you want to keep Google AI access on this account, do not continue this shared-account setup.'
);
console.error(
' CCS is provided as-is and cannot take responsibility for suspension/ban/access-loss decisions.'
);
console.error(` Details: ${ISSUE_509_URL}`);
console.error('');
@@ -177,8 +186,8 @@ export function warnCrossProviderDuplicates(provider: CLIProxyProvider): boolean
}
console.error('');
console.error(' Fix: pause duplicate with "ccs <provider> --pause <account>"');
console.error(' or use separate Google accounts per provider.');
console.error(' Immediate action: pause duplicate account and use separate Google accounts.');
console.error(' Fix command: "ccs cliproxy pause <account> --provider <provider>"');
console.error('');
return true;
@@ -196,8 +205,19 @@ export function warnNewAccountConflict(
console.error(
` ${maskEmail(email)} is also registered under: ${conflictingProviders.join(', ')}`
);
console.error(' Concurrent usage may cause Google to ban your account.');
console.error(
' Reusing one Google account between "ccs gemini" and "ccs agy" can trigger bans.'
);
console.error(
' This applies to both CLI auth and "ccs config" dashboard auth for these providers.'
);
console.error(' 403/Forbidden responses can be an early sign of account disablement.');
console.error(
' If you want to keep Google AI access, do not continue with this shared-account setup.'
);
console.error(
' CCS is provided as-is and cannot take responsibility for suspension/ban/access-loss decisions.'
);
console.error(' Consider pausing the duplicate or using a different account.');
console.error(` Details: ${ISSUE_509_URL}`);
console.error('');
@@ -215,15 +235,18 @@ export function warnOAuthBanRisk(provider: CLIProxyProvider): void {
shownBanWarnings.add(provider);
console.error('');
console.error(warn('Account safety warning (#509)'));
console.error(warn('Account safety warning (#509 - read before continuing)'));
console.error(
' Using the same Google account in both "ccs gemini" and "ccs agy" can trigger suspension.'
' Known risk: one Google account shared by "ccs gemini" + "ccs agy" can be disabled/banned.'
);
console.error(
' If you see 403/Forbidden during provider calls, treat it as likely account disable/ban.'
' This risk applies whether auth was done from CLI or from "ccs config" dashboard.'
);
console.error(
' Use separate Google accounts per provider and stop retrying blocked accounts.'
' If you want to keep Google AI access, do not continue with this shared-account setup.'
);
console.error(
' CCS is provided as-is and cannot take responsibility for suspension/ban/access-loss decisions.'
);
console.error(` Details: ${ISSUE_509_URL}`);
console.error('');
@@ -247,11 +270,16 @@ export function warnPossible403Ban(provider: CLIProxyProvider, errorMessage: str
}
console.error('');
console.error(warn(`Account safety: ${provider} returned 403/Forbidden`));
console.error(warn(`Account safety: ${provider} returned 403/Forbidden (possible disable/ban)`));
console.error(
' For gemini/agy flows this often means the Google account was blocked/disabled.'
' For gemini/agy flows this often means Google blocked or disabled the account.'
);
console.error(
' If you want to keep Google AI access, stop using this account/provider pairing immediately.'
);
console.error(
' CCS is provided as-is and cannot take responsibility for suspension/ban/access-loss decisions.'
);
console.error(' Stop retries for this account and switch to a different account/provider.');
console.error(` Details: ${ISSUE_509_URL}`);
console.error(` Error: "${truncate(errorMessage, 160)}"`);
console.error('');
+3
View File
@@ -166,6 +166,9 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
'Zero-config OAuth authentication via CLIProxy Plus',
'First run: Browser opens for authentication, then model selection',
'Settings: ~/.ccs/{provider}.settings.json (created after auth)',
'Safety: do not reuse one Google account across "ccs gemini" and "ccs agy" (issue #509)',
'If you want to keep Google AI access, do not continue this shared-account setup',
'CCS is as-is and does not take responsibility for account bans/access loss',
],
[
['ccs gemini', 'Google Gemini (gemini-2.5-pro or 3-pro)'],
@@ -0,0 +1,101 @@
import { AlertTriangle, ExternalLink } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils';
interface AccountSafetyWarningCardProps {
className?: string;
showAcknowledgement?: boolean;
acknowledged?: boolean;
onAcknowledgedChange?: (value: boolean) => void;
disabled?: boolean;
}
export function AccountSafetyWarningCard({
className,
showAcknowledgement = false,
acknowledged = false,
onAcknowledgedChange,
disabled = false,
}: AccountSafetyWarningCardProps) {
return (
<section
role="alert"
className={cn(
'relative overflow-hidden rounded-xl border border-amber-500/30 bg-gradient-to-br from-amber-50 via-background to-rose-50/70 shadow-sm dark:from-amber-950/20 dark:to-rose-950/20',
className
)}
>
<div className="absolute inset-x-0 top-0 h-0.5 bg-gradient-to-r from-amber-500 via-orange-500 to-rose-500" />
<div className="space-y-3 p-4">
<div className="flex items-start justify-between gap-3">
<div className="flex items-start gap-2.5">
<div className="mt-0.5 inline-flex h-7 w-7 items-center justify-center rounded-md bg-amber-500/15 text-amber-700 dark:text-amber-400">
<AlertTriangle className="h-4 w-4" />
</div>
<div>
<p className="text-sm font-semibold leading-5">Account Safety Warning</p>
<p className="text-xs text-muted-foreground">
Issue #509 · Shared Gemini + AGY account risk
</p>
</div>
</div>
<Badge
variant="outline"
className="border-amber-500/40 text-amber-700 dark:text-amber-300"
>
High Risk
</Badge>
</div>
<div className="space-y-2 text-sm leading-relaxed">
<p>
Using one Google account for both <code className="font-mono">ccs gemini</code> and{' '}
<code className="font-mono">ccs agy</code> can trigger account disable/ban.
</p>
<p className="font-medium text-amber-900 dark:text-amber-200">
If you want to keep Google AI access, do not continue this shared-account setup.
</p>
<p className="text-xs text-muted-foreground">
CCS is provided as-is and does not take responsibility for suspension, bans, or access
loss from upstream providers.
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<a
href="https://github.com/kaitranntt/ccs/issues/509"
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 rounded-md border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-xs font-medium text-amber-800 transition-colors hover:bg-amber-500/15 dark:text-amber-200"
>
Read issue #509
<ExternalLink className="h-3.5 w-3.5" />
</a>
<span className="rounded-md border border-border/70 bg-muted/60 px-2.5 py-1 text-xs text-muted-foreground">
Applies to CLI and dashboard auth
</span>
</div>
{showAcknowledgement && onAcknowledgedChange && (
<div className="rounded-lg border border-amber-500/25 bg-amber-500/5 p-2.5">
<div className="flex items-start gap-2">
<Checkbox
id="account-risk-ack"
checked={acknowledged}
onCheckedChange={(checked) => onAcknowledgedChange(Boolean(checked))}
disabled={disabled}
/>
<Label htmlFor="account-risk-ack" className="text-xs leading-5">
I understand this risk and that CCS takes no responsibility if I continue this
setup.
</Label>
</div>
</div>
)}
</div>
</section>
);
}
@@ -29,6 +29,7 @@ import { Loader2, ExternalLink, User, Download, Copy, Check } from 'lucide-react
import { useKiroImport } from '@/hooks/use-cliproxy';
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
import { applyDefaultPreset } from '@/lib/preset-utils';
import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card';
import {
DEFAULT_KIRO_AUTH_METHOD,
getKiroAuthMethodOption,
@@ -59,12 +60,14 @@ export function AddAccountDialog({
const [callbackUrl, setCallbackUrl] = useState('');
const [copied, setCopied] = useState(false);
const [localError, setLocalError] = useState<string | null>(null);
const [acknowledgedRisk, setAcknowledgedRisk] = useState(false);
const [kiroAuthMethod, setKiroAuthMethod] = useState<KiroAuthMethod>(DEFAULT_KIRO_AUTH_METHOD);
const wasAuthenticatingRef = useRef(false);
const authFlow = useCliproxyAuthFlow();
const kiroImportMutation = useKiroImport();
const isKiro = provider === 'kiro';
const requiresSafetyAcknowledgement = provider === 'gemini' || provider === 'agy';
const defaultDeviceCode = isDeviceCodeProvider(provider);
const requiresNickname = isNicknameRequiredProvider(provider);
const kiroMethodOption = getKiroAuthMethodOption(kiroAuthMethod);
@@ -78,11 +81,19 @@ export function AddAccountDialog({
setCallbackUrl('');
setCopied(false);
setLocalError(null);
setAcknowledgedRisk(false);
setKiroAuthMethod(DEFAULT_KIRO_AUTH_METHOD);
wasAuthenticatingRef.current = false;
onClose();
};
useEffect(() => {
if (open) {
setAcknowledgedRisk(false);
setLocalError(null);
}
}, [provider, open]);
// When authFlow completes successfully (polling detected success), apply preset and close
useEffect(() => {
if (!authFlow.isAuthenticating && !authFlow.error && authFlow.provider === null && open) {
@@ -131,6 +142,12 @@ export function AddAccountDialog({
* - Authorization code providers use /start-url and polling.
*/
const handleAuthenticate = () => {
if (requiresSafetyAcknowledgement && !acknowledgedRisk) {
setLocalError(
'Please acknowledge the account safety warning before authenticating this provider.'
);
return;
}
if (requiresNickname && !nicknameTrimmed) {
setLocalError(`Nickname is required for ${displayName} accounts.`);
return;
@@ -187,6 +204,18 @@ export function AddAccountDialog({
</DialogHeader>
<div className="space-y-4 py-4">
{requiresSafetyAcknowledgement && !showAuthUI && (
<AccountSafetyWarningCard
showAcknowledgement
acknowledged={acknowledgedRisk}
onAcknowledgedChange={(value) => {
setAcknowledgedRisk(value);
setLocalError(null);
}}
disabled={isPending}
/>
)}
{/* Kiro auth method */}
{isKiro && !showAuthUI && (
<div className="space-y-2">
@@ -374,7 +403,11 @@ export function AddAccountDialog({
{!showAuthUI && (
<Button
onClick={handleAuthenticate}
disabled={isPending || (requiresNickname && !nicknameTrimmed)}
disabled={
isPending ||
(requiresNickname && !nicknameTrimmed) ||
(requiresSafetyAcknowledgement && !acknowledgedRisk)
}
>
<ExternalLink className="w-4 h-4 mr-2" />
Authenticate
+3 -25
View File
@@ -8,12 +8,12 @@ import { useState, useMemo } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Skeleton } from '@/components/ui/skeleton';
import { Check, X, RefreshCw, Sparkles, Zap, GitBranch, Trash2, AlertTriangle } from 'lucide-react';
import { Check, X, RefreshCw, Sparkles, Zap, GitBranch, Trash2 } from 'lucide-react';
import { QuickSetupWizard } from '@/components/quick-setup-wizard';
import { AddAccountDialog } from '@/components/account/add-account-dialog';
import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card';
import { ProviderEditor } from '@/components/cliproxy/provider-editor';
import { ProviderLogo } from '@/components/cliproxy/provider-logo';
import { ProxyStatusWidget } from '@/components/monitoring/proxy-status-widget';
@@ -394,29 +394,7 @@ export function CliproxyPage() {
{/* Right Panel */}
<div className="flex-1 flex flex-col min-w-0 bg-background">
{showAccountSafetyWarning && (
<div className="px-4 pt-4">
<Alert variant="warning" className="py-2">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Account Safety Warning (Issue #509)</AlertTitle>
<AlertDescription>
Using the same Google account in both <code className="font-mono">ccs gemini</code>{' '}
and <code className="font-mono">ccs agy</code> can trigger suspension. If CLI shows{' '}
<code className="font-mono">403 Forbidden</code>, treat it as likely account
disable/ban and switch accounts.{' '}
<a
href="https://github.com/kaitranntt/ccs/issues/509"
target="_blank"
rel="noreferrer"
className="underline font-medium"
>
Read issue #509
</a>
.
</AlertDescription>
</Alert>
</div>
)}
{showAccountSafetyWarning && <AccountSafetyWarningCard className="mx-4 mt-4" />}
{selectedVariantData && parentAuthForVariant ? (
// Variant selected - show ProviderEditor with variant profile name