@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
Markdown
# π @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.