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(REVOKEDis surfaced byLicenseManager.validate). - Full-signature path uses
@suaveplan/crypto-curve'ssecp256k1.verifyon the SHA-256 digest ofpayloadBytes. - Truncated-signature path uses the upstream hash-prefix scheme.
- Zod boundary validation on
options; failures raiseLicenseError(LICENSE.INVALID_INPUT). - OTel emission: counter
license.validate_total{license.valid, license.error?}and histogramlicense.validate_duration_ms. - Pre-aborted
AbortSignalrejects withLicenseError(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).