@lenne.tech/nest-server 11.11.1 → 11.13.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 (113) hide show
  1. package/dist/config.env.js +1 -0
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/interfaces/server-options.interface.d.ts +16 -0
  4. package/dist/core/modules/auth/core-auth.controller.js +1 -1
  5. package/dist/core/modules/auth/core-auth.controller.js.map +1 -1
  6. package/dist/core/modules/auth/core-auth.resolver.js +1 -1
  7. package/dist/core/modules/auth/core-auth.resolver.js.map +1 -1
  8. package/dist/core/modules/better-auth/better-auth-token.service.js +1 -4
  9. package/dist/core/modules/better-auth/better-auth-token.service.js.map +1 -1
  10. package/dist/core/modules/better-auth/better-auth.config.d.ts +13 -0
  11. package/dist/core/modules/better-auth/better-auth.config.js +114 -17
  12. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
  13. package/dist/core/modules/better-auth/better-auth.resolver.d.ts +7 -3
  14. package/dist/core/modules/better-auth/better-auth.resolver.js +16 -6
  15. package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -1
  16. package/dist/core/modules/better-auth/core-better-auth-api.middleware.d.ts +4 -2
  17. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +63 -18
  18. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
  19. package/dist/core/modules/better-auth/core-better-auth-auth.model.d.ts +1 -0
  20. package/dist/core/modules/better-auth/core-better-auth-auth.model.js +7 -0
  21. package/dist/core/modules/better-auth/core-better-auth-auth.model.js.map +1 -1
  22. package/dist/core/modules/better-auth/core-better-auth-cookie.helper.d.ts +41 -0
  23. package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js +107 -0
  24. package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js.map +1 -0
  25. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.d.ts +48 -0
  26. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +241 -0
  27. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -0
  28. package/dist/core/modules/better-auth/core-better-auth-models.d.ts +2 -1
  29. package/dist/core/modules/better-auth/core-better-auth-models.js +8 -4
  30. package/dist/core/modules/better-auth/core-better-auth-models.js.map +1 -1
  31. package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.d.ts +18 -0
  32. package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.js +82 -0
  33. package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.js.map +1 -0
  34. package/dist/core/modules/better-auth/core-better-auth-token.helper.d.ts +16 -0
  35. package/dist/core/modules/better-auth/core-better-auth-token.helper.js +66 -0
  36. package/dist/core/modules/better-auth/core-better-auth-token.helper.js.map +1 -0
  37. package/dist/core/modules/better-auth/core-better-auth-user.mapper.d.ts +0 -1
  38. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +15 -8
  39. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
  40. package/dist/core/modules/better-auth/core-better-auth-web.helper.d.ts +3 -3
  41. package/dist/core/modules/better-auth/core-better-auth-web.helper.js +64 -44
  42. package/dist/core/modules/better-auth/core-better-auth-web.helper.js.map +1 -1
  43. package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +13 -1
  44. package/dist/core/modules/better-auth/core-better-auth.controller.js +108 -49
  45. package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
  46. package/dist/core/modules/better-auth/core-better-auth.middleware.d.ts +0 -1
  47. package/dist/core/modules/better-auth/core-better-auth.middleware.js +57 -39
  48. package/dist/core/modules/better-auth/core-better-auth.middleware.js.map +1 -1
  49. package/dist/core/modules/better-auth/core-better-auth.module.d.ts +6 -0
  50. package/dist/core/modules/better-auth/core-better-auth.module.js +129 -24
  51. package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
  52. package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +12 -5
  53. package/dist/core/modules/better-auth/core-better-auth.resolver.js +64 -17
  54. package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
  55. package/dist/core/modules/better-auth/core-better-auth.service.d.ts +4 -1
  56. package/dist/core/modules/better-auth/core-better-auth.service.js +143 -23
  57. package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
  58. package/dist/core/modules/better-auth/index.d.ts +4 -0
  59. package/dist/core/modules/better-auth/index.js +4 -0
  60. package/dist/core/modules/better-auth/index.js.map +1 -1
  61. package/dist/core/modules/error-code/error-codes.d.ts +45 -0
  62. package/dist/core/modules/error-code/error-codes.js +40 -0
  63. package/dist/core/modules/error-code/error-codes.js.map +1 -1
  64. package/dist/core/modules/user/core-user.model.d.ts +1 -0
  65. package/dist/core/modules/user/core-user.model.js +11 -0
  66. package/dist/core/modules/user/core-user.model.js.map +1 -1
  67. package/dist/server/modules/better-auth/better-auth.controller.d.ts +3 -1
  68. package/dist/server/modules/better-auth/better-auth.controller.js +12 -3
  69. package/dist/server/modules/better-auth/better-auth.controller.js.map +1 -1
  70. package/dist/server/modules/better-auth/better-auth.resolver.d.ts +7 -3
  71. package/dist/server/modules/better-auth/better-auth.resolver.js +16 -6
  72. package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -1
  73. package/dist/server/modules/error-code/error-codes.d.ts +5 -0
  74. package/dist/server/modules/user/user.model.d.ts +5 -0
  75. package/dist/templates/email-verification-de.ejs +78 -0
  76. package/dist/templates/email-verification-en.ejs +78 -0
  77. package/dist/test/test.helper.d.ts +4 -0
  78. package/dist/test/test.helper.js +54 -1
  79. package/dist/test/test.helper.js.map +1 -1
  80. package/dist/tsconfig.build.tsbuildinfo +1 -1
  81. package/package.json +10 -10
  82. package/src/config.env.ts +2 -0
  83. package/src/core/common/interfaces/server-options.interface.ts +240 -0
  84. package/src/core/modules/auth/core-auth.controller.ts +2 -2
  85. package/src/core/modules/auth/core-auth.resolver.ts +2 -2
  86. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +113 -0
  87. package/src/core/modules/better-auth/README.md +72 -7
  88. package/src/core/modules/better-auth/better-auth-token.service.ts +5 -8
  89. package/src/core/modules/better-auth/better-auth.config.ts +282 -29
  90. package/src/core/modules/better-auth/better-auth.resolver.ts +16 -5
  91. package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +100 -22
  92. package/src/core/modules/better-auth/core-better-auth-auth.model.ts +10 -0
  93. package/src/core/modules/better-auth/core-better-auth-cookie.helper.ts +323 -0
  94. package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +433 -0
  95. package/src/core/modules/better-auth/core-better-auth-models.ts +6 -3
  96. package/src/core/modules/better-auth/core-better-auth-signup-validator.service.ts +178 -0
  97. package/src/core/modules/better-auth/core-better-auth-token.helper.ts +200 -0
  98. package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +18 -14
  99. package/src/core/modules/better-auth/core-better-auth-web.helper.ts +119 -69
  100. package/src/core/modules/better-auth/core-better-auth.controller.ts +197 -84
  101. package/src/core/modules/better-auth/core-better-auth.middleware.ts +93 -64
  102. package/src/core/modules/better-auth/core-better-auth.module.ts +215 -38
  103. package/src/core/modules/better-auth/core-better-auth.resolver.ts +140 -20
  104. package/src/core/modules/better-auth/core-better-auth.service.ts +210 -32
  105. package/src/core/modules/better-auth/index.ts +4 -0
  106. package/src/core/modules/error-code/error-codes.ts +45 -0
  107. package/src/core/modules/user/core-user.model.ts +15 -0
  108. package/src/server/modules/better-auth/better-auth.controller.ts +6 -2
  109. package/src/server/modules/better-auth/better-auth.resolver.ts +16 -5
  110. package/src/templates/email-verification-de.ejs +78 -0
  111. package/src/templates/email-verification-en.ejs +78 -0
  112. package/src/test/README.md +190 -0
  113. package/src/test/test.helper.ts +82 -1
