@lenne.tech/nest-server 11.10.3 → 11.10.5

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 (32) hide show
  1. package/dist/core/modules/auth/core-auth.module.js +8 -4
  2. package/dist/core/modules/auth/core-auth.module.js.map +1 -1
  3. package/dist/core/modules/auth/guards/roles-guard-registry.d.ts +9 -0
  4. package/dist/core/modules/auth/guards/roles-guard-registry.js +30 -0
  5. package/dist/core/modules/auth/guards/roles-guard-registry.js.map +1 -0
  6. package/dist/core/modules/better-auth/core-better-auth.controller.js +14 -13
  7. package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
  8. package/dist/core/modules/better-auth/core-better-auth.module.d.ts +1 -0
  9. package/dist/core/modules/better-auth/core-better-auth.module.js +21 -8
  10. package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
  11. package/dist/core/modules/better-auth/core-better-auth.resolver.js +23 -22
  12. package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
  13. package/dist/core/modules/error-code/error-codes.d.ts +99 -0
  14. package/dist/core/modules/error-code/error-codes.js +88 -0
  15. package/dist/core/modules/error-code/error-codes.js.map +1 -1
  16. package/dist/core.module.js +7 -1
  17. package/dist/core.module.js.map +1 -1
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/server/modules/error-code/error-codes.d.ts +11 -0
  22. package/dist/tsconfig.build.tsbuildinfo +1 -1
  23. package/package.json +1 -1
  24. package/src/core/modules/auth/core-auth.module.ts +11 -5
  25. package/src/core/modules/auth/guards/roles-guard-registry.ts +57 -0
  26. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +3 -0
  27. package/src/core/modules/better-auth/core-better-auth.controller.ts +14 -13
  28. package/src/core/modules/better-auth/core-better-auth.module.ts +38 -13
  29. package/src/core/modules/better-auth/core-better-auth.resolver.ts +23 -24
  30. package/src/core/modules/error-code/error-codes.ts +100 -0
  31. package/src/core.module.ts +16 -3
  32. package/src/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.10.3",
3
+ "version": "11.10.5",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -6,6 +6,7 @@ import { PubSub } from 'graphql-subscriptions';
6
6
 
7
7
  import { AuthGuardStrategy } from './auth-guard-strategy.enum';
8
8
  import { LegacyAuthRateLimitGuard } from './guards/legacy-auth-rate-limit.guard';
9
+ import { RolesGuardRegistry } from './guards/roles-guard-registry';
9
10
  import { RolesGuard } from './guards/roles.guard';
10
11
  import { CoreAuthUserService } from './services/core-auth-user.service';
11
12
  import { CoreAuthService } from './services/core-auth.service';
@@ -44,12 +45,17 @@ export class CoreAuthModule {
44
45
  }
45
46
 
46
47
  // Process providers
