#!/usr/bin/env node /** * OAuth2 Setup Script for Google Workspace * * Usage: node oauth_setup.js * Example: node oauth_setup.js psd * * This script: * 1. Starts a local server to receive OAuth callback * 2. Opens browser to Google consent page * 3. Exchanges auth code for tokens * 4. Outputs tokens as JSON (to be stored by token_manager.js) * * Prerequisites: * - .env file with GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET * - npm install googleapis open dotenv */ const { google } = require('googleapis'); const http = require('http'); const url = require('url'); const openModule = require('open'); const open = openModule.default || openModule; const path = require('path'); // Load environment variables from iCloud secrets const os = require('os'); const ENV_PATH = path.join( os.homedir(), 'Library/Mobile Documents/com~apple~CloudDocs/Geoffrey/secrets/.env' ); require('dotenv').config({ path: ENV_PATH }); // Configuration const REDIRECT_PORT = process.env.OAUTH_REDIRECT_PORT || 3000; const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/oauth2callback`; // Full scope list for complete Google Workspace access const SCOPES = [ // Gmail 'https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.compose', 'https://www.googleapis.com/auth/gmail.modify', 'https://www.googleapis.com/auth/gmail.labels', // Calendar 'https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/calendar.events', // Drive 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/drive.file', // Docs, Sheets, Slides 'https://www.googleapis.com/auth/documents', 'https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/presentations', // Forms 'https://www.googleapis.com/auth/forms.body', 'https://www.googleapis.com/auth/forms.responses.readonly', // Chat 'https://www.googleapis.com/auth/chat.spaces', 'https://www.googleapis.com/auth/chat.messages', 'https://www.googleapis.com/auth/chat.memberships.readonly', // Tasks 'https://www.googleapis.com/auth/tasks', // Keep (Note: Keep API has limited availability) // 'https://www.googleapis.com/auth/keep', // User info (to identify which account) 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', // Admin Directory (to look up user names) 'https://www.googleapis.com/auth/admin.directory.user.readonly', ]; async function main() { const accountAlias = process.argv[2]; if (!accountAlias) { console.error(JSON.stringify({ error: 'Missing account alias', usage: 'node oauth_setup.js ', example: 'node oauth_setup.js psd' })); process.exit(1); } // Load client credentials from environment const client_id = process.env.GOOGLE_CLIENT_ID; const client_secret = process.env.GOOGLE_CLIENT_SECRET; if (!client_id || !client_secret) { console.error(JSON.stringify({ error: 'Missing credentials in .env', instructions: [ '1. Go to Google Cloud Console', '2. Create OAuth 2.0 Client ID (Desktop app)', '3. Copy client ID and secret to skills/google-workspace/.env', '4. See .env.example for format' ] })); process.exit(1); } // Create OAuth2 client const oauth2Client = new google.auth.OAuth2( client_id, client_secret, REDIRECT_URI ); // Generate auth URL const authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES, prompt: 'consent', // Force to get refresh token }); // Start local server to receive callback const server = http.createServer(async (req, res) => { try { const parsedUrl = url.parse(req.url, true); if (parsedUrl.pathname === '/oauth2callback') { const code = parsedUrl.query.code; if (!code) { res.writeHead(400); res.end('Missing authorization code'); return; } // Exchange code for tokens const { tokens } = await oauth2Client.getToken(code); // Get user info to confirm which account oauth2Client.setCredentials(tokens); const oauth2 = google.oauth2({ version: 'v2', auth: oauth2Client }); const userInfo = await oauth2.userinfo.get(); // Send success response to browser res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(`

✅ Authorization Successful

Account: ${userInfo.data.email}

Alias: ${accountAlias}

You can close this window.

`); // Output tokens as JSON console.log(JSON.stringify({ success: true, account: accountAlias, email: userInfo.data.email, tokens: { access_token: tokens.access_token, refresh_token: tokens.refresh_token, token_type: tokens.token_type, expiry_date: tokens.expiry_date, } }, null, 2)); // Close server server.close(); } } catch (error) { res.writeHead(500); res.end('Authorization failed'); console.error(JSON.stringify({ error: 'Token exchange failed', message: error.message })); server.close(); process.exit(1); } }); server.listen(REDIRECT_PORT, async () => { console.error(`Opening browser for ${accountAlias} account authorization...`); console.error(`If browser doesn't open, visit: ${authUrl}`); await open(authUrl); }); // Timeout after 5 minutes setTimeout(() => { console.error(JSON.stringify({ error: 'Authorization timeout', message: 'No response received within 5 minutes' })); server.close(); process.exit(1); }, 5 * 60 * 1000); } main().catch(error => { console.error(JSON.stringify({ error: 'Setup failed', message: error.message })); process.exit(1); });