Revert "improvement(auth): layer disposable-email-domains into signup email validation (#5010)" (#5014)

This reverts commit 2c0a10a561.
This commit is contained in:
Theodore Li
2026-06-12 20:42:09 -04:00
committed by GitHub
parent cd324d5657
commit 43017d72e0
7 changed files with 2 additions and 103 deletions
+1 -12
View File
@@ -69,18 +69,7 @@ jobs:
if [ "${ENVIRONMENT}" = "dev" ]; then
echo "Dev environment — pushing schema directly (db:push)"
# `--force` only suppresses the data-loss confirm, not drizzle's
# rename-vs-drop prompt, which fires (and crashes, no TTY) when a
# diff both adds and drops tables/columns at once. Turn that opaque
# crash into an actionable failure instead of a bare stack trace.
push_output="$(bun run db:push --force 2>&1)" && push_status=0 || push_status=$?
echo "$push_output"
if [ "$push_status" -ne 0 ]; then
if printf '%s' "$push_output" | grep -q 'Interactive prompts require a TTY'; then
echo "::error title=Dev schema push needs manual reconciliation::drizzle-kit push hit an interactive rename/drop prompt that CI cannot answer. The dev DB has drifted from schema.ts: it still holds table(s)/column(s) the schema no longer declares while the schema also adds new ones, so drizzle cannot tell a rename from a drop+create. Fix: drop the stale objects on the dev DB to match schema.ts — the same DROPs the latest versioned migration already applied to staging/prod (grep packages/db/migrations for the most recent DROP TABLE / DROP COLUMN) — then re-run this workflow. --force cannot bypass this prompt."
fi
exit "$push_status"
fi
bun run db:push --force
else
echo "Applying versioned migrations (db:migrate)"
bun run ./scripts/migrate.ts
+1 -10
View File
@@ -21,7 +21,6 @@ import {
organization,
} from 'better-auth/plugins'
import { emailHarmony } from 'better-auth-harmony'
import { validateEmail as validateEmailWithMailchecker } from 'better-auth-harmony/email'
import { and, count, eq, inArray, sql } from 'drizzle-orm'
import { headers } from 'next/headers'
import Stripe from 'stripe'
@@ -79,7 +78,6 @@ import {
import { PlatformEvents } from '@/lib/core/telemetry'
import { getBaseUrl, isLocalhostUrl, parseOriginList } from '@/lib/core/utils/urls'
import { processCredentialDraft } from '@/lib/credentials/draft-processor'
import { isDisposableEmailDomain } from '@/lib/messaging/email/disposable-domains.server'
import { sendEmail } from '@/lib/messaging/email/mailer'
import { getFromEmailAddress, getPersonalEmailFrom } from '@/lib/messaging/email/utils'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
@@ -932,14 +930,7 @@ export const auth = betterAuth({
}),
},
plugins: [
...(isSignupEmailValidationEnabled
? [
emailHarmony({
validator: async (email) =>
validateEmailWithMailchecker(email) && !(await isDisposableEmailDomain(email)),
}),
]
: []),
...(isSignupEmailValidationEnabled ? [emailHarmony()] : []),
...(env.TURNSTILE_SECRET_KEY
? [
captcha({
-10
View File
@@ -1,10 +0,0 @@
/** Ambient types for `disposable-email-domains` — ships JSON arrays with no bundled types. */
declare module 'disposable-email-domains' {
const domains: string[]
export default domains
}
declare module 'disposable-email-domains/wildcard.json' {
const baseDomains: string[]
export default baseDomains
}
@@ -1,35 +0,0 @@
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import { isDisposableEmailDomain } from '@/lib/messaging/email/disposable-domains.server'
describe('isDisposableEmailDomain', () => {
it('flags a known disposable domain', async () => {
expect(await isDisposableEmailDomain('someone@mailinator.com')).toBe(true)
})
it('flags a subdomain of a wildcard base domain', async () => {
expect(await isDisposableEmailDomain('someone@inbox.10mail.org')).toBe(true)
})
it('flags the bare wildcard base domain itself', async () => {
expect(await isDisposableEmailDomain('someone@10mail.org')).toBe(true)
})
it('is case-insensitive on the domain', async () => {
expect(await isDisposableEmailDomain('Someone@MailInator.com')).toBe(true)
})
it('allows a normal provider domain', async () => {
expect(await isDisposableEmailDomain('someone@gmail.com')).toBe(false)
})
it('allows a custom catch-all domain that is not on the list', async () => {
expect(await isDisposableEmailDomain('sim6dc088f506@lordfortescue.org.uk')).toBe(false)
})
it('returns false for malformed input with no domain', async () => {
expect(await isDisposableEmailDomain('not-an-email')).toBe(false)
})
})
@@ -1,32 +0,0 @@
let cache: { exact: Set<string>; wildcards: string[] } | undefined
/**
* Lazily loads the `disposable-email-domains` dataset (~120K exact domains plus
* wildcard base domains) on first use and memoizes it. Deferred behind a dynamic
* import so deployments with signup email validation disabled never load it.
*/
async function loadDisposableData(): Promise<{ exact: Set<string>; wildcards: string[] }> {
if (!cache) {
const [{ default: exactList }, { default: wildcards }] = await Promise.all([
import('disposable-email-domains'),
import('disposable-email-domains/wildcard.json'),
])
cache = { exact: new Set(exactList), wildcards }
}
return cache
}
/**
* Server-only disposable-email-domain check. Layered alongside better-auth-harmony's
* bundled Mailchecker list at the signup gate. Matches exact domains and any subdomain
* of (or the bare) wildcard base domain.
*
* Never import from client code — the dataset would bloat the browser bundle.
*/
export async function isDisposableEmailDomain(email: string): Promise<boolean> {
const domain = email.split('@')[1]?.toLowerCase()
if (!domain) return false
const { exact, wildcards } = await loadDisposableData()
if (exact.has(domain)) return true
return wildcards.some((base) => domain === base || domain.endsWith(`.${base}`))
}
-1
View File
@@ -126,7 +126,6 @@
"csv-parse": "6.1.0",
"date-fns": "4.1.0",
"decimal.js": "10.6.0",
"disposable-email-domains": "1.0.62",
"docx": "^9.6.1",
"docx-preview": "^0.3.7",
"drizzle-orm": "^0.45.2",
-3
View File
@@ -185,7 +185,6 @@
"csv-parse": "6.1.0",
"date-fns": "4.1.0",
"decimal.js": "10.6.0",
"disposable-email-domains": "1.0.62",
"docx": "^9.6.1",
"docx-preview": "^0.3.7",
"drizzle-orm": "^0.45.2",
@@ -2213,8 +2212,6 @@
"dingbat-to-unicode": ["dingbat-to-unicode@1.0.1", "", {}, "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w=="],
"disposable-email-domains": ["disposable-email-domains@1.0.62", "", {}, "sha512-LBQvhRw7mznQTPoyZbsmYeNOZt1pN5aCsx4BAU/3siVFuiM9f2oyKzUaB8v1jbxFjE3aYqYiMo63kAL4pHgfWQ=="],
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
"dockerfile-ast": ["dockerfile-ast@0.7.1", "", { "dependencies": { "vscode-languageserver-textdocument": "^1.0.8", "vscode-languageserver-types": "^3.17.3" } }, "sha512-oX/A4I0EhSkGqrFv0YuvPkBUSYp1XiY8O8zAKc8Djglx8ocz+JfOr8gP0ryRMC2myqvDLagmnZaU9ot1vG2ijw=="],