Skip to content

Gmail: setup guide, honest reconnect status, key-clobber fix, labels API - #258

Open
hilash wants to merge 1 commit into
mainfrom
fix/gmail-setup-guide-and-decrypt
Open

Gmail: setup guide, honest reconnect status, key-clobber fix, labels API#258
hilash wants to merge 1 commit into
mainfrom
fix/gmail-setup-guide-and-decrypt

Conversation

@hilash

@hilash hilash commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

What

Four related Gmail improvements, born from a real break: Mailroom fell back to demo mode because the stored App Password could no longer be decrypted, while Integrations still said "Connected".

1. Root cause + fix: encryption-key clobbering

The stored App Password is AES-256-GCM encrypted under a per-install secret in .cabinet.env. getKeySecret() generated-and-persisted a fresh secret whenever a process booted without it in process.env — so a sibling process (Next app vs daemon vs a Turbopack dev worker) could overwrite the persisted secret and orphan every stored password. Confirmed by decrypting the live DB row offline: no available key matched.

Fix: the on-disk secret is now the durable source of truth — encryption prefers it over process.env, and after generating a fresh secret we re-read the file to adopt a concurrent sibling's write. Decrypt already tries env + file + legacy keys, so user-set env overrides still work. Co-located crypto.test.ts (node:test) covers round-trip, fresh-IV, and tamper rejection.

2. /api/gmail/status no longer lies

It only checked that a credentials row existed, so it reported connected: true while every real call 500'd. It now attempts decryption and returns needsReconnect: true; the Integrations panel shows an amber "Reconnect needed" banner with the saved email pre-filled.

3. Setup guide with mockups

Integrations → Gmail now has a Telegram/Slack-style numbered setup guide for Google's 2-Step Verification → App passwords flow, drawn as stylized theme-aware mockups (GmailArt, shared setup-art primitives — no screenshots, no personal data). Brand color switched from Gmail red (read as a danger button) to Google blue. /api/gmail/connect now strips whitespace from pasted App Passwords (Google displays them as xxxx xxxx xxxx xxxx; a verbatim paste failed IMAP auth).

4. Gmail labels API

GET/POST /api/gmail/labels and POST /api/gmail/label (apply = IMAP copy into the label's mailbox; message stays in inbox), with label-name validation at the trust boundary. Powers the Mailroom label controls.

Verification

  • npx tsc --noEmit clean; npm run lint 0 errors
  • npx tsx --test src/lib/gmail/crypto.test.ts — 3/3 pass
  • Live: GET /api/gmail/status on the affected install now returns {"connected":false,"needsReconnect":true,...} for the orphaned credential
  • Labels routes live-tested (validation errors pre-IMAP; full apply pending reconnect)

🤖 Generated with Claude Code

… fixes, labels API

- Integrations > Gmail now has a Telegram-style numbered setup guide with
  stylized theme-aware mockups of Google's 2SV -> App passwords flow
  (GmailArt); brand switched from Gmail red to Google blue; removed the
  redundant collapsed instructions block from GmailSection.
- /api/gmail/connect strips whitespace from pasted App Passwords (Google
  displays them as 'xxxx xxxx xxxx xxxx') and trims the email.
- /api/gmail/status verifies the stored password actually decrypts and
  returns needsReconnect instead of claiming connected; GmailSection shows
  a reconnect banner with the saved email pre-filled.
- getKeySecret() prefers the on-disk .cabinet.env secret over process.env
  and re-reads after generating, so a sibling process (app vs daemon vs
  dev worker) can no longer clobber the persisted secret and orphan the
  stored password. Co-located crypto.test.ts covers round-trip, fresh IV,
  and tamper rejection.
- Gmail labels API: GET/POST /api/gmail/labels, POST /api/gmail/label
  (apply = IMAP copy to label mailbox), with label-name validation.
@hilash
hilash requested a review from a team as a code owner July 18, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant