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

3.9 KiB

@suaveplan/license — validate

(no submodules)

Purpose

License-key validation: parse, verify signature, check version, check expiration, surface every failure as a typed enum on a discriminated union. validateLicense accepts publicKeys as either a Map or a plain Record (matching upstream), honours an optional AbortSignal, and emits the OpenTelemetry counter / histogram pair on every call. isValidLicense is a convenience boolean wrapper.

The package's truncated-signature scheme (default 20 bytes) cannot use standard secp256k1 verification (which requires the full 64-byte signature). The upstream-compatible fallback hashes payloadBytes || publicKey.p with SHA-256 and compares the first N bytes against the embedded signature bytes — giving ~2^160 forgery resistance for the default 20-byte length.

Features

  • Seven failure modes returned on the discriminated union: MALFORMED, INVALID_ENCODING, INVALID_SIGNATURE, UNKNOWN_SIGNING_KEY, EXPIRED, UNSUPPORTED_VERSION, REVOKED (REVOKED is surfaced by LicenseManager.validate).
  • Full-signature path uses @suaveplan/crypto-curve's secp256k1.verify on the SHA-256 digest of payloadBytes.
  • Truncated-signature path uses the upstream hash-prefix scheme.
  • Zod boundary validation on options; failures raise LicenseError(LICENSE.INVALID_INPUT).
  • OTel emission: counter license.validate_total{license.valid, license.error?} and histogram license.validate_duration_ms.
  • Pre-aborted AbortSignal rejects with LicenseError(LICENSE.ABORTED) before any crypto runs.

Usage: Basic

import { validateLicense } from "@suaveplan/license/validate/validate";

const v = await validateLicense(key, {
    publicKeys: { 0: { p: publicKeyBytes } },
});
if (v.success) {
    console.log("Tier:", v.details.packageTier);
} else {
    console.warn("Invalid:", v.error, v.message);
}

Usage: Advanced

Validate with an injected referenceDate, creationDate for a relative-day expiry check, and an AbortSignal for cancellation:

import { validateLicense } from "@suaveplan/license/validate/validate";
import { LicenseError } from "@suaveplan/license";

const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 100);
try {
    const v = await validateLicense(key, {
        publicKeys: new Map([[0, { p }]]),
        referenceDate: new Date("2026-06-17T00:00:00Z"),
        creationDate: new Date("2026-06-01T00:00:00Z"),
        signal: ctrl.signal,
    });
    if (v.success) {
        // ...
    }
} catch (e) {
    if (e instanceof LicenseError && e.code === "LICENSE.ABORTED") {
        console.warn("Validation cancelled");
    }
}

API Reference

validateLicense(licenseKey, options): Promise<LicenseValidationResult>

Options: publicKeys (required), referenceDate?, creationDate?, signal?. Returns the discriminated success/failure union. Throws only on LicenseError(LICENSE.INVALID_INPUT) (bad options) or LicenseError(LICENSE.ABORTED) (aborted signal); every other failure is surfaced on the result.

isValidLicense(licenseKey, options): Promise<boolean>

Thin wrapper that returns result.success.

Implementation Notes

The truncated-signature scheme is what makes the keys compact (~50-60 chars vs ~120). For most consumer products the ~2^160 forgery resistance is generous; for higher-value licenses callers should opt into useFullSignature: true at generation time. Validation auto- detects which scheme the key uses by checking the signature byte length against FULL_SIGNATURE_BYTES.

The OTel emission runs on every code path — including aborts and parse failures — so dashboards always see a complete success/failure ratio. The histogram's unit is documented as ms; the recorded value is Date.now() - start, a wall-clock measurement (acceptable here because validation latency is dominated by the crypto, not by clock skew).