48
+ // Only register RolesGuard if not already registered (prevents duplicate with CoreBetterAuthModule)
49
+ const rolesGuardProvider = RolesGuardRegistry.isRegistered()
50
+ ? []
51
+ : (() => {
52
+ RolesGuardRegistry.markRegistered('CoreAuthModule');
53
+ return [{ provide: APP_GUARD, useClass: RolesGuard }];
54
+ })();
55
+
47
56
  let providers = [
48
- // [Global] The GraphQLAuthGard integrates the user into context
49
- {
50
- provide: APP_GUARD,
51
- useClass: RolesGuard,
52
- },
57
+ // [Global] The GraphQLAuthGuard integrates the user into context
58
+ ...rolesGuardProvider,
53
59
  {
54
60
  provide: CoreAuthUserService,
55
61
  useClass: UserService,
@@ -0,0 +1,57 @@
1
+ import { Logger } from '@nestjs/common';
2
+
3
+ /**
4
+ * Global registry to track RolesGuard registration across modules.
5
+ *
6
+ * This prevents duplicate registration when both CoreAuthModule (Legacy)
7
+ * and CoreBetterAuthModule (IAM) are used in the same application.
8
+ *
9
+ * Multiple APP_GUARD registrations of the same guard would work but cause
10
+ * unnecessary double validation on every request.
11
+ */
12
+ export class RolesGuardRegistry {
13
+ private static readonly logger = new Logger('RolesGuardRegistry');
14
+ private static registered = false;
15
+ private static registeredBy: null | string = null;
16
+
17
+ /**
18
+ * Check if RolesGuard has already been registered as a global guard.
19
+ */
20
+ static isRegistered(): boolean {
21
+ return this.registered;
22
+ }
23
+
24
+ /**
25
+ * Get which module registered the RolesGuard.
26
+ */
27
+ static getRegisteredBy(): null | string {
28
+ return this.registeredBy;
29
+ }
30
+
31
+ /**
32
+ * Mark RolesGuard as registered by a specific module.
33
+ * Call this when adding RolesGuard as APP_GUARD.
34
+ *
35
+ * @param moduleName - Name of the module registering RolesGuard (for debugging)
36
+ */
37
+ static markRegistered(moduleName: string = 'Unknown'): void {
38
+ if (this.registered) {
39
+ this.logger.debug(
40
+ `RolesGuard already registered by ${this.registeredBy}, skipping registration from ${moduleName}`,
41
+ );
42
+ return;
43
+ }
44
+ this.registered = true;
45
+ this.registeredBy = moduleName;
46
+ this.logger.debug(`RolesGuard registered globally by ${moduleName}`);
47
+ }
48
+
49
+ /**
50
+ * Reset the registry state.
51
+ * Used in tests to ensure clean state between test runs.
52
+ */
53
+ static reset(): void {
54
+ this.registered = false;
55
+ this.registeredBy = null;
56
+ }
57
+ }
@@ -101,6 +101,7 @@ The `CoreBetterAuthUserMapper` enables bidirectional password synchronization:
101
101
  BetterAuthModule.forRoot({
102
102
  config: envConfig.betterAuth,
103
103
  fallbackSecrets: [envConfig.jwt?.secret],
104
+ // registerRolesGuardGlobally defaults to true - @Roles() decorators work automatically!
104
105
  }),
105
106
  // ... other modules
106
107
  ],
@@ -108,6 +109,8 @@ The `CoreBetterAuthUserMapper` enables bidirectional password synchronization:
108
109
  export class ServerModule {}
109
110
  ```
110
111
 
112
+ **Note:** Since 11.10.3, `registerRolesGuardGlobally` defaults to `true`. You don't need to set it explicitly.
113
+
111
114
  #### For Existing Projects (Migration):
112
115
  ```typescript
113
116
  @Module({
@@ -20,6 +20,7 @@ import { Roles } from '../../common/decorators/roles.decorator';
20
20
  import { RoleEnum } from '../../common/enums/role.enum';
21
21
  import { maskEmail, maskToken } from '../../common/helpers/logging.helper';
22
22
  import { ConfigService } from '../../common/services/config.service';
23
+ import { ErrorCode } from '../error-code/error-codes';
23
24
  import { BetterAuthSignInResponse, hasSession, hasUser, requires2FA } from './better-auth.types';
24
25
  import { BetterAuthSessionUser, CoreBetterAuthUserMapper } from './core-better-auth-user.mapper';
25
26
  import { sendWebResponse, toWebRequest } from './core-better-auth-web.helper';
@@ -227,7 +228,7 @@ export class CoreBetterAuthController {
227
228
 
228
229
  const api = this.betterAuthService.getApi();
229
230
  if (!api) {
230
- throw new BadRequestException('Better-Auth API not available');
231
+ throw new BadRequestException(ErrorCode.BETTERAUTH_API_NOT_AVAILABLE);
231
232
  }
232
233
 
233
234
  // Step 1: Try legacy user migration BEFORE Better Auth handles the request
@@ -255,7 +256,7 @@ export class CoreBetterAuthController {
255
256
  })) as BetterAuthSignInResponse | null;
256
257
 
257
258
  if (!response) {
258
- throw new UnauthorizedException('Invalid credentials');
259
+ throw new UnauthorizedException(ErrorCode.INVALID_CREDENTIALS);
259
260
  }
260
261
 
261
262
  // Check for 2FA requirement
@@ -268,7 +269,7 @@ export class CoreBetterAuthController {
268
269
  // We need to modify the request body to use the normalized password
269
270
  const authInstance = this.betterAuthService.getInstance();
270
271
  if (!authInstance) {
271
- throw new InternalServerErrorException('Better-Auth not initialized');
272
+ throw new InternalServerErrorException(ErrorCode.BETTERAUTH_NOT_INITIALIZED);
272
273
  }
273
274
 
274
275
  // Create a modified request body with normalized password
@@ -311,7 +312,7 @@ export class CoreBetterAuthController {
311
312
  // Check if response indicates an error
312
313
  const responseAny = response as any;
313
314
  if (responseAny?.error || responseAny?.code === 'CREDENTIAL_ACCOUNT_NOT_FOUND') {
314
- throw new UnauthorizedException('Invalid credentials');
315
+ throw new UnauthorizedException(ErrorCode.INVALID_CREDENTIALS);
315
316
  }
316
317
 
317
318
  if (hasUser(response)) {
@@ -334,7 +335,7 @@ export class CoreBetterAuthController {
334
335
  return this.processCookies(res, result);
335
336
  }
336
337
 
337
- throw new UnauthorizedException('Invalid credentials');
338
+ throw new UnauthorizedException(ErrorCode.INVALID_CREDENTIALS);
338
339
  } catch (error) {
339
340
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
340
341
  this.logger.debug(`Sign-in error: ${errorMessage}`);
@@ -343,7 +344,7 @@ export class CoreBetterAuthController {
343
344
  throw error;
344
345
  }
345
346
 
346
- throw new UnauthorizedException('Invalid credentials');
347
+ throw new UnauthorizedException(ErrorCode.INVALID_CREDENTIALS);
347
348
  }
348
349
  }
349
350
 
@@ -380,7 +381,7 @@ export class CoreBetterAuthController {
380
381
 
381
382
  const api = this.betterAuthService.getApi();
382
383
  if (!api) {
383
- throw new BadRequestException('Better-Auth API not available');
384
+ throw new BadRequestException(ErrorCode.BETTERAUTH_API_NOT_AVAILABLE);
384
385
  }
385
386
 
386
387
  // Normalize password to SHA256 format for consistency with Legacy Auth
@@ -396,7 +397,7 @@ export class CoreBetterAuthController {
396
397
  });
397
398
 
398
399
  if (!response) {
399
- throw new BadRequestException('Sign-up failed');
400
+ throw new BadRequestException(ErrorCode.SIGNUP_FAILED);
400
401
  }
401
402
 
402
403
  if (hasUser(response)) {
@@ -419,14 +420,14 @@ export class CoreBetterAuthController {
419
420
  return this.processCookies(res, result);
420
421
  }
421
422
 
422
- throw new BadRequestException('Sign-up failed');
423
+ throw new BadRequestException(ErrorCode.SIGNUP_FAILED);
423
424
  } catch (error) {
424
425
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
425
426
  this.logger.debug(`Sign-up error: ${errorMessage}`);
426
427
  if (errorMessage.includes('already exists')) {
427
- throw new BadRequestException('User with this email already exists');
428
+ throw new BadRequestException(ErrorCode.EMAIL_ALREADY_EXISTS);
428
429
  }
429
- throw new BadRequestException('Sign-up failed');
430
+ throw new BadRequestException(ErrorCode.SIGNUP_FAILED);
430
431
  }
431
432
  }
432
433
 
@@ -557,7 +558,7 @@ export class CoreBetterAuthController {
557
558
  */
558
559
  protected ensureEnabled(): void {
559
560
  if (!this.betterAuthService.isEnabled()) {
560
- throw new BadRequestException('Better-Auth is not enabled');
561
+ throw new BadRequestException(ErrorCode.BETTERAUTH_DISABLED);
561
562
  }
562
563
  }
563
564
 
@@ -735,7 +736,7 @@ export class CoreBetterAuthController {
735
736
 
736
737
  const authInstance = this.betterAuthService.getInstance();
737
738
  if (!authInstance) {
738
- throw new InternalServerErrorException('Better-Auth not initialized');
739
+ throw new InternalServerErrorException(ErrorCode.BETTERAUTH_NOT_INITIALIZED);
739
740
  }
740
741
 
741
742
  this.logger.debug(`Forwarding to Better Auth: ${req.method} ${req.path}`);
@@ -15,6 +15,7 @@ import mongoose, { Connection } from 'mongoose';
15
15
 
16
16
  import { IBetterAuth } from '../../common/interfaces/server-options.interface';
17
17
  import { ConfigService } from '../../common/services/config.service';
18
+ import { RolesGuardRegistry } from '../auth/guards/roles-guard-registry';
18
19
  import { RolesGuard } from '../auth/guards/roles.guard';
19
20
  import { BetterAuthTokenService } from './better-auth-token.service';
20
21
  import { BetterAuthInstance, createBetterAuthInstance } from './better-auth.config';
@@ -87,13 +88,14 @@ export interface CoreBetterAuthModuleOptions {
87
88
  /**
88
89
  * Register RolesGuard as a global guard.
89
90
  *
90
- * This should be set to `true` for IAM-only setups (CoreModule.forRoot with 1 parameter)
91
- * where CoreAuthModule is not imported (which normally registers RolesGuard globally).
92
- *
93
91
  * When `true`, all `@Roles()` decorators will be enforced automatically without
94
92
  * needing explicit `@UseGuards(RolesGuard)` on each endpoint.
95
93
  *
96
- * @default false
94
+ * **Important:** This should be `false` in Legacy mode (3-parameter CoreModule.forRoot)
95
+ * because CoreAuthModule already registers RolesGuard globally. Setting it to `true`
96
+ * in Legacy mode would cause duplicate guard registration.
97
+ *
98
+ * @default true (secure by default - ensures @Roles() decorators are enforced)
97
99
  */
98
100
  registerRolesGuardGlobally?: boolean;
99
101
 
@@ -218,6 +220,8 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
218
220
  private static customController: null | Type<CoreBetterAuthController> = null;
219
221
  private static customResolver: null | Type<CoreBetterAuthResolver> = null;
220
222
  private static shouldRegisterRolesGuardGlobally = false;
223
+ // Track if registerRolesGuardGlobally was explicitly set to false (for warning)
224
+ private static rolesGuardExplicitlyDisabled = false;
221
225
 
222
226
  /**
223
227
  * Gets the controller class to use (custom or default)
@@ -248,6 +252,16 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
248
252
  if (this.rateLimiter && CoreBetterAuthModule.currentConfig?.rateLimit) {
249
253
  this.rateLimiter.configure(CoreBetterAuthModule.currentConfig.rateLimit);
250
254
  }
255
+
256
+ // Security warning: Check if RolesGuard is registered when explicitly disabled
257
+ // This warning helps developers identify potential security misconfigurations
258
+ if (CoreBetterAuthModule.rolesGuardExplicitlyDisabled && !RolesGuardRegistry.isRegistered()) {
259
+ CoreBetterAuthModule.logger.warn(
260
+ '⚠️ SECURITY WARNING: registerRolesGuardGlobally is explicitly set to false, ' +
261
+ 'but no RolesGuard is registered globally. @Roles() decorators will NOT enforce access control! ' +
262
+ 'Either set registerRolesGuardGlobally: true, or ensure CoreAuthModule (Legacy) is imported.',
263
+ );
264
+ }
251
265
  }
252
266
 
253
267
  /**
@@ -357,8 +371,12 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
357
371
  this.customController = controller || null;
358
372
  // Store custom resolver if provided
359
373
  this.customResolver = resolver || null;
360
- // Store whether to register RolesGuard globally (for IAM-only setups)
361
- this.shouldRegisterRolesGuardGlobally = registerRolesGuardGlobally ?? false;
374
+ // Store whether to register RolesGuard globally
375
+ // Default is true (secure by default) - ensures @Roles() decorators are enforced
376
+ // CoreModule.forRoot sets this to false in Legacy mode (where CoreAuthModule handles it)
377
+ this.shouldRegisterRolesGuardGlobally = registerRolesGuardGlobally ?? true;
378
+ // Track if explicitly disabled (for security warning in onModuleInit)
379
+ this.rolesGuardExplicitlyDisabled = registerRolesGuardGlobally === false;
362
380
 
363
381
  // If better-auth is disabled (config is null or enabled: false), return minimal module
364
382
  // Note: We don't provide middleware classes when disabled because they depend on CoreBetterAuthService
@@ -526,6 +544,9 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
526
544
  this.customController = null;
527
545
  this.customResolver = null;
528
546
  this.shouldRegisterRolesGuardGlobally = false;
547
+ this.rolesGuardExplicitlyDisabled = false;
548
+ // Reset shared RolesGuard registry (shared with CoreAuthModule)
549
+ RolesGuardRegistry.reset();
529
550
  }
530
551
 
531
552
  /**
@@ -627,13 +648,17 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
627
648
  this.getResolverClass(),
628
649
  // Conditionally register RolesGuard globally for IAM-only setups
629
650
  // In Legacy mode, RolesGuard is already registered globally via CoreAuthModule
630
- ...(this.shouldRegisterRolesGuardGlobally
631
- ? [
632
- {
633
- provide: APP_GUARD,
634
- useClass: RolesGuard,
635
- },
636
- ]
651
+ // Uses shared RolesGuardRegistry to prevent duplicate registration across modules
652
+ ...(this.shouldRegisterRolesGuardGlobally && !RolesGuardRegistry.isRegistered()
653
+ ? (() => {
654
+ RolesGuardRegistry.markRegistered('CoreBetterAuthModule');
655
+ return [
656
+ {
657
+ provide: APP_GUARD,
658
+ useClass: RolesGuard,
659
+ },
660
+ ];
661
+ })()
637
662
  : []),
638
663
  ],
639
664
  };
@@ -5,6 +5,7 @@ import { Request, Response } from 'express';
5
5
  import { Roles } from '../../common/decorators/roles.decorator';
6
6
  import { RoleEnum } from '../../common/enums/role.enum';
7
7
  import { maskEmail } from '../../common/helpers/logging.helper';
8
+ import { ErrorCode } from '../error-code/error-codes';
8
9
  import {
9
10
  BetterAuth2FAResponse,
10
11
  BetterAuthSignInResponse,
@@ -203,7 +204,7 @@ export class CoreBetterAuthResolver {
203
204
 
204
205
  const api = this.betterAuthService.getApi();
205
206
  if (!api) {
206
- throw new BadRequestException('Better-Auth API not available');
207
+ throw new BadRequestException(ErrorCode.BETTERAUTH_API_NOT_AVAILABLE);
207
208
  }
208
209
 
209
210
  // Try to sign in, with automatic legacy user migration
@@ -239,7 +240,7 @@ export class CoreBetterAuthResolver {
239
240
  }
240
241
 
241
242
  if (!response) {
242
- throw new UnauthorizedException('Invalid credentials');
243
+ throw new UnauthorizedException(ErrorCode.INVALID_CREDENTIALS);
243
244
  }
244
245
 
245
246
  // Check for 2FA requirement
@@ -271,7 +272,7 @@ export class CoreBetterAuthResolver {
271
272
  };
272
273
  }
273
274
 
274
- throw new UnauthorizedException('Invalid credentials');
275
+ throw new UnauthorizedException(ErrorCode.INVALID_CREDENTIALS);
275
276
  } catch (error) {
276
277
  this.logger.debug(
277
278
  `[SignIn] Sign-in failed for ${maskEmail(email)}: ${error instanceof Error ? error.message : 'Unknown error'}`,
@@ -291,7 +292,7 @@ export class CoreBetterAuthResolver {
291
292
  }
292
293
  }
293
294
 
294
- throw new UnauthorizedException('Invalid credentials');
295
+ throw new UnauthorizedException(ErrorCode.INVALID_CREDENTIALS);
295
296
  }
296
297
  }
297
298
 
@@ -308,7 +309,7 @@ export class CoreBetterAuthResolver {
308
309
  })) as BetterAuthSignInResponse | null;
309
310
 
310
311
  if (!response || !hasUser(response)) {
311
- throw new UnauthorizedException('Invalid credentials');
312
+ throw new UnauthorizedException(ErrorCode.INVALID_CREDENTIALS);
312
313
  }
313
314
 
314
315
  if (requires2FA(response)) {
@@ -348,7 +349,7 @@ export class CoreBetterAuthResolver {
348
349
 
349
350
  const api = this.betterAuthService.getApi();
350
351
  if (!api) {
351
- throw new BadRequestException('Better-Auth API not available');
352
+ throw new BadRequestException(ErrorCode.BETTERAUTH_API_NOT_AVAILABLE);
352
353
  }
353
354
 
354
355
  try {
@@ -361,7 +362,7 @@ export class CoreBetterAuthResolver {
361
362
  })) as BetterAuthSignUpResponse | null;
362
363
 
363
364
  if (!response) {
364
- throw new BadRequestException('Sign-up failed');
365
+ throw new BadRequestException(ErrorCode.SIGNUP_FAILED);
365
366
  }
366
367
 
367
368
  if (hasUser(response)) {
@@ -379,14 +380,14 @@ export class CoreBetterAuthResolver {
379
380
  };
380
381
  }
381
382
 
382
- throw new BadRequestException('Sign-up failed');
383
+ throw new BadRequestException(ErrorCode.SIGNUP_FAILED);
383
384
  } catch (error) {
384
385
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
385
386
  this.logger.debug(`Sign-up error: ${errorMessage}`);
386
387
  if (errorMessage.includes('already exists')) {
387
- throw new BadRequestException('User with this email already exists');
388
+ throw new BadRequestException(ErrorCode.EMAIL_ALREADY_EXISTS);
388
389
  }
389
- throw new BadRequestException('Sign-up failed');
390
+ throw new BadRequestException(ErrorCode.SIGNUP_FAILED);
390
391
  }
391
392
  }
392
393
 
@@ -429,12 +430,12 @@ export class CoreBetterAuthResolver {
429
430
  this.ensureEnabled();
430
431
 
431
432
  if (!this.betterAuthService.isTwoFactorEnabled()) {
432
- throw new BadRequestException('Two-factor authentication is not enabled');
433
+ throw new BadRequestException(ErrorCode.TWO_FACTOR_NOT_ENABLED_SERVER);
433
434
  }
434
435
 
435
436
  const api = this.betterAuthService.getApi();
436
437
  if (!api) {
437
- throw new BadRequestException('Better-Auth API not available');
438
+ throw new BadRequestException(ErrorCode.BETTERAUTH_API_NOT_AVAILABLE);
438
439
  }
439
440
 
440
441
  try {
@@ -450,7 +451,7 @@ export class CoreBetterAuthResolver {
450
451
  };
451
452
 
452
453
  if (!twoFactorApi?.verifyTotp) {
453
- throw new BadRequestException('2FA verification method not available');
454
+ throw new BadRequestException(ErrorCode.TWO_FACTOR_METHOD_NOT_AVAILABLE);
454
455
  }
455
456
 
456
457
  const response = await twoFactorApi.verifyTotp({
@@ -469,10 +470,10 @@ export class CoreBetterAuthResolver {
469
470
  };
470
471
  }
471
472
 
472
- throw new UnauthorizedException('Invalid 2FA code');
473
+ throw new UnauthorizedException(ErrorCode.INVALID_2FA_CODE);
473
474
  } catch (error) {
474
475
  this.logger.debug(`2FA verification error: ${error instanceof Error ? error.message : 'Unknown error'}`);
475
- throw new UnauthorizedException('Invalid 2FA code');
476
+ throw new UnauthorizedException(ErrorCode.INVALID_2FA_CODE);
476
477
  }
477
478
  }
478
479
 
@@ -546,7 +547,7 @@ export class CoreBetterAuthResolver {
546
547
  this.ensureEnabled();
547
548
 
548
549
  if (!this.betterAuthService.isTwoFactorEnabled()) {
549
- throw new BadRequestException('Two-factor authentication is not enabled on this server');
550
+ throw new BadRequestException(ErrorCode.TWO_FACTOR_NOT_ENABLED_SERVER);
550
551
  }
551
552
 
552
553
  const api = this.betterAuthService.getApi();
@@ -564,7 +565,7 @@ export class CoreBetterAuthResolver {
564
565
  };
565
566
 
566
567
  if (!twoFactorApi?.disable) {
567
- throw new BadRequestException('2FA disable method not available');
568
+ throw new BadRequestException(ErrorCode.TWO_FACTOR_METHOD_NOT_AVAILABLE);
568
569
  }
569
570
 
570
571
  const response = await twoFactorApi.disable({
@@ -591,7 +592,7 @@ export class CoreBetterAuthResolver {
591
592
  this.ensureEnabled();
592
593
 
593
594
  if (!this.betterAuthService.isTwoFactorEnabled()) {
594
- throw new BadRequestException('Two-factor authentication is not enabled on this server');
595
+ throw new BadRequestException(ErrorCode.TWO_FACTOR_NOT_ENABLED_SERVER);
595
596
  }
596
597
 
597
598
  const api = this.betterAuthService.getApi();
@@ -609,7 +610,7 @@ export class CoreBetterAuthResolver {
609
610
  };
610
611
 
611
612
  if (!twoFactorApi?.generateBackupCodes) {
612
- throw new BadRequestException('Generate backup codes method not available');
613
+ throw new BadRequestException(ErrorCode.TWO_FACTOR_METHOD_NOT_AVAILABLE);
613
614
  }
614
615
 
615
616
  const response = await twoFactorApi.generateBackupCodes({ headers });
@@ -731,7 +732,7 @@ export class CoreBetterAuthResolver {
731
732
  this.ensureEnabled();
732
733
 
733
734
  if (!this.betterAuthService.isPasskeyEnabled()) {
734
- throw new BadRequestException('Passkey authentication is not enabled on this server');
735
+ throw new BadRequestException(ErrorCode.PASSKEY_NOT_ENABLED_SERVER);
735
736
  }
736
737
 
737
738
  const api = this.betterAuthService.getApi();
@@ -749,7 +750,7 @@ export class CoreBetterAuthResolver {
749
750
  };
750
751
 
751
752
  if (!passkeyApi?.deletePasskey) {
752
- throw new BadRequestException('Delete passkey method not available');
753
+ throw new BadRequestException(ErrorCode.TWO_FACTOR_METHOD_NOT_AVAILABLE);
753
754
  }
754
755
 
755
756
  const response = await passkeyApi.deletePasskey({
@@ -773,9 +774,7 @@ export class CoreBetterAuthResolver {
773
774
  */
774
775
  protected ensureEnabled(): void {
775
776
  if (!this.betterAuthService.isEnabled()) {
776
- throw new BadRequestException(
777
- 'Better-Auth is not enabled. Check that betterAuth.enabled is not set to false in your environment.',
778
- );
777
+ throw new BadRequestException(ErrorCode.BETTERAUTH_DISABLED);
779
778
  }
780
779
  }
781
780
 
@@ -133,6 +133,106 @@ export const LtnsErrors = {
133
133
  },
134
134
  },
135
135
 
136
+ // BetterAuth specific errors (LTNS_0010-LTNS_0049)
137
+ INVALID_CREDENTIALS: {
138
+ code: 'LTNS_0010',
139
+ message: 'Invalid credentials',
140
+ translations: {
141
+ de: 'Ungültige Anmeldedaten.',
142
+ en: 'Invalid credentials.',
143
+ },
144
+ },
145
+
146
+ INVALID_2FA_CODE: {
147
+ code: 'LTNS_0011',
148
+ message: 'Invalid 2FA code',
149
+ translations: {
150
+ de: 'Der 2FA-Code ist ungültig.',
151
+ en: 'The 2FA code is invalid.',
152
+ },
153
+ },
154
+
155
+ TWO_FACTOR_NOT_ENABLED: {
156
+ code: 'LTNS_0012',
157
+ message: 'Two-factor authentication is not enabled',
158
+ translations: {
159
+ de: 'Zwei-Faktor-Authentifizierung ist nicht aktiviert.',
160
+ en: 'Two-factor authentication is not enabled.',
161
+ },
162
+ },
163
+
164
+ TWO_FACTOR_NOT_ENABLED_SERVER: {
165
+ code: 'LTNS_0013',
166
+ message: 'Two-factor authentication is not enabled on this server',
167
+ translations: {
168
+ de: 'Zwei-Faktor-Authentifizierung ist auf diesem Server nicht aktiviert.',
169
+ en: 'Two-factor authentication is not enabled on this server.',
170
+ },
171
+ },
172
+
173
+ PASSKEY_NOT_ENABLED_SERVER: {
174
+ code: 'LTNS_0014',
175
+ message: 'Passkey authentication is not enabled on this server',
176
+ translations: {
177
+ de: 'Passkey-Authentifizierung ist auf diesem Server nicht aktiviert.',
178
+ en: 'Passkey authentication is not enabled on this server.',
179
+ },
180
+ },
181
+
182
+ SIGNUP_FAILED: {
183
+ code: 'LTNS_0015',
184
+ message: 'Sign-up failed',
185
+ translations: {
186
+ de: 'Die Registrierung ist fehlgeschlagen.',
187
+ en: 'Sign-up failed.',
188
+ },
189
+ },
190
+
191
+ BETTERAUTH_NOT_INITIALIZED: {
192
+ code: 'LTNS_0016',
193
+ message: 'Better-Auth not initialized',
194
+ translations: {
195
+ de: 'Better-Auth ist nicht initialisiert.',
196
+ en: 'Better-Auth is not initialized.',
197
+ },
198
+ },
199
+
200
+ BETTERAUTH_DISABLED: {
201
+ code: 'LTNS_0017',
202
+ message: 'Better-Auth is disabled',
203
+ translations: {
204
+ de: 'Better-Auth ist deaktiviert.',
205
+ en: 'Better-Auth is disabled.',
206
+ },
207
+ },
208
+
209
+ BETTERAUTH_API_NOT_AVAILABLE: {
210
+ code: 'LTNS_0018',
211
+ message: 'Better-Auth API not available',
212
+ translations: {
213
+ de: 'Better-Auth API ist nicht verfügbar.',
214
+ en: 'Better-Auth API is not available.',
215
+ },
216
+ },
217
+
218
+ TWO_FACTOR_METHOD_NOT_AVAILABLE: {
219
+ code: 'LTNS_0019',
220
+ message: '2FA verification method not available',
221
+ translations: {
222
+ de: '2FA-Verifizierungsmethode ist nicht verfügbar.',
223
+ en: '2FA verification method is not available.',
224
+ },
225
+ },
226
+
227
+ RATE_LIMIT_EXCEEDED: {
228
+ code: 'LTNS_0020',
229
+ message: 'Too many requests',
230
+ translations: {
231
+ de: 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.',
232
+ en: 'Too many requests. Please try again later.',
233
+ },
234
+ },
235
+
136
236
  // =====================================================
137
237
  // Authorization Errors (LTNS_0100-LTNS_0199)
138
238
  // =====================================================
@@ -247,12 +247,25 @@ export class CoreModule implements NestModule {
247
247
  }
248
248
 
249
249
  // Add CoreBetterAuthModule based on mode
250
- // IAM-only mode: Always register CoreBetterAuthModule (required for subscription auth)
250
+ // IAM-only mode: BetterAuth is enabled by default (it's the only auth option)
251
251
  // Legacy mode: Only register if autoRegister is explicitly true
252
252
  // betterAuth can be: boolean | IBetterAuth | undefined
253
253
  const betterAuthConfig = config.betterAuth;
254
- const isBetterAuthEnabled =
255
- betterAuthConfig === true || (typeof betterAuthConfig === 'object' && betterAuthConfig?.enabled !== false);
254
+
255
+ // Determine if BetterAuth is explicitly disabled
256
+ // In IAM-only mode: enabled by default (undefined = true), only false or { enabled: false } disables
257
+ // In Legacy mode: disabled by default (undefined = false), must be explicitly enabled
258
+ const isExplicitlyDisabled = betterAuthConfig === false ||
259
+ (typeof betterAuthConfig === 'object' && betterAuthConfig?.enabled === false);
260
+ const isExplicitlyEnabled = betterAuthConfig === true ||
261
+ (typeof betterAuthConfig === 'object' && betterAuthConfig?.enabled !== false);
262
+
263
+ // IAM-only mode: enabled unless explicitly disabled
264
+ // Legacy mode: enabled only if explicitly enabled
265
+ const isBetterAuthEnabled = isIamOnlyMode
266
+ ? !isExplicitlyDisabled
267
+ : isExplicitlyEnabled;
268
+
256
269
  const isAutoRegister = typeof betterAuthConfig === 'object' && betterAuthConfig?.autoRegister === true;
257
270
 
258
271
  if (isBetterAuthEnabled) {
package/src/index.ts CHANGED
@@ -112,6 +112,7 @@ export * from './core/modules/auth/exceptions/invalid-token.exception';
112
112
  export * from './core/modules/auth/exceptions/legacy-auth-disabled.exception';
113
113
  export * from './core/modules/auth/guards/auth.guard';
114
114
  export * from './core/modules/auth/guards/legacy-auth-rate-limit.guard';
115
+ export * from './core/modules/auth/guards/roles-guard-registry';
115
116
  export * from './core/modules/auth/guards/roles.guard';
116
117
  export * from './core/modules/auth/inputs/core-auth-sign-in.input';
117
118
  export * from './core/modules/auth/inputs/core-auth-sign-up.input';