@lenne.tech/nest-server 11.10.3 → 11.10.4

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/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.4",
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({
@@ -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
  };
@@ -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';