mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 08:15:17 +00:00
fix(cliproxy): strengthen account safety warning and redesign alert UI
This commit is contained in:
1 parent
e4782c8393
commit
00495b6675
5 files changed
+181
-38
No files matched your search
@@ -165,9 +165,18 @@ export function warnCrossProviderDuplicates(provider: CLIProxyProvider): boolean
|
|||||||
|
|
||||||
console.error('');
|
console.error('');
|
||||||
console.error(warn('Account safety: cross-provider duplicate detected'));
|
console.error(warn('Account safety: cross-provider duplicate detected'));
|
||||||
console.error(' Same Google account across providers risks account bans (ref: #509).');
|
|
||||||
console.error(
|
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(` Details: ${ISSUE_509_URL}`);
|
||||||
console.error('');
|
console.error('');
|
||||||
@@ -177,8 +186,8 @@ export function warnCrossProviderDuplicates(provider: CLIProxyProvider): boolean
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.error('');
|
console.error('');
|
||||||
console.error(' Fix: pause duplicate with "ccs <provider> --pause <account>"');
|
console.error(' Immediate action: pause duplicate account and use separate Google accounts.');
|
||||||
console.error(' or use separate Google accounts per provider.');
|
console.error(' Fix command: "ccs cliproxy pause <account> --provider <provider>"');
|
||||||
console.error('');
|
console.error('');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -196,8 +205,19 @@ export function warnNewAccountConflict(
|
|||||||
console.error(
|
console.error(
|
||||||
` ${maskEmail(email)} is also registered under: ${conflictingProviders.join(', ')}`
|
` ${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(' 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(' Consider pausing the duplicate or using a different account.');
|
||||||
console.error(` Details: ${ISSUE_509_URL}`);
|
console.error(` Details: ${ISSUE_509_URL}`);
|
||||||
console.error('');
|
console.error('');
|
||||||
@@ -215,15 +235,18 @@ export function warnOAuthBanRisk(provider: CLIProxyProvider): void {
|
|||||||
|
|
||||||
shownBanWarnings.add(provider);
|
shownBanWarnings.add(provider);
|
||||||
console.error('');
|
console.error('');
|
||||||
console.error(warn('Account safety warning (#509)'));
|
console.error(warn('Account safety warning (#509 - read before continuing)'));
|
||||||
console.error(
|
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(
|
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(
|
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(` Details: ${ISSUE_509_URL}`);
|
||||||
console.error('');
|
console.error('');
|
||||||
@@ -247,11 +270,16 @@ export function warnPossible403Ban(provider: CLIProxyProvider, errorMessage: str
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.error('');
|
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(
|
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(` Details: ${ISSUE_509_URL}`);
|
||||||
console.error(` Error: "${truncate(errorMessage, 160)}"`);
|
console.error(` Error: "${truncate(errorMessage, 160)}"`);
|
||||||
console.error('');
|
console.error('');
|
||||||
|
|||||||
@@ -166,6 +166,9 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
|||||||
'Zero-config OAuth authentication via CLIProxy Plus',
|
'Zero-config OAuth authentication via CLIProxy Plus',
|
||||||
'First run: Browser opens for authentication, then model selection',
|
'First run: Browser opens for authentication, then model selection',
|
||||||
'Settings: ~/.ccs/{provider}.settings.json (created after auth)',
|
'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)'],
|
['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 { useKiroImport } from '@/hooks/use-cliproxy';
|
||||||
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
|
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
|
||||||
import { applyDefaultPreset } from '@/lib/preset-utils';
|
import { applyDefaultPreset } from '@/lib/preset-utils';
|
||||||
|
import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card';
|
||||||
import {
|
import {
|
||||||
DEFAULT_KIRO_AUTH_METHOD,
|
DEFAULT_KIRO_AUTH_METHOD,
|
||||||
getKiroAuthMethodOption,
|
getKiroAuthMethodOption,
|
||||||
@@ -59,12 +60,14 @@ export function AddAccountDialog({
|
|||||||
const [callbackUrl, setCallbackUrl] = useState('');
|
const [callbackUrl, setCallbackUrl] = useState('');
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [localError, setLocalError] = useState<string | null>(null);
|
const [localError, setLocalError] = useState<string | null>(null);
|
||||||
|
const [acknowledgedRisk, setAcknowledgedRisk] = useState(false);
|
||||||
const [kiroAuthMethod, setKiroAuthMethod] = useState<KiroAuthMethod>(DEFAULT_KIRO_AUTH_METHOD);
|
const [kiroAuthMethod, setKiroAuthMethod] = useState<KiroAuthMethod>(DEFAULT_KIRO_AUTH_METHOD);
|
||||||
const wasAuthenticatingRef = useRef(false);
|
const wasAuthenticatingRef = useRef(false);
|
||||||
const authFlow = useCliproxyAuthFlow();
|
const authFlow = useCliproxyAuthFlow();
|
||||||
const kiroImportMutation = useKiroImport();
|
const kiroImportMutation = useKiroImport();
|
||||||
|
|
||||||
const isKiro = provider === 'kiro';
|
const isKiro = provider === 'kiro';
|
||||||
|
const requiresSafetyAcknowledgement = provider === 'gemini' || provider === 'agy';
|
||||||
const defaultDeviceCode = isDeviceCodeProvider(provider);
|
const defaultDeviceCode = isDeviceCodeProvider(provider);
|
||||||
const requiresNickname = isNicknameRequiredProvider(provider);
|
const requiresNickname = isNicknameRequiredProvider(provider);
|
||||||
const kiroMethodOption = getKiroAuthMethodOption(kiroAuthMethod);
|
const kiroMethodOption = getKiroAuthMethodOption(kiroAuthMethod);
|
||||||
@@ -78,11 +81,19 @@ export function AddAccountDialog({
|
|||||||
setCallbackUrl('');
|
setCallbackUrl('');
|
||||||
setCopied(false);
|
setCopied(false);
|
||||||
setLocalError(null);
|
setLocalError(null);
|
||||||
|
setAcknowledgedRisk(false);
|
||||||
setKiroAuthMethod(DEFAULT_KIRO_AUTH_METHOD);
|
setKiroAuthMethod(DEFAULT_KIRO_AUTH_METHOD);
|
||||||
wasAuthenticatingRef.current = false;
|
wasAuthenticatingRef.current = false;
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setAcknowledgedRisk(false);
|
||||||
|
setLocalError(null);
|
||||||
|
}
|
||||||
|
}, [provider, open]);
|
||||||
|
|
||||||
// When authFlow completes successfully (polling detected success), apply preset and close
|
// When authFlow completes successfully (polling detected success), apply preset and close
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authFlow.isAuthenticating && !authFlow.error && authFlow.provider === null && open) {
|
if (!authFlow.isAuthenticating && !authFlow.error && authFlow.provider === null && open) {
|
||||||
@@ -131,6 +142,12 @@ export function AddAccountDialog({
|
|||||||
* - Authorization code providers use /start-url and polling.
|
* - Authorization code providers use /start-url and polling.
|
||||||
*/
|
*/
|
||||||
const handleAuthenticate = () => {
|
const handleAuthenticate = () => {
|
||||||
|
if (requiresSafetyAcknowledgement && !acknowledgedRisk) {
|
||||||
|
setLocalError(
|
||||||
|
'Please acknowledge the account safety warning before authenticating this provider.'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (requiresNickname && !nicknameTrimmed) {
|
if (requiresNickname && !nicknameTrimmed) {
|
||||||
setLocalError(`Nickname is required for ${displayName} accounts.`);
|
setLocalError(`Nickname is required for ${displayName} accounts.`);
|
||||||
return;
|
return;
|
||||||
@@ -187,6 +204,18 @@ export function AddAccountDialog({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
|
{requiresSafetyAcknowledgement && !showAuthUI && (
|
||||||
|
<AccountSafetyWarningCard
|
||||||
|
showAcknowledgement
|
||||||
|
acknowledged={acknowledgedRisk}
|
||||||
|
onAcknowledgedChange={(value) => {
|
||||||
|
setAcknowledgedRisk(value);
|
||||||
|
setLocalError(null);
|
||||||
|
}}
|
||||||
|
disabled={isPending}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Kiro auth method */}
|
{/* Kiro auth method */}
|
||||||
{isKiro && !showAuthUI && (
|
{isKiro && !showAuthUI && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -374,7 +403,11 @@ export function AddAccountDialog({
|
|||||||
{!showAuthUI && (
|
{!showAuthUI && (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAuthenticate}
|
onClick={handleAuthenticate}
|
||||||
disabled={isPending || (requiresNickname && !nicknameTrimmed)}
|
disabled={
|
||||||
|
isPending ||
|
||||||
|
(requiresNickname && !nicknameTrimmed) ||
|
||||||
|
(requiresSafetyAcknowledgement && !acknowledgedRisk)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ExternalLink className="w-4 h-4 mr-2" />
|
<ExternalLink className="w-4 h-4 mr-2" />
|
||||||
Authenticate
|
Authenticate
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import { useState, useMemo } from 'react';
|
|||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
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 { QuickSetupWizard } from '@/components/quick-setup-wizard';
|
||||||
import { AddAccountDialog } from '@/components/account/add-account-dialog';
|
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 { ProviderEditor } from '@/components/cliproxy/provider-editor';
|
||||||
import { ProviderLogo } from '@/components/cliproxy/provider-logo';
|
import { ProviderLogo } from '@/components/cliproxy/provider-logo';
|
||||||
import { ProxyStatusWidget } from '@/components/monitoring/proxy-status-widget';
|
import { ProxyStatusWidget } from '@/components/monitoring/proxy-status-widget';
|
||||||
@@ -394,29 +394,7 @@ export function CliproxyPage() {
|
|||||||
|
|
||||||
{/* Right Panel */}
|
{/* Right Panel */}
|
||||||
<div className="flex-1 flex flex-col min-w-0 bg-background">
|
<div className="flex-1 flex flex-col min-w-0 bg-background">
|
||||||
{showAccountSafetyWarning && (
|
{showAccountSafetyWarning && <AccountSafetyWarningCard className="mx-4 mt-4" />}
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedVariantData && parentAuthForVariant ? (
|
{selectedVariantData && parentAuthForVariant ? (
|
||||||
// Variant selected - show ProviderEditor with variant profile name
|
// Variant selected - show ProviderEditor with variant profile name
|
||||||
|
|||||||
Reference in new issue
Block a user