[object Object]

← back to George Gmail

feat(auth): add /auth/calendar re-consent route for GOOGLE_CALENDAR_REFRESH_TOKEN

e9b8ed8ab1f44a44ee43ae0eddd969b57fc5e13c · 2026-09-01 12:44:51 -0700 · Steve Abrams

Files touched

Diff

commit e9b8ed8ab1f44a44ee43ae0eddd969b57fc5e13c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 1 12:44:51 2026 -0700

    feat(auth): add /auth/calendar re-consent route for GOOGLE_CALENDAR_REFRESH_TOKEN
---
 server.js | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 84 insertions(+), 1 deletion(-)

diff --git a/server.js b/server.js
index 452d6fd..841da30 100644
--- a/server.js
+++ b/server.js
@@ -66,6 +66,12 @@ const AGENTABRAMS_REDIRECT_URI = 'http://localhost:9850/oauth2callback';
 const AGENTABRAMS_REFRESH_TOKEN = creds.AGENTABRAMS_REFRESH_TOKEN || process.env.AGENTABRAMS_REFRESH_TOKEN;
 const AGENTABRAMS_LOGIN_HINT = creds.AGENTABRAMS_LOGIN_HINT || process.env.AGENTABRAMS_LOGIN_HINT || 'theagentabrams@gmail.com';
 
+// ─── Calendar re-consent (GOOGLE_CALENDAR_REFRESH_TOKEN) ───
+// Reuses GMAIL_CLIENT_ID/SECRET — token-age-warn.mjs exchanges the calendar
+// refresh token with the George client, so it MUST be minted from this client.
+const CALENDAR_REDIRECT_URI = 'http://localhost:9850/oauth2callback';
+const CALENDAR_REFRESH_TOKEN = creds.GOOGLE_CALENDAR_REFRESH_TOKEN || process.env.GOOGLE_CALENDAR_REFRESH_TOKEN;
+
 // ─── OAuth2 Scopes ───
 // FULL_WORKSPACE_SCOPES = unified scope list for all 3 accounts.
 // Steve approved including Forms (Q3=y) so we don't have to re-OAuth later.
@@ -223,6 +229,22 @@ try {
   console.error(`[${AGENT_NAME}] Agent Abrams client error: ${e.message}`);
 }
 
+// ─── Calendar account (GOOGLE_CALENDAR_REFRESH_TOKEN) ───
+let calendarOauth2Client;
+let calendarApi;
+try {
+  calendarOauth2Client = new google.auth.OAuth2(GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, CALENDAR_REDIRECT_URI);
+  if (CALENDAR_REFRESH_TOKEN && !/^your-calendar/.test(CALENDAR_REFRESH_TOKEN)) {
+    calendarOauth2Client.setCredentials({ refresh_token: CALENDAR_REFRESH_TOKEN });
+    calendarApi = google.calendar({ version: 'v3', auth: calendarOauth2Client });
+    console.log(`[${AGENT_NAME}] Calendar account initialized`);
+  } else {
+    console.log(`[${AGENT_NAME}] Calendar not configured — visit /auth/calendar to authorize`);
+  }
+} catch (e) {
+  console.error(`[${AGENT_NAME}] Calendar client error: ${e.message}`);
+}
+
 // ─── SA/DWD override (DORMANT unless GOOGLE_SA_KEYFILE + GEORGE_SA_ACCOUNTS set) ─
 // For each SA-armed Workspace account, rebuild its per-service API clients on the
 // domain-wide-delegation JWT auth, replacing the refresh-token clients. Additive:
