key-rotation-manager 1.0.9 → 1.0.11
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 +487 -207
- package/dist/index.cjs +335 -309
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -34
- package/dist/index.d.ts +51 -34
- package/dist/index.js +335 -308
- package/dist/index.js.map +1 -1
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -1,98 +1,106 @@
|
|
|
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-
|
|
103
|
+
algorithm: 'aes-256-cbc',
|
|
96
104
|
kdf: 'pbkdf2',
|
|
97
105
|
hashAlgorithm: 'sha256',
|
|
98
106
|
keyLength: 32,
|
|
@@ -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
|
```
|
|
@@ -117,259 +126,530 @@ keys/
|
|
|
117
126
|
└── v_{{version}}.json
|
|
118
127
|
```
|
|
119
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
|
|
174
|
+
```
|
|
175
|
+
|
|
120
176
|
---
|
|
121
177
|
|
|
122
|
-
##
|
|
178
|
+
## 📚 Core Concepts
|
|
123
179
|
|
|
124
|
-
###
|
|
180
|
+
### Creating Keys
|
|
181
|
+
|
|
182
|
+
#### 1. Non-Expiring Key
|
|
125
183
|
|
|
126
184
|
```typescript
|
|
127
|
-
const { key } = await
|
|
185
|
+
const { key, path } = await keyManager.newKey({
|
|
128
186
|
type: 'service',
|
|
129
187
|
});
|
|
130
188
|
```
|
|
131
189
|
|
|
132
|
-
|
|
190
|
+
#### 2. Expiring Key with Rotation
|
|
133
191
|
|
|
134
192
|
```typescript
|
|
135
|
-
const { key } = await
|
|
136
|
-
type: '
|
|
193
|
+
const { key, path } = await keyManager.newKey({
|
|
194
|
+
type: 'api',
|
|
137
195
|
duration: 30,
|
|
138
|
-
unit: 'seconds'
|
|
139
|
-
rotate: true,
|
|
196
|
+
unit: 'days', // 'seconds' | 'minutes' | 'hours' | 'days'
|
|
197
|
+
rotate: true, // Enable automatic rotation
|
|
140
198
|
});
|
|
141
199
|
```
|
|
142
200
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
### 3. Merge Strategy
|
|
201
|
+
#### 3. Merge Strategy (Single File)
|
|
146
202
|
|
|
147
|
-
|
|
203
|
+
Store multiple key versions in a single file:
|
|
148
204
|
|
|
149
205
|
```typescript
|
|
150
|
-
const { key } = await keyManager.newKey({
|
|
206
|
+
const { key, path } = await keyManager.newKey({
|
|
151
207
|
type: 'service',
|
|
152
|
-
merge: true,
|
|
153
|
-
|
|
208
|
+
merge: true, // Store all versions in one file
|
|
209
|
+
duration: 7,
|
|
210
|
+
unit: 'days',
|
|
211
|
+
rotate: true,
|
|
154
212
|
});
|
|
155
213
|
```
|
|
156
214
|
|
|
157
|
-
|
|
215
|
+
### Retrieving Keys
|
|
158
216
|
|
|
159
|
-
|
|
217
|
+
#### Basic Retrieval
|
|
160
218
|
|
|
161
219
|
```typescript
|
|
162
|
-
|
|
220
|
+
const result = await keyManager.getKey({
|
|
221
|
+
path: '/path/to/key/file',
|
|
222
|
+
version: 'v1',
|
|
223
|
+
});
|
|
163
224
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
+
},
|
|
169
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
|
+
}
|
|
170
253
|
```
|
|
171
254
|
|
|
172
|
-
|
|
255
|
+
#### Handling Missing Keys
|
|
173
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
|
+
}
|
|
174
267
|
```
|
|
175
|
-
path: ['keys', '{{type}}']
|
|
176
|
-
file: ['{{version}}', '{{custom_variables}}']
|
|
177
|
-
fileExt: "txt"
|
|
178
|
-
type: "service"
|
|
179
|
-
variables: { custom_variables: "example" }
|
|
180
268
|
|
|
181
|
-
|
|
269
|
+
### Key Rotation
|
|
270
|
+
|
|
271
|
+
Key rotation occurs automatically when:
|
|
182
272
|
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
185
281
|
```
|
|
186
282
|
|
|
187
283
|
---
|
|
188
284
|
|
|
189
|
-
##
|
|
285
|
+
## 🔧 Advanced Usage
|
|
190
286
|
|
|
191
|
-
|
|
287
|
+
### Custom Storage Logic
|
|
288
|
+
|
|
289
|
+
#### Custom Get Logic
|
|
290
|
+
|
|
291
|
+
Override how keys are retrieved:
|
|
192
292
|
|
|
193
293
|
```typescript
|
|
194
|
-
keyManager.
|
|
195
|
-
|
|
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
|
+
};
|
|
196
310
|
});
|
|
197
311
|
```
|
|
198
312
|
|
|
199
|
-
|
|
313
|
+
#### Custom Save Logic
|
|
200
314
|
|
|
201
|
-
|
|
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
|
+
```
|
|
202
324
|
|
|
203
|
-
|
|
325
|
+
### Custom Path & File Naming
|
|
204
326
|
|
|
205
|
-
|
|
327
|
+
Use variables in path and file names:
|
|
206
328
|
|
|
207
329
|
```typescript
|
|
208
|
-
const
|
|
209
|
-
path: '
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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',
|
|
216
339
|
},
|
|
217
|
-
|
|
340
|
+
{
|
|
341
|
+
env: 'production',
|
|
342
|
+
region: 'us-east-1',
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Result: keys/api/production/1704067200000_us-east-1.json
|
|
218
347
|
```
|
|
219
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
|
+
|
|
220
383
|
```typescript
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
) => void | Promise<void>;
|
|
236
|
-
/**
|
|
237
|
-
* This will fire when key is renewed
|
|
238
|
-
*/
|
|
239
|
-
onKeyRenewed: (getKey: TGetKey, options: TGetKeyOptions['onRotate']) => void | Promise<void>;
|
|
240
|
-
/**
|
|
241
|
-
* This will fire when key file is not found or version is not found in file
|
|
242
|
-
* @description
|
|
243
|
-
* IMPORTANT: every file invalid should return `{}` as key data and this will caused this event to be fired
|
|
244
|
-
* - Invalid file (file not found or not valid json)
|
|
245
|
-
* - Version not found in file
|
|
246
|
-
* - From date in future
|
|
247
|
-
* - Properties in key data is not valid types
|
|
248
|
-
* - hashedBytes is less than 0
|
|
249
|
-
*/
|
|
250
|
-
onKeyNotFound: (path: string, version: string | number) => void | Promise<void>;
|
|
251
|
-
onExpired: (path: string, key: TKeyGenerated) => void | Promise<void>;
|
|
252
|
-
};
|
|
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
|
+
});
|
|
396
|
+
},
|
|
397
|
+
});
|
|
253
398
|
```
|
|
254
399
|
|
|
255
|
-
|
|
400
|
+
**Set Single Hook:**
|
|
256
401
|
|
|
257
402
|
```typescript
|
|
258
|
-
{
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
403
|
+
keyManager.setHook('onKeyRenewed', (getKey, options) => {
|
|
404
|
+
console.log('Key was renewed successfully');
|
|
405
|
+
});
|
|
262
406
|
```
|
|
263
407
|
|
|
264
|
-
|
|
265
|
-
- `expired` → expired key
|
|
408
|
+
**Async Hooks:**
|
|
266
409
|
|
|
267
|
-
|
|
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
|
+
});
|
|
418
|
+
|
|
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
|
|
268
430
|
|
|
269
431
|
```typescript
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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',
|
|
273
468
|
});
|
|
274
469
|
```
|
|
275
470
|
|
|
276
|
-
|
|
471
|
+
### Logging
|
|
472
|
+
|
|
473
|
+
#### Custom Logger
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
// Synchronous logger
|
|
477
|
+
keyManager.setLogger((...args) => {
|
|
478
|
+
console.log('[KeyManager]', ...args);
|
|
479
|
+
});
|
|
277
480
|
|
|
481
|
+
// Async logger
|
|
482
|
+
keyManager.setLogger(async (...args) => {
|
|
483
|
+
await logToService(...args);
|
|
484
|
+
});
|
|
278
485
|
```
|
|
279
|
-
|
|
486
|
+
|
|
487
|
+
#### System Logging
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
await keyManager.sysLog('Key operation completed');
|
|
280
491
|
```
|
|
281
492
|
|
|
282
|
-
|
|
493
|
+
#### Custom Logging
|
|
283
494
|
|
|
284
495
|
```typescript
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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'],
|
|
288
511
|
});
|
|
512
|
+
|
|
513
|
+
// Cloned instance includes:
|
|
514
|
+
// - Custom save/get hooks
|
|
515
|
+
// - Store path configuration
|
|
516
|
+
// - Logger settings
|
|
517
|
+
// - Hooks settings
|
|
289
518
|
```
|
|
290
519
|
|
|
291
520
|
---
|
|
292
521
|
|
|
293
|
-
##
|
|
522
|
+
## 📖 API Reference
|
|
294
523
|
|
|
295
|
-
|
|
524
|
+
### `create(options?, singleton?)`
|
|
525
|
+
|
|
526
|
+
Creates a new KeyManager instance.
|
|
527
|
+
|
|
528
|
+
**Parameters:**
|
|
529
|
+
- `options` (optional): Partial `TModuleOptions`
|
|
530
|
+
- `singleton` (optional): `boolean` - If `true`, returns a shared singleton instance (default: `false`)
|
|
531
|
+
|
|
532
|
+
**Returns:** `KM` instance
|
|
296
533
|
|
|
534
|
+
**Example:**
|
|
297
535
|
```typescript
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
536
|
+
const km = create({ versionGenerator: () => 'v1' });
|
|
537
|
+
// or
|
|
538
|
+
const km = create({}, true); // 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',
|
|
307
577
|
rotate: true,
|
|
308
|
-
}
|
|
578
|
+
},
|
|
309
579
|
});
|
|
310
580
|
```
|
|
311
581
|
|
|
312
|
-
|
|
582
|
+
### `useHooks(hooks)`
|
|
313
583
|
|
|
314
|
-
|
|
584
|
+
Sets multiple hooks at once.
|
|
315
585
|
|
|
316
|
-
|
|
586
|
+
**Parameters:**
|
|
587
|
+
- `hooks`: `Partial<TModuleHooks>`
|
|
317
588
|
|
|
318
|
-
|
|
319
|
-
km.setLogger((...args: unknown[]) => console.log(...args))
|
|
320
|
-
// or Async
|
|
321
|
-
km.setLogger(async (...args: unknown[]) => console.log(...args))
|
|
589
|
+
**Returns:** `this` (chainable)
|
|
322
590
|
|
|
323
|
-
|
|
324
|
-
km.customLog(async (...args: unknown[]) => console.log(...args))
|
|
325
|
-
```
|
|
591
|
+
### `setHook(name, handler)`
|
|
326
592
|
|
|
327
|
-
|
|
593
|
+
Sets a single hook.
|
|
328
594
|
|
|
329
|
-
|
|
595
|
+
**Parameters:**
|
|
596
|
+
- `name`: `keyof TModuleHooks`
|
|
597
|
+
- `handler`: `TModuleHooks[K]`
|
|
330
598
|
|
|
331
|
-
|
|
332
|
-
const k = km();
|
|
333
|
-
const k2 = k.clone(options);
|
|
334
|
-
```
|
|
599
|
+
**Returns:** `this` (chainable)
|
|
335
600
|
|
|
336
601
|
---
|
|
337
602
|
|
|
338
|
-
##
|
|
603
|
+
## 🎯 Use Cases
|
|
339
604
|
|
|
340
|
-
|
|
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
|
|
341
611
|
|
|
342
|
-
|
|
612
|
+
---
|
|
343
613
|
|
|
344
|
-
|
|
345
|
-
import { create, types } from 'rotate-key';
|
|
346
|
-
const { EEvent } = types;
|
|
347
|
-
const keyManager = create({});
|
|
614
|
+
## 📝 Changelog
|
|
348
615
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
```
|
|
616
|
+
See the full changelog for each version:
|
|
617
|
+
|
|
618
|
+
- **[v1.0.10](./changelogs/1.0.10.md)** - Hooks system, enhanced gitIgnore configuration
|
|
353
619
|
|
|
354
620
|
---
|
|
355
621
|
|
|
356
|
-
##
|
|
622
|
+
## 🐛 Issues & Bug Reports
|
|
357
623
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
-
|
|
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
|
|
364
634
|
|
|
365
635
|
---
|
|
366
636
|
|
|
367
|
-
## Contributing
|
|
637
|
+
## 🤝 Contributing
|
|
368
638
|
|
|
369
639
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
370
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
|
+
|
|
371
647
|
---
|
|
372
648
|
|
|
373
|
-
## License
|
|
649
|
+
## 📄 License
|
|
650
|
+
|
|
651
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
652
|
+
|
|
653
|
+
---
|
|
374
654
|
|
|
375
|
-
|
|
655
|
+
**Made with ❤️ for secure key management**
|