key-rotation-manager 1.0.11 → 1.1.2

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 CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
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.
6
6
 
7
+ **Key Rotation Manager** is a comprehensive Node.js library for secure cryptographic key management. It provides automatic key rotation, expiration handling, versioning, and flexible storage options. Perfect for API key management, token rotation, secret management, and credential handling in production applications.
8
+
7
9
  [![npm version](https://img.shields.io/npm/v/key-rotation-manager)](https://www.npmjs.com/package/key-rotation-manager)
8
10
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen)](https://nodejs.org/)
9
11
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
@@ -44,6 +46,8 @@ A production-ready library for generating, storing, rotating, and retrieving cry
44
46
  - 📡 **Event-Driven** - Lifecycle hooks for key events
45
47
  - 📁 **Git Integration** - Automatic `.gitignore` management
46
48
  - 🎯 **TypeScript** - Full TypeScript support with comprehensive types
49
+ - ⚡ **Zero Dependencies** - Uses native Node.js crypto module
50
+ - 🚀 **Production Ready** - Battle-tested for enterprise applications
47
51
 
48
52
  ---
49
53
 
@@ -55,6 +59,8 @@ npm install key-rotation-manager
55
59
 
56
60
  **Requirements:** Node.js >= 18.0.0
57
61
 
62
+ **Package:** [npmjs.com/package/key-rotation-manager](https://www.npmjs.com/package/key-rotation-manager)
63
+
58
64
  ---
59
65
 
60
66
  ## 🚀 Quick Start
@@ -562,6 +568,7 @@ Retrieves a key by path and version.
562
568
  - `options`: `TGetKeyOptions`
563
569
  - `path`: `string` - Storage path
564
570
  - `version`: `string` - Key version
571
+ - `disableHooks?`: `boolean` - Skip hook execution (default: `false`)
565
572
  - `onRotate?`: Rotation options (if key is expired and rotatable)
566
573
 
567
574
  **Returns:** `Promise<TGetKey>`
@@ -577,8 +584,45 @@ const result = await keyManager.getKey({
577
584
  rotate: true,
578
585
  },
579
586
  });
587
+
588
+ // With disabled hooks for performance
589
+ const result = await keyManager.getKey({
590
+ path: '/keys/api',
591
+ version: 'v1',
592
+ disableHooks: true,
593
+ });
594
+ ```
595
+
596
+ ### `verifyKey(hashedKey, path, version, disableGetKeyHooks?)`
597
+
598
+ Verifies a hashed key against a stored key.
599
+
600
+ **Parameters:**
601
+ - `hashedKey`: `string` - The hashed key to verify
602
+ - `path`: `string` - Storage path
603
+ - `version`: `string | number` - Key version
604
+ - `disableGetKeyHooks?`: `boolean` - Disable hooks in getKey call (default: `true`)
605
+
606
+ **Returns:** `Promise<boolean>`
607
+
608
+ **Example:**
609
+ ```typescript
610
+ const isValid = await keyManager.verifyKey(hashedKey, '/keys/api', 'v1');
611
+ if (isValid) {
612
+ console.log('Key is valid!');
613
+ } else {
614
+ console.log('Key is invalid!');
615
+ }
616
+
617
+ // Verification with hooks enabled
618
+ const isValid = await keyManager.verifyKey(hashedKey, '/keys/api', 'v1', false);
580
619
  ```
581
620
 
621
+ **Notes:**
622
+ - Uses PBKDF2 verification if the key has a `secret` field
623
+ - Falls back to direct hash comparison otherwise
624
+ - Verifies against expired keys if no ready key is available
625
+
582
626
  ### `useHooks(hooks)`
583
627
 
584
628
  Sets multiple hooks at once.
@@ -602,12 +646,16 @@ Sets a single hook.
602
646
 
603
647
  ## 🎯 Use Cases
604
648
 
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
649
+ - **API Key Management** - Secure storage and rotation of API keys for third-party services
650
+ - **Token Rotation** - Automatic credential rotation for OAuth tokens, JWT secrets, and access tokens
651
+ - **Secret Management** - File-based secret storage with expiration for application secrets
652
+ - **Multi-Version Keys** - Support for multiple key versions simultaneously during rotation periods
653
+ - **Custom Persistence** - Integrate with databases, cloud storage, or key management services
654
+ - **Backend Services** - Production-ready key management for Node.js applications and microservices
655
+ - **Encryption Keys** - Manage encryption keys for data protection with automatic rotation
656
+ - **Session Keys** - Rotate session encryption keys periodically for enhanced security
657
+ - **Database Credentials** - Rotate database passwords and connection strings securely
658
+ - **Service-to-Service Authentication** - Manage keys for inter-service communication
611
659
 
612
660
  ---
613
661
 
@@ -615,7 +663,9 @@ Sets a single hook.
615
663
 
616
664
  See the full changelog for each version:
617
665
 
618
- - **[v1.0.10](./changelogs/1.0.10.md)** - Hooks system, enhanced gitIgnore configuration
666
+ - **[v1.1.2](https://github.com/DucAnh2611/key-rotation-manager/tree/master/changelogs/1.1.2.md)** - Key verification, hook control, enhanced documentation
667
+ - **[v1.1.1](https://github.com/DucAnh2611/key-rotation-manager/tree/master/changelogs/1.1.1.md)** - Native crypto, zero dependencies, bug fixes
668
+ - **[v1.0.10](https://github.com/DucAnh2611/key-rotation-manager/tree/master/changelogs/1.0.10.md)** - Hooks system, enhanced gitIgnore configuration
619
669
 
620
670
  ---
621
671
 
@@ -648,7 +698,27 @@ Contributions are welcome! Please feel free to submit a Pull Request.
648
698
 
649
699
  ## 📄 License
650
700
 
651
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
701
+ This project is licensed under the MIT License - see the [LICENSE](https://github.com/DucAnh2611/key-rotation-manager/blob/master/LICENSE) file for details.
702
+
703
+ ---
704
+
705
+ ## 🔍 Search Keywords
706
+
707
+ This package is optimized for searches related to:
708
+ - key rotation
709
+ - key management
710
+ - cryptographic key storage
711
+ - API key rotation
712
+ - secret rotation
713
+ - credential management
714
+ - token rotation
715
+ - key versioning
716
+ - automatic key rotation
717
+ - secure key storage
718
+ - Node.js key management
719
+ - TypeScript key management
720
+ - production key rotation
721
+ - enterprise key management
652
722
 
653
723
  ---
654
724
 
package/dist/index.cjs CHANGED
@@ -2,12 +2,11 @@
2
2
 
3
3
  var promises = require('fs/promises');
4
4
  var path = require('path');
5
- var CryptoJS = require('crypto-js');
5
+ var crypto = require('crypto');
6
6
  var EventEmitter = require('events');
7
7
 
8
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
9
 
10
- var CryptoJS__default = /*#__PURE__*/_interopDefault(CryptoJS);
11
10
  var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
12
11
 
13
12
  // src/constants/default.constant.ts
@@ -185,10 +184,13 @@ var isType = (data) => {
185
184
  return {
186
185
  number: typeof data === "number" && !Number.isNaN(Number(data)),
187
186
  string: typeof data === "string",
188
- stringNumber: typeof data === "string" && !Number.isNaN(Number(data)) || typeof data === "number",
187
+ stringNumber: typeof data === "string" || typeof data === "number",
189
188
  boolean: typeof data === "boolean",
190
189
  null: data === null,
191
- undefined: data === void 0
190
+ undefined: data === void 0,
191
+ optionalNumber: typeof data === "undefined" || typeof data === "number" && !Number.isNaN(Number(data)),
192
+ optionalString: typeof data === "undefined" || typeof data === "string",
193
+ optionalStringNumber: typeof data === "undefined" || typeof data === "string" || typeof data === "number"
192
194
  };
193
195
  };
194
196
  var CryptoService = class {
@@ -196,97 +198,77 @@ var CryptoService = class {
196
198
  constructor(options = {}) {
197
199
  this.options = { ...DEFAULT_CRYPTO_OPTIONS, ...options };
198
200
  }
199
- generateRandomWordArray(length) {
200
- return CryptoJS__default.default.lib.WordArray.random(length);
201
- }
201
+ /**
202
+ * Generate random bytes and encode them
203
+ */
202
204
  generateRandom(length = 32) {
203
- const wordArray = this.generateRandomWordArray(length);
204
- return this.encode(wordArray);
205
+ const buffer = crypto.randomBytes(length);
206
+ return this.encodeBuffer(buffer);
205
207
  }
206
- /*
208
+ /**
207
209
  * Generate a random key
208
- * @param length Length of the key
210
+ * @param length Length of the key in bytes
209
211
  * @default cryptoOptions.keyLength
210
- * @returns { key: string, length: number }
211
212
  */
212
213
  generateKey(length = this.options.keyLength) {
213
214
  return { key: this.generateRandom(length), length };
214
215
  }
215
- /*
216
+ /**
216
217
  * Generate a random salt
217
- * @param length Length of the salt
218
+ * @param length Length of the salt in bytes
218
219
  * @default cryptoOptions.saltLength
219
- * @returns { salt: string, length: number }
220
220
  */
221
221
  generateSalt(length = this.options.saltLength) {
222
222
  return { salt: this.generateRandom(length), length };
223
223
  }
224
- /*
224
+ /**
225
225
  * Generate a random IV
226
- * @param length Length of the IV
226
+ * @param length Length of the IV in bytes
227
227
  * @default cryptoOptions.ivLength
228
- * @returns { iv: string, length: number }
229
228
  */
230
229
  generateIV(length = this.options.ivLength) {
231
230
  return { iv: this.generateRandom(length), length };
232
231
  }
233
- encode(wordArray) {
232
+ encodeBuffer(buffer) {
234
233
  switch (this.options.encoding) {
235
234
  case "hex":
236
- return wordArray.toString(CryptoJS__default.default.enc.Hex);
235
+ return buffer.toString("hex");
237
236
  case "base64url":
238
- return wordArray.toString(CryptoJS__default.default.enc.Base64).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
237
+ return buffer.toString("base64url");
239
238
  case "base64":
240
239
  default:
241
- return wordArray.toString(CryptoJS__default.default.enc.Base64);
240
+ return buffer.toString("base64");
242
241
  }
243
242
  }
244
- decode(encoded) {
243
+ decodeToBuffer(encoded) {
245
244
  switch (this.options.encoding) {
246
245
  case "hex":
247
- return CryptoJS__default.default.enc.Hex.parse(encoded);
246
+ return Buffer.from(encoded, "hex");
248
247
  case "base64url":
249
- const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
250
- const padding = "=".repeat((4 - base64.length % 4) % 4);
251
- return CryptoJS__default.default.enc.Base64.parse(base64 + padding);
248
+ return Buffer.from(encoded, "base64url");
252
249
  case "base64":
253
250
  default:
254
- return CryptoJS__default.default.enc.Base64.parse(encoded);
251
+ return Buffer.from(encoded, "base64");
255
252
  }
256
253
  }
257
254
  deriveKey(password, salt, keyLength) {
258
255
  if (this.options.kdf === "none") {
259
- return CryptoJS__default.default.enc.Hex.parse(password);
256
+ return Buffer.from(password, "hex");
260
257
  }
261
258
  const actualKeyLength = keyLength ?? this.options.keyLength;
262
- const keySize = actualKeyLength / 4;
263
- return CryptoJS__default.default.PBKDF2(password, salt, {
264
- keySize,
265
- iterations: this.options.iterations,
266
- hasher: this.getHasher()
267
- });
268
- }
269
- getHasher() {
270
- switch (this.options.hashAlgorithm) {
271
- case "sha512":
272
- return CryptoJS__default.default.algo.SHA512;
273
- case "sha384":
274
- return CryptoJS__default.default.algo.SHA384;
275
- case "sha256":
276
- default:
277
- return CryptoJS__default.default.algo.SHA256;
278
- }
279
- }
280
- getAESMode() {
281
- return CryptoJS__default.default.mode.CBC;
282
- }
283
- getPadding() {
284
- return CryptoJS__default.default.pad.Pkcs7;
259
+ return crypto.pbkdf2Sync(
260
+ password,
261
+ salt,
262
+ this.options.iterations,
263
+ actualKeyLength,
264
+ this.options.hashAlgorithm
265
+ );
285
266
  }
286
267
  /**
287
- * Encrypt data
268
+ * Encrypt data using AES-CBC
288
269
  * @param plainText Text to encrypt
289
- * @param secret Secret key for encryption Key length, salt, and IV will be randomly generated within configured range
270
+ * @param secret Secret key for encryption
271
+ * @param options Optional lengths for key, salt, and IV
290
272
  */
291
273
  encrypt(plainText, secret, {
292
274
  keyLength,
@@ -296,61 +278,48 @@ var CryptoService = class {
296
278
  keyLength = keyLength ?? this.options.keyLength;
297
279
  saltLength = saltLength ?? this.options.saltLength;
298
280
  ivLength = ivLength ?? this.options.ivLength;
299
- const salt = this.options.kdf !== "none" ? this.generateRandomWordArray(saltLength) : CryptoJS__default.default.lib.WordArray.create();
300
- const iv = this.generateRandomWordArray(ivLength);
281
+ const salt = this.options.kdf !== "none" ? crypto.randomBytes(saltLength) : Buffer.alloc(0);
282
+ const iv = crypto.randomBytes(ivLength);
301
283
  const key = this.deriveKey(secret, salt, keyLength);
302
- const encrypted = CryptoJS__default.default.AES.encrypt(plainText, key, {
303
- iv,
304
- mode: this.getAESMode(),
305
- padding: this.getPadding()
306
- });
307
- const actualSaltLength = salt.sigBytes;
308
- const keyLengthHex = keyLength.toString(16).padStart(4, "0");
309
- const saltLengthHex = actualSaltLength.toString(16).padStart(4, "0");
310
- const ivLengthHex = ivLength.toString(16).padStart(4, "0");
311
- const saltHex = salt.toString(CryptoJS__default.default.enc.Hex);
312
- const ivHex = iv.toString(CryptoJS__default.default.enc.Hex);
313
- const encryptedHex = encrypted.ciphertext.toString(CryptoJS__default.default.enc.Hex);
314
- const combined = keyLengthHex + saltLengthHex + ivLengthHex + saltHex + ivHex + encryptedHex;
315
- return this.encode(CryptoJS__default.default.enc.Hex.parse(combined));
284
+ const cipher = crypto.createCipheriv(this.options.algorithm, key, iv);
285
+ const encrypted = Buffer.concat([cipher.update(plainText, "utf8"), cipher.final()]);
286
+ const keyLengthBuf = Buffer.alloc(2);
287
+ keyLengthBuf.writeUInt16BE(keyLength);
288
+ const saltLengthBuf = Buffer.alloc(2);
289
+ saltLengthBuf.writeUInt16BE(salt.length);
290
+ const ivLengthBuf = Buffer.alloc(2);
291
+ ivLengthBuf.writeUInt16BE(ivLength);
292
+ const combined = Buffer.concat([keyLengthBuf, saltLengthBuf, ivLengthBuf, salt, iv, encrypted]);
293
+ return this.encodeBuffer(combined);
316
294
  }
317
295
  /**
318
296
  * Decrypt data
319
297
  * @param encryptedData Encrypted data to decrypt
320
298
  * @param secret Secret key for decryption
299
+ * @returns Decrypted string, or empty string if decryption fails (wrong password/corrupted data)
321
300
  */
322
301
  decrypt(encryptedData, secret) {
323
- const combined = this.decode(encryptedData);
324
- const combinedHex = combined.toString(CryptoJS__default.default.enc.Hex);
325
- let offset = 0;
326
- const keyLengthHex = combinedHex.substring(offset, offset + 4);
327
- offset += 4;
328
- const saltLengthHex = combinedHex.substring(offset, offset + 4);
329
- offset += 4;
330
- const ivLengthHex = combinedHex.substring(offset, offset + 4);
331
- offset += 4;
332
- const keyLength = parseInt(keyLengthHex, 16);
333
- const saltLength = parseInt(saltLengthHex, 16);
334
- const ivLength = parseInt(ivLengthHex, 16);
335
- const saltHexLength = saltLength * 2;
336
- const ivHexLength = ivLength * 2;
337
- const saltHex = saltHexLength > 0 ? combinedHex.substring(offset, offset + saltHexLength) : "";
338
- offset += saltHexLength;
339
- const ivHex = combinedHex.substring(offset, offset + ivHexLength);
340
- offset += ivHexLength;
341
- const encryptedHex = combinedHex.substring(offset);
342
- const salt = saltHex ? CryptoJS__default.default.enc.Hex.parse(saltHex) : CryptoJS__default.default.lib.WordArray.create();
343
- const iv = CryptoJS__default.default.enc.Hex.parse(ivHex);
344
- const encrypted = CryptoJS__default.default.lib.CipherParams.create({
345
- ciphertext: CryptoJS__default.default.enc.Hex.parse(encryptedHex)
346
- });
347
- const key = this.deriveKey(secret, salt, keyLength);
348
- const decrypted = CryptoJS__default.default.AES.decrypt(encrypted, key, {
349
- iv,
350
- mode: this.getAESMode(),
351
- padding: this.getPadding()
352
- });
353
- return decrypted.toString(CryptoJS__default.default.enc.Utf8);
302
+ try {
303
+ const combined = this.decodeToBuffer(encryptedData);
304
+ let offset = 0;
305
+ const keyLength = combined.readUInt16BE(offset);
306
+ offset += 2;
307
+ const saltLength = combined.readUInt16BE(offset);
308
+ offset += 2;
309
+ const ivLength = combined.readUInt16BE(offset);
310
+ offset += 2;
311
+ const salt = combined.subarray(offset, offset + saltLength);
312
+ offset += saltLength;
313
+ const iv = combined.subarray(offset, offset + ivLength);
314
+ offset += ivLength;
315
+ const encrypted = combined.subarray(offset);
316
+ const key = this.deriveKey(secret, salt, keyLength);
317
+ const decipher = crypto.createDecipheriv(this.options.algorithm, key, iv);
318
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
319
+ return decrypted.toString("utf8");
320
+ } catch {
321
+ return "";
322
+ }
354
323
  }
355
324
  /**
356
325
  * Hash data (one-way)
@@ -359,19 +328,17 @@ var CryptoService = class {
359
328
  * @param salt Optional encoded salt string for deterministic hashing
360
329
  */
361
330
  hash(data, secret, salt) {
362
- const secretWordArray = CryptoJS__default.default.enc.Utf8.parse(secret);
363
- const hasher = this.getHasher();
364
- let saltWordArray;
331
+ let saltBuffer;
365
332
  let saltStr;
366
333
  if (salt) {
367
- saltWordArray = this.decode(salt);
334
+ saltBuffer = this.decodeToBuffer(salt);
368
335
  saltStr = salt;
369
336
  } else {
370
- saltWordArray = this.generateRandomWordArray(this.options.saltLength);
371
- saltStr = this.encode(saltWordArray);
337
+ saltBuffer = crypto.randomBytes(this.options.saltLength);
338
+ saltStr = this.encodeBuffer(saltBuffer);
372
339
  }
373
- const hash = hasher.create().update(data).update(secretWordArray).update(saltWordArray).finalize();
374
- const hashStr = this.encode(hash);
340
+ const hash = crypto.createHash(this.options.hashAlgorithm).update(data).update(secret).update(saltBuffer).digest();
341
+ const hashStr = this.encodeBuffer(hash);
375
342
  return `${saltStr}:${hashStr}`;
376
343
  }
377
344
  /**
@@ -385,11 +352,9 @@ var CryptoService = class {
385
352
  if (!saltStr || !expectedHashStr) {
386
353
  return false;
387
354
  }
388
- const salt = this.decode(saltStr);
389
- const secretWordArray = CryptoJS__default.default.enc.Utf8.parse(secret);
390
- const hasher = this.getHasher();
391
- const hash = hasher.create().update(data).update(secretWordArray).update(salt).finalize();
392
- const hashStr = this.encode(hash);
355
+ const saltBuffer = this.decodeToBuffer(saltStr);
356
+ const hash = crypto.createHash(this.options.hashAlgorithm).update(data).update(secret).update(saltBuffer).digest();
357
+ const hashStr = this.encodeBuffer(hash);
393
358
  return hashStr === expectedHashStr;
394
359
  }
395
360
  };
@@ -676,33 +641,33 @@ var KeyManager = class extends Store {
676
641
  const { path, version } = options;
677
642
  const key = await this.getKeyByStore(path, String(version));
678
643
  if (!key) {
679
- this.runKeyHook("onKeyNotFound", path, version);
644
+ if (!options.disableHooks) this.runKeyHook("onKeyNotFound", path, version);
680
645
  this.sysLog(`Key not found!`, { path, version });
681
646
  return { expired: null, ready: null };
682
647
  }
683
648
  const { ok, message, isExpired, isRenewable, errorOn } = this.validateKey(key);
684
649
  if (!ok && isExpired && isRenewable && key) {
685
650
  if (!options.onRotate) {
686
- this.runKeyHook("onKeyMissingRotateOption", key, options);
651
+ if (!options.disableHooks) this.runKeyHook("onKeyMissingRotateOption", key, options);
687
652
  this.sysLog(`Key missing rotate option!`, { path, version });
688
- return { expired: null, ready: null };
653
+ return { expired: key, ready: null };
689
654
  }
690
655
  const renew = await this.newKey({
691
656
  type: key.type,
692
657
  ...options.onRotate
693
658
  });
694
659
  const resGetKey = { expired: key, ready: renew.key };
695
- this.runKeyHook("onKeyRenewed", resGetKey, options);
660
+ if (!options.disableHooks) this.runKeyHook("onKeyRenewed", resGetKey, options);
696
661
  this.sysLog(`Key renewed!`, { path, version });
697
662
  return resGetKey;
698
663
  }
699
664
  if (!ok && isExpired && !isRenewable && key) {
700
- this.runKeyHook("onKeyExpired", path, key);
665
+ if (!options.disableHooks) this.runKeyHook("onKeyExpired", path, key);
701
666
  this.sysLog(`Key expired!`, { path, version });
702
667
  return { expired: key, ready: null };
703
668
  }
704
669
  if (!ok) {
705
- this.runKeyHook("onKeyInvalid", key, message, errorOn);
670
+ if (!options.disableHooks) this.runKeyHook("onKeyInvalid", key, message, errorOn);
706
671
  this.sysLog(`Key invalid!`, { path, version });
707
672
  return { expired: null, ready: null };
708
673
  }
@@ -748,15 +713,16 @@ var KeyManager = class extends Store {
748
713
  async newKey(options, variables = {}) {
749
714
  const { rotate, duration, type, unit, merge, keyLength } = options;
750
715
  const { key, length: kLength } = this.cryptoService.generateKey(keyLength);
751
- const { salt } = this.cryptoService.generateSalt();
716
+ const { salt: secret } = this.cryptoService.generateSalt();
752
717
  this.sysLog(`Key generated
753
718
  Options:`, options);
754
- const hashedKey = this.cryptoService.hash(key, salt);
719
+ const hashedKey = this.cryptoService.hash(key, secret);
755
720
  const now = /* @__PURE__ */ new Date();
756
721
  const keyGenerated = {
757
722
  from: now.toISOString(),
758
723
  to: duration && unit ? addDuration(now, duration, unit).toISOString() : "NON_EXPIRED",
759
724
  key,
725
+ secret,
760
726
  hashed: hashedKey,
761
727
  hashedBytes: kLength,
762
728
  type,
@@ -771,6 +737,49 @@ Options:`, options);
771
737
  });
