tlc-claude-code 1.2.29 → 1.4.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.
Files changed (182) 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/UsagePane.d.ts +13 -0
  14. package/dashboard/dist/components/UsagePane.js +51 -0
  15. package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
  16. package/dashboard/dist/components/UsagePane.test.js +142 -0
  17. package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
  18. package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
  19. package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
  20. package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
  21. package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
  22. package/dashboard/dist/components/WorkspacePane.js +17 -0
  23. package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
  24. package/dashboard/dist/components/WorkspacePane.test.js +84 -0
  25. package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
  26. package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
  27. package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
  28. package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
  29. package/package.json +1 -1
  30. package/server/lib/access-control-doc.js +541 -0
  31. package/server/lib/access-control-doc.test.js +672 -0
  32. package/server/lib/adr-generator.js +423 -0
  33. package/server/lib/adr-generator.test.js +586 -0
  34. package/server/lib/agent-progress-monitor.js +223 -0
  35. package/server/lib/agent-progress-monitor.test.js +202 -0
  36. package/server/lib/architecture-command.js +450 -0
  37. package/server/lib/architecture-command.test.js +754 -0
  38. package/server/lib/ast-analyzer.js +324 -0
  39. package/server/lib/ast-analyzer.test.js +437 -0
  40. package/server/lib/audit-attribution.js +191 -0
  41. package/server/lib/audit-attribution.test.js +359 -0
  42. package/server/lib/audit-classifier.js +202 -0
  43. package/server/lib/audit-classifier.test.js +209 -0
  44. package/server/lib/audit-command.js +275 -0
  45. package/server/lib/audit-command.test.js +325 -0
  46. package/server/lib/audit-exporter.js +380 -0
  47. package/server/lib/audit-exporter.test.js +464 -0
  48. package/server/lib/audit-logger.js +236 -0
  49. package/server/lib/audit-logger.test.js +364 -0
  50. package/server/lib/audit-query.js +257 -0
  51. package/server/lib/audit-query.test.js +352 -0
  52. package/server/lib/audit-storage.js +269 -0
  53. package/server/lib/audit-storage.test.js +272 -0
  54. package/server/lib/auth-system.test.js +4 -1
  55. package/server/lib/boundary-detector.js +427 -0
  56. package/server/lib/boundary-detector.test.js +320 -0
  57. package/server/lib/budget-alerts.js +138 -0
  58. package/server/lib/budget-alerts.test.js +235 -0
  59. package/server/lib/bulk-repo-init.js +342 -0
  60. package/server/lib/bulk-repo-init.test.js +388 -0
  61. package/server/lib/candidates-tracker.js +210 -0
  62. package/server/lib/candidates-tracker.test.js +300 -0
  63. package/server/lib/checkpoint-manager.js +251 -0
  64. package/server/lib/checkpoint-manager.test.js +474 -0
  65. package/server/lib/circular-detector.js +337 -0
  66. package/server/lib/circular-detector.test.js +353 -0
  67. package/server/lib/cohesion-analyzer.js +310 -0
  68. package/server/lib/cohesion-analyzer.test.js +447 -0
  69. package/server/lib/compliance-checklist.js +866 -0
  70. package/server/lib/compliance-checklist.test.js +476 -0
  71. package/server/lib/compliance-command.js +616 -0
  72. package/server/lib/compliance-command.test.js +551 -0
  73. package/server/lib/compliance-reporter.js +692 -0
  74. package/server/lib/compliance-reporter.test.js +707 -0
  75. package/server/lib/contract-testing.js +625 -0
  76. package/server/lib/contract-testing.test.js +342 -0
  77. package/server/lib/conversion-planner.js +469 -0
  78. package/server/lib/conversion-planner.test.js +361 -0
  79. package/server/lib/convert-command.js +351 -0
  80. package/server/lib/convert-command.test.js +608 -0
  81. package/server/lib/coupling-calculator.js +189 -0
  82. package/server/lib/coupling-calculator.test.js +509 -0
  83. package/server/lib/data-flow-doc.js +665 -0
  84. package/server/lib/data-flow-doc.test.js +659 -0
  85. package/server/lib/dependency-graph.js +367 -0
  86. package/server/lib/dependency-graph.test.js +516 -0
  87. package/server/lib/duplication-detector.js +349 -0
  88. package/server/lib/duplication-detector.test.js +401 -0
  89. package/server/lib/ephemeral-storage.js +249 -0
  90. package/server/lib/ephemeral-storage.test.js +254 -0
  91. package/server/lib/evidence-collector.js +627 -0
  92. package/server/lib/evidence-collector.test.js +901 -0
  93. package/server/lib/example-service.js +616 -0
  94. package/server/lib/example-service.test.js +397 -0
  95. package/server/lib/flow-diagram-generator.js +474 -0
  96. package/server/lib/flow-diagram-generator.test.js +446 -0
  97. package/server/lib/idp-manager.js +626 -0
  98. package/server/lib/idp-manager.test.js +587 -0
  99. package/server/lib/impact-scorer.js +184 -0
  100. package/server/lib/impact-scorer.test.js +211 -0
  101. package/server/lib/memory-exclusion.js +326 -0
  102. package/server/lib/memory-exclusion.test.js +241 -0
  103. package/server/lib/mermaid-generator.js +358 -0
  104. package/server/lib/mermaid-generator.test.js +301 -0
  105. package/server/lib/messaging-patterns.js +750 -0
  106. package/server/lib/messaging-patterns.test.js +213 -0
  107. package/server/lib/mfa-handler.js +452 -0
  108. package/server/lib/mfa-handler.test.js +490 -0
  109. package/server/lib/microservice-template.js +386 -0
  110. package/server/lib/microservice-template.test.js +325 -0
  111. package/server/lib/new-project-microservice.js +450 -0
  112. package/server/lib/new-project-microservice.test.js +600 -0
  113. package/server/lib/oauth-flow.js +375 -0
  114. package/server/lib/oauth-flow.test.js +487 -0
  115. package/server/lib/oauth-registry.js +190 -0
  116. package/server/lib/oauth-registry.test.js +306 -0
  117. package/server/lib/readme-generator.js +490 -0
  118. package/server/lib/readme-generator.test.js +493 -0
  119. package/server/lib/refactor-command.js +326 -0
  120. package/server/lib/refactor-command.test.js +528 -0
  121. package/server/lib/refactor-executor.js +254 -0
  122. package/server/lib/refactor-executor.test.js +305 -0
  123. package/server/lib/refactor-observer.js +292 -0
  124. package/server/lib/refactor-observer.test.js +422 -0
  125. package/server/lib/refactor-progress.js +193 -0
  126. package/server/lib/refactor-progress.test.js +251 -0
  127. package/server/lib/refactor-reporter.js +237 -0
  128. package/server/lib/refactor-reporter.test.js +247 -0
  129. package/server/lib/repo-dependency-tracker.js +261 -0
  130. package/server/lib/repo-dependency-tracker.test.js +350 -0
  131. package/server/lib/retention-policy.js +281 -0
  132. package/server/lib/retention-policy.test.js +486 -0
  133. package/server/lib/role-mapper.js +236 -0
  134. package/server/lib/role-mapper.test.js +395 -0
  135. package/server/lib/saml-provider.js +765 -0
  136. package/server/lib/saml-provider.test.js +643 -0
  137. package/server/lib/security-policy-generator.js +682 -0
  138. package/server/lib/security-policy-generator.test.js +544 -0
  139. package/server/lib/semantic-analyzer.js +198 -0
  140. package/server/lib/semantic-analyzer.test.js +474 -0
  141. package/server/lib/sensitive-detector.js +112 -0
  142. package/server/lib/sensitive-detector.test.js +209 -0
  143. package/server/lib/service-interaction-diagram.js +700 -0
  144. package/server/lib/service-interaction-diagram.test.js +638 -0
  145. package/server/lib/service-scaffold.js +486 -0
  146. package/server/lib/service-scaffold.test.js +373 -0
  147. package/server/lib/service-summary.js +553 -0
  148. package/server/lib/service-summary.test.js +619 -0
  149. package/server/lib/session-purge.js +460 -0
  150. package/server/lib/session-purge.test.js +312 -0
  151. package/server/lib/shared-kernel.js +578 -0
  152. package/server/lib/shared-kernel.test.js +255 -0
  153. package/server/lib/sso-command.js +544 -0
  154. package/server/lib/sso-command.test.js +552 -0
  155. package/server/lib/sso-session.js +492 -0
  156. package/server/lib/sso-session.test.js +670 -0
  157. package/server/lib/traefik-config.js +282 -0
  158. package/server/lib/traefik-config.test.js +312 -0
  159. package/server/lib/usage-command.js +218 -0
  160. package/server/lib/usage-command.test.js +391 -0
  161. package/server/lib/usage-formatter.js +192 -0
  162. package/server/lib/usage-formatter.test.js +267 -0
  163. package/server/lib/usage-history.js +122 -0
  164. package/server/lib/usage-history.test.js +206 -0
  165. package/server/lib/workspace-command.js +249 -0
  166. package/server/lib/workspace-command.test.js +264 -0
  167. package/server/lib/workspace-config.js +270 -0
  168. package/server/lib/workspace-config.test.js +312 -0
  169. package/server/lib/workspace-docs-command.js +547 -0
  170. package/server/lib/workspace-docs-command.test.js +692 -0
  171. package/server/lib/workspace-memory.js +451 -0
  172. package/server/lib/workspace-memory.test.js +403 -0
  173. package/server/lib/workspace-scanner.js +452 -0
  174. package/server/lib/workspace-scanner.test.js +677 -0
  175. package/server/lib/workspace-test-runner.js +315 -0
  176. package/server/lib/workspace-test-runner.test.js +294 -0
  177. package/server/lib/zero-retention-command.js +439 -0
  178. package/server/lib/zero-retention-command.test.js +448 -0
  179. package/server/lib/zero-retention.js +322 -0
  180. package/server/lib/zero-retention.test.js +258 -0
  181. package/server/package-lock.json +14 -0
  182. package/server/package.json +1 -0
