Skip to content

Commit

Permalink
Improve email verification with timeout and fallback handling
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Jan 31, 2025
1 parent 95e45d4 commit df7c4ef
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 12 deletions.
5 changes: 3 additions & 2 deletions app/routes/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@ export async function action({ request }: ActionFunctionArgs) {
async function isEmailVerified(
email: string,
): Promise<{ verified: true } | { verified: false; message: string }> {
const verifierResult = await verifyEmailAddress(email)
if (verifierResult.status) return { verified: true }
const userExists = Boolean(
await prisma.user.findUnique({
select: { id: true },
Expand All @@ -146,6 +144,9 @@ async function isEmailVerified(
const convertKitSubscriber = await getConvertKitSubscriber(email)
if (convertKitSubscriber) return { verified: true }

const verifierResult = await verifyEmailAddress(email)
if (verifierResult.status) return { verified: true }

return { verified: false, message: verifierResult.error.message }

Check failure on line 150 in app/routes/login.tsx

View workflow job for this annotation

GitHub Actions / ʦ TypeScript

Property 'error' does not exist on type '{ status: false; error: { code: number; message: string; }; } | { status: boolean; email: string; domain: string | undefined; }'.
}

Expand Down
43 changes: 33 additions & 10 deletions app/utils/verifier.server.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
// verifier is an email verification service

import { getRequiredServerEnvVar } from './misc.tsx'
import { getErrorMessage, getRequiredServerEnvVar } from './misc.tsx'

const VERIFIER_API_KEY = getRequiredServerEnvVar('VERIFIER_API_KEY')

type VerifierResult =
| { status: true; email: string; domain: string }
| {
status: false
error: { code: number; message: string }
}

export async function verifyEmailAddress(emailAddress: string) {
const verifierUrl = new URL(
`https://verifier.meetchopra.com/verify/${emailAddress}`,
)
verifierUrl.searchParams.append('token', VERIFIER_API_KEY)
type VerifierResult =
| { status: true; email: string; domain: string }
| {
status: false
error: { code: number; message: string }
}
const response = await fetch(verifierUrl.toString())
const verifierResult: VerifierResult = await response.json()
return verifierResult
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 2000)
try {
const response = await fetch(verifierUrl.toString(), {
signal: controller.signal,
})
clearTimeout(timeoutId)
const verifierResult: VerifierResult = await response.json()
return verifierResult
} catch (error: unknown) {
clearTimeout(timeoutId)
if (error instanceof Error && error.name === 'AbortError') {
console.error('Email verification timed out:', emailAddress)
} else {
console.error('Error verifying email:', getErrorMessage(error))
}
// If the request times out, we'll return a default result
// we don't I wanna block the user from logging in if we can't verify the email address
// is invalid...
return {
status: true,
email: emailAddress,
domain: emailAddress.split('@')[1],
}
}
}

0 comments on commit df7c4ef

Please sign in to comment.