@@ -14,6 +14,7 @@ import { getConnectionToken } from '@nestjs/mongoose';
14
14
  import mongoose, { Connection } from 'mongoose';
15
15
 
16
16
  import { IBetterAuth } from '../../common/interfaces/server-options.interface';
17
+ import { BrevoService } from '../../common/services/brevo.service';
17
18
  import { ConfigService } from '../../common/services/config.service';
18
19
  import { RolesGuardRegistry } from '../auth/guards/roles-guard-registry';
19
20
  import { RolesGuard } from '../auth/guards/roles.guard';
@@ -22,8 +23,10 @@ import { BetterAuthInstance, createBetterAuthInstance } from './better-auth.conf
22
23
  import { DefaultBetterAuthResolver } from './better-auth.resolver';
23
24
  import { CoreBetterAuthApiMiddleware } from './core-better-auth-api.middleware';
24
25
  import { CoreBetterAuthChallengeService } from './core-better-auth-challenge.service';
26
+ import { CoreBetterAuthEmailVerificationService } from './core-better-auth-email-verification.service';
25
27
  import { CoreBetterAuthRateLimitMiddleware } from './core-better-auth-rate-limit.middleware';
26
28
  import { CoreBetterAuthRateLimiter } from './core-better-auth-rate-limiter.service';
