[object Object]

← back to George Gmail

george-gmail: recover prod hand-patches (basic-auth env migration) into source + preserve Mac2 static-snapshot 404 guard

ac33e88438fd481399c3aa0fce12b77284ab3577 · 2026-05-26 10:54:55 -0700 · Steve

Files touched

Diff

commit ac33e88438fd481399c3aa0fce12b77284ab3577
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue May 26 10:54:55 2026 -0700

    george-gmail: recover prod hand-patches (basic-auth env migration) into source + preserve Mac2 static-snapshot 404 guard
---
 public/index.html |  8 ++------
 server.js         | 30 ++++++++++++++++++++++++++----
 2 files changed, 28 insertions(+), 10 deletions(-)

diff --git a/public/index.html b/public/index.html
index 7a8fc14..2d7a1e5 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,8 +1,6 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <link rel="icon" type="image/svg+xml" href="/favicon.svg">
-
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>George — Gmail Agent</title>
@@ -57,15 +55,13 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou
 <body>
 <div id="root"></div>
 <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
-<script>
-function xfetch(url,opts){return new Promise(function(resolve,reject){var x=new XMLHttpRequest();x.open((opts&&opts.method)||"GET",url,true);x.setRequestHeader("Authorization","Basic "+btoa("admin:DWSecure2024!"));if(opts&&opts.body){x.setRequestHeader("Content-Type","application/json");}x.onload=function(){try{resolve({ok:x.status>=200&&x.status<300,status:x.status,json:function(){return Promise.resolve(JSON.parse(x.responseText))},text:function(){return Promise.resolve(x.responseText)}})}catch(e){reject(e)}};x.onerror=function(){reject(new Error("XHR failed"))};x.send(opts&&opts.body?JSON.stringify(opts.body):null)})};
-</script>
+function xfetch(url,opts){return new Promise(function(resolve,reject){var x=new XMLHttpRequest();x.open((opts&&opts.method)||"GET",url,true);x.setRequestHeader("Authorization","Basic "+btoa("admin:I3YusisdESUNdxtrmlb3QJeu9q8ODKJO"));if(opts&&opts.body){x.setRequestHeader("Content-Type","application/json");}x.onload=function(){try{resolve({ok:x.status>=200&&x.status<300,status:x.status,json:function(){return Promise.resolve(JSON.parse(x.responseText))},text:function(){return Promise.resolve(x.responseText)}})}catch(e){reject(e)}};x.onerror=function(){reject(new Error("XHR failed"))};x.send(opts&&opts.body?JSON.stringify(opts.body):null)})};
 <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
 <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
 <script type="text/babel">
 const { useState, useEffect, useCallback } = React;
 
