first commit
This commit is contained in:
131
docs/security-stack/security/license/src/manager/manager.md
Normal file
131
docs/security-stack/security/license/src/manager/manager.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# `@suaveplan/license` — manager
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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 the `TRIAL` flag and uses
|
||||
`RELATIVE_DAYS` time encoding.
|
||||
- Injectable `Clock` is used both as the default `referenceDate` for
|
||||
`validate` and as the time base for `createPayload` absolute-day
|
||||
computations.
|
||||
- Injectable `Rng` flows through to `createPayload` for deterministic
|
||||
nonces.
|
||||
- Optional `RevocationStore` consulted before signature verification.
|
||||
- Throws `LicenseError(LICENSE.NO_SIGNING_KEY)` when `generate` is
|
||||
called without a configured signing key.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user