← 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 →