Files
skills/docs/security-stack/security/otp/src/hotp/hotp.schema.md
2026-05-29 08:20:56 +01:00

2.3 KiB

hotp/hotp.schema

Purpose

Zod boundary schema for HotpOptions. Strict-mode parsing rejects unknown keys (so silent typos in caller configuration surface immediately) and narrows algorithm and digits to their literal-union values. Mismatches flow through mapZodToOtp so the boundary error carries a specific code (INVALID_DIGITS for the digits field, INVALID_OPTIONS otherwise).

Features

  • algorithm enum: "SHA1" | "SHA256" | "SHA512".
  • digits literal union: 6 | 7 | 8.
  • Strict-mode object — unknown keys are rejected.

Usage: Basic Example

import { HotpOptionsSchema } from "@suaveplan/otp";

HotpOptionsSchema.parse({ algorithm: "SHA1", digits: 6 });

Usage: Advanced Example

import { HotpOptionsSchema } from "@suaveplan/otp";

// Programmatic option builder — feed parsed result directly into hotp().
const opts = HotpOptionsSchema.parse(JSON.parse(userJson));

API Reference

  • HotpOptionsSchema — Zod object schema with .strict().
  • HotpOptionsInput — type alias for the parsed shape.

Implementation Notes

The strict-mode check is the second-line defence behind TypeScript's compile checks: callers using as never or constructing options from JSON can still slip values past the type system. Pinning the schema here keeps the runtime contract aligned with the type-level contract.

The schema is intentionally narrow — it covers only the surface that HOTP itself needs. Callers building their own option bags around HOTP (e.g. a vault item type that wraps HOTP with extra metadata) should extend this schema using Zod's .extend() or compose it via a discriminated union, rather than relaxing the strict-mode setting here. Keeping the base schema tight prevents accidental option-shape drift across the OTP family and means every caller benefits from the same validation guarantees.

The algorithm enum covers exactly the three hashes RFC 4226 / 6238 §1.2 allow for OTP. SHA-3 family hashes are not part of any OTP RFC and would be rejected at this boundary even though @noble/hashes provides them.

The digits literal union mirrors RFC 4226 §5.3 (6) plus the RFC 6238 §1.2 widening (7, 8). RFC 6287 OCRA additionally permits digits=4, which is handled in the OCRA suite parser rather than here because the HOTP primitive does not need to expose it directly.