profile-manager-secure 1.0.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/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/cli.js.map +1 -0
- package/dist/frontend/assets/index-D0IIAYWB.css +1 -0
- package/dist/frontend/assets/index-D9Y0_s3b.js +273 -0
- package/dist/frontend/index.html +26 -0
- package/dist/middleware/auth.middleware.d.ts +8 -0
- package/dist/middleware/auth.middleware.js +51 -0
- package/dist/middleware/auth.middleware.js.map +1 -0
- package/dist/middleware/error.middleware.d.ts +2 -0
- package/dist/middleware/error.middleware.js +11 -0
- package/dist/middleware/error.middleware.js.map +1 -0
- package/dist/modules/auth/auth.controller.d.ts +1 -0
- package/dist/modules/auth/auth.controller.js +175 -0
- package/dist/modules/auth/auth.controller.js.map +1 -0
- package/dist/modules/credential/credential.controller.d.ts +1 -0
- package/dist/modules/credential/credential.controller.js +263 -0
- package/dist/modules/credential/credential.controller.js.map +1 -0
- package/dist/modules/proxy/proxy.controller.d.ts +1 -0
- package/dist/modules/proxy/proxy.controller.js +270 -0
- package/dist/modules/proxy/proxy.controller.js.map +1 -0
- package/dist/modules/proxy/proxy.service.d.ts +28 -0
- package/dist/modules/proxy/proxy.service.js +185 -0
- package/dist/modules/proxy/proxy.service.js.map +1 -0
- package/dist/modules/tailscale/tailscale.controller.d.ts +1 -0
- package/dist/modules/tailscale/tailscale.controller.js +149 -0
- package/dist/modules/tailscale/tailscale.controller.js.map +1 -0
- package/dist/modules/tailscale/tailscale.service.d.ts +96 -0
- package/dist/modules/tailscale/tailscale.service.js +561 -0
- package/dist/modules/tailscale/tailscale.service.js.map +1 -0
- package/dist/modules/totp/totp.controller.d.ts +1 -0
- package/dist/modules/totp/totp.controller.js +41 -0
- package/dist/modules/totp/totp.controller.js.map +1 -0
- package/dist/modules/totp/totp.service.d.ts +26 -0
- package/dist/modules/totp/totp.service.js +96 -0
- package/dist/modules/totp/totp.service.js.map +1 -0
- package/dist/modules/totp/totp.service.test.d.ts +1 -0
- package/dist/modules/totp/totp.service.test.js +41 -0
- package/dist/modules/totp/totp.service.test.js.map +1 -0
- package/dist/modules/vault/apikey.service.d.ts +27 -0
- package/dist/modules/vault/apikey.service.js +87 -0
- package/dist/modules/vault/apikey.service.js.map +1 -0
- package/dist/modules/vault/constants.d.ts +2 -0
- package/dist/modules/vault/constants.js +24 -0
- package/dist/modules/vault/constants.js.map +1 -0
- package/dist/modules/vault/types.d.ts +73 -0
- package/dist/modules/vault/types.js +2 -0
- package/dist/modules/vault/types.js.map +1 -0
- package/dist/modules/vault/vault-migration.service.d.ts +21 -0
- package/dist/modules/vault/vault-migration.service.js +99 -0
- package/dist/modules/vault/vault-migration.service.js.map +1 -0
- package/dist/modules/vault/vault-storage.service.d.ts +22 -0
- package/dist/modules/vault/vault-storage.service.js +76 -0
- package/dist/modules/vault/vault-storage.service.js.map +1 -0
- package/dist/modules/vault/vault.service.d.ts +66 -0
- package/dist/modules/vault/vault.service.js +229 -0
- package/dist/modules/vault/vault.service.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +67 -0
- package/dist/server.js.map +1 -0
- package/dist/utils/crypto.d.ts +20 -0
- package/dist/utils/crypto.js +54 -0
- package/dist/utils/crypto.js.map +1 -0
- package/dist/utils/crypto.test.d.ts +1 -0
- package/dist/utils/crypto.test.js +66 -0
- package/dist/utils/crypto.test.js.map +1 -0
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +12 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare class VaultStorageService {
|
|
2
|
+
/**
|
|
3
|
+
* Check if the vault file exists
|
|
4
|
+
*/
|
|
5
|
+
exists(vaultPath: string): Promise<boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* Get the password hint from the raw vault file without decrypting its contents
|
|
8
|
+
*/
|
|
9
|
+
getHint(vaultPath: string): Promise<string | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Read raw content from the vault file
|
|
12
|
+
*/
|
|
13
|
+
read(vaultPath: string): Promise<{
|
|
14
|
+
salt: string;
|
|
15
|
+
ciphertext: string;
|
|
16
|
+
hint?: string;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Write content to the vault file atomically after creating a backup of the existing file
|
|
20
|
+
*/
|
|
21
|
+
write(vaultPath: string, fileContent: string): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
export class VaultStorageService {
|
|
5
|
+
/**
|
|
6
|
+
* Check if the vault file exists
|
|
7
|
+
*/
|
|
8
|
+
async exists(vaultPath) {
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(vaultPath);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get the password hint from the raw vault file without decrypting its contents
|
|
19
|
+
*/
|
|
20
|
+
async getHint(vaultPath) {
|
|
21
|
+
if (!(await this.exists(vaultPath))) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const rawContent = await fs.readFile(vaultPath, 'utf8');
|
|
26
|
+
const parsed = JSON.parse(rawContent);
|
|
27
|
+
return parsed.hint || null;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
logger.error('Failed to read password hint from vault file:', error.message);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read raw content from the vault file
|
|
36
|
+
*/
|
|
37
|
+
async read(vaultPath) {
|
|
38
|
+
try {
|
|
39
|
+
const rawContent = await fs.readFile(vaultPath, 'utf8');
|
|
40
|
+
return JSON.parse(rawContent);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
logger.error(`Failed to read vault file at ${vaultPath}:`, error.message);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write content to the vault file atomically after creating a backup of the existing file
|
|
49
|
+
*/
|
|
50
|
+
async write(vaultPath, fileContent) {
|
|
51
|
+
try {
|
|
52
|
+
// 1. Create a backup of the existing vault file if it exists
|
|
53
|
+
if (await this.exists(vaultPath)) {
|
|
54
|
+
const backupPath = `${vaultPath}.bak`;
|
|
55
|
+
try {
|
|
56
|
+
await fs.copyFile(vaultPath, backupPath);
|
|
57
|
+
logger.info(`Vault backup created at ${backupPath}`);
|
|
58
|
+
}
|
|
59
|
+
catch (backupError) {
|
|
60
|
+
logger.warn(`Failed to create vault backup: ${backupError.message}. Proceeding with save.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// 2. Write to a temporary file and rename it to target path for atomic update
|
|
64
|
+
const dir = path.dirname(vaultPath);
|
|
65
|
+
await fs.mkdir(dir, { recursive: true });
|
|
66
|
+
const tmpPath = `${vaultPath}.tmp`;
|
|
67
|
+
await fs.writeFile(tmpPath, fileContent, 'utf8');
|
|
68
|
+
await fs.rename(tmpPath, vaultPath);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.error(`Failed to write vault file at ${vaultPath}:`, error.message);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=vault-storage.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-storage.service.js","sourceRoot":"","sources":["../../../src/modules/vault/vault-storage.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Category, Platform, CustomField, Credential, ApiKey, Proxy, VaultData } from './types.js';
|
|
2
|
+
export type { Category, Platform, CustomField, Credential, ApiKey, Proxy, VaultData };
|
|
3
|
+
declare class VaultService {
|
|
4
|
+
private get vaultPath();
|
|
5
|
+
private isUnlocked;
|
|
6
|
+
private encryptionKey;
|
|
7
|
+
private vaultData;
|
|
8
|
+
private apiKeysService;
|
|
9
|
+
private storageService;
|
|
10
|
+
private migrationService;
|
|
11
|
+
constructor();
|
|
12
|
+
/**
|
|
13
|
+
* Check if vault file exists
|
|
14
|
+
*/
|
|
15
|
+
exists(): Promise<boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* Check if the vault is unlocked
|
|
18
|
+
*/
|
|
19
|
+
unlocked(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Get decrypted vault data (only if unlocked)
|
|
22
|
+
*/
|
|
23
|
+
getData(): VaultData;
|
|
24
|
+
/**
|
|
25
|
+
* Initialize a new vault with a master password
|
|
26
|
+
*/
|
|
27
|
+
initialize(password: string, hint?: string): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Unlock the vault with the master password
|
|
30
|
+
*/
|
|
31
|
+
unlock(password: string): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Lock the vault and wipe memory
|
|
34
|
+
*/
|
|
35
|
+
lock(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Get password hint from vault file without unlocking
|
|
38
|
+
*/
|
|
39
|
+
getHint(): Promise<string | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Save the in-memory vault data back to disk encrypted
|
|
42
|
+
*/
|
|
43
|
+
save(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Change master password and re-encrypt vault database
|
|
46
|
+
*/
|
|
47
|
+
changePassword(currentPassword: string, newPassword: string, hint?: string): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Get vault stats and storage file path info
|
|
50
|
+
*/
|
|
51
|
+
getVaultInfo(): {
|
|
52
|
+
path: string;
|
|
53
|
+
credentialsCount: number;
|
|
54
|
+
categoriesCount: number;
|
|
55
|
+
};
|
|
56
|
+
getApiKeys(): ApiKey[];
|
|
57
|
+
createApiKey(name: string): Promise<string>;
|
|
58
|
+
toggleApiKey(id: string, enabled: boolean): Promise<void>;
|
|
59
|
+
deleteApiKey(id: string): Promise<void>;
|
|
60
|
+
validateApiKey(key: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Automatically initializes the vault with default password '180823' if it does not exist
|
|
63
|
+
*/
|
|
64
|
+
autoInitializeIfMissing(): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
export declare const vaultService: VaultService;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import { deriveKey, encrypt, decrypt, generateSalt, hashPassword } from '../../utils/crypto.js';
|
|
4
|
+
import { logger } from '../../utils/logger.js';
|
|
5
|
+
import { defaultPlatforms } from './constants.js';
|
|
6
|
+
import { ApiKeyService } from './apikey.service.js';
|
|
7
|
+
import { VaultStorageService } from './vault-storage.service.js';
|
|
8
|
+
import { VaultMigrationService } from './vault-migration.service.js';
|
|
9
|
+
class VaultService {
|
|
10
|
+
get vaultPath() {
|
|
11
|
+
const defaultDir = path.join(os.homedir(), '.account-manager');
|
|
12
|
+
return process.env.DB_PATH || path.join(defaultDir, 'vault.enc');
|
|
13
|
+
}
|
|
14
|
+
isUnlocked = false;
|
|
15
|
+
encryptionKey = null;
|
|
16
|
+
vaultData = null;
|
|
17
|
+
apiKeysService;
|
|
18
|
+
storageService;
|
|
19
|
+
migrationService;
|
|
20
|
+
constructor() {
|
|
21
|
+
this.storageService = new VaultStorageService();
|
|
22
|
+
this.migrationService = new VaultMigrationService();
|
|
23
|
+
this.apiKeysService = new ApiKeyService(() => this.getData(), () => this.save(), () => this.unlocked());
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Check if vault file exists
|
|
27
|
+
*/
|
|
28
|
+
async exists() {
|
|
29
|
+
return this.storageService.exists(this.vaultPath);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if the vault is unlocked
|
|
33
|
+
*/
|
|
34
|
+
unlocked() {
|
|
35
|
+
return this.isUnlocked;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get decrypted vault data (only if unlocked)
|
|
39
|
+
*/
|
|
40
|
+
getData() {
|
|
41
|
+
if (!this.isUnlocked || !this.vaultData) {
|
|
42
|
+
throw new Error('Vault is locked');
|
|
43
|
+
}
|
|
44
|
+
return this.vaultData;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Initialize a new vault with a master password
|
|
48
|
+
*/
|
|
49
|
+
async initialize(password, hint) {
|
|
50
|
+
if (await this.exists()) {
|
|
51
|
+
throw new Error('Vault already exists');
|
|
52
|
+
}
|
|
53
|
+
logger.info('Initializing new vault...');
|
|
54
|
+
const salt = generateSalt();
|
|
55
|
+
const storageSalt = generateSalt();
|
|
56
|
+
const passwordHash = hashPassword(password, storageSalt);
|
|
57
|
+
const initialData = {
|
|
58
|
+
schemaVersion: 2, // Initialize with latest version
|
|
59
|
+
user: {
|
|
60
|
+
password_hash: passwordHash,
|
|
61
|
+
password_salt: storageSalt,
|
|
62
|
+
password_hint: hint
|
|
63
|
+
},
|
|
64
|
+
categories: [],
|
|
65
|
+
credentials: [],
|
|
66
|
+
platforms: defaultPlatforms
|
|
67
|
+
};
|
|
68
|
+
// Derive key and encrypt
|
|
69
|
+
const key = deriveKey(password, salt);
|
|
70
|
+
const ciphertext = encrypt(JSON.stringify(initialData), key);
|
|
71
|
+
// Save file
|
|
72
|
+
const fileContent = JSON.stringify({
|
|
73
|
+
salt,
|
|
74
|
+
ciphertext,
|
|
75
|
+
hint
|
|
76
|
+
});
|
|
77
|
+
await this.storageService.write(this.vaultPath, fileContent);
|
|
78
|
+
// Auto-unlock
|
|
79
|
+
this.encryptionKey = key;
|
|
80
|
+
this.vaultData = initialData;
|
|
81
|
+
this.isUnlocked = true;
|
|
82
|
+
logger.info('Vault initialized and unlocked successfully');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Unlock the vault with the master password
|
|
86
|
+
*/
|
|
87
|
+
async unlock(password) {
|
|
88
|
+
if (!(await this.exists())) {
|
|
89
|
+
throw new Error('Vault has not been initialized');
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const { salt, ciphertext } = await this.storageService.read(this.vaultPath);
|
|
93
|
+
const key = deriveKey(password, salt);
|
|
94
|
+
const decryptedDataRaw = decrypt(ciphertext, key);
|
|
95
|
+
const data = JSON.parse(decryptedDataRaw);
|
|
96
|
+
// Verify password hash for double safety
|
|
97
|
+
const computedHash = hashPassword(password, data.user.password_salt);
|
|
98
|
+
if (computedHash !== data.user.password_hash) {
|
|
99
|
+
logger.warn('Password hash verification failed');
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
this.encryptionKey = key;
|
|
103
|
+
this.vaultData = data;
|
|
104
|
+
this.isUnlocked = true;
|
|
105
|
+
// Run sequential migrations
|
|
106
|
+
const { migratedData, needsSave } = await this.migrationService.migrate(this.vaultData);
|
|
107
|
+
this.vaultData = migratedData;
|
|
108
|
+
if (needsSave) {
|
|
109
|
+
await this.save();
|
|
110
|
+
}
|
|
111
|
+
logger.info('Vault unlocked successfully');
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.error('Failed to unlock vault:', error.message);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Lock the vault and wipe memory
|
|
121
|
+
*/
|
|
122
|
+
lock() {
|
|
123
|
+
this.isUnlocked = false;
|
|
124
|
+
this.encryptionKey = null;
|
|
125
|
+
this.vaultData = null;
|
|
126
|
+
logger.info('Vault locked');
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get password hint from vault file without unlocking
|
|
130
|
+
*/
|
|
131
|
+
async getHint() {
|
|
132
|
+
return this.storageService.getHint(this.vaultPath);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Save the in-memory vault data back to disk encrypted
|
|
136
|
+
*/
|
|
137
|
+
async save() {
|
|
138
|
+
if (!this.isUnlocked || !this.encryptionKey || !this.vaultData) {
|
|
139
|
+
throw new Error('Vault is locked, cannot save');
|
|
140
|
+
}
|
|
141
|
+
const { salt } = await this.storageService.read(this.vaultPath);
|
|
142
|
+
const ciphertext = encrypt(JSON.stringify(this.vaultData), this.encryptionKey);
|
|
143
|
+
const fileContent = JSON.stringify({
|
|
144
|
+
salt,
|
|
145
|
+
ciphertext,
|
|
146
|
+
hint: this.vaultData.user.password_hint
|
|
147
|
+
});
|
|
148
|
+
await this.storageService.write(this.vaultPath, fileContent);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Change master password and re-encrypt vault database
|
|
152
|
+
*/
|
|
153
|
+
async changePassword(currentPassword, newPassword, hint) {
|
|
154
|
+
if (!this.isUnlocked || !this.encryptionKey || !this.vaultData) {
|
|
155
|
+
throw new Error('Vault is locked');
|
|
156
|
+
}
|
|
157
|
+
// 1. Verify current password hash
|
|
158
|
+
const computedHash = hashPassword(currentPassword, this.vaultData.user.password_salt);
|
|
159
|
+
if (computedHash !== this.vaultData.user.password_hash) {
|
|
160
|
+
logger.warn('Change password failed: current password incorrect');
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
// 2. Generate new storage salt and hash new password
|
|
164
|
+
const newStorageSalt = generateSalt();
|
|
165
|
+
const newPasswordHash = hashPassword(newPassword, newStorageSalt);
|
|
166
|
+
// 3. Update vaultData user info
|
|
167
|
+
this.vaultData.user = {
|
|
168
|
+
password_hash: newPasswordHash,
|
|
169
|
+
password_salt: newStorageSalt,
|
|
170
|
+
password_hint: hint
|
|
171
|
+
};
|
|
172
|
+
// 4. Generate new salt for file encryption and derive new key
|
|
173
|
+
const newSalt = generateSalt();
|
|
174
|
+
const newKey = deriveKey(newPassword, newSalt);
|
|
175
|
+
// 5. Encrypt vault data with the new key
|
|
176
|
+
const ciphertext = encrypt(JSON.stringify(this.vaultData), newKey);
|
|
177
|
+
const fileContent = JSON.stringify({
|
|
178
|
+
salt: newSalt,
|
|
179
|
+
ciphertext,
|
|
180
|
+
hint
|
|
181
|
+
});
|
|
182
|
+
// 6. Save to disk atomically
|
|
183
|
+
await this.storageService.write(this.vaultPath, fileContent);
|
|
184
|
+
// 7. Update in-memory keys
|
|
185
|
+
this.encryptionKey = newKey;
|
|
186
|
+
logger.info('Master password changed successfully');
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get vault stats and storage file path info
|
|
191
|
+
*/
|
|
192
|
+
getVaultInfo() {
|
|
193
|
+
if (!this.isUnlocked || !this.vaultData) {
|
|
194
|
+
throw new Error('Vault is locked');
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
path: this.vaultPath,
|
|
198
|
+
credentialsCount: this.vaultData.credentials.length,
|
|
199
|
+
categoriesCount: this.vaultData.categories.length
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
getApiKeys() {
|
|
203
|
+
return this.apiKeysService.getApiKeys();
|
|
204
|
+
}
|
|
205
|
+
async createApiKey(name) {
|
|
206
|
+
return this.apiKeysService.createApiKey(name);
|
|
207
|
+
}
|
|
208
|
+
async toggleApiKey(id, enabled) {
|
|
209
|
+
return this.apiKeysService.toggleApiKey(id, enabled);
|
|
210
|
+
}
|
|
211
|
+
async deleteApiKey(id) {
|
|
212
|
+
return this.apiKeysService.deleteApiKey(id);
|
|
213
|
+
}
|
|
214
|
+
validateApiKey(key) {
|
|
215
|
+
return this.apiKeysService.validateApiKey(key);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Automatically initializes the vault with default password '180823' if it does not exist
|
|
219
|
+
*/
|
|
220
|
+
async autoInitializeIfMissing() {
|
|
221
|
+
if (!(await this.exists())) {
|
|
222
|
+
logger.info('Vault database not found. Auto-initializing with default password...');
|
|
223
|
+
await this.initialize('180823', 'Mật khẩu mặc định là 180823');
|
|
224
|
+
this.lock(); // Lock it immediately so the user has to unlock it!
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
export const vaultService = new VaultService();
|
|
229
|
+
//# sourceMappingURL=vault.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.service.js","sourceRoot":"","sources":["../../../src/modules/vault/vault.service.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startServer(port: number, callback: () => void): Promise<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import helmet from 'helmet';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { authRouter } from './modules/auth/auth.controller.js';
|
|
7
|
+
import { credentialRouter } from './modules/credential/credential.controller.js';
|
|
8
|
+
import { tailscaleRouter } from './modules/tailscale/tailscale.controller.js';
|
|
9
|
+
import { proxyRouter } from './modules/proxy/proxy.controller.js';
|
|
10
|
+
import { totpRouter } from './modules/totp/totp.controller.js';
|
|
11
|
+
import { authMiddleware } from './middleware/auth.middleware.js';
|
|
12
|
+
import { errorMiddleware } from './middleware/error.middleware.js';
|
|
13
|
+
import { logger } from './utils/logger.js';
|
|
14
|
+
import { vaultService } from './modules/vault/vault.service.js';
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const app = express();
|
|
19
|
+
// Apply global middlewares
|
|
20
|
+
app.use(helmet({
|
|
21
|
+
contentSecurityPolicy: false, // Disabled to allow loading external platform icons and dynamic assets
|
|
22
|
+
crossOriginEmbedderPolicy: false,
|
|
23
|
+
crossOriginResourcePolicy: false
|
|
24
|
+
}));
|
|
25
|
+
app.use(cors());
|
|
26
|
+
app.use(express.json({ limit: '10mb' }));
|
|
27
|
+
// Request logging middleware
|
|
28
|
+
app.use((req, res, next) => {
|
|
29
|
+
logger.info(`${req.method} ${req.path}`);
|
|
30
|
+
next();
|
|
31
|
+
});
|
|
32
|
+
// API Routes
|
|
33
|
+
app.use('/api/auth', authRouter);
|
|
34
|
+
app.use('/api/credentials', authMiddleware, credentialRouter);
|
|
35
|
+
app.use('/api/tailscale', authMiddleware, tailscaleRouter);
|
|
36
|
+
app.use('/api/proxy', authMiddleware, proxyRouter);
|
|
37
|
+
app.use('/api/totp', authMiddleware, totpRouter);
|
|
38
|
+
// Serve Frontend Static Assets
|
|
39
|
+
const frontendDistPath = fs.existsSync(path.resolve(__dirname, './frontend'))
|
|
40
|
+
? path.resolve(__dirname, './frontend')
|
|
41
|
+
: path.resolve(__dirname, '../../frontend/dist');
|
|
42
|
+
app.use(express.static(frontendDistPath));
|
|
43
|
+
// Wildcard handler for Single Page Application (React Router)
|
|
44
|
+
app.get('*', (req, res) => {
|
|
45
|
+
res.sendFile(path.join(frontendDistPath, 'index.html'));
|
|
46
|
+
});
|
|
47
|
+
// Global Error Handler
|
|
48
|
+
app.use(errorMiddleware);
|
|
49
|
+
export async function startServer(port, callback) {
|
|
50
|
+
logger.info('Starting server in HTTP mode');
|
|
51
|
+
try {
|
|
52
|
+
await vaultService.autoInitializeIfMissing();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
logger.error('Failed to auto-initialize vault:', error);
|
|
56
|
+
}
|
|
57
|
+
return app.listen(port, callback);
|
|
58
|
+
}
|
|
59
|
+
// Support running directly (e.g. during development with tsx)
|
|
60
|
+
if (process.argv[1] && (process.argv[1].endsWith('server.ts') || process.argv[1].endsWith('server.js'))) {
|
|
61
|
+
const port = parseInt(process.env.PORT || '18823', 10);
|
|
62
|
+
startServer(port, () => {
|
|
63
|
+
logger.info(`Server running directly on port ${port}`);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// Trigger restart
|
|
67
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive an encryption key from the master password and a salt
|
|
3
|
+
*/
|
|
4
|
+
export declare function deriveKey(password: string, salt: string): Buffer;
|
|
5
|
+
/**
|
|
6
|
+
* Encrypt plain text using AES-256-GCM with a derived key
|
|
7
|
+
*/
|
|
8
|
+
export declare function encrypt(text: string, key: Buffer): string;
|
|
9
|
+
/**
|
|
10
|
+
* Decrypt cipher text using AES-256-GCM with a derived key
|
|
11
|
+
*/
|
|
12
|
+
export declare function decrypt(encryptedText: string, key: Buffer): string;
|
|
13
|
+
/**
|
|
14
|
+
* Generate a cryptographically secure random salt
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateSalt(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Hash a password for storage verification (different salt/iterations from encryption key)
|
|
19
|
+
*/
|
|
20
|
+
export declare function hashPassword(password: string, salt: string): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
const PBKDF2_ITERATIONS = 100000;
|
|
3
|
+
const KEY_LEN = 32; // 256 bits
|
|
4
|
+
const DIGEST = 'sha256';
|
|
5
|
+
/**
|
|
6
|
+
* Derive an encryption key from the master password and a salt
|
|
7
|
+
*/
|
|
8
|
+
export function deriveKey(password, salt) {
|
|
9
|
+
return crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, KEY_LEN, DIGEST);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Encrypt plain text using AES-256-GCM with a derived key
|
|
13
|
+
*/
|
|
14
|
+
export function encrypt(text, key) {
|
|
15
|
+
const iv = crypto.randomBytes(12);
|
|
16
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
17
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
18
|
+
encrypted += cipher.final('hex');
|
|
19
|
+
const authTag = cipher.getAuthTag().toString('hex');
|
|
20
|
+
// Format: iv:authTag:ciphertext
|
|
21
|
+
return `${iv.toString('hex')}:${authTag}:${encrypted}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Decrypt cipher text using AES-256-GCM with a derived key
|
|
25
|
+
*/
|
|
26
|
+
export function decrypt(encryptedText, key) {
|
|
27
|
+
const parts = encryptedText.split(':');
|
|
28
|
+
if (parts.length !== 3) {
|
|
29
|
+
throw new Error('Invalid encrypted text format');
|
|
30
|
+
}
|
|
31
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
32
|
+
const authTag = Buffer.from(parts[1], 'hex');
|
|
33
|
+
const ciphertext = Buffer.from(parts[2], 'hex');
|
|
34
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
35
|
+
decipher.setAuthTag(authTag);
|
|
36
|
+
let decrypted = decipher.update(ciphertext);
|
|
37
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
38
|
+
return decrypted.toString('utf8');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate a cryptographically secure random salt
|
|
42
|
+
*/
|
|
43
|
+
export function generateSalt() {
|
|
44
|
+
return crypto.randomBytes(16).toString('hex');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Hash a password for storage verification (different salt/iterations from encryption key)
|
|
48
|
+
*/
|
|
49
|
+
export function hashPassword(password, salt) {
|
|
50
|
+
// Use more iterations for storage hashing
|
|
51
|
+
const hash = crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS * 2, KEY_LEN, DIGEST);
|
|
52
|
+
return hash.toString('hex');
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACjC,MAAM,OAAO,GAAG,EAAE,CAAC,CAAC,WAAW;AAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC;AAExB;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAY;IACtD,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAC/E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,GAAW;IAC/C,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAE7D,IAAI,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACnD,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAEjC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEpD,gCAAgC;IAChC,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,IAAI,SAAS,EAAE,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,aAAqB,EAAE,GAAW;IACxD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAEhD,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACjE,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAE7B,IAAI,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5C,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAEzD,OAAO,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,IAAY;IACzD,0CAA0C;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,iBAAiB,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACvF,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { deriveKey, encrypt, decrypt, generateSalt, hashPassword } from './crypto.js';
|
|
2
|
+
import { logger } from './logger.js';
|
|
3
|
+
function runTests() {
|
|
4
|
+
logger.info('Starting unit tests for crypto module...');
|
|
5
|
+
try {
|
|
6
|
+
const password = 'myMasterPassword123!';
|
|
7
|
+
const salt = generateSalt();
|
|
8
|
+
// Test 1: Key Derivation
|
|
9
|
+
logger.info('Test 1: Testing key derivation...');
|
|
10
|
+
const key1 = deriveKey(password, salt);
|
|
11
|
+
const key2 = deriveKey(password, salt);
|
|
12
|
+
if (key1.toString('hex') !== key2.toString('hex')) {
|
|
13
|
+
throw new Error('Key derivation is not deterministic');
|
|
14
|
+
}
|
|
15
|
+
logger.info('Test 1 Passed: Key derivation is deterministic and matches.');
|
|
16
|
+
// Test 2: Symmetric Encryption & Decryption (AES-256-GCM)
|
|
17
|
+
logger.info('Test 2: Testing encryption & decryption...');
|
|
18
|
+
const originalText = 'Highly secret credentials: username=admin, password=secret';
|
|
19
|
+
const ciphertext = encrypt(originalText, key1);
|
|
20
|
+
logger.info(`Ciphertext format (iv:tag:cipher): ${ciphertext.substring(0, 40)}...`);
|
|
21
|
+
const decryptedText = decrypt(ciphertext, key1);
|
|
22
|
+
if (decryptedText !== originalText) {
|
|
23
|
+
throw new Error(`Decrypted text does not match original! Expected: "${originalText}", Got: "${decryptedText}"`);
|
|
24
|
+
}
|
|
25
|
+
logger.info('Test 2 Passed: Decryption recovers original text.');
|
|
26
|
+
// Test 3: Authenticated Tamper Proofing
|
|
27
|
+
logger.info('Test 3: Testing decryption integrity with tampered ciphertext...');
|
|
28
|
+
const parts = ciphertext.split(':');
|
|
29
|
+
// Tamper with the ciphertext content
|
|
30
|
+
parts[2] = parts[2].substring(0, parts[2].length - 2) + (parts[2].endsWith('a') ? 'b' : 'a');
|
|
31
|
+
const tamperedCiphertext = parts.join(':');
|
|
32
|
+
try {
|
|
33
|
+
decrypt(tamperedCiphertext, key1);
|
|
34
|
+
throw new Error('Decryption did not fail on tampered ciphertext (integrity check failed)');
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err.message.includes('Unsupported state or key size') || err.message.includes('BAD_DECRYPT') || err.message.includes('Unsupported state') || err.message.includes('integrity') || err.message.includes('Unsupported') || err.message.includes('AuthTag') || err.message.includes('decrypt') || err.message.includes('MAC')) {
|
|
38
|
+
logger.info('Test 3 Passed: Tampered ciphertext was successfully rejected (throws error).');
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// GCM decryption throw is generic (Unsupported state or key size / decrypt pending etc)
|
|
42
|
+
logger.info(`Test 3 Passed: Rejected with error: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Test 4: Password Hashing
|
|
46
|
+
logger.info('Test 4: Testing storage password hashing...');
|
|
47
|
+
const storageSalt = generateSalt();
|
|
48
|
+
const hash1 = hashPassword(password, storageSalt);
|
|
49
|
+
const hash2 = hashPassword(password, storageSalt);
|
|
50
|
+
const hash3 = hashPassword('differentPassword', storageSalt);
|
|
51
|
+
if (hash1 !== hash2) {
|
|
52
|
+
throw new Error('Password hashing is not deterministic');
|
|
53
|
+
}
|
|
54
|
+
if (hash1 === hash3) {
|
|
55
|
+
throw new Error('Different passwords produced the same hash');
|
|
56
|
+
}
|
|
57
|
+
logger.info('Test 4 Passed: Password hashing works as expected.');
|
|
58
|
+
logger.info('🎉 All crypto unit tests completed successfully!');
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
logger.error('❌ Crypto unit test failed:', error.message);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
runTests();
|
|
66
|
+
//# sourceMappingURL=crypto.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.test.js","sourceRoot":"","sources":["../../src/utils/crypto.test.ts"],"names":[],"mappings":"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"}
|