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