Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,76 @@ your terminal:
gemini extensions install https://github.com/gemini-cli-extensions/workspace
```

### Headless / Docker Authentication

In environments without a browser (Docker, SSH, CI), you can provide OAuth
credentials via a JSON file using the `GEMINI_CLI_WORKSPACE_OAUTH_CREDENTIALS`
environment variable (similar to `GOOGLE_APPLICATION_CREDENTIALS`):

```bash
GEMINI_CLI_WORKSPACE_OAUTH_CREDENTIALS=/path/to/credentials.json gemini
```

The simplest way to generate this file is with `gcloud`:

#### 1. Create an OAuth Client ID

Create a **Desktop** OAuth client in the
[Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials):

1. Click **Create Credentials** → **OAuth client ID**
2. Application type: **Desktop app**
3. Download the client secret JSON file (e.g. `client_secret.json`)

#### 2. Enable Required APIs

Enable the following APIs in your Google Cloud Project
([APIs & Services → Library](https://console.cloud.google.com/apis/library)):

- [Google Calendar API](https://console.cloud.google.com/apis/api/calendar-json.googleapis.com)
- [Google Drive API](https://console.cloud.google.com/apis/api/drive.googleapis.com)
- [Google Docs API](https://console.cloud.google.com/apis/api/docs.googleapis.com)
- [Google Sheets API](https://console.cloud.google.com/apis/api/sheets.googleapis.com)
- [Google Slides API](https://console.cloud.google.com/apis/api/slides.googleapis.com)
- [Gmail API](https://console.cloud.google.com/apis/api/gmail.googleapis.com)
- [Google Chat API](https://console.cloud.google.com/apis/api/chat.googleapis.com)
- [People API](https://console.cloud.google.com/apis/api/people.googleapis.com)
- [Admin SDK API](https://console.cloud.google.com/apis/api/admin.googleapis.com)

#### 3. Generate Credentials

```bash
gcloud auth application-default login \
--client-id-file=client_secret.json \
--project=YOUR_PROJECT_ID \
--scopes="\
https://www.googleapis.com/auth/cloud-platform,\
https://www.googleapis.com/auth/documents,\
https://www.googleapis.com/auth/drive,\
https://www.googleapis.com/auth/calendar,\
https://www.googleapis.com/auth/chat.spaces,\
https://www.googleapis.com/auth/chat.messages,\
https://www.googleapis.com/auth/chat.memberships,\
https://www.googleapis.com/auth/userinfo.profile,\
https://www.googleapis.com/auth/gmail.modify,\
https://www.googleapis.com/auth/directory.readonly,\
https://www.googleapis.com/auth/presentations.readonly,\
https://www.googleapis.com/auth/spreadsheets.readonly"
```

You can customize the list of scopes used above to fit your needs. See the
[Google OAuth 2.0 Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)
page for a list of available scopes.

#### 4. Use the Credentials

```bash
GEMINI_CLI_WORKSPACE_OAUTH_CREDENTIALS=~/.config/gcloud/application_default_credentials.json gemini
```

> **Note:** Both gcloud ADC format (`type: "authorized_user"`) and raw OAuth
> token JSON are supported.

## Usage

Once the extension is installed, you can use it to interact with your Google
Expand Down
70 changes: 70 additions & 0 deletions workspace-server/src/auth/AuthManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { google, Auth } from 'googleapis';
import crypto from 'node:crypto';
import * as fs from 'node:fs';
import * as http from 'node:http';
import * as net from 'node:net';
import * as url from 'node:url';
Expand Down Expand Up @@ -80,10 +81,79 @@ export class AuthManager {

return false;
}
/**
* Load an OAuth2 client from a credentials file specified by the
* GEMINI_CLI_WORKSPACE_OAUTH_CREDENTIALS environment variable.
* Supports gcloud ADC format (type: "authorized_user") and raw
* Auth.Credentials JSON.
*/
private async loadClientFromCredentialsFile(
credentialsFile: string,
): Promise<Auth.OAuth2Client> {
logToFile(`Loading credentials from file: ${credentialsFile}`);
let parsed: Record<string, unknown>;
try {
const fileContents = await fs.promises.readFile(
credentialsFile,
'utf-8',
);
parsed = JSON.parse(fileContents);
} catch (e) {
throw new Error(
`Failed to read or parse credentials from ${credentialsFile}: ${(e as Error).message}`,
);
}

// Support gcloud ADC format (type: "authorized_user")
if (parsed.type === 'authorized_user') {
if (
typeof parsed.client_id !== 'string' ||
typeof parsed.client_secret !== 'string' ||
typeof parsed.refresh_token !== 'string'
) {
throw new Error(
`Malformed "authorized_user" credentials in ${credentialsFile}. ` +
`'client_id', 'client_secret', and 'refresh_token' must be strings.`,
);
}
const oAuth2Client = new google.auth.OAuth2({
clientId: parsed.client_id,
clientSecret: parsed.client_secret,
});
oAuth2Client.setCredentials({
refresh_token: parsed.refresh_token,
});
return oAuth2Client;
}

// Otherwise treat as raw Auth.Credentials JSON
const creds = parsed as Auth.Credentials;
if (!creds.access_token && !creds.refresh_token) {
throw new Error(
`Malformed credentials in ${credentialsFile}. ` +
`The file must contain at least 'access_token' or 'refresh_token'.`,
);
}
const oAuth2Client = new google.auth.OAuth2({ clientId: CLIENT_ID });
oAuth2Client.setCredentials(creds);
return oAuth2Client;
}

public async getAuthenticatedClient(): Promise<Auth.OAuth2Client> {
logToFile('getAuthenticatedClient called');

// If a credentials file is provided via env var, use it directly
// (similar to GOOGLE_APPLICATION_CREDENTIALS)
const credentialsFile =
process.env['GEMINI_CLI_WORKSPACE_OAUTH_CREDENTIALS'];
if (credentialsFile) {
if (!this.client) {
this.client =
await this.loadClientFromCredentialsFile(credentialsFile);
}
return this.client;
}

// Check if we have a cached client with valid credentials
if (
this.client &&
Expand Down