4.9 KiB
@suaveplan/license — manager
(no submodules)
Purpose
High-level LicenseManager class that bundles a public-key registry,
an optional signing key, default signingKeyId / productId,
injectable Clock / Rng, and an optional RevocationStore into a
single object. Most callers will use only the manager — the lower-level
generateLicense / validateLicense functions are exposed for the
rare case where the caller wants stateless calls.
LicenseManager.generate defaults useFullSignature: false (compact
keys) to match upstream's caller-side default; passing
{ useFullSignature: true } opts into 64-byte signatures. revoke and
isRevoked reach into the configured RevocationStore (lazily
allocating an in-memory store on first revoke call if none was
configured).
Features
- 11 methods covering the full lifecycle:
addPublicKey,setSigningKey,setRevocationStore,generate,generateTrial,validate,isValid,parse,revoke,isRevoked(+ constructor). - Quick-trial helper
generateTrial({ expirationDays, productId?, tier?, features? })that sets theTRIALflag and usesRELATIVE_DAYStime encoding. - Injectable
Clockis used both as the defaultreferenceDateforvalidateand as the time base forcreatePayloadabsolute-day computations. - Injectable
Rngflows through tocreatePayloadfor deterministic nonces. - Optional
RevocationStoreconsulted before signature verification. - Throws
LicenseError(LICENSE.NO_SIGNING_KEY)whengenerateis called without a configured signing key.
Usage: Basic
import { secp256k1 } from "@suaveplan/crypto-curve";
import { LicenseManager, PackageTier } from "@suaveplan/license";
const s = secp256k1.randomPrivateKey();
const p = secp256k1.getPublicKey(s);
const m = new LicenseManager({
signingKey: { s, p },
publicKeys: { 0: { p } },
});
const r = await m.generate({ productId: 5, tier: PackageTier.PRO });
const v = await m.validate(r.key); // { success: true, details: { ... } }
Usage: Advanced
A manager with deterministic test injection, a revocation store, and a multi-key registry:
import { secp256k1 } from "@suaveplan/crypto-curve";
import type { Clock, Rng } from "@suaveplan/types";
import {
createMemoryRevocationStore,
fingerprintLicense,
LicenseManager,
} from "@suaveplan/license";
const kp1 = (() => {
const s = secp256k1.randomPrivateKey();
return { s, p: secp256k1.getPublicKey(s) };
})();
const kp2 = (() => {
const s = secp256k1.randomPrivateKey();
return { s, p: secp256k1.getPublicKey(s) };
})();
const clock: Clock = () => Date.UTC(2026, 5, 17, 12, 0, 0);
const rng: Rng = { nextUint32: () => 0xdead, nextFloat: () => 0 };
const store = createMemoryRevocationStore({ maxEntries: 10_000 });
const m = new LicenseManager({
signingKey: { s: kp1.s },
publicKeys: { 0: { p: kp1.p }, 1: { p: kp2.p } },
defaultSigningKeyId: 0,
defaultProductId: 5,
clock,
rng,
revocationStore: store,
});
const trial = await m.generateTrial({ expirationDays: 14 });
await m.revoke(fingerprintLicense(trial.key));
const v = await m.validate(trial.key);
// → { success: false, error: 'REVOKED', message: 'License has been revoked' }
API Reference
class LicenseManager
| Method | Returns | Notes |
|---|---|---|
constructor(config?) |
LicenseManager |
All config fields optional |
addPublicKey(keyId, publicKey) |
void |
Idempotent overwrite |
setSigningKey(signingKey, keyId?) |
void |
Updates default signing key id when supplied |
setRevocationStore(store) |
void |
Replaces an existing store |
generate(options, genOptions?) |
Promise<LicenseGenerationResult> |
Throws NO_SIGNING_KEY if unconfigured |
generateTrial(opts) |
Promise<LicenseGenerationResult> |
Sets TRIAL flag, RELATIVE_DAYS encoding |
validate(licenseKey, options?) |
Promise<LicenseValidationResult> |
Consults revocation store first |
isValid(licenseKey) |
Promise<boolean> |
Wraps validate |
parse(licenseKey) |
LicenseParsedResult |
No verification |
revoke(fingerprint) |
Promise<void> |
Lazily allocates store if none |
isRevoked(fingerprint) |
Promise<boolean> |
Returns false if no store |
interface GenerateTrialOptions
{ expirationDays: number, productId?: number, tier?: PackageTier, features?: number }.
Implementation Notes
The lazy-allocation behaviour of revoke (auto-creates an in-memory
store on first call) means simple consumers can use revocation without
constructing a store explicitly, while still letting power users plug
in a custom backend. The manager's clock injection drives BOTH the
generation-time getDayNumber (for absolute-day expirations) AND the
validation-time referenceDate (when none is supplied) — keeping the
two sides on the same simulated time line for deterministic tests.