Skip to the content
Krizaka TypeScript
Krizaka TypeScript

@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

bashTerminal
npm install @krizaka/email-verification

Also 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 + code and 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 (now is 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.

NameKindWhat it is
ChallengetypeOne pending proof, as the store keeps it.
CodeStoretypeWhere challenges live.
createEmailVerificationfunctionThe 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_CHALLENGESconstantThe memory bound of the in-process store, not a business parameter.
DEFAULT_SETTINGSconstant
EmailVerificationtype
EmailVerificationOptionstype
IssuedtypeWhat issue returns: mail code to the address; hand challengeId back to the browser (it is not secret, it is useless without the code).
memoryCodeStorefunctionChallenges kept in this process: right for one instance and for tests.
MIN_SECRET_LENGTHconstantThe shortest secret accepted: it salts every proof, so a leaked store alone cannot be brute-forced offline.
VERIFICATION_CODEconstant
VerificationErrorclass
VerificationLocaletypeThe mail that carries the code, in the visitor's language.
verificationMailfunctionSubject, text and HTML of the code mail.
VerificationMailtype
VerificationSettingstypeWhat a product tunes.
VERIFY_FAILUREconstantWhy a confirmation was refused.
VerifyFailuretype
VerifyResulttype

@krizaka/email-verification/testkit

NameKindWhat it is
codeStoreContractfunctionWhat every CodeStore adapter must do: the same behaviour behind the same port.

Package

Version on npm0.1.1 (npm)
LicenceApache-2.0
Node.js>=22
Dependencies@krizaka/time workspace:^
Peer dependenciesvitest >=4
Sourceemail-verification

Changelog

0.1.1

Patch changes

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/FR verificationMail. Org rule email-verification (krizaka/.github#464).

On this page