mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
Add enforcedAuthType setting (#6564)
This commit is contained in:
@@ -240,6 +240,10 @@ Settings are organized into categories. All settings should be placed within the
|
|||||||
- **Description:** The currently selected authentication type.
|
- **Description:** The currently selected authentication type.
|
||||||
- **Default:** `undefined`
|
- **Default:** `undefined`
|
||||||
|
|
||||||
|
- **`security.auth.enforcedType`** (string):
|
||||||
|
- **Description:** The required auth type (useful for enterprises).
|
||||||
|
- **Default:** `undefined`
|
||||||
|
|
||||||
- **`security.auth.useExternal`** (boolean):
|
- **`security.auth.useExternal`** (boolean):
|
||||||
- **Description:** Whether to use an external authentication flow.
|
- **Description:** Whether to use an external authentication flow.
|
||||||
- **Default:** `undefined`
|
- **Default:** `undefined`
|
||||||
|
|||||||
@@ -317,6 +317,20 @@ For auditing and monitoring purposes, you can configure Gemini CLI to send telem
|
|||||||
|
|
||||||
**Note:** Ensure that `logPrompts` is set to `false` in an enterprise setting to avoid collecting potentially sensitive information from user prompts.
|
**Note:** Ensure that `logPrompts` is set to `false` in an enterprise setting to avoid collecting potentially sensitive information from user prompts.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
You can enforce a specific authentication method for all users by setting the `enforcedAuthType` in the system-level `settings.json` file. This prevents users from choosing a different authentication method. See the [Authentication docs](./authentication.md) for more details.
|
||||||
|
|
||||||
|
**Example:** Enforce the use of Google login for all users.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enforcedAuthType": "oauth-personal"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If a user has a different authentication method configured, they will be prompted to switch to the enforced method. In non-interactive mode, the CLI will exit with an error if the configured authentication method does not match the enforced one.
|
||||||
|
|
||||||
## Putting It All Together: Example System `settings.json`
|
## Putting It All Together: Example System `settings.json`
|
||||||
|
|
||||||
Here is an example of a system `settings.json` file that combines several of the patterns discussed above to create a secure, controlled environment for Gemini CLI.
|
Here is an example of a system `settings.json` file that combines several of the patterns discussed above to create a secure, controlled environment for Gemini CLI.
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ const MIGRATION_MAP: Record<string, string> = {
|
|||||||
excludeMCPServers: 'mcp.excluded',
|
excludeMCPServers: 'mcp.excluded',
|
||||||
folderTrust: 'security.folderTrust.enabled',
|
folderTrust: 'security.folderTrust.enabled',
|
||||||
selectedAuthType: 'security.auth.selectedType',
|
selectedAuthType: 'security.auth.selectedType',
|
||||||
|
enforcedAuthType: 'security.auth.enforcedType',
|
||||||
useExternalAuth: 'security.auth.useExternal',
|
useExternalAuth: 'security.auth.useExternal',
|
||||||
autoConfigureMaxOldSpaceSize: 'advanced.autoConfigureMemory',
|
autoConfigureMaxOldSpaceSize: 'advanced.autoConfigureMemory',
|
||||||
dnsResolutionOrder: 'advanced.dnsResolutionOrder',
|
dnsResolutionOrder: 'advanced.dnsResolutionOrder',
|
||||||
|
|||||||
@@ -737,6 +737,16 @@ export const SETTINGS_SCHEMA = {
|
|||||||
description: 'The currently selected authentication type.',
|
description: 'The currently selected authentication type.',
|
||||||
showInDialog: false,
|
showInDialog: false,
|
||||||
},
|
},
|
||||||
|
enforcedType: {
|
||||||
|
type: 'string',
|
||||||
|
label: 'Enforced Auth Type',
|
||||||
|
category: 'Advanced',
|
||||||
|
requiresRestart: true,
|
||||||
|
default: undefined as AuthType | undefined,
|
||||||
|
description:
|
||||||
|
'The required auth type. If this does not match the selected auth type, the user will be prompted to re-authenticate.',
|
||||||
|
showInDialog: false,
|
||||||
|
},
|
||||||
useExternal: {
|
useExternal: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
label: 'Use External Auth',
|
label: 'Use External Auth',
|
||||||
|
|||||||
@@ -425,6 +425,7 @@ export async function main() {
|
|||||||
settings.merged.security?.auth?.selectedType,
|
settings.merged.security?.auth?.selectedType,
|
||||||
settings.merged.security?.auth?.useExternal,
|
settings.merged.security?.auth?.useExternal,
|
||||||
config,
|
config,
|
||||||
|
settings,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (config.getDebugMode()) {
|
if (config.getDebugMode()) {
|
||||||
|
|||||||
@@ -333,6 +333,16 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
|
settings.merged.security?.auth?.enforcedType &&
|
||||||
|
settings.merged.security?.auth.selectedType &&
|
||||||
|
settings.merged.security?.auth.enforcedType !==
|
||||||
|
settings.merged.security?.auth.selectedType
|
||||||
|
) {
|
||||||
|
setAuthError(
|
||||||
|
`Authentication is enforced to be ${settings.merged.security?.auth.enforcedType}, but you are currently using ${settings.merged.security?.auth.selectedType}.`,
|
||||||
|
);
|
||||||
|
openAuthDialog();
|
||||||
|
} else if (
|
||||||
settings.merged.security?.auth?.selectedType &&
|
settings.merged.security?.auth?.selectedType &&
|
||||||
!settings.merged.security?.auth?.useExternal
|
!settings.merged.security?.auth?.useExternal
|
||||||
) {
|
) {
|
||||||
@@ -346,6 +356,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
settings.merged.security?.auth?.selectedType,
|
settings.merged.security?.auth?.selectedType,
|
||||||
|
settings.merged.security?.auth?.enforcedType,
|
||||||
settings.merged.security?.auth?.useExternal,
|
settings.merged.security?.auth?.useExternal,
|
||||||
openAuthDialog,
|
openAuthDialog,
|
||||||
setAuthError,
|
setAuthError,
|
||||||
|
|||||||
@@ -427,4 +427,47 @@ describe('AuthDialog', () => {
|
|||||||
expect(onSelect).toHaveBeenCalledWith(undefined, SettingScope.User);
|
expect(onSelect).toHaveBeenCalledWith(undefined, SettingScope.User);
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('enforcedAuthType', () => {
|
||||||
|
it('should display the enforced auth type message if enforcedAuthType is set', () => {
|
||||||
|
const settings: LoadedSettings = new LoadedSettings(
|
||||||
|
{
|
||||||
|
settings: {
|
||||||
|
security: {
|
||||||
|
auth: {
|
||||||
|
enforcedType: AuthType.USE_VERTEX_AI,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
path: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settings: {
|
||||||
|
security: {
|
||||||
|
auth: {
|
||||||
|
selectedType: AuthType.USE_VERTEX_AI,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
path: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settings: {},
|
||||||
|
path: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settings: {},
|
||||||
|
path: '',
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
new Set(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { lastFrame } = renderWithProviders(
|
||||||
|
<AuthDialog onSelect={() => {}} settings={settings} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(lastFrame()).toContain('1. Vertex AI');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function AuthDialog({
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
const items = [
|
let items = [
|
||||||
{
|
{
|
||||||
label: 'Login with Google',
|
label: 'Login with Google',
|
||||||
value: AuthType.LOGIN_WITH_GOOGLE,
|
value: AuthType.LOGIN_WITH_GOOGLE,
|
||||||
@@ -82,7 +82,13 @@ export function AuthDialog({
|
|||||||
{ label: 'Vertex AI', value: AuthType.USE_VERTEX_AI },
|
{ label: 'Vertex AI', value: AuthType.USE_VERTEX_AI },
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialAuthIndex = items.findIndex((item) => {
|
if (settings.merged.security?.auth?.enforcedType) {
|
||||||
|
items = items.filter(
|
||||||
|
(item) => item.value === settings.merged.security?.auth?.enforcedType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let initialAuthIndex = items.findIndex((item) => {
|
||||||
if (settings.merged.security?.auth?.selectedType) {
|
if (settings.merged.security?.auth?.selectedType) {
|
||||||
return item.value === settings.merged.security.auth.selectedType;
|
return item.value === settings.merged.security.auth.selectedType;
|
||||||
}
|
}
|
||||||
@@ -100,6 +106,9 @@ export function AuthDialog({
|
|||||||
|
|
||||||
return item.value === AuthType.LOGIN_WITH_GOOGLE;
|
return item.value === AuthType.LOGIN_WITH_GOOGLE;
|
||||||
});
|
});
|
||||||
|
if (settings.merged.security?.auth?.enforcedType) {
|
||||||
|
initialAuthIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
const handleAuthSelect = (authMethod: AuthType) => {
|
const handleAuthSelect = (authMethod: AuthType) => {
|
||||||
const error = validateAuthMethod(authMethod);
|
const error = validateAuthMethod(authMethod);
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
import type { NonInteractiveConfig } from './validateNonInterActiveAuth.js';
|
|
||||||
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
|
import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js';
|
||||||
import { AuthType } from '@google/gemini-cli-core';
|
import { AuthType } from '@google/gemini-cli-core';
|
||||||
import * as auth from './config/auth.js';
|
import * as auth from './config/auth.js';
|
||||||
|
import { type LoadedSettings } from './config/settings.js';
|
||||||
|
|
||||||
describe('validateNonInterActiveAuth', () => {
|
describe('validateNonInterActiveAuth', () => {
|
||||||
let originalEnvGeminiApiKey: string | undefined;
|
let originalEnvGeminiApiKey: string | undefined;
|
||||||
@@ -16,9 +16,8 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
let originalEnvGcp: string | undefined;
|
let originalEnvGcp: string | undefined;
|
||||||
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
||||||
let processExitSpy: ReturnType<typeof vi.spyOn>;
|
let processExitSpy: ReturnType<typeof vi.spyOn>;
|
||||||
let refreshAuthMock: jest.MockedFunction<
|
let refreshAuthMock: vi.Mock;
|
||||||
(authType: AuthType) => Promise<unknown>
|
let mockSettings: LoadedSettings;
|
||||||
>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalEnvGeminiApiKey = process.env['GEMINI_API_KEY'];
|
originalEnvGeminiApiKey = process.env['GEMINI_API_KEY'];
|
||||||
@@ -32,6 +31,25 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
throw new Error(`process.exit(${code}) called`);
|
throw new Error(`process.exit(${code}) called`);
|
||||||
});
|
});
|
||||||
refreshAuthMock = vi.fn().mockResolvedValue('refreshed');
|
refreshAuthMock = vi.fn().mockResolvedValue('refreshed');
|
||||||
|
mockSettings = {
|
||||||
|
system: { path: '', settings: {} },
|
||||||
|
systemDefaults: { path: '', settings: {} },
|
||||||
|
user: { path: '', settings: {} },
|
||||||
|
workspace: { path: '', settings: {} },
|
||||||
|
errors: [],
|
||||||
|
setValue: vi.fn(),
|
||||||
|
merged: {
|
||||||
|
security: {
|
||||||
|
auth: {
|
||||||
|
enforcedType: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isTrusted: true,
|
||||||
|
migratedInMemorScopes: new Set(),
|
||||||
|
forScope: vi.fn(),
|
||||||
|
computeMergedSettings: vi.fn(),
|
||||||
|
} as unknown as LoadedSettings;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -54,7 +72,7 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('exits if no auth type is configured or env vars set', async () => {
|
it('exits if no auth type is configured or env vars set', async () => {
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
@@ -62,6 +80,7 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect.fail('Should have exited');
|
expect.fail('Should have exited');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -75,26 +94,28 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
|
|
||||||
it('uses LOGIN_WITH_GOOGLE if GOOGLE_GENAI_USE_GCA is set', async () => {
|
it('uses LOGIN_WITH_GOOGLE if GOOGLE_GENAI_USE_GCA is set', async () => {
|
||||||
process.env['GOOGLE_GENAI_USE_GCA'] = 'true';
|
process.env['GOOGLE_GENAI_USE_GCA'] = 'true';
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
await validateNonInteractiveAuth(
|
await validateNonInteractiveAuth(
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE);
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses USE_GEMINI if GEMINI_API_KEY is set', async () => {
|
it('uses USE_GEMINI if GEMINI_API_KEY is set', async () => {
|
||||||
process.env['GEMINI_API_KEY'] = 'fake-key';
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
await validateNonInteractiveAuth(
|
await validateNonInteractiveAuth(
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
||||||
});
|
});
|
||||||
@@ -103,13 +124,14 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
||||||
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
||||||
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
await validateNonInteractiveAuth(
|
await validateNonInteractiveAuth(
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_VERTEX_AI);
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_VERTEX_AI);
|
||||||
});
|
});
|
||||||
@@ -117,13 +139,14 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
it('uses USE_VERTEX_AI if GOOGLE_GENAI_USE_VERTEXAI is true and GOOGLE_API_KEY is set', async () => {
|
it('uses USE_VERTEX_AI if GOOGLE_GENAI_USE_VERTEXAI is true and GOOGLE_API_KEY is set', async () => {
|
||||||
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
||||||
process.env['GOOGLE_API_KEY'] = 'vertex-api-key';
|
process.env['GOOGLE_API_KEY'] = 'vertex-api-key';
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
await validateNonInteractiveAuth(
|
await validateNonInteractiveAuth(
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_VERTEX_AI);
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_VERTEX_AI);
|
||||||
});
|
});
|
||||||
@@ -134,13 +157,14 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
||||||
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
||||||
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
await validateNonInteractiveAuth(
|
await validateNonInteractiveAuth(
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE);
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE);
|
||||||
});
|
});
|
||||||
@@ -150,13 +174,14 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
||||||
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
||||||
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
await validateNonInteractiveAuth(
|
await validateNonInteractiveAuth(
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_VERTEX_AI);
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_VERTEX_AI);
|
||||||
});
|
});
|
||||||
@@ -166,13 +191,14 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
process.env['GEMINI_API_KEY'] = 'fake-key';
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
||||||
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project';
|
||||||
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1';
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
await validateNonInteractiveAuth(
|
await validateNonInteractiveAuth(
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
||||||
});
|
});
|
||||||
@@ -180,13 +206,14 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
it('uses configuredAuthType if provided', async () => {
|
it('uses configuredAuthType if provided', async () => {
|
||||||
// Set required env var for USE_GEMINI
|
// Set required env var for USE_GEMINI
|
||||||
process.env['GEMINI_API_KEY'] = 'fake-key';
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
await validateNonInteractiveAuth(
|
await validateNonInteractiveAuth(
|
||||||
AuthType.USE_GEMINI,
|
AuthType.USE_GEMINI,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
||||||
});
|
});
|
||||||
@@ -194,7 +221,7 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
it('exits if validateAuthMethod returns error', async () => {
|
it('exits if validateAuthMethod returns error', async () => {
|
||||||
// Mock validateAuthMethod to return error
|
// Mock validateAuthMethod to return error
|
||||||
vi.spyOn(auth, 'validateAuthMethod').mockReturnValue('Auth error!');
|
vi.spyOn(auth, 'validateAuthMethod').mockReturnValue('Auth error!');
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
@@ -202,6 +229,7 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
AuthType.USE_GEMINI,
|
AuthType.USE_GEMINI,
|
||||||
undefined,
|
undefined,
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
expect.fail('Should have exited');
|
expect.fail('Should have exited');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -216,7 +244,7 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
const validateAuthMethodSpy = vi
|
const validateAuthMethodSpy = vi
|
||||||
.spyOn(auth, 'validateAuthMethod')
|
.spyOn(auth, 'validateAuthMethod')
|
||||||
.mockReturnValue('Auth error!');
|
.mockReturnValue('Auth error!');
|
||||||
const nonInteractiveConfig: NonInteractiveConfig = {
|
const nonInteractiveConfig = {
|
||||||
refreshAuth: refreshAuthMock,
|
refreshAuth: refreshAuthMock,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,6 +254,7 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
'invalid-auth-type' as AuthType,
|
'invalid-auth-type' as AuthType,
|
||||||
true, // useExternalAuth = true
|
true, // useExternalAuth = true
|
||||||
nonInteractiveConfig,
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(validateAuthMethodSpy).not.toHaveBeenCalled();
|
expect(validateAuthMethodSpy).not.toHaveBeenCalled();
|
||||||
@@ -234,4 +263,44 @@ describe('validateNonInterActiveAuth', () => {
|
|||||||
// We still expect refreshAuth to be called with the (invalid) type
|
// We still expect refreshAuth to be called with the (invalid) type
|
||||||
expect(refreshAuthMock).toHaveBeenCalledWith('invalid-auth-type');
|
expect(refreshAuthMock).toHaveBeenCalledWith('invalid-auth-type');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses enforcedAuthType if provided', async () => {
|
||||||
|
mockSettings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
|
||||||
|
mockSettings.merged.security.auth.selectedType = AuthType.USE_GEMINI;
|
||||||
|
// Set required env var for USE_GEMINI to ensure enforcedAuthType takes precedence
|
||||||
|
process.env['GEMINI_API_KEY'] = 'fake-key';
|
||||||
|
const nonInteractiveConfig = {
|
||||||
|
refreshAuth: refreshAuthMock,
|
||||||
|
};
|
||||||
|
await validateNonInteractiveAuth(
|
||||||
|
AuthType.USE_GEMINI,
|
||||||
|
undefined,
|
||||||
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
|
);
|
||||||
|
expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exits if currentAuthType does not match enforcedAuthType', async () => {
|
||||||
|
mockSettings.merged.security.auth.enforcedType = AuthType.LOGIN_WITH_GOOGLE;
|
||||||
|
process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true';
|
||||||
|
const nonInteractiveConfig = {
|
||||||
|
refreshAuth: refreshAuthMock,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await validateNonInteractiveAuth(
|
||||||
|
AuthType.USE_GEMINI,
|
||||||
|
undefined,
|
||||||
|
nonInteractiveConfig,
|
||||||
|
mockSettings,
|
||||||
|
);
|
||||||
|
expect.fail('Should have exited');
|
||||||
|
} catch (e) {
|
||||||
|
expect((e as Error).message).toContain('process.exit(1) called');
|
||||||
|
}
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||||
|
'The configured auth type is oauth-personal, but the current auth type is vertex-ai. Please re-authenticate with the correct type.',
|
||||||
|
);
|
||||||
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { Config } from '@google/gemini-cli-core';
|
|||||||
import { AuthType } from '@google/gemini-cli-core';
|
import { AuthType } from '@google/gemini-cli-core';
|
||||||
import { USER_SETTINGS_PATH } from './config/settings.js';
|
import { USER_SETTINGS_PATH } from './config/settings.js';
|
||||||
import { validateAuthMethod } from './config/auth.js';
|
import { validateAuthMethod } from './config/auth.js';
|
||||||
|
import { type LoadedSettings } from './config/settings.js';
|
||||||
|
|
||||||
function getAuthTypeFromEnv(): AuthType | undefined {
|
function getAuthTypeFromEnv(): AuthType | undefined {
|
||||||
if (process.env['GOOGLE_GENAI_USE_GCA'] === 'true') {
|
if (process.env['GOOGLE_GENAI_USE_GCA'] === 'true') {
|
||||||
@@ -26,8 +27,21 @@ export async function validateNonInteractiveAuth(
|
|||||||
configuredAuthType: AuthType | undefined,
|
configuredAuthType: AuthType | undefined,
|
||||||
useExternalAuth: boolean | undefined,
|
useExternalAuth: boolean | undefined,
|
||||||
nonInteractiveConfig: Config,
|
nonInteractiveConfig: Config,
|
||||||
|
settings: LoadedSettings,
|
||||||
) {
|
) {
|
||||||
const effectiveAuthType = configuredAuthType || getAuthTypeFromEnv();
|
const enforcedType = settings.merged.security?.auth?.enforcedType;
|
||||||
|
if (enforcedType) {
|
||||||
|
const currentAuthType = getAuthTypeFromEnv();
|
||||||
|
if (currentAuthType !== enforcedType) {
|
||||||
|
console.error(
|
||||||
|
`The configured auth type is ${enforcedType}, but the current auth type is ${currentAuthType}. Please re-authenticate with the correct type.`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveAuthType =
|
||||||
|
enforcedType || getAuthTypeFromEnv() || configuredAuthType;
|
||||||
|
|
||||||
if (!effectiveAuthType) {
|
if (!effectiveAuthType) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
Reference in New Issue
Block a user