@lenne.tech/nest-server 11.7.0 → 11.7.2

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 (120) hide show
  1. package/dist/config.env.js +17 -1
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/interfaces/server-options.interface.d.ts +35 -15
  4. package/dist/core/modules/auth/core-auth.controller.d.ts +1 -0
  5. package/dist/core/modules/auth/core-auth.controller.js +29 -3
  6. package/dist/core/modules/auth/core-auth.controller.js.map +1 -1
  7. package/dist/core/modules/auth/core-auth.module.js +14 -1
  8. package/dist/core/modules/auth/core-auth.module.js.map +1 -1
  9. package/dist/core/modules/auth/core-auth.resolver.d.ts +1 -0
  10. package/dist/core/modules/auth/core-auth.resolver.js +21 -3
  11. package/dist/core/modules/auth/core-auth.resolver.js.map +1 -1
  12. package/dist/core/modules/auth/exceptions/legacy-auth-disabled.exception.d.ts +4 -0
  13. package/dist/core/modules/auth/exceptions/legacy-auth-disabled.exception.js +17 -0
  14. package/dist/core/modules/auth/exceptions/legacy-auth-disabled.exception.js.map +1 -0
  15. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.d.ts +9 -0
  16. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js +74 -0
  17. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js.map +1 -0
  18. package/dist/core/modules/auth/interfaces/auth-provider.interface.d.ts +7 -0
  19. package/dist/core/modules/auth/interfaces/auth-provider.interface.js +5 -0
  20. package/dist/core/modules/auth/interfaces/auth-provider.interface.js.map +1 -0
  21. package/dist/core/modules/auth/interfaces/core-auth-user.interface.d.ts +1 -0
  22. package/dist/core/modules/auth/services/core-auth.service.d.ts +10 -1
  23. package/dist/core/modules/auth/services/core-auth.service.js +141 -9
  24. package/dist/core/modules/auth/services/core-auth.service.js.map +1 -1
  25. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.d.ts +31 -0
  26. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js +153 -0
  27. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js.map +1 -0
  28. package/dist/core/modules/better-auth/better-auth-migration-status.model.d.ts +10 -0
  29. package/dist/core/modules/better-auth/better-auth-migration-status.model.js +57 -0
  30. package/dist/core/modules/better-auth/better-auth-migration-status.model.js.map +1 -0
  31. package/dist/core/modules/better-auth/better-auth-rate-limiter.service.js +1 -1
  32. package/dist/core/modules/better-auth/better-auth-rate-limiter.service.js.map +1 -1
  33. package/dist/core/modules/better-auth/better-auth-user.mapper.d.ts +33 -0
  34. package/dist/core/modules/better-auth/better-auth-user.mapper.js +395 -0
  35. package/dist/core/modules/better-auth/better-auth-user.mapper.js.map +1 -1
  36. package/dist/core/modules/better-auth/better-auth.config.js +29 -10
  37. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
  38. package/dist/core/modules/better-auth/better-auth.middleware.d.ts +1 -0
  39. package/dist/core/modules/better-auth/better-auth.middleware.js +55 -1
  40. package/dist/core/modules/better-auth/better-auth.middleware.js.map +1 -1
  41. package/dist/core/modules/better-auth/better-auth.module.d.ts +1 -1
  42. package/dist/core/modules/better-auth/better-auth.module.js +46 -18
  43. package/dist/core/modules/better-auth/better-auth.module.js.map +1 -1
  44. package/dist/core/modules/better-auth/better-auth.resolver.js +0 -11
  45. package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -1
  46. package/dist/core/modules/better-auth/better-auth.service.d.ts +22 -1
  47. package/dist/core/modules/better-auth/better-auth.service.js +209 -8
  48. package/dist/core/modules/better-auth/better-auth.service.js.map +1 -1
  49. package/dist/core/modules/better-auth/better-auth.types.d.ts +2 -0
  50. package/dist/core/modules/better-auth/better-auth.types.js.map +1 -1
  51. package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +1 -0
  52. package/dist/core/modules/better-auth/core-better-auth.controller.js +15 -2
  53. package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
  54. package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +7 -0
  55. package/dist/core/modules/better-auth/core-better-auth.resolver.js +72 -12
  56. package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
  57. package/dist/core/modules/better-auth/index.d.ts +1 -0
  58. package/dist/core/modules/better-auth/index.js +1 -0
  59. package/dist/core/modules/better-auth/index.js.map +1 -1
  60. package/dist/core/modules/user/core-user.service.d.ts +7 -1
  61. package/dist/core/modules/user/core-user.service.js +57 -3
  62. package/dist/core/modules/user/core-user.service.js.map +1 -1
  63. package/dist/core/modules/user/interfaces/core-user-service-options.interface.d.ts +4 -0
  64. package/dist/core/modules/user/interfaces/core-user-service-options.interface.js +3 -0
  65. package/dist/core/modules/user/interfaces/core-user-service-options.interface.js.map +1 -0
  66. package/dist/core.module.d.ts +3 -0
  67. package/dist/core.module.js +136 -55
  68. package/dist/core.module.js.map +1 -1
  69. package/dist/index.d.ts +5 -0
  70. package/dist/index.js +5 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/server/modules/auth/auth.resolver.js +2 -0
  73. package/dist/server/modules/auth/auth.resolver.js.map +1 -1
  74. package/dist/server/modules/better-auth/better-auth.module.d.ts +1 -1
  75. package/dist/server/modules/better-auth/better-auth.module.js +2 -1
  76. package/dist/server/modules/better-auth/better-auth.module.js.map +1 -1
  77. package/dist/server/modules/better-auth/better-auth.resolver.d.ts +5 -0
  78. package/dist/server/modules/better-auth/better-auth.resolver.js +27 -11
  79. package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -1
  80. package/dist/server/modules/user/user.controller.js +0 -8
  81. package/dist/server/modules/user/user.controller.js.map +1 -1
  82. package/dist/server/modules/user/user.service.d.ts +3 -1
  83. package/dist/server/modules/user/user.service.js +7 -3
  84. package/dist/server/modules/user/user.service.js.map +1 -1
  85. package/dist/tsconfig.build.tsbuildinfo +1 -1
  86. package/package.json +1 -1
  87. package/src/config.env.ts +32 -2
  88. package/src/core/common/interfaces/server-options.interface.ts +304 -58
  89. package/src/core/modules/auth/core-auth.controller.ts +94 -6
  90. package/src/core/modules/auth/core-auth.module.ts +15 -1
  91. package/src/core/modules/auth/core-auth.resolver.ts +71 -3
  92. package/src/core/modules/auth/exceptions/legacy-auth-disabled.exception.ts +35 -0
  93. package/src/core/modules/auth/guards/legacy-auth-rate-limit.guard.ts +109 -0
  94. package/src/core/modules/auth/interfaces/auth-provider.interface.ts +86 -0
  95. package/src/core/modules/auth/interfaces/core-auth-user.interface.ts +6 -0
  96. package/src/core/modules/auth/services/core-auth.service.ts +245 -6
  97. package/src/core/modules/auth/services/legacy-auth-rate-limiter.service.ts +283 -0
  98. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +255 -0
  99. package/src/core/modules/better-auth/README.md +565 -208
  100. package/src/core/modules/better-auth/better-auth-migration-status.model.ts +73 -0
  101. package/src/core/modules/better-auth/better-auth-rate-limiter.service.ts +1 -1
  102. package/src/core/modules/better-auth/better-auth-user.mapper.ts +737 -0
  103. package/src/core/modules/better-auth/better-auth.config.ts +45 -15
  104. package/src/core/modules/better-auth/better-auth.middleware.ts +85 -2
  105. package/src/core/modules/better-auth/better-auth.module.ts +83 -27
  106. package/src/core/modules/better-auth/better-auth.resolver.ts +0 -11
  107. package/src/core/modules/better-auth/better-auth.service.ts +367 -12
  108. package/src/core/modules/better-auth/better-auth.types.ts +16 -0
  109. package/src/core/modules/better-auth/core-better-auth.controller.ts +44 -3
  110. package/src/core/modules/better-auth/core-better-auth.resolver.ts +136 -16
  111. package/src/core/modules/better-auth/index.ts +1 -0
  112. package/src/core/modules/user/core-user.service.ts +131 -4
  113. package/src/core/modules/user/interfaces/core-user-service-options.interface.ts +15 -0
  114. package/src/core.module.ts +264 -76
  115. package/src/index.ts +5 -0
  116. package/src/server/modules/auth/auth.resolver.ts +8 -0
  117. package/src/server/modules/better-auth/better-auth.module.ts +9 -3
  118. package/src/server/modules/better-auth/better-auth.resolver.ts +18 -11
  119. package/src/server/modules/user/user.controller.ts +1 -9
  120. package/src/server/modules/user/user.service.ts +4 -2
