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

1.9 KiB

uri/format-uri

Purpose

Build a canonical otpauth:// URI from an OtpUri value object. Round-trips with parseUri for every valid input. Percent-encodes the label so colons and spaces survive transport; rejects literal : in the label (the colon is reserved for the issuer-prefix convention and an unencoded : would produce an ambiguous URI).

Features

  • TOTP and HOTP variants.
  • Label and issuer percent-encoded.
  • Query parameters emitted in a stable order (secret, issuer, algorithm, digits, period | counter).
  • Strict-mode Zod boundary at the entry point.

Usage: Basic Example

import { formatUri } from "@suaveplan/otp/uri";

const url = formatUri({
  type: "totp",
  label: "alice@example.com",
  secret: "JBSWY3DPEHPK3PXP",
  issuer: "Example",
});

Usage: Advanced Example

import { formatUri } from "@suaveplan/otp/uri";

const url = formatUri({
  type: "hotp",
  label: "alice",
  secret: "JBSWY3DPEHPK3PXP",
  counter: 17n,
  algorithm: "SHA256",
  digits: 8,
});

API Reference

  • formatUri(opts: OtpUri): string — returns the URI. Raises OtpError(OTP.URI_MALFORMED) on Zod failure or on labels containing :.

Implementation Notes

The output is a stable canonical form so two calls with identically-typed input produce byte-identical URLs. This matters when the URI is hashed for QR-code de-duplication or audited via diff tooling. Field order is preserved via a Map rather than a plain object so insertion order is stable across JS engines.

Optional fields are omitted when they match the per-spec default — e.g. TOTP URIs without a period parameter elide it rather than emitting period=30. This keeps the output minimal and matches the wire format emitted by reference Google Authenticator implementations. Round-tripping through parseUri recovers the canonical option bag because both ends agree on the defaults; consumers that need an explicit period=30 should set it on the input.