29
+ import { CoreBetterAuthSignUpValidatorService } from './core-better-auth-signup-validator.service';
27
30
  import { CoreBetterAuthUserMapper } from './core-better-auth-user.mapper';
28
31
  import { CoreBetterAuthController } from './core-better-auth.controller';
29
32
  import { CoreBetterAuthMiddleware } from './core-better-auth.middleware';
@@ -222,6 +225,9 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
222
225
  private static shouldRegisterRolesGuardGlobally = false;
223
226
  // Track if registerRolesGuardGlobally was explicitly set to false (for warning)
224
227
  private static rolesGuardExplicitlyDisabled = false;
228
+ // Static reference to email verification service for Better-Auth hooks (outside DI context)
229
+ private static emailVerificationService: CoreBetterAuthEmailVerificationService | null = null;
230
+ private static mongoConnection: Connection | null = null;
225
231
 
226
232
  /**
227
233
  * Gets the controller class to use (custom or default)
@@ -253,6 +259,26 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
253
259
  this.rateLimiter.configure(CoreBetterAuthModule.currentConfig.rateLimit);
254
260
  }
255
261
 
262
+ // Configuration warning: cookies: false without jwt enabled
263
+ // When cookies are disabled, BetterAuth needs JWT plugin to issue tokens via Authorization header
264
+ // JWT is enabled by default (same logic as CoreBetterAuthService.isJwtEnabled()),
265
+ // so only warn when explicitly disabled via `jwt: false` or `jwt: { enabled: false }`
266
+ if (CoreBetterAuthModule.currentConfig) {
267
+ const globalConfig = ConfigService.configFastButReadOnly;
268
+ const cookiesDisabled = globalConfig?.cookies === false;
269
+ const jwtExplicitlyDisabled = CoreBetterAuthModule.currentConfig.jwt === false
270
+ || (typeof CoreBetterAuthModule.currentConfig.jwt === 'object' && CoreBetterAuthModule.currentConfig.jwt?.enabled === false);
271
+
272
+ if (cookiesDisabled && jwtExplicitlyDisabled) {
273
+ CoreBetterAuthModule.logger.warn(
274
+ 'CONFIGURATION WARNING: cookies is set to false, but betterAuth.jwt is not enabled. ' +
275
+ 'Without cookies, BetterAuth cannot establish sessions via Set-Cookie headers. ' +
276
+ 'Enable betterAuth.jwt (set jwt: true in betterAuth config) to use Bearer token authentication, ' +
277
+ 'or set cookies: true to use cookie-based sessions.',
278
+ );
279
+ }
280
+ }
281
+
256
282
  // Security warning: Check if RolesGuard is registered when explicitly disabled
257
283
  // This warning helps developers identify potential security misconfigurations
258
284
  if (CoreBetterAuthModule.rolesGuardExplicitlyDisabled && !RolesGuardRegistry.isRegistered()) {
@@ -265,22 +291,27 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
265
291
  }
266
292
 
267
293
  /**
268
- * Configure middleware for Better-Auth API handling, session validation, and rate limiting.
294
+ * Configure middleware for Better-Auth session validation, API handling, and rate limiting.
269
295
  *
270
296
  * Middleware order (important!):
271
- * 1. CoreBetterAuthApiMiddleware - Forwards plugin endpoints (passkey, etc.) to Better Auth's native handler
297
+ * 1. CoreBetterAuthMiddleware - Session validation and user mapping for all routes
298
+ * Must run FIRST so that req.betterAuthSession is available for downstream middleware.
299
+ * In JWT mode, this resolves the JWT to a real DB session (via getActiveSessionForUser).
272
300
  * 2. CoreBetterAuthRateLimitMiddleware - Rate limiting for auth endpoints
273
- * 3. CoreBetterAuthMiddleware - Session validation and user mapping for all routes
301
+ * 3. CoreBetterAuthApiMiddleware - Forwards plugin endpoints (passkey, 2FA, etc.) to Better Auth's native handler
302
+ * Runs AFTER session middleware so it can use req.betterAuthSession.session.token
303
+ * to authenticate requests in JWT mode.
274
304
  */
