Initial Commit
This commit is contained in:
@@ -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).
|
||||
Reference in New Issue
Block a user