@bernierllc/auth-suite 1.0.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.
package/dist/suite.js ADDED
@@ -0,0 +1,1145 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.AuthSuite = void 0;
44
+ const auth_service_1 = require("@bernierllc/auth-service");
45
+ const logger_1 = require("@bernierllc/logger");
46
+ const email_sender_1 = require("@bernierllc/email-sender");
47
+ const crypto_utils_1 = require("@bernierllc/crypto-utils");
48
+ const session_manager_1 = require("./managers/session-manager");
49
+ const rbac_manager_1 = require("./managers/rbac-manager");
50
+ const mfa_manager_1 = require("./managers/mfa-manager");
51
+ const audit_manager_1 = require("./managers/audit-manager");
52
+ /**
53
+ * Complete authentication suite providing comprehensive auth functionality
54
+ *
55
+ * Features:
56
+ * - Full authentication service capabilities
57
+ * - Enhanced session management with cleanup
58
+ * - Role-based access control (RBAC)
59
+ * - Multi-factor authentication (MFA)
60
+ * - Comprehensive audit logging
61
+ * - OAuth/SAML/LDAP integration support
62
+ * - Advanced security features
63
+ */
64
+ class AuthSuite {
65
+ authService;
66
+ sessionManager;
67
+ rbacManager;
68
+ mfaManager;
69
+ auditManager;
70
+ logger;
71
+ config;
72
+ emailSender;
73
+ neverhubAdapter;
74
+ verificationTokens = new Map();
75
+ constructor(config) {
76
+ this.config = this.mergeWithDefaults(config);
77
+ this.logger = (0, logger_1.createLogger)({
78
+ level: logger_1.LogLevel.INFO,
79
+ context: { service: 'auth-suite' }
80
+ });
81
+ // Initialize core auth service
82
+ this.authService = new auth_service_1.AuthService(this.config);
83
+ // Initialize managers based on configuration
84
+ this.initializeManagers();
85
+ // Initialize email sender if configured
86
+ if (this.config.email?.enabled && this.config.email.provider) {
87
+ try {
88
+ this.emailSender = new email_sender_1.EmailSender(this.config.email.provider);
89
+ this.logger.info('Email sender initialized successfully');
90
+ }
91
+ catch (error) {
92
+ this.logger.warn('Failed to initialize email sender', { error });
93
+ if (this.config.email.fallback?.disableOnError) {
94
+ this.config.email.enabled = false;
95
+ this.logger.info('Email integration disabled due to initialization error');
96
+ }
97
+ }
98
+ }
99
+ // Initialize NeverHub adapter if configured
100
+ this.initializeNeverHub();
101
+ this.logger.info('AuthSuite initialized', {
102
+ features: {
103
+ sessions: !!this.config.sessions?.enabled,
104
+ rbac: !!this.config.rbac?.enabled,
105
+ mfa: !!this.config.mfaEnhanced?.enabled,
106
+ auditing: !!this.config.auditing?.enabled,
107
+ email: !!this.config.email?.enabled,
108
+ neverhub: !!this.neverhubAdapter
109
+ }
110
+ });
111
+ }
112
+ /**
113
+ * Register a new user with enhanced features
114
+ */
115
+ async register(credentials) {
116
+ try {
117
+ // Audit the registration attempt
118
+ await this.auditManager.log({
119
+ event: 'register_attempt',
120
+ level: 'info',
121
+ details: { email: credentials.email, username: credentials.username }
122
+ });
123
+ // Register user through auth service
124
+ const result = await this.authService.register(credentials);
125
+ if (result.success && result.user) {
126
+ // Create session if enabled
127
+ let session;
128
+ if (this.config.sessions?.enabled !== false) {
129
+ session = await this.sessionManager.create(result.user.id, {
130
+ registrationMethod: 'direct'
131
+ });
132
+ }
133
+ // Assign default role if RBAC is enabled
134
+ if (this.config.rbac?.enabled && this.config.rbac.roles.length > 0) {
135
+ const defaultRole = this.config.rbac.roles.find(r => r.name === 'user') || this.config.rbac.roles[0];
136
+ await this.rbacManager.assignRole(result.user.id, defaultRole.name);
137
+ }
138
+ // Get user permissions
139
+ const permissions = this.config.rbac?.enabled
140
+ ? await this.getUserPermissions(result.user.id)
141
+ : [];
142
+ // Send verification email if email integration is enabled
143
+ let emailVerificationRequired = false;
144
+ let verificationTokenSent = false;
145
+ if (this.shouldSendVerificationEmail()) {
146
+ try {
147
+ const verificationSent = await this.sendVerificationEmail(result.user.id, credentials.email);
148
+ emailVerificationRequired = this.config.email?.verification?.required ?? false;
149
+ verificationTokenSent = verificationSent;
150
+ }
151
+ catch (error) {
152
+ this.logger.warn('Failed to send verification email', { error, userId: result.user.id });
153
+ // Handle email error gracefully
154
+ if (this.config.email?.fallback?.disableOnError) {
155
+ this.logger.info('Disabling email verification due to send error');
156
+ emailVerificationRequired = false;
157
+ }
158
+ }
159
+ }
160
+ // Send welcome email if configured
161
+ if (this.shouldSendWelcomeEmail('registration')) {
162
+ try {
163
+ await this.sendWelcomeEmail(result.user.id, credentials.email, 'registration');
164
+ }
165
+ catch (error) {
166
+ this.logger.warn('Failed to send welcome email', { error, userId: result.user.id });
167
+ }
168
+ }
169
+ // Audit successful registration
170
+ await this.auditManager.log({
171
+ userId: result.user.id,
172
+ sessionId: session?.sessionId,
173
+ event: 'register',
174
+ level: 'info',
175
+ details: {
176
+ email: credentials.email,
177
+ emailVerificationRequired,
178
+ verificationTokenSent
179
+ }
180
+ });
181
+ return {
182
+ ...result,
183
+ session,
184
+ permissions,
185
+ emailVerificationRequired,
186
+ verificationTokenSent
187
+ };
188
+ }
189
+ // Audit failed registration
190
+ await this.auditManager.log({
191
+ event: 'register_failed',
192
+ level: 'warn',
193
+ details: { email: credentials.email, error: result.error }
194
+ });
195
+ return result;
196
+ }
197
+ catch (error) {
198
+ this.logger.error('Registration error', error instanceof Error ? error : undefined);
199
+ await this.auditManager.log({
200
+ event: 'register_error',
201
+ level: 'error',
202
+ details: { error: error instanceof Error ? error.message : String(error) }
203
+ });
204
+ return {
205
+ success: false,
206
+ error: 'Registration failed due to internal error'
207
+ };
208
+ }
209
+ }
210
+ /**
211
+ * Login user with enhanced security features
212
+ */
213
+ async login(credentials) {
214
+ try {
215
+ // Audit login attempt
216
+ await this.auditManager.log({
217
+ event: 'login_attempt',
218
+ level: 'info',
219
+ details: { identifier: credentials.email || credentials.username }
220
+ });
221
+ // Attempt authentication
222
+ const result = await this.authService.login(credentials);
223
+ if (result.success && result.user) {
224
+ // Check if MFA is required
225
+ const mfaRequired = this.config.mfaEnhanced?.enabled && await this.isMfaRequired(result.user.id);
226
+ if (mfaRequired) {
227
+ // Generate MFA challenge
228
+ const mfaChallenge = await this.mfaManager.generateChallenge(result.user.id);
229
+ await this.auditManager.log({
230
+ userId: result.user.id,
231
+ event: 'mfa_required',
232
+ level: 'info',
233
+ details: { challengeType: mfaChallenge.type }
234
+ });
235
+ return {
236
+ ...result,
237
+ mfaRequired: true,
238
+ mfaChallenge
239
+ };
240
+ }
241
+ // Complete login process
242
+ return await this.completeLogin(result);
243
+ }
244
+ // Audit failed login
245
+ await this.auditManager.log({
246
+ event: 'login_failed',
247
+ level: 'warn',
248
+ details: {
249
+ identifier: credentials.email || credentials.username,
250
+ error: result.error
251
+ }
252
+ });
253
+ return result;
254
+ }
255
+ catch (error) {
256
+ this.logger.error('Login error', error instanceof Error ? error : undefined);
257
+ await this.auditManager.log({
258
+ event: 'login_error',
259
+ level: 'error',
260
+ details: { error: error instanceof Error ? error.message : String(error) }
261
+ });
262
+ return {
263
+ success: false,
264
+ error: 'Login failed due to internal error'
265
+ };
266
+ }
267
+ }
268
+ /**
269
+ * Complete MFA verification and finish login
270
+ */
271
+ async completeMfaLogin(challengeId, response) {
272
+ try {
273
+ const verified = await this.mfaManager.verifyChallenge(challengeId, response);
274
+ if (!verified) {
275
+ await this.auditManager.log({
276
+ event: 'mfa_failed',
277
+ level: 'warn',
278
+ details: { challengeId }
279
+ });
280
+ return {
281
+ success: false,
282
+ error: 'Invalid MFA code'
283
+ };
284
+ }
285
+ // Get user from challenge (implementation depends on MFA manager)
286
+ // For now, return success - this would need proper user retrieval
287
+ await this.auditManager.log({
288
+ event: 'mfa_success',
289
+ level: 'info',
290
+ details: { challengeId }
291
+ });
292
+ return {
293
+ success: true,
294
+ user: {}, // Would retrieve actual user
295
+ token: '', // Would generate actual token
296
+ refreshToken: ''
297
+ };
298
+ }
299
+ catch (error) {
300
+ this.logger.error('MFA completion error', error instanceof Error ? error : undefined);
301
+ return {
302
+ success: false,
303
+ error: 'MFA verification failed'
304
+ };
305
+ }
306
+ }
307
+ /**
308
+ * Verify token with enhanced session management
309
+ */
310
+ async verifyToken(token) {
311
+ try {
312
+ const user = await this.authService.verifyToken(token);
313
+ if (user && this.config.sessions?.enabled !== false) {
314
+ // Update session last accessed time
315
+ // Note: This would require session ID from token or separate lookup
316
+ }
317
+ return user;
318
+ }
319
+ catch (error) {
320
+ this.logger.error('Token verification error', error instanceof Error ? error : undefined);
321
+ return null;
322
+ }
323
+ }
324
+ /**
325
+ * Check user permission with RBAC
326
+ */
327
+ async checkPermission(check) {
328
+ if (!this.config.rbac?.enabled) {
329
+ return { allowed: true, reason: 'RBAC disabled' };
330
+ }
331
+ try {
332
+ return await this.rbacManager.checkPermission(check);
333
+ }
334
+ catch (error) {
335
+ this.logger.error('Permission check error', error instanceof Error ? error : undefined);
336
+ return { allowed: false, reason: 'Permission check failed' };
337
+ }
338
+ }
339
+ /**
340
+ * Get user roles
341
+ */
342
+ async getUserRoles(userId) {
343
+ if (!this.config.rbac?.enabled) {
344
+ return [];
345
+ }
346
+ try {
347
+ return await this.rbacManager.getUserRoles(userId);
348
+ }
349
+ catch (error) {
350
+ this.logger.error('Get user roles error', error instanceof Error ? error : undefined);
351
+ return [];
352
+ }
353
+ }
354
+ /**
355
+ * Get user permissions (all permissions from all roles)
356
+ */
357
+ async getUserPermissions(userId) {
358
+ if (!this.config.rbac?.enabled) {
359
+ return [];
360
+ }
361
+ try {
362
+ const roles = await this.rbacManager.getUserRoles(userId);
363
+ const permissionSets = await Promise.all(roles.map(role => this.rbacManager.getRolePermissions(role)));
364
+ // Flatten and deduplicate permissions
365
+ return [...new Set(permissionSets.flat())];
366
+ }
367
+ catch (error) {
368
+ this.logger.error('Get user permissions error', error instanceof Error ? error : undefined);
369
+ return [];
370
+ }
371
+ }
372
+ /**
373
+ * Logout user and cleanup session
374
+ */
375
+ async logout(sessionId) {
376
+ try {
377
+ if (sessionId && this.config.sessions?.enabled !== false) {
378
+ const destroyed = await this.sessionManager.destroy(sessionId);
379
+ await this.auditManager.log({
380
+ sessionId,
381
+ event: 'logout',
382
+ level: 'info',
383
+ details: { sessionDestroyed: destroyed }
384
+ });
385
+ }
386
+ return { success: true };
387
+ }
388
+ catch (error) {
389
+ this.logger.error('Logout error', error instanceof Error ? error : undefined);
390
+ return { success: false };
391
+ }
392
+ }
393
+ /**
394
+ * Setup MFA for user
395
+ */
396
+ async setupMfa(userId, type) {
397
+ if (!this.config.mfaEnhanced?.enabled) {
398
+ throw new Error('MFA is not enabled');
399
+ }
400
+ try {
401
+ const result = await this.mfaManager.setupMfa(userId, type);
402
+ await this.auditManager.log({
403
+ userId,
404
+ event: 'mfa_setup',
405
+ level: 'info',
406
+ details: { type }
407
+ });
408
+ return result;
409
+ }
410
+ catch (error) {
411
+ this.logger.error('MFA setup error', error instanceof Error ? error : undefined);
412
+ throw error;
413
+ }
414
+ }
415
+ /**
416
+ * Get audit logs with filtering
417
+ */
418
+ async getAuditLogs(filters) {
419
+ if (!this.config.auditing?.enabled) {
420
+ return [];
421
+ }
422
+ try {
423
+ return await this.auditManager.query(filters);
424
+ }
425
+ catch (error) {
426
+ this.logger.error('Get audit logs error', error instanceof Error ? error : undefined);
427
+ return [];
428
+ }
429
+ }
430
+ /**
431
+ * Cleanup expired sessions and audit logs
432
+ */
433
+ async cleanup() {
434
+ try {
435
+ const sessionsCleaned = this.config.sessions?.enabled !== false
436
+ ? await this.sessionManager.cleanup()
437
+ : 0;
438
+ const auditLogsCleaned = this.config.auditing?.enabled && this.config.auditing.retention?.days
439
+ ? await this.auditManager.cleanup(this.config.auditing.retention.days)
440
+ : 0;
441
+ this.logger.info('Cleanup completed', { sessionsCleaned, auditLogsCleaned });
442
+ return { sessions: sessionsCleaned, auditLogs: auditLogsCleaned };
443
+ }
444
+ catch (error) {
445
+ this.logger.error('Cleanup error', error instanceof Error ? error : undefined);
446
+ return { sessions: 0, auditLogs: 0 };
447
+ }
448
+ }
449
+ /**
450
+ * Send verification email to user
451
+ */
452
+ async sendVerificationEmail(userId, email) {
453
+ if (!this.emailSender)
454
+ return false;
455
+ try {
456
+ // Generate verification token
457
+ const token = await (0, crypto_utils_1.generateSessionToken)();
458
+ const expiryHours = this.config.email?.verification?.tokenExpiry ?? 24;
459
+ const expiresAt = new Date(Date.now() + expiryHours * 60 * 60 * 1000);
460
+ // Store token for verification
461
+ this.verificationTokens.set(token, {
462
+ userId,
463
+ token,
464
+ expiresAt,
465
+ type: 'verification'
466
+ });
467
+ // Get template configuration with enhanced defaults
468
+ const template = this.config.email?.templates?.verification;
469
+ const defaultSubject = 'Verify your email address';
470
+ const defaultHtmlContent = `
471
+ <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
472
+ <h1 style="color: #333;">Email Verification Required</h1>
473
+ <p>Hello,</p>
474
+ <p>Please verify your email address by clicking the button below:</p>
475
+ <div style="text-align: center; margin: 20px 0;">
476
+ <a href="{{verificationUrl}}"
477
+ style="background-color: #007bff; color: white; padding: 12px 24px;
478
+ text-decoration: none; border-radius: 4px; display: inline-block;">
479
+ Verify Email Address
480
+ </a>
481
+ </div>
482
+ <p>Or copy and paste this link into your browser:</p>
483
+ <p style="word-break: break-all; color: #666;">{{verificationUrl}}</p>
484
+ <p><strong>This link will expire in {{expiryHours}} hours.</strong></p>
485
+ <hr style="margin: 20px 0; border: none; border-top: 1px solid #eee;">
486
+ <p style="font-size: 12px; color: #666;">
487
+ If you didn't create an account, you can safely ignore this email.
488
+ </p>
489
+ </div>
490
+ `;
491
+ // Build template variables
492
+ const verificationUrl = this.buildVerificationUrl(token);
493
+ const templateVariables = {
494
+ verificationUrl,
495
+ expiryHours: expiryHours.toString(),
496
+ userId,
497
+ email,
498
+ ...template?.customVariables
499
+ };
500
+ // Process template content
501
+ const subject = this.processTemplate(template?.subject || defaultSubject, templateVariables);
502
+ const htmlContent = this.processTemplate(template?.htmlContent || defaultHtmlContent, templateVariables);
503
+ const textContent = template?.textContent ?
504
+ this.processTemplate(template.textContent, templateVariables) :
505
+ `Please verify your email address by visiting: ${verificationUrl} (expires in ${expiryHours} hours)`;
506
+ // Prepare email message with template overrides
507
+ const emailMessage = {
508
+ toEmail: email,
509
+ subject,
510
+ htmlContent,
511
+ textContent,
512
+ fromName: template?.fromName,
513
+ fromEmail: template?.fromEmail,
514
+ replyTo: template?.replyTo
515
+ };
516
+ // Send email with NeverHub integration and fallback logic
517
+ const result = await this.sendEmailWithNeverHubFallback(emailMessage, 'verification');
518
+ if (result.success) {
519
+ this.logger.info('Verification email sent successfully', {
520
+ userId,
521
+ email,
522
+ tokenExpiry: expiresAt.toISOString()
523
+ });
524
+ return true;
525
+ }
526
+ else {
527
+ this.logger.error('Failed to send verification email', undefined, {
528
+ userId,
529
+ email,
530
+ errorMessage: result.errorMessage
531
+ });
532
+ return false;
533
+ }
534
+ }
535
+ catch (error) {
536
+ this.logger.error('Error sending verification email', error instanceof Error ? error : undefined, { userId, email });
537
+ return false;
538
+ }
539
+ }
540
+ /**
541
+ * Manually send verification email to a user
542
+ */
543
+ async sendVerificationEmailManually(userId, email) {
544
+ if (!this.config.email?.enabled) {
545
+ return { success: false, error: 'Email functionality is disabled' };
546
+ }
547
+ if (!this.config.email.verification?.enabled) {
548
+ return { success: false, error: 'Email verification is disabled' };
549
+ }
550
+ if (!this.emailSender) {
551
+ return { success: false, error: 'Email sender not configured' };
552
+ }
553
+ try {
554
+ await this.auditManager.log({
555
+ userId,
556
+ event: 'manual_verification_email_sent',
557
+ level: 'info',
558
+ details: { email }
559
+ });
560
+ const result = await this.sendVerificationEmail(userId, email);
561
+ return { success: result };
562
+ }
563
+ catch (error) {
564
+ this.logger.error('Error sending manual verification email', error instanceof Error ? error : undefined, { userId, email });
565
+ return {
566
+ success: false,
567
+ error: error instanceof Error ? error.message : 'Unknown error'
568
+ };
569
+ }
570
+ }
571
+ /**
572
+ * Get email configuration status
573
+ */
574
+ getEmailConfiguration() {
575
+ return {
576
+ enabled: this.config.email?.enabled ?? false,
577
+ verification: {
578
+ enabled: this.config.email?.verification?.enabled ?? false,
579
+ required: this.config.email?.verification?.required ?? false,
580
+ automaticSend: this.config.email?.verification?.automaticSend ?? false
581
+ },
582
+ welcome: {
583
+ enabled: this.config.email?.welcome?.enabled ?? false,
584
+ sendOnRegistration: this.config.email?.welcome?.sendOnRegistration ?? false,
585
+ sendOnFirstLogin: this.config.email?.welcome?.sendOnFirstLogin ?? false
586
+ },
587
+ passwordReset: {
588
+ enabled: this.config.email?.passwordReset?.enabled ?? false
589
+ },
590
+ providerConfigured: !!this.emailSender,
591
+ neverhub: {
592
+ enabled: this.config.email?.neverhub?.enabled ?? false,
593
+ available: !!this.neverhubAdapter,
594
+ overrideDirect: this.config.email?.neverhub?.overrideDirect ?? false,
595
+ fallbackToDirect: this.config.email?.neverhub?.fallbackToDirect ?? true,
596
+ serviceName: this.config.email?.neverhub?.serviceName ?? 'email'
597
+ }
598
+ };
599
+ }
600
+ /**
601
+ * Verify user email with token
602
+ */
603
+ async verifyEmail(token) {
604
+ const tokenData = this.verificationTokens.get(token);
605
+ if (!tokenData) {
606
+ return {
607
+ success: false,
608
+ error: 'Invalid verification token'
609
+ };
610
+ }
611
+ if (tokenData.expiresAt < new Date()) {
612
+ this.verificationTokens.delete(token);
613
+ return {
614
+ success: false,
615
+ error: 'Verification token has expired'
616
+ };
617
+ }
618
+ if (tokenData.type !== 'verification') {
619
+ return {
620
+ success: false,
621
+ error: 'Invalid token type'
622
+ };
623
+ }
624
+ try {
625
+ // Mark user as verified (this would depend on your user storage implementation)
626
+ // For now, we'll just return success
627
+ this.verificationTokens.delete(token);
628
+ await this.auditManager.log({
629
+ userId: tokenData.userId,
630
+ event: 'email_verified',
631
+ level: 'info',
632
+ details: { token: token.substring(0, 8) + '...' }
633
+ });
634
+ return {
635
+ success: true,
636
+ verified: true
637
+ };
638
+ }
639
+ catch (err) {
640
+ this.logger.error('Email verification error', err instanceof Error ? err : new Error(String(err)), { userId: tokenData.userId });
641
+ return {
642
+ success: false,
643
+ error: 'Email verification failed'
644
+ };
645
+ }
646
+ }
647
+ /**
648
+ * Send password reset email
649
+ */
650
+ async sendPasswordResetEmail(email) {
651
+ if (!this.emailSender) {
652
+ return { success: false, error: 'Email functionality not configured' };
653
+ }
654
+ try {
655
+ // This would typically look up user by email first
656
+ // For now, we'll generate a reset token
657
+ const token = await (0, crypto_utils_1.generateSessionToken)();
658
+ const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
659
+ // Store token for password reset
660
+ this.verificationTokens.set(token, {
661
+ userId: 'unknown', // Would be actual user ID
662
+ token,
663
+ expiresAt,
664
+ type: 'password_reset'
665
+ });
666
+ // Get template configuration
667
+ const template = this.config.email?.templates?.passwordReset;
668
+ const subject = template?.subject || 'Reset your password';
669
+ const htmlContent = template?.htmlContent || `
670
+ <h1>Password Reset</h1>
671
+ <p>Click the link below to reset your password:</p>
672
+ <a href="{{resetUrl}}">Reset Password</a>
673
+ <p>This link will expire in 1 hour.</p>
674
+ `;
675
+ // Prepare email message
676
+ const emailMessage = {
677
+ toEmail: email,
678
+ subject,
679
+ htmlContent: htmlContent.replace('{{resetUrl}}', `${this.config.email?.provider?.endpoint || ''}/reset?token=${token}`),
680
+ textContent: template?.textContent
681
+ };
682
+ // Send email
683
+ const result = await this.emailSender.sendEmail(emailMessage);
684
+ if (result.success) {
685
+ this.logger.info('Password reset email sent', { email });
686
+ await this.auditManager.log({
687
+ event: 'password_reset_requested',
688
+ level: 'info',
689
+ details: { email }
690
+ });
691
+ return { success: true };
692
+ }
693
+ else {
694
+ this.logger.error('Failed to send password reset email', undefined, { emailAddress: email, errorMessage: result.errorMessage });
695
+ return { success: false, error: result.errorMessage };
696
+ }
697
+ }
698
+ catch (err) {
699
+ this.logger.error('Error sending password reset email', err instanceof Error ? err : new Error(String(err)), { emailAddress: email });
700
+ return { success: false, error: 'Failed to send password reset email' };
701
+ }
702
+ }
703
+ // Private methods
704
+ mergeWithDefaults(config) {
705
+ return {
706
+ ...config,
707
+ sessions: {
708
+ enabled: true,
709
+ storage: 'memory',
710
+ cleanup: {
711
+ enabled: true,
712
+ intervalMs: 60000, // 1 minute
713
+ maxIdleMs: 24 * 60 * 60 * 1000 // 24 hours
714
+ },
715
+ persistence: {
716
+ enabled: false,
717
+ ttl: 7 * 24 * 60 * 60 * 1000 // 7 days
718
+ },
719
+ ...config.sessions
720
+ },
721
+ rbac: {
722
+ enabled: false,
723
+ roles: [],
724
+ permissions: [],
725
+ hierarchical: false,
726
+ ...config.rbac
727
+ },
728
+ mfaEnhanced: {
729
+ enabled: false,
730
+ providers: [],
731
+ requirements: [],
732
+ backup: {
733
+ enabled: true,
734
+ codeCount: 10,
735
+ codeLength: 8
736
+ },
737
+ ...config.mfaEnhanced
738
+ },
739
+ auditing: {
740
+ enabled: true,
741
+ events: [{
742
+ type: 'login',
743
+ level: 'info',
744
+ includeMetadata: true
745
+ }, {
746
+ type: 'logout',
747
+ level: 'info',
748
+ includeMetadata: true
749
+ }, {
750
+ type: 'register',
751
+ level: 'info',
752
+ includeMetadata: true
753
+ }, {
754
+ type: 'password_change',
755
+ level: 'info',
756
+ includeMetadata: true
757
+ }],
758
+ storage: 'file',
759
+ retention: {
760
+ days: 90,
761
+ maxSize: '100MB'
762
+ },
763
+ ...config.auditing
764
+ },
765
+ email: config.email ? {
766
+ enabled: config.email.enabled || false,
767
+ provider: config.email.provider,
768
+ neverhub: {
769
+ enabled: false,
770
+ serviceName: 'email',
771
+ overrideDirect: true,
772
+ fallbackToDirect: true,
773
+ timeout: 5000,
774
+ ...config.email.neverhub
775
+ },
776
+ verification: {
777
+ enabled: true,
778
+ required: false,
779
+ tokenExpiry: 24,
780
+ automaticSend: true,
781
+ ...config.email.verification
782
+ },
783
+ passwordReset: {
784
+ enabled: true,
785
+ tokenExpiry: 1,
786
+ ...config.email.passwordReset
787
+ },
788
+ welcome: {
789
+ enabled: false,
790
+ sendOnRegistration: false,
791
+ sendOnFirstLogin: false,
792
+ ...config.email.welcome
793
+ },
794
+ templates: {
795
+ verification: {
796
+ subject: 'Verify your email address',
797
+ htmlContent: '<h1>Email Verification</h1><p>Please verify your email address by clicking the link: {{verificationUrl}}</p>',
798
+ textContent: 'Please verify your email address by visiting: {{verificationUrl}}'
799
+ },
800
+ passwordReset: {
801
+ subject: 'Reset your password',
802
+ htmlContent: '<h1>Password Reset</h1><p>Click the link to reset your password: {{resetUrl}}</p>',
803
+ textContent: 'Click the link to reset your password: {{resetUrl}}'
804
+ },
805
+ welcome: {
806
+ subject: 'Welcome!',
807
+ htmlContent: '<h1>Welcome</h1><p>Thank you for joining us!</p>',
808
+ textContent: 'Welcome! Thank you for joining us!'
809
+ },
810
+ ...config.email.templates
811
+ },
812
+ fallback: {
813
+ disableOnError: false,
814
+ retryAttempts: 3,
815
+ ...config.email.fallback
816
+ }
817
+ } : undefined
818
+ };
819
+ }
820
+ /**
821
+ * Initialize NeverHub adapter if configured and available
822
+ */
823
+ async initializeNeverHub() {
824
+ if (!this.config.email?.neverhub?.enabled) {
825
+ return;
826
+ }
827
+ try {
828
+ // Try to dynamically import NeverHub adapter
829
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
830
+ const neverhubModule = await Promise.resolve(`${'@bernierllc/neverhub-adapter'}`).then(s => __importStar(require(s))).catch(() => null);
831
+ if (!neverhubModule) {
832
+ this.logger.info('NeverHub adapter not available, using direct email integration only');
833
+ return;
834
+ }
835
+ const { NeverHubAdapter: Adapter } = neverhubModule;
836
+ // Check if NeverHub is available
837
+ if (await Adapter.detect()) {
838
+ this.neverhubAdapter = new Adapter();
839
+ // Register with NeverHub
840
+ await this.neverhubAdapter.register({
841
+ type: 'auth-service',
842
+ name: '@bernierllc/auth-suite',
843
+ version: '1.0.0',
844
+ capabilities: [
845
+ { type: 'email', name: 'email-sending', version: '1.0.0' }
846
+ ],
847
+ dependencies: [this.config.email.neverhub.serviceName || 'email']
848
+ });
849
+ this.logger.info('NeverHub adapter initialized successfully for auth-suite', {
850
+ serviceName: this.config.email.neverhub.serviceName || 'email'
851
+ });
852
+ }
853
+ else {
854
+ this.logger.info('NeverHub not available, using direct email integration only');
855
+ }
856
+ }
857
+ catch (error) {
858
+ this.logger.warn('Failed to initialize NeverHub adapter', { error });
859
+ if (!this.config.email.neverhub.fallbackToDirect) {
860
+ this.logger.warn('NeverHub fallback disabled, email functionality may be limited');
861
+ }
862
+ }
863
+ }
864
+ initializeManagers() {
865
+ // Initialize session manager
866
+ this.sessionManager = new session_manager_1.MemorySessionManager(this.config.sessions || {});
867
+ // Initialize RBAC manager
868
+ this.rbacManager = new rbac_manager_1.BasicRBACManager(this.config.rbac || { enabled: false, roles: [], permissions: [], hierarchical: false });
869
+ // Initialize MFA manager
870
+ this.mfaManager = new mfa_manager_1.TOTPMfaManager(this.config.mfaEnhanced || { enabled: false, providers: [], requirements: [], backup: { enabled: false, codeCount: 0, codeLength: 0 } });
871
+ // Initialize audit manager
872
+ this.auditManager = new audit_manager_1.FileAuditManager(this.config.auditing || { enabled: false, events: [], storage: 'file', retention: { days: 0, maxSize: '' } });
873
+ }
874
+ async completeLogin(result) {
875
+ // Create session
876
+ let session;
877
+ if (this.config.sessions?.enabled !== false) {
878
+ session = await this.sessionManager.create(result.user.id, {
879
+ loginMethod: 'password'
880
+ });
881
+ }
882
+ // Get permissions
883
+ const permissions = this.config.rbac?.enabled
884
+ ? await this.getUserPermissions(result.user.id)
885
+ : [];
886
+ // Send welcome email if configured for first login
887
+ if (this.shouldSendWelcomeEmail('login')) {
888
+ try {
889
+ await this.sendWelcomeEmail(result.user.id, result.user.email, 'login');
890
+ }
891
+ catch (error) {
892
+ this.logger.warn('Failed to send welcome email on login', { error, userId: result.user.id });
893
+ }
894
+ }
895
+ // Audit successful login
896
+ await this.auditManager.log({
897
+ userId: result.user.id,
898
+ sessionId: session?.sessionId,
899
+ event: 'login',
900
+ level: 'info',
901
+ details: { method: 'password' }
902
+ });
903
+ return {
904
+ ...result,
905
+ session,
906
+ permissions
907
+ };
908
+ }
909
+ async isMfaRequired(userId) {
910
+ // Check MFA requirements based on configuration
911
+ // This is a simplified implementation
912
+ return this.config.mfaEnhanced?.requirements.some(req => req.trigger === 'login') || false;
913
+ }
914
+ /**
915
+ * Check if verification email should be sent
916
+ */
917
+ shouldSendVerificationEmail() {
918
+ return !!(this.emailSender &&
919
+ this.config.email?.enabled &&
920
+ this.config.email.verification?.enabled !== false &&
921
+ this.config.email.verification?.automaticSend !== false);
922
+ }
923
+ /**
924
+ * Check if welcome email should be sent
925
+ */
926
+ shouldSendWelcomeEmail(trigger) {
927
+ if (!this.emailSender || !this.config.email?.enabled || !this.config.email.welcome?.enabled) {
928
+ return false;
929
+ }
930
+ if (trigger === 'registration') {
931
+ return this.config.email.welcome.sendOnRegistration !== false;
932
+ }
933
+ if (trigger === 'login') {
934
+ return this.config.email.welcome.sendOnFirstLogin === true;
935
+ }
936
+ return false;
937
+ }
938
+ /**
939
+ * Build verification URL with token
940
+ */
941
+ buildVerificationUrl(token) {
942
+ const baseUrl = this.config.email?.provider?.endpoint || 'https://example.com';
943
+ return `${baseUrl}/verify?token=${token}`;
944
+ }
945
+ /**
946
+ * Process template variables in content
947
+ */
948
+ processTemplate(content, variables) {
949
+ let processed = content;
950
+ Object.entries(variables).forEach(([key, value]) => {
951
+ const pattern = new RegExp(`{{${key}}}`, 'g');
952
+ processed = processed.replace(pattern, value || '');
953
+ });
954
+ return processed;
955
+ }
956
+ /**
957
+ * Send email with NeverHub integration and fallback logic
958
+ */
959
+ async sendEmailWithNeverHubFallback(emailMessage, type) {
960
+ // Check if NeverHub should be used
961
+ if (this.shouldUseNeverHub()) {
962
+ try {
963
+ this.logger.debug('Attempting to send email via NeverHub', { type, toEmail: emailMessage.toEmail });
964
+ const neverhubResult = await this.sendEmailViaNeverHub(emailMessage, type);
965
+ if (neverhubResult.success) {
966
+ this.logger.info('Email sent successfully via NeverHub', { type, method: 'neverhub' });
967
+ return { ...neverhubResult, method: 'neverhub' };
968
+ }
969
+ else {
970
+ this.logger.warn('NeverHub email sending failed', {
971
+ type,
972
+ error: neverhubResult.errorMessage,
973
+ fallbackEnabled: this.config.email?.neverhub?.fallbackToDirect
974
+ });
975
+ // Fall back to direct email if configured
976
+ if (this.config.email?.neverhub?.fallbackToDirect && this.emailSender) {
977
+ this.logger.info('Falling back to direct email sending', { type });
978
+ return this.sendEmailWithRetry(emailMessage, type);
979
+ }
980
+ else {
981
+ return neverhubResult;
982
+ }
983
+ }
984
+ }
985
+ catch (err) {
986
+ this.logger.error('Error sending email via NeverHub', err instanceof Error ? err : new Error(String(err)), { emailType: type });
987
+ // Fall back to direct email if configured
988
+ if (this.config.email?.neverhub?.fallbackToDirect && this.emailSender) {
989
+ this.logger.info('Falling back to direct email due to NeverHub error', { type });
990
+ return this.sendEmailWithRetry(emailMessage, type);
991
+ }
992
+ else {
993
+ return {
994
+ success: false,
995
+ errorMessage: err instanceof Error ? err.message : 'NeverHub email error'
996
+ };
997
+ }
998
+ }
999
+ }
1000
+ // Use direct email sending
1001
+ if (this.emailSender) {
1002
+ return this.sendEmailWithRetry(emailMessage, type);
1003
+ }
1004
+ return { success: false, errorMessage: 'No email sending method available' };
1005
+ }
1006
+ /**
1007
+ * Send email via NeverHub service discovery
1008
+ */
1009
+ async sendEmailViaNeverHub(emailMessage, type) {
1010
+ if (!this.neverhubAdapter) {
1011
+ return { success: false, errorMessage: 'NeverHub adapter not initialized' };
1012
+ }
1013
+ try {
1014
+ const serviceName = this.config.email?.neverhub?.serviceName || 'email';
1015
+ const timeout = this.config.email?.neverhub?.timeout || 5000;
1016
+ // Get email service from NeverHub
1017
+ const emailService = await this.neverhubAdapter.getService(serviceName);
1018
+ if (!emailService) {
1019
+ return { success: false, errorMessage: `Email service '${serviceName}' not found in NeverHub` };
1020
+ }
1021
+ // Send email message via NeverHub
1022
+ const result = await Promise.race([
1023
+ this.neverhubAdapter.publishEvent({
1024
+ type: 'email.send',
1025
+ data: {
1026
+ ...emailMessage,
1027
+ metadata: {
1028
+ source: 'auth-suite',
1029
+ emailType: type,
1030
+ timestamp: new Date().toISOString()
1031
+ }
1032
+ },
1033
+ targetService: serviceName
1034
+ }),
1035
+ new Promise((_, reject) => setTimeout(() => reject(new Error('NeverHub email timeout')), timeout))
1036
+ ]);
1037
+ return { success: true };
1038
+ }
1039
+ catch (error) {
1040
+ return {
1041
+ success: false,
1042
+ errorMessage: error instanceof Error ? error.message : 'NeverHub communication error'
1043
+ };
1044
+ }
1045
+ }
1046
+ /**
1047
+ * Check if NeverHub should be used for email sending
1048
+ */
1049
+ shouldUseNeverHub() {
1050
+ return !!(this.neverhubAdapter &&
1051
+ this.config.email?.neverhub?.enabled &&
1052
+ this.config.email.neverhub.overrideDirect);
1053
+ }
1054
+ /**
1055
+ * Send email with retry logic
1056
+ */
1057
+ async sendEmailWithRetry(emailMessage, type) {
1058
+ const maxRetries = this.config.email?.fallback?.retryAttempts ?? 3;
1059
+ let lastError;
1060
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1061
+ try {
1062
+ const result = await this.emailSender.sendEmail(emailMessage);
1063
+ if (result.success) {
1064
+ if (attempt > 1) {
1065
+ this.logger.info(`Email sent successfully on attempt ${attempt}`, { type });
1066
+ }
1067
+ return { ...result, method: 'direct' };
1068
+ }
1069
+ else {
1070
+ lastError = result.errorMessage;
1071
+ if (attempt < maxRetries) {
1072
+ this.logger.warn(`Email send attempt ${attempt} failed, retrying...`, {
1073
+ type,
1074
+ error: result.errorMessage
1075
+ });
1076
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
1077
+ }
1078
+ }
1079
+ }
1080
+ catch (error) {
1081
+ lastError = error instanceof Error ? error.message : String(error);
1082
+ if (attempt < maxRetries) {
1083
+ this.logger.warn(`Email send attempt ${attempt} error, retrying...`, { type, error });
1084
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
1085
+ }
1086
+ }
1087
+ }
1088
+ this.logger.error(`Failed to send email after ${maxRetries} attempts`, undefined, { emailType: type, lastError });
1089
+ return { success: false, errorMessage: lastError, method: 'direct' };
1090
+ }
1091
+ /**
1092
+ * Send welcome email to user
1093
+ */
1094
+ async sendWelcomeEmail(userId, email, trigger) {
1095
+ if (!this.emailSender)
1096
+ return false;
1097
+ try {
1098
+ const template = this.config.email?.templates?.welcome;
1099
+ const defaultSubject = 'Welcome to our platform!';
1100
+ const defaultHtmlContent = `
1101
+ <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
1102
+ <h1 style="color: #333;">Welcome!</h1>
1103
+ <p>Thank you for joining our platform. We're excited to have you on board!</p>
1104
+ <p>If you have any questions, feel free to reach out to our support team.</p>
1105
+ <p>Best regards,<br>The Team</p>
1106
+ </div>
1107
+ `;
1108
+ const templateVariables = {
1109
+ userId,
1110
+ email,
1111
+ trigger,
1112
+ ...template?.customVariables
1113
+ };
1114
+ const subject = this.processTemplate(template?.subject || defaultSubject, templateVariables);
1115
+ const htmlContent = this.processTemplate(template?.htmlContent || defaultHtmlContent, templateVariables);
1116
+ const textContent = template?.textContent ?
1117
+ this.processTemplate(template.textContent, templateVariables) :
1118
+ 'Welcome to our platform! Thank you for joining us.';
1119
+ const emailMessage = {
1120
+ toEmail: email,
1121
+ subject,
1122
+ htmlContent,
1123
+ textContent,
1124
+ fromName: template?.fromName,
1125
+ fromEmail: template?.fromEmail,
1126
+ replyTo: template?.replyTo
1127
+ };
1128
+ const result = await this.sendEmailWithNeverHubFallback(emailMessage, 'welcome');
1129
+ if (result.success) {
1130
+ this.logger.info('Welcome email sent successfully', { userIdValue: userId, emailAddress: email, triggerType: trigger });
1131
+ return true;
1132
+ }
1133
+ else {
1134
+ this.logger.error('Failed to send welcome email', undefined, { userIdValue: userId, emailAddress: email, errorMessage: result.errorMessage });
1135
+ return false;
1136
+ }
1137
+ }
1138
+ catch (err) {
1139
+ this.logger.error('Error sending welcome email', err instanceof Error ? err : new Error(String(err)), { userIdValue: userId, emailAddress: email });
1140
+ return false;
1141
+ }
1142
+ }
1143
+ }
1144
+ exports.AuthSuite = AuthSuite;
1145
+ //# sourceMappingURL=suite.js.map