@@ -168,42 +168,48 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Bett
168
168
  * Builds the plugins array based on configuration.
169
169
  * Merges built-in plugins (jwt, twoFactor, passkey) with custom plugins from config.
170
170
  *
171
- * Plugins are enabled by default when their configuration block is present.
172
- * Set `enabled: false` to explicitly disable a configured plugin.
171
+ * Plugins accept both boolean and object configuration:
172
+ * - `true` or `{}`: Enable with defaults
173
+ * - `{ option: value }`: Enable with custom settings
174
+ * - `false` or `{ enabled: false }`: Disable
175
+ * - `undefined`: Disabled (default)
173
176
  */
174
177
  function buildPlugins(config: IBetterAuth): BetterAuthPlugin[] {
175
178
  const plugins: BetterAuthPlugin[] = [];
176
179
 
177
180
  // JWT Plugin for API client compatibility
178
- // Enabled by default when jwt config is present, unless explicitly disabled
179
- if (config.jwt && config.jwt.enabled !== false) {
181
+ // JWT is enabled by default unless explicitly disabled (jwt: false or jwt: { enabled: false })
182
+ const jwtExplicitlyDisabled =
183
+ config.jwt === false || (typeof config.jwt === 'object' && config.jwt?.enabled === false);
184
+ if (!jwtExplicitlyDisabled) {
185
+ const jwtConfig = typeof config.jwt === 'object' ? config.jwt : {};
180
186
  plugins.push(
181
187
  jwt({
182
188
  jwt: {
183
- expirationTime: config.jwt.expiresIn || '15m',
189
+ expirationTime: jwtConfig.expiresIn || '15m',
184
190
  },
185
191
  }),
186
192
  );
187
193
  }
188
194
 
189
195
  // Two-Factor Authentication Plugin
190
- // Enabled by default when twoFactor config is present, unless explicitly disabled
191
- if (config.twoFactor && config.twoFactor.enabled !== false) {
196
+ const twoFactorConfig = getPluginConfig(config.twoFactor);
197
+ if (twoFactorConfig) {
192
198
  plugins.push(
193
199
  twoFactor({
194
- issuer: config.twoFactor.appName || 'Nest Server',
200
+ issuer: twoFactorConfig.appName || 'Nest Server',
195
201
  }),
196
202
  );
197
203
  }
198
204
 
199
205
  // Passkey/WebAuthn Plugin
200
- // Enabled by default when passkey config is present, unless explicitly disabled
201
- if (config.passkey && config.passkey.enabled !== false) {
206
+ const passkeyConfig = getPluginConfig(config.passkey);
207
+ if (passkeyConfig) {
202
208
  plugins.push(
203
209
  passkey({
204
- origin: config.passkey.origin || 'http://localhost:3000',
205
- rpID: config.passkey.rpId || 'localhost',
206
- rpName: config.passkey.rpName || 'Nest Server',
210
+ origin: passkeyConfig.origin || 'http://localhost:3000',
211
+ rpID: passkeyConfig.rpId || 'localhost',
212
+ rpName: passkeyConfig.rpName || 'Nest Server',
207
213
  }),
208
214
  );
209
215
  }
@@ -322,6 +328,29 @@ function getAutoGeneratedSecret(): string {
322
328
  return cachedAutoGeneratedSecret;
323
329
  }
324
330
 
331
+ /**
332
+ * Gets plugin configuration as object, handling boolean shorthand.
333
+ * Returns undefined if disabled, or the config object if enabled.
334
+ */
335
+ function getPluginConfig<T extends { enabled?: boolean }>(config: boolean | T | undefined): T | undefined {
336
+ if (!isPluginEnabled(config)) return undefined;
337
+ if (typeof config === 'boolean') return {} as T;
338
+ return config;
339
+ }
340
+
341
+ /**
342
+ * Checks if a plugin configuration is enabled.
343
+ * Supports both boolean and object configuration:
344
+ * - `true` or `{}` or `{ enabled: true }` → enabled
345
+ * - `false` or `{ enabled: false }` → disabled
346
+ * - `undefined` → disabled
347
+ */
348
+ function isPluginEnabled<T extends { enabled?: boolean }>(config: boolean | T | undefined): boolean {
349
+ if (config === undefined) return false;
350
+ if (typeof config === 'boolean') return config;
351
+ return config.enabled !== false;
352
+ }
353
+
325
354
  /**
326
355
  * Checks if a secret has valid minimum length (32 characters)
327
356
  */
@@ -416,8 +445,9 @@ function validateConfig(config: IBetterAuth, fallbackSecrets?: (string | undefin
416
445
  }
417
446
 
418
447
  // Validate passkey origin
419
- if (config.passkey?.enabled && config.passkey.origin && !isValidUrl(config.passkey.origin)) {
420
- errors.push(`Invalid passkey origin format: ${config.passkey.origin}`);
448
+ const passkeyConfig = typeof config.passkey === 'object' ? config.passkey : null;
449
+ if (passkeyConfig?.enabled && passkeyConfig.origin && !isValidUrl(passkeyConfig.origin)) {
450
+ errors.push(`Invalid passkey origin format: ${passkeyConfig.origin}`);
421
451
  }
422
452
 
423
453
  // Validate social providers dynamically
@@ -49,7 +49,7 @@ export class BetterAuthMiddleware implements NestMiddleware {
49
49
  }
50
50
 
51
51
  try {
52
- // Get session from Better-Auth
52
+ // Strategy 1: Try session-based authentication (cookies)
53
53
  const session = await this.getSession(req);
54
54
 
55
55
  if (session?.user) {
@@ -63,7 +63,69 @@ export class BetterAuthMiddleware implements NestMiddleware {
63
63
  if (mappedUser) {
64
64
  // Attach the mapped user to the request
65
65
  // This makes it compatible with @CurrentUser() and RolesGuard
66
- req.user = mappedUser;
66
+ // Set _authenticatedViaBetterAuth flag so AuthGuard skips Passport JWT verification
67
+ req.user = { ...mappedUser, _authenticatedViaBetterAuth: true };
68
+ return next();
69
+ }
70
+ }
71
+
72
+ // Strategy 2: Try Authorization header (Bearer token)
73
+ // The token could be a BetterAuth JWT, a Legacy JWT, or a session token
74
+ if (req.headers.authorization) {
75
+ const authHeader = req.headers.authorization;
76
+ const token = authHeader.startsWith('Bearer ') ? authHeader.substring(7) : authHeader;
77
+ const tokenParts = token.split('.').length;
78
+
79
+ // Check if token looks like a JWT (has 3 parts)
80
+ if (tokenParts === 3) {
81
+ // Decode JWT payload to check if it's a Legacy JWT or BetterAuth JWT
82
+ // Legacy JWTs have 'id' claim, BetterAuth JWTs have 'sub' claim
83
+ const isLegacyJwt = this.isLegacyJwt(token);
84
+ if (isLegacyJwt) {
85
+ // Legacy JWT - skip BetterAuth processing, let Passport handle it
86
+ return next();
87
+ }
88
+
89
+ // Try BetterAuth JWT verification
90
+ if (this.betterAuthService.isJwtEnabled()) {
91
+ const jwtPayload = await this.betterAuthService.verifyJwtFromRequest(req);
92
+
93
+ if (jwtPayload?.sub) {
94
+ // JWT payload contains user info - create a session-like user object
95
+ const sessionUser: BetterAuthSessionUser = {
96
+ email: jwtPayload.email || '',
97
+ emailVerified: jwtPayload.emailVerified,
98
+ id: jwtPayload.sub,
99
+ name: jwtPayload.name,
100
+ };
101
+
102
+ req.betterAuthUser = sessionUser;
103
+
104
+ // Map the JWT user to our User model with hasRole()
105
+ const mappedUser = await this.userMapper.mapSessionUser(sessionUser);
106
+
107
+ if (mappedUser) {
108
+ req.user = { ...mappedUser, _authenticatedViaBetterAuth: true };
109
+ return next();
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ // If user is still not set, try session token verification as fallback
116
+ // This handles both non-JWT tokens and JWTs that couldn't be verified
117
+ if (!req.user) {
118
+ const sessionResult = await this.betterAuthService.getSessionByToken(token);
119
+ if (sessionResult?.user) {
120
+ req.betterAuthSession = { session: sessionResult.session, user: sessionResult.user };
121
+ req.betterAuthUser = sessionResult.user;
122
+
123
+ const mappedUser = await this.userMapper.mapSessionUser(sessionResult.user);
124
+ if (mappedUser) {
125
+ req.user = { ...mappedUser, _authenticatedViaBetterAuth: true };
126
+ return next();
127
+ }
128
+ }
67
129
  }
68
130
  }
69
131
  } catch (error) {
@@ -75,6 +137,27 @@ export class BetterAuthMiddleware implements NestMiddleware {
75
137
  next();
76
138
  }
77
139
 
140
+ /**
141
+ * Checks if a JWT token is a Legacy Auth JWT (has 'id' claim but no 'sub' claim)
142
+ * Legacy JWTs use 'id' for user ID, BetterAuth JWTs use 'sub'
143
+ */
144
+ private isLegacyJwt(token: string): boolean {
145
+ try {
146
+ const parts = token.split('.');
147
+ if (parts.length !== 3) return false;
148
+
149
+ // Decode the payload (second part)
150
+ const payloadStr = Buffer.from(parts[1], 'base64url').toString('utf-8');
151
+ const payload = JSON.parse(payloadStr);
152
+
153
+ // Legacy JWT has 'id' claim (and typically 'deviceId', 'tokenId')
154
+ // BetterAuth JWT has 'sub' claim
155
+ return payload.id !== undefined && payload.sub === undefined;
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+
78
161
  /**
79
162
  * Gets the session from Better-Auth
80
163
  */
@@ -20,7 +20,7 @@ import { BetterAuthUserMapper } from './better-auth-user.mapper';
20
20
  import { BetterAuthInstance, createBetterAuthInstance } from './better-auth.config';
21
21
  import { BetterAuthMiddleware } from './better-auth.middleware';
22
22
  import { BetterAuthResolver } from './better-auth.resolver';
23
- import { BetterAuthService } from './better-auth.service';
23
+ import { BETTER_AUTH_CONFIG, BetterAuthService } from './better-auth.service';
24
24
  import { CoreBetterAuthController } from './core-better-auth.controller';
25
25
  import { CoreBetterAuthResolver } from './core-better-auth.resolver';
26
26
 
@@ -34,9 +34,13 @@ export const BETTER_AUTH_INSTANCE = 'BETTER_AUTH_INSTANCE';
34
34
  */
35
35
  export interface BetterAuthModuleOptions {
36
36
  /**
37
- * Better-auth configuration
37
+ * Better-auth configuration.
38
+ * Accepts:
39
+ * - `true`: Enable with all defaults (including JWT)
40
+ * - `false`: Disable BetterAuth
41
+ * - `{ ... }`: Enable with custom configuration
38
42
  */
39
- config: IBetterAuth;
43
+ config: boolean | IBetterAuth;
40
44
 
41
45
  /**
42
46
  * Custom controller class to use instead of the default CoreBetterAuthController.
@@ -100,6 +104,20 @@ export interface BetterAuthModuleOptions {
100
104
  resolver?: Type<CoreBetterAuthResolver>;
101
105
  }
102
106
 
107
+ /**
108
+ * Normalizes betterAuth config from boolean | IBetterAuth to IBetterAuth | null
109
+ * - `true` → `{}` (enabled with defaults)
110
+ * - `false` → `null` (disabled)
111
+ * - `undefined` → `null` (disabled for backward compatibility)
112
+ * - `{ ... }` → `{ ... }` (pass through)
113
+ */
114
+ function normalizeBetterAuthConfig(config: boolean | IBetterAuth | undefined): IBetterAuth | null {
115
+ if (config === undefined || config === null) return null;
116
+ if (config === true) return {};
117
+ if (config === false) return null;
118
+ return config;
119
+ }
120
+
103
121
  /**
104
122
  * BetterAuthModule provides integration with the better-auth authentication framework.
105
123
  *
@@ -178,12 +196,12 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
178
196
  // Apply rate limiting to Better-Auth endpoints only
179
197
  if (BetterAuthModule.currentConfig?.rateLimit?.enabled) {
180
198
  consumer.apply(BetterAuthRateLimitMiddleware).forRoutes(`${basePath}/*path`);
181
- BetterAuthModule.logger.log(`Rate limiting enabled for ${basePath}/*path endpoints`);
199
+ BetterAuthModule.logger.debug(`Rate limiting middleware registered for ${basePath}/*path endpoints`);
182
200
  }
183
201
 
184
202
  // Apply session middleware to all routes
185
203
  consumer.apply(BetterAuthMiddleware).forRoutes('(.*)'); // New path-to-regexp syntax for wildcard
186
- BetterAuthModule.logger.log('BetterAuthMiddleware registered for all routes');
204
+ BetterAuthModule.logger.debug('BetterAuthMiddleware registered for all routes');
187
205
  }
188
206
  }
189
207
 
@@ -224,7 +242,10 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
224
242
  * @returns Dynamic module configuration
225
243
  */
226
244
  static forRoot(options: BetterAuthModuleOptions): DynamicModule {
227
- const { config, controller, fallbackSecrets, resolver } = options;
245
+ const { config: rawConfig, controller, fallbackSecrets, resolver } = options;
246
+
247
+ // Normalize config: true → {}, false/undefined → null
248
+ const config = normalizeBetterAuthConfig(rawConfig);
228
249
 
229
250
  // Store config for middleware configuration
230
251
  this.currentConfig = config;
@@ -233,12 +254,11 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
233
254
  // Store custom resolver if provided
234
255
  this.customResolver = resolver || null;
235
256
 
236
- // If better-auth is explicitly disabled, return minimal module
257
+ // If better-auth is disabled (config is null or enabled: false), return minimal module
237
258
  // Note: We don't provide middleware classes when disabled because they depend on BetterAuthService
238
259
  // and won't be used anyway (middleware is only applied when enabled)
239
- // BetterAuth is enabled by default unless explicitly set to false
240
- if (config?.enabled === false) {
241
- this.logger.debug('BetterAuth is explicitly disabled - skipping initialization');
260
+ if (config === null || config?.enabled === false) {
261
+ this.logger.debug('BetterAuth is disabled - skipping initialization');
242
262
  this.betterAuthEnabled = false;
243
263
  return {
244
264
  exports: [BETTER_AUTH_INSTANCE, BetterAuthService, BetterAuthUserMapper, BetterAuthRateLimiter],
@@ -285,15 +305,16 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
285
305
  inject: [ConfigService],
286
306
  provide: BETTER_AUTH_INSTANCE,
287
307
  useFactory: async (configService: ConfigService) => {
288
- const config = configService.get<IBetterAuth>('betterAuth');
289
-
290
- // Store config for middleware configuration
291
- this.currentConfig = config || null;
292
-
293
- // BetterAuth is enabled by default unless explicitly set to false
294
- if (config?.enabled === false) {
295
- this.logger.debug('BetterAuth is explicitly disabled');
308
+ // Get raw config (can be boolean or object)
309
+ const rawConfig = configService.get<boolean | IBetterAuth>('betterAuth');
310
+ // Normalize: true {}, false/undefined → null
311
+ const config = normalizeBetterAuthConfig(rawConfig);
312
+
313
+ // BetterAuth is disabled if config is null or enabled: false
314
+ if (config === null || config?.enabled === false) {
315
+ this.logger.debug('BetterAuth is disabled');
296
316
  this.betterAuthEnabled = false;
317
+ this.currentConfig = config;
297
318
  return null;
298
319
  }
299
320
 
@@ -315,6 +336,10 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
315
336
  // with fallback to jwt.secret, jwt.refresh.secret, or auto-generation
316
337
  this.authInstance = createBetterAuthInstance({ config, db, fallbackSecrets });
317
338
 
339
+ // IMPORTANT: Store the config AFTER createBetterAuthInstance mutates it
340
+ // This ensures BetterAuthService has access to the resolved secret (with fallback applied)
341
+ this.currentConfig = config;
342
+
318
343
  if (this.authInstance) {
319
344
  this.logger.log('BetterAuth initialized successfully');
320
345
  this.logEnabledFeatures(config);
@@ -323,13 +348,22 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
323
348
  return this.authInstance;
324
349
  },
325
350
  },
351
+ // Provide the resolved config for BetterAuthService
352
+ {
353
+ provide: BETTER_AUTH_CONFIG,
354
+ useFactory: () => this.currentConfig,
355
+ },
326
356
  // BetterAuthService needs to be a factory that explicitly depends on BETTER_AUTH_INSTANCE
327
357
  // to ensure proper initialization order
328
358
  {
329
- inject: [BETTER_AUTH_INSTANCE, ConfigService],
359
+ inject: [BETTER_AUTH_INSTANCE, BETTER_AUTH_CONFIG, getConnectionToken()],
330
360
  provide: BetterAuthService,
331
- useFactory: (authInstance: BetterAuthInstance | null, configService: ConfigService) => {
332
- return new BetterAuthService(authInstance, configService);
361
+ useFactory: (
362
+ authInstance: BetterAuthInstance | null,
363
+ resolvedConfig: IBetterAuth | null,
364
+ connection: Connection,
365
+ ) => {
366
+ return new BetterAuthService(authInstance, connection, resolvedConfig);
333
367
  },
334
368
  },
335
369
  BetterAuthUserMapper,
@@ -399,6 +433,10 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
399
433
  this.authInstance = createBetterAuthInstance({ config, db, fallbackSecrets });
400
434
  }
401
435
 
436
+ // IMPORTANT: Store the config AFTER createBetterAuthInstance mutates it
437
+ // This ensures BetterAuthService has access to the resolved secret (with fallback applied)
438
+ this.currentConfig = config;
439
+
402
440
  if (this.authInstance && !this.initLogged) {
403
441
  this.initLogged = true;
404
442
  this.logger.log('BetterAuth initialized');
@@ -408,13 +446,22 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
408
446
  return this.authInstance;
409
447
  },
410
448
  },
449
+ // Provide the resolved config for BetterAuthService
450
+ {
451
+ provide: BETTER_AUTH_CONFIG,
452
+ useFactory: () => this.currentConfig,
453
+ },
411
454
  // BetterAuthService needs to be a factory that explicitly depends on BETTER_AUTH_INSTANCE
412
455
  // to ensure proper initialization order
413
456
  {
414
- inject: [BETTER_AUTH_INSTANCE, ConfigService],
457
+ inject: [BETTER_AUTH_INSTANCE, BETTER_AUTH_CONFIG, getConnectionToken()],
415
458
  provide: BetterAuthService,
416
- useFactory: (authInstance: BetterAuthInstance | null, configService: ConfigService) => {
417
- return new BetterAuthService(authInstance, configService);
459
+ useFactory: (
460
+ authInstance: BetterAuthInstance | null,
461
+ resolvedConfig: IBetterAuth | null,
462
+ connection: Connection,
463
+ ) => {
464
+ return new BetterAuthService(authInstance, connection, resolvedConfig);
418
465
  },
419
466
  },
420
467
  BetterAuthUserMapper,
@@ -434,14 +481,23 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
434
481
  private static logEnabledFeatures(config: IBetterAuth): void {
435
482
  const features: string[] = [];
436
483
 
484
+ // Helper to check if a plugin is enabled
485
+ // Supports: true, { enabled: true }, { ... } (enabled by default when config block present)
486
+ // Disabled only when: false or { enabled: false }
487
+ const isPluginEnabled = <T extends { enabled?: boolean }>(value: boolean | T | undefined): boolean => {
488
+ if (value === undefined || value === false) return false;
489
+ if (value === true) return true;
490
+ return value.enabled !== false;
491
+ };
492
+
437
493
  // Plugins are enabled by default when config block is present
438
- if (config.jwt && config.jwt.enabled !== false) {
494
+ if (isPluginEnabled(config.jwt)) {
439
495
  features.push('JWT');
440
496
  }
441
- if (config.twoFactor && config.twoFactor.enabled !== false) {
497
+ if (isPluginEnabled(config.twoFactor)) {
442
498
  features.push('2FA/TOTP');
443
499
  }
444
- if (config.passkey && config.passkey.enabled !== false) {
500
+ if (isPluginEnabled(config.passkey)) {
445
501
  features.push('Passkey/WebAuthn');
446
502
  }
447
503
 
@@ -1,11 +1,8 @@
1
- import { UseGuards } from '@nestjs/common';
2
1
  import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
3
2
  import { Request, Response } from 'express';
4
3
 
5
4
  import { Roles } from '../../common/decorators/roles.decorator';
6
5
  import { RoleEnum } from '../../common/enums/role.enum';
7
- import { AuthGuardStrategy } from '../auth/auth-guard-strategy.enum';
8
- import { AuthGuard } from '../auth/guards/auth.guard';
9
6
  import { BetterAuthAuthModel } from './better-auth-auth.model';
10
7
  import {
11
8
  BetterAuth2FASetupModel,
@@ -73,7 +70,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
73
70
  nullable: true,
74
71
  })
75
72
  @Roles(RoleEnum.S_USER)
76
- @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
77
73
  override async betterAuthSession(@Context() ctx: { req: Request }): Promise<BetterAuthSessionModel | null> {
78
74
  return super.betterAuthSession(ctx);
79
75
  }
@@ -95,7 +91,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
95
91
  nullable: true,
96
92
  })
97
93
  @Roles(RoleEnum.S_USER)
98
- @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
99
94
  override async betterAuthListPasskeys(@Context() ctx: { req: Request }): Promise<BetterAuthPasskeyModel[] | null> {
100
95
  return super.betterAuthListPasskeys(ctx);
101
96
  }
@@ -130,7 +125,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
130
125
 
131
126
  @Mutation(() => Boolean, { description: 'Sign out via Better-Auth' })
132
127
  @Roles(RoleEnum.S_USER)
133
- @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
134
128
  override async betterAuthSignOut(@Context() ctx: { req: Request }): Promise<boolean> {
135
129
  return super.betterAuthSignOut(ctx);
136
130
  }
@@ -154,7 +148,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
154
148
  description: 'Enable 2FA for the current user',
155
149
  })
156
150
  @Roles(RoleEnum.S_USER)
157
- @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
158
151
  override async betterAuthEnable2FA(
159
152
  @Args('password') password: string,
160
153
  @Context() ctx: { req: Request },
@@ -166,7 +159,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
166
159
  description: 'Disable 2FA for the current user',
167
160
  })
168
161
  @Roles(RoleEnum.S_USER)
169
- @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
170
162
  override async betterAuthDisable2FA(
171
163
  @Args('password') password: string,
172
164
  @Context() ctx: { req: Request },
@@ -179,7 +171,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
179
171
  nullable: true,
180
172
  })
181
173
  @Roles(RoleEnum.S_USER)
182
- @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
183
174
  override async betterAuthGenerateBackupCodes(@Context() ctx: { req: Request }): Promise<null | string[]> {
184
175
  return super.betterAuthGenerateBackupCodes(ctx);
185
176
  }
@@ -192,7 +183,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
192
183
  description: 'Get passkey registration challenge for WebAuthn',
193
184
  })
194
185
  @Roles(RoleEnum.S_USER)
195
- @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
196
186
  override async betterAuthGetPasskeyChallenge(
197
187
  @Context() ctx: { req: Request },
198
188
  ): Promise<BetterAuthPasskeyChallengeModel> {
@@ -203,7 +193,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
203
193
  description: 'Delete a passkey by ID',
204
194
  })
205
195
  @Roles(RoleEnum.S_USER)
206
- @UseGuards(AuthGuard(AuthGuardStrategy.JWT))
207
196
  override async betterAuthDeletePasskey(
208
197
  @Args('passkeyId') passkeyId: string,
209
198
  @Context() ctx: { req: Request },