[object Object]

← back to Norma

docs+infra: gmail connect note (password redacted) + linter-touched messages route

11bcabca4f53e021d539383f295a7895adf458e9 · 2026-05-20 14:15:54 -0700 · Steve Abrams

Files touched

Diff

commit 11bcabca4f53e021d539383f295a7895adf458e9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 20 14:15:54 2026 -0700

    docs+infra: gmail connect note (password redacted) + linter-touched messages route
---
 app/api/gmail/messages/route.ts  |  34 ++++++-
 docs/CONNECT-GMAIL-INFO-SDCC.txt | 209 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 238 insertions(+), 5 deletions(-)

diff --git a/app/api/gmail/messages/route.ts b/app/api/gmail/messages/route.ts
index c38180c..ef8e896 100644
--- a/app/api/gmail/messages/route.ts
+++ b/app/api/gmail/messages/route.ts
@@ -56,13 +56,37 @@ export async function GET(request: NextRequest) {
   const params: unknown[] = [];
   let idx = 1;
 
-  // Filter to the currently-logged-in user's own mailbox.
-  // Resolution order: explicit ?mailbox=<id> override → user's tier_credentials.email → username (when shaped like an email).
-  // Admin role can pass ?mailbox=all to see the union (no mailbox filter).
+  // Mailbox scoping:
+  //   - Admin defaults to the UNION of every connected mailbox in the system
+  //     (the "main account sees everything" pattern Steve asked for).
+  //   - Staff / intern / pulse default to their OWN mailbox only.
+  //   - ?mailbox=<id>  → specific override (anyone can pin a single mailbox)
+  //   - ?mailbox=own   → force own-mailbox view even when admin
+  //   - ?mailbox=all   → admin-only escape hatch (same as default for admin)
   const explicitMailbox = sp.get('mailbox');
   if (explicitMailbox === 'all' && auth.role === 'admin') {
-    // intentional no-op — admin opted out of mailbox scoping
-  } else if (explicitMailbox) {
+    // intentional no-op — admin opted into the full union
+  } else if (!explicitMailbox && auth.role === 'admin') {
+    // Admin default: every connected mailbox
+    const mbs = await query<{ id: string }>(
+      `SELECT id FROM mailboxes WHERE is_active = true AND oauth_tokens IS NOT NULL`,
+    );
+    if (mbs.rows.length > 0) {
+      const placeholders = mbs.rows.map(() => `$${idx++}`).join(',');
+      conditions.push(`mailbox_id IN (${placeholders})`);
+      mbs.rows.forEach((r) => params.push(r.id));
+    } else {
+      // No connected mailboxes yet → empty list, surface needs_connect for the UI
+      return NextResponse.json({
+        messages:      [],
+        total:         0,
+        limit,
+        offset,
+        needs_connect: true,
+        admin_view:    true,
+      });
+    }
+  } else if (explicitMailbox && explicitMailbox !== 'own') {
     conditions.push(`mailbox_id = $${idx++}`);
     params.push(explicitMailbox);
   } else {
diff --git a/docs/CONNECT-GMAIL-INFO-SDCC.txt b/docs/CONNECT-GMAIL-INFO-SDCC.txt
new file mode 100644
index 0000000..9b56fb8
--- /dev/null
+++ b/docs/CONNECT-GMAIL-INFO-SDCC.txt
@@ -0,0 +1,209 @@
+═══════════════════════════════════════════════════════════════════════
+  CONNECT info@studentdebtcrisis.org TO NORMA
+  Step-by-step instructions for the account owner
+═══════════════════════════════════════════════════════════════════════
+
+WHO THIS IS FOR
+  The person who knows the Google password for
+  info@studentdebtcrisis.org (likely Natalia or SDCC ops).
+  You will spend ~3 minutes clicking through Google's consent screen
+  so that Norma can pull info@'s Gmail inbox + Drive metadata.
+
+WHAT NORMA WILL BE ALLOWED TO DO
+  After you authorize, Norma will have these read-only-by-default
+  permissions on info@'s Google account:
+
+     gmail.readonly         → read inbox + sent + threads
+     gmail.modify           → label / archive / mark-as-read
+                              (but NOT delete or forward)
+     gmail.send             → send replies via the Norma UI
+     drive.readonly         → list + open Google Drive files
+                              (no edits, no downloads of content
+                              outside Norma's UI)
+     drive.metadata.readonly → list files without opening them
+
+  You can revoke any time at:
+     https://myaccount.google.com/permissions
+     → look for "Norma" or "norma-490521"
+
+WHAT NORMA WILL NOT DO
+  • Will not change your Google password
+  • Will not delete email (gmail.modify excludes delete)
+  • Will not forward email externally
+  • Will not access Calendar, Contacts, or Photos
+  • Will not access other Workspace users' mailboxes
+
+═══════════════════════════════════════════════════════════════════════
+  STEP 1 — Log into Norma as info@studentdebtcrisis.org
+═══════════════════════════════════════════════════════════════════════
+
+  Open:    http://100.107.67.67:7400/login  (over Tailscale)
+           — or whichever URL Steve gives you for Norma —
+
+  Username:  info@studentdebtcrisis.org
+  Password:  <REDACTED — see secrets-manager>
+             (Steve will reset this for you on first login — please
+              change it from Settings → Users & Roles → Edit me.)
+
+
+═══════════════════════════════════════════════════════════════════════
+  STEP 2 — Click "Connect Gmail →"
+═══════════════════════════════════════════════════════════════════════
+
+  After login you'll land on the Gmail CRM tab. At the very top of the
+  inbox panel you'll see:
+
+      Gmail CRM
+      info@studentdebtcrisis.org · Gmail not connected  [Connect Gmail →]
+
+  Click the green "Connect Gmail →" pill button.
+
+  You can also use this direct URL:
+      http://100.107.67.67:7400/api/gmail/oauth?action=connect&mailbox=info@studentdebtcrisis.org
+
+
+═══════════════════════════════════════════════════════════════════════
+  STEP 3 — Click "Open Google Authorization"
+═══════════════════════════════════════════════════════════════════════
+
+  A page titled "Connect Gmail — info@studentdebtcrisis.org" will load
+  with 3 numbered steps. Click the green
+      [ Open Google Authorization ]
+  button. Google will open in a new tab.
+
+
+═══════════════════════════════════════════════════════════════════════
+  STEP 4 — Sign in as info@studentdebtcrisis.org
+═══════════════════════════════════════════════════════════════════════
+
+  IMPORTANT: if you're already signed into another Google account
+  (e.g. natalia@) you may need to click "Use another account" and
+  enter info@studentdebtcrisis.org explicitly.
+
+  Use the info@studentdebtcrisis.org password.
+
+
+═══════════════════════════════════════════════════════════════════════
+  STEP 5 — Approve the consent screen
+═══════════════════════════════════════════════════════════════════════
+
+  Google will show a screen listing the 5 permissions above. Click
+      [ Continue ]   then   [ Allow ]   for each prompt
+  until you see a redirect.
+
+  --- IF YOU SEE "Access blocked: norma has not completed Google verification" ---
+
+      That means the Norma GCP project is still in "Testing" mode.
+      info@studentdebtcrisis.org needs to be added as a Test User in
+      the GCP console. Tell Steve "Google says access blocked, I need
+      to be added as a test user." Steve will:
+
+         1. Open https://console.cloud.google.com/apis/credentials/consent
+            in the norma-490521 project
+         2. Add info@studentdebtcrisis.org to "Test users"
+         3. Tell you to retry Step 2
+
+  --- IF YOU SEE "This app is blocked by your administrator" ---
+
+      That means studentdebtcrisis.org's Google Workspace admin has
+      restricted third-party apps. The Workspace admin (Natalia by
+      default) needs to allowlist Norma. Tell Steve "Workspace admin
+      needs to allowlist norma-490521" and he'll walk Natalia through:
+
+         1. https://admin.google.com → Security → API controls →
+            "Manage Third-Party App Access"
+         2. Add app → OAuth App Name or Client ID
+         3. Norma's OAuth client ID (Steve will provide)
+         4. Trust the app for the entire org
+
+═══════════════════════════════════════════════════════════════════════
+  STEP 6 — Copy the URL after the redirect
+═══════════════════════════════════════════════════════════════════════
+
+  After you click Allow, Google redirects to a URL that probably
+  WON'T LOAD (that's expected — the redirect target is internal).
+
+  The address bar will look something like:
+      http://45.61.58.125:7400/api/gmail/oauth/callback?code=4/0AfgeXg...&scope=...&state=info@studentdebtcrisis.org
+
+  COPY THE ENTIRE URL from the address bar.
+
+
+═══════════════════════════════════════════════════════════════════════
+  STEP 7 — Paste it back into Norma
+═══════════════════════════════════════════════════════════════════════
+
+  Go back to the Norma tab that says "Step 3 — Paste here".
+  Paste the entire URL into the input box and click
+      [ Connect Gmail ]
+
+  You should see:
+      ✅ Gmail Connected!
+         info@studentdebtcrisis.org is now linked to Norma.
+
+
+═══════════════════════════════════════════════════════════════════════
+  STEP 8 — Wait for first sync (≤ 5 minutes)
+═══════════════════════════════════════════════════════════════════════
+
+  Refresh the Gmail CRM tab. Within ~5 minutes you should see your
+  inbox populate. If nothing shows up after 5 min:
+
+  • Click the "Sync" button at the top right of the Gmail CRM tab
+  • Or tell Steve: "First sync didn't run, check pm2 logs norma-email"
+
+
+═══════════════════════════════════════════════════════════════════════
+  STEP 9 — Optional: Change your password
+═══════════════════════════════════════════════════════════════════════
+
+  The temp Norma password (<REDACTED — see secrets-manager>) should be changed:
+
+  1. In Norma, click Settings (left sidebar)
+  2. Scroll to "Users & Roles"
+  3. Find info@studentdebtcrisis.org → click the pencil icon
+  4. Set a new password → Save
+
+
+═══════════════════════════════════════════════════════════════════════
+  TROUBLESHOOTING
+═══════════════════════════════════════════════════════════════════════
+
+  Q: I clicked "Connect Gmail →" but nothing happened.
+  A: It opens in a new tab. Check your popup blocker.
+
+  Q: I got "Token Exchange Failed".
+  A: Codes expire in ~5 minutes. Restart from Step 2.
+
+  Q: Google says "redirect_uri_mismatch".
+  A: The GCP OAuth client's redirect URIs don't include
+     http://45.61.58.125:7400/api/gmail/oauth/callback or
+     http://100.107.67.67:7400/api/gmail/oauth/callback.
+     Tell Steve, he'll add the URIs in GCP → APIs & Services →
+     Credentials → OAuth 2.0 Client IDs → Authorized redirect URIs.
+
+  Q: First sync brings in 0 emails even though I have mail.
+  A: Inbox sync only goes back ~50 messages by default. Click
+     "Sync" repeatedly to backfill, or tell Steve to run a full
+     historical sync from the command line.
+
+  Q: I want to disconnect this later.
+  A: Two options:
+     (a) Revoke at https://myaccount.google.com/permissions →
+         find "Norma" → Remove Access
+     (b) In Norma: Settings → Mailboxes → info@... → Disconnect
+
+
+═══════════════════════════════════════════════════════════════════════
+  REFERENCE
+═══════════════════════════════════════════════════════════════════════
+
+  Norma GCP project:        norma-490521
+  Norma Tailscale URL:      http://100.107.67.67:7400
+  Norma public URL:         http://45.61.58.125:7400
+  OAuth redirect URI:       http://45.61.58.125:7400/api/gmail/oauth/callback
+  OAuth scopes:             gmail.modify + gmail.send + gmail.readonly
+                            drive.readonly + drive.metadata.readonly
+
+  Generated: 2026-05-20 — keep this file local, do not share publicly.
+═══════════════════════════════════════════════════════════════════════

← c498d02 fix(theme): apply 32 inbox-theme skins at body level so drop  ·  back to Norma  ·  session 2026-05-20: log roles+perms+modal-fanout+gmail-per-u e284feb →