Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
# `@suaveplan/license` — validate
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```ts
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:
```ts
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).