mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
@@ -80,6 +80,69 @@ describe('requestPasteCallbackStart', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Gemini Plus OAuth credential diagnostics', () => {
|
||||
it('fails fast when Gemini uses Plus without OAuth client env', async () => {
|
||||
const { getGeminiPlusOAuthCredentialError } = await import(
|
||||
`../oauth-handler?gemini-plus-missing-env=${Date.now()}`
|
||||
);
|
||||
|
||||
const error = getGeminiPlusOAuthCredentialError('gemini', 'plus', {});
|
||||
|
||||
expect(error).toContain('Gemini OAuth from CLIProxy Plus is missing');
|
||||
expect(error).toContain('CLIPROXY_GEMINI_OAUTH_CLIENT_ID');
|
||||
expect(error).toContain('CLIPROXY_GEMINI_OAUTH_CLIENT_SECRET');
|
||||
expect(error).toContain('cliproxy.backend');
|
||||
expect(error).toContain('original');
|
||||
});
|
||||
|
||||
it('allows Gemini Plus when both OAuth client env values exist', async () => {
|
||||
const { getGeminiPlusOAuthCredentialError } = await import(
|
||||
`../oauth-handler?gemini-plus-env-present=${Date.now()}`
|
||||
);
|
||||
|
||||
expect(
|
||||
getGeminiPlusOAuthCredentialError('gemini', 'plus', {
|
||||
CLIPROXY_GEMINI_OAUTH_CLIENT_ID: 'client-id',
|
||||
CLIPROXY_GEMINI_OAUTH_CLIENT_SECRET: 'client-secret',
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('does not warn for Gemini on the original backend', async () => {
|
||||
const { getGeminiPlusOAuthCredentialError } = await import(
|
||||
`../oauth-handler?gemini-original-backend=${Date.now()}`
|
||||
);
|
||||
|
||||
expect(getGeminiPlusOAuthCredentialError('gemini', 'original', {})).toBeNull();
|
||||
});
|
||||
|
||||
it('detects Gemini auth URLs missing client_id before display', async () => {
|
||||
const { getGeminiAuthUrlCredentialError } = await import(
|
||||
`../oauth-handler?gemini-auth-url-missing-client=${Date.now()}`
|
||||
);
|
||||
|
||||
const error = getGeminiAuthUrlCredentialError(
|
||||
'gemini',
|
||||
'https://accounts.google.com/o/oauth2/v2/auth?client_id=&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Foauth2callback&state=test'
|
||||
);
|
||||
|
||||
expect(error).toContain('Gemini OAuth from CLIProxy Plus is missing');
|
||||
});
|
||||
|
||||
it('allows Gemini auth URLs with client_id present', async () => {
|
||||
const { getGeminiAuthUrlCredentialError } = await import(
|
||||
`../oauth-handler?gemini-auth-url-client-present=${Date.now()}`
|
||||
);
|
||||
|
||||
expect(
|
||||
getGeminiAuthUrlCredentialError(
|
||||
'gemini',
|
||||
'https://accounts.google.com/o/oauth2/v2/auth?client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Foauth2callback&state=test'
|
||||
)
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('usesKiroLocalCallbackReplay', () => {
|
||||
it('limits local callback replay to CLI auth-code flows', async () => {
|
||||
const { usesKiroLocalCallbackReplay } = await import(
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
import * as fs from 'fs';
|
||||
import { fail, info, warn, color, ok } from '../../utils/ui';
|
||||
import { createLogger } from '../../services/logging';
|
||||
import { ensureCLIProxyBinary } from '../binary-manager';
|
||||
import { ensureCLIProxyBinary, getStoredConfiguredBackend } from '../binary-manager';
|
||||
import { generateConfig } from '../config/config-generator';
|
||||
import { CLIProxyProvider } from '../types';
|
||||
import { CLIProxyBackend, CLIProxyProvider } from '../types';
|
||||
import {
|
||||
AccountInfo,
|
||||
getProviderAccounts,
|
||||
@@ -82,9 +82,54 @@ interface PasteCallbackStartData {
|
||||
|
||||
const PASTE_CALLBACK_AUTH_URL_POLL_INTERVAL_MS = 3000;
|
||||
const POLLED_AUTH_LOCAL_TOKEN_GRACE_MS = 15 * 1000;
|
||||
const GEMINI_PLUS_CLIENT_ID_ENV = 'CLIPROXY_GEMINI_OAUTH_CLIENT_ID';
|
||||
const GEMINI_PLUS_CLIENT_SECRET_ENV = 'CLIPROXY_GEMINI_OAUTH_CLIENT_SECRET';
|
||||
|
||||
const logger = createLogger('cliproxy:auth:oauth');
|
||||
|
||||
function buildGeminiPlusOAuthCredentialMessage(missing?: string[]): string {
|
||||
const missingText = missing?.length ? ` Missing: ${missing.join(', ')}.` : '';
|
||||
return (
|
||||
'Gemini OAuth from CLIProxy Plus is missing Google OAuth client credentials.' +
|
||||
missingText +
|
||||
` Set ${GEMINI_PLUS_CLIENT_ID_ENV} and ${GEMINI_PLUS_CLIENT_SECRET_ENV} before starting CLIProxy Plus,` +
|
||||
' or switch `cliproxy.backend` to `original` for Gemini.'
|
||||
);
|
||||
}
|
||||
|
||||
export function getGeminiPlusOAuthCredentialError(
|
||||
provider: CLIProxyProvider,
|
||||
backend: CLIProxyBackend,
|
||||
env: NodeJS.ProcessEnv = process.env
|
||||
): string | null {
|
||||
if (provider !== 'gemini' || backend !== 'plus') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const missing = [GEMINI_PLUS_CLIENT_ID_ENV, GEMINI_PLUS_CLIENT_SECRET_ENV].filter(
|
||||
(name) => !env[name]?.trim()
|
||||
);
|
||||
|
||||
return missing.length > 0 ? buildGeminiPlusOAuthCredentialMessage(missing) : null;
|
||||
}
|
||||
|
||||
export function getGeminiAuthUrlCredentialError(
|
||||
provider: CLIProxyProvider,
|
||||
authUrl: string
|
||||
): string | null {
|
||||
if (provider !== 'gemini') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(authUrl);
|
||||
const clientId = parsed.searchParams.get('client_id')?.trim();
|
||||
return clientId ? null : buildGeminiPlusOAuthCredentialMessage();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function requestPasteCallbackStart(
|
||||
provider: CLIProxyProvider,
|
||||
target: ProxyTarget,
|
||||
@@ -549,6 +594,12 @@ async function handlePasteCallbackMode(
|
||||
return null;
|
||||
}
|
||||
|
||||
const authUrlCredentialError = getGeminiAuthUrlCredentialError(provider, authUrl);
|
||||
if (authUrlCredentialError) {
|
||||
console.log(fail(authUrlCredentialError));
|
||||
return null;
|
||||
}
|
||||
|
||||
const oauthState = startData.state || parseAuthUrlState(authUrl);
|
||||
const knownTokenFiles = listProviderTokenSnapshots(provider, tokenDir);
|
||||
|
||||
@@ -920,6 +971,17 @@ export async function triggerOAuth(
|
||||
const useSelectedKiroDirectCliFlow =
|
||||
provider === 'kiro' && (isDeviceCodeFlow || useSelectedKiroLocalPasteCallback);
|
||||
|
||||
if (!(selectedPasteCallback && !useSelectedKiroDirectCliFlow)) {
|
||||
const credentialError = getGeminiPlusOAuthCredentialError(
|
||||
provider,
|
||||
getStoredConfiguredBackend()
|
||||
);
|
||||
if (credentialError) {
|
||||
console.log(fail(credentialError));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingAccounts.length > 0 && !add) {
|
||||
console.log('');
|
||||
console.log(
|
||||
|
||||
Reference in New Issue
Block a user