Files
gh-krishagel-geoffrey/skills/google-workspace/auth/oauth_setup.js
2025-11-30 08:35:59 +08:00

214 lines
6.1 KiB
JavaScript

#!/usr/bin/env node
/**
* OAuth2 Setup Script for Google Workspace
*
* Usage: node oauth_setup.js <account-alias>
* 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 <account-alias>',
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(`
<html>
<body style="font-family: sans-serif; padding: 40px; text-align: center;">
<h1>✅ Authorization Successful</h1>
<p>Account: <strong>${userInfo.data.email}</strong></p>
<p>Alias: <strong>${accountAlias}</strong></p>
<p>You can close this window.</p>
</body>
</html>
`);
// 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);
});