2.2 KiB
totp/verify-many
Purpose
Backup-aware TOTP verify. Used during rotation windows where an old and a new TOTP secret coexist — the user's authenticator may still be holding the old secret while the server has moved to the new one. Returns the matching secret index and signed step delta so the caller can mark the rotation complete after the user has successfully authenticated against the new secret.
Features
- Multi-secret loop with constant-time evaluation across the entire list.
- Same
±windowsemantics asverify. - Returns
{ valid, delta, secretIndex }so the caller can record which secret was used. - Window cap and Zod-boundary semantics shared with
verify.
Usage: Basic Example
import { verifyMany } from "@suaveplan/otp";
const r = verifyMany(code, [oldSecret, newSecret]);
if (r.valid) markUsed(r.secretIndex);
Usage: Advanced Example
import { verifyMany } from "@suaveplan/otp";
// During rotation grace period — accept either secret but log when the old
// one is used so the rollout dashboard tracks lag.
const r = verifyMany(code, [oldSecret, newSecret], {
window: 1,
clock: () => Date.now(),
});
if (r.valid && r.secretIndex === 0) metric.inc("totp.rotation.legacy");
API Reference
verifyMany(code, secrets, opts?): TotpVerifyManyResult—{ valid: true; delta; secretIndex }on match, all-nulltriple on miss.
Implementation Notes
Both loops (over secrets and over windowed steps) run to completion even
after the first match so the wall-time of verifyMany is independent of
which secret / which step matched. The match flag uses matchedDelta === null
to record the first hit without affecting subsequent iterations. This is
the same constant-time pattern as verify, extended to cover the secret
dimension as well.
When the secret rotation completes (every authenticated user has
re-enrolled against the new secret), the caller drops the old secret from
the list and verifyMany collapses back to single-secret behaviour. There
is no separate "single-secret" code path inside verifyMany — a
single-element list runs the same way as a multi-element one. This keeps
the constant-time guarantees uniform across rotation states.