@krizaka/email-verification
Proof of an e-mail address for public forms: a 6-digit code valid 15 minutes, single use, five tries, kept as a salted hash; the message is released only after the code.
Install
npm install @krizaka/email-verificationAlso published as subpath exports: @krizaka/email-verification/testkit.
Proof that a person controls an e-mail address, for every Krizaka public form (rule email-verification of the org registry: public e-mail forms verify the address). The message is held and delivered only after the code is confirmed (verify first): a bot that types someone else's address costs that person one e-mail and the team nothing.
- a 6-digit code (
crypto.randomInt), valid 15 minutes, single use, 5 tries (then the challenge is destroyed); - the store keeps only a salted HMAC of
id + address + codeand the held payload — never the code, never the address in the clear; - the proof binds the address: the right code with another address is refused;
- mail texts EN and FR (
verificationMail); the module never sends: the product queues the mail (@krizaka/mail, Mailgun, …); - no environment variable, no wall clock (
nowis injected), no vendor; the secret comes in as a parameter (≥ 32 characters).
import { createEmailVerification, memoryCodeStore, verificationMail } from "@krizaka/email-verification";
const verification = createEmailVerification({ store: memoryCodeStore(), secret, now: () => new Date() });
// 1. the form is posted (after its CAPTCHA and rate limit): hold the message, mail the code
const { challengeId, code, ttlMinutes } = await verification.issue({ address, payload: message });
await send({ to: address, ...verificationMail({ locale, code, ttlMinutes }) });
// answer { challengeId } — the browser asks for the code
// 2. the visitor returns the code
const result = await verification.confirm<Message>({ challengeId, address, code });
if (result.ok) await deliver(result.payload); // released once
else /* result.reason: invalid (attemptsLeft) | expired | locked | unknown */;memoryCodeStore is right for one instance and for tests (a visitor who lands on another instance, or after a restart, is asked for a new code). A product with several instances implements CodeStore (5 methods) and runs codeStoreContract from @krizaka/email-verification/testkit.
Callers still own the rate limit of issue (@krizaka/rate-limit: per address and per IP, so the code mail cannot be used to flood a mailbox) and the CAPTCHA (@krizaka/rate-limit/turnstile).
Exports
The 19 names the package exports, read from its types.
| Name | Kind | What it is |
|---|---|---|
Challenge | type | One pending proof, as the store keeps it. |
CodeStore | type | Where challenges live. |
createEmailVerification | function | The verification of an address: issue holds an action (the payload) and produces the code to mail; confirm releases the action only for the right code, a… |
DEFAULT_MAX_CHALLENGES | constant | The memory bound of the in-process store, not a business parameter. |
DEFAULT_SETTINGS | constant | |
EmailVerification | type | |
EmailVerificationOptions | type | |
Issued | type | What issue returns: mail code to the address; hand challengeId back to the browser (it is not secret, it is useless without the code). |
memoryCodeStore | function | Challenges kept in this process: right for one instance and for tests. |
MIN_SECRET_LENGTH | constant | The shortest secret accepted: it salts every proof, so a leaked store alone cannot be brute-forced offline. |
VERIFICATION_CODE | constant | |
VerificationError | class | |
VerificationLocale | type | The mail that carries the code, in the visitor's language. |
verificationMail | function | Subject, text and HTML of the code mail. |
VerificationMail | type | |
VerificationSettings | type | What a product tunes. |
VERIFY_FAILURE | constant | Why a confirmation was refused. |
VerifyFailure | type | |
VerifyResult | type |
@krizaka/email-verification/testkit
| Name | Kind | What it is |
|---|---|---|
codeStoreContract | function | What every CodeStore adapter must do: the same behaviour behind the same port. |
Package
| Version on npm | 0.1.1 (npm) |
| Licence | Apache-2.0 |
| Node.js | >=22 |
| Dependencies | @krizaka/time workspace:^ |
| Peer dependencies | vitest >=4 |
| Source | email-verification |
Changelog
0.1.1
Patch changes
- Updated dependencies [2a7898c]
- @krizaka/[email protected]
0.1.0
Minor changes
- New package: proof of an e-mail address for public forms (
createEmailVerification): a 6-digit code valid 15 minutes, single use, 5 tries, kept as a salted hash; the action is held and released only after the code is confirmed.memoryCodeStore,codeStoreContract(/testkit), EN/FRverificationMail. Org ruleemail-verification(krizaka/.github#464).