Files
gemini-cli/packages/cli/src/ui/auth/AuthDialog.tsx

207 lines
5.5 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { useCallback } from 'react';
import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
import type { LoadedSettings } from '../../config/settings.js';
import { SettingScope } from '../../config/settings.js';
import {
AuthType,
clearCachedCredentialFile,
type Config,
} from '@google/gemini-cli-core';
import { useKeypress } from '../hooks/useKeypress.js';
import { AuthState } from '../types.js';
import { runExitCleanup } from '../../utils/cleanup.js';
import { validateAuthMethodWithSettings } from './useAuth.js';
interface AuthDialogProps {
config: Config;
settings: LoadedSettings;
setAuthState: (state: AuthState) => void;
authError: string | null;
onAuthError: (error: string | null) => void;
}
export function AuthDialog({
config,
settings,
setAuthState,
authError,
onAuthError,
}: AuthDialogProps): React.JSX.Element {
2025-09-03 15:33:37 -07:00
let items = [
{
label: 'Login with Google',
value: AuthType.LOGIN_WITH_GOOGLE,
key: AuthType.LOGIN_WITH_GOOGLE,
},
...(process.env['CLOUD_SHELL'] === 'true'
? [
{
label: 'Use Cloud Shell user credentials',
value: AuthType.CLOUD_SHELL,
key: AuthType.CLOUD_SHELL,
},
]
: []),
{
label: 'Use Gemini API Key',
value: AuthType.USE_GEMINI,
key: AuthType.USE_GEMINI,
},
{
label: 'Vertex AI',
value: AuthType.USE_VERTEX_AI,
key: AuthType.USE_VERTEX_AI,
},
];
2025-09-03 15:33:37 -07:00
if (settings.merged.security?.auth?.enforcedType) {
items = items.filter(
(item) => item.value === settings.merged.security?.auth?.enforcedType,
);
}
let defaultAuthType = null;
const defaultAuthTypeEnv = process.env['GEMINI_DEFAULT_AUTH_TYPE'];
if (
defaultAuthTypeEnv &&
Object.values(AuthType).includes(defaultAuthTypeEnv as AuthType)
) {
defaultAuthType = defaultAuthTypeEnv as AuthType;
}
2025-09-03 15:33:37 -07:00
let initialAuthIndex = items.findIndex((item) => {
if (settings.merged.security?.auth?.selectedType) {
return item.value === settings.merged.security.auth.selectedType;
}
if (defaultAuthType) {
return item.value === defaultAuthType;
}
if (process.env['GEMINI_API_KEY']) {
return item.value === AuthType.USE_GEMINI;
}
return item.value === AuthType.LOGIN_WITH_GOOGLE;
});
2025-09-03 15:33:37 -07:00
if (settings.merged.security?.auth?.enforcedType) {
initialAuthIndex = 0;
}
const onSelect = useCallback(
async (authType: AuthType | undefined, scope: SettingScope) => {
if (authType) {
await clearCachedCredentialFile();
settings.setValue(scope, 'security.auth.selectedType', authType);
if (
authType === AuthType.LOGIN_WITH_GOOGLE &&
config.isBrowserLaunchSuppressed()
) {
runExitCleanup();
console.log(
`
----------------------------------------------------------------
Logging in with Google... Please restart Gemini CLI to continue.
----------------------------------------------------------------
`,
);
process.exit(0);
}
}
setAuthState(AuthState.Unauthenticated);
},
[settings, config, setAuthState],
);
2025-06-30 15:53:05 -07:00
const handleAuthSelect = (authMethod: AuthType) => {
const error = validateAuthMethodWithSettings(authMethod, settings);
if (error) {
onAuthError(error);
} else {
onSelect(authMethod, SettingScope.User);
}
};
useKeypress(
(key) => {
if (key.name === 'escape') {
// Prevent exit if there is an error message.
// This means they user is not authenticated yet.
if (authError) {
return;
}
if (settings.merged.security?.auth?.selectedType === undefined) {
// Prevent exiting if no auth method is set
onAuthError(
'You must select an auth method to proceed. Press Ctrl+C twice to exit.',
);
return;
}
onSelect(undefined, SettingScope.User);
}
},
{ isActive: true },
);
return (
<Box
borderStyle="round"
borderColor={theme.border.default}
flexDirection="column"
padding={1}
width="100%"
>
2025-09-11 14:22:16 -07:00
<Text bold color={theme.text.primary}>
Get started
</Text>
<Box marginTop={1}>
<Text color={theme.text.primary}>
How would you like to authenticate for this project?
</Text>
</Box>
<Box marginTop={1}>
<RadioButtonSelect
items={items}
initialIndex={initialAuthIndex}
onSelect={handleAuthSelect}
onHighlight={() => {
onAuthError(null);
}}
/>
</Box>
{authError && (
<Box marginTop={1}>
<Text color={theme.status.error}>{authError}</Text>
</Box>
)}
<Box marginTop={1}>
<Text color={theme.text.secondary}>
(Use Enter to select, Esc to close)
</Text>
</Box>
<Box marginTop={1}>
<Text color={theme.text.primary}>
Terms of Services and Privacy Notice for Gemini CLI
</Text>
</Box>
<Box marginTop={1}>
<Text color={theme.text.link}>
{
'https://github.com/google-gemini/gemini-cli/blob/main/docs/tos-privacy.md'
}
</Text>
</Box>
</Box>
);
}