@devilsdev/rag-pipeline-utils 2.1.12 → 2.2.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 (113) hide show
  1. package/.ragrc.schema.json +218 -0
  2. package/README.md +178 -550
  3. package/bin/cli.js +8 -14
  4. package/contracts/reranker-contract.json +69 -0
  5. package/dist/index.d.ts +237 -0
  6. package/dist/index.mjs +22 -7
  7. package/package.json +18 -13
  8. package/src/ai/adaptive-retrieval.js +191 -0
  9. package/src/ai/federated-learning.js +214 -0
  10. package/src/ai/federation/federated-learning-coordinator.js +345 -0
  11. package/src/ai/federation.js +76 -0
  12. package/src/ai/index-fixed.js +509 -0
  13. package/src/ai/index.js +33 -0
  14. package/src/ai/model-trainer.js +217 -0
  15. package/src/ai/model-training.js +205 -0
  16. package/src/ai/module-template.js +37 -0
  17. package/src/ai/multimodal/multi-modal-processor.js +345 -0
  18. package/src/ai/multimodal-processing.js +268 -0
  19. package/src/ai/multimodal.js +71 -0
  20. package/src/ai/orchestrator.js +70 -0
  21. package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
  22. package/src/ai/retrieval-engine.js +69 -0
  23. package/src/ai/training/model-training-orchestrator.js +228 -0
  24. package/src/ci/workflow-analyzer.js +71 -0
  25. package/src/cli/commands/ai-ml.js +967 -0
  26. package/src/cli/commands/docs.js +228 -0
  27. package/src/cli/commands/dx.js +538 -0
  28. package/src/cli/commands/plugin-hub.js +875 -0
  29. package/src/cli/doctor-command.js +639 -0
  30. package/src/cli/enhanced-cli-commands.js +945 -0
  31. package/src/cli/interactive-wizard.js +997 -0
  32. package/src/cli/plugin-marketplace-commands.js +869 -0
  33. package/src/cli.js +17 -0
  34. package/src/config/enhanced-ragrc-schema.js +632 -0
  35. package/src/config/load-config.js +54 -0
  36. package/src/config/load-plugin-config.js +69 -0
  37. package/src/config/normalize-config.js +105 -0
  38. package/src/config/schema-validator.js +315 -0
  39. package/src/config/validate-plugin-schema.js +35 -0
  40. package/src/config/validate-schema.js +113 -0
  41. package/src/contracts/plugin-contracts.js +33 -0
  42. package/src/core/create-pipeline.js +99 -0
  43. package/src/core/dependency-injection.js +242 -0
  44. package/src/core/observability/event-logger.js +241 -0
  45. package/src/core/observability/instrumented-pipeline.js +609 -0
  46. package/src/core/observability/metrics.js +497 -0
  47. package/src/core/observability/tracing.js +781 -0
  48. package/src/core/performance/benchmark.js +516 -0
  49. package/src/core/performance/parallel-processor.js +240 -0
  50. package/src/core/performance/streaming-safeguards.js +620 -0
  51. package/src/core/pipeline-factory.js +60 -0
  52. package/src/core/plugin-contracts.js +89 -0
  53. package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
  54. package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
  55. package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
  56. package/src/core/plugin-marketplace/version-resolver.js +61 -0
  57. package/src/core/plugin-registry.js +294 -0
  58. package/src/dag/dag-engine.js +954 -0
  59. package/src/dag/dag-node.js +76 -0
  60. package/src/dag/enhanced-dag-engine.js +822 -0
  61. package/src/dx/index.js +40 -0
  62. package/src/dx/integration-templates.js +665 -0
  63. package/src/dx/performance-profiler.js +968 -0
  64. package/src/dx/realtime-debugger.js +755 -0
  65. package/src/dx/visual-pipeline-builder.js +578 -0
  66. package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
  67. package/src/ecosystem/plugin-certification.js +580 -0
  68. package/src/ecosystem/plugin-hub.js +821 -0
  69. package/src/enterprise/audit-logging.js +967 -0
  70. package/src/enterprise/data-governance.js +408 -0
  71. package/src/enterprise/multi-tenancy.js +496 -0
  72. package/src/enterprise/sso-integration.js +1030 -0
  73. package/src/evaluate/evaluator.js +52 -0
  74. package/src/evaluate/scoring.js +91 -0
  75. package/src/index.js +67 -0
  76. package/src/ingest.js +59 -0
  77. package/src/loader/csv-loader.js +38 -0
  78. package/src/loader/directory-loader.js +48 -0
  79. package/src/loader/html-loader.js +62 -0
  80. package/src/loader/markdown-loader.js +67 -0
  81. package/src/mocks/openai-embedder.js +119 -0
  82. package/src/mocks/openai-llm.js +77 -0
  83. package/src/mocks/pdf-loader.js +21 -0
  84. package/src/mocks/pinecone-retriever.js +106 -0
  85. package/src/observability/audit-logger.js +758 -0
  86. package/src/observability/dashboard-config.json +675 -0
  87. package/src/observability/event-logger.js +310 -0
  88. package/src/observability/metrics.js +691 -0
  89. package/src/observability/security-monitor.js +830 -0
  90. package/src/observability/slo-monitor.js +319 -0
  91. package/src/observability/telemetry.js +686 -0
  92. package/src/pipeline/pipeline.js +18 -0
  93. package/src/plugins/sample-embedder.js +53 -0
  94. package/src/plugins/sample-llm.js +30 -0
  95. package/src/plugins/sample-loader.js +24 -0
  96. package/src/plugins/sample-retriever.js +84 -0
  97. package/src/query.js +117 -0
  98. package/src/reranker/llm-reranker.js +111 -0
  99. package/src/scripts/script-utilities.js +36 -0
  100. package/src/security/plugin-sandbox.js +586 -0
  101. package/src/security/plugin-signature-verifier.js +414 -0
  102. package/src/security/validator.js +36 -0
  103. package/src/utils/ci/diagnostic-reporter.js +57 -0
  104. package/src/utils/logger.js +211 -0
  105. package/src/utils/performance-monitor.js +561 -0
  106. package/src/utils/plugin-scaffolder.js +447 -0
  107. package/src/utils/rate-limiter.js +244 -0
  108. package/src/utils/retry-policy.js +443 -0
  109. package/src/utils/retry.js +107 -0
  110. package/src/utils/safe-expression-evaluator.js +579 -0
  111. package/src/utils/secure-logging.js +338 -0
  112. package/src/utils/structured-logger.js +388 -0
  113. package/src/utils/validate-plugin-contract.js +42 -0
