key-rotation-manager 1.0.7 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +492 -171
- package/dist/index.cjs +420 -207
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -81
- package/dist/index.d.ts +102 -81
- package/dist/index.js +418 -200
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,99 +1,107 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🔐 Key Rotation Manager
|
|
2
2
|
|
|
3
|
-
Flexible, file-based key management for Node.js
|
|
4
|
-
It helps you **generate, store, rotate, and retrieve cryptographic keys** with support for expiration, versioning, merge strategies, custom storage logic, and lifecycle events.
|
|
3
|
+
> **Flexible, file-based cryptographic key management for Node.js**
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
A production-ready library for generating, storing, rotating, and retrieving cryptographic keys with support for expiration, versioning, merge strategies, custom storage logic, and lifecycle hooks.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/key-rotation-manager)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
[](LICENSE)
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
---
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
13
|
+
## 📋 Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Features](#-features)
|
|
16
|
+
- [Installation](#-installation)
|
|
17
|
+
- [Quick Start](#-quick-start)
|
|
18
|
+
- [Configuration](#-configuration)
|
|
19
|
+
- [Core Concepts](#-core-concepts)
|
|
20
|
+
- [Creating Keys](#creating-keys)
|
|
21
|
+
- [Retrieving Keys](#retrieving-keys)
|
|
22
|
+
- [Key Rotation](#key-rotation)
|
|
23
|
+
- [Advanced Usage](#-advanced-usage)
|
|
24
|
+
- [Custom Storage Logic](#custom-storage-logic)
|
|
25
|
+
- [Custom Path & File Naming](#custom-path--file-naming)
|
|
26
|
+
- [Hooks & Lifecycle Events](#hooks--lifecycle-events)
|
|
27
|
+
- [Logging](#logging)
|
|
28
|
+
- [Instance Cloning](#instance-cloning)
|
|
29
|
+
- [API Reference](#-api-reference)
|
|
30
|
+
- [Use Cases](#-use-cases)
|
|
31
|
+
- [Changelog](#-changelog)
|
|
32
|
+
- [Contributing](#-contributing)
|
|
33
|
+
- [License](#-license)
|
|
22
34
|
|
|
23
35
|
---
|
|
24
36
|
|
|
25
|
-
## Features
|
|
37
|
+
## ✨ Features
|
|
26
38
|
|
|
27
|
-
- 🔐 Secure
|
|
28
|
-
- 🔄
|
|
29
|
-
- 🗂 File-based storage with configurable structure
|
|
30
|
-
- 🧩 Merge
|
|
31
|
-
- 🔧 Custom save
|
|
32
|
-
- 📡 Event-
|
|
33
|
-
- 📁 Automatic `.gitignore`
|
|
39
|
+
- 🔐 **Secure Key Generation** - AES-256-CBC encryption with PBKDF2 key derivation
|
|
40
|
+
- 🔄 **Automatic Rotation** - Built-in expiration and rotation support
|
|
41
|
+
- 🗂 **Flexible Storage** - File-based storage with configurable structure
|
|
42
|
+
- 🧩 **Merge Strategies** - Support for single-file or multi-file key storage
|
|
43
|
+
- 🔧 **Extensible** - Custom save/get hooks and storage logic
|
|
44
|
+
- 📡 **Event-Driven** - Lifecycle hooks for key events
|
|
45
|
+
- 📁 **Git Integration** - Automatic `.gitignore` management
|
|
46
|
+
- 🎯 **TypeScript** - Full TypeScript support with comprehensive types
|
|
34
47
|
|
|
35
48
|
---
|
|
36
49
|
|
|
37
|
-
## Installation
|
|
50
|
+
## 📦 Installation
|
|
38
51
|
|
|
39
52
|
```bash
|
|
40
53
|
npm install key-rotation-manager
|
|
41
54
|
```
|
|
42
55
|
|
|
56
|
+
**Requirements:** Node.js >= 18.0.0
|
|
57
|
+
|
|
43
58
|
---
|
|
44
59
|
|
|
45
|
-
## Quick Start
|
|
60
|
+
## 🚀 Quick Start
|
|
46
61
|
|
|
47
62
|
```typescript
|
|
48
|
-
import { create
|
|
63
|
+
import { create } from 'key-rotation-manager';
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const keyManager = km({});
|
|
65
|
+
// Initialize key manager
|
|
66
|
+
const keyManager = create();
|
|
53
67
|
|
|
68
|
+
// Generate a new key
|
|
54
69
|
const { key, path } = await keyManager.newKey({
|
|
55
70
|
type: 'api',
|
|
56
|
-
|
|
71
|
+
duration: 30,
|
|
72
|
+
unit: 'days',
|
|
73
|
+
rotate: true,
|
|
57
74
|
});
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
This will:
|
|
61
|
-
- Create a `keys/` directory base on `{{path}}/{{filename}}.{{fileExt}}`
|
|
62
|
-
- Generate a new key
|
|
63
|
-
- Save it using default storage rules
|
|
64
|
-
|
|
65
|
-
---
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
console.log('Key generated:', key.key);
|
|
77
|
+
console.log('Storage path:', path);
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const keyManager = create();
|
|
75
|
-
// or
|
|
76
|
-
const keyManager = km();
|
|
79
|
+
// Retrieve the key
|
|
80
|
+
const result = await keyManager.getKey({
|
|
81
|
+
path,
|
|
82
|
+
version: key.version,
|
|
83
|
+
});
|
|
77
84
|
```
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- A store initialization event is emitted
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ⚙️ Configuration
|
|
83
89
|
|
|
84
|
-
### Default
|
|
90
|
+
### Default Options
|
|
85
91
|
|
|
86
92
|
```typescript
|
|
87
93
|
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
// Storage configuration
|
|
95
|
+
path: ['keys', '{{type}}'], // Directory structure (supports variables)
|
|
96
|
+
file: ['v', '{{version}}'], // File naming pattern
|
|
97
|
+
fileSplitor: '_', // File name separator
|
|
98
|
+
fileExt: 'json', // File extension
|
|
99
|
+
gitIgnore: true, // Auto-update .gitignore (boolean | string | string[])
|
|
100
|
+
|
|
101
|
+
// Cryptographic settings
|
|
94
102
|
crypto: {
|
|
95
|
-
algorithm: 'aes-256-
|
|
96
|
-
kdf: '
|
|
103
|
+
algorithm: 'aes-256-cbc',
|
|
104
|
+
kdf: 'pbkdf2',
|
|
97
105
|
hashAlgorithm: 'sha256',
|
|
98
106
|
keyLength: 32,
|
|
99
107
|
ivLength: 16,
|
|
@@ -103,6 +111,7 @@ On initialization:
|
|
|
103
111
|
encoding: 'base64',
|
|
104
112
|
},
|
|
105
113
|
|
|
114
|
+
// Version generation
|
|
106
115
|
versionGenerator: () => Date.now().toString()
|
|
107
116
|
}
|
|
108
117
|
```
|
|
@@ -113,222 +122,534 @@ With default settings, keys are stored as:
|
|
|
113
122
|
|
|
114
123
|
```
|
|
115
124
|
keys/
|
|
116
|
-
└──
|
|
125
|
+
└── {{type}}/
|
|
126
|
+
└── v_{{version}}.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Example:** `keys/api/v_1704067200000.json`
|
|
130
|
+
|
|
131
|
+
### Git Ignore Configuration
|
|
132
|
+
|
|
133
|
+
The `gitIgnore` option controls how the key storage folder is added to `.gitignore`.
|
|
134
|
+
|
|
135
|
+
**Type:** `boolean | string | string[]`
|
|
136
|
+
|
|
137
|
+
| Value | Behavior |
|
|
138
|
+
|-------|----------|
|
|
139
|
+
| `false` | Disable auto-update `.gitignore` |
|
|
140
|
+
| `true` | Uses `path + file + fileExt` pattern |
|
|
141
|
+
| `string \| string[]` | Uses custom path pattern |
|
|
142
|
+
|
|
143
|
+
**Default:** `true` → adds `path + file + fileExt` to `.gitignore`
|
|
144
|
+
|
|
145
|
+
**Variable Replacement:** Any `{{variable}}` in the path will be replaced with `*` in `.gitignore`.
|
|
146
|
+
|
|
147
|
+
**Wildcard Support:** You can use `'*'` directly in the path array for explicit wildcard matching.
|
|
148
|
+
|
|
149
|
+
**Examples:**
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// Disable gitignore management
|
|
153
|
+
const km1 = create({ gitIgnore: false });
|
|
154
|
+
|
|
155
|
+
// Use full path pattern (path + file + fileExt)
|
|
156
|
+
const km2 = create({ gitIgnore: true });
|
|
157
|
+
// .gitignore: keys/{{type}}/v_{{version}}.json → keys/*/v_*.json
|
|
158
|
+
|
|
159
|
+
// Custom folder with wildcard (default)
|
|
160
|
+
const km3 = create({ gitIgnore: ['keys', '*'] });
|
|
161
|
+
// .gitignore: keys/*
|
|
162
|
+
|
|
163
|
+
// Custom path with variables
|
|
164
|
+
const km4 = create({ gitIgnore: ['secrets', '{{env}}', 'keys'] });
|
|
165
|
+
// .gitignore: secrets/*/keys
|
|
166
|
+
|
|
167
|
+
// Custom path with explicit wildcard
|
|
168
|
+
const km5 = create({ gitIgnore: ['keys', '{{type}}', '*'] });
|
|
169
|
+
// .gitignore: keys/*/*
|
|
170
|
+
|
|
171
|
+
// Simple string
|
|
172
|
+
const km6 = create({ gitIgnore: 'keys' });
|
|
173
|
+
// .gitignore: keys
|
|
117
174
|
```
|
|
118
175
|
|
|
119
176
|
---
|
|
120
177
|
|
|
121
|
-
##
|
|
178
|
+
## 📚 Core Concepts
|
|
179
|
+
|
|
180
|
+
### Creating Keys
|
|
122
181
|
|
|
123
|
-
|
|
182
|
+
#### 1. Non-Expiring Key
|
|
124
183
|
|
|
125
184
|
```typescript
|
|
126
|
-
const { key } = await
|
|
185
|
+
const { key, path } = await keyManager.newKey({
|
|
127
186
|
type: 'service',
|
|
128
187
|
});
|
|
129
188
|
```
|
|
130
189
|
|
|
131
|
-
|
|
190
|
+
#### 2. Expiring Key with Rotation
|
|
132
191
|
|
|
133
192
|
```typescript
|
|
134
|
-
const { key } = await
|
|
135
|
-
type: '
|
|
193
|
+
const { key, path } = await keyManager.newKey({
|
|
194
|
+
type: 'api',
|
|
136
195
|
duration: 30,
|
|
137
|
-
unit: 'seconds'
|
|
138
|
-
rotate: true,
|
|
196
|
+
unit: 'days', // 'seconds' | 'minutes' | 'hours' | 'days'
|
|
197
|
+
rotate: true, // Enable automatic rotation
|
|
139
198
|
});
|
|
140
199
|
```
|
|
141
200
|
|
|
142
|
-
|
|
201
|
+
#### 3. Merge Strategy (Single File)
|
|
143
202
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
Merge mode stores multiple key versions in a single file.
|
|
203
|
+
Store multiple key versions in a single file:
|
|
147
204
|
|
|
148
205
|
```typescript
|
|
149
|
-
const { key } = await keyManager.newKey({
|
|
206
|
+
const { key, path } = await keyManager.newKey({
|
|
150
207
|
type: 'service',
|
|
151
|
-
|
|
152
|
-
|
|
208
|
+
merge: true, // Store all versions in one file
|
|
209
|
+
duration: 7,
|
|
210
|
+
unit: 'days',
|
|
153
211
|
rotate: true,
|
|
154
|
-
merge: true, // Merge into 1 file {{path}}/{filename}
|
|
155
212
|
});
|
|
156
213
|
```
|
|
157
214
|
|
|
158
|
-
|
|
215
|
+
### Retrieving Keys
|
|
159
216
|
|
|
160
|
-
|
|
217
|
+
#### Basic Retrieval
|
|
161
218
|
|
|
162
219
|
```typescript
|
|
163
|
-
|
|
220
|
+
const result = await keyManager.getKey({
|
|
221
|
+
path: '/path/to/key/file',
|
|
222
|
+
version: 'v1',
|
|
223
|
+
});
|
|
164
224
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
225
|
+
// Result structure
|
|
226
|
+
{
|
|
227
|
+
ready: TKeyGenerated | null, // Valid, usable key
|
|
228
|
+
expired: TKeyGenerated | null // Expired key (if rotation occurred)
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### Key Rotation
|
|
233
|
+
|
|
234
|
+
When a key is expired and rotatable, provide rotation options:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
const result = await keyManager.getKey({
|
|
238
|
+
path: '/path/to/key/file',
|
|
239
|
+
version: 'v1',
|
|
240
|
+
onRotate: {
|
|
241
|
+
duration: 30,
|
|
242
|
+
unit: 'days',
|
|
243
|
+
rotate: true,
|
|
244
|
+
merge: true, // Optional: merge strategy for new key
|
|
245
|
+
},
|
|
170
246
|
});
|
|
247
|
+
|
|
248
|
+
if (result.expired) {
|
|
249
|
+
console.log('Key was rotated');
|
|
250
|
+
console.log('Old version:', result.expired.version);
|
|
251
|
+
console.log('New version:', result.ready?.version);
|
|
252
|
+
}
|
|
171
253
|
```
|
|
172
254
|
|
|
173
|
-
|
|
255
|
+
#### Handling Missing Keys
|
|
174
256
|
|
|
257
|
+
```typescript
|
|
258
|
+
const result = await keyManager.getKey({
|
|
259
|
+
path: '/path/to/key/file',
|
|
260
|
+
version: 'v1',
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (!result.ready) {
|
|
264
|
+
// Key not found or invalid
|
|
265
|
+
console.log('Key unavailable');
|
|
266
|
+
}
|
|
175
267
|
```
|
|
176
|
-
keys/custom/service.txt
|
|
177
268
|
|
|
178
|
-
|
|
179
|
-
|
|
269
|
+
### Key Rotation
|
|
270
|
+
|
|
271
|
+
Key rotation occurs automatically when:
|
|
272
|
+
|
|
273
|
+
1. A key is expired (`to` date has passed)
|
|
274
|
+
2. The key has `rotate: true`
|
|
275
|
+
3. `onRotate` options are provided in `getKey()`
|
|
276
|
+
|
|
277
|
+
**Rotation Flow:**
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
Expired Key → Check rotate flag → Generate new key → Return both keys
|
|
180
281
|
```
|
|
181
282
|
|
|
182
283
|
---
|
|
183
284
|
|
|
184
|
-
##
|
|
285
|
+
## 🔧 Advanced Usage
|
|
185
286
|
|
|
186
|
-
|
|
287
|
+
### Custom Storage Logic
|
|
288
|
+
|
|
289
|
+
#### Custom Get Logic
|
|
290
|
+
|
|
291
|
+
Override how keys are retrieved:
|
|
187
292
|
|
|
188
293
|
```typescript
|
|
189
|
-
keyManager.
|
|
190
|
-
|
|
294
|
+
keyManager.useGetKey(async (path, version) => {
|
|
295
|
+
// Your custom retrieval logic
|
|
296
|
+
const data = await fetchFromDatabase(path, version);
|
|
297
|
+
|
|
298
|
+
if (!data) return null;
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
from: data.createdAt,
|
|
302
|
+
to: data.expiresAt,
|
|
303
|
+
key: data.rawKey,
|
|
304
|
+
hashed: data.hashedKey,
|
|
305
|
+
hashedBytes: 32,
|
|
306
|
+
type: data.type,
|
|
307
|
+
version: data.version,
|
|
308
|
+
rotate: data.rotate,
|
|
309
|
+
};
|
|
191
310
|
});
|
|
192
311
|
```
|
|
193
312
|
|
|
194
|
-
|
|
313
|
+
#### Custom Save Logic
|
|
195
314
|
|
|
196
|
-
|
|
315
|
+
Override how keys are saved:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
keyManager.useSaveKey(async (keyData, options) => {
|
|
319
|
+
// Your custom save logic
|
|
320
|
+
const savedPath = await saveToDatabase(keyData);
|
|
321
|
+
return savedPath; // This becomes key.path
|
|
322
|
+
});
|
|
323
|
+
```
|
|
197
324
|
|
|
198
|
-
|
|
325
|
+
### Custom Path & File Naming
|
|
199
326
|
|
|
200
|
-
|
|
327
|
+
Use variables in path and file names:
|
|
201
328
|
|
|
202
329
|
```typescript
|
|
203
|
-
const
|
|
204
|
-
path: 'keys
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
330
|
+
const keyManager = create({
|
|
331
|
+
path: ['keys', '{{type}}', '{{env}}'],
|
|
332
|
+
file: ['{{version}}', '{{region}}'],
|
|
333
|
+
fileExt: 'json',
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const { key, path } = await keyManager.newKey(
|
|
337
|
+
{
|
|
338
|
+
type: 'api',
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
env: 'production',
|
|
342
|
+
region: 'us-east-1',
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Result: keys/api/production/1704067200000_us-east-1.json
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Available Variables:**
|
|
350
|
+
- `{{type}}` - Key type
|
|
351
|
+
- `{{version}}` - Key version
|
|
352
|
+
- Any custom variables passed to `newKey()`
|
|
353
|
+
|
|
354
|
+
### Hooks & Lifecycle Events
|
|
355
|
+
|
|
356
|
+
> **Available since v1.0.10**
|
|
357
|
+
|
|
358
|
+
Hooks allow you to execute custom logic when specific key lifecycle events occur.
|
|
359
|
+
|
|
360
|
+
#### Available Hooks
|
|
361
|
+
|
|
362
|
+
**Key Manager Hooks:**
|
|
363
|
+
|
|
364
|
+
| Hook | Description | Parameters |
|
|
365
|
+
|------|-------------|------------|
|
|
366
|
+
| `onKeyNotFound` | Fires when key file is not found or version doesn't exist | `(path: string, version: string \| number)` |
|
|
367
|
+
| `onKeyInvalid` | Fires when key validation fails | `(key: TKeyGenerated, message: string, errorOn?: keyof TKeyGenerated)` |
|
|
368
|
+
| `onKeyMissingRotateOption` | Fires when expired key needs rotation but options are missing | `(key: TKeyGenerated, options: TGetKeyOptions)` |
|
|
369
|
+
| `onKeyRenewed` | Fires when a key is successfully rotated | `(getKey: TGetKey, options: TGetKeyOptions)` |
|
|
370
|
+
| `onKeyExpired` | Fires when a key expires but is not renewable | `(path: string, key: TKeyGenerated)` |
|
|
371
|
+
|
|
372
|
+
**Base Hooks:**
|
|
373
|
+
|
|
374
|
+
| Hook | Description | Parameters |
|
|
375
|
+
|------|-------------|------------|
|
|
376
|
+
| `onHookNotFound` | Fires when a hook is called but not registered | `(name: string)` |
|
|
377
|
+
| `onHookOverriding` | Fires when a hook is being overridden | `(name: string)` |
|
|
378
|
+
|
|
379
|
+
#### Setting Hooks
|
|
380
|
+
|
|
381
|
+
**Set Multiple Hooks:**
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
keyManager.useHooks({
|
|
385
|
+
onKeyNotFound: (path, version) => {
|
|
386
|
+
console.log(`Key not found: ${path}@${version}`);
|
|
387
|
+
},
|
|
388
|
+
onKeyInvalid: (key, message, errorOn) => {
|
|
389
|
+
console.error(`Invalid key: ${message}`, { errorOn, key });
|
|
390
|
+
},
|
|
391
|
+
onKeyRenewed: (getKey, options) => {
|
|
392
|
+
console.log('Key renewed:', {
|
|
393
|
+
expired: getKey.expired?.version,
|
|
394
|
+
ready: getKey.ready?.version,
|
|
395
|
+
});
|
|
211
396
|
},
|
|
212
397
|
});
|
|
213
398
|
```
|
|
214
399
|
|
|
215
|
-
|
|
400
|
+
**Set Single Hook:**
|
|
216
401
|
|
|
217
402
|
```typescript
|
|
218
|
-
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
403
|
+
keyManager.setHook('onKeyRenewed', (getKey, options) => {
|
|
404
|
+
console.log('Key was renewed successfully');
|
|
405
|
+
});
|
|
222
406
|
```
|
|
223
407
|
|
|
224
|
-
|
|
225
|
-
|
|
408
|
+
**Async Hooks:**
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
keyManager.setHook('onKeyRenewed', async (getKey, options) => {
|
|
412
|
+
await sendNotification({
|
|
413
|
+
message: 'Key was rotated',
|
|
414
|
+
expiredVersion: getKey.expired?.version,
|
|
415
|
+
newVersion: getKey.ready?.version,
|
|
416
|
+
});
|
|
417
|
+
});
|
|
226
418
|
|
|
227
|
-
|
|
419
|
+
keyManager.setHook('onKeyInvalid', async (key, message, errorOn) => {
|
|
420
|
+
await logToDatabase({
|
|
421
|
+
event: 'key_invalid',
|
|
422
|
+
message,
|
|
423
|
+
errorOn,
|
|
424
|
+
keyVersion: key.version,
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
#### Complete Hook Example
|
|
228
430
|
|
|
229
431
|
```typescript
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
432
|
+
import { create } from 'key-rotation-manager';
|
|
433
|
+
|
|
434
|
+
const keyManager = create({
|
|
435
|
+
versionGenerator: () => 'v1',
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Configure all hooks
|
|
439
|
+
keyManager.useHooks({
|
|
440
|
+
onKeyNotFound: (path, version) => {
|
|
441
|
+
console.log(`[Hook] Key not found: ${path}@${version}`);
|
|
442
|
+
},
|
|
443
|
+
onKeyInvalid: (key, message, errorOn) => {
|
|
444
|
+
console.error(`[Hook] Invalid key: ${message}`, {
|
|
445
|
+
version: key.version,
|
|
446
|
+
errorOn,
|
|
447
|
+
});
|
|
448
|
+
},
|
|
449
|
+
onKeyRenewed: async (getKey, options) => {
|
|
450
|
+
console.log('[Hook] Key renewed:', {
|
|
451
|
+
from: getKey.expired?.version,
|
|
452
|
+
to: getKey.ready?.version,
|
|
453
|
+
});
|
|
454
|
+
// Perform async operations (notifications, logging, etc.)
|
|
455
|
+
},
|
|
456
|
+
onKeyMissingRotateOption: (key, options) => {
|
|
457
|
+
console.warn(`[Hook] Missing rotate option for key: ${key.version}`);
|
|
458
|
+
},
|
|
459
|
+
onKeyExpired: (path, key) => {
|
|
460
|
+
console.log(`[Hook] Key expired: ${path}@${key.version}`);
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Use the key manager
|
|
465
|
+
const result = await keyManager.getKey({
|
|
466
|
+
path: '/keys/api',
|
|
467
|
+
version: 'v1',
|
|
233
468
|
});
|
|
234
469
|
```
|
|
235
470
|
|
|
236
|
-
|
|
471
|
+
### Logging
|
|
472
|
+
|
|
473
|
+
#### Custom Logger
|
|
237
474
|
|
|
475
|
+
```typescript
|
|
476
|
+
// Synchronous logger
|
|
477
|
+
keyManager.setLogger((...args) => {
|
|
478
|
+
console.log('[KeyManager]', ...args);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Async logger
|
|
482
|
+
keyManager.setLogger(async (...args) => {
|
|
483
|
+
await logToService(...args);
|
|
484
|
+
});
|
|
238
485
|
```
|
|
239
|
-
|
|
486
|
+
|
|
487
|
+
#### System Logging
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
await keyManager.sysLog('Key operation completed');
|
|
240
491
|
```
|
|
241
492
|
|
|
242
|
-
|
|
493
|
+
#### Custom Logging
|
|
243
494
|
|
|
244
495
|
```typescript
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
496
|
+
await keyManager.customLog(async (...args) => {
|
|
497
|
+
await customLoggingService.log(...args);
|
|
498
|
+
}, 'Log message');
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Instance Cloning
|
|
502
|
+
|
|
503
|
+
Create a new instance with shared configuration:
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
const original = create({ versionGenerator: () => 'v1' });
|
|
507
|
+
|
|
508
|
+
// Clone with additional options
|
|
509
|
+
const cloned = original.clone({
|
|
510
|
+
path: ['custom', 'keys'],
|
|
248
511
|
});
|
|
512
|
+
|
|
513
|
+
// Cloned instance includes:
|
|
514
|
+
// - Custom save/get hooks
|
|
515
|
+
// - Store path configuration
|
|
516
|
+
// - Logger settings
|
|
517
|
+
// - Hooks settings
|
|
249
518
|
```
|
|
250
519
|
|
|
251
520
|
---
|
|
252
521
|
|
|
253
|
-
##
|
|
522
|
+
## 📖 API Reference
|
|
254
523
|
|
|
255
|
-
|
|
524
|
+
### `create(options?, only?)`
|
|
525
|
+
|
|
526
|
+
Creates a new KeyManager instance.
|
|
527
|
+
|
|
528
|
+
**Parameters:**
|
|
529
|
+
- `options` (optional): Partial `TModuleOptions`
|
|
530
|
+
- `only` (optional): `boolean` - If `false`, returns a singleton instance
|
|
531
|
+
|
|
532
|
+
**Returns:** `KM` instance
|
|
256
533
|
|
|
534
|
+
**Example:**
|
|
257
535
|
```typescript
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
536
|
+
const km = create({ versionGenerator: () => 'v1' });
|
|
537
|
+
// or
|
|
538
|
+
const km = create({}, false); // Singleton instance
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### `newKey(options, variables?)`
|
|
542
|
+
|
|
543
|
+
Generates a new cryptographic key.
|
|
544
|
+
|
|
545
|
+
**Parameters:**
|
|
546
|
+
- `options`: `TGenerateKeyOptions`
|
|
547
|
+
- `type`: `string` - Key type identifier
|
|
548
|
+
- `duration?`: `number` - Expiration duration
|
|
549
|
+
- `unit?`: `'seconds' | 'minutes' | 'hours' | 'days'` - Time unit
|
|
550
|
+
- `rotate?`: `boolean` - Enable rotation
|
|
551
|
+
- `merge?`: `boolean` - Merge strategy
|
|
552
|
+
- `keyLength?`: `number` - Key length in bytes
|
|
553
|
+
- `variables?`: `TKeyVariables` - Custom variables for path/file naming
|
|
554
|
+
|
|
555
|
+
**Returns:** `Promise<{ key: TKeyGenerated, path: string }>`
|
|
556
|
+
|
|
557
|
+
### `getKey(options)`
|
|
558
|
+
|
|
559
|
+
Retrieves a key by path and version.
|
|
560
|
+
|
|
561
|
+
**Parameters:**
|
|
562
|
+
- `options`: `TGetKeyOptions`
|
|
563
|
+
- `path`: `string` - Storage path
|
|
564
|
+
- `version`: `string` - Key version
|
|
565
|
+
- `onRotate?`: Rotation options (if key is expired and rotatable)
|
|
566
|
+
|
|
567
|
+
**Returns:** `Promise<TGetKey>`
|
|
568
|
+
|
|
569
|
+
**Example:**
|
|
570
|
+
```typescript
|
|
571
|
+
const result = await keyManager.getKey({
|
|
572
|
+
path: '/keys/api',
|
|
573
|
+
version: 'v1',
|
|
574
|
+
onRotate: {
|
|
575
|
+
duration: 30,
|
|
576
|
+
unit: 'days',
|
|
266
577
|
rotate: true,
|
|
267
|
-
}
|
|
578
|
+
},
|
|
268
579
|
});
|
|
269
580
|
```
|
|
270
581
|
|
|
271
|
-
|
|
582
|
+
### `useHooks(hooks)`
|
|
272
583
|
|
|
273
|
-
|
|
584
|
+
Sets multiple hooks at once.
|
|
274
585
|
|
|
275
|
-
|
|
586
|
+
**Parameters:**
|
|
587
|
+
- `hooks`: `Partial<TModuleHooks>`
|
|
276
588
|
|
|
277
|
-
|
|
278
|
-
km.setLogger((...args: unknown[]) => console.log(...args))
|
|
279
|
-
// or Async
|
|
280
|
-
km.setLogger(async (...args: unknown[]) => console.log(...args))
|
|
589
|
+
**Returns:** `this` (chainable)
|
|
281
590
|
|
|
282
|
-
|
|
283
|
-
km.customLog(async (...args: unknown[]) => console.log(...args))
|
|
284
|
-
```
|
|
591
|
+
### `setHook(name, handler)`
|
|
285
592
|
|
|
286
|
-
|
|
593
|
+
Sets a single hook.
|
|
287
594
|
|
|
288
|
-
|
|
595
|
+
**Parameters:**
|
|
596
|
+
- `name`: `keyof TModuleHooks`
|
|
597
|
+
- `handler`: `TModuleHooks[K]`
|
|
289
598
|
|
|
290
|
-
|
|
291
|
-
const k = km();
|
|
292
|
-
const k2 = k.clone(options);
|
|
293
|
-
```
|
|
599
|
+
**Returns:** `this` (chainable)
|
|
294
600
|
|
|
295
601
|
---
|
|
296
602
|
|
|
297
|
-
##
|
|
603
|
+
## 🎯 Use Cases
|
|
298
604
|
|
|
299
|
-
|
|
605
|
+
- **API Key Management** - Secure storage and rotation of API keys
|
|
606
|
+
- **Token Rotation** - Automatic credential rotation for services
|
|
607
|
+
- **Secret Management** - File-based secret storage with expiration
|
|
608
|
+
- **Multi-Version Keys** - Support for multiple key versions simultaneously
|
|
609
|
+
- **Custom Persistence** - Integrate with databases or cloud storage
|
|
610
|
+
- **Backend Services** - Production-ready key management for Node.js applications
|
|
300
611
|
|
|
301
|
-
|
|
612
|
+
---
|
|
302
613
|
|
|
303
|
-
|
|
304
|
-
import { create, types } from 'rotate-key';
|
|
305
|
-
const { EEvent } = types;
|
|
306
|
-
const keyManager = create({});
|
|
614
|
+
## 📝 Changelog
|
|
307
615
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
```
|
|
616
|
+
See the full changelog for each version:
|
|
617
|
+
|
|
618
|
+
- **[v1.0.10](./changelogs/1.0.10.md)** - Hooks system, enhanced gitIgnore configuration
|
|
312
619
|
|
|
313
620
|
---
|
|
314
621
|
|
|
315
|
-
##
|
|
622
|
+
## 🐛 Issues & Bug Reports
|
|
316
623
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
-
|
|
624
|
+
Found a bug or have a feature request?
|
|
625
|
+
|
|
626
|
+
**[🐛 Report an Issue](https://github.com/DucAnh2611/key-rotation-manager/issues/new)**
|
|
627
|
+
|
|
628
|
+
Please include:
|
|
629
|
+
- Clear description of the issue
|
|
630
|
+
- Steps to reproduce
|
|
631
|
+
- Expected vs actual behavior
|
|
632
|
+
- Environment details (Node.js version, OS, package version)
|
|
633
|
+
- Code samples if applicable
|
|
323
634
|
|
|
324
635
|
---
|
|
325
636
|
|
|
326
|
-
## Contributing
|
|
637
|
+
## 🤝 Contributing
|
|
327
638
|
|
|
328
639
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
329
640
|
|
|
641
|
+
1. Fork the repository
|
|
642
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
643
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
644
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
645
|
+
5. Open a Pull Request
|
|
646
|
+
|
|
330
647
|
---
|
|
331
648
|
|
|
332
|
-
## License
|
|
649
|
+
## 📄 License
|
|
650
|
+
|
|
651
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
652
|
+
|
|
653
|
+
---
|
|
333
654
|
|
|
334
|
-
|
|
655
|
+
**Made with ❤️ for secure key management**
|