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,133 @@
# `commands/license/license`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Wraps `@suaveplan/license` `LicenseManager` into the five sub-commands
the operator-facing `license` bin exposes: `keygen`, `generate`,
`validate`, `parse`, `revoke`, plus an `exportPublicKeyFile` helper that
derives a public-keys JSON from a freshly generated keypair file. Each
sub-command translates a `LicenseError` into
`ServerKeysError(SERVER_KEYS_LICENSE_FAILED)` with the original error
preserved as `cause`, so the top-level `runFunction` wrapper still
prints a stable code on failure. Input boundary errors raise
`SERVER_KEYS_INVALID_INPUT`, and missing key files raise
`SERVER_KEYS_FILE_NOT_FOUND` — all four error paths are exercised in the
spec-conformance tests.
The on-disk JSON envelopes (`LicenseKeypairFile`, `LicensePublicKeyFile`)
are deliberately minimal: hex-encoded secp256k1 secret + public, a
numeric key id, and an ISO-8601 creation timestamp. Operators can edit
them by hand without a tool. The revocation store is a JSON array of
hex license fingerprints — created on first revoke, append-only.
## Features
- `keygen(outputPath, keyId, options?)` — fresh secp256k1 keypair via
`crypto-curve`'s `secp256k1.randomPrivateKey()` + `getPublicKey()`.
- `generate(opts)` — `LicenseManager.generate` wrapper with hex-encoded
signing-key loading.
- `validate(licenseKey, opts)` — public-keys JSON loader, supports
either an object or an array layout.
- `parse(licenseKey, options?)` — synchronous, no signature check.
- `revoke(licenseKey, opts)` — appends to a JSON revocation-store
file; creates the file if absent.
- `exportPublicKeyFile(keypairFile, outputPath)` — derive the public-
keys file from a freshly generated keypair (used by the bin's
`--export-pub <path>` flag and by tests).
## Usage: Basic
```ts
import {
keygen,
generate,
validate,
parse,
revoke,
exportPublicKeyFile,
} from "@suaveplan/server-keys/commands/license/license";
import { PackageTier } from "@suaveplan/license";
const kp = await keygen("/tmp/lic/keys.json", 1);
await exportPublicKeyFile(kp, "/tmp/lic/public-keys.json");
const lic = await generate({
signingKeyPath: "/tmp/lic/keys.json",
productId: 42,
tier: PackageTier.PRO,
expirationDays: 365,
});
const valid = await validate(lic.key, {
publicKeysPath: "/tmp/lic/public-keys.json",
});
expect(valid.success).toBe(true);
const parsed = parse(lic.key);
expect(parsed.payload.productId).toBe(42);
await revoke(lic.key, { revocationStorePath: "/tmp/lic/revocations.json" });
const after = await validate(lic.key, {
publicKeysPath: "/tmp/lic/public-keys.json",
});
// `revoke` modifies the in-memory store only inside the same Manager;
// the file-backed store is observed by the next caller that constructs
// a Manager with that store path (see bin/license.ts for the wiring).
```
## Usage: Advanced
Flag a license as a TRIAL with a feature mask, then validate against a
fixed reference date for deterministic expiration windows:
```ts
import { generate, validate } from "@suaveplan/server-keys/commands/license/license";
import { LicenseFlags, PackageTier } from "@suaveplan/license";
const lic = await generate({
signingKeyPath: "/tmp/lic/keys.json",
productId: 1,
tier: PackageTier.BASIC,
expirationDays: 30,
flags: LicenseFlags.TRIAL,
featureMask: 0b1011, // features 0, 1, 3
useAbsoluteTime: false,
useFullSignature: true,
});
const checked = await validate(lic.key, {
publicKeysPath: "/tmp/lic/public-keys.json",
referenceDate: new Date("2026-01-01T00:00:00Z"),
});
```
## API Reference
- `keygen(outputPath, keyId, options?) => Promise<LicenseKeypairFile>`
- `generate(opts: GenerateLicenseOptions) =>
Promise<LicenseGenerationResult>`
- `validate(licenseKey, opts: ValidateLicenseOptions) =>
Promise<LicenseValidationResult>`
- `parse(licenseKey, options?: LicenseCommandOptions) =>
LicenseParsedResult`
- `revoke(licenseKey, opts: RevokeLicenseOptions) => Promise<string>`
- `exportPublicKeyFile(keypairFile, outputPath) =>
Promise<LicensePublicKeyFile>`
All option bags accept `{ logger?, signal? }` for injection.
## Implementation Notes
`generate` constructs a `LicenseManager` per call (instead of caching
one across sub-commands) because each call's signing key may be
different — the bin invocation is the granularity that owns a key. The
revocation store loader tolerates `ENOENT` (no file yet) but treats any
other I/O error as a `LICENSE_FAILED` failure so operators don't
silently revoke into a misconfigured path. The optional `signal`
parameter is checked at the boundary of each sub-command and at the
start of generate/validate so an aborted run never burns CPU on the
LicenseManager's hash + signature work.