Files
2026-05-27 23:29:53 +01:00

10 KiB
Raw Permalink Blame History

Tasks — @suaveplan/otp (RFC 4226 / 6238 / 6287 + Key URI + recovery codes)

Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify.

1. Spec authoring

  • 1.1 Read ../suaveplan/enterprise/otp.md end-to-end
  • 1.2 Read openspec/AGENTS.md Sections 13, 14, 14a
  • 1.3 Read RFC 4226 (HOTP), RFC 6238 (TOTP), RFC 6287 (OCRA), Google Authenticator Key URI Format wiki page
  • 1.4 Draft proposal.md from openspec/templates/proposal.md
  • 1.5 Fill in ## Source mapping table — every requirement cites an RFC clause OR appears in the "no source line" table with justification
  • 1.6 Draft specs/otp/spec.md from openspec/templates/spec-delta.md
  • 1.7 Every requirement in the spec delta carries a **Verified by:** line naming concrete it(...) titles

2. Package scaffold

  • 2.1 packages/vault/otp/ directory structure (src/{hotp,totp,uri,base32,ocra,recovery,drift,errors,observability}/)
  • 2.2 package.json — @suaveplan/otp@0.0.0, type: "module", sideEffects: false, deps via workspace:^ / catalog:
  • 2.3 tsconfig.json extends @suaveplan/typescript-config/library.json
  • 2.4 vite.config.ts for build, vitest.config.ts from @suaveplan/testing/config
  • 2.5 README.md with package description, install snippet, quick-start, ## Modules table linking every co-located .md
  • 2.6 Subpath exports map covers ., ./uri, ./base32, ./ocra, ./errors, ./observability

3. Implementation — HOTP (RFC 4226)

  • 3.1 src/hotp/hotp.ts — hotp(secret, counter, opts?) with dynamic truncation per §5.3 step 2
  • 3.2 src/hotp/hotp.types.ts — HotpOptions, HotpAlgorithm, HotpDigits
  • 3.3 src/hotp/hotp.schema.ts — HotpOptionsSchema Zod boundary
  • 3.4 8-byte big-endian counter encoding helper in src/hotp/encode-counter.ts
  • 3.5 src/hotp/index.ts — explicit named re-exports

4. Implementation — TOTP (RFC 6238)

  • 4.1 src/totp/totp.ts — totp(secret, opts?) over T = floor((now − T0) / period) with injectable Clock
  • 4.2 src/totp/verify.ts — verify(code, secret, opts?) with ±window drift; constant-time loop
  • 4.3 src/totp/verify-many.ts — verifyMany(code, secrets[], opts?) constant-time across the secret list
  • 4.4 src/totp/totp.types.ts — TotpOptions, TotpVerifyOptions, TotpVerifyResult, TotpVerifyManyResult
  • 4.5 src/totp/totp.schema.ts — TotpOptionsSchema, TotpVerifyOptionsSchema (window ∈ [0,10])
  • 4.6 src/totp/index.ts — explicit named re-exports

5. Implementation — Key URI Format

  • 5.1 src/uri/parse-uri.ts — parseUri(uri) for otpauth://totp/... and hotp variant
  • 5.2 src/uri/format-uri.ts — formatUri(opts) round-trip with percent-encoding of label and issuer
  • 5.3 src/uri/uri.types.ts — OtpUri discriminated union (type: "totp" | "hotp")
  • 5.4 src/uri/uri.schema.ts — OtpUriSchema Zod boundary
  • 5.5 src/uri/index.ts — explicit named re-exports

6. Implementation — Base32 (RFC 4648 §6)

  • 6.1 src/base32/base32.ts — base32Encode(bytes, opts?) and base32Decode(input); padding=false default to match Google Authenticator
  • 6.2 src/base32/index.ts — explicit named re-exports

7. Implementation — OCRA (RFC 6287)

  • 7.1 src/ocra/ocra.ts — ocra(suite, key, params) per §6 computation
  • 7.2 src/ocra/suite.ts — parseOcraSuite(suite) per §6.1 grammar
  • 7.3 src/ocra/verify-ocra.ts — verifyOcra(...) constant-time
  • 7.4 src/ocra/mutual-auth.ts — OcraMutualAuthClient, OcraMutualAuthServer per §7.1
  • 7.5 src/ocra/ocra.types.ts — OcraSuite, OcraParams, OcraCryptoFunction, OcraDataInput
  • 7.6 src/ocra/ocra.schema.ts — OcraParamsSchema Zod boundary
  • 7.7 src/ocra/index.ts — explicit named re-exports

8. Implementation — Recovery codes

  • 8.1 src/recovery/generate.ts — generateRecoveryCodes(count, opts?) Crockford-base32 grouping, injectable Rng
  • 8.2 src/recovery/hash.ts — hashRecoveryCode(code, opts?) Argon2id via @suaveplan/crypto-hash
  • 8.3 src/recovery/verify.ts — verifyRecoveryCode(input, hashedCodes[]) constant-time over the full list
  • 8.4 src/recovery/recovery.types.ts — RecoveryCodeOptions, RecoveryVerifyResult
  • 8.5 src/recovery/index.ts — explicit named re-exports

9. Implementation — Clock-drift detector

  • 9.1 src/drift/detect.ts — detectDrift(code, secret, opts?) searches ±maxSteps outside the verify window
  • 9.2 src/drift/drift.types.ts — DriftDetectOptions, DriftResult
  • 9.3 src/drift/index.ts — explicit named re-exports

