tlc-claude-code 1.3.0 → 1.4.1

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.
Files changed (105) hide show
  1. package/dashboard/dist/components/AuditPane.d.ts +30 -0
  2. package/dashboard/dist/components/AuditPane.js +127 -0
  3. package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
  4. package/dashboard/dist/components/AuditPane.test.js +339 -0
  5. package/dashboard/dist/components/CompliancePane.d.ts +39 -0
  6. package/dashboard/dist/components/CompliancePane.js +96 -0
  7. package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
  8. package/dashboard/dist/components/CompliancePane.test.js +183 -0
  9. package/dashboard/dist/components/SSOPane.d.ts +36 -0
  10. package/dashboard/dist/components/SSOPane.js +71 -0
  11. package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
  12. package/dashboard/dist/components/SSOPane.test.js +155 -0
  13. package/dashboard/dist/components/WorkspaceDocsPane.js +0 -16
  14. package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
  15. package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
  16. package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
  17. package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
  18. package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
  19. package/package.json +1 -1
  20. package/server/lib/access-control-doc.js +541 -0
  21. package/server/lib/access-control-doc.test.js +672 -0
  22. package/server/lib/adr-generator.js +423 -0
  23. package/server/lib/adr-generator.test.js +586 -0
  24. package/server/lib/agent-progress-monitor.js +223 -0
  25. package/server/lib/agent-progress-monitor.test.js +202 -0
  26. package/server/lib/audit-attribution.js +191 -0
  27. package/server/lib/audit-attribution.test.js +359 -0
  28. package/server/lib/audit-classifier.js +202 -0
  29. package/server/lib/audit-classifier.test.js +209 -0
  30. package/server/lib/audit-command.js +275 -0
  31. package/server/lib/audit-command.test.js +325 -0
  32. package/server/lib/audit-exporter.js +380 -0
  33. package/server/lib/audit-exporter.test.js +464 -0
  34. package/server/lib/audit-logger.js +236 -0
  35. package/server/lib/audit-logger.test.js +364 -0
  36. package/server/lib/audit-query.js +257 -0
  37. package/server/lib/audit-query.test.js +352 -0
  38. package/server/lib/audit-storage.js +269 -0
  39. package/server/lib/audit-storage.test.js +272 -0
  40. package/server/lib/bulk-repo-init.js +342 -0
  41. package/server/lib/bulk-repo-init.test.js +388 -0
  42. package/server/lib/compliance-checklist.js +866 -0
  43. package/server/lib/compliance-checklist.test.js +476 -0
  44. package/server/lib/compliance-command.js +616 -0
  45. package/server/lib/compliance-command.test.js +551 -0
  46. package/server/lib/compliance-reporter.js +692 -0
  47. package/server/lib/compliance-reporter.test.js +707 -0
  48. package/server/lib/data-flow-doc.js +665 -0
  49. package/server/lib/data-flow-doc.test.js +659 -0
  50. package/server/lib/ephemeral-storage.js +249 -0
  51. package/server/lib/ephemeral-storage.test.js +254 -0
  52. package/server/lib/evidence-collector.js +627 -0
  53. package/server/lib/evidence-collector.test.js +901 -0
  54. package/server/lib/flow-diagram-generator.js +474 -0
  55. package/server/lib/flow-diagram-generator.test.js +446 -0
  56. package/server/lib/idp-manager.js +626 -0
  57. package/server/lib/idp-manager.test.js +587 -0
  58. package/server/lib/memory-exclusion.js +326 -0
  59. package/server/lib/memory-exclusion.test.js +241 -0
  60. package/server/lib/mfa-handler.js +452 -0
  61. package/server/lib/mfa-handler.test.js +490 -0
  62. package/server/lib/oauth-flow.js +375 -0
  63. package/server/lib/oauth-flow.test.js +487 -0
  64. package/server/lib/oauth-registry.js +190 -0
  65. package/server/lib/oauth-registry.test.js +306 -0
  66. package/server/lib/readme-generator.js +490 -0
  67. package/server/lib/readme-generator.test.js +493 -0
  68. package/server/lib/repo-dependency-tracker.js +261 -0
  69. package/server/lib/repo-dependency-tracker.test.js +350 -0
  70. package/server/lib/retention-policy.js +281 -0
  71. package/server/lib/retention-policy.test.js +486 -0
  72. package/server/lib/role-mapper.js +236 -0
  73. package/server/lib/role-mapper.test.js +395 -0
  74. package/server/lib/saml-provider.js +765 -0
  75. package/server/lib/saml-provider.test.js +643 -0
  76. package/server/lib/security-policy-generator.js +682 -0
  77. package/server/lib/security-policy-generator.test.js +544 -0
  78. package/server/lib/sensitive-detector.js +112 -0
  79. package/server/lib/sensitive-detector.test.js +209 -0
  80. package/server/lib/service-interaction-diagram.js +700 -0
  81. package/server/lib/service-interaction-diagram.test.js +638 -0
  82. package/server/lib/service-summary.js +553 -0
  83. package/server/lib/service-summary.test.js +619 -0
  84. package/server/lib/session-purge.js +460 -0
  85. package/server/lib/session-purge.test.js +312 -0
  86. package/server/lib/sso-command.js +544 -0
  87. package/server/lib/sso-command.test.js +552 -0
  88. package/server/lib/sso-session.js +492 -0
  89. package/server/lib/sso-session.test.js +670 -0
  90. package/server/lib/workspace-command.js +249 -0
  91. package/server/lib/workspace-command.test.js +264 -0
  92. package/server/lib/workspace-config.js +270 -0
  93. package/server/lib/workspace-config.test.js +312 -0
  94. package/server/lib/workspace-docs-command.js +547 -0
  95. package/server/lib/workspace-docs-command.test.js +692 -0
  96. package/server/lib/workspace-memory.js +451 -0
  97. package/server/lib/workspace-memory.test.js +403 -0
  98. package/server/lib/workspace-scanner.js +452 -0
  99. package/server/lib/workspace-scanner.test.js +677 -0
  100. package/server/lib/workspace-test-runner.js +315 -0
  101. package/server/lib/workspace-test-runner.test.js +294 -0
  102. package/server/lib/zero-retention-command.js +439 -0
  103. package/server/lib/zero-retention-command.test.js +448 -0
  104. package/server/lib/zero-retention.js +322 -0
  105. package/server/lib/zero-retention.test.js +258 -0
