/** * One-time script to generate a Gmail OAuth2 refresh token. * * Usage: * node get-refresh-token.js * * Prerequisites: * - GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET set in .env * - In Google Cloud Console → OAuth 2.0 Client → Authorized redirect URIs: * add http://localhost:3000/oauth2callback * * Flow: * 1. Script prints an auth URL. Open it in any browser (same host or remote). * 2a. Same-host browser: redirect to localhost:3000 is captured automatically. * 2b. Remote browser (e.g. Windows, script running on Linux over SSH): * the browser fails to load localhost:3000/oauth2callback?code=... — copy * the full URL (or just the code= value) from the address bar and paste * it into the terminal prompt. */ require('dotenv').config(); const { google } = require('googleapis'); const http = require('http'); const url = require('url'); const readline = require('readline'); const CLIENT_ID = process.env.GOOGLE_CLIENT_ID; const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET; const REDIRECT_URI = 'http://localhost:3000/oauth2callback'; if (!CLIENT_ID || !CLIENT_SECRET) { console.error('GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET must be set in .env'); process.exit(1); } const oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); const authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', prompt: 'consent', // forces refresh_token to be returned even if previously authorized scope: [ 'https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.modify', ], }); console.log('\n=== Gmail OAuth2 Token Generator ===\n'); console.log('1. Open this URL in your browser:\n'); console.log(authUrl); console.log('\n2. Authorize the app with your support Gmail account.'); console.log('\n3a. If the browser runs on this host, the redirect is captured automatically.'); console.log('3b. If the browser is on a different machine, it will fail to load'); console.log(' http://localhost:3000/oauth2callback?code=... — copy the full URL'); console.log(' (or just the code= value) from the address bar and paste it below.\n'); let done = false; function extractCode(input) { const trimmed = (input || '').trim(); if (!trimmed) return null; try { // If the user pastes a full URL, pull the `code` query param. // URL() treats localhost URLs fine; searchParams auto-decodes. const parsed = new URL(trimmed); return parsed.searchParams.get('code'); } catch { // Not a URL — assume the user pasted the raw code. return trimmed; } } async function finish(code, source, httpRespond) { if (done) return; done = true; try { const { tokens } = await oauth2Client.getToken(code); if (httpRespond) { httpRespond('

Success! Check your terminal for the refresh token.

You can close this tab.

'); } console.log(`\n=== SUCCESS (via ${source}) ===\n`); console.log('Add this to your .env:\n'); console.log(`REFRESH_TOKEN=${tokens.refresh_token}`); if (!tokens.refresh_token) { console.log('\nWARNING: No refresh_token returned.'); console.log('Go to https://myaccount.google.com/permissions and revoke access for your app, then run this script again.'); } console.log('\nAll tokens (for reference):'); console.log(JSON.stringify(tokens, null, 2)); } catch (err) { if (httpRespond) httpRespond('Error exchanging code: ' + err.message); console.error('\nError exchanging code:', err.response?.data || err.message); process.exitCode = 1; } finally { try { server.close(); } catch {} try { rl.close(); } catch {} } } const server = http.createServer(async (req, res) => { const parsed = url.parse(req.url, true); if (parsed.pathname !== '/oauth2callback') { res.end('Not found'); return; } const code = parsed.query.code; if (!code) { res.end('No code received.'); return; } await finish(code, 'browser redirect', (body) => res.end(body)); }); server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.log('Port 3000 is in use — auto-capture disabled. Use the paste prompt below.\n'); } else { console.error('HTTP server error:', err.message); } }); server.listen(3000, () => { console.log('Waiting for OAuth callback on http://localhost:3000 (or paste below) ...\n'); }); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question('Paste redirect URL or code here: ', async (answer) => { if (done) return; const code = extractCode(answer); if (!code) { console.error('Could not parse a code from that input.'); process.exitCode = 1; try { server.close(); } catch {} rl.close(); return; } await finish(code, 'pasted input'); });