Skip to content

Recognize digest email links and sync pins across devices - #205

Merged
jimdc merged 2 commits into
mainfrom
magic-link-recognition-from-digest-emails-server
Jul 30, 2026
Merged

Recognize digest email links and sync pins across devices#205
jimdc merged 2 commits into
mainfrom
magic-link-recognition-from-digest-emails-server

Conversation

@jimdc

@jimdc jimdc commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Alert digest emails can now quietly recognize the browser that opens them, so pinned investigations follow a subscriber across devices — without a password account or login form.

  • Magic-link session: every CityScroll notice link in a digest carries a pins-scoped signed token (same HMAC machinery as confirm/unsubscribe). Clicking through sets an HttpOnly, Secure, SameSite=Lax session cookie and lands on the notice URL with the token already removed.
  • Server-synced pins: recognized sessions store the investigation pin list in worker KV next to subscriptions. First visit merges local and server pins (union, dedupe by type+id); later edits write through. Anonymous visitors keep today's localStorage-only behavior.
  • Honest chrome: a dismissible banner says "Signed in via your email link" with a Not you? control that drops the session immediately. Privacy copy (all locales) explains the tradeoff and how to sign out.

Bearer-token tradeoff

Anyone who can open a subscriber's email can open a recognized session on their own device by clicking a digest link. That is the same reality as one-click unsubscribe and most "magic link" mail products. Mitigations: the session is scoped to read + pin sync only (unsubscribe, re-subscribe, and other account changes still need a fresh purpose-specific token); email-link tokens expire (~30 days) and renew on the next digest; the session cookie has its own bounded lifetime (~14 days); invalid or expired tokens degrade silently to anonymous; a visible Not you? control signs out instantly; tokens are stripped from the landing URL so they do not linger in history or referrers.

Screenshots

Mobile (390) session banner:

Session banner at 390px

Desktop (1440) session banner:

Session banner at 1440px

Privacy section (email links and pins):

Privacy copy at 1440px

Validation

  • Worker: token issue/verify/expiry/scope, cookie exchange, silent degrade, pin merge, two-device pin sync simulation
    • cd worker && node --test test/session_pins.test.mjs test/digest_footer.test.mjs
  • Client characterization (anonymous fallback, banner, merge helpers)
    • node --test test/session_pins_client.test.mjs
  • i18n key parity across all shipping languages; about.html reading level ≤ grade 7
  • Screenshot raw URLs curl-verified HTTP 200 at commit 731dad19661d84a2a3532324d58d54434e905ad6

Test plan

  • Subscribe with a test address and wait for (or dry-run) a digest containing a notice link with s=
  • Click the link in browser A → session banner appears; pin a notice → investigation list updates
  • Click a fresh email link in browser B → same pins visible
  • Not you? clears the banner and stops server pin writes
  • Unsubscribe still requires the dedicated unsubscribe token (session cookie alone is not enough)

Digest links now carry a pins-scoped signed token. Clicking through sets an
HttpOnly session cookie so the browser is recognized quietly, with a
dismissible "Not you?" banner. Pinned investigations sync to the worker for
recognized sessions (union merge on first visit); anonymous localStorage
behavior is unchanged. Account changes still require purpose-specific tokens.
@jimdc
jimdc enabled auto-merge July 30, 2026 23:06
Update characterization for honest privacy intro, await async redirect
handlers, and accept session tokens on digest notice links.
@jimdc
jimdc added this pull request to the merge queue Jul 30, 2026
Merged via the queue into main with commit 38fd0b6 Jul 30, 2026
11 checks passed
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