10. Implementation — Errors and observability

  • 10.1 src/errors/codes.ts — OTP_CODES registry via defineCodes("OTP", [...]); OtpError extends SuaveplanError<OtpErrorCode>
  • 10.2 src/errors/index.ts — explicit named re-exports
  • 10.3 src/observability/metrics.ts — histograms and counters wired against @opentelemetry/api
  • 10.4 src/observability/index.ts — explicit named re-exports

11. Root barrel + subpath exports

  • 11.1 src/index.ts — explicit named re-exports of HOTP, TOTP, verify, verifyMany, recovery, drift, errors, observability surfaces (no export *)
  • 11.2 Verify exports map count matches feature module count (§13 step 8)

12. Documentation

  • 12.1 Sibling .md for every implementation .ts (≥200 words; basic + advanced examples; full API reference; implementation notes)
  • 12.2 README links every co-located .md under ## Modules

13. Tests (runner-neutral imports — @suaveplan/testing/runner only)

  • 13.1 src/hotp/hotp.test.ts — RFC 4226 Appendix D 10 vectors, SHA-256/512 widening, bigint past 2^53, digits 6/7/8
  • 13.2 src/totp/totp.test.ts — RFC 6238 Appendix B 6 vectors across SHA-1/256/512 at digits=8
  • 13.3 src/totp/verify.test.ts — window 0 / 1 / 10 / >10; delta semantics
  • 13.4 src/totp/verify-many.test.ts — multi-secret happy path + miss path
  • 13.5 src/totp/constant-time.test.ts — every windowed step and every secret evaluated regardless of early match
  • 13.6 src/uri/parse-uri.test.ts — every documented parameter; hotp variant; malformed paths
  • 13.7 src/uri/format-uri.test.ts — label / issuer percent-encoding; : in label rejected
  • 13.8 src/uri/round-trip.property.test.ts — fast-check property test
  • 13.9 src/base32/base32.test.ts — RFC 4648 §6 vectors; padded + unpadded; case-insensitivity; invalid alphabet
  • 13.10 src/ocra/ocra.test.ts — RFC 6287 Appendix C.1–C.3 vectors
  • 13.11 src/ocra/suite.test.ts — every §6.1 grammar branch
  • 13.12 src/ocra/mutual-auth.test.ts — three-message §7.1 round-trip
  • 13.13 src/recovery/generate.test.ts — Crockford-base32 grouping; deterministic with injected Rng
  • 13.14 src/recovery/verify.test.ts — Argon2id round-trip; constant-time over the full set
  • 13.15 src/drift/detect.test.ts — drift outside the verify window
  • 13.16 src/errors/codes.spec-conformance.test.ts — every OTP_CODES entry thrown by at least one production path
  • 13.17 src/observability/metrics.spec-conformance.test.ts — every histogram and counter emitted; assertions against an in-memory MeterProvider
  • 13.18 src/index.test.ts — every documented subpath resolves
  • 13.19 Every it(...) carries { timeout: 5000 } (or 30000 for genuinely async)
  • 13.20 Runner-neutral imports — CLAUDE.md §11 grep clean

14. Pre-archive verification (the 5-gate triple)

  • 14.1 bun run check — Biome 0 errors / 0 warnings / 0 info across the whole monorepo
  • 14.2 bunx turbo lint typecheck test --filter=@suaveplan/otp — green
  • 14.3 bunx turbo test:coverage --filter=@suaveplan/otp — 100/100/100/100
  • 14.4 bun scripts/openspec-conformance.ts @suaveplan/otp — exit 0, no error findings
  • 14.5 bunx turbo build --filter=@suaveplan/otp — green

15. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 15.1 Generics threaded; expectTypeOf assertions on every literal-type claim (HotpAlgorithm, HotpDigits, OtpUri.type)
  • 15.2 Zod schemas wired at every declared boundary (HotpOptionsSchema, TotpOptionsSchema, TotpVerifyOptionsSchema, OtpUriSchema, OcraParamsSchema)
  • 15.3 Every error code in OTP_CODES thrown by at least one production path
  • 15.4 Strict-mode behaviors tested both on and off (window cap, base32 padding, label : rejection)
  • 15.5 OTel histograms and counters emitted from production paths and asserted with a real in-memory MeterProvider
  • 15.6 Lifecycle dedup — N/A (pure functions; no shared mutable state)
  • 15.7 No retroactive tick — every box above reflects work done in this change

16. Benchmarks (mitata, src/__benchmarks__/)

  • 16.1 totp_generate.bench.ts
  • 16.2 totp_verify_window_1.bench.ts
  • 16.3 totp_verify_window_10.bench.ts
  • 16.4 parse_uri.bench.ts
  • 16.5 format_uri.bench.ts
  • 16.6 Baseline numbers published in README

17. Root-doc refresh + markdownlint (AGENTS.md §13b)

  • 17.1 bun scripts/folder-reorg/generate-manifest.ts — refresh MANIFEST.md
  • 17.2 bun scripts/generate-missing.ts — refresh MISSING.yml
  • 17.3 STATUS.md — move @suaveplan/otp from Pending to Completed
  • 17.4 SUMMARY.md — add per-package paragraph
  • 17.5 CLAUDE.md — add package listing under "Vaulted" / vault-orchestration section
  • 17.6 bunx markdownlint-cli2 --fix — auto-fix; re-stage
  • 17.7 bunx markdownlint-cli2 — second pass exit 0

18. Archive

  • 18.1 Fold spec delta into openspec/specs/otp/spec.md (canonical, status ACTIVE)
  • 18.2 Move openspec/changes/enterprise-otp/ → openspec/archive/<yyyy-mm-dd>-enterprise-otp/