Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
# `drift/detect`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Search outside the regular verify window for clock drift. Callers use this
to render UI hints like _"your device clock is 47 seconds fast"_ — the
verify path with `window=1` would simply reject such a code, leaving the
user with no idea why. `detectDrift` sweeps a wider range and returns both
the signed step delta and the inferred clock offset in milliseconds.
## Features
- Default `±60` steps (= ±30 minutes at the default `period=30`).
- Returns `{ delta, suggestedClockOffsetMs }` discriminated union.
- Constant-time across the sweep range — same defence as `verify`.
- Honours the same TOTP option bag as `verify` (algorithm / digits / period
/ t0 / clock).
## Usage: Basic Example
```ts
import { detectDrift } from "@suaveplan/otp";
const r = detectDrift(code, secret);
if (r.delta !== null && Math.abs(r.delta) > 1) {
show("Your device clock is " + r.suggestedClockOffsetMs + " ms off.");
}
```
## Usage: Advanced Example
```ts
import { detectDrift } from "@suaveplan/otp";
const r = detectDrift(code, secret, {
maxSteps: 120, // sweep ±60 minutes at period=30
clock: () => Date.now(),
});
```
## API Reference
- `detectDrift(code, secret, opts?): DriftResult` — both `delta` and
`suggestedClockOffsetMs` are `null` when no candidate matched.
## Implementation Notes
The negative-step guard is critical near time origin (t < `period * maxSteps`)
because BigInt arithmetic does not raise on underflow; the guard lets the
sweep skip unreachable negative steps without affecting wall-clock timing
of the loop. The inferred `suggestedClockOffsetMs` is `delta * period * 1000`
and is signed so the UI can render direction.
This module is intentionally separate from `verify` because the two have
different threat models: `verify` is on the hot path of every login and
must be tightly bounded, while `detectDrift` runs only when `verify`
already failed, sweeps a wider range, and exists to render a helpful
error message rather than to authenticate. Callers should never accept a
code on the basis of `detectDrift` alone — its purpose is diagnostic, not
gating.