-const AUTH = 'Basic ' + btoa('admin:DWSecure2024!');
+const AUTH = 'Basic ' + btoa('admin:I3YusisdESUNdxtrmlb3QJeu9q8ODKJO');
 async function api(path) {
   const r = await xfetch(path, { headers: { Authorization: AUTH } });
   return r.json();
diff --git a/server.js b/server.js
index 75bd59f..e25a0e3 100644
--- a/server.js
+++ b/server.js
@@ -1,7 +1,7 @@
 /**
  * George the Gmail Agent — Port 9889
  * Secure email access for DW-Agents ecosystem
- * Auth: admin / DWSecure2024!
+ * Auth: admin / I3YusisdESUNdxtrmlb3QJeu9q8ODKJO
  */
 const express = require('express');
 const helmet = require('helmet');
@@ -220,6 +220,28 @@ const app = express();
 app.use(helmet({ contentSecurityPolicy: false }));
 app.use(express.json({ limit: '50mb' }));
 
+// ─── Basic Auth credential resolution (2026-05-24 migration PR-1) ───
+// Reads GEORGE_BASIC_AUTH=user:pass from env if present; falls back to the
+// historical hardcoded value so the ~20 caller scripts in /root/DW-Agents/
+// keep working unchanged. Future PRs in the migration plan documented at
+// /root/DW-Agents/KNOWN-ISSUE-george-auth.md will switch callers off the
+// hardcoded value, then rotate, then drop the fallback.
+const _GEORGE_AUTH_ENV =
+  (typeof creds !== "undefined" && creds.GEORGE_BASIC_AUTH) ||
+  process.env.GEORGE_BASIC_AUTH ||
+  "";
+const _GEORGE_AUTH_SOURCE = _GEORGE_AUTH_ENV ? "env" : "hardcoded-fallback";
+const [_GEORGE_USER, _GEORGE_PASS] = _GEORGE_AUTH_ENV.includes(":")
+  ? _GEORGE_AUTH_ENV.split(":")
+  : ["admin", (process.env.GEORGE_BASIC_AUTH_PASS || "I3YusisdESUNdxtrmlb3QJeu9q8ODKJO")];
+console.log(`[George] basic-auth source: ${_GEORGE_AUTH_SOURCE} (user=${_GEORGE_USER}, pass-len=${_GEORGE_PASS.length})`);
+const crypto = require("crypto");
+function _ctEq(a, b) {
+  const ab = Buffer.from(String(a)); const bb = Buffer.from(String(b));
+  if (ab.length !== bb.length) { crypto.timingSafeEqual(ab, ab); return false; }
+  return crypto.timingSafeEqual(ab, bb);
+}
+
 // ─── 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'];
@@ -231,7 +253,7 @@ app.use((req, res, next) => {
   }
   const decoded = Buffer.from(auth.slice(6), 'base64').toString();
   const [user, pass] = decoded.split(':');
-  if (user === 'admin' && pass === 'DWSecure2024!') return next();
+  if (_ctEq(user, _GEORGE_USER) && _ctEq(pass, _GEORGE_PASS)) return next();
   res.setHeader('WWW-Authenticate', 'Basic realm="George Gmail Agent"');
   res.status(401).json({ error: 'Invalid credentials' });
 });
@@ -728,7 +750,7 @@ app.use((req, res, next) => {
   const auth = req.headers.authorization;
   if (!auth || !auth.startsWith('Basic ')) return res.status(401).json({ error: 'Auth required' });
   const [user, pass] = Buffer.from(auth.split(' ')[1], 'base64').toString().split(':');
-  if (user !== 'admin' || pass !== 'DWSecure2024!') return res.status(401).json({ error: 'Invalid credentials' });
+  if (user !== 'admin' || pass !== (process.env.GEORGE_BASIC_AUTH_PASS || 'I3YusisdESUNdxtrmlb3QJeu9q8ODKJO')) return res.status(401).json({ error: 'Invalid credentials' });
   next();
 });
 
@@ -989,7 +1011,7 @@ function inferSource(body, req) {
   return String(raw).slice(0, 120);
 }
 function withSourceFooter(htmlBody, source) {
-  if (!source) return htmlBody || '';   // empty source → no banner (customer-facing send)
+  if (!source) return htmlBody || '';   // empty source -> no banner (customer-facing send)
   const ts = new Date().toLocaleString('en-US', { timeZone: 'America/Los_Angeles', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' });
   const tag = `<div style="font-family:ui-monospace,Menlo,monospace;font-size:10px;letter-spacing:.10em;text-transform:uppercase;color:#7a766f;padding:8px 12px;background:rgba(212,160,74,0.08);border-left:3px solid #d4a04a;margin-bottom:18px;border-radius:2px"><strong style="color:#d4a04a">From job:</strong> ${escapeHtml(source)} <span style="color:#a8a39c">·</span> ${ts}</div>`;
   return tag + (htmlBody || '');

← 630eea8 fix(public/index.html): wrap xfetch helper in <script> tags  ·  back to George Gmail  ·  Add per-site favicon (kills /favicon.ico 404) 0d55d20 →