275
305
  configure(consumer: MiddlewareConsumer) {
276
306
  // Only apply middleware if Better-Auth is enabled
277
307
  if (CoreBetterAuthModule.betterAuthEnabled && this.betterAuthService?.isEnabled()) {
278
308
  const basePath = CoreBetterAuthModule.currentConfig?.basePath || '/iam';
279
309
 
280
- // Apply API middleware to Better-Auth endpoints FIRST
281
- // This handles plugin endpoints (passkey, social login, etc.) that are not defined in the controller
282
- consumer.apply(CoreBetterAuthApiMiddleware).forRoutes(`${basePath}/*path`);
283
- CoreBetterAuthModule.logger.debug(`CoreBetterAuthApiMiddleware registered for ${basePath}/*path endpoints`);
310
+ // Apply session middleware to all routes FIRST
311
+ // This resolves JWT tokens to DB sessions, making req.betterAuthSession available
312
+ // for the API middleware to use when forwarding to Better Auth's native handler.
313
+ consumer.apply(CoreBetterAuthMiddleware).forRoutes('(.*)'); // New path-to-regexp syntax for wildcard
314
+ CoreBetterAuthModule.logger.debug('CoreBetterAuthMiddleware registered for all routes');
284
315
 
285
316
  // Apply rate limiting to Better-Auth endpoints only
286
317
  if (CoreBetterAuthModule.currentConfig?.rateLimit?.enabled) {
@@ -288,9 +319,11 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
288
319
  CoreBetterAuthModule.logger.debug(`Rate limiting middleware registered for ${basePath}/*path endpoints`);
289
320
  }
290
321
 
291
- // Apply session middleware to all routes
292
- consumer.apply(CoreBetterAuthMiddleware).forRoutes('(.*)'); // New path-to-regexp syntax for wildcard
293
- CoreBetterAuthModule.logger.debug('CoreBetterAuthMiddleware registered for all routes');
322
+ // Apply API middleware to Better-Auth endpoints LAST
323
+ // This handles plugin endpoints (passkey, 2FA, social login, etc.) that are not defined in the controller.
324
+ // It uses req.betterAuthSession (set by session middleware above) for JWT mode authentication.
325
+ consumer.apply(CoreBetterAuthApiMiddleware).forRoutes(`${basePath}/*path`);
326
+ CoreBetterAuthModule.logger.debug(`CoreBetterAuthApiMiddleware registered for ${basePath}/*path endpoints`);
294
327
  }
295
328
  }
