@benzid.wael/secure-vault 0.0.1
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/LICENSE +21 -0
- package/README.md +268 -0
- package/bin/cli.js +83 -0
- package/bin/commands/env.js +807 -0
- package/package.json +167 -0
- package/src/electron/models/EnvironmentVault.js +251 -0
- package/src/electron/models/Vault.js +87 -0
- package/src/electron/services/CryptographyService.js +54 -0
- package/src/electron/services/EnvironmentVaultService.js +564 -0
- package/src/electron/services/ImportExportService.js +126 -0
- package/src/electron/services/MenuService.js +110 -0
- package/src/electron/services/SecurityManager.js +109 -0
- package/src/electron/services/VaultFileService.js +137 -0
- package/src/electron/services/VaultRecoveryService.js +134 -0
- package/src/electron/services/VaultService.js +578 -0
- package/src/electron/services/VaultSettingsService.js +78 -0
- package/src/electron/services/WindowManager.js +266 -0
- package/src/electron/services/recovery/IRecoveryMethod.js +88 -0
- package/src/electron/services/recovery/KeyRecoveryService.js +245 -0
- package/src/electron/services/recovery/PasswordRecoveryService.js +128 -0
- package/src/electron/services/recovery/SecretQuestionRecoveryService.js +267 -0
- package/src/electron/services/recovery/UsbRecoveryService.js +244 -0
- package/src/electron/utils/appPaths.js +50 -0
- package/src/electron/utils/passwordValidation.js +29 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
import { Vault } from '../models/Vault.js';
|
|
2
|
+
import { CryptographyService } from './CryptographyService.js';
|
|
3
|
+
import { VaultFileService } from './VaultFileService.js';
|
|
4
|
+
import { validatePasswordStrength } from '../utils/passwordValidation.js';
|
|
5
|
+
import { KeyRecoveryService } from './recovery/KeyRecoveryService.js';
|
|
6
|
+
import { PasswordRecoveryService } from './recovery/PasswordRecoveryService.js';
|
|
7
|
+
import { RecoveryData } from './recovery/IRecoveryMethod.js';
|
|
8
|
+
|
|
9
|
+
export class VaultService {
|
|
10
|
+
constructor(vaultDirectory) {
|
|
11
|
+
this.fileService = new VaultFileService(vaultDirectory);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async getAvailableVaults() {
|
|
15
|
+
const vaults = await this.fileService.listVaults();
|
|
16
|
+
|
|
17
|
+
// Ensure default vault exists
|
|
18
|
+
if (!vaults.includes('default')) {
|
|
19
|
+
await this.createDefaultVault();
|
|
20
|
+
vaults.unshift('default');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return vaults;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async verifyVaultPassword(vaultName, password) {
|
|
27
|
+
try {
|
|
28
|
+
// Try to load the vault with the provided password
|
|
29
|
+
const result = await this.loadVault(vaultName, password);
|
|
30
|
+
return {
|
|
31
|
+
success: result.success,
|
|
32
|
+
valid: result.success && !!result.data,
|
|
33
|
+
error: result.error,
|
|
34
|
+
};
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`Error verifying password for vault ${vaultName}:`, error);
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
valid: false,
|
|
40
|
+
error: error.message,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async encryptVault(data, masterPassword, recoveryKey, oldPassword) {
|
|
46
|
+
const salt = CryptographyService.generateSalt();
|
|
47
|
+
const key = CryptographyService.deriveKey(masterPassword, salt);
|
|
48
|
+
|
|
49
|
+
// Generate recovery key
|
|
50
|
+
let recoveryMetadata = RecoveryKeyService.createRecoveryMetadata(
|
|
51
|
+
recoveryKey,
|
|
52
|
+
masterPassword,
|
|
53
|
+
salt
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (oldPassword) {
|
|
57
|
+
const currentKey = CryptographyService.deriveKey(oldPassword, salt);
|
|
58
|
+
const recoveryPassword = CryptographyService.encrypt(
|
|
59
|
+
{ masterPassword },
|
|
60
|
+
currentKey
|
|
61
|
+
);
|
|
62
|
+
recoveryMetadata = {
|
|
63
|
+
...recoveryMetadata,
|
|
64
|
+
recoveryPassword,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const encryptedData = CryptographyService.encrypt(vault.toJSON(), key);
|
|
69
|
+
return {
|
|
70
|
+
...encryptedData,
|
|
71
|
+
salt: salt.toString('hex'),
|
|
72
|
+
recoveryMetadata,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async createVault(vaultName, masterPassword) {
|
|
77
|
+
if (await this.fileService.vaultExists(vaultName)) {
|
|
78
|
+
throw new Error('Vault already exists');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const vault = new Vault({ name: vaultName });
|
|
82
|
+
const salt = CryptographyService.generateSalt();
|
|
83
|
+
const key = CryptographyService.deriveKey(masterPassword, salt);
|
|
84
|
+
|
|
85
|
+
// Generate recovery key
|
|
86
|
+
const keyRecoveryService = new KeyRecoveryService();
|
|
87
|
+
const recoveryKey = await keyRecoveryService.generate();
|
|
88
|
+
const recoveryKeyMetadata = keyRecoveryService.createMetadata(
|
|
89
|
+
vaultName,
|
|
90
|
+
masterPassword,
|
|
91
|
+
recoveryKey
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const encryptedData = CryptographyService.encrypt(vault.toJSON(), key);
|
|
95
|
+
let recoveryMetadata = {};
|
|
96
|
+
recoveryMetadata[keyRecoveryService.getRecoveryMethodId()] =
|
|
97
|
+
recoveryKeyMetadata;
|
|
98
|
+
const vaultFile = {
|
|
99
|
+
...encryptedData,
|
|
100
|
+
salt: salt.toString('hex'),
|
|
101
|
+
recoveryMetadata,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
await this.fileService.writeVaultFile(vaultName, vaultFile);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
recoveryKey,
|
|
109
|
+
recoveryKeyCreatedAt: recoveryMetadata.recoveryKey.createdAt,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async verifyPassword(vaultName, password, vaultPath = null) {
|
|
114
|
+
if (!!!vaultPath) {
|
|
115
|
+
vaultPath = this.fileService.getVaultPath(vaultName);
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const vaultFile = await this.fileService.readVaultPath(vaultPath);
|
|
119
|
+
const salt = Buffer.from(vaultFile.salt, 'hex');
|
|
120
|
+
const key = CryptographyService.deriveKey(password, salt);
|
|
121
|
+
|
|
122
|
+
const encryptedData = {
|
|
123
|
+
encrypted: vaultFile.encrypted,
|
|
124
|
+
authTag: vaultFile.authTag,
|
|
125
|
+
iv: vaultFile.iv,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
CryptographyService.decrypt(encryptedData, key);
|
|
129
|
+
return { success: true };
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return { success: false, error: 'Invalid master password' };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async loadVault(vaultName, password) {
|
|
136
|
+
try {
|
|
137
|
+
console.log(`[VaultService] Loading vault: ${vaultName}`);
|
|
138
|
+
const vaultFile = await this.fileService.readVaultFile(vaultName);
|
|
139
|
+
console.log(
|
|
140
|
+
'[VaultService] Vault file loaded:',
|
|
141
|
+
JSON.stringify(vaultFile, null, 2)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const salt = Buffer.from(vaultFile.salt, 'hex');
|
|
145
|
+
const key = CryptographyService.deriveKey(password, salt);
|
|
146
|
+
|
|
147
|
+
const encryptedData = {
|
|
148
|
+
encrypted: vaultFile.encrypted,
|
|
149
|
+
authTag: vaultFile.authTag,
|
|
150
|
+
iv: vaultFile.iv,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const vaultData = CryptographyService.decrypt(encryptedData, key);
|
|
154
|
+
|
|
155
|
+
const vault = Vault.fromJSON(vaultData, vaultName);
|
|
156
|
+
const vaultJson = vault.toJSON();
|
|
157
|
+
return { success: true, data: vaultJson };
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return { success: false, error: error.message };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async saveVault(vaultName, password, vaultData) {
|
|
164
|
+
try {
|
|
165
|
+
// Load existing recovery metadata
|
|
166
|
+
let existingRecoveryMetadata = {};
|
|
167
|
+
if (await this.fileService.vaultExists(vaultName)) {
|
|
168
|
+
try {
|
|
169
|
+
const existingFile = await this.fileService.readVaultFile(vaultName);
|
|
170
|
+
existingRecoveryMetadata = existingFile.recoveryMetadata || {};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.warn('Could not load existing recovery metadata');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const vault = Vault.fromJSON(vaultData, vaultName);
|
|
177
|
+
const salt = CryptographyService.generateSalt();
|
|
178
|
+
const key = CryptographyService.deriveKey(password, salt);
|
|
179
|
+
|
|
180
|
+
const encryptedData = CryptographyService.encrypt(vault.toJSON(), key);
|
|
181
|
+
const vaultFile = {
|
|
182
|
+
...encryptedData,
|
|
183
|
+
salt: salt.toString('hex'),
|
|
184
|
+
recoveryMetadata: existingRecoveryMetadata,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
await this.fileService.writeVaultFile(vaultName, vaultFile);
|
|
188
|
+
return { success: true };
|
|
189
|
+
} catch (error) {
|
|
190
|
+
return { success: false, error: error.message };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async changePassword(vaultName, currentPassword, newPassword) {
|
|
195
|
+
try {
|
|
196
|
+
// Validate new password
|
|
197
|
+
const passwordErrors = validatePasswordStrength(newPassword);
|
|
198
|
+
if (passwordErrors.length > 0) {
|
|
199
|
+
return { success: false, error: passwordErrors[0] };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!(await this.fileService.vaultExists(vaultName))) {
|
|
203
|
+
return { success: false, error: 'Vault not found' };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Create backup
|
|
207
|
+
await this.fileService.createBackup(vaultName);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
// Load and verify current password
|
|
211
|
+
const vaultFile = await this.fileService.readVaultFile(vaultName);
|
|
212
|
+
const currentSalt = Buffer.from(vaultFile.salt, 'hex');
|
|
213
|
+
const currentKey = CryptographyService.deriveKey(
|
|
214
|
+
currentPassword,
|
|
215
|
+
currentSalt
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const encryptedData = {
|
|
219
|
+
encrypted: vaultFile.encrypted,
|
|
220
|
+
authTag: vaultFile.authTag,
|
|
221
|
+
iv: vaultFile.iv,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const vaultData = CryptographyService.decrypt(
|
|
225
|
+
encryptedData,
|
|
226
|
+
currentKey
|
|
227
|
+
);
|
|
228
|
+
const vault = Vault.fromJSON(vaultData, vaultName);
|
|
229
|
+
|
|
230
|
+
// Check password reuse
|
|
231
|
+
const newPasswordHash = CryptographyService.hashPassword(newPassword);
|
|
232
|
+
if (newPassword === currentPassword) {
|
|
233
|
+
return {
|
|
234
|
+
success: false,
|
|
235
|
+
error: 'New password must be different from current password',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (vault.checkPasswordReuse(newPasswordHash)) {
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
error:
|
|
243
|
+
'This password has been used before. Please choose a different password.',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Update vault with password history
|
|
248
|
+
const currentPasswordHash =
|
|
249
|
+
CryptographyService.hashPassword(currentPassword);
|
|
250
|
+
vault.addPasswordToHistory(currentPasswordHash);
|
|
251
|
+
vault.updateLastPasswordChange();
|
|
252
|
+
|
|
253
|
+
// Update recovery metadata
|
|
254
|
+
const updatedRecoveryMetadata =
|
|
255
|
+
await this.#updateRecoveryMetadataForPasswordChange(
|
|
256
|
+
vaultName,
|
|
257
|
+
vaultFile.recoveryMetadata || {},
|
|
258
|
+
currentPassword,
|
|
259
|
+
newPassword
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Re-encrypt with new password
|
|
263
|
+
const newSalt = CryptographyService.generateSalt();
|
|
264
|
+
const newKey = CryptographyService.deriveKey(newPassword, newSalt);
|
|
265
|
+
const newEncryptedData = CryptographyService.encrypt(
|
|
266
|
+
vault.toJSON(),
|
|
267
|
+
newKey
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const finalVaultFile = {
|
|
271
|
+
...newEncryptedData,
|
|
272
|
+
salt: newSalt.toString('hex'),
|
|
273
|
+
recoveryMetadata: updatedRecoveryMetadata,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// Test decryption before saving
|
|
277
|
+
const testKey = CryptographyService.deriveKey(newPassword, newSalt);
|
|
278
|
+
const testEncryptedData = {
|
|
279
|
+
encrypted: finalVaultFile.encrypted,
|
|
280
|
+
authTag: finalVaultFile.authTag,
|
|
281
|
+
iv: finalVaultFile.iv,
|
|
282
|
+
};
|
|
283
|
+
CryptographyService.decrypt(testEncryptedData, testKey);
|
|
284
|
+
|
|
285
|
+
// Atomically write the new vault file
|
|
286
|
+
await this.fileService.atomicWriteVaultFile(vaultName, finalVaultFile);
|
|
287
|
+
|
|
288
|
+
return { success: true };
|
|
289
|
+
} catch (error) {
|
|
290
|
+
// Restore from backup on error
|
|
291
|
+
await this.fileService.restoreFromBackup(vaultName);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error('Error changing password:', error);
|
|
296
|
+
return { success: false, error: 'Failed to change master password' };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async deleteVault(vaultName, confirmationPassword = null) {
|
|
301
|
+
try {
|
|
302
|
+
if (!(await this.fileService.vaultExists(vaultName))) {
|
|
303
|
+
return { success: false, error: 'Vault not found' };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (vaultName === 'default' && !confirmationPassword) {
|
|
307
|
+
return {
|
|
308
|
+
success: false,
|
|
309
|
+
error: 'Cannot delete default vault without password confirmation',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Verify password if provided
|
|
314
|
+
if (confirmationPassword) {
|
|
315
|
+
const verification = await this.verifyPassword(
|
|
316
|
+
vaultName,
|
|
317
|
+
confirmationPassword
|
|
318
|
+
);
|
|
319
|
+
if (!verification.success) {
|
|
320
|
+
return {
|
|
321
|
+
success: false,
|
|
322
|
+
error: 'Invalid password. Vault not deleted.',
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const backupFile = await this.fileService.deleteVault(vaultName);
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
success: true,
|
|
331
|
+
message: `Vault "${vaultName}" has been deleted. A backup was created.`,
|
|
332
|
+
backupFile,
|
|
333
|
+
};
|
|
334
|
+
} catch (error) {
|
|
335
|
+
return { success: false, error: 'Failed to delete vault' };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async createDefaultVault() {
|
|
340
|
+
const defaultPassword = 'changeme123';
|
|
341
|
+
const result = await this.createVault('default', defaultPassword);
|
|
342
|
+
|
|
343
|
+
if (result.success) {
|
|
344
|
+
console.log(
|
|
345
|
+
'Default vault created with recovery key:',
|
|
346
|
+
result.recoveryKey
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Recovery methods
|
|
354
|
+
async generateRecoveryKey(vaultName, masterPassword) {
|
|
355
|
+
try {
|
|
356
|
+
if (!(await this.fileService.vaultExists(vaultName))) {
|
|
357
|
+
return { success: false, error: 'Vault not found' };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Verify password
|
|
361
|
+
const verification = await this.verifyPassword(vaultName, masterPassword);
|
|
362
|
+
if (!verification.success) {
|
|
363
|
+
return { success: false, error: 'Invalid master password' };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const vaultFile = await this.fileService.readVaultFile(vaultName);
|
|
367
|
+
|
|
368
|
+
const keyRecoveryService = new KeyRecoveryService();
|
|
369
|
+
const recoveryKey = await keyRecoveryService.generate();
|
|
370
|
+
const recoveryMetadata = keyRecoveryService.createMetadata(
|
|
371
|
+
vaultName,
|
|
372
|
+
masterPassword,
|
|
373
|
+
recoveryKey
|
|
374
|
+
);
|
|
375
|
+
let newRecoveryMetadata = vaultFile.recoveryMetadata;
|
|
376
|
+
newRecoveryMetadata[keyRecoveryService.getRecoveryMethodId()] =
|
|
377
|
+
recoveryMetadata;
|
|
378
|
+
|
|
379
|
+
// Update vault file
|
|
380
|
+
const updatedVaultFile = {
|
|
381
|
+
...vaultFile,
|
|
382
|
+
recoveryMetadata: newRecoveryMetadata,
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
await this.fileService.writeVaultFile(vaultName, updatedVaultFile);
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
success: true,
|
|
389
|
+
recoveryKey: recoveryKey.data.key,
|
|
390
|
+
createdAt: recoveryMetadata.createdAt,
|
|
391
|
+
};
|
|
392
|
+
} catch (error) {
|
|
393
|
+
console.error('Failed to generate recovery key: ', error);
|
|
394
|
+
return { success: false, error: 'Failed to generate recovery key' };
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async verifyRecoveryKey(vaultName, recoveryKey) {
|
|
399
|
+
try {
|
|
400
|
+
if (!(await this.fileService.vaultExists(vaultName))) {
|
|
401
|
+
return { success: false, error: 'Vault not found' };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const keyRecoveryService = new KeyRecoveryService();
|
|
405
|
+
const methodId = keyRecoveryService.getRecoveryMethodId();
|
|
406
|
+
const vaultFile = await this.fileService.readVaultFile(vaultName);
|
|
407
|
+
|
|
408
|
+
if (
|
|
409
|
+
!vaultFile.recoveryMetadata ||
|
|
410
|
+
!vaultFile.recoveryMetadata[methodId]
|
|
411
|
+
) {
|
|
412
|
+
return {
|
|
413
|
+
success: false,
|
|
414
|
+
error: 'No recovery key found for this vault',
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const recoveryData = new RecoveryData({ data: { key: recoveryKey } });
|
|
419
|
+
return await keyRecoveryService.verify(
|
|
420
|
+
vaultName,
|
|
421
|
+
vaultFile.recoveryMetadata[methodId],
|
|
422
|
+
recoveryData
|
|
423
|
+
);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
console.error(`Failed to verify recovery key: ${error}`);
|
|
426
|
+
return { success: false, error: 'Failed to verify recovery key' };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async loadVaultWithPassword(vaultName, oldPassword) {
|
|
431
|
+
try {
|
|
432
|
+
if (!(await this.fileService.vaultExists(vaultName))) {
|
|
433
|
+
return { success: false, error: 'Vault not found' };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const vaultFile = await this.fileService.readVaultFile(vaultName);
|
|
437
|
+
|
|
438
|
+
// First, try if the old password is actually the current password
|
|
439
|
+
try {
|
|
440
|
+
const loadResult = await this.loadVault(vaultName, oldPassword);
|
|
441
|
+
|
|
442
|
+
if (loadResult.success) {
|
|
443
|
+
loadResult.password = oldPassword;
|
|
444
|
+
return loadResult;
|
|
445
|
+
}
|
|
446
|
+
} catch (error) {
|
|
447
|
+
// Not the current password, try recovery
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const passwordRecoveryService = new PasswordRecoveryService();
|
|
451
|
+
const methodId = passwordRecoveryService.getRecoveryMethodId();
|
|
452
|
+
// Check if we have previous password recovery data
|
|
453
|
+
if (
|
|
454
|
+
!vaultFile.recoveryMetadata ||
|
|
455
|
+
!vaultFile.recoveryMetadata[methodId]
|
|
456
|
+
) {
|
|
457
|
+
return {
|
|
458
|
+
success: false,
|
|
459
|
+
error:
|
|
460
|
+
'No recovery data available for this vault. You need to change your password at least once to enable previous password recovery.',
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
const recoveryData = new RecoveryData({
|
|
466
|
+
data: { password: oldPassword },
|
|
467
|
+
});
|
|
468
|
+
const result = await passwordRecoveryService.recoverMasterPassword(
|
|
469
|
+
vaultName,
|
|
470
|
+
vaultFile.recoveryMetadata[methodId],
|
|
471
|
+
recoveryData
|
|
472
|
+
);
|
|
473
|
+
if (!result.success) {
|
|
474
|
+
return result;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const masterPassword = result.masterPassword;
|
|
478
|
+
const loadResult = await this.loadVault(vaultName, masterPassword);
|
|
479
|
+
|
|
480
|
+
if (loadResult.success) {
|
|
481
|
+
loadResult.password = masterPassword;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return loadResult;
|
|
485
|
+
} catch (error) {
|
|
486
|
+
const message =
|
|
487
|
+
'Failed to recover vault using recovered master password';
|
|
488
|
+
console.error(`${message}: ${error}`);
|
|
489
|
+
return { success: false, error: message };
|
|
490
|
+
}
|
|
491
|
+
} catch (error) {
|
|
492
|
+
const message = 'Failed to recover vault with old password';
|
|
493
|
+
console.error(message, ': ', error);
|
|
494
|
+
return { success: false, error: message };
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async loadVaultWithRecoveryKey(vaultName, recoveryKey) {
|
|
499
|
+
try {
|
|
500
|
+
if (!(await this.fileService.vaultExists(vaultName))) {
|
|
501
|
+
return { success: false, error: 'Vault not found' };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const keyRecoveryService = new KeyRecoveryService();
|
|
505
|
+
const methodId = keyRecoveryService.getRecoveryMethodId();
|
|
506
|
+
const vaultFile = await this.fileService.readVaultFile(vaultName);
|
|
507
|
+
|
|
508
|
+
if (
|
|
509
|
+
!vaultFile.recoveryMetadata ||
|
|
510
|
+
!vaultFile.recoveryMetadata[methodId]
|
|
511
|
+
) {
|
|
512
|
+
return {
|
|
513
|
+
success: false,
|
|
514
|
+
error: 'No recovery key found for this vault',
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const recoveryData = new RecoveryData({ data: { key: recoveryKey } });
|
|
519
|
+
const result = await keyRecoveryService.recoverMasterPassword(
|
|
520
|
+
vaultName,
|
|
521
|
+
vaultFile.recoveryMetadata[methodId],
|
|
522
|
+
recoveryData
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
if (!result.success) {
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const masterPassword = result.masterPassword;
|
|
530
|
+
const loadResult = await this.loadVault(vaultName, masterPassword);
|
|
531
|
+
|
|
532
|
+
if (loadResult.success) {
|
|
533
|
+
loadResult.password = masterPassword;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return loadResult;
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error(`Failed to load vault with recovery key: ${error}`);
|
|
539
|
+
return {
|
|
540
|
+
success: false,
|
|
541
|
+
error: 'Failed to load vault with recovery key',
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async #updateRecoveryMetadataForPasswordChange(
|
|
547
|
+
vaultName,
|
|
548
|
+
existingMetadata,
|
|
549
|
+
currentPassword,
|
|
550
|
+
newPassword
|
|
551
|
+
) {
|
|
552
|
+
const keyRecoveryService = new KeyRecoveryService();
|
|
553
|
+
const passwordRecoveryService = new PasswordRecoveryService();
|
|
554
|
+
|
|
555
|
+
const metadata = [keyRecoveryService, passwordRecoveryService]
|
|
556
|
+
.flatMap((recoveryMethod) => {
|
|
557
|
+
const methodId = recoveryMethod.getRecoveryMethodId();
|
|
558
|
+
const recoveryMetadata = existingMetadata[methodId] || {};
|
|
559
|
+
const result = recoveryMethod.onPasswordChange(
|
|
560
|
+
vaultName,
|
|
561
|
+
recoveryMetadata,
|
|
562
|
+
currentPassword,
|
|
563
|
+
newPassword
|
|
564
|
+
);
|
|
565
|
+
// By default let's return old data, so that we could recover it later if possible
|
|
566
|
+
return {
|
|
567
|
+
methodId,
|
|
568
|
+
data: result.success ? result.metadata : recoveryMetadata,
|
|
569
|
+
};
|
|
570
|
+
})
|
|
571
|
+
.reduce((obj, item) => {
|
|
572
|
+
obj[item.methodId] = item.data;
|
|
573
|
+
return obj;
|
|
574
|
+
}, {});
|
|
575
|
+
|
|
576
|
+
return metadata;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { VaultFileService } from './VaultFileService.js';
|
|
2
|
+
import { CryptographyService } from './CryptographyService.js';
|
|
3
|
+
import { Vault } from '../models/Vault.js';
|
|
4
|
+
|
|
5
|
+
export class VaultSettingsService {
|
|
6
|
+
constructor(vaultDirectory) {
|
|
7
|
+
this.fileService = new VaultFileService(vaultDirectory);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async updateVaultSettings(vaultName, vaultPassword, newSettings) {
|
|
11
|
+
try {
|
|
12
|
+
if (!(await this.fileService.vaultExists(vaultName))) {
|
|
13
|
+
return { success: false, error: 'Vault not found' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const vaultFile = await this.fileService.readVaultFile(vaultName);
|
|
17
|
+
const salt = Buffer.from(vaultFile.salt, 'hex');
|
|
18
|
+
const key = CryptographyService.deriveKey(vaultPassword, salt);
|
|
19
|
+
|
|
20
|
+
let vaultData;
|
|
21
|
+
try {
|
|
22
|
+
const encryptedData = {
|
|
23
|
+
encrypted: vaultFile.encrypted,
|
|
24
|
+
authTag: vaultFile.authTag,
|
|
25
|
+
iv: vaultFile.iv,
|
|
26
|
+
};
|
|
27
|
+
vaultData = CryptographyService.decrypt(encryptedData, key);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return { success: false, error: 'Invalid password' };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const vault = Vault.fromJSON(vaultData, vaultName);
|
|
33
|
+
vault.updateSettings(newSettings);
|
|
34
|
+
|
|
35
|
+
const newEncryptedData = CryptographyService.encrypt(vault.toJSON(), key);
|
|
36
|
+
const finalData = {
|
|
37
|
+
...newEncryptedData,
|
|
38
|
+
salt: salt.toString('hex'),
|
|
39
|
+
recoveryMetadata: vaultFile.recoveryMetadata || {},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await this.fileService.writeVaultFile(vaultName, finalData);
|
|
43
|
+
return { success: true };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Error updating vault settings:', error);
|
|
46
|
+
return { success: false, error: 'Failed to update settings' };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getVaultSettings(vaultName, vaultPassword) {
|
|
51
|
+
try {
|
|
52
|
+
if (!(await this.fileService.vaultExists(vaultName))) {
|
|
53
|
+
return { success: false, error: 'Vault not found' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const vaultFile = await this.fileService.readVaultFile(vaultName);
|
|
57
|
+
const salt = Buffer.from(vaultFile.salt, 'hex');
|
|
58
|
+
const key = CryptographyService.deriveKey(vaultPassword, salt);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const encryptedData = {
|
|
62
|
+
encrypted: vaultFile.encrypted,
|
|
63
|
+
authTag: vaultFile.authTag,
|
|
64
|
+
iv: vaultFile.iv,
|
|
65
|
+
};
|
|
66
|
+
const vaultData = CryptographyService.decrypt(encryptedData, key);
|
|
67
|
+
const vault = Vault.fromJSON(vaultData, vaultName);
|
|
68
|
+
|
|
69
|
+
return { success: true, settings: vault.settings };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return { success: false, error: 'Invalid password' };
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Error getting vault settings:', error);
|
|
75
|
+
return { success: false, error: 'Failed to get settings' };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|