# 🔐 @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.
