@rodit/rodit-auth-be 9.11.14
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/CHANGELOG.md +54 -0
- package/README.md +3543 -0
- package/index.js +1884 -0
- package/lib/auth/authentication.js +1971 -0
- package/lib/auth/roditmanager.js +627 -0
- package/lib/auth/sessionmanager.js +1302 -0
- package/lib/auth/tokenservice.js +2418 -0
- package/lib/blockchain/blockchainservice.js +1715 -0
- package/lib/blockchain/statemanager.js +1614 -0
- package/lib/middleware/authenticationmw.js +2301 -0
- package/lib/middleware/environcredentialstoremw.js +176 -0
- package/lib/middleware/filecredentialstoremw.js +158 -0
- package/lib/middleware/loggingmw.js +82 -0
- package/lib/middleware/performanceexamplemw.js +58 -0
- package/lib/middleware/performancemw.js +172 -0
- package/lib/middleware/ratelimitmw.js +171 -0
- package/lib/middleware/validatepermissionsmw.js +439 -0
- package/lib/middleware/vaultcredentialstoremw.js +617 -0
- package/lib/middleware/versioningmw.js +142 -0
- package/lib/middleware/webhookhandlermw.js +1388 -0
- package/package.json +57 -0
- package/services/configsdk.js +588 -0
- package/services/env.js +34 -0
- package/services/error-response.js +29 -0
- package/services/logger.js +160 -0
- package/services/performanceservice.js +568 -0
- package/services/utils.js +1024 -0
- package/services/versionmanager.js +81 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault-based credential storage
|
|
3
|
+
* Copyright (c) 2026 Discernible IO. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const config = require('../../services/configsdk');
|
|
7
|
+
const { ulid } = require("ulid");
|
|
8
|
+
const logger = require("../../services/logger");
|
|
9
|
+
const { createLogContext, logErrorWithMetrics } = logger;
|
|
10
|
+
const { validateAndExtractCredentials } = require("../../services/utils");
|
|
11
|
+
|
|
12
|
+
logger.debugWithContext("Loading vaultcredentialstoremw.js module", createLogContext(
|
|
13
|
+
"ModuleLoader",
|
|
14
|
+
"moduleInitialization",
|
|
15
|
+
{
|
|
16
|
+
module: "vaultcredentialstoremw.js",
|
|
17
|
+
loadedAt: new Date().toISOString()
|
|
18
|
+
}
|
|
19
|
+
));
|
|
20
|
+
|
|
21
|
+
class VaultCredentialManager {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.vault = require("node-vault")();
|
|
24
|
+
this.vault.endpoint = config.get("VAULT_ENDPOINT");
|
|
25
|
+
this.vault.apiVersion = "v1";
|
|
26
|
+
this.roleId = config.get("VAULT_ROLE_ID");
|
|
27
|
+
this.secretId = config.get("VAULT_SECRET_ID");
|
|
28
|
+
this.renewalInterval = 60 * 60 * 1000; // 1 hour in milliseconds
|
|
29
|
+
this.vaultInitialized = false;
|
|
30
|
+
this.vaultPath = null;
|
|
31
|
+
this.credentials = {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getVaultToken() {
|
|
35
|
+
const requestId = ulid();
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
|
|
38
|
+
// Create a base context for this method
|
|
39
|
+
const baseContext = createLogContext(
|
|
40
|
+
"CredentialManager",
|
|
41
|
+
"getVaultToken",
|
|
42
|
+
{ requestId }
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
logger.infoWithContext("Attempting Vault authentication", {
|
|
46
|
+
...baseContext,
|
|
47
|
+
result: 'call',
|
|
48
|
+
reason: 'Vault authentication requested'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const result = await this.vault.approleLogin({
|
|
53
|
+
role_id: this.roleId,
|
|
54
|
+
secret_id: this.secretId,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
logger.infoWithContext("Vault authentication successful", {
|
|
58
|
+
...baseContext,
|
|
59
|
+
duration: Date.now() - startTime,
|
|
60
|
+
result: 'success',
|
|
61
|
+
reason: 'Vault authentication succeeded'
|
|
62
|
+
});
|
|
63
|
+
logger.metric("vault_authentication_duration_ms", Date.now() - startTime, {
|
|
64
|
+
component: "CredentialManager",
|
|
65
|
+
result: 'success',
|
|
66
|
+
reason: 'Vault authentication succeeded'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return result.auth.client_token;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logger.metric("vault_authentication_duration_ms", Date.now() - startTime, {
|
|
72
|
+
component: "CredentialManager",
|
|
73
|
+
result: 'failure',
|
|
74
|
+
reason: error.message || 'Vault authentication failed'
|
|
75
|
+
});
|
|
76
|
+
logErrorWithMetrics(
|
|
77
|
+
"Error authenticating with Vault",
|
|
78
|
+
{
|
|
79
|
+
...baseContext,
|
|
80
|
+
duration: Date.now() - startTime,
|
|
81
|
+
result: 'failure',
|
|
82
|
+
reason: error.message || 'Vault authentication failed'
|
|
83
|
+
},
|
|
84
|
+
error,
|
|
85
|
+
"vault_authentication_error",
|
|
86
|
+
{ error_type: "auth_failure" }
|
|
87
|
+
);
|
|
88
|
+
throw new Error("Error 108: Vault authentication failed");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async initialize() {
|
|
93
|
+
const requestId = ulid();
|
|
94
|
+
const startTime = Date.now();
|
|
95
|
+
const baseContext = createLogContext('CredentialManager', 'initialize', { requestId });
|
|
96
|
+
const log = (message, extra = {}) => logger.infoWithContext(message, { ...baseContext, ...extra });
|
|
97
|
+
const logError = (error, context = {}) => logErrorWithMetrics(
|
|
98
|
+
error.message,
|
|
99
|
+
{ ...baseContext, ...context, result: 'failure' },
|
|
100
|
+
error,
|
|
101
|
+
'vault_initialization_error',
|
|
102
|
+
{ error_type: error.errorType || 'initialization_failure' }
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
log('Starting vault initialization', { result: 'call', reason: 'Vault initialization requested' });
|
|
106
|
+
|
|
107
|
+
if (this.vaultInitialized) {
|
|
108
|
+
log('Vault already initialized', {
|
|
109
|
+
duration: Date.now() - startTime,
|
|
110
|
+
result: 'success',
|
|
111
|
+
reason: 'Vault was already initialized'
|
|
112
|
+
});
|
|
113
|
+
return this.vault;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Initialize secret storage token
|
|
118
|
+
this.vault.token = await this.getVaultToken();
|
|
119
|
+
log('Checking Vault health status', { result: 'call', reason: 'Vault health status check requested' });
|
|
120
|
+
|
|
121
|
+
// Check Vault health
|
|
122
|
+
const health = await this.vault.health();
|
|
123
|
+
|
|
124
|
+
if (!health.initialized || health.sealed) {
|
|
125
|
+
const error = new Error(health.sealed ? 'Error 110: Vault is sealed' : 'Error 109: Vault is not initialized');
|
|
126
|
+
error.errorType = health.sealed ? 'sealed' : 'not_initialized';
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Load and validate config
|
|
131
|
+
if (!config || typeof config.get !== 'function') {
|
|
132
|
+
const error = new Error('Config object is not properly initialized');
|
|
133
|
+
error.errorType = 'config_initialization_error';
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.vaultPath = config.get('VAULT_RODIT_KEYVALUE_PATH');
|
|
138
|
+
log('Retrieved vault path from config', {
|
|
139
|
+
vaultPath: this.vaultPath,
|
|
140
|
+
result: 'success',
|
|
141
|
+
reason: 'Vault path retrieved from config'
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
this.vaultInitialized = true;
|
|
145
|
+
const duration = Date.now() - startTime;
|
|
146
|
+
|
|
147
|
+
log('Vault initialized successfully', {
|
|
148
|
+
vaultInitialized: true,
|
|
149
|
+
vaultPath: this.vaultPath,
|
|
150
|
+
duration,
|
|
151
|
+
result: 'success',
|
|
152
|
+
reason: 'Vault initialized successfully'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
logger.metric('vault_initialization_duration_ms', duration, {
|
|
156
|
+
component: 'CredentialManager',
|
|
157
|
+
result: 'success',
|
|
158
|
+
reason: 'Vault initialized successfully'
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return this.vault;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const duration = Date.now() - startTime;
|
|
164
|
+
this.vaultInitialized = false;
|
|
165
|
+
|
|
166
|
+
logError(error, {
|
|
167
|
+
duration,
|
|
168
|
+
reason: error.message || 'Vault initialization failed',
|
|
169
|
+
errorType: error.code || 'UNKNOWN_ERROR'
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
logger.metric('vault_initialization_duration_ms', duration, {
|
|
173
|
+
component: 'CredentialManager',
|
|
174
|
+
result: 'failure',
|
|
175
|
+
reason: error.message || 'Vault initialization failed',
|
|
176
|
+
errorType: error.code || 'UNKNOWN_ERROR'
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
logger.metric('vault_initialization_errors_total', 1, {
|
|
180
|
+
component: 'CredentialManager',
|
|
181
|
+
errorType: error.code || 'UNKNOWN_ERROR'
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async setupSecretStorageTokenRenewal() {
|
|
189
|
+
const requestId = ulid();
|
|
190
|
+
const startTime = Date.now();
|
|
191
|
+
|
|
192
|
+
// Create a base context for this method
|
|
193
|
+
const baseContext = createLogContext(
|
|
194
|
+
"CredentialManager",
|
|
195
|
+
"setupSecretStorageTokenRenewal",
|
|
196
|
+
{ requestId }
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
logger.debugWithContext("Starting secret storage token renewal setup", baseContext);
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// Get secret storage token info to determine TTL
|
|
203
|
+
const tokenInfo = await this.vault.tokenLookupSelf();
|
|
204
|
+
const ttlSeconds = tokenInfo.data.ttl;
|
|
205
|
+
|
|
206
|
+
// Calculate renewal time (renew at 80% of TTL)
|
|
207
|
+
const renewalTimeMs = (ttlSeconds * 0.8) * 1000;
|
|
208
|
+
|
|
209
|
+
logger.infoWithContext("Setting up secret storage token renewal", {
|
|
210
|
+
...baseContext,
|
|
211
|
+
ttlSeconds,
|
|
212
|
+
renewalIntervalMs: renewalTimeMs || this.renewalInterval,
|
|
213
|
+
nextRenewalAt: new Date(Date.now() + (renewalTimeMs || this.renewalInterval)).toISOString(),
|
|
214
|
+
duration: Date.now() - startTime
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const interval = renewalTimeMs || this.renewalInterval;
|
|
218
|
+
|
|
219
|
+
setInterval(async () => {
|
|
220
|
+
const renewalRequestId = ulid();
|
|
221
|
+
const renewalStartTime = Date.now();
|
|
222
|
+
|
|
223
|
+
// Create a context for the renewal operation
|
|
224
|
+
const renewalContext = createLogContext(
|
|
225
|
+
"CredentialManager",
|
|
226
|
+
"secretStorageTokenRenewal",
|
|
227
|
+
{ requestId: renewalRequestId }
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
logger.debugWithContext("Attempting to renew secret storage token", renewalContext);
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Renew secret storage token instead of re-authenticating
|
|
234
|
+
const renewResponse = await this.vault.tokenRenew();
|
|
235
|
+
|
|
236
|
+
logger.infoWithContext("Successfully renewed secret storage token", {
|
|
237
|
+
...renewalContext,
|
|
238
|
+
newTtl: renewResponse.auth?.lease_duration || "unknown",
|
|
239
|
+
duration: Date.now() - renewalStartTime
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Add metrics for successful renewal
|
|
243
|
+
logger.metric("vault_token_renewal_duration_ms", Date.now() - renewalStartTime, {
|
|
244
|
+
success: true,
|
|
245
|
+
component: "CredentialManager"
|
|
246
|
+
});
|
|
247
|
+
} catch (error) {
|
|
248
|
+
logErrorWithMetrics(
|
|
249
|
+
"Error renewing secret storage token, attempting re-authentication",
|
|
250
|
+
{
|
|
251
|
+
...renewalContext,
|
|
252
|
+
duration: Date.now() - renewalStartTime
|
|
253
|
+
},
|
|
254
|
+
error,
|
|
255
|
+
"vault_token_renewal_error",
|
|
256
|
+
{ error_type: "renewal_failure" }
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const token = await this.getVaultToken();
|
|
261
|
+
this.vault.token = token;
|
|
262
|
+
|
|
263
|
+
logger.infoWithContext("Successfully re-authenticated secret storage with Vault", {
|
|
264
|
+
...renewalContext,
|
|
265
|
+
duration: Date.now() - renewalStartTime
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Add metrics for successful re-authentication
|
|
269
|
+
logger.metric("vault_token_reauthentication_duration_ms", Date.now() - renewalStartTime, {
|
|
270
|
+
success: true,
|
|
271
|
+
component: "CredentialManager"
|
|
272
|
+
});
|
|
273
|
+
} catch (reAuthError) {
|
|
274
|
+
logErrorWithMetrics(
|
|
275
|
+
"Failed to re-authenticate secret storage with Vault",
|
|
276
|
+
{
|
|
277
|
+
...renewalContext,
|
|
278
|
+
duration: Date.now() - renewalStartTime
|
|
279
|
+
},
|
|
280
|
+
reAuthError,
|
|
281
|
+
"vault_token_reauthentication_error",
|
|
282
|
+
{ error_type: "reauthentication_failure" }
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}, interval);
|
|
287
|
+
|
|
288
|
+
return true;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logErrorWithMetrics(
|
|
291
|
+
"Error setting up secret storage token renewal",
|
|
292
|
+
{
|
|
293
|
+
...baseContext,
|
|
294
|
+
duration: Date.now() - startTime
|
|
295
|
+
},
|
|
296
|
+
error,
|
|
297
|
+
"vault_token_renewal_setup_error",
|
|
298
|
+
{ error_type: "setup_failure" }
|
|
299
|
+
);
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async getRoditFromVault(vaultPath, secretKey) {
|
|
305
|
+
const requestId = ulid();
|
|
306
|
+
const startTime = Date.now();
|
|
307
|
+
|
|
308
|
+
// Create a base context for this method
|
|
309
|
+
const baseContext = createLogContext(
|
|
310
|
+
"CredentialManager",
|
|
311
|
+
"getRoditFromVault",
|
|
312
|
+
{
|
|
313
|
+
requestId,
|
|
314
|
+
vaultPath,
|
|
315
|
+
secretKey
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
this.validateVaultParameters(vaultPath, secretKey);
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
logger.debugWithContext("Retrieving data from Vault", {
|
|
323
|
+
...baseContext,
|
|
324
|
+
path: `secret/data/${vaultPath}`,
|
|
325
|
+
apiEndpoint: this.vault.endpoint,
|
|
326
|
+
hasToken: !!this.vault.token
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const result = await this.vault.read(`secret/data/${vaultPath}`);
|
|
330
|
+
const secretData = result.data.data[secretKey];
|
|
331
|
+
|
|
332
|
+
if (!secretData) {
|
|
333
|
+
const error = new Error(
|
|
334
|
+
`Error 048: No data found for ${secretKey} at secret/data/${vaultPath}`
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
logErrorWithMetrics(
|
|
338
|
+
"No data found in Vault path",
|
|
339
|
+
baseContext,
|
|
340
|
+
error,
|
|
341
|
+
"vault_data_retrieval_error",
|
|
342
|
+
{ error_type: "data_not_found" }
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
logger.debugWithContext("Successfully retrieved data from Vault", {
|
|
349
|
+
...baseContext,
|
|
350
|
+
duration: Date.now() - startTime
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const parsedData = this.parseSecretData(secretData, secretKey);
|
|
354
|
+
return validateAndExtractCredentials(parsedData, logger);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
logErrorWithMetrics(
|
|
357
|
+
"Error retrieving Rodit config from Vault",
|
|
358
|
+
{
|
|
359
|
+
...baseContext,
|
|
360
|
+
duration: Date.now() - startTime,
|
|
361
|
+
errorDetails: error.response?.data || error.response || "No details available",
|
|
362
|
+
statusCode: error.response?.statusCode
|
|
363
|
+
},
|
|
364
|
+
error,
|
|
365
|
+
"vault_data_retrieval_error",
|
|
366
|
+
{ error_type: "retrieval_failure" }
|
|
367
|
+
);
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
validateVaultParameters(vaultPath, secretKey) {
|
|
373
|
+
const requestId = ulid();
|
|
374
|
+
|
|
375
|
+
// Create a base context for this method
|
|
376
|
+
const baseContext = createLogContext(
|
|
377
|
+
"CredentialManager",
|
|
378
|
+
"validateVaultParameters",
|
|
379
|
+
{
|
|
380
|
+
requestId,
|
|
381
|
+
vaultPath,
|
|
382
|
+
secretKey
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
logger.debugWithContext("Validating Vault parameters", baseContext);
|
|
387
|
+
|
|
388
|
+
if (!this.vault || typeof this.vault.read !== "function") {
|
|
389
|
+
const error = new Error("Error 051: Invalid vault object");
|
|
390
|
+
logErrorWithMetrics(
|
|
391
|
+
"Invalid vault object",
|
|
392
|
+
baseContext,
|
|
393
|
+
error,
|
|
394
|
+
"vault_parameter_validation_error",
|
|
395
|
+
{ error_type: "invalid_vault" }
|
|
396
|
+
);
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
if (!vaultPath || typeof vaultPath !== "string") {
|
|
400
|
+
const error = new Error("Error 052: Invalid VAULT_RODIT_KEYVALUE_PATH");
|
|
401
|
+
logErrorWithMetrics(
|
|
402
|
+
"Invalid vault path",
|
|
403
|
+
baseContext,
|
|
404
|
+
error,
|
|
405
|
+
"vault_parameter_validation_error",
|
|
406
|
+
{ error_type: "invalid_path" }
|
|
407
|
+
);
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
if (!secretKey || typeof secretKey !== "string") {
|
|
411
|
+
const error = new Error("Error 047: Invalid or missing secretKey parameter");
|
|
412
|
+
logErrorWithMetrics(
|
|
413
|
+
"Invalid secret key",
|
|
414
|
+
baseContext,
|
|
415
|
+
error,
|
|
416
|
+
"vault_parameter_validation_error",
|
|
417
|
+
{ error_type: "invalid_key" }
|
|
418
|
+
);
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
logger.debugWithContext("Vault parameters validated successfully", baseContext);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
parseSecretData(secretData, secretKey) {
|
|
426
|
+
const requestId = ulid();
|
|
427
|
+
|
|
428
|
+
// Create a base context for this method
|
|
429
|
+
const baseContext = createLogContext(
|
|
430
|
+
"CredentialManager",
|
|
431
|
+
"parseSecretData",
|
|
432
|
+
{
|
|
433
|
+
requestId,
|
|
434
|
+
secretKey,
|
|
435
|
+
dataType: typeof secretData
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
logger.debugWithContext("Parsing secret data", baseContext);
|
|
440
|
+
|
|
441
|
+
if (typeof secretData === "string") {
|
|
442
|
+
try {
|
|
443
|
+
const parsedData = JSON.parse(secretData);
|
|
444
|
+
logger.debugWithContext("Successfully parsed secret data", baseContext);
|
|
445
|
+
return parsedData;
|
|
446
|
+
} catch (parseError) {
|
|
447
|
+
const error = new Error(`Error 046: Invalid JSON format in ${secretKey}`);
|
|
448
|
+
logErrorWithMetrics(
|
|
449
|
+
"Failed to parse secret data",
|
|
450
|
+
baseContext,
|
|
451
|
+
parseError,
|
|
452
|
+
"vault_data_parsing_error",
|
|
453
|
+
{ error_type: "invalid_json" }
|
|
454
|
+
);
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
logger.debugWithContext("Secret data already in object format", baseContext);
|
|
460
|
+
return secretData;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async getCredentials(type) {
|
|
464
|
+
const requestId = ulid();
|
|
465
|
+
const startTime = Date.now();
|
|
466
|
+
const maxRetries = 2; // Maximum number of retry attempts
|
|
467
|
+
let retryCount = 0;
|
|
468
|
+
let lastError = null;
|
|
469
|
+
|
|
470
|
+
// Create a base context for this method
|
|
471
|
+
const baseContext = createLogContext(
|
|
472
|
+
"CredentialManager",
|
|
473
|
+
"getCredentials",
|
|
474
|
+
{
|
|
475
|
+
requestId,
|
|
476
|
+
credentialType: type
|
|
477
|
+
}
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
logger.debugWithContext("Retrieving credentials", baseContext);
|
|
481
|
+
|
|
482
|
+
// Use cached credentials if available
|
|
483
|
+
if (this.credentials[type]) {
|
|
484
|
+
logger.debugWithContext("Using cached credentials", baseContext);
|
|
485
|
+
return this.credentials[type];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Make sure vault is initialized
|
|
489
|
+
if (!this.vaultInitialized) {
|
|
490
|
+
logger.debugWithContext("Vault not initialized, initializing now", baseContext);
|
|
491
|
+
await this.initialize();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Retry logic for vault operations
|
|
495
|
+
while (retryCount <= maxRetries) {
|
|
496
|
+
try {
|
|
497
|
+
// Make the accountType consistent with the type parameter
|
|
498
|
+
const accountType = `account_${type}`;
|
|
499
|
+
const vaultPath = `${this.vaultPath}/${type}`;
|
|
500
|
+
|
|
501
|
+
logger.debugWithContext("Fetching credentials from vault", {
|
|
502
|
+
...baseContext,
|
|
503
|
+
vaultPath,
|
|
504
|
+
attempt: retryCount + 1,
|
|
505
|
+
maxAttempts: maxRetries + 1
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const vaultData = await this.getRoditFromVault(
|
|
509
|
+
vaultPath,
|
|
510
|
+
accountType
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
// Add detailed logging about the vault data received
|
|
514
|
+
logger.debugWithContext("Vault data received", {
|
|
515
|
+
...baseContext,
|
|
516
|
+
hasVaultData: !!vaultData,
|
|
517
|
+
dataKeys: vaultData ? Object.keys(vaultData) : [],
|
|
518
|
+
hasPrivateKey: vaultData && !!vaultData.private_key
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Safely check for private_key before using it
|
|
522
|
+
if (!vaultData || !vaultData.private_key || typeof vaultData.private_key !== "string") {
|
|
523
|
+
const error = new Error(`Invalid or missing private_key for ${type}`);
|
|
524
|
+
logErrorWithMetrics(
|
|
525
|
+
"Invalid private key format",
|
|
526
|
+
baseContext,
|
|
527
|
+
error,
|
|
528
|
+
"credential_retrieval_error",
|
|
529
|
+
{ error_type: "invalid_private_key" }
|
|
530
|
+
);
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Cache the credentials for future use
|
|
535
|
+
this.credentials[type] = vaultData;
|
|
536
|
+
|
|
537
|
+
const duration = Date.now() - startTime;
|
|
538
|
+
logger.infoWithContext("Successfully retrieved credentials", {
|
|
539
|
+
...baseContext,
|
|
540
|
+
duration,
|
|
541
|
+
accountId: vaultData.account_id // Safe to log account ID
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Emit metrics for dashboards
|
|
545
|
+
logger.metric("credential_retrieval_duration_ms", duration, {
|
|
546
|
+
success: true,
|
|
547
|
+
credentialType: type,
|
|
548
|
+
component: "CredentialManager"
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
return vaultData;
|
|
552
|
+
} catch (error) {
|
|
553
|
+
lastError = error;
|
|
554
|
+
|
|
555
|
+
// Log the error but don't throw yet if we have retries left
|
|
556
|
+
const isRetrying = retryCount < maxRetries;
|
|
557
|
+
const duration = Date.now() - startTime;
|
|
558
|
+
|
|
559
|
+
if (isRetrying) {
|
|
560
|
+
logger.warnWithContext(`Retryable error retrieving credentials`, {
|
|
561
|
+
...baseContext,
|
|
562
|
+
duration,
|
|
563
|
+
errorMessage: error.message,
|
|
564
|
+
errorCode: error.code || "UNKNOWN_ERROR",
|
|
565
|
+
attempt: retryCount + 1,
|
|
566
|
+
willRetry: true
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// Emit metrics for dashboards
|
|
570
|
+
logger.metric("credential_retrieval_duration_ms", duration, {
|
|
571
|
+
success: false,
|
|
572
|
+
credentialType: type,
|
|
573
|
+
errorType: error.code || "UNKNOWN_ERROR",
|
|
574
|
+
component: "CredentialManager",
|
|
575
|
+
retryAttempt: retryCount
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
retryCount++;
|
|
579
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount)); // Exponential backoff
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// If we've exhausted retries, log and throw the last error
|
|
584
|
+
logErrorWithMetrics(
|
|
585
|
+
"Failed to retrieve credentials after all retry attempts",
|
|
586
|
+
{
|
|
587
|
+
...baseContext,
|
|
588
|
+
duration,
|
|
589
|
+
attempts: retryCount + 1,
|
|
590
|
+
maxAttempts: maxRetries + 1
|
|
591
|
+
},
|
|
592
|
+
lastError,
|
|
593
|
+
"credential_retrieval_error",
|
|
594
|
+
{ error_type: "max_retries_exceeded" }
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
// Emit metrics for dashboards
|
|
598
|
+
logger.metric("credential_retrieval_errors_total", 1, {
|
|
599
|
+
credentialType: type,
|
|
600
|
+
errorType: error.code || "UNKNOWN_ERROR",
|
|
601
|
+
component: "CredentialManager"
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
throw lastError;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const vaultManager = new VaultCredentialManager();
|
|
611
|
+
|
|
612
|
+
module.exports = {
|
|
613
|
+
initializeCredentialStore: () => vaultManager.initialize(),
|
|
614
|
+
setupSecretStorageTokenRenewal: () => vaultManager.setupSecretStorageTokenRenewal(),
|
|
615
|
+
getCredentials: (type) => vaultManager.getCredentials(type),
|
|
616
|
+
vault: vaultManager.vault,
|
|
617
|
+
};
|