Skip to main content
Glama
auth.ts6.15 kB
// src/auth.ts import { google } from 'googleapis'; import { OAuth2Client } from 'google-auth-library'; import { JWT } from 'google-auth-library'; // ADDED: Import for Service Account client import * as fs from 'fs/promises'; import * as path from 'path'; import * as readline from 'readline/promises'; import { fileURLToPath } from 'url'; // --- Calculate paths relative to this script file (ESM way) --- const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const projectRootDir = path.resolve(__dirname, '..'); const TOKEN_PATH = path.join(projectRootDir, 'token.json'); const CREDENTIALS_PATH = path.join(projectRootDir, 'credentials.json'); // --- End of path calculation --- const SCOPES = [ 'https://www.googleapis.com/auth/documents', 'https://www.googleapis.com/auth/drive' // Full Drive access for listing, searching, and document discovery ]; // --- NEW FUNCTION: Handles Service Account Authentication --- // This entire function is new. It is called only when the // SERVICE_ACCOUNT_PATH environment variable is set. async function authorizeWithServiceAccount(): Promise<JWT> { const serviceAccountPath = process.env.SERVICE_ACCOUNT_PATH!; // We know this is set if we are in this function try { const keyFileContent = await fs.readFile(serviceAccountPath, 'utf8'); const serviceAccountKey = JSON.parse(keyFileContent); const auth = new JWT({ email: serviceAccountKey.client_email, key: serviceAccountKey.private_key, scopes: SCOPES, }); await auth.authorize(); console.error('Service Account authentication successful!'); return auth; } catch (error: any) { if (error.code === 'ENOENT') { console.error(`FATAL: Service account key file not found at path: ${serviceAccountPath}`); throw new Error(`Service account key file not found. Please check the path in SERVICE_ACCOUNT_PATH.`); } console.error('FATAL: Error loading or authorizing the service account key:', error.message); throw new Error('Failed to authorize using the service account. Ensure the key file is valid and the path is correct.'); } } // --- END OF NEW FUNCTION--- async function loadSavedCredentialsIfExist(): Promise<OAuth2Client | null> { try { const content = await fs.readFile(TOKEN_PATH); const credentials = JSON.parse(content.toString()); const { client_secret, client_id, redirect_uris } = await loadClientSecrets(); const client = new google.auth.OAuth2(client_id, client_secret, redirect_uris?.[0]); client.setCredentials(credentials); return client; } catch (err) { return null; } } async function loadClientSecrets() { const content = await fs.readFile(CREDENTIALS_PATH); const keys = JSON.parse(content.toString()); const key = keys.installed || keys.web; if (!key) throw new Error("Could not find client secrets in credentials.json."); return { client_id: key.client_id, client_secret: key.client_secret, redirect_uris: key.redirect_uris || ['http://localhost:3000/'], // Default for web clients client_type: keys.web ? 'web' : 'installed' }; } async function saveCredentials(client: OAuth2Client): Promise<void> { const { client_secret, client_id } = await loadClientSecrets(); const payload = JSON.stringify({ type: 'authorized_user', client_id: client_id, client_secret: client_secret, refresh_token: client.credentials.refresh_token, }); await fs.writeFile(TOKEN_PATH, payload); console.error('Token stored to', TOKEN_PATH); } async function authenticate(): Promise<OAuth2Client> { const { client_secret, client_id, redirect_uris, client_type } = await loadClientSecrets(); // For web clients, use the configured redirect URI; for desktop clients, use 'urn:ietf:wg:oauth:2.0:oob' const redirectUri = client_type === 'web' ? redirect_uris[0] : 'urn:ietf:wg:oauth:2.0:oob'; console.error(`DEBUG: Using redirect URI: ${redirectUri}`); console.error(`DEBUG: Client type: ${client_type}`); const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectUri); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const authorizeUrl = oAuth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES.join(' '), }); console.error('DEBUG: Generated auth URL:', authorizeUrl); console.error('Authorize this app by visiting this url:', authorizeUrl); const code = await rl.question('Enter the code from that page here: '); rl.close(); try { const { tokens } = await oAuth2Client.getToken(code); oAuth2Client.setCredentials(tokens); if (tokens.refresh_token) { // Save only if we got a refresh token await saveCredentials(oAuth2Client); } else { console.error("Did not receive refresh token. Token might expire."); } console.error('Authentication successful!'); return oAuth2Client; } catch (err) { console.error('Error retrieving access token', err); throw new Error('Authentication failed'); } } // --- MODIFIED: The Main Exported Function --- // This function now acts as a router. It checks for the environment // variable and decides which authentication method to use. export async function authorize(): Promise<OAuth2Client | JWT> { // Check if the Service Account environment variable is set. if (process.env.SERVICE_ACCOUNT_PATH) { console.error('Service account path detected. Attempting service account authentication...'); return authorizeWithServiceAccount(); } else { // If not, execute the original OAuth 2.0 flow exactly as it was. console.error('No service account path detected. Falling back to standard OAuth 2.0 flow...'); let client = await loadSavedCredentialsIfExist(); if (client) { // Optional: Add token refresh logic here if needed, though library often handles it. console.error('Using saved credentials.'); return client; } console.error('Starting authentication flow...'); client = await authenticate(); return client; } } // --- END OF MODIFIED: The Main Exported Function ---

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/a-bonus/google-docs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server