@lenne.tech/nest-server 11.6.0 → 11.6.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 (87) hide show
  1. package/dist/config.env.js +141 -0
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/decorators/graphql-populate.decorator.d.ts +2 -2
  4. package/dist/core/common/decorators/restricted.decorator.d.ts +1 -0
  5. package/dist/core/common/decorators/restricted.decorator.js +1 -1
  6. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  7. package/dist/core/common/helpers/input.helper.d.ts +1 -0
  8. package/dist/core/common/helpers/input.helper.js +1 -1
  9. package/dist/core/common/helpers/input.helper.js.map +1 -1
  10. package/dist/core/common/interceptors/check-security.interceptor.js +4 -3
  11. package/dist/core/common/interceptors/check-security.interceptor.js.map +1 -1
  12. package/dist/core/common/interfaces/server-options.interface.d.ts +50 -0
  13. package/dist/core/modules/auth/auth-guard-strategy.enum.d.ts +1 -0
  14. package/dist/core/modules/auth/auth-guard-strategy.enum.js +1 -0
  15. package/dist/core/modules/auth/auth-guard-strategy.enum.js.map +1 -1
  16. package/dist/core/modules/auth/guards/auth.guard.js +11 -5
  17. package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
  18. package/dist/core/modules/auth/tokens.decorator.d.ts +1 -1
  19. package/dist/core/modules/better-auth/better-auth-auth.model.d.ts +9 -0
  20. package/dist/core/modules/better-auth/better-auth-auth.model.js +63 -0
  21. package/dist/core/modules/better-auth/better-auth-auth.model.js.map +1 -0
  22. package/dist/core/modules/better-auth/better-auth-models.d.ts +44 -0
  23. package/dist/core/modules/better-auth/better-auth-models.js +185 -0
  24. package/dist/core/modules/better-auth/better-auth-models.js.map +1 -0
  25. package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.d.ts +12 -0
  26. package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.js +70 -0
  27. package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.js.map +1 -0
  28. package/dist/core/modules/better-auth/better-auth-rate-limiter.service.d.ts +32 -0
  29. package/dist/core/modules/better-auth/better-auth-rate-limiter.service.js +173 -0
  30. package/dist/core/modules/better-auth/better-auth-rate-limiter.service.js.map +1 -0
  31. package/dist/core/modules/better-auth/better-auth-user.mapper.d.ts +43 -0
  32. package/dist/core/modules/better-auth/better-auth-user.mapper.js +159 -0
  33. package/dist/core/modules/better-auth/better-auth-user.mapper.js.map +1 -0
  34. package/dist/core/modules/better-auth/better-auth.config.d.ts +9 -0
  35. package/dist/core/modules/better-auth/better-auth.config.js +251 -0
  36. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -0
  37. package/dist/core/modules/better-auth/better-auth.middleware.d.ts +20 -0
  38. package/dist/core/modules/better-auth/better-auth.middleware.js +79 -0
  39. package/dist/core/modules/better-auth/better-auth.middleware.js.map +1 -0
  40. package/dist/core/modules/better-auth/better-auth.module.d.ts +30 -0
  41. package/dist/core/modules/better-auth/better-auth.module.js +265 -0
  42. package/dist/core/modules/better-auth/better-auth.module.js.map +1 -0
  43. package/dist/core/modules/better-auth/better-auth.resolver.d.ts +49 -0
  44. package/dist/core/modules/better-auth/better-auth.resolver.js +539 -0
  45. package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -0
  46. package/dist/core/modules/better-auth/better-auth.service.d.ts +38 -0
  47. package/dist/core/modules/better-auth/better-auth.service.js +151 -0
  48. package/dist/core/modules/better-auth/better-auth.service.js.map +1 -0
  49. package/dist/core/modules/better-auth/better-auth.types.d.ts +38 -0
  50. package/dist/core/modules/better-auth/better-auth.types.js +15 -0
  51. package/dist/core/modules/better-auth/better-auth.types.js.map +1 -0
  52. package/dist/core/modules/better-auth/index.d.ts +11 -0
  53. package/dist/core/modules/better-auth/index.js +28 -0
  54. package/dist/core/modules/better-auth/index.js.map +1 -0
  55. package/dist/core/modules/user/core-user.model.d.ts +2 -0
  56. package/dist/core/modules/user/core-user.model.js +21 -0
  57. package/dist/core/modules/user/core-user.model.js.map +1 -1
  58. package/dist/core.module.js +7 -0
  59. package/dist/core.module.js.map +1 -1
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.js +1 -0
  62. package/dist/index.js.map +1 -1
  63. package/dist/tsconfig.build.tsbuildinfo +1 -1
  64. package/package.json +9 -1
  65. package/src/config.env.ts +148 -1
  66. package/src/core/common/decorators/restricted.decorator.ts +2 -2
  67. package/src/core/common/helpers/input.helper.ts +2 -2
  68. package/src/core/common/interceptors/check-security.interceptor.ts +6 -5
  69. package/src/core/common/interfaces/server-options.interface.ts +344 -20
  70. package/src/core/modules/auth/auth-guard-strategy.enum.ts +1 -0
  71. package/src/core/modules/auth/guards/auth.guard.ts +20 -6
  72. package/src/core/modules/better-auth/README.md +1096 -0
  73. package/src/core/modules/better-auth/better-auth-auth.model.ts +69 -0
  74. package/src/core/modules/better-auth/better-auth-models.ts +143 -0
  75. package/src/core/modules/better-auth/better-auth-rate-limit.middleware.ts +113 -0
  76. package/src/core/modules/better-auth/better-auth-rate-limiter.service.ts +326 -0
  77. package/src/core/modules/better-auth/better-auth-user.mapper.ts +269 -0
  78. package/src/core/modules/better-auth/better-auth.config.ts +483 -0
  79. package/src/core/modules/better-auth/better-auth.middleware.ts +111 -0
  80. package/src/core/modules/better-auth/better-auth.module.ts +433 -0
  81. package/src/core/modules/better-auth/better-auth.resolver.ts +678 -0
  82. package/src/core/modules/better-auth/better-auth.service.ts +323 -0
  83. package/src/core/modules/better-auth/better-auth.types.ts +75 -0
  84. package/src/core/modules/better-auth/index.ts +25 -0
  85. package/src/core/modules/user/core-user.model.ts +29 -0
  86. package/src/core.module.ts +12 -0
  87. package/src/index.ts +6 -0
