mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 16:16:52 +00:00
Merge pull request #620 from kaitranntt/kai/fix/account-safety-alert-design
fix(cliproxy): strengthen account safety warnings and redesign alert section
This commit is contained in:
@@ -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('');
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user