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/README.md +209 -0
- package/admin/enterprise.png +0 -0
- package/admin/index.html +5297 -0
- package/admin/install.html +511 -0
- package/bin/cli.js +247 -0
- package/compliance-checks.cjs +221 -0
- package/db.cjs +999 -0
- package/kms-providers.cjs +329 -0
- package/package.json +59 -0
- package/server.cjs +4214 -0
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
|
+
};
|