772
738
  return { key: keyGenerated, path };
773
739
  }
740
+ /**
741
+ * Verify a key by hashed key and path and version
742
+ * @param hashedKey Hashed key to verify
743
+ * @param path Path to the key
744
+ * @param version Version of the key
745
+ * @param disableGetKeyHooks Disable getKey method hooks, should not use hooks in this method
746
+ * @returns True if the key is valid, false otherwise
747
+ *
748
+ * @example
749
+ * ```ts
750
+ * const isValid = await keyManager.verifyKey(hashedKey, path, version);
751
+ * if (isValid) {
752
+ * console.log('Key is valid!');
753
+ * } else {
754
+ * console.log('Key is invalid!');
755
+ * }
756
+ * ```
757
+ *
758
+ * @note
759
+ * 1. This use getKey method to get key, so disableGetKeyHooks option is set to true, if disableGetKeyHooks is not provided, it will be set to true
760
+ *
761
+ * 2. Get key from store
762
+ * - If the key is expired, the expired key will be used to verify the key
763
+ * - If the key is not expired, the ready key will be used to verify the key
764
+ *
765
+ * 3. If the key is not found, the function will return false
766
+ *
767
+ * 4. Verify key strategy
768
+ * - If the key is found and secret is provided, compare original key with hashed key using secret
769
+ * - If the key is found and secret is not provided, compare original key with hashed key
770
+ *
771
+ */
772
+ async verifyKey(hashedKey, path, version, disableGetKeyHooks = true) {
773
+ const { ready, expired } = await this.getKey({ path, version: String(version), disableHooks: disableGetKeyHooks });
774
+ if (!ready && !expired) {
775
+ this.sysLog(`Key not found to verify!`, { path, version });
776
+ return false;
777
+ }
778
+ const key = expired ?? ready;
779
+ this.sysLog("Verifying key...", { hashedKey, path, version, validateMethod: key.secret ? "verifyHash(key, hashedKey, secret)" : "hashedKey === key.hashed" });
780
+ if (key.secret) return this.cryptoService.verifyHash(key.key, hashedKey, key.secret);
781
+ return hashedKey === key.hashed;
782
+ }
774
783
  async getKeyByStore(path, version) {
775
784
  if (this.getKeyFn) {
776
785
  return this.getKeyFn(path, version);
@@ -795,7 +804,8 @@ Options:`, options);
795
804
  rotate: "boolean",
796
805
  type: "string",
797
806
  version: "stringNumber",
798
- hashedBytes: "number"
807
+ hashedBytes: "number",
808
+ secret: "optionalString"
799
809
  };
800
810
  for (const [field, type] of Object.entries(typeChecks)) {
801
811
  if (!isType(requiredKeyGenerated[field])[type])