@faizahmed/secret-keystore 1.1.0

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/src/errors.js ADDED
@@ -0,0 +1,372 @@
1
+ /**
2
+ * @faizahmed/secret-keystore - Error Classes
3
+ *
4
+ * Comprehensive error taxonomy with codes for proper handling.
5
+ * All errors extend SecretKeyStoreError as the base class.
6
+ */
7
+
8
+ // ═══════════════════════════════════════════════════════════════════════════
9
+ // BASE ERROR CLASS
10
+ // ═══════════════════════════════════════════════════════════════════════════
11
+
12
+ class SecretKeyStoreError extends Error {
13
+ /**
14
+ * @param {string} message - Error message
15
+ * @param {string} code - Error code
16
+ * @param {Error} [cause] - Original error that caused this
17
+ */
18
+ constructor(message, code, cause) {
19
+ super(message);
20
+ this.name = 'SecretKeyStoreError';
21
+ this.code = code;
22
+ this.cause = cause;
23
+ this.timestamp = new Date();
24
+ Error.captureStackTrace(this, this.constructor);
25
+ }
26
+
27
+ toJSON() {
28
+ return {
29
+ name: this.name,
30
+ code: this.code,
31
+ message: this.message,
32
+ timestamp: this.timestamp.toISOString(),
33
+ cause: this.cause
34
+ ? {
35
+ name: this.cause.name,
36
+ message: this.cause.message
37
+ }
38
+ : undefined
39
+ };
40
+ }
41
+ }
42
+
43
+ // ═══════════════════════════════════════════════════════════════════════════
44
+ // KMS ERRORS
45
+ // ═══════════════════════════════════════════════════════════════════════════
46
+
47
+ class KmsError extends SecretKeyStoreError {
48
+ /**
49
+ * @param {string} message - Error message
50
+ * @param {string} code - Error code
51
+ * @param {string} kmsKeyId - KMS key ID
52
+ * @param {Error} [cause] - Original error
53
+ */
54
+ constructor(message, code, kmsKeyId, cause) {
55
+ super(message, code, cause);
56
+ this.name = 'KmsError';
57
+ this.kmsKeyId = kmsKeyId;
58
+ this.awsRequestId = cause?.['$metadata']?.requestId;
59
+ }
60
+ }
61
+
62
+ // KMS Error Codes
63
+ const KMS_ERROR_CODES = {
64
+ KEY_NOT_FOUND: 'KMS_KEY_NOT_FOUND',
65
+ KEY_DISABLED: 'KMS_KEY_DISABLED',
66
+ ACCESS_DENIED: 'KMS_ACCESS_DENIED',
67
+ INVALID_CIPHERTEXT: 'KMS_INVALID_CIPHERTEXT',
68
+ THROTTLED: 'KMS_THROTTLED',
69
+ ENCRYPT_FAILED: 'KMS_ENCRYPT_FAILED',
70
+ DECRYPT_FAILED: 'KMS_DECRYPT_FAILED',
71
+ CONNECTION_ERROR: 'KMS_CONNECTION_ERROR'
72
+ };
73
+
74
+ // ═══════════════════════════════════════════════════════════════════════════
75
+ // ATTESTATION ERRORS
76
+ // ═══════════════════════════════════════════════════════════════════════════
77
+
78
+ class AttestationError extends SecretKeyStoreError {
79
+ constructor(message, code, cause) {
80
+ super(message, code, cause);
81
+ this.name = 'AttestationError';
82
+ }
83
+ }
84
+
85
+ // Attestation Error Codes
86
+ const ATTESTATION_ERROR_CODES = {
87
+ DOCUMENT_MISSING: 'ATTESTATION_DOCUMENT_MISSING',
88
+ DOCUMENT_INVALID: 'ATTESTATION_DOCUMENT_INVALID',
89
+ DOCUMENT_EXPIRED: 'ATTESTATION_DOCUMENT_EXPIRED',
90
+ GETTER_FAILED: 'ATTESTATION_GETTER_FAILED',
91
+ NOT_AVAILABLE: 'ATTESTATION_NOT_AVAILABLE',
92
+ RETRY_FAILED: 'ATTESTATION_RETRY_FAILED',
93
+ INIT_FAILED: 'ATTESTATION_INIT_FAILED',
94
+ CMS_UNWRAP_FAILED: 'ATTESTATION_CMS_UNWRAP_FAILED',
95
+ KEYPAIR_GENERATION_FAILED: 'ATTESTATION_KEYPAIR_GENERATION_FAILED',
96
+ ENDPOINT_UNREACHABLE: 'ATTESTATION_ENDPOINT_UNREACHABLE'
97
+ };
98
+
99
+ // ═══════════════════════════════════════════════════════════════════════════
100
+ // CONTENT ERRORS
101
+ // ═══════════════════════════════════════════════════════════════════════════
102
+
103
+ class ContentError extends SecretKeyStoreError {
104
+ /**
105
+ * @param {string} message - Error message
106
+ * @param {string} code - Error code
107
+ * @param {string} [format] - Content format (env, json, yaml)
108
+ * @param {Error} [cause] - Original error
109
+ */
110
+ constructor(message, code, format, cause) {
111
+ super(message, code, cause);
112
+ this.name = 'ContentError';
113
+ this.format = format;
114
+ }
115
+ }
116
+
117
+ // Content Error Codes
118
+ const CONTENT_ERROR_CODES = {
119
+ PARSE_FAILED: 'CONTENT_PARSE_FAILED',
120
+ INVALID_FORMAT: 'CONTENT_INVALID_FORMAT',
121
+ EMPTY_CONTENT: 'CONTENT_EMPTY',
122
+ SERIALIZATION_FAILED: 'CONTENT_SERIALIZATION_FAILED'
123
+ };
124
+
125
+ // ═══════════════════════════════════════════════════════════════════════════
126
+ // PATH ERRORS
127
+ // ═══════════════════════════════════════════════════════════════════════════
128
+
129
+ class PathError extends SecretKeyStoreError {
130
+ /**
131
+ * @param {string} message - Error message
132
+ * @param {string} code - Error code
133
+ * @param {string} [path] - The path that caused the error
134
+ * @param {Error} [cause] - Original error
135
+ */
136
+ constructor(message, code, path, cause) {
137
+ super(message, code, cause);
138
+ this.name = 'PathError';
139
+ this.path = path;
140
+ }
141
+ }
142
+
143
+ // Path Error Codes
144
+ const PATH_ERROR_CODES = {
145
+ NOT_FOUND: 'PATH_NOT_FOUND',
146
+ INVALID_PATTERN: 'PATH_INVALID_PATTERN',
147
+ ACCESS_DENIED: 'PATH_ACCESS_DENIED'
148
+ };
149
+
150
+ // ═══════════════════════════════════════════════════════════════════════════
151
+ // ENCRYPTION ERRORS
152
+ // ═══════════════════════════════════════════════════════════════════════════
153
+
154
+ class EncryptionError extends SecretKeyStoreError {
155
+ /**
156
+ * @param {string} message - Error message
157
+ * @param {string} code - Error code
158
+ * @param {string} [key] - The key that failed to encrypt
159
+ * @param {Error} [cause] - Original error
160
+ */
161
+ constructor(message, code, key, cause) {
162
+ super(message, code, cause);
163
+ this.name = 'EncryptionError';
164
+ this.key = key;
165
+ }
166
+ }
167
+
168
+ // Encryption Error Codes
169
+ const ENCRYPTION_ERROR_CODES = {
170
+ FAILED: 'ENCRYPTION_FAILED',
171
+ INVALID_VALUE: 'ENCRYPTION_INVALID_VALUE',
172
+ ALREADY_ENCRYPTED: 'ENCRYPTION_ALREADY_ENCRYPTED'
173
+ };
174
+
175
+ // ═══════════════════════════════════════════════════════════════════════════
176
+ // DECRYPTION ERRORS
177
+ // ═══════════════════════════════════════════════════════════════════════════
178
+
179
+ class DecryptionError extends SecretKeyStoreError {
180
+ /**
181
+ * @param {string} message - Error message
182
+ * @param {string} code - Error code
183
+ * @param {string} [key] - The key that failed to decrypt
184
+ * @param {Error} [cause] - Original error
185
+ */
186
+ constructor(message, code, key, cause) {
187
+ super(message, code, cause);
188
+ this.name = 'DecryptionError';
189
+ this.key = key;
190
+ }
191
+ }
192
+
193
+ // Decryption Error Codes
194
+ const DECRYPTION_ERROR_CODES = {
195
+ FAILED: 'DECRYPTION_FAILED',
196
+ INVALID_CIPHERTEXT: 'DECRYPTION_INVALID_CIPHERTEXT',
197
+ NOT_ENCRYPTED: 'DECRYPTION_NOT_ENCRYPTED'
198
+ };
199
+
200
+ // ═══════════════════════════════════════════════════════════════════════════
201
+ // KEYSTORE ERRORS
202
+ // ═══════════════════════════════════════════════════════════════════════════
203
+
204
+ class KeystoreError extends SecretKeyStoreError {
205
+ constructor(message, code, cause) {
206
+ super(message, code, cause);
207
+ this.name = 'KeystoreError';
208
+ }
209
+ }
210
+
211
+ // Keystore Error Codes
212
+ const KEYSTORE_ERROR_CODES = {
213
+ NOT_INITIALIZED: 'KEYSTORE_NOT_INITIALIZED',
214
+ ALREADY_INITIALIZED: 'KEYSTORE_ALREADY_INITIALIZED',
215
+ DESTROYED: 'KEYSTORE_DESTROYED',
216
+ SECRET_NOT_FOUND: 'SECRET_NOT_FOUND',
217
+ SECRET_EXPIRED: 'SECRET_EXPIRED',
218
+ ACCESS_LIMIT_EXCEEDED: 'SECRET_ACCESS_LIMIT_EXCEEDED',
219
+ INITIALIZATION_FAILED: 'KEYSTORE_INITIALIZATION_FAILED',
220
+ REFRESH_FAILED: 'KEYSTORE_REFRESH_FAILED'
221
+ };
222
+
223
+ // ═══════════════════════════════════════════════════════════════════════════
224
+ // VALIDATION ERRORS
225
+ // ═══════════════════════════════════════════════════════════════════════════
226
+
227
+ class ValidationError extends SecretKeyStoreError {
228
+ /**
229
+ * @param {string} message - Error message
230
+ * @param {string} code - Error code
231
+ * @param {string} [field] - The field that failed validation
232
+ * @param {Error} [cause] - Original error
233
+ */
234
+ constructor(message, code, field, cause) {
235
+ super(message, code, cause);
236
+ this.name = 'ValidationError';
237
+ this.field = field;
238
+ }
239
+ }
240
+
241
+ // Validation Error Codes
242
+ const VALIDATION_ERROR_CODES = {
243
+ REQUIRED_FIELD: 'VALIDATION_REQUIRED_FIELD',
244
+ INVALID_TYPE: 'VALIDATION_INVALID_TYPE',
245
+ INVALID_VALUE: 'VALIDATION_INVALID_VALUE',
246
+ INVALID_OPTIONS: 'VALIDATION_INVALID_OPTIONS',
247
+ KMS_KEY_REQUIRED: 'VALIDATION_KMS_KEY_REQUIRED',
248
+ PROCESS_ENV_LEAK: 'VALIDATION_PROCESS_ENV_LEAK'
249
+ };
250
+
251
+ // ═══════════════════════════════════════════════════════════════════════════
252
+ // CONVENIENCE ERROR CLASSES
253
+ // ═══════════════════════════════════════════════════════════════════════════
254
+
255
+ class InitializationError extends KeystoreError {
256
+ constructor(message, cause) {
257
+ super(message, KEYSTORE_ERROR_CODES.INITIALIZATION_FAILED, cause);
258
+ this.name = 'InitializationError';
259
+ }
260
+ }
261
+
262
+ class SecretNotFoundError extends KeystoreError {
263
+ constructor(key) {
264
+ super(`Secret not found: ${key}`, KEYSTORE_ERROR_CODES.SECRET_NOT_FOUND);
265
+ this.name = 'SecretNotFoundError';
266
+ this.secretKey = key;
267
+ }
268
+ }
269
+
270
+ class NotInitializedError extends KeystoreError {
271
+ constructor() {
272
+ super(
273
+ 'SecretKeyStore not initialized. Call initialize() first.',
274
+ KEYSTORE_ERROR_CODES.NOT_INITIALIZED
275
+ );
276
+ this.name = 'NotInitializedError';
277
+ }
278
+ }
279
+
280
+ // ═══════════════════════════════════════════════════════════════════════════
281
+ // HELPER FUNCTIONS
282
+ // ═══════════════════════════════════════════════════════════════════════════
283
+
284
+ /**
285
+ * Check if an error is recoverable (can be retried)
286
+ * @param {Error} error - Error to check
287
+ * @returns {boolean}
288
+ */
289
+ function isRecoverableError(error) {
290
+ if (!(error instanceof SecretKeyStoreError)) {
291
+ return false;
292
+ }
293
+
294
+ const recoverableCodes = [
295
+ KMS_ERROR_CODES.KEY_DISABLED,
296
+ KMS_ERROR_CODES.THROTTLED,
297
+ KMS_ERROR_CODES.CONNECTION_ERROR,
298
+ ATTESTATION_ERROR_CODES.DOCUMENT_EXPIRED,
299
+ KEYSTORE_ERROR_CODES.SECRET_EXPIRED
300
+ ];
301
+
302
+ return recoverableCodes.includes(error.code);
303
+ }
304
+
305
+ /**
306
+ * Create a KMS error from an AWS SDK error
307
+ * @param {Error} awsError - AWS SDK error
308
+ * @param {string} kmsKeyId - KMS key ID
309
+ * @param {string} operation - 'encrypt' or 'decrypt'
310
+ * @returns {KmsError}
311
+ */
312
+ function createKmsErrorFromAws(awsError, kmsKeyId, operation = 'decrypt') {
313
+ const message = awsError.message || 'KMS operation failed';
314
+ const errorName = awsError.name || '';
315
+
316
+ let code;
317
+ if (errorName.includes('NotFoundException') || message.includes('not found')) {
318
+ code = KMS_ERROR_CODES.KEY_NOT_FOUND;
319
+ } else if (errorName.includes('DisabledException') || message.includes('disabled')) {
320
+ code = KMS_ERROR_CODES.KEY_DISABLED;
321
+ } else if (errorName.includes('AccessDeniedException') || message.includes('Access Denied')) {
322
+ code = KMS_ERROR_CODES.ACCESS_DENIED;
323
+ } else if (errorName.includes('InvalidCiphertextException') || message.includes('ciphertext')) {
324
+ code = KMS_ERROR_CODES.INVALID_CIPHERTEXT;
325
+ } else if (errorName.includes('ThrottlingException') || message.includes('throttl')) {
326
+ code = KMS_ERROR_CODES.THROTTLED;
327
+ } else if (operation === 'encrypt') {
328
+ code = KMS_ERROR_CODES.ENCRYPT_FAILED;
329
+ } else {
330
+ code = KMS_ERROR_CODES.DECRYPT_FAILED;
331
+ }
332
+
333
+ return new KmsError(message, code, kmsKeyId, awsError);
334
+ }
335
+
336
+ // ═══════════════════════════════════════════════════════════════════════════
337
+ // EXPORTS
338
+ // ═══════════════════════════════════════════════════════════════════════════
339
+
340
+ module.exports = {
341
+ // Base class
342
+ SecretKeyStoreError,
343
+
344
+ // Specialized error classes
345
+ KmsError,
346
+ AttestationError,
347
+ ContentError,
348
+ PathError,
349
+ EncryptionError,
350
+ DecryptionError,
351
+ KeystoreError,
352
+ ValidationError,
353
+
354
+ // Convenience classes
355
+ InitializationError,
356
+ SecretNotFoundError,
357
+ NotInitializedError,
358
+
359
+ // Error codes
360
+ KMS_ERROR_CODES,
361
+ ATTESTATION_ERROR_CODES,
362
+ CONTENT_ERROR_CODES,
363
+ PATH_ERROR_CODES,
364
+ ENCRYPTION_ERROR_CODES,
365
+ DECRYPTION_ERROR_CODES,
366
+ KEYSTORE_ERROR_CODES,
367
+ VALIDATION_ERROR_CODES,
368
+
369
+ // Helper functions
370
+ isRecoverableError,
371
+ createKmsErrorFromAws
372
+ };