296
329
 
@@ -381,6 +414,8 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
381
414
  // If better-auth is disabled (config is null or enabled: false), return minimal module
382
415
  // Note: We don't provide middleware classes when disabled because they depend on CoreBetterAuthService
383
416
  // and won't be used anyway (middleware is only applied when enabled)
417
+ // Note: EmailVerificationService and SignUpValidatorService are not provided in disabled mode
418
+ // because they require ConfigService and are only useful when BetterAuth is enabled
384
419
  if (config === null || config?.enabled === false) {
385
420
  this.logger.debug('BetterAuth is disabled - skipping initialization');
386
421
  this.betterAuthEnabled = false;
@@ -399,6 +434,8 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
399
434
  CoreBetterAuthRateLimiter,
400
435
  BetterAuthTokenService,
401
436
  CoreBetterAuthChallengeService,
437
+ // Note: EmailVerificationService and SignUpValidatorService are NOT provided when disabled
438
+ // because they require ConfigService and have no purpose when BetterAuth is disabled
402
439
  ],
403
440
  };
404
441
  }
@@ -412,7 +449,8 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
412
449
  // Always use deferred initialization to ensure MongoDB is ready
413
450
  // This prevents timing issues during application startup
414
451
  // Pass server-level URLs for Passkey auto-detection (using effective values from ConfigService fallback)
