email-otp
@noy-db/on-email-otp·pnpm add @noy-db/on-email-otpEmail OTP second factor, bring-your-own transport.
What it does
issueEmailOtp({ email, transport, ttlSeconds, digits, maxAttempts }) generates
a numeric code (default 6 digits), hashes it with a random salt
(SHA-256(code ⊕ salt)) into an EmailOtpRecord — the plaintext code is
never stored, but treat the record as the code until it expires: a 6-digit code has a
million values, so anyone holding the salt and digest recovers it in milliseconds — and calls your transport function with the code to
deliver it, so any SMTP / SES / Postmark / custom sender works. Default
TTL is 300 seconds (5 minutes), default max attempts 5. verifyEmailOtp(input, record) increments attempts on every call and returns { ok, reason }
where reason is 'expired', 'mismatch', or 'locked' once
maxAttempts is hit; the caller deletes the record on success or
exhaustion.
Usage
import { issueEmailOtp, verifyEmailOtp } from '@noy-db/on-email-otp'
const { record } = await issueEmailOtp({
email: 'alice@acme.com',
transport: async ({ to, code }) => sendViaSES(to, `Your code: ${code}`),
})
const result = await verifyEmailOtp(userEnteredCode, record)
if (result.ok) { /* second factor satisfied */ }