Skip to content

fix(totp): real otplib 12->13 migration (supersedes #27)#31

Merged
Jess Sullivan (Jesssullivan) merged 1 commit into
mainfrom
fix/otplib-13-real
Jul 10, 2026
Merged

fix(totp): real otplib 12->13 migration (supersedes #27)#31
Jess Sullivan (Jesssullivan) merged 1 commit into
mainfrom
fix/otplib-13-real

Conversation

@Jesssullivan

Copy link
Copy Markdown
Contributor

Why

Dependabot #27 (otplib 12.0.1 -> 13.4.1) is red: 7 failures in tests/totp.test.ts. otplib v13 introduced two breaking changes the v12-era shim could not absorb:

  1. Functional-export restructure. v13 removed the stateful authenticator singleton (checkDelta/verify/keyuri). getAuthenticatorCheckDelta() fell through to throw new Error("otplib checkDelta/verify export is unavailable"). The functional surface now lives in @otplib/core / @otplib/totp and is re-exported from otplib as generate*/verify*.
  2. 128-bit secret floor. v13 enforces SecretTooShortError for secrets < 16 bytes. The constant-time unknown-user dummy secret (JBSWY3DPEHPK3PXP, 10 bytes) now throws instead of running the dummy verification.

This PR authors the real migration and supersedes #27.

What changed

  • src/totp/otplib-compat.ts — rewritten against v13 functional exports (generateSecret, generateSync, generateURI, verify, verifySync). v12's mutable authenticator.options is replaced with module-level config; epochTolerance is derived from step * window to preserve the +/-1-step verification window.
  • Replay guard preserved. verifySync returns { valid, delta, timeStep, epoch }, so getAuthenticatorCheckDelta() recovers the exact match delta directly — no lossy fallback. The P0-SEC verifyTokenWithStep single-use / monotonic-step invariants are unchanged (regression tests green).
  • Security floor kept, not weakened. Fresh secrets stay 160-bit (20 bytes). Sub-floor legacy secrets get a clear, actionable migration error on the crypto paths (generate/verify/checkDelta) instead of otplib's low-level error or silent padding.
  • Dummy secret bumped to a 160-bit value so the unknown-user constant-time path clears the floor.
  • package.json otplib -> ^13.4.1; pnpm-lock.yaml refreshed.

Validation

  • pnpm typecheck clean
  • pnpm test398 passed (18 files), including the TOTP replay-protection regression block
  • pnpm build clean
  • pnpm check:package (publint) clean

Scope / follow-up

Lands in tinyland-auth only. Reaching tinyland.dev prod needs the separate tag -> registry-promote -> pin-bump wave (not attempted here). Relates to tinyland.dev #692.

Supersedes the broken dependabot bump (#27). otplib v13 removed the
stateful `authenticator` singleton (checkDelta/verify/keyuri) in favour
of stateless functional exports backed by @otplib/core / @otplib/totp,
and now enforces a 128-bit (16-byte) minimum secret length via
SecretTooShortError. Both broke the v12-era shim.

- Rewrite src/totp/otplib-compat.ts against the v13 functional exports
  (generateSecret/generateSync/generateURI/verify/verifySync). Replace
  v12's mutable authenticator.options with module-level config; derive
  epochTolerance from step*window to preserve the +/-1-step window.
- Recover the exact match delta from verifySync's { valid, delta } result
  so the replay-guard primitive (getAuthenticatorCheckDelta) keeps working
  with no lossy fallback; verifyTokenWithStep replay/single-use invariants
  are unchanged.
- Enforce the 128-bit floor without weakening it: freshly generated
  secrets stay 160-bit, and sub-floor legacy secrets get a clear,
  actionable migration error on the crypto paths instead of otplib's
  low-level error or silent padding.
- Bump the constant-time unknown-user dummy secret to a 160-bit value so
  it clears the floor instead of throwing SecretTooShortError.
- Bump otplib to ^13.4.1 and refresh pnpm-lock.yaml.

Relates to tinyland.dev #692. Reaching prod still needs the separate
tag -> registry-promote -> pin-bump wave.
@Jesssullivan Jess Sullivan (Jesssullivan) merged commit 88c7a0e into main Jul 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant