Support logging in with Application Default Credentials (#1157)
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
parent
5b2cea8eda
commit
3453b977b8
18
README.md
18
README.md
|
@ -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:**
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 = {},
|
||||
) {}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue