@lenne.tech/nest-server 11.6.1 → 11.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/config.env.js +132 -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/filter.helper.d.ts +9 -9
  8. package/dist/core/common/helpers/filter.helper.js +2 -4
  9. package/dist/core/common/helpers/filter.helper.js.map +1 -1
  10. package/dist/core/common/helpers/gridfs.helper.js +3 -3
  11. package/dist/core/common/helpers/gridfs.helper.js.map +1 -1
  12. package/dist/core/common/helpers/input.helper.d.ts +1 -0
  13. package/dist/core/common/helpers/input.helper.js +1 -1
  14. package/dist/core/common/helpers/input.helper.js.map +1 -1
  15. package/dist/core/common/interfaces/server-options.interface.d.ts +51 -0
  16. package/dist/core/common/services/crud.service.d.ts +16 -16
  17. package/dist/core/common/services/crud.service.js +1 -1
  18. package/dist/core/common/services/crud.service.js.map +1 -1
  19. package/dist/core/modules/auth/auth-guard-strategy.enum.d.ts +1 -0
  20. package/dist/core/modules/auth/auth-guard-strategy.enum.js +1 -0
  21. package/dist/core/modules/auth/auth-guard-strategy.enum.js.map +1 -1
  22. package/dist/core/modules/auth/guards/auth.guard.js +11 -5
  23. package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
  24. package/dist/core/modules/auth/tokens.decorator.d.ts +1 -1
  25. package/dist/core/modules/better-auth/better-auth-auth.model.d.ts +9 -0
  26. package/dist/core/modules/better-auth/better-auth-auth.model.js +63 -0
  27. package/dist/core/modules/better-auth/better-auth-auth.model.js.map +1 -0
  28. package/dist/core/modules/better-auth/better-auth-models.d.ts +43 -0
  29. package/dist/core/modules/better-auth/better-auth-models.js +181 -0
  30. package/dist/core/modules/better-auth/better-auth-models.js.map +1 -0
  31. package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.d.ts +12 -0
  32. package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.js +70 -0
  33. package/dist/core/modules/better-auth/better-auth-rate-limit.middleware.js.map +1 -0
  34. package/dist/core/modules/better-auth/better-auth-rate-limiter.service.d.ts +32 -0
  35. package/dist/core/modules/better-auth/better-auth-rate-limiter.service.js +173 -0
  36. package/dist/core/modules/better-auth/better-auth-rate-limiter.service.js.map +1 -0
  37. package/dist/core/modules/better-auth/better-auth-user.mapper.d.ts +43 -0
  38. package/dist/core/modules/better-auth/better-auth-user.mapper.js +159 -0
  39. package/dist/core/modules/better-auth/better-auth-user.mapper.js.map +1 -0
  40. package/dist/core/modules/better-auth/better-auth.config.d.ts +9 -0
  41. package/dist/core/modules/better-auth/better-auth.config.js +254 -0
  42. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -0
  43. package/dist/core/modules/better-auth/better-auth.middleware.d.ts +20 -0
  44. package/dist/core/modules/better-auth/better-auth.middleware.js +79 -0
  45. package/dist/core/modules/better-auth/better-auth.middleware.js.map +1 -0
  46. package/dist/core/modules/better-auth/better-auth.module.d.ts +38 -0
  47. package/dist/core/modules/better-auth/better-auth.module.js +253 -0
  48. package/dist/core/modules/better-auth/better-auth.module.js.map +1 -0
  49. package/dist/core/modules/better-auth/better-auth.resolver.d.ts +45 -0
  50. package/dist/core/modules/better-auth/better-auth.resolver.js +221 -0
  51. package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -0
  52. package/dist/core/modules/better-auth/better-auth.service.d.ts +37 -0
  53. package/dist/core/modules/better-auth/better-auth.service.js +148 -0
  54. package/dist/core/modules/better-auth/better-auth.service.js.map +1 -0
  55. package/dist/core/modules/better-auth/better-auth.types.d.ts +39 -0
  56. package/dist/core/modules/better-auth/better-auth.types.js +26 -0
  57. package/dist/core/modules/better-auth/better-auth.types.js.map +1 -0
  58. package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +66 -0
  59. package/dist/core/modules/better-auth/core-better-auth.controller.js +491 -0
  60. package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -0
  61. package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +59 -0
  62. package/dist/core/modules/better-auth/core-better-auth.resolver.js +538 -0
  63. package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -0
  64. package/dist/core/modules/better-auth/index.d.ts +13 -0
  65. package/dist/core/modules/better-auth/index.js +30 -0
  66. package/dist/core/modules/better-auth/index.js.map +1 -0
  67. package/dist/core/modules/user/core-user.model.d.ts +2 -0
  68. package/dist/core/modules/user/core-user.model.js +21 -0
  69. package/dist/core/modules/user/core-user.model.js.map +1 -1
  70. package/dist/core.module.js +7 -0
  71. package/dist/core.module.js.map +1 -1
  72. package/dist/index.d.ts +1 -0
  73. package/dist/index.js +1 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/server/modules/better-auth/better-auth.controller.d.ts +10 -0
  76. package/dist/server/modules/better-auth/better-auth.controller.js +36 -0
  77. package/dist/server/modules/better-auth/better-auth.controller.js.map +1 -0
  78. package/dist/server/modules/better-auth/better-auth.module.d.ts +9 -0
  79. package/dist/server/modules/better-auth/better-auth.module.js +44 -0
  80. package/dist/server/modules/better-auth/better-auth.module.js.map +1 -0
  81. package/dist/server/modules/better-auth/better-auth.resolver.d.ts +45 -0
  82. package/dist/server/modules/better-auth/better-auth.resolver.js +221 -0
  83. package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -0
  84. package/dist/server/modules/file/file-info.model.d.ts +71 -3
  85. package/dist/server/modules/user/user.model.d.ts +169 -3
  86. package/dist/server/server.module.js +6 -1
  87. package/dist/server/server.module.js.map +1 -1
  88. package/dist/tsconfig.build.tsbuildinfo +1 -1
  89. package/package.json +21 -22
  90. package/src/config.env.ts +139 -1
  91. package/src/core/common/decorators/restricted.decorator.ts +2 -2
  92. package/src/core/common/helpers/filter.helper.ts +15 -17
  93. package/src/core/common/helpers/gridfs.helper.ts +5 -5
  94. package/src/core/common/helpers/input.helper.ts +2 -2
  95. package/src/core/common/interfaces/server-options.interface.ts +377 -20
  96. package/src/core/common/services/crud.service.ts +22 -22
  97. package/src/core/modules/auth/auth-guard-strategy.enum.ts +1 -0
  98. package/src/core/modules/auth/guards/auth.guard.ts +20 -6
  99. package/src/core/modules/better-auth/README.md +1422 -0
  100. package/src/core/modules/better-auth/better-auth-auth.model.ts +69 -0
  101. package/src/core/modules/better-auth/better-auth-models.ts +140 -0
  102. package/src/core/modules/better-auth/better-auth-rate-limit.middleware.ts +113 -0
  103. package/src/core/modules/better-auth/better-auth-rate-limiter.service.ts +326 -0
  104. package/src/core/modules/better-auth/better-auth-user.mapper.ts +269 -0
  105. package/src/core/modules/better-auth/better-auth.config.ts +488 -0
  106. package/src/core/modules/better-auth/better-auth.middleware.ts +111 -0
  107. package/src/core/modules/better-auth/better-auth.module.ts +474 -0
  108. package/src/core/modules/better-auth/better-auth.resolver.ts +213 -0
  109. package/src/core/modules/better-auth/better-auth.service.ts +314 -0
  110. package/src/core/modules/better-auth/better-auth.types.ts +90 -0
  111. package/src/core/modules/better-auth/core-better-auth.controller.ts +605 -0
  112. package/src/core/modules/better-auth/core-better-auth.resolver.ts +705 -0
  113. package/src/core/modules/better-auth/index.ts +32 -0
  114. package/src/core/modules/user/core-user.model.ts +29 -0
  115. package/src/core.module.ts +13 -0
  116. package/src/index.ts +6 -0
  117. package/src/server/modules/better-auth/better-auth.controller.ts +41 -0
  118. package/src/server/modules/better-auth/better-auth.module.ts +88 -0
  119. package/src/server/modules/better-auth/better-auth.resolver.ts +201 -0
  120. package/src/server/server.module.ts +10 -1
