Support logging in with Application Default Credentials (#1157)

Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
Tommaso Sciortino 2025-06-18 09:49:13 -07:00 committed by GitHub
parent 5b2cea8eda
commit 3453b977b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 50 additions and 42 deletions

View File

@ -33,19 +33,29 @@ The Gemini CLI requires you to authenticate with Google's AI services. You'll ne
1. **Gemini Code Assist:**
- To enable this mode you only need set the GEMINI_CODE_ASSIST environment variable to true.
- Enterprise users must also provide a GOOGLE_CLOUD_PROJECT environment variable specifying their Google Cloud project.
In the following methods, replace `GOOGLE_CLOUD_PROJECT` with the relevant values for your project:
- You can temporarily set the environment variable in your current shell session using the following command:
```bash
export GEMINI_CODE_ASSIST="true"
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID" // Enterprise users only.
```
- For repeated use, you can add the environment variable to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following command adds the environment variable to a `~/.bashrc` file:
```bash
echo 'export GEMINI_CODE_ASSIST="true"' >> ~/.bashrc
echo 'export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"' >> ~/.bashrc # Enterprise users only.
source ~/.bashrc
```
- There are two types of Google Accounts you can use with Gemini CLI:
- **Personal Google Account**: This is the standard, free account you use for services like Gmail, Google Photos, and Google Drive for personal use (e.g. your-name@gmail.com).
- **Google Workspace Account**: This is a paid service for businesses and organizations that provides a suite of productivity tools, including a custom email domain (e.g. your-name@your-company.com), enhanced security features, and administrative controls. These accounts are often managed by an employer or school.
- Google Workspace Account must configure a Google Cloud Project Id to use. You can temporarily set the environment variable in your current shell session using the following command:
```bash
export GOOGLE_CLOUD_PROJECT_ID="YOUR_PROJECT_ID"
```
- For repeated use, you can add the environment variable to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following command adds the environment variable to a `~/.bashrc` file:
```bash
echo 'export GOOGLE_CLOUD_PROJECT_ID="YOUR_PROJECT_ID"' >> ~/.bashrc
source ~/.bashrc
```
- During start up, Gemini CLI will direct you to a webpage for authentication. Once authenticated, your credentials will be cached locally so the web login can be skipped on subsequent runs. Cached credentials last about 20 hours before expiring.
- Note that the the web login must be done in a browser that can communicate with the machine Gemini Cli is being run from. (Specifically, the browser will be redirected to a localhost url that Gemini CLI will be listening on).
2. **Gemini API key:**

View File

@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { GoogleAuth, AuthClient } from 'google-auth-library';
import { ContentGenerator } from '../core/contentGenerator.js';
import { getOauthClient } from './oauth2.js';
import { setupUser } from './setup.js';
@ -12,10 +13,17 @@ import { CodeAssistServer, HttpOptions } from './server.js';
export async function createCodeAssistContentGenerator(
httpOptions: HttpOptions,
): Promise<ContentGenerator> {
const oauth2Client = await getOauthClient();
const projectId = await setupUser(
oauth2Client,
process.env.GOOGLE_CLOUD_PROJECT,
);
return new CodeAssistServer(oauth2Client, projectId, httpOptions);
const authClient = await getAuthClient();
const projectId = await setupUser(authClient);
return new CodeAssistServer(authClient, projectId, httpOptions);
}
async function getAuthClient(): Promise<AuthClient> {
try {
// Try for Application Default Credentials.
return await new GoogleAuth().getClient();
} catch (_) {
// No Application Default Credentials so try Oauth.
return await getOauthClient();
}
}

View File

@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { OAuth2Client } from 'google-auth-library';
import { OAuth2Client, Credentials } from 'google-auth-library';
import * as http from 'http';
import url from 'url';
import crypto from 'crypto';
@ -42,23 +42,12 @@ const SIGN_IN_FAILURE_URL =
const GEMINI_DIR = '.gemini';
const CREDENTIAL_FILENAME = 'oauth_creds.json';
export async function clearCachedCredentials(): Promise<void> {
await fs.rm(getCachedCredentialPath());
}
export async function getOauthClient(): Promise<OAuth2Client> {
try {
return await getCachedCredentialClient();
} catch (_) {
const loggedInClient = await webLoginClient();
await fs.mkdir(path.dirname(getCachedCredentialPath()), {
recursive: true,
});
await fs.writeFile(
getCachedCredentialPath(),
JSON.stringify(loggedInClient.credentials, null, 2),
);
await setCachedCredentials(loggedInClient.credentials);
return loggedInClient;
}
}
@ -149,7 +138,6 @@ function getAvailablePort(): Promise<number> {
async function getCachedCredentialClient(): Promise<OAuth2Client> {
try {
const creds = await fs.readFile(getCachedCredentialPath(), 'utf-8');
const oAuth2Client = new OAuth2Client({
clientId: OAUTH_CLIENT_ID,
clientSecret: OAUTH_CLIENT_SECRET,
@ -165,6 +153,14 @@ async function getCachedCredentialClient(): Promise<OAuth2Client> {
}
}
async function setCachedCredentials(credentials: Credentials) {
const filePath = getCachedCredentialPath();
await fs.mkdir(path.dirname(filePath), { recursive: true });
const credString = JSON.stringify(credentials, null, 2);
await fs.writeFile(filePath, credString);
}
function getCachedCredentialPath(): string {
return path.join(os.homedir(), GEMINI_DIR, CREDENTIAL_FILENAME);
}

View File

@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { OAuth2Client } from 'google-auth-library';
import { AuthClient } from 'google-auth-library';
import {
LoadCodeAssistResponse,
LoadCodeAssistRequest,
@ -42,7 +42,7 @@ export const CODE_ASSIST_API_VERSION = 'v1internal';
export class CodeAssistServer implements ContentGenerator {
constructor(
readonly auth: OAuth2Client,
readonly auth: AuthClient,
readonly projectId?: string,
readonly httpOptions: HttpOptions = {},
) {}

View File

@ -6,31 +6,27 @@
import { ClientMetadata, OnboardUserRequest } from './types.js';
import { CodeAssistServer } from './server.js';
import { OAuth2Client } from 'google-auth-library';
import { clearCachedCredentials } from './oauth2.js';
import { AuthClient } from 'google-auth-library';
/**
*
* @param projectId the user's project id, if any
* @returns the user's actual project id
*/
export async function setupUser(
oAuth2Client: OAuth2Client,
projectId?: string,
): Promise<string> {
const caServer = new CodeAssistServer(oAuth2Client, projectId);
export async function setupUser(authClient: AuthClient): Promise<string> {
const projectId = process.env.GOOGLE_CLOUD_PROJECT;
const caServer = new CodeAssistServer(authClient, projectId);
const clientMetadata: ClientMetadata = {
ideType: 'IDE_UNSPECIFIED',
platform: 'PLATFORM_UNSPECIFIED',
pluginType: 'GEMINI',
duetProject: projectId,
};
if (process.env.GOOGLE_CLOUD_PROJECT) {
clientMetadata.duetProject = process.env.GOOGLE_CLOUD_PROJECT;
}
// TODO: Support Free Tier user without projectId.
const loadRes = await caServer.loadCodeAssist({
cloudaicompanionProject: process.env.GOOGLE_CLOUD_PROJECT,
cloudaicompanionProject: projectId,
metadata: clientMetadata,
});
@ -39,7 +35,7 @@ export async function setupUser(
const onboardReq: OnboardUserRequest = {
tierId: onboardTier,
cloudaicompanionProject: loadRes.cloudaicompanionProject || '',
cloudaicompanionProject: loadRes.cloudaicompanionProject || projectId || '',
metadata: clientMetadata,
};
try {
@ -49,14 +45,12 @@ export async function setupUser(
await new Promise((f) => setTimeout(f, 5000));
lroRes = await caServer.onboardUser(onboardReq);
}
return lroRes.response?.cloudaicompanionProject?.id || '';
} catch (e) {
await clearCachedCredentials();
console.log(
'\n\nError onboarding with Code Assist.\n' +
'Enterprise users must specify GOOGLE_CLOUD_PROJECT ' +
'in their environment variables or .env file.\n\n',
'Google Workspace Account (e.g. your-name@your-company.com)' +
' must specify a GOOGLE_CLOUD_PROJECT environment variable.\n\n',
);
throw e;
}