@@ -0,0 +1,452 @@
1
+ /**
2
+ * MFA Handler
3
+ * Multi-factor authentication support with TOTP and backup codes
4
+ */
5
+
6
+ const crypto = require('crypto');
7
+
8
+ // Base32 alphabet for encoding secrets
9
+ const BASE32_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
10
+
11
+ // TOTP configuration (RFC 6238)
12
+ const TOTP_CONFIG = {
13
+ algorithm: 'sha1',
14
+ digits: 6,
15
+ period: 30, // 30-second time step
16
+ window: 1, // Allow +-1 time step for clock drift
17
+ };
18
+
19
+ /**
20
+ * Encode bytes to base32
21
+ * @param {Buffer} buffer - Bytes to encode
22
+ * @returns {string} Base32 encoded string
23
+ */
24
+ function base32Encode(buffer) {
25
+ let bits = '';
26
+ for (const byte of buffer) {
27
+ bits += byte.toString(2).padStart(8, '0');
28
+ }
29
+
30
+ let result = '';
31
+ for (let i = 0; i < bits.length; i += 5) {
32
+ const chunk = bits.slice(i, i + 5).padEnd(5, '0');
33
+ result += BASE32_CHARS[parseInt(chunk, 2)];
34
+ }
35
+
36
+ return result;
37
+ }
38
+
39
+ /**
40
+ * Decode base32 string to bytes
41
+ * @param {string} str - Base32 encoded string
42
+ * @returns {Buffer} Decoded bytes
43
+ */
44
+ function base32Decode(str) {
45
+ let bits = '';
46
+ for (const char of str.toUpperCase()) {
47
+ const val = BASE32_CHARS.indexOf(char);
48
+ if (val >= 0) {
49
+ bits += val.toString(2).padStart(5, '0');
50
+ }
51
+ }
52
+
53
+ const bytes = [];
54
+ for (let i = 0; i + 8 <= bits.length; i += 8) {
55
+ bytes.push(parseInt(bits.slice(i, i + 8), 2));
56
+ }
57
+
58
+ return Buffer.from(bytes);
59
+ }
60
+
61
+ /**
62
+ * Generate TOTP code for given secret and counter
63
+ * @param {string} secret - Base32 encoded secret
64
+ * @param {number} counter - Time counter
65
+ * @returns {string} 6-digit TOTP code
66
+ */
67
+ function generateTotp(secret, counter) {
68
+ const key = base32Decode(secret);
69
+
70
+ // Convert counter to 8-byte big-endian buffer
71
+ const counterBuffer = Buffer.alloc(8);
72
+ counterBuffer.writeBigInt64BE(BigInt(counter));
73
+
74
+ // Generate HMAC-SHA1
75
+ const hmac = crypto.createHmac(TOTP_CONFIG.algorithm, key);
76
+ hmac.update(counterBuffer);
77
+ const hash = hmac.digest();
78
+
79
+ // Dynamic truncation (RFC 4226)
80
+ const offset = hash[hash.length - 1] & 0x0f;
81
+ const code = (
82
+ ((hash[offset] & 0x7f) << 24) |
83
+ ((hash[offset + 1] & 0xff) << 16) |
84
+ ((hash[offset + 2] & 0xff) << 8) |
85
+ (hash[offset + 3] & 0xff)
86
+ ) % Math.pow(10, TOTP_CONFIG.digits);
87
+
88
+ return code.toString().padStart(TOTP_CONFIG.digits, '0');
89
+ }
90
+
91
+ /**
92
+ * Get current time counter
93
+ * @param {number} offset - Time step offset
94
+ * @returns {number} Time counter
95
+ */
96
+ function getTimeCounter(offset = 0) {
97
+ return Math.floor(Date.now() / 1000 / TOTP_CONFIG.period) + offset;
98
+ }
99
+
100
+ /**
101
+ * Generate TOTP secret for a user
102
+ * @param {string} email - User's email for QR code URL
103
+ * @returns {Object} Secret and QR code URL
104
+ */
105
+ function generateSecret(email) {
106
+ // Generate 20 random bytes for the secret (160 bits as recommended)
107
+ const secretBytes = crypto.randomBytes(20);
108
+ const secret = base32Encode(secretBytes);
109
+
110
+ // Build otpauth:// URL for QR code
111
+ const encodedEmail = encodeURIComponent(email);
112
+ const qrCodeUrl = `otpauth://totp/TLC:${encodedEmail}?secret=${secret}&issuer=TLC`;
113
+
114
+ return {
115
+ secret,
116
+ qrCodeUrl,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Validate TOTP code
122
+ * @param {string} secret - Base32 encoded secret
123
+ * @param {string} code - 6-digit code to validate
124
+ * @returns {boolean} Whether code is valid
125
+ */
126
+ function validateCode(secret, code) {
127
+ if (!secret || !code) {
128
+ return false;
129
+ }
130
+
131
+ // Validate code format
132
+ if (typeof code !== 'string' || code.length !== TOTP_CONFIG.digits) {
133
+ return false;
134
+ }
135
+
136
+ if (!/^\d+$/.test(code)) {
137
+ return false;
138
+ }
139
+
140
+ // Check code against current time and allowed window
141
+ const currentCounter = getTimeCounter();
142
+
143
+ for (let offset = -TOTP_CONFIG.window; offset <= TOTP_CONFIG.window; offset++) {
144
+ const expectedCode = generateTotp(secret, currentCounter + offset);
145
+ if (timingSafeEqual(code, expectedCode)) {
146
+ return true;
147
+ }
148
+ }
149
+
150
+ return false;
151
+ }
152
+
153
+ /**
154
+ * Timing-safe string comparison
155
+ * @param {string} a - First string
156
+ * @param {string} b - Second string
157
+ * @returns {boolean} Whether strings are equal
158
+ */
159
+ function timingSafeEqual(a, b) {
160
+ if (a.length !== b.length) {
161
+ return false;
162
+ }
163
+
164
+ try {
165
+ return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
166
+ } catch {
167
+ return false;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Generate random alphanumeric string
173
+ * @param {number} length - String length
174
+ * @returns {string} Random alphanumeric string (uppercase)
175
+ */
176
+ function generateAlphanumeric(length) {
177
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
178
+ let result = '';
179
+
180
+ const randomBytes = crypto.randomBytes(length);
181
+ for (let i = 0; i < length; i++) {
182
+ result += chars[randomBytes[i] % chars.length];
183
+ }
184
+
185
+ return result;
186
+ }
187
+
188
+ /**
189
+ * Hash a backup code for storage
190
+ * @param {string} code - Plain backup code
191
+ * @returns {string} Hashed code
192
+ */
193
+ function hashBackupCode(code) {
194
+ return crypto.createHash('sha256').update(code.toUpperCase()).digest('hex');
195
+ }
196
+
197
+ /**
198
+ * Generate backup codes
199
+ * @returns {Object} Plain codes (for user) and hashed codes (for storage)
200
+ */
201
+ function generateBackupCodes() {
202
+ const codes = [];
203
+ const hashedCodes = [];
204
+
205
+ for (let i = 0; i < 10; i++) {
206
+ const code = generateAlphanumeric(8);
207
+ codes.push(code);
208
+ hashedCodes.push(hashBackupCode(code));
209
+ }
210
+
211
+ return {
212
+ codes,
213
+ hashedCodes,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Validate a backup code
219
+ * @param {string} code - Backup code to validate
220
+ * @param {string[]} hashedCodes - Array of hashed backup codes
221
+ * @param {Set<number>} usedCodes - Set of used code indices
222
+ * @returns {Object} Validation result with valid flag and index
223
+ */
224
+ function validateBackupCode(code, hashedCodes, usedCodes) {
225
+ if (!code || !hashedCodes) {
226
+ return { valid: false };
227
+ }
228
+
229
+ const normalizedCode = code.toUpperCase();
230
+ const codeHash = hashBackupCode(normalizedCode);
231
+
232
+ for (let i = 0; i < hashedCodes.length; i++) {
233
+ // Skip used codes
234
+ if (usedCodes && usedCodes.has(i)) {
235
+ continue;
236
+ }
237
+
238
+ if (timingSafeEqual(codeHash, hashedCodes[i])) {
239
+ return { valid: true, index: i };
240
+ }
241
+ }
242
+
243
+ return { valid: false };
244
+ }
245
+
246
+ /**
247
+ * Check if MFA is required for user based on user settings and policy
248
+ * @param {Object} user - User object
249
+ * @param {Object} policy - MFA policy configuration
250
+ * @returns {boolean} Whether MFA is required
251
+ */
252
+ function isMfaRequired(user, policy) {
253
+ if (!user) {
254
+ return false;
255
+ }
256
+
257
+ // User has MFA enabled
258
+ if (user.mfaEnabled) {
259
+ return true;
260
+ }
261
+
262
+ // Check policy
263
+ if (!policy) {
264
+ return false;
265
+ }
266
+
267
+ // Policy requires MFA for all users
268
+ if (policy.required) {
269
+ return true;
270
+ }
271
+
272
+ // Policy requires MFA for specific roles
273
+ if (policy.requiredRoles && user.role) {
274
+ return policy.requiredRoles.includes(user.role);
275
+ }
276
+
277
+ return false;
278
+ }
279
+
280
+ /**
281
+ * Enable MFA requirement for user
282
+ * @param {Object} user - User object
283
+ * @returns {Object} Updated user object
284
+ */
285
+ function enforceMfa(user) {
286
+ if (!user) {
287
+ throw new Error('User is required');
288
+ }
289
+
290
+ return {
291
+ ...user,
292
+ mfaEnabled: true,
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Disable MFA for user
298
+ * @param {Object} user - User object
299
+ * @returns {Object} Updated user object with MFA data removed
300
+ */
301
+ function disableMfa(user) {
302
+ if (!user) {
303
+ throw new Error('User is required');
304
+ }
305
+
306
+ const { mfaSecret, backupCodes, usedBackupCodes, ...rest } = user;
307
+
308
+ return {
309
+ ...rest,
310
+ mfaEnabled: false,
311
+ };
312
+ }
313
+
314
+ /**
315
+ * Create in-memory MFA store
316
+ * @returns {Object} MFA store
317
+ */
318
+ function createMfaStore() {
319
+ const mfaData = new Map();
320
+
321
+ return {
322
+ /**
323
+ * Set up MFA for a user
324
+ * @param {string} userId - User ID
325
+ * @param {string} email - User email
326
+ * @returns {Object} Setup data including secret, QR URL, and backup codes
327
+ */
328
+ async setupMfa(userId, email) {
329
+ const { secret, qrCodeUrl } = generateSecret(email);
330
+ const { codes, hashedCodes } = generateBackupCodes();
331
+
332
+ mfaData.set(userId, {
333
+ secret,
334
+ hashedCodes,
335
+ usedCodes: new Set(),
336
+ enabled: true,
337
+ setupAt: new Date().toISOString(),
338
+ });
339
+
340
+ return {
341
+ secret,
342
+ qrCodeUrl,
343
+ backupCodes: codes,
344
+ };
345
+ },
346
+
347
+ /**
348
+ * Verify TOTP code for user
349
+ * @param {string} userId - User ID
350
+ * @param {string} code - TOTP code
351
+ * @returns {Object} Verification result
352
+ */
353
+ async verifyMfa(userId, code) {
354
+ const data = mfaData.get(userId);
355
+
356
+ if (!data || !data.enabled) {
357
+ return { valid: false, error: 'MFA not enabled' };
358
+ }
359
+
360
+ const valid = validateCode(data.secret, code);
361
+
362
+ return { valid };
363
+ },
364
+
365
+ /**
366
+ * Verify backup code for user (marks as used if valid)
367
+ * @param {string} userId - User ID
368
+ * @param {string} code - Backup code
369
+ * @returns {Object} Verification result
370
+ */
371
+ async verifyBackupCode(userId, code) {
372
+ const data = mfaData.get(userId);
373
+
374
+ if (!data || !data.enabled) {
375
+ return { valid: false, error: 'MFA not enabled' };
376
+ }
377
+
378
+ const result = validateBackupCode(code, data.hashedCodes, data.usedCodes);
379
+
380
+ if (result.valid) {
381
+ // Mark code as used
382
+ data.usedCodes.add(result.index);
383
+ }
384
+
385
+ return { valid: result.valid };
386
+ },
387
+
388
+ /**
389
+ * Regenerate backup codes for user
390
+ * @param {string} userId - User ID
391
+ * @returns {string[]} New backup codes
392
+ */
393
+ async regenerateBackupCodes(userId) {
394
+ const data = mfaData.get(userId);
395
+
396
+ if (!data) {
397
+ throw new Error('MFA not set up for user');
398
+ }
399
+
400
+ const { codes, hashedCodes } = generateBackupCodes();
401
+
402
+ data.hashedCodes = hashedCodes;
403
+ data.usedCodes = new Set();
404
+
405
+ return codes;
406
+ },
407
+
408
+ /**
409
+ * Get MFA status for user
410
+ * @param {string} userId - User ID
411
+ * @returns {Object} MFA status
412
+ */
413
+ async getMfaStatus(userId) {
414
+ const data = mfaData.get(userId);
415
+
416
+ if (!data) {
417
+ return { enabled: false };
418
+ }
419
+
420
+ const backupCodesRemaining = data.hashedCodes.length - data.usedCodes.size;
421
+
422
+ return {
423
+ enabled: data.enabled,
424
+ backupCodesRemaining,
425
+ setupAt: data.setupAt,
426
+ };
427
+ },
428
+
429
+ /**
430
+ * Remove MFA from user
431
+ * @param {string} userId - User ID
432
+ */
433
+ async removeMfa(userId) {
434
+ mfaData.delete(userId);
435
+ },
436
+ };
437
+ }
438
+
439
+ module.exports = {
440
+ generateSecret,
441
+ validateCode,
442
+ generateBackupCodes,
443
+ validateBackupCode,
444
+ isMfaRequired,
445
+ enforceMfa,
446
+ disableMfa,
447
+ createMfaStore,
448
+ // Export for testing
449
+ base32Encode,
450
+ base32Decode,
451
+ generateTotp,
452
+ };