mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-23 19:44:30 -07:00
Refactor Authentication Components and Hooks (#7750)
This commit is contained in:
committed by
GitHub
parent
d8dbe6271f
commit
7239c5cd9a
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* @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 { Colors } from '../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) => void;
|
||||
}
|
||||
|
||||
export function AuthDialog({
|
||||
config,
|
||||
settings,
|
||||
setAuthState,
|
||||
authError,
|
||||
onAuthError,
|
||||
}: AuthDialogProps): React.JSX.Element {
|
||||
let items = [
|
||||
{
|
||||
label: 'Login with Google',
|
||||
value: AuthType.LOGIN_WITH_GOOGLE,
|
||||
},
|
||||
...(process.env['CLOUD_SHELL'] === 'true'
|
||||
? [
|
||||
{
|
||||
label: 'Use Cloud Shell user credentials',
|
||||
value: AuthType.CLOUD_SHELL,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: 'Use Gemini API Key',
|
||||
value: AuthType.USE_GEMINI,
|
||||
},
|
||||
{ label: 'Vertex AI', value: AuthType.USE_VERTEX_AI },
|
||||
];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
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],
|
||||
);
|
||||
|
||||
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={Colors.Gray}
|
||||
flexDirection="column"
|
||||
padding={1}
|
||||
width="100%"
|
||||
>
|
||||
<Text bold>Get started</Text>
|
||||
<Box marginTop={1}>
|
||||
<Text>How would you like to authenticate for this project?</Text>
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<RadioButtonSelect
|
||||
items={items}
|
||||
initialIndex={initialAuthIndex}
|
||||
onSelect={handleAuthSelect}
|
||||
/>
|
||||
</Box>
|
||||
{authError && (
|
||||
<Box marginTop={1}>
|
||||
<Text color={Colors.AccentRed}>{authError}</Text>
|
||||
</Box>
|
||||
)}
|
||||
<Box marginTop={1}>
|
||||
<Text color={Colors.Gray}>(Use Enter to select)</Text>
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<Text>Terms of Services and Privacy Notice for Gemini CLI</Text>
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<Text color={Colors.AccentBlue}>
|
||||
{
|
||||
'https://github.com/google-gemini/gemini-cli/blob/main/docs/tos-privacy.md'
|
||||
}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user