Skip to content

feat(identity): add email verification flow#14378

Open
dylanjeffers wants to merge 3 commits into
mainfrom
claude/xenodochial-mestorf-aef187
Open

feat(identity): add email verification flow#14378
dylanjeffers wants to merge 3 commits into
mainfrom
claude/xenodochial-mestorf-aef187

Conversation

@dylanjeffers
Copy link
Copy Markdown
Contributor

Summary

  • Adds native email verification to identity-service so we can drop Bouncer's deliverability check (Option A).
  • New `isEmailVerified`, `emailVerificationToken` (sha256-hashed), and `emailVerificationTokenCreatedAt` columns on `Users`. Bouncer / `isEmailDeliverable` are intentionally kept for backward compatibility — removal is a follow-up.
  • Signup now generates a token, stores its hash, and sends a verification email (24h TTL). Signups from disposable-email domains are rejected using the open-source disposable-email-domains blocklist embedded as a static file.
  • New endpoints: `GET /email/verify?token=…` (redirects to `audius.co/verify-email?status=…`) and `POST /email/resend-verification` (authed).
  • `recovery.js` and `welcomeEmail.js` now suppress sends when neither `isEmailVerified` nor the legacy `isEmailDeliverable` flag is true (so older accounts keep receiving mail).

Files

  • Migration: `packages/identity-service/sequelize/migrations/20260521000000-add-email-verification.js`
  • Model: `packages/identity-service/src/models/user.js`
  • Routes: `src/routes/user.js`, `src/routes/emailVerification.js`, `src/routes/recovery.js`, `src/routes/welcomeEmail.js`
  • Helpers: `src/utils/emailVerification.js`, `src/utils/disposableEmail.js`
  • Template: `src/notifications/emails/emailVerification.js`
  • Blocklist data: `src/data/disposable_email_blocklist.conf`

Out of scope (follow-ups)

  • Remove Bouncer call + drop `isEmailDeliverable` column once verification ramps.
  • Update the anti-abuse oracle (pedalboard) to score on `isEmailVerified` instead of `isEmailDeliverable`.

Test plan

  • `npm run typecheck` and `npm run lint` in `packages/identity-service` — both pass locally.
  • Run `db:migrate` against a dev DB; verify the three columns and index land, then run the `down` migration cleanly.
  • Sign up a new user; confirm a row is created with `isEmailVerified=false` and a hashed token, and a verification email is sent via Sendgrid (or skipped with a warn log when Sendgrid is unconfigured).
  • Click the link → `isEmailVerified` flips to true, token columns cleared, redirected to `/verify-email?status=success`.
  • Tamper with the token / let it expire → redirected to `status=invalid` / `status=expired`.
  • Hit `POST /email/resend-verification` while authed; new token issued, email sent.
  • Try signing up with `@mailinator.com` (or any address from the blocklist) → 400 with a clear error message.
  • On an account with `isEmailVerified=false` and `isEmailDeliverable=true` (legacy), recovery + welcome emails still send.
  • On an account with both flags false, recovery + welcome emails are suppressed.

🤖 Generated with Claude Code

dylanjeffers and others added 3 commits May 20, 2026 17:00
Instruments three key contest interactions following the existing
'Remix Contest:' event prefix and {remixContestId, trackId} property
shape used by the host/pick-winners events:

  - REMIX_CONTEST_VIEW: contest page/screen first resolves trackId+eventId
  - REMIX_CONTEST_ENTER: user taps Enter Contest / Upload Remix
  - REMIX_CONTEST_VIEW_SUBMISSIONS: user opens the submissions tab

Mobile submissions-tab firing uses useFocusedTab from
react-native-collapsible-tab-view because the contest tabs mount
eagerly (lazy: false) — a plain mount effect would fire even for
users who only view the Details tab.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove the app from the list immediately on delete click rather than
waiting for the API response. Restores previous state on error.

Moves list update from onSuccess to onMutate (with cancelQueries to
avoid race conditions) and adds onError rollback.
Add native email verification to identity-service so we can stop relying
on Bouncer's deliverability check. New columns isEmailVerified,
emailVerificationToken (sha256-hashed), and emailVerificationTokenCreatedAt
are added to the Users table. Signup now sends a verification email with
a 24h-expiring token, exposes GET /email/verify and authed POST
/email/resend-verification, and rejects signups from disposable-email
domains (open-source blocklist embedded as a static file). Recovery and
welcome email suppression now honors isEmailVerified, falling back to
the legacy isEmailDeliverable flag so existing accounts keep working.
Bouncer code is intentionally left in place; removal will follow once
verification has rolled out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 21, 2026

⚠️ No Changeset found

Latest commit: e70e926

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

🌐 Web preview ready

Preview URL: https://audius-web-preview-pr-14378.audius.workers.dev

Unique preview for this PR (deployed from this branch).
Workflow run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant