Skip to content

feat(auth): record owner-mailbox proof from OIDC logins - #1076

Merged
jiashuoz merged 3 commits into
mainfrom
feat/oidc-owner-email-proof
Oct 10, 2026
Merged

jiashuoz merged 3 commits into
mainfrom
feat/oidc-owner-email-proof

Conversation

@jiashuoz

@jiashuoz jiashuoz commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Problem

A restricted account may still email its verified owner mailbox (the sign-in email), but only if proof exists in users.owner_email_verified_*. Today exactly one path writes it: the Google OAuth callback. The generic OIDC login never records proof and requests only the openid scope, so on deployments where OIDC is the default sign-in most users never get proof and their agents cannot email them.

The rule and trust argument

After the OIDC callback resolves a non-trashed user, if the ID token carries email (string) and email_verified (JSON boolean true; the string "true" is not verified) and the trimmed/lower-cased claim equals the account's current email, RecordOIDCOwnerEmailProof writes proof with source oidc.

  • The ID token is issuer-signed and audience/nonce-verified; user_id_claim binds the login to the account; email_verified is the IdP's assertion.
  • Equality with the account's current email is re-checked in SQL (deleted_at IS NULL AND lower(btrim(email)) = $2), same as the Google path.
  • Best effort like Google: a failed write logs [auth] record owner email proof failed: user=<id> err=... and never fails the login. Email and raw claims are never logged. Mismatch/absent/unverified silently records nothing.

Config

oidc.scopes / E2A_OIDC_SCOPES (comma-separated, trimmed). Default [openid]. Validation: no empty entries, no duplicates, must contain openid. Documented in config.example.yaml.

Migration

132_owner_email_proof_oidc_source.sql widens users_owner_email_verified_source_check to ('google_oauth', 'oidc'); idempotent.

Backward compatibility

Default scopes are unchanged, so existing deployments see zero behaviour change. Adding email requires the IdP client registration to allow it, otherwise logins fail with invalid_scope.

Tests

  • config: default, yaml list, env parsing (spaces, trailing comma), errors for missing openid, empty entry, duplicate.
  • auth/oidc: authorize URL carries configured scopes; table-driven callback cases (verified match, unverified, string "true", different email, case/whitespace, claims absent, email without flag, trashed account) asserting proof row, session cookie/redirect and metric outcome.
  • identity: RecordOIDCOwnerEmailProof success/normalization, deleted account, mismatch, empty inputs, constraint still rejects unknown sources.
  • Docs updated (async-message-pipeline.md, data-handling.md). No dashboard copy named Google as the only verifier, so web is untouched.

Rollout for hosted deployments

Config may ship in either order relative to the binary: an older binary decodes the main config leniently and silently ignores oidc.scopes / E2A_OIDC_SCOPES, continuing to request openid. The only hard ordering constraint is that the IdP's client registration must allow email before any binary that requests it is live, or every OIDC login fails with invalid_scope. Enable email only against an IdP that sets email_verified: true solely after a mailbox confirmation (see the trust warning in config.example.yaml and docs/deployment.md).

Merge note

Migration slot 132 is also claimed by open PRs #1075 and #1061; whichever merges second must renumber this file, because internal/identity/migrate_test.go fails on a shared slot.

🤖 Generated with Claude Code

jiashuoz and others added 3 commits October 10, 2026 15:45
A restricted account may email its verified owner mailbox, but proof was
only ever written by the Google OAuth callback. The generic OIDC login now
records it when the issuer-signed ID token asserts email_verified (JSON
true) for an email equal to the account's current email. Adds oidc.scopes
(E2A_OIDC_SCOPES, default [openid]) so deployments can request the email
scope, and migration 132 allowing 'oidc' as a proof source.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Trim scope entries, reject internal whitespace and comma-only
E2A_OIDC_SCOPES, document the email_verified trust requirement and the
env var in deployment docs, and test numeric email_verified.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jiashuoz
jiashuoz merged commit a4efc5b into main Oct 10, 2026
29 checks passed
@jiashuoz
jiashuoz deleted the feat/oidc-owner-email-proof branch October 10, 2026 09:12
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