@@ -0,0 +1,1422 @@
1
+ # Better-Auth Module
2
+
3
+ Integration of the [better-auth](https://better-auth.com) authentication framework with @lenne.tech/nest-server.
4
+
5
+ ## Features
6
+
7
+ ### Built-in Plugins (Explicit Configuration)
8
+
9
+ - **JWT Tokens** - For API clients and stateless authentication
10
+ - **Two-Factor Authentication (2FA)** - TOTP-based second factor
11
+ - **Passkey/WebAuthn** - Passwordless authentication
12
+
13
+ ### Core Features
14
+
15
+ - **Email/Password Authentication** - Standard username/password login
16
+ - **Social Login** - Any OAuth provider (Google, GitHub, Apple, Discord, etc.)
17
+ - **Parallel Operation** - Runs alongside Legacy Auth without side effects
18
+
19
+ ### Extensible via Plugins
20
+
21
+ - **Organization** - Multi-tenant, teams, member management
22
+ - **Admin** - User impersonation, banning
23
+ - **SSO** - Single Sign-On (OIDC, SAML)
24
+ - **And many more** - See [Plugins and Extensions](#plugins-and-extensions)
25
+
26
+ ## Quick Start
27
+
28
+ Better-Auth is **enabled by default** and uses sensible defaults. Integration follows the same pattern as Legacy Auth - via an extended module in your project.
29
+
30
+ ### 1. Create Extended Module (Recommended)
31
+
32
+ ```typescript
33
+ // src/server/modules/better-auth/better-auth.module.ts
34
+ import { BetterAuthModule as CoreBetterAuthModule } from '@lenne.tech/nest-server';
35
+
36
+ @Module({})
37
+ export class BetterAuthModule {
38
+ static forRoot(options) {
39
+ return {
40
+ module: BetterAuthModule,
41
+ imports: [CoreBetterAuthModule.forRoot(options)],
42
+ };
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### 2. Import in ServerModule
48
+
49
+ ```typescript
50
+ // src/server/server.module.ts
51
+ import { BetterAuthModule } from './modules/better-auth/better-auth.module';
52
+
53
+ @Module({
54
+ imports: [
55
+ CoreModule.forRoot(environment),
56
+ BetterAuthModule.forRoot({ config: environment.betterAuth }),
57
+ ],
58
+ })
59
+ export class ServerModule {}
60
+ ```
61
+
62
+ ### 3. Configure (Optional)
63
+
64
+ ```typescript
65
+ // config.env.ts - customize behavior (optional):
66
+ const config = {
67
+ betterAuth: {
68
+ // baseUrl: 'https://your-domain.com',
69
+ // basePath: '/iam',
70
+ },
71
+ };
72
+ ```
73
+
74
+ **Default values (used when not configured):**
75
+
76
+ - **Secret**: Falls back to `jwt.secret` → `jwt.refresh.secret` → auto-generated
77
+ - **Base URL**: `http://localhost:3000`
78
+ - **Base Path**: `/iam`
79
+ - **Passkey Origin**: `http://localhost:3000`
80
+ - **Passkey rpId**: `localhost`
81
+ - **Passkey rpName**: `Nest Server`
82
+
83
+ To **explicitly disable** Better-Auth:
84
+
85
+ ```typescript
86
+ const config = {
87
+ betterAuth: {
88
+ enabled: false,
89
+ },
90
+ };
91
+ ```
92
+
93
+ Read the security section below for production deployments.
94
+
95
+ ## Understanding Core Settings
96
+
97
+ ### Base URL (`baseUrl`)
98
+
99
+ **Purpose:** Absolute URL generation for links and redirects
100
+
101
+ **Technically used for:**
102
+
103
+ - **Email Links**: Password reset links, email verification links
104
+ ```text
105
+ https://your-domain.com/iam/reset-password?token=xyz
106
+ ```
107
+ - **OAuth Redirect URIs**: Callback URLs for social login providers
108
+ ```text
109
+ https://your-domain.com/iam/callback/google
110
+ ```
111
+ - **CORS Origin Validation**: If no `trustedOrigins` are set, `baseUrl` is used as the trusted origin
112
+
113
+ **Impact of incorrect value:** Email links point to wrong server, OAuth callbacks fail.
114
+
115
+ ### Base Path (`basePath`)
116
+
117
+ **Purpose:** URL prefix for all Better-Auth REST endpoints
118
+
119
+ **Technically used for:**
120
+
121
+ - **Routing**: All endpoints are registered under this path
122
+ ```text
123
+ /iam/sign-in
124
+ /iam/sign-up
125
+ /iam/session
126
+ /iam/callback/:provider
127
+ ```
128
+ - **Middleware Matching**: `BetterAuthMiddleware` only forwards requests to paths starting with `basePath`
129
+
130
+ **Default `/iam`** avoids collisions with existing `/auth` routes from Legacy Auth.
131
+
132
+ ### Passkey Origin (`passkey.origin`)
133
+
134
+ **Purpose:** WebAuthn/Passkey security validation
135
+
136
+ **Technically used for:**
137
+
138
+ - **Passkey Registration/Authentication**: Browser sends origin in WebAuthn request
139
+ - **Origin Verification**: Server validates that request comes from expected origin
140
+ - **Phishing Protection**: Prevents passkeys from being used on other domains
141
+
142
+ **Impact of incorrect value:** Passkey authentication fails with "origin mismatch" error.
143
+
144
+ ### Configuration Summary
145
+
146
+ | Setting | Technical Purpose | Impact of Wrong Value |
147
+ | ----------------- | ---------------------- | ---------------------------- |
148
+ | `baseUrl` | Email links, OAuth | Links point to wrong server |
149
+ | `basePath` | Endpoint routing | 404 on API calls |
150
+ | `passkey.origin` | WebAuthn security | Passkey auth fails |
151
+
152
+ **For Development:** The defaults (`http://localhost:3000`, `/iam`) are correct.
153
+
154
+ **For Production:** You must set `baseUrl` and `passkey.origin` to your actual domain:
155
+
156
+ ```typescript
157
+ const config = {
158
+ betterAuth: {
159
+ baseUrl: 'https://api.your-domain.com',
160
+ passkey: {
161
+ // enabled by default when config block is present
162
+ origin: 'https://your-domain.com', // Frontend domain
163
+ rpId: 'your-domain.com', // Domain without protocol
164
+ rpName: 'Your Application',
165
+ },
166
+ },
167
+ };
168
+ ```
169
+
170
+ ## IMPORTANT: Secret Configuration
171
+
172
+ ### Secret Resolution (Fallback Chain)
173
+
174
+ Better-Auth resolves the secret in the following order:
175
+
176
+ 1. **`betterAuth.secret`** - If explicitly configured
177
+ 2. **`jwt.secret`** - Fallback for backwards compatibility (if ≥32 chars)
178
+ 3. **`jwt.refresh.secret`** - Second fallback (if ≥32 chars)
179
+ 4. **Auto-generated** - Secure random secret (with warning)
180
+
181
+ ### Backwards Compatibility
182
+
183
+ If you already have `jwt.secret` configured with ≥32 characters, **Better-Auth will automatically use it** as a fallback. This means:
184
+
185
+ - No configuration needed for existing projects with proper JWT secrets
186
+ - Sessions persist across server restarts (uses existing secret)
187
+ - A warning reminds you to set `betterAuth.secret` explicitly
188
+
189
+ ### Development (Auto-Generated Secret)
190
+
191
+ When no valid secret is found (including fallbacks), Better-Auth **automatically generates a secure random secret** at server startup.
192
+
193
+ **Consequences:**
194
+
195
+ - **Secure** - The secret is cryptographically random (32 bytes)
196
+ - **Sessions lost on restart** - All user sessions become invalid when the server restarts
197
+ - **Acceptable for development** - Frequent restarts during development are normal
198
+
199
+ ### Production (Recommended)
200
+
201
+ For production, explicitly set `betterAuth.secret` or ensure `jwt.secret` is ≥32 characters:
202
+
203
+ ```bash
204
+ # Generate a secure secret:
205
+ node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
206
+
207
+ # Set in your environment:
208
+ export BETTER_AUTH_SECRET="your-generated-secret-here"
209
+ ```
210
+
211
+ **Consequences of using auto-generated secret in production:**
212
+
213
+ - Sessions lost on every deployment - All users must re-login after each deploy
214
+ - Sessions lost on server restart - Any restart invalidates all sessions
215
+ - No session sharing in clusters - Multiple server instances can't share sessions
216
+
217
+ ### Secret Requirements
218
+
219
+ | Requirement | Value |
220
+ | ----------------- | --------------------------------------------------------- |
221
+ | Minimum length | 32 characters |
222
+ | Recommended | 44+ characters (32 bytes base64) |
223
+ | Character types | At least 2 of: lowercase, uppercase, numbers, special |
224
+
225
+ ### Configuration Examples
226
+
227
+ **Via Environment Variable (Recommended):**
228
+
229
+ ```bash
230
+ BETTER_AUTH_SECRET=K7xR2mN9pQ4wE6tY8uI0oP3aS5dF7gH9jK1lZ2xC4vB6nM8=
231
+ ```
232
+
233
+ **Via config.env.ts:**
234
+
235
+ ```typescript
236
+ const config = {
237
+ betterAuth: {
238
+ // enabled: true by default - no need to set explicitly
239
+ secret: process.env.BETTER_AUTH_SECRET,
240
+ },
241
+ };
242
+ ```
243
+
244
+ ## Configuration
245
+
246
+ **Optional** - Better-Auth works without any configuration (true zero-config). Only add this block if you need to customize behavior:
247
+
248
+ ```typescript
249
+ // In config.env.ts
250
+ export default {
251
+ // ... other config
252
+
253
+ // OPTIONAL: Better-Auth configuration
254
+ // Omit entirely for default behavior, or customize as needed:
255
+ betterAuth: {
256
+ // enabled: true by default - only set to false to disable
257
+ // secret: auto-generated if not set (see Security section above)
258
+ // baseUrl: 'http://localhost:3000', // Default
259
+ // basePath: '/iam', // Default
260
+
261
+ // JWT Plugin (enabled by default when config block is present)
262
+ // Set enabled: false to explicitly disable
263
+ jwt: {
264
+ expiresIn: '15m',
265
+ },
266
+
267
+ // Two-Factor Authentication (enabled by default when config block is present)
268
+ // Set enabled: false to explicitly disable
269
+ twoFactor: {
270
+ appName: 'My Application',
271
+ },
272
+
273
+ // Passkey/WebAuthn (enabled by default when config block is present)
274
+ // Set enabled: false to explicitly disable
275
+ passkey: {
276
+ rpId: 'localhost',
277
+ rpName: 'My Application',
278
+ origin: 'http://localhost:3000',
279
+ },
280
+
281
+ // Social Providers (enabled by default when credentials are configured)
282
+ // Set enabled: false to explicitly disable a provider
283
+ socialProviders: {
284
+ google: {
285
+ clientId: 'your-google-client-id',
286
+ clientSecret: 'your-google-client-secret',
287
+ },
288
+ github: {
289
+ clientId: 'your-github-client-id',
290
+ clientSecret: 'your-github-client-secret',
291
+ },
292
+ },
293
+
294
+ // Trusted Origins for CORS
295
+ trustedOrigins: ['http://localhost:3000', 'https://your-app.com'],
296
+
297
+ // Rate Limiting (optional)
298
+ rateLimit: {
299
+ enabled: true,
300
+ max: 10,
301
+ windowSeconds: 60,
302
+ message: 'Too many requests, please try again later.',
303
+ strictEndpoints: ['/sign-in', '/sign-up', '/forgot-password', '/reset-password'],
304
+ skipEndpoints: ['/session', '/callback'],
305
+ },
306
+ },
307
+ };
308
+ ```
309
+
310
+ ## Advanced Configuration
311
+
312
+ ### Email/Password Authentication
313
+
314
+ Email/password authentication is enabled by default. You can disable it if you only want social login:
315
+
316
+ ```typescript
317
+ const config = {
318
+ betterAuth: {
319
+ emailAndPassword: {
320
+ enabled: false, // Disable email/password, only allow social login
321
+ },
322
+ },
323
+ };
324
+ ```
325
+
326
+ ### Additional User Fields
327
+
328
+ Add custom fields to the Better-Auth user schema:
329
+
330
+ ```typescript
331
+ const config = {
332
+ betterAuth: {
333
+ additionalUserFields: {
334
+ phoneNumber: { type: 'string', defaultValue: null },
335
+ department: { type: 'string', required: true },
336
+ preferences: { type: 'string', defaultValue: '{}' },
337
+ isActive: { type: 'boolean', defaultValue: true },
338
+ },
339
+ },
340
+ };
341
+ ```
342
+
343
+ **Available field types:** `'string'`, `'number'`, `'boolean'`, `'date'`, `'json'`, `'string[]'`, `'number[]'`
344
+
345
+ ### Module Integration (Recommended Pattern)
346
+
347
+ By default (`autoRegister: false`), projects integrate BetterAuth via an **extended module** in their project. This follows the same pattern as Legacy Auth and allows for custom resolvers, controllers, and project-specific authentication logic.
348
+
349
+ ```typescript
350
+ // src/server/modules/better-auth/better-auth.module.ts
351
+ import { Module, DynamicModule } from '@nestjs/common';
352
+ import { BetterAuthModule as CoreBetterAuthModule } from '@lenne.tech/nest-server';
353
+
354
+ @Module({})
355
+ export class BetterAuthModule {
356
+ static forRoot(options): DynamicModule {
357
+ return {
358
+ module: BetterAuthModule,
359
+ imports: [CoreBetterAuthModule.forRoot(options)],
360
+ // Add custom providers, resolvers, etc.
361
+ };
362
+ }
363
+ }
364
+
365
+ // src/server/server.module.ts
366
+ import { BetterAuthModule } from './modules/better-auth/better-auth.module';
367
+
368
+ @Module({
369
+ imports: [
370
+ CoreModule.forRoot(environment),
371
+ BetterAuthModule.forRoot({
372
+ config: environment.betterAuth,
373
+ resolver: CustomBetterAuthResolver, // Optional custom resolver
374
+ }),
375
+ ],
376
+ })
377
+ export class ServerModule {}
378
+ ```
379
+
380
+ ### Auto-Registration (Simple Projects)
381
+
382
+ For simple projects that don't need customization, you can enable auto-registration:
383
+
384
+ ```typescript
385
+ // In config.env.ts
386
+ const config = {
387
+ betterAuth: {
388
+ autoRegister: true, // Enable auto-registration in CoreModule
389
+ },
390
+ };
391
+
392
+ // No manual import needed - CoreModule handles everything
393
+ @Module({
394
+ imports: [CoreModule.forRoot(environment)],
395
+ })
396
+ export class ServerModule {}
397
+ ```
398
+
399
+ ### Options Passthrough
400
+
401
+ For full Better-Auth customization, use the `options` passthrough. These options are passed directly to Better-Auth:
402
+
403
+ ```typescript
404
+ const config = {
405
+ betterAuth: {
406
+ options: {
407
+ emailAndPassword: {
408
+ requireEmailVerification: true,
409
+ sendResetPassword: async ({ user, url }) => {
410
+ // Custom password reset email logic
411
+ },
412
+ },
413
+ account: {
414
+ accountLinking: { enabled: true },
415
+ },
416
+ session: {
417
+ expiresIn: 60 * 60 * 24 * 7, // 7 days
418
+ updateAge: 60 * 60 * 24, // 1 day
419
+ },
420
+ advanced: {
421
+ cookiePrefix: 'my-app',
422
+ useSecureCookies: true,
423
+ },
424
+ },
425
+ },
426
+ };
427
+ ```
428
+
429
+ See [Better-Auth Options Reference](https://www.better-auth.com/docs/reference/options) for all available options.
430
+
431
+ ## Plugins and Extensions
432
+
433
+ Better-Auth provides a rich plugin ecosystem. This module uses a **hybrid approach**:
434
+
435
+ - **Built-in plugins** (JWT, 2FA, Passkey): Explicitly configured with typed options
436
+ - **Additional plugins**: Dynamically added via the `plugins` array
437
+
438
+ ### Built-in Plugins (Explicit Configuration)
439
+
440
+ These plugins are enabled by default when their config block is present. **All properties have sensible defaults**, so an empty block `{}` is sufficient!
441
+
442
+ | Plugin | Minimal Config | Default Values |
443
+ | ------------------ | -------------------- | --------------------------------------------------------------------------------- |
444
+ | **JWT** | `jwt: {}` | `expiresIn: '15m'` |
445
+ | **Two-Factor** | `twoFactor: {}` | `appName: 'Nest Server'` |
446
+ | **Passkey** | `passkey: {}` | `origin: 'http://localhost:3000'`, `rpId: 'localhost'`, `rpName: 'Nest Server'` |
447
+
448
+ #### Minimal Syntax (Recommended for Development)
449
+
450
+ ```typescript
451
+ const config = {
452
+ betterAuth: {
453
+ // Just add empty blocks - all defaults are applied!
454
+ jwt: {},
455
+ twoFactor: {},
456
+ passkey: {},
457
+ },
458
+ };
459
+ ```
460
+
461
+ #### Custom Configuration (Recommended for Production)
462
+
463
+ ```typescript
464
+ const config = {
465
+ betterAuth: {
466
+ jwt: { expiresIn: '30m' },
467
+ twoFactor: { appName: 'My App' },
468
+ passkey: {
469
+ rpId: 'example.com',
470
+ rpName: 'My App',
471
+ origin: 'https://example.com',
472
+ },
473
+ },
474
+ };
475
+ ```
476
+
477
+ #### Disabling a Plugin
478
+
479
+ ```typescript
480
+ const config = {
481
+ betterAuth: {
482
+ jwt: { enabled: false }, // Explicitly disable JWT
483
+ twoFactor: {}, // 2FA still enabled with defaults
484
+ },
485
+ };
486
+ ```
487
+
488
+ ### Dynamic Plugins (plugins Array)
489
+
490
+ For all other Better-Auth plugins, use the `plugins` array. This provides maximum flexibility without requiring updates to this package.
491
+
492
+ ```typescript
493
+ import { organization } from 'better-auth/plugins';
494
+ import { admin } from 'better-auth/plugins';
495
+ import { multiSession } from 'better-auth/plugins';
496
+ import { apiKey } from 'better-auth/plugins';
497
+
498
+ const config = {
499
+ betterAuth: {
500
+ // Built-in plugins
501
+ jwt: { expiresIn: '30m' },
502
+
503
+ // Additional plugins via array
504
+ plugins: [
505
+ organization({
506
+ allowUserToCreateOrganization: true,
507
+ creatorRole: 'owner',
508
+ }),
509
+ admin({
510
+ impersonationSessionDuration: 60 * 60,
511
+ }),
512
+ multiSession({
513
+ maximumSessions: 5,
514
+ }),
515
+ apiKey(),
516
+ ],
517
+ },
518
+ };
519
+ ```
520
+
521
+ ### Available Better-Auth Plugins
522
+
523
+ | Plugin | Use Case | Recommendation |
524
+ | ------------------ | --------------------------------------------- | --------------------------- |
525
+ | **organization** | Multi-tenant apps, teams, member management | Common for SaaS/B2B |
526
+ | **admin** | User impersonation, banning, user management | Common for admin panels |
527
+ | **multiSession** | Multiple active sessions per user | Account switching apps |
528
+ | **apiKey** | API key based authentication | Public APIs |
529
+ | **sso** | Single Sign-On (OIDC, SAML 2.0) | Enterprise apps |
530
+ | **oidcProvider** | Build your own identity provider | Identity platforms |
531
+ | **genericOAuth** | Custom OAuth providers | Special OAuth integrations |
532
+ | **polar** | Usage-based billing with Polar | SaaS billing |
533
+
534
+ For the complete list of plugins, see:
535
+
536
+ - [Official Plugins](https://www.better-auth.com/docs/concepts/plugins)
537
+ - [Community Plugins](https://www.better-auth.com/docs/plugins/community-plugins)
538
+
539
+ ### Example: Organization Plugin
540
+
541
+ Multi-tenant support with teams and roles:
542
+
543
+ ```typescript
544
+ import { organization } from 'better-auth/plugins';
545
+
546
+ const config = {
547
+ betterAuth: {
548
+ plugins: [
549
+ organization({
550
+ allowUserToCreateOrganization: true,
551
+ creatorRole: 'owner',
552
+ membershipLimit: 100,
553
+ organizationLimit: 5,
554
+ teams: {
555
+ enabled: true,
556
+ maximumTeams: 10,
557
+ },
558
+ }),
559
+ ],
560
+ },
561
+ };
562
+ ```
563
+
564
+ ### Example: Admin Plugin
565
+
566
+ User management and impersonation:
567
+
568
+ ```typescript
569
+ import { admin } from 'better-auth/plugins';
570
+
571
+ const config = {
572
+ betterAuth: {
573
+ plugins: [
574
+ admin({
575
+ impersonationSessionDuration: 60 * 60,
576
+ defaultRole: 'user',
577
+ adminRole: 'admin',
578
+ }),
579
+ ],
580
+ },
581
+ };
582
+ ```
583
+
584
+ ### Example: SSO Plugin
585
+
586
+ Enterprise Single Sign-On:
587
+
588
+ ```typescript
589
+ import { sso } from 'better-auth/plugins';
590
+
591
+ const config = {
592
+ betterAuth: {
593
+ plugins: [
594
+ sso({
595
+ issuer: 'https://your-identity-provider.com',
596
+ // ... OIDC/SAML configuration
597
+ }),
598
+ ],
599
+ },
600
+ };
601
+ ```
602
+
603
+ ### Why This Hybrid Approach?
604
+
605
+ | Approach | Pros | Cons |
606
+ | --------------------------------- | -------------------------------------------------- | --------------------------------- |
607
+ | **Built-in** (jwt, 2fa, passkey) | TypeScript types, IDE autocomplete, documentation | Package update needed for changes |
608
+ | **Dynamic** (plugins array) | Any plugin works immediately, future-proof | No typed config in IBetterAuth |
609
+
610
+ **Best of both worlds:**
611
+
612
+ - Core auth plugins with great developer experience
613
+ - Full flexibility for specialized plugins
614
+ - No package updates needed for new Better-Auth plugins
615
+
616
+ ## Module Setup
617
+
618
+ Better-Auth is **enabled by default** but requires explicit module integration (`autoRegister: false` by default). This follows the same pattern as Legacy Auth, giving projects full control over customization.
619
+
620
+ ### Recommended: Extended Module Pattern
621
+
622
+ Projects typically integrate Better-Auth via an extended module:
623
+
624
+ ```typescript
625
+ // src/server/modules/better-auth/better-auth.module.ts
626
+ import { BetterAuthModule as CoreBetterAuthModule } from '@lenne.tech/nest-server';
627
+
628
+ @Module({})
629
+ export class BetterAuthModule {
630
+ static forRoot(options): DynamicModule {
631
+ return {
632
+ module: BetterAuthModule,
633
+ imports: [CoreBetterAuthModule.forRoot(options)],
634
+ };
635
+ }
636
+ }
637
+
638
+ // src/server/server.module.ts
639
+ @Module({
640
+ imports: [
641
+ CoreModule.forRoot(environment),
642
+ BetterAuthModule.forRoot({ config: environment.betterAuth }),
643
+ ],
644
+ })
645
+ export class ServerModule {}
646
+ ```
647
+
648
+ ### Simple: Auto-Registration
649
+
650
+ For simple projects without customization needs:
651
+
652
+ ```typescript
653
+ // In config.env.ts
654
+ const config = {
655
+ betterAuth: {
656
+ autoRegister: true, // Let CoreModule handle registration
657
+ },
658
+ };
659
+
660
+ // In server.module.ts - no manual import needed
661
+ @Module({
662
+ imports: [CoreModule.forRoot(environment)],
663
+ })
664
+ export class ServerModule {}
665
+ ```
666
+
667
+ ### Disable Better-Auth
668
+
669
+ To explicitly disable Better-Auth:
670
+
671
+ ```typescript
672
+ const config = {
673
+ betterAuth: {
674
+ enabled: false,
675
+ },
676
+ };
677
+ ```
678
+
679
+ ## REST API Endpoints
680
+
681
+ When enabled, Better-Auth exposes the following endpoints at the configured `basePath` (default: `/iam`):
682
+
683
+ | Endpoint | Method | Description |
684
+ | --------------------------- | ------ | ---------------------------- |
685
+ | `/iam/sign-up/email` | POST | Register new user |
686
+ | `/iam/sign-in/email` | POST | Sign in with email/password |
687
+ | `/iam/sign-out` | GET | Sign out (invalidate session)|
688
+ | `/iam/session` | GET | Get current session |
689
+ | `/iam/forgot-password` | POST | Request password reset |
690
+ | `/iam/reset-password` | POST | Reset password with token |
691
+ | `/iam/verify-email` | POST | Verify email address |
692
+
693
+ ### Social Login Endpoints
694
+
695
+ | Endpoint | Method | Description |
696
+ | -------------------------- | ------ | --------------------- |
697
+ | `/iam/sign-in/social` | POST | Initiate social login |
698
+ | `/iam/callback/:provider` | GET | OAuth callback |
699
+
700
+ ### Two-Factor Authentication Endpoints
701
+
702
+ | Endpoint | Method | Description |
703
+ | ------------------------- | ------ | ---------------- |
704
+ | `/iam/two-factor/enable` | POST | Enable 2FA |
705
+ | `/iam/two-factor/disable` | POST | Disable 2FA |
706
+ | `/iam/two-factor/verify` | POST | Verify 2FA code |
707
+
708
+ ### Passkey Endpoints
709
+
710
+ | Endpoint | Method | Description |
711
+ | ---------------------------- | ------ | ------------------------- |
712
+ | `/iam/passkey/register` | POST | Register new passkey |
713
+ | `/iam/passkey/authenticate` | POST | Authenticate with passkey |
714
+
715
+ ## GraphQL API
716
+
717
+ In addition to REST endpoints, Better-Auth provides GraphQL queries and mutations:
718
+
719
+ ### Queries
720
+
721
+ | Query | Arguments | Return Type | Description |
722
+ | ------------------------ | --------- | ---------------------------- | ------------------------------- |
723
+ | `betterAuthEnabled` | - | `Boolean` | Check if Better-Auth is enabled |
724
+ | `betterAuthFeatures` | - | `BetterAuthFeaturesModel` | Get enabled features status |
725
+ | `betterAuthSession` | - | `BetterAuthSessionModel` | Get current session (auth req.) |
726
+ | `betterAuthListPasskeys` | - | `[BetterAuthPasskeyModel]` | List user's passkeys (auth req.)|
727
+
728
+ ### Mutations
729
+
730
+ #### Authentication
731
+
732
+ | Mutation | Arguments | Return Type | Description |
733
+ | --------------------- | ---------------------------- | -------------------- | ------------------------- |
734
+ | `betterAuthSignIn` | `email`, `password` | `BetterAuthAuthModel`| Sign in with email/pass |
735
+ | `betterAuthSignUp` | `email`, `password`, `name?` | `BetterAuthAuthModel`| Register new account |
736
+ | `betterAuthSignOut` | - | `Boolean` | Sign out (requires auth) |
737
+ | `betterAuthVerify2FA` | `code` | `BetterAuthAuthModel`| Verify 2FA code |
738
+
739
+ #### 2FA Management (requires authentication)
740
+
741
+ | Mutation | Arguments | Return Type | Description |
742
+ | ------------------------------ | ---------- | ------------------------ | ------------------------------ |
743
+ | `betterAuthEnable2FA` | `password` | `BetterAuth2FASetupModel`| Enable 2FA, get TOTP URI |
744
+ | `betterAuthDisable2FA` | `password` | `Boolean` | Disable 2FA for user |
745
+ | `betterAuthGenerateBackupCodes`| - | `[String]` | Generate new backup codes |
746
+
747
+ #### Passkey Management (requires authentication)
748
+
749
+ | Mutation | Arguments | Return Type | Description |
750
+ | ------------------------------ | ----------- | -------------------------------- | --------------------------- |
751
+ | `betterAuthGetPasskeyChallenge`| - | `BetterAuthPasskeyChallengeModel`| Get WebAuthn challenge |
752
+ | `betterAuthDeletePasskey` | `passkeyId` | `Boolean` | Delete a passkey |
753
+
754
+ ### Response Types
755
+
756
+ #### BetterAuthAuthModel
757
+
758
+ ```graphql
759
+ type BetterAuthAuthModel {
760
+ success: Boolean!
761
+ requiresTwoFactor: Boolean
762
+ token: String
763
+ user: BetterAuthUserModel
764
+ session: BetterAuthSessionInfoModel
765
+ error: String
766
+ }
767
+ ```
768
+
769
+ #### BetterAuthFeaturesModel
770
+
771
+ ```graphql
772
+ type BetterAuthFeaturesModel {
773
+ enabled: Boolean!
774
+ jwt: Boolean!
775
+ twoFactor: Boolean!
776
+ passkey: Boolean!
777
+ socialProviders: [String!]!
778
+ }
779
+ ```
780
+
781
+ #### BetterAuth2FASetupModel
782
+
783
+ ```graphql
784
+ type BetterAuth2FASetupModel {
785
+ success: Boolean!
786
+ totpUri: String
787
+ backupCodes: [String!]
788
+ error: String
789
+ }
790
+ ```
791
+
792
+ #### BetterAuthPasskeyModel
793
+
794
+ ```graphql
795
+ type BetterAuthPasskeyModel {
796
+ id: String!
797
+ name: String
798
+ credentialId: String!
799
+ createdAt: DateTime!
800
+ }
801
+ ```
802
+
803
+ #### BetterAuthPasskeyChallengeModel
804
+
805
+ ```graphql
806
+ type BetterAuthPasskeyChallengeModel {
807
+ success: Boolean!
808
+ challenge: String
809
+ error: String
810
+ }
811
+ ```
812
+
813
+ ### Example Usage
814
+
815
+ ```graphql
816
+ # Check if Better-Auth is enabled
817
+ query {
818
+ betterAuthEnabled
819
+ }
820
+
821
+ # Get available features
822
+ query {
823
+ betterAuthFeatures {
824
+ enabled
825
+ jwt
826
+ twoFactor
827
+ passkey
828
+ socialProviders
829
+ }
830
+ }
831
+
832
+ # Sign in
833
+ mutation {
834
+ betterAuthSignIn(email: "user@example.com", password: "password123") {
835
+ success
836
+ requiresTwoFactor
837
+ token
838
+ user {
839
+ id
840
+ email
841
+ name
842
+ roles
843
+ }
844
+ }
845
+ }
846
+
847
+ # Sign up
848
+ mutation {
849
+ betterAuthSignUp(
850
+ email: "newuser@example.com"
851
+ password: "securePassword123"
852
+ name: "New User"
853
+ ) {
854
+ success
855
+ user {
856
+ id
857
+ email
858
+ }
859
+ }
860
+ }
861
+
862
+ # Verify 2FA (after sign-in with requiresTwoFactor: true)
863
+ mutation {
864
+ betterAuthVerify2FA(code: "123456") {
865
+ success
866
+ token
867
+ user {
868
+ id
869
+ email
870
+ }
871
+ }
872
+ }
873
+
874
+ # Enable 2FA (requires authentication)
875
+ mutation {
876
+ betterAuthEnable2FA(password: "yourPassword") {
877
+ success
878
+ totpUri
879
+ backupCodes
880
+ error
881
+ }
882
+ }
883
+
884
+ # Disable 2FA
885
+ mutation {
886
+ betterAuthDisable2FA(password: "yourPassword")
887
+ }
888
+
889
+ # Generate new backup codes
890
+ mutation {
891
+ betterAuthGenerateBackupCodes
892
+ }
893
+
894
+ # List passkeys
895
+ query {
896
+ betterAuthListPasskeys {
897
+ id
898
+ name
899
+ credentialId
900
+ createdAt
901
+ }
902
+ }
903
+
904
+ # Get passkey registration challenge (for WebAuthn)
905
+ mutation {
906
+ betterAuthGetPasskeyChallenge {
907
+ success
908
+ challenge
909
+ error
910
+ }
911
+ }
912
+
913
+ # Delete a passkey
914
+ mutation {
915
+ betterAuthDeletePasskey(passkeyId: "passkey-id-here")
916
+ }
917
+ ```
918
+
919
+ ## Using BetterAuthService
920
+
921
+ Inject `BetterAuthService` to access Better-Auth functionality:
922
+
923
+ ```typescript
924
+ import { BetterAuthService } from '@lenne.tech/nest-server';
925
+
926
+ @Injectable()
927
+ export class MyService {
928
+ constructor(private readonly betterAuthService: BetterAuthService) {}
929
+
930
+ async checkUser(req: Request) {
931
+ // Check if Better-Auth is enabled
932
+ if (!this.betterAuthService.isEnabled()) {
933
+ return null;
934
+ }
935
+
936
+ // Get current session
937
+ const { session, user } = await this.betterAuthService.getSession(req);
938
+
939
+ if (session) {
940
+ console.log('User:', user.email);
941
+ console.log('Session expires:', session.expiresAt);
942
+
943
+ // Check if session is expiring soon
944
+ if (this.betterAuthService.isSessionExpiringSoon(session)) {
945
+ console.log('Session expiring soon!');
946
+ }
947
+
948
+ // Get remaining time
949
+ const remaining = this.betterAuthService.getSessionTimeRemaining(session);
950
+ console.log(`Session valid for ${remaining} seconds`);
951
+ }
952
+
953
+ return user;
954
+ }
955
+
956
+ async logout(sessionToken: string) {
957
+ const success = await this.betterAuthService.revokeSession(sessionToken);
958
+ return success;
959
+ }
960
+ }
961
+ ```
962
+
963
+ ### Available Methods
964
+
965
+ | Method | Description |
966
+ | ----------------------------------- | -------------------------------------------- |
967
+ | `isEnabled()` | Check if Better-Auth is enabled |
968
+ | `getInstance()` | Get the Better-Auth instance |
969
+ | `getApi()` | Get the Better-Auth API |
970
+ | `getConfig()` | Get the current configuration |
971
+ | `isJwtEnabled()` | Check if JWT plugin is enabled |
972
+ | `isTwoFactorEnabled()` | Check if 2FA is enabled |
973
+ | `isPasskeyEnabled()` | Check if Passkey is enabled |
974
+ | `getEnabledSocialProviders()` | Get list of enabled social providers |
975
+ | `getBasePath()` | Get the base path for endpoints |
976
+ | `getBaseUrl()` | Get the base URL |
977
+ | `getSession(req)` | Get current session from request |
978
+ | `revokeSession(token)` | Revoke a session (logout) |
979
+ | `isSessionExpiringSoon(session, t?)`| Check if session is expiring soon |
980
+ | `getSessionTimeRemaining(session)` | Get remaining session time in seconds |
981
+
982
+ ## Security Integration
983
+
984
+ Better-Auth users are automatically integrated with the existing security system:
985
+
986
+ ### Role-Based Access Control
987
+
988
+ Better-Auth users work seamlessly with `@Roles()` decorators:
989
+
990
+ ```typescript
991
+ @Roles(RoleEnum.ADMIN)
992
+ @Query(() => [User])
993
+ async findAllUsers() {
994
+ // Only accessible by users with ADMIN role
995
+ }
996
+ ```
997
+
998
+ ### Special Roles
999
+
1000
+ | Role | Description |
1001
+ | ------------- | ------------------------------------- |
1002
+ | `S_EVERYONE` | Accessible by everyone (no auth req.) |
1003
+ | `S_USER` | Any authenticated user |
1004
+ | `S_VERIFIED` | Users with verified email |
1005
+ | `S_NO_ONE` | Never accessible |
1006
+
1007
+ ### How It Works
1008
+
1009
+ 1. `BetterAuthMiddleware` validates the session on each request
1010
+ 2. `BetterAuthUserMapper` maps the session user to a user with `hasRole()` capability
1011
+ 3. The mapped user is set to `req.user` for use with guards and decorators
1012
+ 4. `RolesGuard` and `@Restricted()` work as expected
1013
+
1014
+ ## User Mapping
1015
+
1016
+ The `BetterAuthUserMapper` handles the conversion between Better-Auth sessions and application users:
1017
+
1018
+ ```typescript
1019
+ import { BetterAuthUserMapper } from '@lenne.tech/nest-server';
1020
+
1021
+ @Injectable()
1022
+ export class MyService {
1023
+ constructor(private readonly userMapper: BetterAuthUserMapper) {}
1024
+
1025
+ async mapUser(sessionUser: BetterAuthSessionUser) {
1026
+ // Maps session user to application user with roles
1027
+ const user = await this.userMapper.mapSessionUser(sessionUser);
1028
+
1029
+ if (user) {
1030
+ // Check roles
1031
+ user.hasRole(RoleEnum.ADMIN); // true/false
1032
+ user.hasRole([RoleEnum.ADMIN, 'editor']); // true if any match
1033
+ }
1034
+ }
1035
+
1036
+ async linkUser(sessionUser: BetterAuthSessionUser) {
1037
+ // Links Better-Auth user with application database
1038
+ const dbUser = await this.userMapper.linkOrCreateUser(sessionUser);
1039
+ // Creates new user or links existing one via iamId
1040
+ }
1041
+ }
1042
+ ```
1043
+
1044
+ ### Mapped User Properties
1045
+
1046
+ | Property | Type | Description |
1047
+ | ----------------------------- | ---------- | ----------------------------------------------------- |
1048
+ | `id` | string | User ID (from database or Better-Auth ID as fallback) |
1049
+ | `iamId` | string | IAM provider user ID (e.g., Better-Auth) |
1050
+ | `email` | string | User email |
1051
+ | `name` | string | User display name |
1052
+ | `roles` | string[] | User roles from database |
1053
+ | `verified` | boolean | Whether user is verified |
1054
+ | `emailVerified` | boolean | Better-Auth email verification status |
1055
+ | `hasRole(roles)` | function | Check if user has any of the specified roles |
1056
+ | `_authenticatedViaBetterAuth` | true | Marker for Better-Auth authenticated users |
1057
+
1058
+ ## Parallel Operation with Legacy Auth
1059
+
1060
+ Better-Auth runs **parallel to Legacy JWT authentication** without conflicts. Both systems are fully compatible because they use the same password hashing (bcrypt) and share the same users collection.
1061
+
1062
+ ### How It Works
1063
+
1064
+ ```typescript
1065
+ // Legacy Auth - continues to work as before
1066
+ const legacyToken = await authService.signIn({ email, password });
1067
+
1068
+ // Better-Auth - works with the same users
1069
+ // POST /iam/sign-in { email, password }
1070
+
1071
+ // Both share the same users collection and bcrypt passwords
1072
+ ```
1073
+
1074
+ ### Key Points
1075
+
1076
+ 1. **Shared Users Collection**: Both systems use the same `users` MongoDB collection
1077
+ 2. **bcrypt Compatibility**: Both systems use bcrypt - passwords work with either system
1078
+ 3. **User Linking**: When a user logs in via Better-Auth, `iamId` is set to link them
1079
+ 4. **Role Preservation**: User roles work with both systems
1080
+
1081
+ ### Compatibility Matrix
1082
+
1083
+ | Scenario | Result |
1084
+ | ---------------------------------- | ---------------------------------- |
1085
+ | Legacy user → Legacy login | Works |
1086
+ | Legacy user → Better-Auth login | Works (bcrypt compatible) |
1087
+ | Better-Auth user → Better-Auth | Works |
1088
+ | Better-Auth user → Legacy login | Works (password field preserved) |
1089
+ | Social-only user → Legacy login | Fails (no password field) |
1090
+
1091
+ ### User Database Fields
1092
+
1093
+ | Field | Purpose |
1094
+ | ---------- | ------------------------------------------------- |
1095
+ | `password` | Password hash (bcrypt) - used by both systems |
1096
+ | `iamId` | IAM provider user ID (set on first Better-Auth login) |
1097
+
1098
+ **No migration is required** - users can authenticate with either system immediately.
1099
+
1100
+ ## Testing
1101
+
1102
+ The module provides a `reset()` method for testing:
1103
+
1104
+ ```typescript
1105
+ import { BetterAuthModule } from '@lenne.tech/nest-server';
1106
+
1107
+ describe('My Tests', () => {
1108
+ beforeEach(() => {
1109
+ // Reset static state between tests
1110
+ BetterAuthModule.reset();
1111
+ });
1112
+
1113
+ afterAll(() => {
1114
+ BetterAuthModule.reset();
1115
+ });
1116
+ });
1117
+ ```
1118
+
1119
+ ## Troubleshooting
1120
+
1121
+ ### Better-Auth endpoints return 404
1122
+
1123
+ - Ensure `betterAuth.enabled` is NOT explicitly set to `false`
1124
+ - Check that a valid secret is available
1125
+ - Verify MongoDB connection is established
1126
+ - Check logs for initialization errors during startup
1127
+
1128
+ ### Session not being set on requests
1129
+
1130
+ - Check that `BetterAuthMiddleware` is being applied (automatic with module)
1131
+ - Verify cookies are being sent with requests
1132
+ - Check browser developer tools for session cookies
1133
+
1134
+ ### Social login not working
1135
+
1136
+ - Verify `clientId` and `clientSecret` are correct
1137
+ - Check that redirect URLs are configured in provider settings
1138
+ - Ensure `trustedOrigins` includes your application URL
1139
+
1140
+ ### 2FA/Passkey not working
1141
+
1142
+ - Ensure the respective plugin is enabled in configuration
1143
+ - For Passkey, verify `rpId` matches your domain
1144
+ - Check browser console for WebAuthn errors
1145
+
1146
+ ## Rate Limiting
1147
+
1148
+ The Better-Auth module includes built-in rate limiting to protect against brute-force attacks.
1149
+
1150
+ ### Configuration
1151
+
1152
+ ```typescript
1153
+ const config = {
1154
+ betterAuth: {
1155
+ rateLimit: {
1156
+ enabled: true,
1157
+ max: 10,
1158
+ windowSeconds: 60,
1159
+ message: 'Too many requests, please try again later.',
1160
+ strictEndpoints: ['/sign-in', '/sign-up', '/forgot-password', '/reset-password'],
1161
+ skipEndpoints: ['/session', '/callback'],
1162
+ },
1163
+ },
1164
+ };
1165
+ ```
1166
+
1167
+ ### Configuration Options
1168
+
1169
+ | Option | Type | Default | Description |
1170
+ | ----------------- | -------- | -------------------------- | ------------------------------------- |
1171
+ | `enabled` | boolean | `false` | Enable/disable rate limiting |
1172
+ | `max` | number | `10` | Maximum requests per time window |
1173
+ | `windowSeconds` | number | `60` | Time window in seconds |
1174
+ | `message` | string | `'Too many requests...'` | Error message when limit exceeded |
1175
+ | `strictEndpoints` | string[] | See below | Endpoints with half the normal limit |
1176
+ | `skipEndpoints` | string[] | See below | Endpoints that skip rate limiting |
1177
+
1178
+ ### Default Strict Endpoints
1179
+
1180
+ Strict endpoints receive half the configured `max` limit to provide extra protection:
1181
+
1182
+ - `/sign-in` - Login attempts
1183
+ - `/sign-up` - Registration attempts
1184
+ - `/forgot-password` - Password reset requests
1185
+ - `/reset-password` - Password reset submissions
1186
+
1187
+ ### Default Skip Endpoints
1188
+
1189
+ These endpoints bypass rate limiting entirely:
1190
+
1191
+ - `/session` - Session checks (frequent client-side calls)
1192
+ - `/callback` - OAuth callbacks
1193
+
1194
+ ### Response Headers
1195
+
1196
+ When rate limiting is enabled, the following headers are added to responses:
1197
+
1198
+ | Header | Description |
1199
+ | ---------------------- | ---------------------------------------- |
1200
+ | `X-RateLimit-Limit` | Maximum requests allowed in the window |
1201
+ | `X-RateLimit-Remaining`| Remaining requests in current window |
1202
+ | `X-RateLimit-Reset` | Seconds until the rate limit resets |
1203
+ | `Retry-After` | (429 only) Seconds to wait before retry |
1204
+
1205
+ ### Rate Limit Exceeded Response
1206
+
1207
+ When the rate limit is exceeded, a `429 Too Many Requests` response is returned:
1208
+
1209
+ ```json
1210
+ {
1211
+ "statusCode": 429,
1212
+ "error": "Too Many Requests",
1213
+ "message": "Too many requests, please try again later.",
1214
+ "retryAfter": 45
1215
+ }
1216
+ ```
1217
+
1218
+ ### Using BetterAuthRateLimiter Programmatically
1219
+
1220
+ ```typescript
1221
+ import { BetterAuthRateLimiter } from '@lenne.tech/nest-server';
1222
+
1223
+ @Injectable()
1224
+ export class MyService {
1225
+ constructor(private readonly rateLimiter: BetterAuthRateLimiter) {}
1226
+
1227
+ checkCustomLimit(ip: string) {
1228
+ // Check rate limit for custom endpoint
1229
+ const result = this.rateLimiter.check(ip, '/custom-endpoint');
1230
+
1231
+ if (!result.allowed) {
1232
+ console.log(`Rate limit exceeded. Retry in ${result.resetIn} seconds`);
1233
+ }
1234
+ }
1235
+
1236
+ resetUserLimit(ip: string) {
1237
+ // Reset rate limit for specific IP (e.g., after successful captcha)
1238
+ this.rateLimiter.reset(ip);
1239
+ }
1240
+
1241
+ getStats() {
1242
+ // Get rate limiter statistics
1243
+ return this.rateLimiter.getStats();
1244
+ }
1245
+ }
1246
+ ```
1247
+
1248
+ ### Production Recommendations
1249
+
1250
+ For production environments, consider:
1251
+
1252
+ 1. **Enable rate limiting** - Always enable in production
1253
+ 2. **Lower limits** - Use stricter limits (e.g., `max: 5`) for production
1254
+ 3. **Environment variables** - Configure via environment:
1255
+
1256
+ ```typescript
1257
+ const config = {
1258
+ rateLimit: {
1259
+ enabled: process.env.RATE_LIMIT_ENABLED !== 'false',
1260
+ max: parseInt(process.env.RATE_LIMIT_MAX || '10', 10),
1261
+ windowSeconds: parseInt(process.env.RATE_LIMIT_WINDOW_SECONDS || '60', 10),
1262
+ },
1263
+ };
1264
+ ```
1265
+
1266
+ 4. **Monitor rate limit events** - The service logs warnings when limits are exceeded
1267
+ 5. **Consider Redis** - For multi-instance deployments, implement Redis-based rate limiting
1268
+
1269
+ ## Extending Better-Auth (Custom Resolver)
1270
+
1271
+ Better-Auth uses the same extension pattern as Legacy Auth. You can extend `CoreBetterAuthResolver` to add custom logic before/after authentication operations.
1272
+
1273
+ ### Creating a Custom Resolver
1274
+
1275
+ ```typescript
1276
+ // src/server/modules/better-auth/better-auth.resolver.ts
1277
+ import { Resolver } from '@nestjs/graphql';
1278
+ import { Roles } from '@lenne.tech/nest-server';
1279
+ import {
1280
+ BetterAuthAuthModel,
1281
+ BetterAuthService,
1282
+ BetterAuthUserMapper,
1283
+ CoreBetterAuthResolver,
1284
+ RoleEnum,
1285
+ } from '@lenne.tech/nest-server';
1286
+ import { EmailService } from '../email/email.service';
1287
+
1288
+ @Resolver(() => BetterAuthAuthModel)
1289
+ @Roles(RoleEnum.ADMIN)
1290
+ export class BetterAuthResolver extends CoreBetterAuthResolver {
1291
+ constructor(
1292
+ betterAuthService: BetterAuthService,
1293
+ userMapper: BetterAuthUserMapper,
1294
+ private readonly emailService: EmailService,
1295
+ ) {
1296
+ super(betterAuthService, userMapper);
1297
+ }
1298
+
1299
+ /**
1300
+ * Override signUp to send welcome email after registration
1301
+ */
1302
+ override async betterAuthSignUp(
1303
+ email: string,
1304
+ password: string,
1305
+ name?: string,
1306
+ ): Promise<BetterAuthAuthModel> {
1307
+ // Call original implementation
1308
+ const result = await super.betterAuthSignUp(email, password, name);
1309
+
1310
+ // Add custom logic after successful sign-up
1311
+ if (result.success && result.user) {
1312
+ await this.emailService.sendWelcomeEmail(result.user.email, result.user.name);
1313
+ await this.analyticsService.trackSignUp(result.user.id);
1314
+ }
1315
+
1316
+ return result;
1317
+ }
1318
+
1319
+ /**
1320
+ * Override signIn to add custom tracking
1321
+ */
1322
+ override async betterAuthSignIn(
1323
+ email: string,
1324
+ password: string,
1325
+ ctx: { req: Request; res: Response },
1326
+ ): Promise<BetterAuthAuthModel> {
1327
+ // Add pre-login logic
1328
+ this.logger.log(`Login attempt for ${email}`);
1329
+
1330
+ const result = await super.betterAuthSignIn(email, password, ctx);
1331
+
1332
+ // Add post-login logic
1333
+ if (result.success && result.user) {
1334
+ await this.analyticsService.trackLogin(result.user.id);
1335
+ }
1336
+
1337
+ return result;
1338
+ }
1339
+ }
1340
+ ```
1341
+
1342
+ ### Registering the Custom Resolver
1343
+
1344
+ **Option 1: Via BetterAuthModule options**
1345
+
1346
+ ```typescript
1347
+ // src/server/server.module.ts
1348
+ import { BetterAuthModule } from '@lenne.tech/nest-server';
1349
+ import { BetterAuthResolver } from './modules/better-auth/better-auth.resolver';
1350
+
1351
+ @Module({
1352
+ imports: [
1353
+ CoreModule.forRoot(environment),
1354
+ BetterAuthModule.forRoot({
1355
+ config: environment.betterAuth,
1356
+ resolver: BetterAuthResolver, // Your custom resolver
1357
+ }),
1358
+ ],
1359
+ })
1360
+ export class ServerModule {}
1361
+ ```
1362
+
1363
+ **Option 2: Create your own module (like Legacy Auth)**
1364
+
1365
+ ```typescript
1366
+ // src/server/modules/better-auth/better-auth.module.ts
1367
+ import { Module } from '@nestjs/common';
1368
+ import { BetterAuthModule as CoreBetterAuthModule } from '@lenne.tech/nest-server';
1369
+ import { BetterAuthResolver } from './better-auth.resolver';
1370
+ import { EmailModule } from '../email/email.module';
1371
+
1372
+ @Module({
1373
+ imports: [
1374
+ CoreBetterAuthModule.forRoot({
1375
+ config: environment.betterAuth,
1376
+ resolver: BetterAuthResolver,
1377
+ }),
1378
+ EmailModule,
1379
+ ],
1380
+ providers: [BetterAuthResolver],
1381
+ exports: [BetterAuthResolver],
1382
+ })
1383
+ export class BetterAuthModule {}
1384
+ ```
1385
+
1386
+ ### Available Override Methods
1387
+
1388
+ All methods in `CoreBetterAuthResolver` can be overridden:
1389
+
1390
+ | Method | Description |
1391
+ | ------ | ----------- |
1392
+ | `betterAuthSignIn(email, password, ctx)` | Sign in with email/password |
1393
+ | `betterAuthSignUp(email, password, name?)` | Register new user |
1394
+ | `betterAuthSignOut(ctx)` | Sign out current session |
1395
+ | `betterAuthVerify2FA(code, ctx)` | Verify 2FA code |
1396
+ | `betterAuthEnable2FA(password, ctx)` | Enable 2FA for user |
1397
+ | `betterAuthDisable2FA(password, ctx)` | Disable 2FA for user |
1398
+ | `betterAuthGenerateBackupCodes(ctx)` | Generate new backup codes |
1399
+ | `betterAuthGetPasskeyChallenge(ctx)` | Get WebAuthn challenge |
1400
+ | `betterAuthListPasskeys(ctx)` | List user's passkeys |
1401
+ | `betterAuthDeletePasskey(passkeyId, ctx)` | Delete a passkey |
1402
+ | `betterAuthSession(ctx)` | Get current session |
1403
+ | `betterAuthEnabled()` | Check if Better-Auth is enabled |
1404
+ | `betterAuthFeatures()` | Get enabled features |
1405
+
1406
+ ### Helper Methods (Protected)
1407
+
1408
+ These protected methods are available for use in your custom resolver:
1409
+
1410
+ ```typescript
1411
+ // Check if Better-Auth is enabled (throws if not)
1412
+ this.ensureEnabled();
1413
+
1414
+ // Convert Express headers to Web API Headers
1415
+ const headers = this.convertHeaders(ctx.req.headers);
1416
+
1417
+ // Map session info
1418
+ const sessionInfo = this.mapSessionInfo(response.session);
1419
+
1420
+ // Map user to model
1421
+ const userModel = this.mapToUserModel(mappedUser);
1422
+ ```