@@ -0,0 +1,433 @@
1
+ import {
2
+ DynamicModule,
3
+ Global,
4
+ Logger,
5
+ MiddlewareConsumer,
6
+ Module,
7
+ NestModule,
8
+ OnModuleInit,
9
+ Optional,
10
+ } from '@nestjs/common';
11
+ import { getConnectionToken } from '@nestjs/mongoose';
12
+ import { AuthModule, AuthService } from '@thallesp/nestjs-better-auth';
13
+ import mongoose, { Connection } from 'mongoose';
14
+
15
+ import { IBetterAuth } from '../../common/interfaces/server-options.interface';
16
+ import { ConfigService } from '../../common/services/config.service';
17
+ import { BetterAuthRateLimitMiddleware } from './better-auth-rate-limit.middleware';
18
+ import { BetterAuthRateLimiter } from './better-auth-rate-limiter.service';
19
+ import { BetterAuthUserMapper } from './better-auth-user.mapper';
20
+ import { BetterAuthInstance, createBetterAuthInstance } from './better-auth.config';
21
+ import { BetterAuthMiddleware } from './better-auth.middleware';
22
+ import { BetterAuthResolver } from './better-auth.resolver';
23
+ import { BetterAuthService } from './better-auth.service';
24
+
25
+ /**
26
+ * Token for injecting the better-auth instance
27
+ */
28
+ export const BETTER_AUTH_INSTANCE = 'BETTER_AUTH_INSTANCE';
29
+
30
+ /**
31
+ * Options for BetterAuthModule.forRoot()
32
+ */
33
+ export interface BetterAuthModuleOptions {
34
+ /**
35
+ * Better-auth configuration
36
+ */
37
+ config: IBetterAuth;
38
+
39
+ /**
40
+ * Fallback secrets to try if no betterAuth.secret is configured.
41
+ * The array is iterated and the first valid secret (≥32 chars) is used.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * fallbackSecrets: [config.jwt?.secret, config.jwt?.refresh?.secret]
46
+ * ```
47
+ */
48
+ fallbackSecrets?: (string | undefined)[];
49
+ }
50
+
51
+ /**
52
+ * BetterAuthModule provides integration with the better-auth authentication framework.
53
+ *
54
+ * This module:
55
+ * - Creates and configures a better-auth instance based on server configuration
56
+ * - Integrates with @thallesp/nestjs-better-auth for NestJS support
57
+ * - Supports JWT, 2FA, Passkey, and Social Login based on configuration
58
+ * - Enabled by default (zero-config) - set `enabled: false` to disable explicitly
59
+ * - Uses the global mongoose connection for MongoDB access
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // In your AppModule - import after CoreModule so mongoose is connected
64
+ * @Module({
65
+ * imports: [
66
+ * CoreModule.forRoot(...),
67
+ * BetterAuthModule.forRoot({ config: environment.betterAuth }),
68
+ * ],
69
+ * })
70
+ * export class AppModule {}
71
+ * ```
72
+ */
73
+ @Global()
74
+ @Module({})
75
+ export class BetterAuthModule implements NestModule, OnModuleInit {
76
+ private static logger = new Logger(BetterAuthModule.name);
77
+ private static authInstance: BetterAuthInstance | null = null;
78
+ private static initialized = false;
79
+ private static betterAuthEnabled = false;
80
+ private static currentConfig: IBetterAuth | null = null;
81
+
82
+ constructor(
83
+ @Optional() private readonly betterAuthService?: BetterAuthService,
84
+ @Optional() private readonly rateLimiter?: BetterAuthRateLimiter,
85
+ ) {}
86
+
87
+ onModuleInit() {
88
+ if (BetterAuthModule.authInstance && !BetterAuthModule.initialized) {
89
+ BetterAuthModule.initialized = true;
90
+ BetterAuthModule.logger.log('BetterAuthModule ready');
91
+ }
92
+
93
+ // Configure rate limiter with stored config
94
+ if (this.rateLimiter && BetterAuthModule.currentConfig?.rateLimit) {
95
+ this.rateLimiter.configure(BetterAuthModule.currentConfig.rateLimit);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Configure middleware for Better-Auth session handling and rate limiting
101
+ * The session middleware runs on all routes and maps Better-Auth sessions to users
102
+ * The rate limit middleware runs only on Better-Auth endpoints
103
+ */
104
+ configure(consumer: MiddlewareConsumer) {
105
+ // Only apply middleware if Better-Auth is enabled
106
+ if (BetterAuthModule.betterAuthEnabled && this.betterAuthService?.isEnabled()) {
107
+ const basePath = BetterAuthModule.currentConfig?.basePath || '/iam';
108
+
109
+ // Apply rate limiting to Better-Auth endpoints only
110
+ if (BetterAuthModule.currentConfig?.rateLimit?.enabled) {
111
+ consumer.apply(BetterAuthRateLimitMiddleware).forRoutes(`${basePath}/*`);
112
+ BetterAuthModule.logger.debug(`Rate limiting enabled for ${basePath}/* endpoints`);
113
+ }
114
+
115
+ // Apply session middleware to all routes
116
+ consumer.apply(BetterAuthMiddleware).forRoutes('*');
117
+ BetterAuthModule.logger.debug('BetterAuthMiddleware registered for all routes');
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Waits for mongoose connection to be ready using polling
123
+ * This is more reliable than event-based waiting in test environments
124
+ * @throws Error if connection times out or fails
125
+ */
126
+ private static async waitForMongoConnection(): Promise<void> {
127
+ const maxAttempts = 60; // 60 attempts * 500ms = 30 seconds max
128
+ const pollInterval = 500;
129
+
130
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
131
+ // Check if already connected
132
+ if (mongoose.connection.readyState === 1 && mongoose.connection.db) {
133
+ return;
134
+ }
135
+
136
+ // Check for error state
137
+ if (mongoose.connection.readyState === 0) {
138
+ // Disconnected - wait for reconnection
139
+ this.logger.debug(`MongoDB not connected (attempt ${attempt + 1}/${maxAttempts})`);
140
+ }
141
+
142
+ // Wait before next check
143
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
144
+ }
145
+
146
+ throw new Error('MongoDB connection timeout - ensure MongoDB is running and accessible');
147
+ }
148
+
149
+ /**
150
+ * Creates a dynamic module for BetterAuth (synchronous)
151
+ * Note: This requires mongoose connection to be already established.
152
+ * For async initialization, use forRootAsync.
153
+ *
154
+ * @param options - Configuration options
155
+ * @returns Dynamic module configuration
156
+ */
157
+ static forRoot(options: BetterAuthModuleOptions): DynamicModule {
158
+ const { config, fallbackSecrets } = options;
159
+
160
+ // Store config for middleware configuration
161
+ this.currentConfig = config;
162
+
163
+ // If better-auth is explicitly disabled, return minimal module
164
+ // Note: We don't provide middleware classes when disabled because they depend on BetterAuthService
165
+ // and won't be used anyway (middleware is only applied when enabled)
166
+ // BetterAuth is enabled by default unless explicitly set to false
167
+ if (config?.enabled === false) {
168
+ this.logger.debug('BetterAuth is explicitly disabled - skipping initialization');
169
+ this.betterAuthEnabled = false;
170
+ return {
171
+ exports: [BETTER_AUTH_INSTANCE, BetterAuthService, BetterAuthUserMapper, BetterAuthRateLimiter],
172
+ module: BetterAuthModule,
173
+ providers: [
174
+ {
175
+ provide: BETTER_AUTH_INSTANCE,
176
+ useValue: null,
177
+ },
178
+ // Note: ConfigService is provided globally by CoreModule
179
+ // Tests need to provide their own ConfigService
180
+ BetterAuthService,
181
+ BetterAuthUserMapper,
182
+ BetterAuthRateLimiter,
183
+ ],
184
+ };
185
+ }
186
+
187
+ // Enable middleware registration
188
+ this.betterAuthEnabled = true;
189
+
190
+ // Note: Secret validation is now handled in createBetterAuthInstance
191
+ // with fallback to jwt.secret, jwt.refresh.secret, or auto-generation
192
+
193
+ // Always use deferred initialization to ensure MongoDB is ready
194
+ // This prevents timing issues during application startup
195
+ return this.createDeferredModule(config, fallbackSecrets);
196
+ }
197
+
198
+ /**
199
+ * Creates an async dynamic module for BetterAuth
200
+ * This is the preferred method as it properly waits for mongoose connection.
201
+ *
202
+ * @returns Dynamic module configuration
203
+ */
204
+ static forRootAsync(): DynamicModule {
205
+ return {
206
+ exports: [BETTER_AUTH_INSTANCE, BetterAuthService, BetterAuthUserMapper, BetterAuthRateLimiter],
207
+ imports: [],
208
+ module: BetterAuthModule,
209
+ providers: [
210
+ {
211
+ inject: [ConfigService],
212
+ provide: BETTER_AUTH_INSTANCE,
213
+ useFactory: async (configService: ConfigService) => {
214
+ const config = configService.get<IBetterAuth>('betterAuth');
215
+
216
+ // Store config for middleware configuration
217
+ this.currentConfig = config || null;
218
+
219
+ // BetterAuth is enabled by default unless explicitly set to false
220
+ if (config?.enabled === false) {
221
+ this.logger.debug('BetterAuth is explicitly disabled');
222
+ this.betterAuthEnabled = false;
223
+ return null;
224
+ }
225
+
226
+ // Enable middleware registration
227
+ this.betterAuthEnabled = true;
228
+
229
+ await this.waitForMongoConnection();
230
+
231
+ const db = mongoose.connection.db;
232
+ if (!db) {
233
+ throw new Error('MongoDB database not available');
234
+ }
235
+
236
+ // Get JWT secrets from config for backwards compatibility fallback
237
+ const jwtConfig = configService.get<{ refresh?: { secret?: string }; secret?: string }>('jwt');
238
+ const fallbackSecrets = [jwtConfig?.secret, jwtConfig?.refresh?.secret];
239
+
240
+ // Note: Secret validation is now handled in createBetterAuthInstance
241
+ // with fallback to jwt.secret, jwt.refresh.secret, or auto-generation
242
+ this.authInstance = createBetterAuthInstance({ config, db, fallbackSecrets });
243
+
244
+ if (this.authInstance) {
245
+ this.logger.log('BetterAuth initialized successfully');
246
+ this.logEnabledFeatures(config);
247
+ }
248
+
249
+ return this.authInstance;
250
+ },
251
+ },
252
+ // Note: ConfigService is provided globally by CoreModule
253
+ BetterAuthService,
254
+ BetterAuthUserMapper,
255
+ BetterAuthMiddleware,
256
+ BetterAuthRateLimiter,
257
+ BetterAuthRateLimitMiddleware,
258
+ BetterAuthResolver,
259
+ ],
260
+ };
261
+ }
262
+
263
+ /**
264
+ * Gets the current better-auth instance
265
+ * Useful for accessing the auth API directly
266
+ */
267
+ static getInstance(): BetterAuthInstance | null {
268
+ return this.authInstance;
269
+ }
270
+
271
+ /**
272
+ * Resets the static state of BetterAuthModule
273
+ * This is primarily useful for testing to ensure clean state between tests
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * afterEach(() => {
278
+ * BetterAuthModule.reset();
279
+ * });
280
+ * ```
281
+ */
282
+ static reset(): void {
283
+ this.authInstance = null;
284
+ this.initialized = false;
285
+ this.betterAuthEnabled = false;
286
+ this.currentConfig = null;
287
+ this.logger.debug('BetterAuthModule state reset');
288
+ }
289
+
290
+ /**
291
+ * Creates the actual module with better-auth
292
+ */
293
+ private static createModule(config: IBetterAuth, db: any, fallbackSecrets?: (string | undefined)[]): DynamicModule {
294
+ // Create the better-auth instance
295
+ this.authInstance = createBetterAuthInstance({ config, db, fallbackSecrets });
296
+
297
+ if (!this.authInstance) {
298
+ throw new Error('Failed to create better-auth instance');
299
+ }
300
+
301
+ this.logger.log('BetterAuth initialized successfully');
302
+ this.logEnabledFeatures(config);
303
+
304
+ // Use @thallesp/nestjs-better-auth's AuthModule
305
+ const authModule = AuthModule.forRoot({
306
+ auth: this.authInstance,
307
+ disableControllers: false, // Enable REST endpoints
308
+ disableGlobalAuthGuard: true, // We use our own RolesGuard
309
+ });
310
+
311
+ return {
312
+ exports: [
313
+ BETTER_AUTH_INSTANCE,
314
+ AuthModule,
315
+ AuthService,
316
+ BetterAuthService,
317
+ BetterAuthUserMapper,
318
+ BetterAuthRateLimiter,
319
+ ],
320
+ imports: [authModule],
321
+ module: BetterAuthModule,
322
+ providers: [
323
+ {
324
+ provide: BETTER_AUTH_INSTANCE,
325
+ useValue: this.authInstance,
326
+ },
327
+ // Note: ConfigService is provided globally by CoreModule
328
+ BetterAuthService,
329
+ BetterAuthUserMapper,
330
+ BetterAuthMiddleware,
331
+ BetterAuthRateLimiter,
332
+ BetterAuthRateLimitMiddleware,
333
+ BetterAuthResolver,
334
+ ],
335
+ };
336
+ }
337
+
338
+ /**
339
+ * Creates a deferred initialization module that waits for mongoose connection
340
+ * By injecting the Connection token, NestJS ensures Mongoose is ready first
341
+ */
342
+ private static createDeferredModule(config: IBetterAuth, fallbackSecrets?: (string | undefined)[]): DynamicModule {
343
+ return {
344
+ exports: [BETTER_AUTH_INSTANCE, BetterAuthService, BetterAuthUserMapper, BetterAuthRateLimiter],
345
+ module: BetterAuthModule,
346
+ providers: [
347
+ {
348
+ // Inject Mongoose Connection to ensure NestJS waits for it to be ready
349
+ inject: [getConnectionToken()],
350
+ provide: BETTER_AUTH_INSTANCE,
351
+ useFactory: async (connection: Connection) => {
352
+ // Connection is now guaranteed to be established
353
+ const db = connection.db;
354
+ if (!db) {
355
+ // Fallback to global mongoose if connection.db is not yet available
356
+ await this.waitForMongoConnection();
357
+ const globalDb = mongoose.connection.db;
358
+ if (!globalDb) {
359
+ throw new Error('MongoDB database not available');
360
+ }
361
+ this.authInstance = createBetterAuthInstance({ config, db: globalDb, fallbackSecrets });
362
+ } else {
363
+ this.authInstance = createBetterAuthInstance({ config, db, fallbackSecrets });
364
+ }
365
+
366
+ if (this.authInstance) {
367
+ this.logger.log('BetterAuth initialized');
368
+ this.logEnabledFeatures(config);
369
+ }
370
+
371
+ return this.authInstance;
372
+ },
373
+ },
374
+ // Note: ConfigService is provided globally by CoreModule
375
+ BetterAuthService,
376
+ BetterAuthUserMapper,
377
+ BetterAuthMiddleware,
378
+ BetterAuthRateLimiter,
379
+ BetterAuthRateLimitMiddleware,
380
+ BetterAuthResolver,
381
+ ],
382
+ };
383
+ }
384
+
385
+ /**
386
+ * Logs which features are enabled.
387
+ * Features are enabled by default when their config block is present,
388
+ * unless explicitly disabled with enabled: false.
389
+ */
390
+ private static logEnabledFeatures(config: IBetterAuth): void {
391
+ const features: string[] = [];
392
+
393
+ // Plugins are enabled by default when config block is present
394
+ if (config.jwt && config.jwt.enabled !== false) {
395
+ features.push('JWT');
396
+ }
397
+ if (config.twoFactor && config.twoFactor.enabled !== false) {
398
+ features.push('2FA/TOTP');
399
+ }
400
+ if (config.passkey && config.passkey.enabled !== false) {
401
+ features.push('Passkey/WebAuthn');
402
+ }
403
+ if (config.legacyPassword && config.legacyPassword.enabled !== false) {
404
+ features.push('Legacy Password Handling');
405
+ }
406
+
407
+ // Dynamically collect enabled social providers
408
+ // Providers are enabled by default if they have credentials configured
409
+ // Only disabled when explicitly set to enabled: false
410
+ const socialProviders: string[] = [];
411
+ if (config.socialProviders) {
412
+ for (const [name, provider] of Object.entries(config.socialProviders)) {
413
+ if (provider?.clientId && provider?.clientSecret && provider?.enabled !== false) {
414
+ // Capitalize first letter for display
415
+ socialProviders.push(name.charAt(0).toUpperCase() + name.slice(1));
416
+ }
417
+ }
418
+ }
419
+
420
+ if (socialProviders.length > 0) {
421
+ features.push(`Social Login (${socialProviders.join(', ')})`);
422
+ }
423
+
424
+ // Rate limiting still requires explicit enabled: true
425
+ if (config.rateLimit?.enabled) {
426
+ features.push(`Rate Limiting (${config.rateLimit.max || 10}/${config.rateLimit.windowSeconds || 60}s)`);
427
+ }
428
+
429
+ if (features.length > 0) {
430
+ this.logger.log(`Enabled features: ${features.join(', ')}`);
431
+ }
432
+ }
433
+ }