64 lines
2.1 KiB
Markdown
64 lines
2.1 KiB
Markdown
# `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.
|