@bilkobibitkov/preflight-license
v1.0.3
Published
Offline license key validation for Preflight CLIs
Maintainers
Readme
@preflight/license
Offline license key validation for the Preflight CLI suite. No license server, no network calls — keys are self-contained HMAC-signed tokens validated locally.
Install
npm install @preflight/licenseHow it works
Keys are structured as:
preflight_<base64url-payload>.<hmac-signature>The payload is a JSON object containing { org, tier, expiry, issued }. The HMAC signature binds the payload to a shared signing secret. Validation happens entirely offline — the CLI checks the signature and expiry, that's it.
Tiers: free | team | enterprise
Usage in a CLI
import { guard, getLicense } from '@preflight/license';
// Gate a paid feature — prints upgrade message and exits if unlicensed
guard('team', { feature: '--format sarif' });
// Or check the license yourself
const license = getLicense();
if (!license.valid) {
console.error('No valid license.');
}getLicense() reads PREFLIGHT_LICENSE_KEY from the environment. Set it to a valid key to unlock paid features.
guard() behaviour
| Scenario | Result | |----------|--------| | No key set | Prints upgrade message, exits with code 1 | | Valid team key, team feature | Passes — no output | | Expired key | Prints expiry date, exits with code 1 | | Tampered key | Prints "invalid signature", exits with code 1 | | Free feature (no guard call) | Always passes |
validate()
For lower-level use:
import { validate } from '@preflight/license';
const result = validate(process.env.PREFLIGHT_LICENSE_KEY);
// {
// valid: boolean
// tier: 'free' | 'team' | 'enterprise'
// org: string
// expiry: string | null // ISO date or null for perpetual
// reason?: string // human-readable when valid=false
// }mintKey()
Generate a key programmatically (useful in tests):
import { mintKey } from '@preflight/license';
const key = mintKey({ org: 'acme', tier: 'team', days: 365, perpetual: false });
// preflight_eyJvcmciOiJhY21l....<signature>Key generation (CLI)
Use the preflight-keygen CLI to mint keys for customers:
# Install globally or run via npx
npx @preflight/license keygen --org acme --tier team --days 365
# Perpetual key (no expiry)
npx @preflight/license keygen --org acme --tier enterprise --perpetual
# Production: always set PREFLIGHT_SIGN_SECRET
PREFLIGHT_SIGN_SECRET=my-prod-secret npx @preflight/license keygen --org acme --tier team --days 365Output:
Org: acme
Tier: team
Expiry: 365 days
PREFLIGHT_LICENSE_KEY=preflight_eyJvcmciOiJhY21l....Give the customer the PREFLIGHT_LICENSE_KEY=... line to set in their CI environment.
Integrating into a new CLI
- Install:
npm install @preflight/license - Import
guardfrom@preflight/license - Call
guard('team', { feature: '--format sarif' })before executing any paid feature
// In your command handler:
import { guard } from '@preflight/license';
async function runCommand(opts: { format?: string }) {
if (opts.format === 'sarif' || opts.format === 'junit') {
guard('team', { feature: `--format ${opts.format}` });
}
// ... rest of command
}- Done. Free features are unaffected — only calls guarded by
guard('team', ...)require a key.
Environment variables
| Variable | Purpose |
|----------|---------|
| PREFLIGHT_LICENSE_KEY | License key provided by the customer |
| PREFLIGHT_SIGN_SECRET | Override signing secret (operator use — key generation) |
Security model
This is intentional soft security — the signing secret ships with the package. Any determined user can reverse-engineer it. This is the same model used by tools like Laravel Spark and Gumroad license keys. The goal is honest enforcement, not cryptographic DRM.
For v2: add an optional online validation endpoint (1 API call per day) to catch key reuse across organizations.
Rotate the signing secret
- Set
PREFLIGHT_SIGN_SECRET=new-secretwhen minting new keys - Old keys signed with the default secret continue to work (they check against the baked-in default)
- At v2.0.0, remove the default fallback — all keys must use the explicit secret
License
MIT