@@ -0,0 +1,492 @@
1
+ /**
2
+ * SSO Session Manager
3
+ *
4
+ * Enhanced session management for SSO with IdP integration.
5
+ * Handles session creation from IdP authentication, token storage,
6
+ * session timeout, single logout, refresh, and concurrent session limits.
7
+ */
8
+
9
+ const crypto = require('crypto');
10
+
11
+ /**
12
+ * Default session configuration
13
+ */
14
+ const SESSION_DEFAULTS = {
15
+ sessionDuration: 86400000, // 24 hours in milliseconds
16
+ maxConcurrentSessions: 5,
17
+ tokenRefreshThreshold: 300000, // 5 minutes before token expiry
18
+ };
19
+
20
+ /**
21
+ * Generate a UUID for session IDs
22
+ * @returns {string} UUID
23
+ */
24
+ function generateSessionId() {
25
+ return crypto.randomUUID();
26
+ }
27
+
28
+ /**
29
+ * Creates an SSO Session Manager instance.
30
+ *
31
+ * @param {Object} options - Manager configuration
32
+ * @param {Object} options.idpManager - IdP Manager instance
33
+ * @param {Object} [options.mfaStore] - MFA Store instance
34
+ * @param {number} [options.sessionDuration] - Session duration in milliseconds
35
+ * @param {number} [options.maxConcurrentSessions] - Max concurrent sessions per user
36
+ * @param {number} [options.tokenRefreshThreshold] - Time before token expiry to trigger refresh
37
+ * @returns {Object} SSO Session Manager instance
38
+ */
39
+ function createSsoSessionManager(options = {}) {
40
+ const {
41
+ idpManager,
42
+ mfaStore,
43
+ sessionDuration = SESSION_DEFAULTS.sessionDuration,
44
+ maxConcurrentSessions = SESSION_DEFAULTS.maxConcurrentSessions,
45
+ tokenRefreshThreshold = SESSION_DEFAULTS.tokenRefreshThreshold,
46
+ } = options;
47
+
48
+ // In-memory session store
49
+ // Key: session ID, Value: session object
50
+ const sessions = new Map();
51
+
52
+ // Index by user ID for efficient lookup
53
+ // Key: user ID, Value: Set of session IDs
54
+ const userSessions = new Map();
55
+
56
+ // Index by access token for efficient lookup
57
+ // Key: access token, Value: session ID
58
+ const tokenIndex = new Map();
59
+
60
+ /**
61
+ * Create a session from IdP authentication result
62
+ *
63
+ * @param {string} provider - Provider name (e.g., 'github', 'google')
64
+ * @param {Object} authResult - Authentication result from IdP
65
+ * @param {Object} metadata - Session metadata
66
+ * @returns {Promise<Object>} Created session
67
+ */
68
+ async function createSession(provider, authResult, metadata = {}) {
69
+ const { profile, tokens = {}, providerType } = authResult;
70
+ const now = Date.now();
71
+
72
+ // Determine MFA verification status
73
+ // mfaVerified is true only if user has MFA enabled AND verified with a code
74
+ let mfaVerified = false;
75
+ if (mfaStore && metadata.mfaCode) {
76
+ const mfaStatus = await mfaStore.getMfaStatus(profile.id);
77
+ if (mfaStatus.enabled) {
78
+ const mfaResult = await mfaStore.verifyMfa(profile.id, metadata.mfaCode);
79
+ mfaVerified = mfaResult.valid;
80
+ }
81
+ }
82
+ // If no mfaCode provided or MFA not enabled, mfaVerified stays false
83
+
84
+ // Calculate token expiry
85
+ let tokenExpiry = null;
86
+ if (tokens.expiresIn) {
87
+ tokenExpiry = now + (tokens.expiresIn * 1000);
88
+ }
89
+
90
+ const session = {
91
+ id: generateSessionId(),
92
+ userId: profile.id,
93
+ provider,
94
+ providerType: providerType || 'oauth',
95
+ accessToken: tokens.accessToken || null,
96
+ refreshToken: tokens.refreshToken || null,
97
+ tokenExpiry,
98
+ createdAt: now,
99
+ expiresAt: now + sessionDuration,
100
+ lastActivityAt: now,
101
+ userAgent: metadata.userAgent || null,
102
+ ipAddress: metadata.ipAddress || null,
103
+ mfaVerified: mfaVerified === true ? true : false,
104
+ // Store additional profile data for SAML logout
105
+ nameId: profile.nameId || null,
106
+ };
107
+
108
+ // Store session
109
+ sessions.set(session.id, session);
110
+
111
+ // Update user sessions index
112
+ if (!userSessions.has(session.userId)) {
113
+ userSessions.set(session.userId, new Set());
114
+ }
115
+ userSessions.get(session.userId).add(session.id);
116
+
117
+ // Update token index
118
+ if (session.accessToken) {
119
+ tokenIndex.set(session.accessToken, session.id);
120
+ }
121
+
122
+ // Enforce session limit
123
+ await enforceSessionLimitForUser(session.userId);
124
+
125
+ return session;
126
+ }
127
+
128
+ /**
129
+ * Enforce concurrent session limit for a user
130
+ *
131
+ * @param {string} userId - User ID
132
+ */
133
+ async function enforceSessionLimitForUser(userId) {
134
+ const userSessionIds = userSessions.get(userId);
135
+ if (!userSessionIds || userSessionIds.size <= maxConcurrentSessions) {
136
+ return;
137
+ }
138
+
139
+ // Get all sessions for this user, sorted by creation time (oldest first)
140
+ const userSessionList = Array.from(userSessionIds)
141
+ .map(id => sessions.get(id))
142
+ .filter(s => s !== undefined)
143
+ .sort((a, b) => a.createdAt - b.createdAt);
144
+
145
+ // Remove oldest sessions to enforce limit
146
+ const sessionsToRemove = userSessionList.slice(0, userSessionList.length - maxConcurrentSessions);
147
+
148
+ for (const session of sessionsToRemove) {
149
+ await destroySession(session.id);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Get a session by ID
155
+ *
156
+ * @param {string} sessionId - Session ID
157
+ * @returns {Promise<Object|null>} Session or null if not found/expired
158
+ */
159
+ async function getSession(sessionId) {
160
+ const session = sessions.get(sessionId);
161
+
162
+ if (!session) {
163
+ return null;
164
+ }
165
+
166
+ // Check if session is expired
167
+ if (Date.now() > session.expiresAt) {
168
+ return null;
169
+ }
170
+
171
+ // Update last activity
172
+ session.lastActivityAt = Date.now();
173
+
174
+ return session;
175
+ }
176
+
177
+ /**
178
+ * Get a session by access token
179
+ *
180
+ * @param {string} accessToken - Access token
181
+ * @returns {Promise<Object|null>} Session or null if not found
182
+ */
183
+ async function getSessionByToken(accessToken) {
184
+ const sessionId = tokenIndex.get(accessToken);
185
+ if (!sessionId) {
186
+ return null;
187
+ }
188
+
189
+ return getSession(sessionId);
190
+ }
191
+
192
+ /**
193
+ * Refresh a session
194
+ *
195
+ * @param {string} sessionId - Session ID
196
+ * @returns {Promise<Object|null>} Refreshed session or null
197
+ */
198
+ async function refreshSession(sessionId) {
199
+ const session = sessions.get(sessionId);
200
+
201
+ if (!session) {
202
+ return null;
203
+ }
204
+
205
+ // Check if session is expired
206
+ if (Date.now() > session.expiresAt) {
207
+ return null;
208
+ }
209
+
210
+ const now = Date.now();
211
+
212
+ // Extend session lifetime
213
+ session.expiresAt = now + sessionDuration;
214
+ session.lastActivityAt = now;
215
+
216
+ // Check if IdP tokens need refreshing
217
+ if (session.tokenExpiry && session.refreshToken) {
218
+ const timeUntilExpiry = session.tokenExpiry - now;
219
+
220
+ if (timeUntilExpiry <= tokenRefreshThreshold) {
221
+ await refreshIdpTokens(session);
222
+ }
223
+ }
224
+
225
+ return session;
226
+ }
227
+
228
+ /**
229
+ * Refresh IdP tokens for a session
230
+ *
231
+ * @param {Object} session - Session object
232
+ */
233
+ async function refreshIdpTokens(session) {
234
+ if (!idpManager || !idpManager.oauthRegistry) {
235
+ return;
236
+ }
237
+
238
+ const provider = idpManager.oauthRegistry.getProvider(session.provider);
239
+ if (!provider || !provider.tokenUrl) {
240
+ return;
241
+ }
242
+
243
+ try {
244
+ const response = await fetch(provider.tokenUrl, {
245
+ method: 'POST',
246
+ headers: {
247
+ 'Content-Type': 'application/x-www-form-urlencoded',
248
+ Accept: 'application/json',
249
+ },
250
+ body: new URLSearchParams({
251
+ grant_type: 'refresh_token',
252
+ refresh_token: session.refreshToken,
253
+ client_id: provider.clientId,
254
+ client_secret: provider.clientSecret,
255
+ }),
256
+ });
257
+
258
+ if (response.ok) {
259
+ const tokenData = await response.json();
260
+
261
+ // Remove old token from index
262
+ if (session.accessToken) {
263
+ tokenIndex.delete(session.accessToken);
264
+ }
265
+
266
+ // Update session with new tokens
267
+ session.accessToken = tokenData.access_token;
268
+ if (tokenData.refresh_token) {
269
+ session.refreshToken = tokenData.refresh_token;
270
+ }
271
+ if (tokenData.expires_in) {
272
+ session.tokenExpiry = Date.now() + (tokenData.expires_in * 1000);
273
+ }
274
+
275
+ // Update token index
276
+ tokenIndex.set(session.accessToken, session.id);
277
+ }
278
+ } catch (error) {
279
+ // Token refresh failed, session continues with old tokens
280
+ console.error('Token refresh failed:', error.message);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Destroy a session
286
+ *
287
+ * @param {string} sessionId - Session ID
288
+ * @param {Object} [options] - Destroy options
289
+ * @param {boolean} [options.triggerIdpLogout] - Trigger IdP logout
290
+ * @returns {Promise<Object>} Result with optional logout URL
291
+ */
292
+ async function destroySession(sessionId, options = {}) {
293
+ const session = sessions.get(sessionId);
294
+
295
+ const result = { success: true };
296
+
297
+ if (!session) {
298
+ return result;
299
+ }
300
+
301
+ // Handle IdP logout if requested
302
+ if (options.triggerIdpLogout && idpManager) {
303
+ // SAML logout
304
+ if (session.providerType === 'saml' && idpManager.samlProvider) {
305
+ const idp = idpManager.samlProvider.getIdP(session.provider);
306
+ if (idp && idp.sloUrl) {
307
+ const logoutRequest = idpManager.samlProvider.createLogoutRequest(session.provider, {
308
+ nameId: session.nameId,
309
+ sessionIndex: session.id,
310
+ });
311
+ result.logoutUrl = logoutRequest.url;
312
+ }
313
+ }
314
+
315
+ // OAuth token revocation
316
+ if (session.providerType !== 'saml' && idpManager.oauthRegistry) {
317
+ const provider = idpManager.oauthRegistry.getProvider(session.provider);
318
+ if (provider && provider.revokeUrl && session.accessToken) {
319
+ try {
320
+ await fetch(provider.revokeUrl, {
321
+ method: 'POST',
322
+ headers: {
323
+ 'Content-Type': 'application/x-www-form-urlencoded',
324
+ },
325
+ body: new URLSearchParams({
326
+ token: session.accessToken,
327
+ client_id: provider.clientId,
328
+ client_secret: provider.clientSecret,
329
+ }),
330
+ });
331
+ } catch (error) {
332
+ // Token revocation failed, continue with session destruction
333
+ console.error('Token revocation failed:', error.message);
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ // Remove from token index
340
+ if (session.accessToken) {
341
+ tokenIndex.delete(session.accessToken);
342
+ }
343
+
344
+ // Remove from user sessions index
345
+ const userSessionSet = userSessions.get(session.userId);
346
+ if (userSessionSet) {
347
+ userSessionSet.delete(sessionId);
348
+ if (userSessionSet.size === 0) {
349
+ userSessions.delete(session.userId);
350
+ }
351
+ }
352
+
353
+ // Remove session
354
+ sessions.delete(sessionId);
355
+
356
+ return result;
357
+ }
358
+
359
+ /**
360
+ * Get all active sessions for a user
361
+ *
362
+ * @param {string} userId - User ID
363
+ * @returns {Promise<Object[]>} List of active sessions
364
+ */
365
+ async function getActiveSessions(userId) {
366
+ const userSessionIds = userSessions.get(userId);
367
+ if (!userSessionIds) {
368
+ return [];
369
+ }
370
+
371
+ const now = Date.now();
372
+ const activeSessions = [];
373
+
374
+ for (const sessionId of userSessionIds) {
375
+ const session = sessions.get(sessionId);
376
+ if (session && now <= session.expiresAt) {
377
+ // Return sanitized session data (no tokens in list view)
378
+ activeSessions.push({
379
+ id: session.id,
380
+ userId: session.userId,
381
+ provider: session.provider,
382
+ createdAt: session.createdAt,
383
+ expiresAt: session.expiresAt,
384
+ lastActivityAt: session.lastActivityAt,
385
+ userAgent: session.userAgent,
386
+ ipAddress: session.ipAddress,
387
+ mfaVerified: session.mfaVerified,
388
+ });
389
+ }
390
+ }
391
+
392
+ return activeSessions;
393
+ }
394
+
395
+ /**
396
+ * Cleanup expired sessions
397
+ *
398
+ * @returns {Promise<number>} Number of sessions removed
399
+ */
400
+ async function cleanupExpiredSessions() {
401
+ const now = Date.now();
402
+ let removed = 0;
403
+
404
+ for (const [sessionId, session] of sessions) {
405
+ if (now > session.expiresAt) {
406
+ await destroySession(sessionId);
407
+ removed++;
408
+ }
409
+ }
410
+
411
+ return removed;
412
+ }
413
+
414
+ /**
415
+ * Destroy all sessions for a user
416
+ *
417
+ * @param {string} userId - User ID
418
+ * @param {Object} [options] - Destroy options
419
+ * @returns {Promise<number>} Number of sessions destroyed
420
+ */
421
+ async function destroyAllUserSessions(userId, options = {}) {
422
+ const userSessionIds = userSessions.get(userId);
423
+ if (!userSessionIds) {
424
+ return 0;
425
+ }
426
+
427
+ // Copy the set since we'll be modifying it during iteration
428
+ const sessionIds = Array.from(userSessionIds);
429
+ let removed = 0;
430
+
431
+ for (const sessionId of sessionIds) {
432
+ await destroySession(sessionId, options);
433
+ removed++;
434
+ }
435
+
436
+ return removed;
437
+ }
438
+
439
+ /**
440
+ * Get session statistics
441
+ *
442
+ * @returns {Promise<Object>} Session statistics
443
+ */
444
+ async function getSessionStats() {
445
+ const now = Date.now();
446
+ const byProvider = {};
447
+ const uniqueUsers = new Set();
448
+ let totalSessions = 0;
449
+
450
+ for (const session of sessions.values()) {
451
+ if (now <= session.expiresAt) {
452
+ totalSessions++;
453
+ uniqueUsers.add(session.userId);
454
+
455
+ if (!byProvider[session.provider]) {
456
+ byProvider[session.provider] = 0;
457
+ }
458
+ byProvider[session.provider]++;
459
+ }
460
+ }
461
+
462
+ return {
463
+ totalSessions,
464
+ uniqueUsers: uniqueUsers.size,
465
+ byProvider,
466
+ };
467
+ }
468
+
469
+ return {
470
+ // Session lifecycle
471
+ createSession,
472
+ getSession,
473
+ getSessionByToken,
474
+ refreshSession,
475
+ destroySession,
476
+
477
+ // User session management
478
+ getActiveSessions,
479
+ destroyAllUserSessions,
480
+
481
+ // Maintenance
482
+ cleanupExpiredSessions,
483
+
484
+ // Statistics
485
+ getSessionStats,
486
+ };
487
+ }
488
+
489
+ module.exports = {
490
+ createSsoSessionManager,
491
+ SESSION_DEFAULTS,
492
+ };