filepilot-enterprise-vault 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/bin/cli.js ADDED
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const readline = require('readline/promises');
7
+ const { stdin: input, stdout: output } = require('process');
8
+
9
+ const DATA_DIR = process.env.VAULT_DATA_DIR || process.cwd();
10
+ const KEY_FILE = path.join(DATA_DIR, '.vault_key');
11
+ const configPath = path.join(DATA_DIR, 'config.json');
12
+
13
+ async function ask(rl, query, defaultValue) {
14
+ const displayQuery = defaultValue !== undefined ? `${query} [${defaultValue}]: ` : `${query}: `;
15
+ const answer = await rl.question(displayQuery);
16
+ return answer.trim() || defaultValue;
17
+ }
18
+
19
+ async function askPassword(rl, query) {
20
+ if (!process.stdin.isTTY) {
21
+ const answer = await rl.question(query + ': ');
22
+ return answer.trim();
23
+ }
24
+
25
+ return new Promise((resolve) => {
26
+ process.stdout.write(query + ': ');
27
+ process.stdin.setRawMode(true);
28
+ process.stdin.resume();
29
+ process.stdin.setEncoding('utf8');
30
+
31
+ let password = '';
32
+ const onData = (char) => {
33
+ char = char.toString();
34
+ if (char === '\n' || char === '\r' || char === '\u0004') {
35
+ process.stdin.setRawMode(false);
36
+ process.stdin.removeListener('data', onData);
37
+ process.stdout.write('\n');
38
+ resolve(password.trim());
39
+ } else if (char === '\u0003') {
40
+ process.exit();
41
+ } else if (char === '\u0008' || char === '\u007f') {
42
+ if (password.length > 0) {
43
+ password = password.slice(0, -1);
44
+ process.stdout.write('\b \b');
45
+ }
46
+ } else {
47
+ password += char;
48
+ process.stdout.write('*');
49
+ }
50
+ };
51
+ process.stdin.on('data', onData);
52
+ });
53
+ }
54
+
55
+ async function runCliWizard() {
56
+ console.log('==================================================');
57
+ console.log(' FilePilot Enterprise Vault Installation Wizard ');
58
+ console.log('==================================================\n');
59
+
60
+ const rl = readline.createInterface({ input, output });
61
+
62
+ try {
63
+ // 1. Check if already installed
64
+ const envFilePath = path.join(DATA_DIR, '.env');
65
+ if (fs.existsSync(envFilePath) || fs.existsSync(configPath)) {
66
+ const overwrite = await ask(rl, 'A configuration already exists. Do you want to overwrite it? (y/n)', 'n');
67
+ if (overwrite.toLowerCase() !== 'y') {
68
+ console.log('Initialization aborted.');
69
+ rl.close();
70
+ return false;
71
+ }
72
+ }
73
+
74
+ // Ensure data directory exists
75
+ fs.mkdirSync(DATA_DIR, { recursive: true });
76
+
77
+ // 2. Database dialect selection
78
+ let dialect = '';
79
+ while (true) {
80
+ dialect = await ask(rl, 'Database dialect (sqlite / postgres / mysql)', 'sqlite');
81
+ dialect = dialect.toLowerCase();
82
+ if (['sqlite', 'postgres', 'mysql'].includes(dialect)) {
83
+ break;
84
+ }
85
+ console.log('Invalid dialect. Please choose sqlite, postgres, or mysql.');
86
+ }
87
+
88
+ let dbConfig = { dialect };
89
+
90
+ if (dialect === 'sqlite') {
91
+ const defaultDbPath = path.join(DATA_DIR, 'vault.db');
92
+ const dbStorage = await ask(rl, `SQLite database file storage path`, defaultDbPath);
93
+ dbConfig.storage = path.resolve(dbStorage);
94
+ } else {
95
+ const defaultPort = dialect === 'postgres' ? '5432' : '3306';
96
+ dbConfig.host = await ask(rl, 'Database host', 'localhost');
97
+ dbConfig.port = parseInt(await ask(rl, 'Database port', defaultPort)) || parseInt(defaultPort);
98
+ dbConfig.username = await ask(rl, 'Database username', 'vault_admin');
99
+ dbConfig.password = await askPassword(rl, 'Database password');
100
+ dbConfig.database = await ask(rl, 'Database name', 'filepilot_vault');
101
+
102
+ const sslRequired = await ask(rl, 'Enable SSL/TLS for database connection? (y/n)', 'n');
103
+ if (sslRequired.toLowerCase() === 'y') {
104
+ dbConfig.ssl = true;
105
+ }
106
+ }
107
+
108
+ // 3. Encryption key setup
109
+ console.log('\n--- Encryption Key Setup ---');
110
+ console.log('1. Generate a new random 256-bit key (Recommended)');
111
+ console.log('2. Enter a custom key');
112
+ const keyChoice = await ask(rl, 'Choose an option', '1');
113
+
114
+ let masterKeyStr;
115
+ if (keyChoice === '2') {
116
+ const customKey = await askPassword(rl, 'Enter your 256-bit Master Key (hex, base64 or plaintext)');
117
+ masterKeyStr = customKey;
118
+ } else {
119
+ masterKeyStr = crypto.randomBytes(32).toString('hex');
120
+ console.log(`Generated new master key: ${masterKeyStr}`);
121
+ console.log('This key will be written to your .env file.');
122
+ }
123
+
124
+ // 4. Create Administrator Account
125
+ console.log('\n--- Create Administrator Account ---');
126
+ const adminUsername = await ask(rl, 'Admin username', 'admin');
127
+ let adminPassword = '';
128
+ while (true) {
129
+ adminPassword = await askPassword(rl, 'Admin password');
130
+ if (adminPassword.length < 8) {
131
+ console.log('Password must be at least 8 characters long.');
132
+ continue;
133
+ }
134
+ const confirmPassword = await askPassword(rl, 'Confirm admin password');
135
+ if (adminPassword === confirmPassword) {
136
+ break;
137
+ }
138
+ console.log('Passwords do not match. Please try again.');
139
+ }
140
+
141
+ // 5. Write env configurations
142
+ console.log('\nWriting configurations...');
143
+
144
+ const envContent = [
145
+ `# FilePilot Corporate Vault Environment Configuration`,
146
+ `VAULT_MASTER_KEY=${masterKeyStr}`,
147
+ `DB_DIALECT=${dbConfig.dialect}`,
148
+ `DB_HOST=${dbConfig.host || 'localhost'}`,
149
+ `DB_PORT=${dbConfig.port || ''}`,
150
+ `DB_USERNAME=${dbConfig.username || ''}`,
151
+ `DB_PASSWORD=${dbConfig.password || ''}`,
152
+ `DB_NAME=${dbConfig.database || ''}`,
153
+ `DB_STORAGE=${dbConfig.storage || ''}`,
154
+ `DB_SSL=${dbConfig.ssl || 'false'}`
155
+ ].join('\n');
156
+
157
+ fs.writeFileSync(envFilePath, envContent, 'utf8');
158
+ console.log(`Saved configuration to: ${envFilePath}`);
159
+
160
+ // Clean up legacy files to avoid confusion
161
+ if (fs.existsSync(configPath)) {
162
+ try { fs.unlinkSync(configPath); } catch (_) {}
163
+ }
164
+ if (fs.existsSync(KEY_FILE)) {
165
+ try { fs.unlinkSync(KEY_FILE); } catch (_) {}
166
+ }
167
+
168
+ // Set environment variables so the database library loads them correctly
169
+ process.env.VAULT_DATA_DIR = DATA_DIR;
170
+ process.env.VAULT_MASTER_KEY = masterKeyStr;
171
+ process.env.DB_DIALECT = dbConfig.dialect;
172
+ process.env.DB_HOST = dbConfig.host || 'localhost';
173
+ if (dbConfig.port) process.env.DB_PORT = String(dbConfig.port);
174
+ if (dbConfig.username) process.env.DB_USERNAME = dbConfig.username;
175
+ if (dbConfig.password) process.env.DB_PASSWORD = dbConfig.password;
176
+ if (dbConfig.database) process.env.DB_NAME = dbConfig.database;
177
+ if (dbConfig.storage) process.env.DB_STORAGE = dbConfig.storage;
178
+ process.env.DB_SSL = dbConfig.ssl ? 'true' : 'false';
179
+
180
+ // 6. Connect and run database setup
181
+ console.log('Initializing database models and schema...');
182
+ try {
183
+ const dbPath = require.resolve(path.join(__dirname, '..', 'db.cjs'));
184
+ delete require.cache[dbPath];
185
+ } catch (_) {}
186
+ const db = require(path.join(__dirname, '..', 'db.cjs'));
187
+
188
+ db.updateDbConnection(dbConfig);
189
+ await db.initDb();
190
+
191
+ // 7. Seed Admin user
192
+ const adminHash = crypto.createHash('sha256').update(adminPassword).digest('hex');
193
+ let adminUser = await db.User.findOne({ where: { username: adminUsername } });
194
+ if (adminUser) {
195
+ adminUser.passwordHash = adminHash;
196
+ adminUser.role = 'admin';
197
+ await adminUser.save();
198
+ } else {
199
+ await db.User.create({
200
+ username: adminUsername,
201
+ passwordHash: adminHash,
202
+ role: 'admin'
203
+ });
204
+ }
205
+
206
+ // 8. Seed default configurations
207
+ await db.SystemConfig.findOrCreate({ where: { key: 'siem_webhook_url' }, defaults: { value: '' } });
208
+ await db.SystemConfig.findOrCreate({ where: { key: 'siem_webhook_secret' }, defaults: { value: crypto.randomBytes(32).toString('hex') } });
209
+ await db.SystemConfig.findOrCreate({ where: { key: 'audit_logging_enabled' }, defaults: { value: 'true' } });
210
+
211
+ console.log('\n==================================================');
212
+ console.log(' Installation Completed Successfully! ');
213
+ console.log('==================================================\n');
214
+ return true;
215
+ } catch (err) {
216
+ console.error('\nInstallation failed:', err);
217
+ return false;
218
+ } finally {
219
+ rl.close();
220
+ }
221
+ }
222
+
223
+ async function main() {
224
+ const args = process.argv.slice(2);
225
+ const command = args[0] || 'start';
226
+
227
+ if (command === 'init') {
228
+ await runCliWizard();
229
+ } else if (command === 'start') {
230
+ console.log('Starting FilePilot Enterprise Vault Server...');
231
+ process.env.RUN_VAULT_SERVER = 'true';
232
+ process.env.VAULT_DATA_DIR = DATA_DIR;
233
+ // Require server.cjs to run the server in the current process
234
+ require('../server.cjs');
235
+ } else {
236
+ console.log('Usage: filepilot-vault [init | start]');
237
+ console.log('\nCommands:');
238
+ console.log(' init - Run interactive installation wizard to configure the vault');
239
+ console.log(' start - Start the vault server');
240
+ }
241
+ }
242
+
243
+ if (require.main === module) {
244
+ main().catch(console.error);
245
+ }
246
+
247
+ module.exports = { runCliWizard };
@@ -0,0 +1,221 @@
1
+ const crypto = require('crypto');
2
+ const {
3
+ VaultGroup,
4
+ KeyVersion,
5
+ User,
6
+ AccessKey,
7
+ TransferLog,
8
+ SystemConfig,
9
+ AdminSession
10
+ } = require('./db.cjs');
11
+
12
+ // Granular RBAC Permission Matrix (must align with server.cjs)
13
+ const ROLE_PERMISSIONS = {
14
+ admin: ['*'],
15
+ manager: [
16
+ 'vault.*',
17
+ 'profile.*',
18
+ 'token.*',
19
+ 'backup.*',
20
+ 'audit.view',
21
+ 'system.view_dashboard'
22
+ ],
23
+ operator: [
24
+ 'vault.view_groups',
25
+ 'profile.view',
26
+ 'profile.create',
27
+ 'profile.edit',
28
+ 'profile.test_connection',
29
+ 'token.view',
30
+ 'token.issue',
31
+ 'backup.view',
32
+ 'system.view_dashboard'
33
+ ],
34
+ auditor: [
35
+ 'vault.view_groups',
36
+ 'profile.view',
37
+ 'token.view',
38
+ 'audit.view',
39
+ 'audit.export',
40
+ 'backup.view',
41
+ 'system.view_dashboard'
42
+ ]
43
+ };
44
+
45
+ // Reusable hash computation logic for log verification
46
+ function computeLogHash(log, expectedPrevHash) {
47
+ let logFields;
48
+ if (!log.hash_version || log.hash_version === 1) {
49
+ logFields = {
50
+ token: log.token || null,
51
+ username: log.username || null,
52
+ connectionId: log.connectionId,
53
+ filePath: log.filePath,
54
+ fileSize: log.fileSize ? parseInt(log.fileSize) : 0,
55
+ action: log.action,
56
+ status: log.status || 'success',
57
+ errorMessage: log.metadata || log.errorMessage || null,
58
+ prev_hash: expectedPrevHash
59
+ };
60
+ } else {
61
+ logFields = {
62
+ token: log.token || null,
63
+ username: log.username || null,
64
+ connectionId: log.connectionId,
65
+ filePath: log.filePath,
66
+ fileSize: log.fileSize ? parseInt(log.fileSize) : 0,
67
+ action: log.action,
68
+ status: log.status || 'success',
69
+ errorMessage: log.errorMessage || null,
70
+ metadata: log.metadata || null,
71
+ prev_hash: expectedPrevHash
72
+ };
73
+ }
74
+ const hashInput = JSON.stringify(logFields);
75
+ return crypto.createHash('sha256').update(hashInput).digest('hex');
76
+ }
77
+
78
+ /**
79
+ * 1. Encryption Posture
80
+ */
81
+ async function getEncryptionPosture() {
82
+ const groups = await VaultGroup.findAll();
83
+ const keyVersionCount = await KeyVersion.count();
84
+ const rotationPerformed = keyVersionCount > 1;
85
+
86
+ const groupPostures = groups.map(g => ({
87
+ groupId: g.id,
88
+ groupName: g.name,
89
+ kmsProvider: g.kms_provider,
90
+ dekVersion: g.dek_version,
91
+ kekRotationPerformed: rotationPerformed || g.dek_version > 1
92
+ }));
93
+
94
+ return {
95
+ rotationHistoryCount: keyVersionCount,
96
+ globalRotationPerformed: rotationPerformed,
97
+ groups: groupPostures
98
+ };
99
+ }
100
+
101
+ /**
102
+ * 2. Access Control Posture
103
+ */
104
+ async function getAccessControlPosture() {
105
+ const users = await User.findAll({ attributes: ['id', 'username', 'role', 'mfa_enabled'] });
106
+ const tokens = await AccessKey.findAll({ include: [User] });
107
+
108
+ const tokenList = [];
109
+ for (const t of tokens) {
110
+ const lastLog = await TransferLog.findOne({
111
+ where: { token: t.token_hash },
112
+ order: [['createdAt', 'DESC']]
113
+ });
114
+ const ageDays = (Date.now() - new Date(t.createdAt)) / (1000 * 60 * 60 * 24);
115
+ tokenList.push({
116
+ token_hash_truncated: t.token_hash.substring(0, 10) + '...',
117
+ user: t.User ? t.User.username : 'Unknown',
118
+ groupId: t.groupId,
119
+ status: t.status,
120
+ expiresAt: t.expiresAt,
121
+ createdAt: t.createdAt,
122
+ ageDays: parseFloat(ageDays.toFixed(2)),
123
+ lastUsedAt: lastLog ? lastLog.createdAt : null
124
+ });
125
+ }
126
+
127
+ return {
128
+ rolePermissions: ROLE_PERMISSIONS,
129
+ users: users.map(u => ({ id: u.id, username: u.username, role: u.role, mfa_enabled: u.mfa_enabled })),
130
+ activeTokens: tokenList
131
+ };
132
+ }
133
+
134
+ /**
135
+ * 3. Audit Integrity Posture
136
+ */
137
+ async function getAuditIntegrityPosture() {
138
+ const logs = await TransferLog.findAll({ order: [['id', 'ASC']] });
139
+ let intact = true;
140
+ let brokenAtId = null;
141
+ let reason = null;
142
+ let expectedPrevHash = '0';
143
+
144
+ for (const log of logs) {
145
+ if (log.prev_hash !== expectedPrevHash) {
146
+ intact = false;
147
+ brokenAtId = log.id;
148
+ reason = `prev_hash mismatch. Expected: ${expectedPrevHash}, Actual: ${log.prev_hash}`;
149
+ break;
150
+ }
151
+ const computedHash = computeLogHash(log, expectedPrevHash);
152
+ if (log.entry_hash !== computedHash) {
153
+ intact = false;
154
+ brokenAtId = log.id;
155
+ reason = `entry_hash mismatch. Computed: ${computedHash}, Actual: ${log.entry_hash}`;
156
+ break;
157
+ }
158
+ expectedPrevHash = computedHash;
159
+ }
160
+
161
+ const oldestLog = logs.length > 0 ? logs[0].createdAt : null;
162
+ const newestLog = logs.length > 0 ? logs[logs.length - 1].createdAt : null;
163
+
164
+ return {
165
+ intact,
166
+ brokenAtId,
167
+ reason,
168
+ totalCount: logs.length,
169
+ oldestLog,
170
+ newestLog
171
+ };
172
+ }
173
+
174
+ /**
175
+ * 4. Access Policy Posture
176
+ */
177
+ async function getAccessPolicyPosture() {
178
+ const groups = await VaultGroup.findAll();
179
+ return groups.map(g => ({
180
+ groupId: g.id,
181
+ groupName: g.name,
182
+ hasIpAllowlist: !!(g.ipAllowlist && g.ipAllowlist.trim() !== ''),
183
+ ipAllowlist: g.ipAllowlist || ''
184
+ }));
185
+ }
186
+
187
+ /**
188
+ * 5. Session Security Posture
189
+ */
190
+ async function getSessionSecurityPosture() {
191
+ const sessionTimeoutConfig = await SystemConfig.findOne({ where: { key: 'session_timeout' } });
192
+ const maxSessionsConfig = await SystemConfig.findOne({ where: { key: 'max_sessions_per_user' } });
193
+
194
+ const sessionTimeout = sessionTimeoutConfig ? sessionTimeoutConfig.value : '28800000'; // default 8 hours in ms
195
+ const maxSessionsPerUser = maxSessionsConfig ? maxSessionsConfig.value : '5'; // default 5
196
+
197
+ const activeSessions = await AdminSession.findAll({ include: [User] });
198
+ const userSessionsMap = {};
199
+ for (const session of activeSessions) {
200
+ const user = session.User || { username: 'Unknown' };
201
+ const username = user.username;
202
+ if (!userSessionsMap[username]) {
203
+ userSessionsMap[username] = 0;
204
+ }
205
+ userSessionsMap[username]++;
206
+ }
207
+
208
+ return {
209
+ sessionTimeoutMs: parseInt(sessionTimeout),
210
+ maxSessionsPerUser: parseInt(maxSessionsPerUser),
211
+ activeSessionsPerUser: userSessionsMap
212
+ };
213
+ }
214
+
215
+ module.exports = {
216
+ getEncryptionPosture,
217
+ getAccessControlPosture,
218
+ getAuditIntegrityPosture,
219
+ getAccessPolicyPosture,
220
+ getSessionSecurityPosture
221
+ };