@@ -310,7 +332,7 @@ function _ctEq(a, b) {
 
 // ─── Basic Auth (skip health + OAuth callbacks) ───
 app.use((req, res, next) => {
-  const publicPaths = ['/health', '/auth', '/auth/info', '/auth/steve-office', '/auth/steve-personal', '/auth/agentabrams', '/oauth2callback'];
+  const publicPaths = ['/health', '/auth', '/auth/info', '/auth/steve-office', '/auth/steve-personal', '/auth/agentabrams', '/auth/calendar', '/oauth2callback'];
   if (publicPaths.includes(req.path)) return next();
   const auth = req.headers.authorization;
   if (!auth || !auth.startsWith('Basic ')) {
@@ -353,6 +375,7 @@ app.get('/auth', (req, res) => {
     'info':           '/auth/info',
     'steve-personal': '/auth/steve-personal',
     'agentabrams':    '/auth/agentabrams',
+    'calendar':       '/auth/calendar',
   };
   if (routes[acct]) return res.redirect(routes[acct]);
   // steve-office (default, or unknown account key) — full Workspace re-auth for steve@
@@ -491,6 +514,41 @@ app.get('/oauth2callback', async (req, res) => {
       return res.send(msg);
     }
 
+    // Route to calendar handler if state=calendar
+    if (state === 'calendar') {
+      const { tokens } = await calendarOauth2Client.getToken({ code, redirect_uri: CALENDAR_REDIRECT_URI });
+      calendarOauth2Client.setCredentials(tokens);
+      calendarApi = google.calendar({ version: 'v3', auth: calendarOauth2Client });
+
+      let msg = '<html><body style="font-family:sans-serif;background:#0f1117;color:#e4e4e7;padding:40px">';
+      msg += '<h2 style="color:#4ade80">Calendar Authorized!</h2>';
+      if (tokens.refresh_token) {
+        msg += `<p><strong>New Calendar Refresh Token:</strong></p><code style="background:#1a1d27;padding:10px;display:block;word-break:break-all;border-radius:8px">${tokens.refresh_token}</code>`;
+        console.log(`[${AGENT_NAME}] Calendar refresh token obtained: ${tokens.refresh_token.substring(0, 20)}...`);
+        try {
+          let envContent = fs.readFileSync(ENV_PATH, 'utf8');
+          if (envContent.includes('GOOGLE_CALENDAR_REFRESH_TOKEN=')) {
+            envContent = envContent.replace(/GOOGLE_CALENDAR_REFRESH_TOKEN=.*/, `GOOGLE_CALENDAR_REFRESH_TOKEN=${tokens.refresh_token}`);
+          } else {
+            envContent += `\n# Google Calendar OAuth Token\nGOOGLE_CALENDAR_REFRESH_TOKEN=${tokens.refresh_token}\n`;
+          }
+          fs.writeFileSync(ENV_PATH, envContent);
+          msg += '<p style="color:#4ade80">Token auto-saved to central .env!</p>';
+          audit('calendar', 'oauth-authorized', { scopes: ['https://www.googleapis.com/auth/calendar'].length });
+        } catch (e) {
+          msg += `<p style="color:#f87171">Could not auto-save: ${e.message}</p>`;
+        }
+      }
+      try {
+        const list = await calendarApi.calendarList.list({ maxResults: 1 });
+        const primary = (list.data.items || [])[0];
+        if (primary) msg += `<p style="color:#60a5fa">Connected — calendar: <strong>${primary.summary || primary.id}</strong></p>`;
+      } catch {}
+      msg += '<p style="color:#a3a3a3">You can close this tab. Restart george-gmail to load the new token persistently.</p>';
+      msg += '<p><a href="/" style="color:#60a5fa">Go to Dashboard</a></p></body></html>';
+      return res.send(msg);
+    }
+
     // Default: steve@ account (now full Workspace)
     const { tokens } = await oauth2Client.getToken({ code, redirect_uri: OAUTH_REDIRECT });
     oauth2Client.setCredentials(tokens);
@@ -663,6 +721,31 @@ app.get('/api/auth-url/steve-personal', (req, res) => {
   res.json({ authUrl: url, account: 'steveabramsdesigns@gmail.com', scopes: PERSONAL_SCOPES });
 });
 
+// ─── Calendar OAuth Authorization Flow ───
+app.get('/auth/calendar', (req, res) => {
+  if (!calendarOauth2Client) return res.status(500).send('Calendar OAuth client not configured');
+  const url = calendarOauth2Client.generateAuthUrl({
+    access_type: 'offline',
+    scope: ['https://www.googleapis.com/auth/calendar'],
+    prompt: 'consent',
+    redirect_uri: CALENDAR_REDIRECT_URI,
+    state: 'calendar',
+  });
+  res.redirect(url);
+});
+
+app.get('/api/auth-url/calendar', (req, res) => {
+  if (!calendarOauth2Client) return res.status(500).json({ error: 'Calendar OAuth client not configured' });
+  const url = calendarOauth2Client.generateAuthUrl({
+    access_type: 'offline',
+    scope: ['https://www.googleapis.com/auth/calendar'],
+    prompt: 'consent',
+    redirect_uri: CALENDAR_REDIRECT_URI,
+    state: 'calendar',
+  });
+  res.json({ authUrl: url, account: 'GOOGLE_CALENDAR_REFRESH_TOKEN', scopes: ['https://www.googleapis.com/auth/calendar'] });
+});
+
 app.get('/api/exchange-code/steve-personal', async (req, res) => {
   try {
     const { code } = req.query;

← 93c7bf4 auto-data-snapshot: 2026-09-01T08:28:16 (1 data files) — dat  ·  back to George Gmail  ·  fix(token-age): valid-past-7d-horizon = OK (long-lived), not 9ca35af →