@@ -0,0 +1,1030 @@
1
+ /**
2
+ * Enterprise SSO Integration
3
+ * SAML 2.0, OAuth2, Active Directory, and identity provider support
4
+ */
5
+
6
+ const fs = require('fs').promises;
7
+ const path = require('path');
8
+ const crypto = require('crypto');
9
+ const { EventEmitter } = require('events');
10
+ const jwt = require('jsonwebtoken');
11
+ const RateLimiter = require('../utils/rate-limiter');
12
+
13
+ class SSOManager extends EventEmitter {
14
+ constructor(_options = {}) {
15
+ super();
16
+
17
+ this._config = {
18
+ providers: {
19
+ saml: {
20
+ enabled: false,
21
+ entityId: _options.entityId || 'rag-pipeline-utils',
22
+ ssoUrl: _options.ssoUrl,
23
+ sloUrl: _options.sloUrl,
24
+ certificate: _options.certificate,
25
+ privateKey: _options.privateKey,
26
+ },
27
+ oauth2: {
28
+ enabled: false,
29
+ clientId: _options.clientId,
30
+ clientSecret: _options.clientSecret,
31
+ authorizationUrl: _options.authorizationUrl,
32
+ tokenUrl: _options.tokenUrl,
33
+ userInfoUrl: _options.userInfoUrl,
34
+ scopes: ['openid', 'profile', 'email'],
35
+ },
36
+ activeDirectory: {
37
+ enabled: false,
38
+ domain: _options.adDomain,
39
+ url: _options.adUrl,
40
+ baseDN: _options.baseDN,
41
+ bindDN: _options.bindDN,
42
+ bindCredentials: _options.bindCredentials,
43
+ },
44
+ oidc: {
45
+ enabled: false,
46
+ issuer: _options.oidcIssuer,
47
+ clientId: _options.oidcClientId,
48
+ clientSecret: _options.oidcClientSecret,
49
+ redirectUri: _options.redirectUri,
50
+ },
51
+ },
52
+ session: {
53
+ timeout: _options.sessionTimeout || 8 * 60 * 60 * 1000, // 8 hours
54
+ renewalThreshold: _options.renewalThreshold || 30 * 60 * 1000, // 30 minutes
55
+ maxConcurrentSessions: _options.maxConcurrentSessions || 5,
56
+ },
57
+ security: {
58
+ jwtSecret: _options.jwtSecret || crypto.randomBytes(64).toString('hex'),
59
+ encryptionKey: _options.encryptionKey || crypto.randomBytes(32),
60
+ tokenExpiry: _options.tokenExpiry || 3600, // 1 hour
61
+ refreshTokenExpiry: _options.refreshTokenExpiry || 7 * 24 * 3600, // 7 days
62
+ },
63
+ rateLimit: {
64
+ enabled: _options.rateLimitEnabled !== false, // Enabled by default
65
+ maxLoginAttempts: _options.maxLoginAttempts || 5,
66
+ loginWindowMs: _options.loginWindowMs || 15 * 60 * 1000, // 15 minutes
67
+ blockDurationMs: _options.blockDurationMs || 60 * 60 * 1000, // 1 hour
68
+ },
69
+ ..._options,
70
+ };
71
+
72
+ this.sessions = new Map();
73
+ this.providers = new Map();
74
+ this.userCache = new Map();
75
+
76
+ // Initialize rate limiter for login attempts
77
+ if (this._config.rateLimit.enabled) {
78
+ this.rateLimiter = new RateLimiter({
79
+ maxAttempts: this._config.rateLimit.maxLoginAttempts,
80
+ windowMs: this._config.rateLimit.loginWindowMs,
81
+ blockDurationMs: this._config.rateLimit.blockDurationMs,
82
+ });
83
+ }
84
+
85
+ this._initializeProviders();
86
+ }
87
+
88
+ /**
89
+ * Initialize SSO providers
90
+ */
91
+ async _initializeProviders() {
92
+ if (this._config.providers.saml.enabled) {
93
+ this.providers.set('saml', new SAMLProvider(this._config.providers.saml));
94
+ }
95
+
96
+ if (this._config.providers.oauth2.enabled) {
97
+ this.providers.set(
98
+ 'oauth2',
99
+ new OAuth2Provider(this._config.providers.oauth2),
100
+ );
101
+ }
102
+
103
+ if (this._config.providers.activeDirectory.enabled) {
104
+ this.providers.set(
105
+ 'ad',
106
+ new ActiveDirectoryProvider(this._config.providers.activeDirectory),
107
+ );
108
+ }
109
+
110
+ if (this._config.providers.oidc.enabled) {
111
+ this.providers.set('oidc', new OIDCProvider(this._config.providers.oidc));
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Initiate SSO login
117
+ */
118
+ async initiateLogin(tenantId, provider, _redirectUrl) {
119
+ const ssoProvider = this.providers.get(provider);
120
+ if (!ssoProvider) {
121
+ throw new Error(`SSO provider ${provider} not configured`);
122
+ }
123
+
124
+ // Rate limiting check
125
+ if (this.rateLimiter) {
126
+ const identifier = `${tenantId}:${provider}`;
127
+ const rateLimitResult = this.rateLimiter.allowRequest(identifier);
128
+
129
+ if (!rateLimitResult.allowed) {
130
+ this.emit('login_rate_limited', {
131
+ tenantId,
132
+ provider,
133
+ blockedUntil: rateLimitResult.blockedUntil,
134
+ retryAfter: rateLimitResult.retryAfter,
135
+ });
136
+
137
+ const error = new Error(
138
+ 'Too many login attempts. Please try again later.',
139
+ );
140
+ error.code = 'RATE_LIMIT_EXCEEDED';
141
+ error.retryAfter = rateLimitResult.retryAfter;
142
+ error.blockedUntil = rateLimitResult.blockedUntil;
143
+ throw error;
144
+ }
145
+ }
146
+
147
+ const state = crypto.randomUUID();
148
+ const loginRequest = {
149
+ tenantId,
150
+ provider,
151
+ state,
152
+ _redirectUrl,
153
+ timestamp: Date.now(),
154
+ expiresAt: Date.now() + 10 * 60 * 1000, // 10 minutes
155
+ };
156
+
157
+ // Store login request for callback validation
158
+ this.sessions.set(state, loginRequest);
159
+
160
+ const authUrl = await ssoProvider.getAuthorizationUrl(state, _redirectUrl);
161
+
162
+ this.emit('login_initiated', { tenantId, provider, state });
163
+
164
+ return {
165
+ authUrl,
166
+ state,
167
+ expiresAt: loginRequest.expiresAt,
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Handle SSO callback
173
+ */
174
+ async handleCallback(provider, _callbackData) {
175
+ const ssoProvider = this.providers.get(provider);
176
+ if (!ssoProvider) {
177
+ throw new Error(`SSO provider ${provider} not configured`);
178
+ }
179
+
180
+ const loginRequest = this.sessions.get(_callbackData.state);
181
+ if (!loginRequest) {
182
+ throw new Error('Invalid or expired login request');
183
+ }
184
+
185
+ if (loginRequest.expiresAt < Date.now()) {
186
+ this.sessions.delete(_callbackData.state);
187
+ throw new Error('Login request expired');
188
+ }
189
+
190
+ // Exchange authorization code for tokens
191
+ const tokenData = await ssoProvider.exchangeCodeForTokens(_callbackData);
192
+
193
+ // Get user information
194
+ const userInfo = await ssoProvider.getUserInfo(tokenData._accessToken);
195
+
196
+ // Create internal user session
197
+ const session = await this._createUserSession(
198
+ loginRequest.tenantId,
199
+ userInfo,
200
+ provider,
201
+ tokenData,
202
+ );
203
+
204
+ // Clean up login request
205
+ this.sessions.delete(_callbackData.state);
206
+
207
+ this.emit('login_completed', {
208
+ tenantId: loginRequest.tenantId,
209
+ provider,
210
+ userId: userInfo.id,
211
+ sessionId: session.id,
212
+ });
213
+
214
+ return {
215
+ session,
216
+ user: userInfo,
217
+ redirectUrl: loginRequest._redirectUrl,
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Create user session with JWT tokens
223
+ */
224
+ async _createUserSession(tenantId, userInfo, provider, tokenData) {
225
+ const sessionId = crypto.randomUUID();
226
+ const now = Date.now();
227
+
228
+ // Check concurrent session limits
229
+ const userSessions = Array.from(this.sessions.values()).filter(
230
+ (s) => s.userId === userInfo.id && s.tenantId === tenantId,
231
+ );
232
+
233
+ if (userSessions.length >= this._config.session.maxConcurrentSessions) {
234
+ // Remove oldest session
235
+ const oldestSession = userSessions.sort(
236
+ (a, b) => a.createdAt - b.createdAt,
237
+ )[0];
238
+ this.sessions.delete(oldestSession.id);
239
+ this.emit('session_evicted', {
240
+ sessionId: oldestSession.id,
241
+ reason: 'concurrent_limit',
242
+ });
243
+ }
244
+
245
+ const session = {
246
+ id: sessionId,
247
+ tenantId,
248
+ userId: userInfo.id,
249
+ provider,
250
+ user: {
251
+ id: userInfo.id,
252
+ email: userInfo.email,
253
+ name: userInfo.name,
254
+ roles: userInfo.roles || [],
255
+ groups: userInfo.groups || [],
256
+ attributes: userInfo.attributes || {},
257
+ },
258
+ tokens: {
259
+ _accessToken: this._generateJWT({
260
+ sub: userInfo.id,
261
+ tenant: tenantId,
262
+ provider,
263
+ roles: userInfo.roles || [],
264
+ exp: Math.floor(now / 1000) + this._config.security.tokenExpiry,
265
+ }),
266
+ refreshToken: this._generateRefreshToken(),
267
+ externalTokens: {
268
+ accessToken: tokenData._accessToken,
269
+ refreshToken: tokenData.refreshToken,
270
+ expiresAt: tokenData.expiresAt,
271
+ },
272
+ },
273
+ metadata: {
274
+ createdAt: now,
275
+ lastActivity: now,
276
+ expiresAt: now + this._config.session.timeout,
277
+ ipAddress: tokenData.ipAddress,
278
+ userAgent: tokenData.userAgent,
279
+ },
280
+ };
281
+
282
+ this.sessions.set(sessionId, session);
283
+ this.userCache.set(`${tenantId}:${userInfo.id}`, userInfo);
284
+
285
+ return session;
286
+ }
287
+
288
+ /**
289
+ * Validate and refresh session
290
+ */
291
+ async validateSession(sessionId, tenantId) {
292
+ const session = this.sessions.get(sessionId);
293
+ if (!session) {
294
+ throw new Error('Session not found');
295
+ }
296
+
297
+ if (session.tenantId !== tenantId) {
298
+ throw new Error('Session tenant mismatch');
299
+ }
300
+
301
+ const now = Date.now();
302
+
303
+ // Check if session expired
304
+ if (session.metadata.expiresAt < now) {
305
+ this.sessions.delete(sessionId);
306
+ this.emit('session_expired', { sessionId, tenantId });
307
+ throw new Error('Session expired');
308
+ }
309
+
310
+ // Check if session needs renewal
311
+ const renewalTime =
312
+ session.metadata.expiresAt - this._config.session.renewalThreshold;
313
+ if (now > renewalTime) {
314
+ await this._renewSession(session);
315
+ }
316
+
317
+ // Update last activity
318
+ session.metadata.lastActivity = now;
319
+
320
+ return session;
321
+ }
322
+
323
+ /**
324
+ * Renew session tokens
325
+ */
326
+ async _renewSession(session) {
327
+ const _provider = this.providers.get(session.provider);
328
+ if (!_provider) {
329
+ throw new Error(`Provider ${session.provider} not available`);
330
+ }
331
+
332
+ try {
333
+ // Refresh external tokens if needed
334
+ if (session.tokens.externalTokens.refreshToken) {
335
+ const newTokens = await _provider.refreshTokens(
336
+ session.tokens.externalTokens.refreshToken,
337
+ );
338
+ session.tokens.externalTokens = newTokens;
339
+ }
340
+
341
+ // Generate new internal tokens
342
+ const now = Date.now();
343
+ session.tokens.accessToken = this._generateJWT({
344
+ sub: session.userId,
345
+ tenant: session.tenantId,
346
+ provider: session.provider,
347
+ roles: session.user.roles,
348
+ exp: Math.floor(now / 1000) + this._config.security.tokenExpiry,
349
+ });
350
+
351
+ session.metadata.expiresAt = now + this._config.session.timeout;
352
+
353
+ this.emit('session_renewed', {
354
+ sessionId: session.id,
355
+ tenantId: session.tenantId,
356
+ });
357
+ } catch (error) {
358
+ this.emit('session_renewal_failed', {
359
+ sessionId: session.id,
360
+ tenantId: session.tenantId,
361
+ error: error.message,
362
+ });
363
+ throw error;
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Logout user and invalidate session
369
+ */
370
+ async logout(sessionId, tenantId, _options = {}) {
371
+ const session = this.sessions.get(sessionId);
372
+ if (!session || session.tenantId !== tenantId) {
373
+ return { success: false, reason: 'session_not_found' };
374
+ }
375
+
376
+ // Perform SSO logout if supported
377
+ if (_options.ssoLogout) {
378
+ const provider = this.providers.get(session.provider);
379
+ if (provider && provider.logout) {
380
+ try {
381
+ await provider.logout(session.tokens.externalTokens._accessToken);
382
+ } catch (error) {
383
+ this.emit('sso_logout_failed', {
384
+ sessionId,
385
+ tenantId,
386
+ provider: session.provider,
387
+ error: error.message,
388
+ });
389
+ }
390
+ }
391
+ }
392
+
393
+ // Remove session
394
+ this.sessions.delete(sessionId);
395
+
396
+ this.emit('logout_completed', {
397
+ sessionId,
398
+ tenantId,
399
+ userId: session.userId,
400
+ provider: session.provider,
401
+ });
402
+
403
+ return { success: true };
404
+ }
405
+
406
+ /**
407
+ * Get user information from session
408
+ */
409
+ async getUser(sessionId, tenantId) {
410
+ const session = await this.validateSession(sessionId, tenantId);
411
+ return session.user;
412
+ }
413
+
414
+ /**
415
+ * List active sessions for a tenant
416
+ */
417
+ async getActiveSessions(tenantId, _options = {}) {
418
+ const sessions = Array.from(this.sessions.values())
419
+ .filter((s) => s.tenantId === tenantId)
420
+ .map((s) => ({
421
+ id: s.id,
422
+ userId: s.userId,
423
+ provider: s.provider,
424
+ user: _options.includeUserInfo
425
+ ? s.user
426
+ : { id: s.user.id, email: s.user.email },
427
+ metadata: s.metadata,
428
+ }));
429
+
430
+ return sessions;
431
+ }
432
+
433
+ /**
434
+ * Revoke all sessions for a user
435
+ */
436
+ async revokeUserSessions(tenantId, userId, excludeSessionId = null) {
437
+ const userSessions = Array.from(this.sessions.entries()).filter(
438
+ ([sessionId, session]) =>
439
+ session.tenantId === tenantId &&
440
+ session.userId === userId &&
441
+ sessionId !== excludeSessionId,
442
+ );
443
+
444
+ for (const [sessionId] of userSessions) {
445
+ this.sessions.delete(sessionId);
446
+ this.emit('session_revoked', {
447
+ sessionId,
448
+ tenantId,
449
+ userId,
450
+ reason: 'admin_revoke',
451
+ });
452
+ }
453
+
454
+ return { revokedSessions: userSessions.length };
455
+ }
456
+
457
+ /**
458
+ * Generate JWT token using secure jsonwebtoken library
459
+ * @param {object} payload - JWT payload
460
+ * @returns {string} Signed JWT token
461
+ */
462
+ _generateJWT(payload) {
463
+ // Security fix: Use proper JWT library with signature verification
464
+ try {
465
+ return jwt.sign(payload, this._config.security.jwtSecret, {
466
+ algorithm: 'HS256',
467
+ issuer: 'rag-pipeline-utils',
468
+ audience: 'rag-pipeline-api',
469
+ expiresIn: this._config.security.jwtExpiresIn || '1h', // Default 1 hour expiration
470
+ jwtid: crypto.randomUUID(),
471
+ notBefore: 0,
472
+ });
473
+ } catch (error) {
474
+ throw new Error(`JWT generation failed: ${error.message}`);
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Verify and decode JWT token
480
+ * @param {string} token - JWT token to verify
481
+ * @returns {object} Decoded payload
482
+ */
483
+ _verifyJWT(token) {
484
+ try {
485
+ return jwt.verify(token, this._config.security.jwtSecret, {
486
+ algorithms: ['HS256'],
487
+ issuer: 'rag-pipeline-utils',
488
+ audience: 'rag-pipeline-api',
489
+ clockTolerance: 30, // 30 seconds tolerance for clock skew
490
+ });
491
+ } catch (error) {
492
+ if (error.name === 'TokenExpiredError') {
493
+ throw new Error('JWT token has expired');
494
+ } else if (error.name === 'JsonWebTokenError') {
495
+ throw new Error('Invalid JWT token');
496
+ } else if (error.name === 'NotBeforeError') {
497
+ throw new Error('JWT token not yet valid');
498
+ }
499
+ throw new Error(`JWT verification failed: ${error.message}`);
500
+ }
501
+ }
502
+
503
+ /**
504
+ * Generate refresh token
505
+ */
506
+ _generateRefreshToken() {
507
+ return crypto.randomBytes(32).toString('hex');
508
+ }
509
+
510
+ /**
511
+ * Cleanup expired sessions
512
+ */
513
+ async cleanupExpiredSessions() {
514
+ const now = Date.now();
515
+ const expiredSessions = [];
516
+
517
+ for (const [sessionId, session] of this.sessions.entries()) {
518
+ if (session.metadata.expiresAt < now) {
519
+ expiredSessions.push(sessionId);
520
+ this.sessions.delete(sessionId);
521
+ this.emit('session_expired', { sessionId, tenantId: session.tenantId });
522
+ }
523
+ }
524
+
525
+ return { cleanedSessions: expiredSessions.length };
526
+ }
527
+
528
+ /**
529
+ * Reset rate limit for a specific tenant/provider
530
+ * @param {string} tenantId - Tenant identifier
531
+ * @param {string} provider - Provider name
532
+ */
533
+ resetRateLimit(tenantId, provider) {
534
+ if (this.rateLimiter) {
535
+ const identifier = `${tenantId}:${provider}`;
536
+ this.rateLimiter.reset(identifier);
537
+ this.emit('rate_limit_reset', { tenantId, provider });
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Get rate limit status for a tenant/provider
543
+ * @param {string} tenantId - Tenant identifier
544
+ * @param {string} provider - Provider name
545
+ * @returns {Object} Rate limit status
546
+ */
547
+ getRateLimitStatus(tenantId, provider) {
548
+ if (!this.rateLimiter) {
549
+ return { enabled: false };
550
+ }
551
+
552
+ const identifier = `${tenantId}:${provider}`;
553
+ return {
554
+ enabled: true,
555
+ ...this.rateLimiter.getStatus(identifier),
556
+ };
557
+ }
558
+
559
+ /**
560
+ * Get rate limiter statistics
561
+ * @returns {Object} Statistics about rate limiting
562
+ */
563
+ getRateLimiterStats() {
564
+ if (!this.rateLimiter) {
565
+ return { enabled: false };
566
+ }
567
+
568
+ return {
569
+ enabled: true,
570
+ ...this.rateLimiter.getStats(),
571
+ };
572
+ }
573
+
574
+ /**
575
+ * Destroy SSO manager and cleanup resources
576
+ */
577
+ destroy() {
578
+ if (this.rateLimiter) {
579
+ this.rateLimiter.destroy();
580
+ this.rateLimiter = null;
581
+ }
582
+
583
+ this.sessions.clear();
584
+ this.providers.clear();
585
+ this.userCache.clear();
586
+ this.removeAllListeners();
587
+ }
588
+ }
589
+
590
+ // SSO Provider implementations
591
+ class SAMLProvider {
592
+ constructor(_config) {
593
+ this._config = _config;
594
+ }
595
+
596
+ async getAuthorizationUrl(state, _redirectUrl) {
597
+ // SAML SSO URL construction
598
+ const params = new URLSearchParams({
599
+ SAMLRequest: this._buildSAMLRequest(state),
600
+ RelayState: state,
601
+ });
602
+
603
+ return `${this._config.ssoUrl}?${params.toString()}`;
604
+ }
605
+
606
+ async exchangeCodeForTokens(_callbackData) {
607
+ // SAML assertion processing
608
+ const assertion = this._validateSAMLResponse(_callbackData.SAMLResponse);
609
+
610
+ return {
611
+ _accessToken: assertion.sessionIndex,
612
+ refreshToken: null,
613
+ expiresAt: assertion.notOnOrAfter,
614
+ };
615
+ }
616
+
617
+ async getUserInfo(_accessToken) {
618
+ // Extract user info from SAML assertion
619
+ return {
620
+ id: _accessToken.nameID,
621
+ email: _accessToken.attributes.email,
622
+ name: _accessToken.attributes.displayName,
623
+ roles: _accessToken.attributes.roles || [],
624
+ groups: _accessToken.attributes.groups || [],
625
+ };
626
+ }
627
+
628
+ _buildSAMLRequest(state) {
629
+ if (process.env.NODE_ENV === 'production') {
630
+ throw new Error(
631
+ 'Production SAML implementation required - please configure SAML library',
632
+ );
633
+ }
634
+
635
+ // Development only - remove in production
636
+ return Buffer.from(
637
+ `<samlp:AuthnRequest ID="${state}" Version="2.0" IssueInstant="${new Date().toISOString()}" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
638
+ <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">${this._config.entityId}</saml:Issuer>
639
+ <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress" AllowCreate="true"/>
640
+ </samlp:AuthnRequest>`,
641
+ ).toString('base64');
642
+ }
643
+
644
+ _validateSAMLResponse(response) {
645
+ if (process.env.NODE_ENV === 'production') {
646
+ throw new Error(
647
+ 'Production SAML validation required - please configure SAML library',
648
+ );
649
+ }
650
+
651
+ if (!response) {
652
+ throw new Error('SAML response is required');
653
+ }
654
+
655
+ // Development only - implement proper SAML validation in production
656
+ try {
657
+ const decodedResponse = Buffer.from(response, 'base64').toString('utf8');
658
+
659
+ if (!decodedResponse.includes('samlp:Response')) {
660
+ throw new Error('Invalid SAML response format');
661
+ }
662
+
663
+ return {
664
+ sessionIndex: crypto.randomUUID(),
665
+ nameID: 'dev-user@example.com',
666
+ attributes: {
667
+ email: 'dev-user@example.com',
668
+ displayName: 'Development User',
669
+ roles: ['user', 'dev'],
670
+ },
671
+ notOnOrAfter: Date.now() + 8 * 60 * 60 * 1000,
672
+ };
673
+ } catch (error) {
674
+ throw new Error(`SAML response validation failed: ${error.message}`);
675
+ }
676
+ }
677
+ }
678
+
679
+ class OAuth2Provider {
680
+ constructor(_config) {
681
+ this._config = _config;
682
+ }
683
+
684
+ async getAuthorizationUrl(state, _redirectUrl) {
685
+ const params = new URLSearchParams({
686
+ response_type: 'code',
687
+ client_id: this._config.clientId,
688
+ redirect_uri: _redirectUrl,
689
+ scope: this._config.scopes.join(' '),
690
+ state,
691
+ });
692
+
693
+ return `${this._config.authorizationUrl}?${params.toString()}`;
694
+ }
695
+
696
+ async exchangeCodeForTokens(callbackData) {
697
+ if (process.env.NODE_ENV === 'production') {
698
+ // Production implementation using real OAuth2 flow
699
+ const tokenRequest = {
700
+ method: 'POST',
701
+ headers: {
702
+ 'Content-Type': 'application/x-www-form-urlencoded',
703
+ Authorization: `Basic ${Buffer.from(`${this._config.clientId}:${this._config.clientSecret}`).toString('base64')}`,
704
+ },
705
+ body: new URLSearchParams({
706
+ grant_type: 'authorization_code',
707
+ code: callbackData.code,
708
+ redirect_uri: callbackData.redirect_uri,
709
+ }),
710
+ };
711
+
712
+ try {
713
+ const response = await fetch(this._config.tokenUrl, tokenRequest);
714
+
715
+ if (!response.ok) {
716
+ throw new Error(
717
+ `Token exchange failed: ${response.status} ${response.statusText}`,
718
+ );
719
+ }
720
+
721
+ const tokenData = await response.json();
722
+
723
+ return {
724
+ _accessToken: tokenData.access_token,
725
+ refreshToken: tokenData.refresh_token,
726
+ expiresAt: Date.now() + tokenData.expires_in * 1000,
727
+ tokenType: tokenData.token_type || 'Bearer',
728
+ };
729
+ } catch (error) {
730
+ throw new Error(`OAuth2 token exchange failed: ${error.message}`);
731
+ }
732
+ }
733
+
734
+ // Development only
735
+ if (!callbackData.code) {
736
+ throw new Error('Authorization code is required');
737
+ }
738
+
739
+ return {
740
+ _accessToken: crypto.randomBytes(32).toString('hex'),
741
+ refreshToken: crypto.randomBytes(32).toString('hex'),
742
+ expiresAt: Date.now() + 3600 * 1000,
743
+ };
744
+ }
745
+
746
+ async getUserInfo(accessToken) {
747
+ if (process.env.NODE_ENV === 'production') {
748
+ // Production implementation
749
+ try {
750
+ const userResponse = await fetch(this._config.userInfoUrl, {
751
+ headers: {
752
+ Authorization: `Bearer ${accessToken}`,
753
+ Accept: 'application/json',
754
+ },
755
+ });
756
+
757
+ if (!userResponse.ok) {
758
+ throw new Error(`User info request failed: ${userResponse.status}`);
759
+ }
760
+
761
+ const userData = await userResponse.json();
762
+
763
+ return {
764
+ id: userData.id || userData.sub,
765
+ email: userData.email,
766
+ name: userData.name || userData.display_name,
767
+ roles: userData.roles || [],
768
+ attributes: userData,
769
+ };
770
+ } catch (error) {
771
+ throw new Error(`Failed to get user info: ${error.message}`);
772
+ }
773
+ }
774
+
775
+ // Development only
776
+ return {
777
+ id: 'dev-' + crypto.randomUUID(),
778
+ email: 'dev-oauth-user@example.com',
779
+ name: 'Development OAuth User',
780
+ roles: ['user', 'dev'],
781
+ };
782
+ }
783
+
784
+ async refreshTokens(refreshToken) {
785
+ if (process.env.NODE_ENV === 'production') {
786
+ // Production token refresh
787
+ const refreshRequest = {
788
+ method: 'POST',
789
+ headers: {
790
+ 'Content-Type': 'application/x-www-form-urlencoded',
791
+ Authorization: `Basic ${Buffer.from(`${this._config.clientId}:${this._config.clientSecret}`).toString('base64')}`,
792
+ },
793
+ body: new URLSearchParams({
794
+ grant_type: 'refresh_token',
795
+ refresh_token: refreshToken,
796
+ }),
797
+ };
798
+
799
+ try {
800
+ const response = await fetch(this._config.tokenUrl, refreshRequest);
801
+
802
+ if (!response.ok) {
803
+ throw new Error(`Token refresh failed: ${response.status}`);
804
+ }
805
+
806
+ const tokenData = await response.json();
807
+
808
+ return {
809
+ _accessToken: tokenData.access_token,
810
+ refreshToken: tokenData.refresh_token || refreshToken,
811
+ expiresAt: Date.now() + tokenData.expires_in * 1000,
812
+ };
813
+ } catch (error) {
814
+ throw new Error(`Token refresh failed: ${error.message}`);
815
+ }
816
+ }
817
+
818
+ // Development only
819
+ return {
820
+ _accessToken: crypto.randomBytes(32).toString('hex'),
821
+ refreshToken: refreshToken,
822
+ expiresAt: Date.now() + 3600 * 1000,
823
+ };
824
+ }
825
+ }
826
+
827
+ class ActiveDirectoryProvider {
828
+ constructor(_config) {
829
+ this._config = _config;
830
+ }
831
+
832
+ async getAuthorizationUrl(state, _redirectUrl) {
833
+ // AD FS OAuth2 flow
834
+ const params = new URLSearchParams({
835
+ response_type: 'code',
836
+ client_id: 'rag-pipeline-utils',
837
+ resource: this._config.url,
838
+ redirect_uri: _redirectUrl,
839
+ state,
840
+ });
841
+
842
+ return `${this._config.url}/oauth2/authorize?${params.toString()}`;
843
+ }
844
+
845
+ async exchangeCodeForTokens(callbackData) {
846
+ if (process.env.NODE_ENV === 'production') {
847
+ throw new Error(
848
+ 'Production Active Directory integration required - please configure AD FS or Azure AD',
849
+ );
850
+ }
851
+
852
+ // Development only
853
+ if (!callbackData.code) {
854
+ throw new Error('Authorization code is required');
855
+ }
856
+
857
+ return {
858
+ _accessToken: crypto.randomBytes(32).toString('hex'),
859
+ refreshToken: crypto.randomBytes(32).toString('hex'),
860
+ expiresAt: Date.now() + 3600 * 1000,
861
+ };
862
+ }
863
+
864
+ async getUserInfo(accessToken) {
865
+ if (process.env.NODE_ENV === 'production') {
866
+ throw new Error(
867
+ 'Production Active Directory user lookup required - please configure AD integration',
868
+ );
869
+ }
870
+
871
+ // Development only
872
+ return {
873
+ id: 'dev-' + crypto.randomUUID(),
874
+ email: 'dev-user@corp.com',
875
+ name: 'Development AD User',
876
+ roles: ['user', 'dev'],
877
+ groups: ['Domain Users', 'Developers'],
878
+ };
879
+ }
880
+ }
881
+
882
+ class OIDCProvider {
883
+ constructor(_config) {
884
+ this._config = _config;
885
+ }
886
+
887
+ async getAuthorizationUrl(state, _redirectUrl) {
888
+ const params = new URLSearchParams({
889
+ response_type: 'code',
890
+ client_id: this._config.clientId,
891
+ redirect_uri: _redirectUrl,
892
+ scope: 'openid profile email',
893
+ state,
894
+ });
895
+
896
+ return `${this._config.issuer}/auth?${params.toString()}`;
897
+ }
898
+
899
+ async exchangeCodeForTokens(callbackData) {
900
+ if (process.env.NODE_ENV === 'production') {
901
+ // Production OIDC implementation
902
+ const tokenRequest = {
903
+ method: 'POST',
904
+ headers: {
905
+ 'Content-Type': 'application/x-www-form-urlencoded',
906
+ Authorization: `Basic ${Buffer.from(`${this._config.clientId}:${this._config.clientSecret}`).toString('base64')}`,
907
+ },
908
+ body: new URLSearchParams({
909
+ grant_type: 'authorization_code',
910
+ code: callbackData.code,
911
+ redirect_uri: callbackData.redirect_uri,
912
+ }),
913
+ };
914
+
915
+ try {
916
+ const response = await fetch(
917
+ `${this._config.issuer}/token`,
918
+ tokenRequest,
919
+ );
920
+
921
+ if (!response.ok) {
922
+ throw new Error(`OIDC token exchange failed: ${response.status}`);
923
+ }
924
+
925
+ const tokenData = await response.json();
926
+
927
+ return {
928
+ _accessToken: tokenData.access_token,
929
+ refreshToken: tokenData.refresh_token,
930
+ idToken: tokenData.id_token,
931
+ expiresAt: Date.now() + tokenData.expires_in * 1000,
932
+ };
933
+ } catch (error) {
934
+ throw new Error(`OIDC token exchange failed: ${error.message}`);
935
+ }
936
+ }
937
+
938
+ // Development only
939
+ if (!callbackData.code) {
940
+ throw new Error('Authorization code is required');
941
+ }
942
+
943
+ return {
944
+ _accessToken: crypto.randomBytes(32).toString('hex'),
945
+ refreshToken: crypto.randomBytes(32).toString('hex'),
946
+ idToken: this._generateMockIdToken(),
947
+ expiresAt: Date.now() + 3600 * 1000,
948
+ };
949
+ }
950
+
951
+ async getUserInfo(accessToken) {
952
+ if (process.env.NODE_ENV === 'production') {
953
+ // Production OIDC userinfo
954
+ try {
955
+ const userResponse = await fetch(`${this._config.issuer}/userinfo`, {
956
+ headers: {
957
+ Authorization: `Bearer ${accessToken}`,
958
+ Accept: 'application/json',
959
+ },
960
+ });
961
+
962
+ if (!userResponse.ok) {
963
+ throw new Error(
964
+ `OIDC userinfo request failed: ${userResponse.status}`,
965
+ );
966
+ }
967
+
968
+ const userData = await userResponse.json();
969
+
970
+ return {
971
+ id: userData.sub,
972
+ email: userData.email,
973
+ name: userData.name || userData.preferred_username,
974
+ roles: userData.roles || [],
975
+ groups: userData.groups || [],
976
+ attributes: userData,
977
+ };
978
+ } catch (error) {
979
+ throw new Error(`OIDC getUserInfo failed: ${error.message}`);
980
+ }
981
+ }
982
+
983
+ // Development only
984
+ return {
985
+ id: 'dev-' + crypto.randomUUID(),
986
+ email: 'dev-user@oidc.com',
987
+ name: 'Development OIDC User',
988
+ roles: ['user', 'dev'],
989
+ };
990
+ }
991
+
992
+ _generateMockIdToken() {
993
+ if (process.env.NODE_ENV === 'production') {
994
+ throw new Error('Mock ID token generation not allowed in production');
995
+ }
996
+
997
+ // Development only - basic JWT structure
998
+ const header = Buffer.from(
999
+ JSON.stringify({ alg: 'HS256', typ: 'JWT' }),
1000
+ ).toString('base64url');
1001
+ const payload = Buffer.from(
1002
+ JSON.stringify({
1003
+ sub: 'dev-user-' + crypto.randomUUID(),
1004
+ iss: this._config.issuer || 'dev-issuer',
1005
+ aud: this._config.clientId,
1006
+ exp: Math.floor(Date.now() / 1000) + 3600,
1007
+ iat: Math.floor(Date.now() / 1000),
1008
+ email: 'dev-user@oidc.com',
1009
+ name: 'Development OIDC User',
1010
+ }),
1011
+ ).toString('base64url');
1012
+ const signature = crypto
1013
+ .createHmac('sha256', 'dev-secret')
1014
+ .update(`${header}.${payload}`)
1015
+ .digest('base64url');
1016
+
1017
+ return `${header}.${payload}.${signature}`;
1018
+ }
1019
+ }
1020
+
1021
+ module.exports = {
1022
+ SSOManager,
1023
+ SAMLProvider,
1024
+ OAuth2Provider,
1025
+ ActiveDirectoryProvider,
1026
+ OIDCProvider,
1027
+ };
1028
+
1029
+ // Ensure module.exports is properly defined
1030
+ module.exports.default = module.exports;