415
- return this.createDeferredModule(config, effectiveFallbackSecrets, {
452
+ return this.createDeferredModule(config, {
453
+ fallbackSecrets: effectiveFallbackSecrets,
416
454
  serverAppUrl: effectiveServerAppUrl,
417
455
  serverBaseUrl: effectiveServerBaseUrl,
418
456
  serverEnv: effectiveServerEnv,
@@ -428,14 +466,32 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
428
466
  static forRootAsync(): DynamicModule {
429
467
  return {
430
468
  controllers: [this.getControllerClass()],
431
- exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter, BetterAuthTokenService, CoreBetterAuthChallengeService],
469
+ exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter, BetterAuthTokenService, CoreBetterAuthChallengeService, CoreBetterAuthEmailVerificationService, CoreBetterAuthSignUpValidatorService],
432
470
  imports: [],
433
471
  module: CoreBetterAuthModule,
434
472
  providers: [
473
+ // Optional BrevoService: uses factory to avoid constructor error when brevo config is missing
435
474
  {
436
475
  inject: [ConfigService],
476
+ provide: CoreBetterAuthEmailVerificationService.BREVO_SERVICE_TOKEN,
477
+ useFactory: (configService: ConfigService) => {
478
+ if (configService.configFastButReadOnly?.brevo?.apiKey) {
479
+ return new BrevoService(configService);
480
+ }
481
+ return null;
482
+ },
483
+ },
484
+ // Email verification service - must be initialized early for callbacks
485
+ CoreBetterAuthEmailVerificationService,
486
+ // Sign-up validator service
487
+ CoreBetterAuthSignUpValidatorService,
488
+ {
489
+ inject: [ConfigService, CoreBetterAuthEmailVerificationService],
437
490
  provide: BETTER_AUTH_INSTANCE,
438
- useFactory: async (configService: ConfigService) => {
491
+ useFactory: async (configService: ConfigService, emailVerificationService: CoreBetterAuthEmailVerificationService) => {
492
+ // Set static reference for callbacks BEFORE creating Better-Auth instance
493
+ this.setEmailVerificationService(emailVerificationService);
494
+
439
495
  // Get raw config (can be boolean or object)
440
496
  const rawConfig = configService.get<boolean | IBetterAuth>('betterAuth');
441
497
  // Normalize: true → {}, false/undefined → null
@@ -463,13 +519,27 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
463
519
  const jwtConfig = configService.get<{ refresh?: { secret?: string }; secret?: string }>('jwt');
464
520
  const fallbackSecrets = [jwtConfig?.secret, jwtConfig?.refresh?.secret];
465
521
 
522
+ // Create email verification callbacks that delegate to the NestJS service
523
+ const { onEmailVerified, sendVerificationEmail } = this.createEmailVerificationCallbacks();
524
+
466
525
  // Note: Secret validation is now handled in createBetterAuthInstance
467
526
  // with fallback to jwt.secret, jwt.refresh.secret, or auto-generation
468
- this.authInstance = createBetterAuthInstance({ config, db, fallbackSecrets });
469
-
470
- // IMPORTANT: Store the config AFTER createBetterAuthInstance mutates it
471
- // This ensures CoreBetterAuthService has access to the resolved secret (with fallback applied)
472
- this.currentConfig = config;
527
+ this.authInstance = createBetterAuthInstance({
528
+ config,
529
+ db,
530
+ fallbackSecrets,
531
+ onEmailVerified,
532
+ sendVerificationEmail,
533
+ });
534
+
535
+ // Store a config copy with the resolved secret so that consumers
536
+ // (CoreBetterAuthService, CoreBetterAuthController) can sign cookies.
537
+ // The original config object may be frozen (from ConfigService), so we
538
+ // create a shallow copy with the resolved fallback secret applied.
539
+ const resolvedSecret = config.secret || fallbackSecrets?.find((s) => s && s.length >= 32);
540
+ this.currentConfig = resolvedSecret && resolvedSecret !== config.secret
541
+ ? { ...config, secret: resolvedSecret }
542
+ : config;
473
543
 
474
544
  if (this.authInstance) {
475
545
  this.logger.log('BetterAuth initialized successfully');
@@ -480,7 +550,9 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
480
550
  },
481
551
  },
482
552
  // Provide the resolved config for CoreBetterAuthService
553
+ // IMPORTANT: Must depend on BETTER_AUTH_INSTANCE to ensure currentConfig is set
483
554
  {
555
+ inject: [BETTER_AUTH_INSTANCE],
484
556
  provide: BETTER_AUTH_CONFIG,
485
557
  useFactory: () => this.currentConfig,
486
558
  },
@@ -545,33 +617,131 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
545
617
  this.customResolver = null;
546
618
  this.shouldRegisterRolesGuardGlobally = false;
547
619
  this.rolesGuardExplicitlyDisabled = false;
620
+ this.emailVerificationService = null;
548
621
  // Reset shared RolesGuard registry (shared with CoreAuthModule)
549
622
  RolesGuardRegistry.reset();
550
623
  }
551
624
 
625
+ /**
626
+ * Set the email verification service instance for Better-Auth hooks.
627
+ * Called internally during module initialization.
628
+ * @internal
629
+ */
630
+ static setEmailVerificationService(service: CoreBetterAuthEmailVerificationService): void {
631
+ this.emailVerificationService = service;
632
+ }
633
+
634
+ /**
635
+ * Get the email verification service instance.
636
+ * @internal
637
+ */
638
+ static getEmailVerificationService(): CoreBetterAuthEmailVerificationService | null {
639
+ return this.emailVerificationService;
640
+ }
641
+
642
+ /**
643
+ * Create email verification callbacks that delegate to the NestJS service.
644
+ * These callbacks are passed to Better-Auth during initialization.
645
+ * They access the service via static reference since Better-Auth hooks run outside DI context.
646
+ * @internal
647
+ */
648
+ private static createEmailVerificationCallbacks(): {
649
+ onEmailVerified: (userId: string) => Promise<void>;
650
+ sendVerificationEmail: (options: { token: string; url: string; user: { email: string; id: string; name?: null | string } }) => Promise<void>;
651
+ } {
652
+ return {
653
+ onEmailVerified: async (userId: string) => {
654
+ // This callback is called by Better-Auth when email is verified
655
+ // Sync nest-server's verified/verifiedAt fields with Better-Auth's emailVerified
656
+ try {
657
+ const db = this.mongoConnection?.db;
658
+ if (db) {
659
+ const { ObjectId } = await import('mongodb');
660
+ await db.collection('users').updateOne(
661
+ { _id: new ObjectId(userId) },
662
+ { $set: { verified: true, verifiedAt: new Date() } },
663
+ );
664
+ this.logger.debug(`Email verified for user ${userId} - synced verified/verifiedAt`);
665
+ } else {
666
+ this.logger.warn(`Cannot sync verifiedAt for user ${userId} - no database connection`);
667
+ }
668
+ } catch (error) {
669
+ this.logger.error(`Failed to sync verifiedAt for user ${userId}: ${error instanceof Error ? error.message : 'Unknown error'}`);
670
+ }
671
+ },
672
+ sendVerificationEmail: async (options) => {
673
+ // Delegate to the NestJS service
674
+ if (this.emailVerificationService) {
675
+ await this.emailVerificationService.sendVerificationEmail(options);
676
+ } else {
677
+ // Fallback: Log verification URL if service not available
678
+ this.logger.warn('Email verification service not available, logging URL for development');
679
+ this.logger.log(`[DEV] Verification URL for ${options.user.email}: ${options.url}`);
680
+ }
681
+ },
682
+ };
683
+ }
684
+
552
685
  /**
553
686
  * Creates a deferred initialization module that waits for mongoose connection
554
687
  * By injecting the Connection token, NestJS ensures Mongoose is ready first
555
688
  *
556
689
  * @param config - BetterAuth configuration
557
- * @param fallbackSecrets - Fallback secrets for backwards compatibility
558
- * @param serverUrls - Server-level URLs for Passkey auto-detection
690
+ * @param options - Optional deferred module options (fallback secrets, server URLs)
559
691
  */
560
692
  private static createDeferredModule(
561
693
  config: IBetterAuth,
562
- fallbackSecrets?: (string | undefined)[],
563
- serverUrls?: { serverAppUrl?: string; serverBaseUrl?: string; serverEnv?: string },
694
+ options?: {
695
+ fallbackSecrets?: (string | undefined)[];
696
+ serverAppUrl?: string;
697
+ serverBaseUrl?: string;
698
+ serverEnv?: string;
699
+ },
564
700
  ): DynamicModule {
565
701
  return {
566
702
  controllers: [this.getControllerClass()],
567
- exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter, BetterAuthTokenService, CoreBetterAuthChallengeService],
703
+ exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter, BetterAuthTokenService, CoreBetterAuthChallengeService, CoreBetterAuthEmailVerificationService, CoreBetterAuthSignUpValidatorService],
568
704
  module: CoreBetterAuthModule,
569
705
  providers: [
706
+ // Optional BrevoService: uses factory to avoid constructor error when brevo config is missing
707
+ {
708
+ inject: [ConfigService],
709
+ provide: CoreBetterAuthEmailVerificationService.BREVO_SERVICE_TOKEN,
710
+ useFactory: (configService: ConfigService) => {
711
+ if (configService.configFastButReadOnly?.brevo?.apiKey) {
712
+ return new BrevoService(configService);
713
+ }
714
+ return null;
715
+ },
716
+ },
717
+ // Email verification service - must be initialized early for callbacks
718
+ CoreBetterAuthEmailVerificationService,
719
+ // Sign-up validator service
720
+ CoreBetterAuthSignUpValidatorService,
570
721
  {
571
722
  // Inject Mongoose Connection to ensure NestJS waits for it to be ready
572
- inject: [getConnectionToken()],
723
+ // Also inject EmailVerificationService to set static reference before Better-Auth init
724
+ inject: [getConnectionToken(), CoreBetterAuthEmailVerificationService],
573
725
  provide: BETTER_AUTH_INSTANCE,
574
- useFactory: async (connection: Connection) => {
726
+ useFactory: async (connection: Connection, emailVerificationService: CoreBetterAuthEmailVerificationService) => {
727
+ // Set static references for callbacks BEFORE creating Better-Auth instance
728
+ this.setEmailVerificationService(emailVerificationService);
729
+ this.mongoConnection = connection;
730
+
731
+ // Create email verification callbacks that delegate to the NestJS service
732
+ const { onEmailVerified, sendVerificationEmail } = this.createEmailVerificationCallbacks();
733
+
734
+ // Build shared instance options
735
+ const sharedInstanceOptions = {
736
+ config,
737
+ fallbackSecrets: options?.fallbackSecrets,
738
+ onEmailVerified,
739
+ sendVerificationEmail,
740
+ serverAppUrl: options?.serverAppUrl,
741
+ serverBaseUrl: options?.serverBaseUrl,
742
+ serverEnv: options?.serverEnv,
743
+ };
744
+
575
745
  // Connection is now guaranteed to be established
576
746
  const db = connection.db;
577
747
  if (!db) {
@@ -582,27 +752,22 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
582
752
  throw new Error('MongoDB database not available');
583
753
  }
584
754
  this.authInstance = createBetterAuthInstance({
585
- config,
755
+ ...sharedInstanceOptions,
586
756
  db: globalDb,
587
- fallbackSecrets,
588
- serverAppUrl: serverUrls?.serverAppUrl,
589
- serverBaseUrl: serverUrls?.serverBaseUrl,
590
- serverEnv: serverUrls?.serverEnv,
591
757
  });
592
758
  } else {
593
759
  this.authInstance = createBetterAuthInstance({
594
- config,
760
+ ...sharedInstanceOptions,
595
761
  db,
596
- fallbackSecrets,
597
- serverAppUrl: serverUrls?.serverAppUrl,
598
- serverBaseUrl: serverUrls?.serverBaseUrl,
599
- serverEnv: serverUrls?.serverEnv,
600
762
  });
601
763
  }
602
764
 
603
- // IMPORTANT: Store the config AFTER createBetterAuthInstance mutates it
604
- // This ensures CoreBetterAuthService has access to the resolved secret (with fallback applied)
605
- this.currentConfig = config;
765
+ // Store a config copy with the resolved secret (same as first forRoot variant)
766
+ const fallbacks = options?.fallbackSecrets;
767
+ const resolvedSecret2 = config.secret || fallbacks?.find((s) => s && s.length >= 32);
768
+ this.currentConfig = resolvedSecret2 && resolvedSecret2 !== config.secret
769
+ ? { ...config, secret: resolvedSecret2 }
770
+ : config;
606
771
 
607
772
  if (this.authInstance && !this.initLogged) {
608
773
  this.initLogged = true;
@@ -614,7 +779,9 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
614
779
  },
615
780
  },
616
781
  // Provide the resolved config for CoreBetterAuthService
782
+ // IMPORTANT: Must depend on BETTER_AUTH_INSTANCE to ensure currentConfig is set
617
783
  {
784
+ inject: [BETTER_AUTH_INSTANCE],
618
785
  provide: BETTER_AUTH_CONFIG,
619
786
  useFactory: () => this.currentConfig,
620
787
  },
@@ -716,6 +883,16 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
716
883
  features.push(`Rate Limiting (${config.rateLimit.max || 10}/${config.rateLimit.windowSeconds || 60}s)`);
717
884
  }
718
885
 
886
+ // Email verification is enabled by default unless explicitly disabled
887
+ if (!isExplicitlyDisabled(config.emailVerification)) {
888
+ features.push('Email Verification');
889
+ }
890
+
891
+ // Sign-up checks are enabled by default unless explicitly disabled
892
+ if (!isExplicitlyDisabled(config.signUpChecks)) {
893
+ features.push('Sign-Up Checks');
894
+ }
895
+
719
896
  if (features.length > 0) {
720
897
  this.logger.log(`Enabled features: ${features.join(', ')}`);
721
898
  }