[object Object]

← back to Dw Signup Fulfillment

fix George Basic-auth resolution (real 401 root cause: wrong cred source→empty pass); verify via config; abort honor live-run if Shopify token unset

d5c8741f3658dbdf4229c01a2536f19aed46af11 · 2026-08-14 11:30:39 -0700 · steve

Files touched

Diff

commit d5c8741f3658dbdf4229c01a2536f19aed46af11
Author: steve <steve@designerwallcoverings.com>
Date:   Fri Aug 14 11:30:39 2026 -0700

    fix George Basic-auth resolution (real 401 root cause: wrong cred source→empty pass); verify via config; abort honor live-run if Shopify token unset
---
 lib/config.js            | 12 +++++++++++-
 scripts/george-verify.js | 18 ++++++++----------
 scripts/honor-reissue.js |  8 ++++++++
 3 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/lib/config.js b/lib/config.js
index 4a7ddc3..872e177 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -94,7 +94,17 @@ const config = {
   GEORGE_ACCOUNT: process.env.GEORGE_ACCOUNT || 'steve-office',
   GEORGE_FROM: process.env.GEORGE_FROM || 'info@designerwallcoverings.com',
   GEORGE_EXTERNAL_SEND_TOKEN: firstEnv('GEORGE_EXTERNAL_SEND_TOKEN', GEORGE_ENVS),
-  GEORGE_BASIC_AUTH: firstEnv('GEORGE_BASIC_AUTH', GEORGE_ENVS) || 'admin:',
+  // Basic-auth credential for George. Resolve from the SAME source the working
+  // token-bridge uses (secrets-manager GEORGE_AUTH) if george-gmail/.env doesn't carry
+  // GEORGE_BASIC_AUTH, then normalize any form (a "Basic <b64>" header, a bare password,
+  // or "user:pass") down to "user:pass" so email.js can base64-encode it correctly. The
+  // old 'admin:' default silently produced a 401 — this is the fix for that.
+  GEORGE_BASIC_AUTH: (() => {
+    let v = firstEnv('GEORGE_BASIC_AUTH', GEORGE_ENVS) || firstEnv('GEORGE_AUTH', SECRETS_ENVS) || 'admin:';
+    if (v.startsWith('Basic ')) { try { v = Buffer.from(v.slice(6), 'base64').toString(); } catch (e) {} }
+    if (!v.includes(':')) v = 'admin:' + v;
+    return v;
+  })(),
 
   // Admin review surface basic-auth. Resolve order: explicit env → on-host secrets
   // master → the fleet default. Reading from the secrets file lets a strong per-host
diff --git a/scripts/george-verify.js b/scripts/george-verify.js
index 2785dd0..cd85864 100644
--- a/scripts/george-verify.js
+++ b/scripts/george-verify.js
@@ -8,21 +8,19 @@
 //   Expected: HTTP 200 and no "blocked" field  → token path healthy.
 //   HTTP 403 blocked                            → token mismatch (would need a fix).
 // Run:  node ~/Projects/dw-signup-fulfillment/scripts/george-verify.js
-const fs = require('fs');
-const os = require('os');
-const path = require('path');
+// Use the project's own config so this test exercises the EXACT credential path the real
+// sends use (config.GEORGE_BASIC_AUTH now resolves from secrets-manager GEORGE_AUTH and
+// normalizes to user:pass; config.GEORGE_EXTERNAL_SEND_TOKEN is the static approval token).
+const config = require('../lib/config');
 
-const envPath = path.join(os.homedir(), 'Projects', 'george-gmail', '.env');
-const env = fs.readFileSync(envPath, 'utf8');
-const pick = (k) => { const m = env.match(new RegExp('^' + k + '=(.+)$', 'm')); return m ? m[1].trim().replace(/^["']|["']$/g, '') : ''; };
-
-const token = pick('GEORGE_EXTERNAL_SEND_TOKEN');
-let auth = pick('GEORGE_BASIC_AUTH') || 'admin:';
+const token = config.GEORGE_EXTERNAL_SEND_TOKEN;
+const auth = config.GEORGE_BASIC_AUTH || 'admin:';
 const basic = auth.startsWith('Basic ') ? auth : 'Basic ' + Buffer.from(auth.includes(':') ? auth : 'admin:' + auth).toString('base64');
 
 console.log('token:', token ? 'SET (last4 ...' + token.slice(-4) + ')' : 'EMPTY');
+console.log('basic-auth user:', auth.split(':')[0], '(pass-len ' + ((auth.split(':')[1] || '').length) + ')');
 
-fetch('http://127.0.0.1:9850/api/send', {
+fetch((config.GEORGE_URL || 'http://127.0.0.1:9850') + '/api/send', {
   method: 'POST',
   headers: { 'Content-Type': 'application/json', Authorization: basic, 'X-Send-Approval': token },
   body: JSON.stringify({
diff --git a/scripts/honor-reissue.js b/scripts/honor-reissue.js
index 3314523..300fed7 100644
--- a/scripts/honor-reissue.js
+++ b/scripts/honor-reissue.js
@@ -77,6 +77,14 @@ function appendLedger(row) {
 }
 
 async function main() {
+  // SAFETY PREFLIGHT: live mode with no Shopify token would make createGiftCard/disable
+  // silently no-op (returning a synthetic stub) while STILL emailing the customer a code —
+  // i.e. a code for a gift card that doesn't exist. Refuse to run live without the token.
+  if (!config.DRY_RUN && !config.SHOPIFY_FULFILLMENT_TOKEN) {
+    console.error('ABORT: --apply requested but SHOPIFY_FULFILLMENT_TOKEN is unset. Refusing to email codes for cards that would not actually be minted. Set the token, then re-run.');
+    process.exit(2);
+  }
+
   const rows = fs.readFileSync(WORKLIST, 'utf8').split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l));
   const done = loadLedger();
   const pending = rows.filter((r) => { const d = done.get(String(r.customer_id)) || {}; return !d.minted && !d.finalized; });

← 3658732 honor-reissue: explicit --apply flag to go live (clean comma  ·  back to Dw Signup Fulfillment  ·  honor-reissue: 429-aware retry + gentle pacing for the 51-ca 5ea0034 →