first commit
This commit is contained in:
62
docs/security-stack/security/otp/src/totp/verify-many.md
Normal file
62
docs/security-stack/security/otp/src/totp/verify-many.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# `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 `±window` semantics as `verify`.
|
||||
- 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
|
||||
|
||||
```ts
|
||||
import { verifyMany } from "@suaveplan/otp";
|
||||
|
||||
const r = verifyMany(code, [oldSecret, newSecret]);
|
||||
if (r.valid) markUsed(r.secretIndex);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
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-`null` triple 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.
|
||||
Reference in New Issue
Block a user