UNPKG

@trap_stevo/lockline

Version:

The ultimate solution for secure, scalable, and tamper-resistant license key validation. Combine flexible validation strategies, encrypted / pluggable storage, smart fallback resilience, and real-time revocation control to lock down your software β€” and un

365 lines (262 loc) β€’ 13.1 kB
# πŸ” @trap_stevo/lockline **The ultimate solution for secure, scalable, and tamper-resistant license key validation.** Combine flexible validation strategies, encrypted / pluggable storage, smart fallback resilience, and real-time revocation control to lock down your software β€” and unlock next-gen licensing workflows. --- ## πŸš€ Features - πŸ”Ž **Pluggable Validators** – Register and chain custom validation strategies - 🧠 **Smart Fallbacks** – Automatically retry with secondary validators if primary fails - πŸ“¦ **Flexible Storage Engine** – Support for encrypted file-based or custom async storage - πŸ”’ **Tamper-Resistant Sealing** – Enrich keys with metadata and protect against injection - πŸ” **Offline Tolerance** – Optional trust logic when all validators are unreachable - 🧹 **Automatic Cleanup** – Purge or audit expired keys with configurable callbacks - 🧰 **Provisioning & Revocation** – Issue, inspect, or revoke license keys in real-time --- ## βš™οΈ Storage Setup ### Custom Storage You can configure a custom storage backend with `setStorage`. ```js lock.setStorage({ get : async (key) => { /* retrieve */ }, set : async (key, value) => { /* store */ }, delete : async (key) => { /* remove */ }, keys : async () => { /* list keys */ } }, { strict : false }); ``` #### Custom Callback Methods | Key | Type | Description | |-----------|-------------------------------|----------------------------------------------| | `get` | `(key) => Promise<value>` | Retrieve a value by key | | `set` | `(key, value) => Promise<void>` | Store a key-value pair | | `delete` | `(key) => Promise<void>` | Delete a specific key | | `keys` | `() => Promise<string[]>` | Retrieve all stored keys | #### `setStorage` Options | Key | Type | Description | Default | |-----------|-----------|-----------------------------------------------|---------| | `strict` | `boolean` | Throw an error if found a missing a method | `false` | --- ### πŸ” Built-in Lockbox Shortcut Use the built-in encrypted file storage engine with: ```js lock.setStorage({ path : "./lockbox" }); ``` #### Full Lockbox Usage ```js lock.setStorage({ path : "./lockbox", namespace : "my-service", encrypt : true, offset : "optional-entropy", lockprintOptions : { filename : ".device-id", printDNA : 64 } }); ``` Equivalent to: ```js lock.setStorage(Lockline.lockbox("./lockbox", { namespace : "my-service", encrypt : true, offset : "optional-entropy", lockprintOptions : { filename : ".device-id", printDNA : 64 } })); ``` #### Lockbox Options | Key | Type | Description | Default | |-------------------|------------|-----------------------------------------------------------------------------|---------------------------| | `path` | `string` | Root path for the lockbox storage | `process.cwd()/lockbox` | | `namespace` | `string` | Subfolder within the lockbox path | `"default"` | | `encrypt` | `boolean` | Enable lockbox encryption | `true` | | `offset` | `string` | Optional entropy used in secret derivation | `""` | | `lockprint` | `string` | Provide a fixed fingerprint (skips auto generation) | `null` | | `lockprintOptions`| `object` | Options when generating a fingerprint if `lockprint` is not provided | `{}` | ##### `lockprintOptions` Fields | Key | Type | Description | Default | |-------------|----------|--------------------------------------------------------|-----------------| | `filename` | `string` | File used to persist fingerprint | `.fingerprint` | | `printDNA` | `number` | Number of random bytes used for fingerprint generation | `32` | --- ## 🧠 Validator Management | Method | Description | |--------------------------------|----------------------------------------------| | `registerValidator(name, fn)` | Register a custom validator by name | | `setDefaultValidator(name)` | Set the fallback validator if none defined | ### Validator Function Signature ```js async function ({ key, input }) { return { valid : true, reason : "Optional message", raw : { any : "metadata" } }; } ``` --- ## πŸ” License Key Workflow | Method | Description | Async | |-------------------------|---------------------------------------------------------------|--------| | `provisionKey(key, config)` | Temporarily register a key for validation (in-memory) | ❌ | | `validate(key, options)` | Run validator chain against a key | βœ… | | `sealKey(key, config, options)`| Validate and persist enriched metadata to storage | βœ… | | `inspectKey(key)` | View current stored key metadata | βœ… | | `revokeKey(key)` | Delete key from storage | βœ… | --- ## πŸ”Ž Validation Options | Option | Type | Description | |------------------------|-----------|--------------------------------------------------------------------------| | `recordAssumedValidation` | `boolean` | Stores the assume validation metadata to the key | | `assumePreviouslyValid` | `boolean` | Trust stored key if validators are unreachable | | `fallbackIfOffline` | `boolean` | Allow fallback if offline (requires `assumePreviouslyValid`) | | `autoRemoveExpired` | `boolean` | Automatically remove key if expired | | `storeInvalid` | `boolean` | Store even if result is invalid | | `metaFilter` | `Function`| Custom function to filter extracted metadata | --- ## πŸ” Key Configuration | Config Key | Type | Description | |------------------|------------------------|---------------------------------------------------------------| | `validators` | `Array<{ name, input, fallback?, tag? }>` | Chain of validator strategies | | `validate` | `Function(results)` | Custom reducer for validator results (default = AND) | | `shortCircuit` | `boolean` | Exit chain early if one validator fails | | `meta` | `Object` | Custom metadata (`expiresAt`, license tier, etc) | --- ## 🧹 Key Lifecycle | Method | Description | Async | |------------------------|------------------------------------------------------------------|--------| | `scanKeys({ filter })` | Returns keys by filter: `"all"`, `"valid"`, `"expired"` | βœ… | | `auditExpiredKeys()` | Returns list of expired keys with metadata | βœ… | | `purgeExpiredKeys(cb)` | Deletes expired keys, optionally invoking callback per key | βœ… | --- ## 🧭 License Scope Management | Method | Description | Async | |-----------------------------------|-----------------------------------------------------------------------------|--------| | `loadRecentLicenseKeys(groupBy?)`| Loads most recently updated valid key per group and stores by scope | βœ… | | `setCurrentLicenseKey(scope, key)`| Manually set the active license key for a specific scope | ❌ | | `getCurrentLicenseKey(scope?)` | Retrieve the currently active license key for a given scope (default = `"default"`) | ❌ | | `touchLicenseKey(key)` | Update a license key's `lastUsedAt` metadata to mark recent access | βœ… | ### `loadRecentLicenseKeys(groupBy?)` Scans all stored keys and selects the most recently updated valid key per logical group. Groups are defined by the optional `groupBy(meta)` function (default returns `"default"`). #### Example ```js await lock.loadRecentLicenseKeys(meta => meta.module || "default"); const key = lock.getCurrentLicenseKey("analytics"); ``` --- ### `setCurrentLicenseKey(scope, key)` Manually assign a license key to a specific logical scope. ```js lock.setCurrentLicenseKey("videoEditor", "ACCESS-VID-123"); ``` --- ### `getCurrentLicenseKey(scope?)` Returns the current license key for the given scope. Defaults to `"default"`. ```js const key = lock.getCurrentLicenseKey(); // returns key for "default" ``` --- ### `touchLicenseKey(key)` Updates the `lastUsedAt` timestamp in storage for the license key. ```js await lock.touchLicenseKey("ACCESS-XYZ"); ``` Use this in protected methods to track key usage and improve recent-key prioritization. --- ## πŸ›‘οΈ License Protection Wrapper | Method | Description | |-------------------------------|------------------------------------------------------------------| | `withLicenseProtection(fn, scope?, options?)` | Wrap a function to enforce license validation per scope | ### Options Object | Key | Type | Description | |--------------------|------------|-----------------------------------------------------------------------------| | `onMissingKey` | `Function` | Called if no license key found for the given scope | | `onInvalidKey` | `Function` | Called if the license invalid or expired | | `onSuccess` | `Function` | Called if the license valid and access granted | | `onLicenseBlocked` | `Function` | Called if access blocked; return fallback | ### Example ```js lock.setStorage({ path : "./lockbox" }); await lock.loadRecentLicenseKeys(); const protectedExport = lock.withLicenseProtection(() => { return { success : true, data : "Exported data" }; }, "analytics", { onLicenseBlocked : () => ({ error : "License required to access this feature." }), onSuccess : () => console.log("πŸ”“ License valid"), onInvalidKey : () => console.warn("❌ Invalid license"), onMissingKey : () => console.warn("⚠️ No license key loaded") }); await protectedExport(); // Only runs if valid license valid ``` --- ## πŸ“¦ Installation ```bash npm install @trap_stevo/lockline ``` --- ## πŸ› οΈ Constructor ```js const Lockline = require("@trap_stevo/lockline"); const lock = new Lockline(); ``` --- ## πŸ’‘ Example Usage ### Set storage and default validator ```js lock.setStorage({ path : "./lockbox" }); /* If you want explicit control ~ lock.setStorage(lock.lockbox("./lockbox", { namespace : "keys", namespace : "keys", encrypt : true, offset : 7, lockprint : "CUSTOM-LOCKPRINT-1234" })); */ ``` ### Register a validator ```js lock.registerValidator("isAccessKey", async ({ key }) => { return { valid : key.startsWith("ACCESS-"), reason : "Must start with ACCESS-", raw : { region : "EU" } }; }); ``` ### Set default validator ```js lock.setDefaultValidator("isAccessKey"); ``` ### Provision and validate ```js lock.provisionKey("ACCESS-123", { validators : [{ name : "isAccessKey" }], meta : { expiresAt : Date.now() + 1000 * 60 * 60 } }); const result = await lock.validate("ACCESS-123"); console.log(result.valid); // true ``` ### Seal to storage ```js await lock.sealKey("ACCESS-123", { validators : [{ name : "isAccessKey" }], meta : { licenseTier : "pro", expiresAt : Date.now() + 7 * 86400000 } }); ``` ### Purge expired ```js await lock.purgeExpiredKeys((key, meta) => { console.log(`Deleted expired key: ${key}`); }); ``` --- ## πŸ“œ License See License in [LICENSE.md](./LICENSE.md) --- ## πŸ” Lock Down. Unlock Possibilities. **Lockline** gives you the tools to craft smart, trusted, resilient licensing workflows that adapt to your software’s ecosystem β€” from CLI tools to full SaaS systems. Empower validation logic that evolves with you.