tlc-claude-code 1.4.7 → 1.4.9
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/docker-compose.dev.yml +6 -3
- package/package.json +1 -1
- package/server/index.js +229 -14
- package/server/lib/compliance/control-mapper.js +401 -0
- package/server/lib/compliance/control-mapper.test.js +117 -0
- package/server/lib/compliance/evidence-linker.js +296 -0
- package/server/lib/compliance/evidence-linker.test.js +121 -0
- package/server/lib/compliance/gdpr-checklist.js +416 -0
- package/server/lib/compliance/gdpr-checklist.test.js +131 -0
- package/server/lib/compliance/hipaa-checklist.js +277 -0
- package/server/lib/compliance/hipaa-checklist.test.js +101 -0
- package/server/lib/compliance/iso27001-checklist.js +287 -0
- package/server/lib/compliance/iso27001-checklist.test.js +99 -0
- package/server/lib/compliance/multi-framework-reporter.js +284 -0
- package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
- package/server/lib/compliance/pci-dss-checklist.js +214 -0
- package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
- package/server/lib/compliance/trust-centre.js +187 -0
- package/server/lib/compliance/trust-centre.test.js +93 -0
- package/server/lib/dashboard/api-server.js +155 -0
- package/server/lib/dashboard/api-server.test.js +155 -0
- package/server/lib/dashboard/health-api.js +199 -0
- package/server/lib/dashboard/health-api.test.js +122 -0
- package/server/lib/dashboard/notes-api.js +234 -0
- package/server/lib/dashboard/notes-api.test.js +134 -0
- package/server/lib/dashboard/router-api.js +176 -0
- package/server/lib/dashboard/router-api.test.js +132 -0
- package/server/lib/dashboard/tasks-api.js +289 -0
- package/server/lib/dashboard/tasks-api.test.js +161 -0
- package/server/lib/dashboard/tlc-introspection.js +197 -0
- package/server/lib/dashboard/tlc-introspection.test.js +138 -0
- package/server/lib/dashboard/version-api.js +222 -0
- package/server/lib/dashboard/version-api.test.js +112 -0
- package/server/lib/dashboard/websocket-server.js +104 -0
- package/server/lib/dashboard/websocket-server.test.js +118 -0
- package/server/lib/deploy/branch-classifier.js +163 -0
- package/server/lib/deploy/branch-classifier.test.js +164 -0
- package/server/lib/deploy/deployment-approval.js +299 -0
- package/server/lib/deploy/deployment-approval.test.js +296 -0
- package/server/lib/deploy/deployment-audit.js +374 -0
- package/server/lib/deploy/deployment-audit.test.js +307 -0
- package/server/lib/deploy/deployment-executor.js +335 -0
- package/server/lib/deploy/deployment-executor.test.js +329 -0
- package/server/lib/deploy/deployment-rules.js +163 -0
- package/server/lib/deploy/deployment-rules.test.js +188 -0
- package/server/lib/deploy/rollback-manager.js +379 -0
- package/server/lib/deploy/rollback-manager.test.js +321 -0
- package/server/lib/deploy/security-gates.js +236 -0
- package/server/lib/deploy/security-gates.test.js +222 -0
- package/server/lib/k8s/gitops-config.js +188 -0
- package/server/lib/k8s/gitops-config.test.js +59 -0
- package/server/lib/k8s/helm-generator.js +196 -0
- package/server/lib/k8s/helm-generator.test.js +59 -0
- package/server/lib/k8s/kustomize-generator.js +176 -0
- package/server/lib/k8s/kustomize-generator.test.js +58 -0
- package/server/lib/k8s/network-policy.js +114 -0
- package/server/lib/k8s/network-policy.test.js +53 -0
- package/server/lib/k8s/pod-security.js +114 -0
- package/server/lib/k8s/pod-security.test.js +55 -0
- package/server/lib/k8s/rbac-generator.js +132 -0
- package/server/lib/k8s/rbac-generator.test.js +57 -0
- package/server/lib/k8s/resource-manager.js +172 -0
- package/server/lib/k8s/resource-manager.test.js +60 -0
- package/server/lib/k8s/secrets-encryption.js +168 -0
- package/server/lib/k8s/secrets-encryption.test.js +49 -0
- package/server/lib/monitoring/alert-manager.js +238 -0
- package/server/lib/monitoring/alert-manager.test.js +106 -0
- package/server/lib/monitoring/health-check.js +226 -0
- package/server/lib/monitoring/health-check.test.js +176 -0
- package/server/lib/monitoring/incident-manager.js +230 -0
- package/server/lib/monitoring/incident-manager.test.js +98 -0
- package/server/lib/monitoring/log-aggregator.js +147 -0
- package/server/lib/monitoring/log-aggregator.test.js +89 -0
- package/server/lib/monitoring/metrics-collector.js +337 -0
- package/server/lib/monitoring/metrics-collector.test.js +172 -0
- package/server/lib/monitoring/status-page.js +214 -0
- package/server/lib/monitoring/status-page.test.js +105 -0
- package/server/lib/monitoring/uptime-monitor.js +194 -0
- package/server/lib/monitoring/uptime-monitor.test.js +109 -0
- package/server/lib/network/fail2ban-config.js +294 -0
- package/server/lib/network/fail2ban-config.test.js +275 -0
- package/server/lib/network/firewall-manager.js +252 -0
- package/server/lib/network/firewall-manager.test.js +254 -0
- package/server/lib/network/geoip-filter.js +282 -0
- package/server/lib/network/geoip-filter.test.js +264 -0
- package/server/lib/network/rate-limiter.js +229 -0
- package/server/lib/network/rate-limiter.test.js +293 -0
- package/server/lib/network/request-validator.js +351 -0
- package/server/lib/network/request-validator.test.js +345 -0
- package/server/lib/network/security-headers.js +251 -0
- package/server/lib/network/security-headers.test.js +283 -0
- package/server/lib/network/tls-config.js +210 -0
- package/server/lib/network/tls-config.test.js +248 -0
- package/server/lib/security/auth-security.js +369 -0
- package/server/lib/security/auth-security.test.js +448 -0
- package/server/lib/security/cis-benchmark.js +152 -0
- package/server/lib/security/cis-benchmark.test.js +137 -0
- package/server/lib/security/compose-templates.js +312 -0
- package/server/lib/security/compose-templates.test.js +229 -0
- package/server/lib/security/container-runtime.js +456 -0
- package/server/lib/security/container-runtime.test.js +503 -0
- package/server/lib/security/cors-validator.js +278 -0
- package/server/lib/security/cors-validator.test.js +310 -0
- package/server/lib/security/crypto-utils.js +253 -0
- package/server/lib/security/crypto-utils.test.js +409 -0
- package/server/lib/security/dockerfile-linter.js +459 -0
- package/server/lib/security/dockerfile-linter.test.js +483 -0
- package/server/lib/security/dockerfile-templates.js +278 -0
- package/server/lib/security/dockerfile-templates.test.js +164 -0
- package/server/lib/security/error-sanitizer.js +426 -0
- package/server/lib/security/error-sanitizer.test.js +331 -0
- package/server/lib/security/headers-generator.js +368 -0
- package/server/lib/security/headers-generator.test.js +398 -0
- package/server/lib/security/image-scanner.js +83 -0
- package/server/lib/security/image-scanner.test.js +106 -0
- package/server/lib/security/input-validator.js +352 -0
- package/server/lib/security/input-validator.test.js +330 -0
- package/server/lib/security/network-policy.js +174 -0
- package/server/lib/security/network-policy.test.js +164 -0
- package/server/lib/security/output-encoder.js +237 -0
- package/server/lib/security/output-encoder.test.js +276 -0
- package/server/lib/security/path-validator.js +359 -0
- package/server/lib/security/path-validator.test.js +293 -0
- package/server/lib/security/query-builder.js +421 -0
- package/server/lib/security/query-builder.test.js +318 -0
- package/server/lib/security/secret-detector.js +290 -0
- package/server/lib/security/secret-detector.test.js +354 -0
- package/server/lib/security/secrets-validator.js +137 -0
- package/server/lib/security/secrets-validator.test.js +120 -0
- package/server/lib/security-testing/dast-runner.js +154 -0
- package/server/lib/security-testing/dast-runner.test.js +62 -0
- package/server/lib/security-testing/dependency-scanner.js +172 -0
- package/server/lib/security-testing/dependency-scanner.test.js +64 -0
- package/server/lib/security-testing/pentest-runner.js +230 -0
- package/server/lib/security-testing/pentest-runner.test.js +60 -0
- package/server/lib/security-testing/sast-runner.js +136 -0
- package/server/lib/security-testing/sast-runner.test.js +62 -0
- package/server/lib/security-testing/secret-scanner.js +153 -0
- package/server/lib/security-testing/secret-scanner.test.js +66 -0
- package/server/lib/security-testing/security-gate.js +216 -0
- package/server/lib/security-testing/security-gate.test.js +115 -0
- package/server/lib/security-testing/security-reporter.js +303 -0
- package/server/lib/security-testing/security-reporter.test.js +114 -0
- package/server/lib/standards/audit-checker.js +546 -0
- package/server/lib/standards/audit-checker.test.js +415 -0
- package/server/lib/standards/cleanup-executor.js +452 -0
- package/server/lib/standards/cleanup-executor.test.js +293 -0
- package/server/lib/standards/refactor-stepper.js +425 -0
- package/server/lib/standards/refactor-stepper.test.js +298 -0
- package/server/lib/standards/standards-injector.js +167 -0
- package/server/lib/standards/standards-injector.test.js +232 -0
- package/server/lib/user-management.test.js +284 -0
- package/server/lib/vps/backup-manager.js +157 -0
- package/server/lib/vps/backup-manager.test.js +59 -0
- package/server/lib/vps/caddy-config.js +159 -0
- package/server/lib/vps/caddy-config.test.js +48 -0
- package/server/lib/vps/compose-orchestrator.js +219 -0
- package/server/lib/vps/compose-orchestrator.test.js +50 -0
- package/server/lib/vps/database-config.js +208 -0
- package/server/lib/vps/database-config.test.js +47 -0
- package/server/lib/vps/deploy-script.js +211 -0
- package/server/lib/vps/deploy-script.test.js +53 -0
- package/server/lib/vps/secrets-manager.js +148 -0
- package/server/lib/vps/secrets-manager.test.js +58 -0
- package/server/lib/vps/server-hardening.js +174 -0
- package/server/lib/vps/server-hardening.test.js +70 -0
- package/server/package-lock.json +19 -0
- package/server/package.json +1 -0
- package/server/templates/CLAUDE.md +37 -0
- package/server/templates/CODING-STANDARDS.md +408 -0
package/docker-compose.dev.yml
CHANGED
|
@@ -101,9 +101,12 @@ services:
|
|
|
101
101
|
working_dir: /project
|
|
102
102
|
command: >
|
|
103
103
|
sh -c "
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
echo 'Installing TLC...' &&
|
|
105
|
+
npm install -g tlc-claude-code@latest &&
|
|
106
|
+
TLC_DIR=/usr/local/lib/node_modules/tlc-claude-code &&
|
|
107
|
+
echo 'TLC installed at:' $$TLC_DIR &&
|
|
108
|
+
ls -la $$TLC_DIR/server/ &&
|
|
109
|
+
cd /project && node $$TLC_DIR/server/index.js --proxy-only --skip-db
|
|
107
110
|
"
|
|
108
111
|
environment:
|
|
109
112
|
- TLC_PORT=3147
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -19,6 +19,8 @@ const {
|
|
|
19
19
|
verifyJWT,
|
|
20
20
|
hashPassword,
|
|
21
21
|
verifyPassword,
|
|
22
|
+
hasPermission,
|
|
23
|
+
USER_ROLES,
|
|
22
24
|
} = require('./lib/auth-system');
|
|
23
25
|
|
|
24
26
|
// Handle PGlite WASM crashes gracefully
|
|
@@ -66,39 +68,102 @@ const userStore = createUserStore();
|
|
|
66
68
|
const JWT_SECRET = process.env.TLC_JWT_SECRET || 'tlc-dashboard-secret-change-in-production';
|
|
67
69
|
const AUTH_ENABLED = process.env.TLC_AUTH !== 'false';
|
|
68
70
|
|
|
69
|
-
// Initialize
|
|
71
|
+
// Initialize users from config or environment
|
|
70
72
|
async function initializeAuth() {
|
|
71
73
|
const tlcConfigPath = path.join(PROJECT_DIR, '.tlc.json');
|
|
72
|
-
let
|
|
73
|
-
let adminPassword = process.env.TLC_ADMIN_PASSWORD;
|
|
74
|
+
let users = [];
|
|
74
75
|
|
|
75
76
|
// Try to read from .tlc.json
|
|
76
77
|
if (fs.existsSync(tlcConfigPath)) {
|
|
77
78
|
try {
|
|
78
79
|
const config = JSON.parse(fs.readFileSync(tlcConfigPath, 'utf-8'));
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
|
|
81
|
+
// Support new multi-user format: auth.users array
|
|
82
|
+
if (config.auth?.users && Array.isArray(config.auth.users)) {
|
|
83
|
+
users = config.auth.users;
|
|
84
|
+
}
|
|
85
|
+
// Also support legacy single admin format for backwards compatibility
|
|
86
|
+
else if (config.auth?.adminEmail && config.auth?.adminPassword) {
|
|
87
|
+
users.push({
|
|
88
|
+
email: config.auth.adminEmail,
|
|
89
|
+
password: config.auth.adminPassword,
|
|
90
|
+
name: config.auth.adminName || 'Admin',
|
|
91
|
+
role: 'admin',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
81
94
|
} catch (e) {
|
|
82
|
-
|
|
95
|
+
console.error('[TLC] Failed to parse .tlc.json:', e.message);
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
98
|
|
|
86
|
-
//
|
|
87
|
-
if (
|
|
99
|
+
// Also check environment variables for admin
|
|
100
|
+
if (process.env.TLC_ADMIN_EMAIL && process.env.TLC_ADMIN_PASSWORD) {
|
|
101
|
+
const envAdmin = {
|
|
102
|
+
email: process.env.TLC_ADMIN_EMAIL,
|
|
103
|
+
password: process.env.TLC_ADMIN_PASSWORD,
|
|
104
|
+
name: process.env.TLC_ADMIN_NAME || 'Admin',
|
|
105
|
+
role: 'admin',
|
|
106
|
+
};
|
|
107
|
+
// Don't duplicate if already in config
|
|
108
|
+
if (!users.find(u => u.email === envAdmin.email)) {
|
|
109
|
+
users.push(envAdmin);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Create all users
|
|
114
|
+
for (const userData of users) {
|
|
115
|
+
if (!userData.email || !userData.password) {
|
|
116
|
+
console.warn('[TLC] Skipping user without email or password');
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
88
120
|
try {
|
|
89
121
|
await userStore.createUser({
|
|
90
|
-
email:
|
|
91
|
-
password:
|
|
92
|
-
name: '
|
|
93
|
-
role: '
|
|
122
|
+
email: userData.email,
|
|
123
|
+
password: userData.password,
|
|
124
|
+
name: userData.name || userData.email.split('@')[0],
|
|
125
|
+
role: userData.role || 'engineer',
|
|
94
126
|
}, { skipValidation: true }); // Dev tool - allow simple passwords
|
|
95
|
-
console.log(`[TLC]
|
|
127
|
+
console.log(`[TLC] User initialized: ${userData.email} (${userData.role || 'engineer'})`);
|
|
96
128
|
} catch (e) {
|
|
97
129
|
if (!e.message.includes('already registered')) {
|
|
98
|
-
console.error(
|
|
130
|
+
console.error(`[TLC] Failed to create user ${userData.email}:`, e.message);
|
|
99
131
|
}
|
|
100
132
|
}
|
|
101
133
|
}
|
|
134
|
+
|
|
135
|
+
const userCount = await userStore.getUserCount();
|
|
136
|
+
if (userCount === 0) {
|
|
137
|
+
console.log('[TLC] No users configured. Add users to .tlc.json:');
|
|
138
|
+
console.log('[TLC] "auth": { "users": [{ "email": "...", "password": "...", "role": "admin" }] }');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Role-based permission middleware
|
|
143
|
+
function requireRole(...allowedRoles) {
|
|
144
|
+
return (req, res, next) => {
|
|
145
|
+
if (!req.user) {
|
|
146
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
147
|
+
}
|
|
148
|
+
if (!allowedRoles.includes(req.user.role) && req.user.role !== 'admin') {
|
|
149
|
+
return res.status(403).json({ error: 'Insufficient permissions' });
|
|
150
|
+
}
|
|
151
|
+
next();
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Permission-based middleware
|
|
156
|
+
function requirePermission(permission) {
|
|
157
|
+
return async (req, res, next) => {
|
|
158
|
+
if (!req.user) {
|
|
159
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
160
|
+
}
|
|
161
|
+
const user = await userStore.findUserById(req.user.sub);
|
|
162
|
+
if (!user || !hasPermission(user, permission)) {
|
|
163
|
+
return res.status(403).json({ error: 'Permission denied' });
|
|
164
|
+
}
|
|
165
|
+
next();
|
|
166
|
+
};
|
|
102
167
|
}
|
|
103
168
|
|
|
104
169
|
// Auth middleware for protected routes
|
|
@@ -198,6 +263,156 @@ app.get('/api/auth/me', (req, res) => {
|
|
|
198
263
|
res.json({ user: req.user });
|
|
199
264
|
});
|
|
200
265
|
|
|
266
|
+
// ============================================
|
|
267
|
+
// User Management API (Admin only)
|
|
268
|
+
// ============================================
|
|
269
|
+
|
|
270
|
+
// List all users
|
|
271
|
+
app.get('/api/users', requireRole('admin'), async (req, res) => {
|
|
272
|
+
try {
|
|
273
|
+
const users = await userStore.listUsers();
|
|
274
|
+
res.json({ users });
|
|
275
|
+
} catch (e) {
|
|
276
|
+
res.status(500).json({ error: e.message });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Get single user
|
|
281
|
+
app.get('/api/users/:id', requireRole('admin'), async (req, res) => {
|
|
282
|
+
try {
|
|
283
|
+
const user = await userStore.findUserById(req.params.id);
|
|
284
|
+
if (!user) {
|
|
285
|
+
return res.status(404).json({ error: 'User not found' });
|
|
286
|
+
}
|
|
287
|
+
// Don't return password hash
|
|
288
|
+
const { passwordHash, passwordSalt, ...safeUser } = user;
|
|
289
|
+
res.json({ user: safeUser });
|
|
290
|
+
} catch (e) {
|
|
291
|
+
res.status(500).json({ error: e.message });
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Create new user
|
|
296
|
+
app.post('/api/users', requireRole('admin'), async (req, res) => {
|
|
297
|
+
const { email, password, name, role } = req.body;
|
|
298
|
+
|
|
299
|
+
if (!email || !password) {
|
|
300
|
+
return res.status(400).json({ error: 'Email and password required' });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Validate role
|
|
304
|
+
const validRoles = ['admin', 'engineer', 'qa', 'po'];
|
|
305
|
+
if (role && !validRoles.includes(role)) {
|
|
306
|
+
return res.status(400).json({ error: `Invalid role. Valid roles: ${validRoles.join(', ')}` });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const user = await userStore.createUser({
|
|
311
|
+
email,
|
|
312
|
+
password,
|
|
313
|
+
name: name || email.split('@')[0],
|
|
314
|
+
role: role || 'engineer',
|
|
315
|
+
}, { skipValidation: true }); // Allow simple passwords for dev
|
|
316
|
+
|
|
317
|
+
res.status(201).json({ user });
|
|
318
|
+
} catch (e) {
|
|
319
|
+
if (e.message.includes('already registered')) {
|
|
320
|
+
return res.status(409).json({ error: 'Email already registered' });
|
|
321
|
+
}
|
|
322
|
+
res.status(500).json({ error: e.message });
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Update user
|
|
327
|
+
app.put('/api/users/:id', requireRole('admin'), async (req, res) => {
|
|
328
|
+
const { name, role, active } = req.body;
|
|
329
|
+
|
|
330
|
+
// Validate role if provided
|
|
331
|
+
const validRoles = ['admin', 'engineer', 'qa', 'po'];
|
|
332
|
+
if (role && !validRoles.includes(role)) {
|
|
333
|
+
return res.status(400).json({ error: `Invalid role. Valid roles: ${validRoles.join(', ')}` });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const updates = {};
|
|
338
|
+
if (name !== undefined) updates.name = name;
|
|
339
|
+
if (role !== undefined) updates.role = role;
|
|
340
|
+
if (active !== undefined) updates.active = active;
|
|
341
|
+
|
|
342
|
+
const user = await userStore.updateUser(req.params.id, updates);
|
|
343
|
+
if (!user) {
|
|
344
|
+
return res.status(404).json({ error: 'User not found' });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
res.json({ user });
|
|
348
|
+
} catch (e) {
|
|
349
|
+
res.status(500).json({ error: e.message });
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// Delete user
|
|
354
|
+
app.delete('/api/users/:id', requireRole('admin'), async (req, res) => {
|
|
355
|
+
// Prevent self-deletion
|
|
356
|
+
if (req.user.sub === req.params.id) {
|
|
357
|
+
return res.status(400).json({ error: 'Cannot delete your own account' });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const deleted = await userStore.deleteUser(req.params.id);
|
|
362
|
+
if (!deleted) {
|
|
363
|
+
return res.status(404).json({ error: 'User not found' });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Also invalidate their sessions
|
|
367
|
+
await userStore.invalidateUserSessions(req.params.id);
|
|
368
|
+
|
|
369
|
+
res.json({ success: true });
|
|
370
|
+
} catch (e) {
|
|
371
|
+
res.status(500).json({ error: e.message });
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Reset user password (admin only)
|
|
376
|
+
app.post('/api/users/:id/reset-password', requireRole('admin'), async (req, res) => {
|
|
377
|
+
const { newPassword } = req.body;
|
|
378
|
+
|
|
379
|
+
if (!newPassword) {
|
|
380
|
+
return res.status(400).json({ error: 'New password required' });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
const user = await userStore.findUserById(req.params.id);
|
|
385
|
+
if (!user) {
|
|
386
|
+
return res.status(404).json({ error: 'User not found' });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Use the internal hash function
|
|
390
|
+
const { hash, salt } = hashPassword(newPassword);
|
|
391
|
+
user.passwordHash = hash;
|
|
392
|
+
user.passwordSalt = salt;
|
|
393
|
+
user.updatedAt = new Date().toISOString();
|
|
394
|
+
|
|
395
|
+
// Invalidate all sessions for security
|
|
396
|
+
await userStore.invalidateUserSessions(req.params.id);
|
|
397
|
+
|
|
398
|
+
res.json({ success: true, message: 'Password reset successfully' });
|
|
399
|
+
} catch (e) {
|
|
400
|
+
res.status(500).json({ error: e.message });
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Get available roles
|
|
405
|
+
app.get('/api/roles', (req, res) => {
|
|
406
|
+
res.json({
|
|
407
|
+
roles: [
|
|
408
|
+
{ id: 'admin', name: 'Admin', description: 'Full access to all features' },
|
|
409
|
+
{ id: 'engineer', name: 'Engineer', description: 'Can read, write, deploy, and claim tasks' },
|
|
410
|
+
{ id: 'qa', name: 'QA', description: 'Can read, verify, report bugs, and run tests' },
|
|
411
|
+
{ id: 'po', name: 'Product Owner', description: 'Can read, plan, verify, and approve' },
|
|
412
|
+
],
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
201
416
|
// Serve static files (after auth middleware)
|
|
202
417
|
app.use(express.static(path.join(__dirname, 'dashboard')));
|
|
203
418
|
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control Mapper - Cross-framework control mapping
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Default control mappings between frameworks
|
|
6
|
+
const DEFAULT_MAPPINGS = [
|
|
7
|
+
{
|
|
8
|
+
source: { framework: 'pci-dss', control: 'req-1.1' },
|
|
9
|
+
target: { framework: 'iso27001', control: 'A.13.1' },
|
|
10
|
+
theme: 'network-security',
|
|
11
|
+
confidence: 0.85
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
source: { framework: 'pci-dss', control: 'req-3.4' },
|
|
15
|
+
target: { framework: 'iso27001', control: 'A.8.24' },
|
|
16
|
+
theme: 'encryption',
|
|
17
|
+
confidence: 0.95
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
source: { framework: 'pci-dss', control: 'req-3.4' },
|
|
21
|
+
target: { framework: 'hipaa', control: '164.312(a)(2)(iv)' },
|
|
22
|
+
theme: 'encryption',
|
|
23
|
+
confidence: 0.90
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
source: { framework: 'pci-dss', control: 'req-8.1' },
|
|
27
|
+
target: { framework: 'iso27001', control: 'A.5.15' },
|
|
28
|
+
theme: 'access-control',
|
|
29
|
+
confidence: 0.90
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
source: { framework: 'pci-dss', control: 'req-8.1' },
|
|
33
|
+
target: { framework: 'hipaa', control: '164.312(d)' },
|
|
34
|
+
theme: 'access-control',
|
|
35
|
+
confidence: 0.85
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
source: { framework: 'hipaa', control: '164.312(a)(1)' },
|
|
39
|
+
target: { framework: 'iso27001', control: 'A.5.15' },
|
|
40
|
+
theme: 'access-control',
|
|
41
|
+
confidence: 0.85
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
source: { framework: 'hipaa', control: '164.312(e)(1)' },
|
|
45
|
+
target: { framework: 'iso27001', control: 'A.8.24' },
|
|
46
|
+
theme: 'encryption',
|
|
47
|
+
confidence: 0.90
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
source: { framework: 'hipaa', control: '164.312(e)(1)' },
|
|
51
|
+
target: { framework: 'pci-dss', control: 'req-3.4' },
|
|
52
|
+
theme: 'encryption',
|
|
53
|
+
confidence: 0.85
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// Framework-specific control themes and keywords
|
|
58
|
+
const FRAMEWORK_THEMES = {
|
|
59
|
+
'pci-dss': {
|
|
60
|
+
encryption: ['req-3.4', 'req-3.5', 'req-4.1'],
|
|
61
|
+
'access-control': ['req-7.1', 'req-7.2', 'req-8.1', 'req-8.2'],
|
|
62
|
+
'network-security': ['req-1.1', 'req-1.2', 'req-1.3'],
|
|
63
|
+
logging: ['req-10.1', 'req-10.2', 'req-10.3']
|
|
64
|
+
},
|
|
65
|
+
'iso27001': {
|
|
66
|
+
encryption: ['A.8.24', 'A.10.1'],
|
|
67
|
+
'access-control': ['A.5.15', 'A.5.16', 'A.5.17', 'A.5.18'],
|
|
68
|
+
'network-security': ['A.13.1', 'A.13.2'],
|
|
69
|
+
logging: ['A.8.15', 'A.8.16']
|
|
70
|
+
},
|
|
71
|
+
'hipaa': {
|
|
72
|
+
encryption: ['164.312(a)(2)(iv)', '164.312(e)(1)', '164.312(e)(2)(ii)'],
|
|
73
|
+
'access-control': ['164.312(a)(1)', '164.312(d)'],
|
|
74
|
+
'network-security': ['164.312(e)(1)'],
|
|
75
|
+
logging: ['164.312(b)']
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Unique requirements per framework
|
|
80
|
+
const UNIQUE_REQUIREMENTS = {
|
|
81
|
+
'pci-dss': [
|
|
82
|
+
'Payment card industry specific requirements',
|
|
83
|
+
'PAN masking and truncation',
|
|
84
|
+
'Quarterly vulnerability scans'
|
|
85
|
+
],
|
|
86
|
+
'hipaa': [
|
|
87
|
+
'Protected Health Information (PHI) handling',
|
|
88
|
+
'Business Associate Agreements',
|
|
89
|
+
'Patient rights documentation'
|
|
90
|
+
],
|
|
91
|
+
'iso27001': [
|
|
92
|
+
'Information Security Management System (ISMS)',
|
|
93
|
+
'Risk assessment methodology',
|
|
94
|
+
'Continuous improvement process'
|
|
95
|
+
]
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Custom frameworks storage
|
|
99
|
+
const customFrameworks = new Map();
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create a control mapper instance
|
|
103
|
+
*/
|
|
104
|
+
export function createControlMapper(options = {}) {
|
|
105
|
+
const mappings = options.loadDefaults ? [...DEFAULT_MAPPINGS] : [];
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
map: (source, target) => mapControl({ source, target }),
|
|
109
|
+
findOverlaps: (frameworks) => findOverlaps(frameworks),
|
|
110
|
+
getMappings: () => mappings,
|
|
111
|
+
addMapping: (mapping) => mappings.push(mapping)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Map a control from one framework to another
|
|
117
|
+
*/
|
|
118
|
+
export function mapControl({ source, target }) {
|
|
119
|
+
const result = {
|
|
120
|
+
sourceFramework: source.framework,
|
|
121
|
+
sourceControl: source.control,
|
|
122
|
+
targetFramework: target.framework,
|
|
123
|
+
targetControls: [],
|
|
124
|
+
confidence: 0,
|
|
125
|
+
unmapped: false
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Find direct mappings
|
|
129
|
+
const directMappings = DEFAULT_MAPPINGS.filter(m =>
|
|
130
|
+
m.source.framework === source.framework &&
|
|
131
|
+
m.source.control === source.control &&
|
|
132
|
+
m.target.framework === target.framework
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (directMappings.length > 0) {
|
|
136
|
+
result.targetControls = directMappings.map(m => m.target.control);
|
|
137
|
+
result.confidence = Math.max(...directMappings.map(m => m.confidence));
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Try reverse mappings
|
|
142
|
+
const reverseMappings = DEFAULT_MAPPINGS.filter(m =>
|
|
143
|
+
m.target.framework === source.framework &&
|
|
144
|
+
m.target.control === source.control &&
|
|
145
|
+
m.source.framework === target.framework
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
if (reverseMappings.length > 0) {
|
|
149
|
+
result.targetControls = reverseMappings.map(m => m.source.control);
|
|
150
|
+
result.confidence = Math.max(...reverseMappings.map(m => m.confidence)) * 0.9; // Slightly lower confidence for reverse
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Try theme-based mapping
|
|
155
|
+
const sourceThemes = FRAMEWORK_THEMES[source.framework] || {};
|
|
156
|
+
let sourceTheme = null;
|
|
157
|
+
|
|
158
|
+
for (const [theme, controls] of Object.entries(sourceThemes)) {
|
|
159
|
+
if (controls.includes(source.control)) {
|
|
160
|
+
sourceTheme = theme;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (sourceTheme && FRAMEWORK_THEMES[target.framework]) {
|
|
166
|
+
const targetControls = FRAMEWORK_THEMES[target.framework][sourceTheme];
|
|
167
|
+
if (targetControls && targetControls.length > 0) {
|
|
168
|
+
result.targetControls = targetControls;
|
|
169
|
+
result.confidence = 0.6; // Lower confidence for theme-based
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// No mapping found
|
|
175
|
+
result.unmapped = true;
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Find overlapping controls between frameworks
|
|
181
|
+
*/
|
|
182
|
+
export function findOverlaps(frameworks) {
|
|
183
|
+
const overlaps = [];
|
|
184
|
+
const themes = ['encryption', 'access-control', 'network-security', 'logging'];
|
|
185
|
+
|
|
186
|
+
for (const theme of themes) {
|
|
187
|
+
const frameworkControls = {};
|
|
188
|
+
let count = 0;
|
|
189
|
+
|
|
190
|
+
for (const framework of frameworks) {
|
|
191
|
+
const controls = FRAMEWORK_THEMES[framework]?.[theme];
|
|
192
|
+
if (controls && controls.length > 0) {
|
|
193
|
+
frameworkControls[framework] = controls;
|
|
194
|
+
count++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (count >= 2) {
|
|
199
|
+
const totalControls = Object.values(frameworkControls).reduce((sum, c) => sum + c.length, 0);
|
|
200
|
+
const avgControlsPerFramework = totalControls / count;
|
|
201
|
+
|
|
202
|
+
overlaps.push({
|
|
203
|
+
theme,
|
|
204
|
+
frameworks: Object.keys(frameworkControls),
|
|
205
|
+
controls: frameworkControls,
|
|
206
|
+
overlapPercentage: Math.round((count / frameworks.length) * 100)
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return overlaps;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Generate a cross-reference matrix between frameworks
|
|
216
|
+
*/
|
|
217
|
+
export function generateCrossReference(frameworks, options = {}) {
|
|
218
|
+
if (options.format === 'markdown') {
|
|
219
|
+
return generateMarkdownMatrix(frameworks);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (options.format === 'csv') {
|
|
223
|
+
return generateCsvMatrix(frameworks);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Return object matrix by default
|
|
227
|
+
const matrix = {};
|
|
228
|
+
|
|
229
|
+
for (const source of frameworks) {
|
|
230
|
+
matrix[source] = {};
|
|
231
|
+
for (const target of frameworks) {
|
|
232
|
+
if (source !== target) {
|
|
233
|
+
const mappings = DEFAULT_MAPPINGS.filter(m =>
|
|
234
|
+
(m.source.framework === source && m.target.framework === target) ||
|
|
235
|
+
(m.target.framework === source && m.source.framework === target)
|
|
236
|
+
);
|
|
237
|
+
matrix[source][target] = mappings.length;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return matrix;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Generate markdown cross-reference matrix
|
|
247
|
+
*/
|
|
248
|
+
function generateMarkdownMatrix(frameworks) {
|
|
249
|
+
const frameworkNames = {
|
|
250
|
+
'pci-dss': 'PCI DSS',
|
|
251
|
+
'iso27001': 'ISO 27001',
|
|
252
|
+
'hipaa': 'HIPAA'
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
let md = '| Framework |';
|
|
256
|
+
for (const f of frameworks) {
|
|
257
|
+
md += ` ${frameworkNames[f] || f} |`;
|
|
258
|
+
}
|
|
259
|
+
md += '\n|---|';
|
|
260
|
+
for (let i = 0; i < frameworks.length; i++) {
|
|
261
|
+
md += '---|';
|
|
262
|
+
}
|
|
263
|
+
md += '\n';
|
|
264
|
+
|
|
265
|
+
for (const source of frameworks) {
|
|
266
|
+
md += `| ${frameworkNames[source] || source} |`;
|
|
267
|
+
for (const target of frameworks) {
|
|
268
|
+
if (source === target) {
|
|
269
|
+
md += ' - |';
|
|
270
|
+
} else {
|
|
271
|
+
const count = DEFAULT_MAPPINGS.filter(m =>
|
|
272
|
+
(m.source.framework === source && m.target.framework === target) ||
|
|
273
|
+
(m.target.framework === source && m.source.framework === target)
|
|
274
|
+
).length;
|
|
275
|
+
md += ` ${count} |`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
md += '\n';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return md;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Generate CSV cross-reference matrix
|
|
286
|
+
*/
|
|
287
|
+
function generateCsvMatrix(frameworks) {
|
|
288
|
+
const frameworkNames = {
|
|
289
|
+
'pci-dss': 'PCI DSS',
|
|
290
|
+
'iso27001': 'ISO 27001',
|
|
291
|
+
'hipaa': 'HIPAA'
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
let csv = 'Framework,' + frameworks.map(f => frameworkNames[f] || f).join(',') + '\n';
|
|
295
|
+
|
|
296
|
+
for (const source of frameworks) {
|
|
297
|
+
csv += frameworkNames[source] || source;
|
|
298
|
+
for (const target of frameworks) {
|
|
299
|
+
if (source === target) {
|
|
300
|
+
csv += ',-';
|
|
301
|
+
} else {
|
|
302
|
+
const count = DEFAULT_MAPPINGS.filter(m =>
|
|
303
|
+
(m.source.framework === source && m.target.framework === target) ||
|
|
304
|
+
(m.target.framework === source && m.source.framework === target)
|
|
305
|
+
).length;
|
|
306
|
+
csv += `,${count}`;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
csv += '\n';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return csv;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Import a custom framework
|
|
317
|
+
*/
|
|
318
|
+
export function importFramework(framework, options = {}) {
|
|
319
|
+
// Validate required fields
|
|
320
|
+
if (!framework.id) {
|
|
321
|
+
throw new Error('Framework id is required');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!framework.name) {
|
|
325
|
+
throw new Error('Framework name is required');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const result = {
|
|
329
|
+
imported: true,
|
|
330
|
+
frameworkId: framework.id,
|
|
331
|
+
controlCount: framework.controls?.length || 0,
|
|
332
|
+
mappingsCreated: 0
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Store the framework
|
|
336
|
+
customFrameworks.set(framework.id, framework);
|
|
337
|
+
|
|
338
|
+
// Auto-map to existing frameworks if requested
|
|
339
|
+
if (options.autoMap && framework.controls) {
|
|
340
|
+
for (const control of framework.controls) {
|
|
341
|
+
// Check for explicit mappings
|
|
342
|
+
if (control.mappings) {
|
|
343
|
+
for (const mapping of control.mappings) {
|
|
344
|
+
const [targetFramework, targetControl] = mapping.split(':');
|
|
345
|
+
DEFAULT_MAPPINGS.push({
|
|
346
|
+
source: { framework: framework.id, control: control.id },
|
|
347
|
+
target: { framework: targetFramework, control: targetControl },
|
|
348
|
+
theme: 'custom',
|
|
349
|
+
confidence: 0.9
|
|
350
|
+
});
|
|
351
|
+
result.mappingsCreated++;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Check for keyword-based mappings
|
|
356
|
+
if (control.keywords) {
|
|
357
|
+
for (const keyword of control.keywords) {
|
|
358
|
+
const keywordLower = keyword.toLowerCase();
|
|
359
|
+
// Map to encryption controls
|
|
360
|
+
if (keywordLower.includes('encrypt') || keywordLower.includes('data protection')) {
|
|
361
|
+
for (const [fw, themes] of Object.entries(FRAMEWORK_THEMES)) {
|
|
362
|
+
if (themes.encryption) {
|
|
363
|
+
DEFAULT_MAPPINGS.push({
|
|
364
|
+
source: { framework: framework.id, control: control.id },
|
|
365
|
+
target: { framework: fw, control: themes.encryption[0] },
|
|
366
|
+
theme: 'encryption',
|
|
367
|
+
confidence: 0.7
|
|
368
|
+
});
|
|
369
|
+
result.mappingsCreated++;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Map to access control
|
|
374
|
+
if (keywordLower.includes('access')) {
|
|
375
|
+
for (const [fw, themes] of Object.entries(FRAMEWORK_THEMES)) {
|
|
376
|
+
if (themes['access-control']) {
|
|
377
|
+
DEFAULT_MAPPINGS.push({
|
|
378
|
+
source: { framework: framework.id, control: control.id },
|
|
379
|
+
target: { framework: fw, control: themes['access-control'][0] },
|
|
380
|
+
theme: 'access-control',
|
|
381
|
+
confidence: 0.7
|
|
382
|
+
});
|
|
383
|
+
result.mappingsCreated++;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export default {
|
|
396
|
+
createControlMapper,
|
|
397
|
+
mapControl,
|
|
398
|
+
findOverlaps,
|
|
399
|
+
generateCrossReference,
|
|
400
|
+
importFramework
|
|
401
|
+
};
|