first commit
This commit is contained in:
63
docs/security-stack/security/otp/src/recovery/verify.md
Normal file
63
docs/security-stack/security/otp/src/recovery/verify.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# `recovery/verify`
|
||||
|
||||
## Purpose
|
||||
|
||||
Verify a user-supplied recovery code against a list of stored hashes.
|
||||
Constant-time over the full set — every entry is evaluated even after a
|
||||
match is found so loop-position timing does not leak which code matched.
|
||||
On success the returned `codeIndex` lets the caller mark that hash as used
|
||||
(recovery codes are single-use).
|
||||
|
||||
## Features
|
||||
|
||||
- Constant-time across the full hash list.
|
||||
- Argon2id re-derivation using the per-hash stored parameters.
|
||||
- Returns `{ valid, codeIndex }` discriminated union.
|
||||
- Input normalisation — separators stripped, upper-cased — so `aaaa-bbbb`
|
||||
matches the original `AAAABBBB` form regardless of how the user types it.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { verifyRecoveryCode } from "@suaveplan/otp";
|
||||
|
||||
const r = await verifyRecoveryCode("aaaa-bbbb", user.recoveryHashes);
|
||||
if (r.valid) markUsed(user.recoveryHashes[r.codeIndex]);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { verifyRecoveryCode } from "@suaveplan/otp";
|
||||
|
||||
// Drop the matched hash from the list atomically; subsequent attempts will
|
||||
// miss even if the same code is replayed.
|
||||
const r = await verifyRecoveryCode(input, hashes);
|
||||
if (r.valid) {
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.removeHash(user.id, hashes[r.codeIndex]);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `verifyRecoveryCode(input, hashedCodes): Promise<RecoveryVerifyResult>` —
|
||||
returns `{ valid: true; codeIndex }` on match, `{ valid: false; codeIndex: null }`
|
||||
otherwise.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Argon2id is intentionally slow — verifying a list of N hashes runs N derivations.
|
||||
The constant-time loop completes regardless of where the match is so timing
|
||||
does not reveal list position. Production callers typically cap the list at
|
||||
10 codes; below that the wall-clock cost is bounded and acceptable for an
|
||||
interactive recovery flow.
|
||||
|
||||
Each list entry carries its own Argon2id parameters in the encoded form so
|
||||
parameter rotation works without re-hashing the entire user base: as the
|
||||
threat model evolves, new codes can be hashed with stronger parameters
|
||||
while older codes verify against their original parameters. The hash
|
||||
format is self-describing precisely so this rotation is not a breaking
|
||||
change. Callers should still re-hash on next-use to migrate stragglers
|
||||
forward.
|
||||
Reference in New Issue
Block a user