i18ntk 2.4.0 → 2.5.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/README.md +6 -5
- package/main/manage/commands/FixerCommand.js +97 -97
- package/main/manage/managers/DebugMenu.js +10 -9
- package/package.json +61 -32
- package/runtime/index.js +14 -8
- package/utils/admin-auth.js +594 -576
- package/utils/config-manager.js +72 -72
- package/utils/env-manager.js +117 -26
- package/utils/i18n-helper.js +50 -49
- package/utils/json-output.js +13 -12
- package/utils/logger.js +7 -6
- package/utils/prompt-helper.js +44 -41
- package/utils/secure-errors.js +156 -154
- package/utils/security.js +235 -233
- package/utils/setup-enforcer.js +110 -109
- package/utils/terminal-icons.js +164 -163
- package/settings/i18ntk-config.json +0 -283
- package/utils/admin-pin.js +0 -520
- package/utils/arg-parser.js +0 -40
- package/utils/cli-args.js +0 -210
- package/utils/mini-commander.js +0 -179
- package/utils/missing-key-validator.js +0 -858
- package/utils/path-utils.js +0 -33
- package/utils/performance-optimizer.js +0 -246
- package/utils/prompt-new.js +0 -55
- package/utils/promptPin.js +0 -76
- package/utils/safe-json.js +0 -40
- package/utils/secure-backup.js +0 -340
- package/utils/security-check-improved.js +0 -393
- package/utils/security-config.js +0 -239
- package/utils/setup-validator.js +0 -717
- package/utils/ultra-performance-optimizer.js +0 -352
package/utils/secure-backup.js
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const fsp = fs.promises;
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const zlib = require('zlib');
|
|
6
|
-
const { promisify } = require('util');
|
|
7
|
-
const { existsSync, mkdirSync } = require('fs');
|
|
8
|
-
const { EncryptionError, ValidationError } = require('./secure-errors');
|
|
9
|
-
|
|
10
|
-
// Promisify functions
|
|
11
|
-
const gzip = promisify(zlib.gzip);
|
|
12
|
-
const gunzip = promisify(zlib.gunzip);
|
|
13
|
-
|
|
14
|
-
// Constants
|
|
15
|
-
const SALT_LENGTH = 32;
|
|
16
|
-
const IV_LENGTH = 16;
|
|
17
|
-
const KEY_LENGTH = 32;
|
|
18
|
-
const ITERATIONS = 100000;
|
|
19
|
-
const ALGORITHM = 'aes-256-gcm';
|
|
20
|
-
const BACKUP_HEADER = 'I18NTK_BACKUP';
|
|
21
|
-
const BACKUP_VERSION = 1;
|
|
22
|
-
|
|
23
|
-
class SecureBackupManager {
|
|
24
|
-
constructor(config = {}) {
|
|
25
|
-
this.config = {
|
|
26
|
-
backupDir: path.join(process.cwd(), 'backups'),
|
|
27
|
-
maxBackups: 10,
|
|
28
|
-
compress: true,
|
|
29
|
-
...config
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Create backup directory synchronously in constructor
|
|
33
|
-
if (!existsSync(this.config.backupDir)) {
|
|
34
|
-
mkdirSync(this.config.backupDir, { recursive: true });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Derive a key from a password using PBKDF2
|
|
40
|
-
*/
|
|
41
|
-
async deriveKey(password, salt) {
|
|
42
|
-
return new Promise((resolve, reject) => {
|
|
43
|
-
crypto.pbkdf2(
|
|
44
|
-
password,
|
|
45
|
-
salt,
|
|
46
|
-
ITERATIONS,
|
|
47
|
-
KEY_LENGTH,
|
|
48
|
-
'sha512',
|
|
49
|
-
(err, derivedKey) => {
|
|
50
|
-
if (err) {
|
|
51
|
-
reject(new EncryptionError('Key derivation failed', { error: err.message }));
|
|
52
|
-
} else {
|
|
53
|
-
resolve(derivedKey);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Generate a random salt
|
|
62
|
-
*/
|
|
63
|
-
generateSalt() {
|
|
64
|
-
return crypto.randomBytes(SALT_LENGTH);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Encrypt data with a password
|
|
69
|
-
*/
|
|
70
|
-
async encryptData(data, password) {
|
|
71
|
-
try {
|
|
72
|
-
// Generate a random salt
|
|
73
|
-
const salt = this.generateSalt();
|
|
74
|
-
|
|
75
|
-
// Derive key from password
|
|
76
|
-
const key = await this.deriveKey(password, salt);
|
|
77
|
-
|
|
78
|
-
// Generate a random IV
|
|
79
|
-
const iv = crypto.randomBytes(IV_LENGTH);
|
|
80
|
-
|
|
81
|
-
// Create cipher
|
|
82
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
83
|
-
|
|
84
|
-
// Encrypt the data
|
|
85
|
-
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
86
|
-
encrypted += cipher.final('hex');
|
|
87
|
-
|
|
88
|
-
// Get the auth tag
|
|
89
|
-
const authTag = cipher.getAuthTag();
|
|
90
|
-
|
|
91
|
-
// Return the encrypted data with metadata
|
|
92
|
-
return {
|
|
93
|
-
encrypted,
|
|
94
|
-
iv: iv.toString('hex'),
|
|
95
|
-
salt: salt.toString('hex'),
|
|
96
|
-
authTag: authTag.toString('hex'),
|
|
97
|
-
algorithm: ALGORITHM,
|
|
98
|
-
version: BACKUP_VERSION
|
|
99
|
-
};
|
|
100
|
-
} catch (error) {
|
|
101
|
-
throw new EncryptionError('Encryption failed', { cause: error });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Decrypt data with a password
|
|
107
|
-
*/
|
|
108
|
-
async decryptData(encryptedData, password) {
|
|
109
|
-
try {
|
|
110
|
-
// Parse the encrypted data
|
|
111
|
-
const { encrypted, iv, salt, authTag, version } = encryptedData;
|
|
112
|
-
|
|
113
|
-
// Validate version
|
|
114
|
-
if (version !== BACKUP_VERSION) {
|
|
115
|
-
throw new ValidationError('Unsupported backup version');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Convert from hex
|
|
119
|
-
const ivBuffer = Buffer.from(iv, 'hex');
|
|
120
|
-
const saltBuffer = Buffer.from(salt, 'hex');
|
|
121
|
-
const authTagBuffer = Buffer.from(authTag, 'hex');
|
|
122
|
-
|
|
123
|
-
// Derive the key
|
|
124
|
-
const key = await this.deriveKey(password, saltBuffer);
|
|
125
|
-
|
|
126
|
-
// Create decipher
|
|
127
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, ivBuffer);
|
|
128
|
-
decipher.setAuthTag(authTagBuffer);
|
|
129
|
-
|
|
130
|
-
// Decrypt the data
|
|
131
|
-
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
132
|
-
decrypted += decipher.final('utf8');
|
|
133
|
-
|
|
134
|
-
return decrypted;
|
|
135
|
-
} catch (error) {
|
|
136
|
-
throw new EncryptionError('Decryption failed', { cause: error });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Create a secure backup
|
|
142
|
-
*/
|
|
143
|
-
async createBackup(data, password, options = {}) {
|
|
144
|
-
try {
|
|
145
|
-
// Validate input
|
|
146
|
-
if (!data) {
|
|
147
|
-
throw new ValidationError('No data provided for backup');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!password) {
|
|
151
|
-
throw new ValidationError('Password is required for backup');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Stringify data if it's an object
|
|
155
|
-
const dataString = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
156
|
-
|
|
157
|
-
// Compress the data if enabled
|
|
158
|
-
let processedData = dataString;
|
|
159
|
-
if (this.config.compress) {
|
|
160
|
-
processedData = await gzip(dataString);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Encrypt the data
|
|
164
|
-
const encryptedData = await this.encryptData(
|
|
165
|
-
processedData.toString('base64'),
|
|
166
|
-
password
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
// Add metadata
|
|
170
|
-
const backupData = {
|
|
171
|
-
header: BACKUP_HEADER,
|
|
172
|
-
version: BACKUP_VERSION,
|
|
173
|
-
timestamp: new Date().toISOString(),
|
|
174
|
-
compressed: this.config.compress,
|
|
175
|
-
...encryptedData
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// Create backup filename with timestamp
|
|
179
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
180
|
-
const backupName = `backup-${timestamp}.i18ntk`;
|
|
181
|
-
const backupPath = path.join(this.config.backupDir, backupName);
|
|
182
|
-
|
|
183
|
-
// Write the backup file
|
|
184
|
-
await fsp.writeFile(backupPath, JSON.stringify(backupData, null, 2), 'utf8');
|
|
185
|
-
|
|
186
|
-
// Clean up old backups if we've exceeded the limit
|
|
187
|
-
await this.cleanupOldBackups();
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
success: true,
|
|
191
|
-
backupPath,
|
|
192
|
-
backupName,
|
|
193
|
-
timestamp: backupData.timestamp,
|
|
194
|
-
size: JSON.stringify(backupData).length
|
|
195
|
-
};
|
|
196
|
-
} catch (error) {
|
|
197
|
-
throw new EncryptionError('Backup creation failed', { cause: error });
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Restore a backup
|
|
203
|
-
*/
|
|
204
|
-
async restoreBackup(backupPath, password) {
|
|
205
|
-
try {
|
|
206
|
-
// Read the backup file
|
|
207
|
-
const backupData = JSON.parse(await fsp.readFile(backupPath, 'utf8'));
|
|
208
|
-
|
|
209
|
-
// Validate the backup
|
|
210
|
-
if (backupData.header !== BACKUP_HEADER) {
|
|
211
|
-
throw new ValidationError('Invalid backup file');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Decrypt the data
|
|
215
|
-
const decryptedData = await this.decryptData(backupData, password);
|
|
216
|
-
|
|
217
|
-
// Decompress if needed
|
|
218
|
-
let processedData = Buffer.from(decryptedData, 'base64');
|
|
219
|
-
if (backupData.compressed) {
|
|
220
|
-
processedData = await gunzip(processedData);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Parse the data if it's JSON
|
|
224
|
-
try {
|
|
225
|
-
return JSON.parse(processedData.toString('utf8'));
|
|
226
|
-
} catch {
|
|
227
|
-
return processedData.toString('utf8');
|
|
228
|
-
}
|
|
229
|
-
} catch (error) {
|
|
230
|
-
throw new EncryptionError('Backup restoration failed', { cause: error });
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* List available backups
|
|
236
|
-
*/
|
|
237
|
-
async listBackups() {
|
|
238
|
-
try {
|
|
239
|
-
// Read the backup directory
|
|
240
|
-
const files = (await fsp.readdir(this.config.backupDir, { withFileTypes: true }))
|
|
241
|
-
.filter(dirent => dirent.isFile())
|
|
242
|
-
.map(dirent => dirent.name);
|
|
243
|
-
|
|
244
|
-
// Filter for backup files
|
|
245
|
-
const backupFiles = files.filter(file => file.endsWith('.i18ntk'));
|
|
246
|
-
|
|
247
|
-
// Get file stats
|
|
248
|
-
const backups = await Promise.all(
|
|
249
|
-
backupFiles.map(async (file) => {
|
|
250
|
-
const filePath = path.join(this.config.backupDir, file);
|
|
251
|
-
const stat = await fsp.stat(filePath);
|
|
252
|
-
if (stat.isDirectory()) {
|
|
253
|
-
throw new Error('Backup path is a directory');
|
|
254
|
-
}
|
|
255
|
-
return {
|
|
256
|
-
name: file,
|
|
257
|
-
path: filePath,
|
|
258
|
-
size: stat.size,
|
|
259
|
-
createdAt: stat.birthtime,
|
|
260
|
-
modifiedAt: stat.mtime
|
|
261
|
-
};
|
|
262
|
-
})
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
// Sort by creation date (newest first)
|
|
266
|
-
return backups.sort((a, b) => b.createdAt - a.createdAt);
|
|
267
|
-
} catch (error) {
|
|
268
|
-
throw new Error(`Failed to list backups: ${error.message}`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Clean up old backups
|
|
274
|
-
*/
|
|
275
|
-
async cleanupOldBackups() {
|
|
276
|
-
try {
|
|
277
|
-
const backups = await this.listBackups();
|
|
278
|
-
|
|
279
|
-
// If we haven't exceeded the limit, do nothing
|
|
280
|
-
if (backups.length <= this.config.maxBackups) {
|
|
281
|
-
return [];
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Sort by creation date (oldest first)
|
|
285
|
-
const sortedBackups = [...backups].sort((a, b) => a.createdAt - b.createdAt);
|
|
286
|
-
|
|
287
|
-
// Determine how many to delete
|
|
288
|
-
const toDelete = sortedBackups.slice(0, backups.length - this.config.maxBackups);
|
|
289
|
-
|
|
290
|
-
// Delete the old backups
|
|
291
|
-
const deleted = [];
|
|
292
|
-
for (const backup of toDelete) {
|
|
293
|
-
try {
|
|
294
|
-
await fsp.unlink(backup.path);
|
|
295
|
-
deleted.push(backup.name);
|
|
296
|
-
} catch (error) {
|
|
297
|
-
console.error(`Failed to delete backup ${backup.name}:`, error);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return deleted;
|
|
302
|
-
} catch (error) {
|
|
303
|
-
console.error('Failed to clean up old backups:', error);
|
|
304
|
-
return [];
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Verify a backup password
|
|
310
|
-
*/
|
|
311
|
-
async verifyBackup(backupPath, password) {
|
|
312
|
-
try {
|
|
313
|
-
// Read the backup file
|
|
314
|
-
const backupData = JSON.parse(await fsp.readFile(backupPath, 'utf8'));
|
|
315
|
-
|
|
316
|
-
// Try to decrypt a small part to verify the password
|
|
317
|
-
const testData = await this.decryptData(backupData, password);
|
|
318
|
-
|
|
319
|
-
// If we got here, the password is correct
|
|
320
|
-
return {
|
|
321
|
-
valid: true,
|
|
322
|
-
timestamp: backupData.timestamp,
|
|
323
|
-
compressed: backupData.compressed
|
|
324
|
-
};
|
|
325
|
-
} catch (error) {
|
|
326
|
-
// If decryption failed, the password is likely incorrect
|
|
327
|
-
if (error instanceof EncryptionError) {
|
|
328
|
-
return { valid: false, reason: 'Invalid password or corrupted backup' };
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// For other errors, rethrow
|
|
332
|
-
throw error;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
module.exports = {
|
|
338
|
-
SecureBackupManager,
|
|
339
|
-
createBackupManager: (config) => new SecureBackupManager(config)
|
|
340
|
-
};
|