mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 10:34:35 -07:00
fix(core, a2a-server): prevent hang during OAuth in non-interactive sessions (#21045)
This commit is contained in:
@@ -40,7 +40,10 @@ import { FORCE_ENCRYPTED_FILE_ENV_VAR } from '../mcp/token-storage/index.js';
|
||||
import { GEMINI_DIR, homedir as pathsHomedir } from '../utils/paths.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { writeToStdout } from '../utils/stdio.js';
|
||||
import { FatalCancellationError } from '../utils/errors.js';
|
||||
import {
|
||||
FatalCancellationError,
|
||||
FatalAuthenticationError,
|
||||
} from '../utils/errors.js';
|
||||
import process from 'node:process';
|
||||
import { coreEvents } from '../utils/events.js';
|
||||
import { isHeadlessMode } from '../utils/headless.js';
|
||||
@@ -107,6 +110,7 @@ const mockConfig = {
|
||||
getProxy: () => 'http://test.proxy.com:8080',
|
||||
isBrowserLaunchSuppressed: () => false,
|
||||
getExperimentalZedIntegration: () => false,
|
||||
isInteractive: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
// Mock fetch globally
|
||||
@@ -316,11 +320,31 @@ describe('oauth2', () => {
|
||||
await eventPromise;
|
||||
});
|
||||
|
||||
it('should throw FatalAuthenticationError in non-interactive session when manual auth is required', async () => {
|
||||
const mockConfigNonInteractive = {
|
||||
getNoBrowser: () => true,
|
||||
getProxy: () => 'http://test.proxy.com:8080',
|
||||
isBrowserLaunchSuppressed: () => true,
|
||||
isInteractive: () => false,
|
||||
} as unknown as Config;
|
||||
|
||||
await expect(
|
||||
getOauthClient(AuthType.LOGIN_WITH_GOOGLE, mockConfigNonInteractive),
|
||||
).rejects.toThrow(FatalAuthenticationError);
|
||||
|
||||
await expect(
|
||||
getOauthClient(AuthType.LOGIN_WITH_GOOGLE, mockConfigNonInteractive),
|
||||
).rejects.toThrow(
|
||||
'Manual authorization is required but the current session is non-interactive.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should perform login with user code', async () => {
|
||||
const mockConfigWithNoBrowser = {
|
||||
getNoBrowser: () => true,
|
||||
getProxy: () => 'http://test.proxy.com:8080',
|
||||
isBrowserLaunchSuppressed: () => true,
|
||||
isInteractive: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
const mockCodeVerifier = {
|
||||
@@ -391,6 +415,7 @@ describe('oauth2', () => {
|
||||
getNoBrowser: () => true,
|
||||
getProxy: () => 'http://test.proxy.com:8080',
|
||||
isBrowserLaunchSuppressed: () => true,
|
||||
isInteractive: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
const mockCodeVerifier = {
|
||||
@@ -1171,6 +1196,7 @@ describe('oauth2', () => {
|
||||
getNoBrowser: () => true,
|
||||
getProxy: () => 'http://test.proxy.com:8080',
|
||||
isBrowserLaunchSuppressed: () => true,
|
||||
isInteractive: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
const mockOAuth2Client = {
|
||||
|
||||
@@ -226,6 +226,13 @@ async function initOauthClient(
|
||||
}
|
||||
|
||||
if (config.isBrowserLaunchSuppressed()) {
|
||||
if (!config.isInteractive()) {
|
||||
throw new FatalAuthenticationError(
|
||||
'Manual authorization is required but the current session is non-interactive. ' +
|
||||
'Please run the Gemini CLI in an interactive terminal to log in, ' +
|
||||
'provide a GEMINI_API_KEY, or ensure Application Default Credentials are configured.',
|
||||
);
|
||||
}
|
||||
let success = false;
|
||||
const maxRetries = 2;
|
||||
// Enter alternate buffer
|
||||
@@ -412,14 +419,24 @@ async function authWithUserCode(client: OAuth2Client): Promise<boolean> {
|
||||
'\n\n',
|
||||
);
|
||||
|
||||
const code = await new Promise<string>((resolve, _) => {
|
||||
const code = await new Promise<string>((resolve, reject) => {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: createWorkingStdio().stdout,
|
||||
terminal: true,
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
rl.close();
|
||||
reject(
|
||||
new FatalAuthenticationError(
|
||||
'Authorization timed out after 5 minutes.',
|
||||
),
|
||||
);
|
||||
}, 300000); // 5 minute timeout
|
||||
|
||||
rl.question('Enter the authorization code: ', (code) => {
|
||||
clearTimeout(timeout);
|
||||
rl.close();
|
||||
resolve(code.trim());
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user