@lenne.tech/nest-server 11.7.0 → 11.7.1

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 (89) 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 +17 -0
  4. package/dist/core/modules/auth/core-auth.controller.d.ts +1 -0
  5. package/dist/core/modules/auth/core-auth.controller.js +28 -2
  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 +20 -2
  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-user.mapper.d.ts +33 -0
  32. package/dist/core/modules/better-auth/better-auth-user.mapper.js +443 -0
  33. package/dist/core/modules/better-auth/better-auth-user.mapper.js.map +1 -1
  34. package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +1 -0
  35. package/dist/core/modules/better-auth/core-better-auth.controller.js +15 -2
  36. package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
  37. package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +2 -0
  38. package/dist/core/modules/better-auth/core-better-auth.resolver.js +14 -0
  39. package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
  40. package/dist/core/modules/better-auth/index.d.ts +1 -0
  41. package/dist/core/modules/better-auth/index.js +1 -0
  42. package/dist/core/modules/better-auth/index.js.map +1 -1
  43. package/dist/core/modules/user/core-user.service.d.ts +7 -1
  44. package/dist/core/modules/user/core-user.service.js +57 -3
  45. package/dist/core/modules/user/core-user.service.js.map +1 -1
  46. package/dist/core/modules/user/interfaces/core-user-service-options.interface.d.ts +4 -0
  47. package/dist/core/modules/user/interfaces/core-user-service-options.interface.js +3 -0
  48. package/dist/core/modules/user/interfaces/core-user-service-options.interface.js.map +1 -0
  49. package/dist/core.module.d.ts +3 -0
  50. package/dist/core.module.js +133 -55
  51. package/dist/core.module.js.map +1 -1
  52. package/dist/index.d.ts +5 -0
  53. package/dist/index.js +5 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/server/modules/auth/auth.resolver.js +2 -0
  56. package/dist/server/modules/auth/auth.resolver.js.map +1 -1
  57. package/dist/server/modules/better-auth/better-auth.resolver.d.ts +2 -0
  58. package/dist/server/modules/better-auth/better-auth.resolver.js +13 -0
  59. package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -1
  60. package/dist/server/modules/user/user.service.d.ts +3 -1
  61. package/dist/server/modules/user/user.service.js +7 -3
  62. package/dist/server/modules/user/user.service.js.map +1 -1
  63. package/dist/tsconfig.build.tsbuildinfo +1 -1
  64. package/package.json +1 -1
  65. package/src/config.env.ts +32 -2
  66. package/src/core/common/interfaces/server-options.interface.ts +175 -0
  67. package/src/core/modules/auth/core-auth.controller.ts +93 -5
  68. package/src/core/modules/auth/core-auth.module.ts +15 -1
  69. package/src/core/modules/auth/core-auth.resolver.ts +70 -2
  70. package/src/core/modules/auth/exceptions/legacy-auth-disabled.exception.ts +35 -0
  71. package/src/core/modules/auth/guards/legacy-auth-rate-limit.guard.ts +109 -0
  72. package/src/core/modules/auth/interfaces/auth-provider.interface.ts +86 -0
  73. package/src/core/modules/auth/interfaces/core-auth-user.interface.ts +6 -0
  74. package/src/core/modules/auth/services/core-auth.service.ts +245 -6
  75. package/src/core/modules/auth/services/legacy-auth-rate-limiter.service.ts +283 -0
  76. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +254 -0
  77. package/src/core/modules/better-auth/README.md +487 -169
  78. package/src/core/modules/better-auth/better-auth-migration-status.model.ts +73 -0
  79. package/src/core/modules/better-auth/better-auth-user.mapper.ts +805 -0
  80. package/src/core/modules/better-auth/core-better-auth.controller.ts +44 -3
  81. package/src/core/modules/better-auth/core-better-auth.resolver.ts +25 -0
  82. package/src/core/modules/better-auth/index.ts +1 -0
  83. package/src/core/modules/user/core-user.service.ts +131 -4
  84. package/src/core/modules/user/interfaces/core-user-service-options.interface.ts +15 -0
  85. package/src/core.module.ts +258 -76
  86. package/src/index.ts +5 -0
  87. package/src/server/modules/auth/auth.resolver.ts +8 -0
  88. package/src/server/modules/better-auth/better-auth.resolver.ts +9 -0
  89. package/src/server/modules/user/user.service.ts +4 -2
@@ -2,6 +2,38 @@
2
2
 
3
3
  Integration of the [better-auth](https://better-auth.com) authentication framework with @lenne.tech/nest-server.
4
4
 
5
+ ## TL;DR
6
+
7
+ ```typescript
8
+ // 1. Follow INTEGRATION-CHECKLIST.md to create required files
9
+ // 2. Add to server.module.ts:
10
+ CoreModule.forRoot(envConfig), // IAM-only (new projects)
11
+ BetterAuthModule.forRoot({ config: envConfig.betterAuth, fallbackSecrets: [envConfig.jwt?.secret] }),
12
+
13
+ // 3. Configure in config.env.ts:
14
+ betterAuth: { jwt: {}, twoFactor: {}, passkey: {} }
15
+ ```
16
+
17
+ **Quick Links:** [Integration Checklist](./INTEGRATION-CHECKLIST.md) | [REST API](#rest-api-endpoints) | [GraphQL API](#graphql-api) | [Configuration](#configuration)
18
+
19
+ ---
20
+
21
+ ## Table of Contents
22
+
23
+ - [Features](#features)
24
+ - [Quick Integration](#quick-integration-for-claude-code--ai-assistants)
25
+ - [Project Integration Guide](#project-integration-guide-required-steps)
26
+ - [Configuration](#configuration)
27
+ - [REST API Endpoints](#rest-api-endpoints)
28
+ - [GraphQL API](#graphql-api)
29
+ - [CoreModule Signatures](#coremoduleforroot-signatures)
30
+ - [Migration Roadmap](#migration-roadmap-legacy-auth--betterauth)
31
+ - [Password Synchronization](#bidirectional-password-synchronization)
32
+ - [Security Integration](#security-integration)
33
+ - [Troubleshooting](#troubleshooting)
34
+
35
+ ---
36
+
5
37
  ## Features
6
38
 
7
39
  ### Built-in Plugins (Explicit Configuration)
@@ -15,6 +47,7 @@ Integration of the [better-auth](https://better-auth.com) authentication framewo
15
47
  - **Email/Password Authentication** - Standard username/password login
16
48
  - **Social Login** - Any OAuth provider (Google, GitHub, Apple, Discord, etc.)
17
49
  - **Parallel Operation** - Runs alongside Legacy Auth without side effects
50
+ - **Bidirectional Sync** - Users can sign in via Legacy Auth or Better-Auth interchangeably
18
51
 
19
52
  ### Extensible via Plugins
20
53
 
@@ -23,54 +56,99 @@ Integration of the [better-auth](https://better-auth.com) authentication framewo
23
56
  - **SSO** - Single Sign-On (OIDC, SAML)
24
57
  - **And many more** - See [Plugins and Extensions](#plugins-and-extensions)
25
58
 
26
- ## Quick Start
59
+ ---
27
60
 
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.
61
+ ## Quick Integration (for Claude Code / AI Assistants)
29
62
 
30
- ### 1. Create Extended Module (Recommended)
63
+ **Use [INTEGRATION-CHECKLIST.md](./INTEGRATION-CHECKLIST.md)** for a concise checklist with references to the actual implementation files.
31
64
 
32
- ```typescript
33
- // src/server/modules/better-auth/better-auth.module.ts
34
- import { BetterAuthModule as CoreBetterAuthModule } from '@lenne.tech/nest-server';
65
+ ---
35
66
 
36
- @Module({})
37
- export class BetterAuthModule {
38
- static forRoot(options) {
39
- return {
40
- module: BetterAuthModule,
41
- imports: [CoreBetterAuthModule.forRoot(options)],
42
- };
43
- }
44
- }
67
+ ## Reference Implementation
68
+
69
+ A complete working implementation exists in this package:
70
+
71
+ **Local (in your node_modules):**
72
+ ```
73
+ node_modules/@lenne.tech/nest-server/src/server/modules/better-auth/
45
74
  ```
46
75
 
47
- ### 2. Import in ServerModule
76
+ **GitHub:**
77
+ https://github.com/lenneTech/nest-server/tree/develop/src/server/modules/better-auth
48
78
 
49
- ```typescript
50
- // src/server/server.module.ts
51
- import { BetterAuthModule } from './modules/better-auth/better-auth.module';
79
+ **UserService integration:**
80
+ - Local: `node_modules/@lenne.tech/nest-server/src/server/modules/user/user.service.ts`
81
+ - GitHub: https://github.com/lenneTech/nest-server/blob/develop/src/server/modules/user/user.service.ts
52
82
 
53
- @Module({
54
- imports: [
55
- CoreModule.forRoot(environment),
56
- BetterAuthModule.forRoot({ config: environment.betterAuth }),
57
- ],
58
- })
59
- export class ServerModule {}
60
- ```
83
+ ---
84
+
85
+ ## Project Integration Guide (Required Steps)
86
+
87
+ ### Step 1: Create BetterAuth Module
88
+ **Create:** `src/server/modules/better-auth/better-auth.module.ts`
89
+ **Copy from:** Reference implementation (see above)
90
+
91
+ ### Step 2: Create BetterAuth Resolver (CRITICAL!)
92
+ **Create:** `src/server/modules/better-auth/better-auth.resolver.ts`
93
+ **Copy from:** Reference implementation
94
+
95
+ **WHY must ALL decorators be re-declared?**
96
+ GraphQL schema is built from decorators at compile time. The parent class (`CoreBetterAuthResolver`) is marked as `isAbstract: true`, so its methods are not registered in the schema. You MUST re-declare `@Query`, `@Mutation`, `@Roles`, `@UseGuards` decorators in the child class for the methods to appear in the GraphQL schema.
97
+
98
+ ### Step 3: Create BetterAuth Controller
99
+ **Create:** `src/server/modules/better-auth/better-auth.controller.ts`
100
+ **Copy from:** Reference implementation
101
+
102
+ ### Step 4: Inject BetterAuthUserMapper in UserService (CRITICAL!)
103
+ **Modify:** `src/server/modules/user/user.service.ts`
104
+ **Reference:** See UserService in reference implementation
105
+
106
+ **Required changes:**
107
+
108
+ 1. Add import:
109
+ ```typescript
110
+ import { BetterAuthUserMapper } from '@lenne.tech/nest-server';
111
+ ```
112
+
113
+ 2. Add constructor parameter:
114
+ ```typescript
115
+ @Optional() private readonly betterAuthUserMapper?: BetterAuthUserMapper,
116
+ ```
117
+
118
+ 3. Pass to super() via options object:
119
+ ```typescript
120
+ super(configService, emailService, mainDbModel, mainModelConstructor, { betterAuthUserMapper });
121
+ ```
122
+
123
+ **Why is this critical?**
124
+ The `BetterAuthUserMapper` enables bidirectional password synchronization:
125
+ - User signs up via BetterAuth → password synced to Legacy Auth (bcrypt hash)
126
+ - User changes password → synced between both systems
127
+ - **Without this, users can only authenticate via ONE system!**
128
+
129
+ ### Step 5: Import in ServerModule
130
+ **Modify:** `src/server/server.module.ts`
131
+ **Reference:** See ServerModule in reference implementation
61
132
 
62
- ### 3. Configure (Optional)
133
+ Add import and include BetterAuthModule in imports array with `fallbackSecrets`:
63
134
 
64
135
  ```typescript
65
- // config.env.ts - customize behavior (optional):
66
- const config = {
67
- betterAuth: {
68
- // baseUrl: 'https://your-domain.com',
69
- // basePath: '/iam',
70
- },
71
- };
136
+ BetterAuthModule.forRoot({
137
+ config: envConfig.betterAuth,
138
+ fallbackSecrets: [envConfig.jwt?.secret, envConfig.jwt?.refresh?.secret],
139
+ }),
72
140
  ```
73
141
 
142
+ ### Step 6: Configure in config.env.ts
143
+ **Modify:** `src/config.env.ts`
144
+ **Reference:** See config.env.ts in reference implementation
145
+
146
+ Add `betterAuth` configuration block. See reference for all available options including `jwt`, `passkey`, `twoFactor`, and `socialProviders`.
147
+
148
+ ---
149
+
150
+ ## Quick Reference
151
+
74
152
  **Default values (used when not configured):**
75
153
 
76
154
  - **Secret**: Falls back to `jwt.secret` → `jwt.refresh.secret` → auto-generated
@@ -92,6 +170,8 @@ const config = {
92
170
 
93
171
  Read the security section below for production deployments.
94
172
 
173
+ ---
174
+
95
175
  ## Understanding Core Settings
96
176
 
97
177
  ### Base URL (`baseUrl`)
@@ -615,39 +695,22 @@ const config = {
615
695
 
616
696
  ## Module Setup
617
697
 
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
698
+ See the [Project Integration Guide](#project-integration-guide-required-steps) at the top of this document for complete step-by-step instructions.
621
699
 
622
- Projects typically integrate Better-Auth via an extended module:
700
+ ### Summary
623
701
 
624
- ```typescript
625
- // src/server/modules/better-auth/better-auth.module.ts
626
- import { BetterAuthModule as CoreBetterAuthModule } from '@lenne.tech/nest-server';
702
+ 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.
627
703
 
628
- @Module({})
629
- export class BetterAuthModule {
630
- static forRoot(options): DynamicModule {
631
- return {
632
- module: BetterAuthModule,
633
- imports: [CoreBetterAuthModule.forRoot(options)],
634
- };
635
- }
636
- }
704
+ **Required components:**
637
705
 
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
- ```
706
+ 1. `BetterAuthModule` - Wraps CoreBetterAuthModule with custom resolver/controller
707
+ 2. `BetterAuthResolver` - Extends CoreBetterAuthResolver for GraphQL operations
708
+ 3. `BetterAuthController` - Extends CoreBetterAuthController for REST endpoints
709
+ 4. `UserService` - Inject `BetterAuthUserMapper` for bidirectional auth sync
647
710
 
648
711
  ### Simple: Auto-Registration
649
712
 
650
- For simple projects without customization needs:
713
+ For simple projects without customization needs (not recommended for production):
651
714
 
652
715
  ```typescript
653
716
  // In config.env.ts
@@ -1055,9 +1118,132 @@ export class MyService {
1055
1118
  | `hasRole(roles)` | function | Check if user has any of the specified roles |
1056
1119
  | `_authenticatedViaBetterAuth` | true | Marker for Better-Auth authenticated users |
1057
1120
 
1121
+ ## CoreModule.forRoot() Signatures
1122
+
1123
+ Two signatures are available for different use cases:
1124
+
1125
+ ### IAM-Only Signature (Recommended for New Projects)
1126
+
1127
+ ```typescript
1128
+ // server.module.ts
1129
+ @Module({
1130
+ imports: [
1131
+ CoreModule.forRoot(envConfig),
1132
+ BetterAuthModule.forRoot({
1133
+ config: envConfig.betterAuth,
1134
+ fallbackSecrets: [envConfig.jwt?.secret],
1135
+ }),
1136
+ ],
1137
+ })
1138
+ export class ServerModule {}
1139
+ ```
1140
+
1141
+ **Features:**
1142
+ - Simplified setup - no Legacy Auth overhead
1143
+ - GraphQL Subscription authentication via BetterAuth sessions
1144
+ - BetterAuthModule is auto-registered when using this signature
1145
+
1146
+ **Requirements:**
1147
+ - Create BetterAuthModule, Resolver, and Controller in your project
1148
+ - Inject BetterAuthUserMapper in UserService
1149
+ - Set `auth.legacyEndpoints.enabled: false` in config
1150
+
1151
+ ### Legacy + IAM Signature (For Existing Projects)
1152
+
1153
+ ```typescript
1154
+ // server.module.ts
1155
+ @Module({
1156
+ imports: [
1157
+ CoreModule.forRoot(AuthService, AuthModule.forRoot(envConfig.jwt), envConfig),
1158
+ BetterAuthModule.forRoot({
1159
+ config: envConfig.betterAuth,
1160
+ fallbackSecrets: [envConfig.jwt?.secret],
1161
+ }),
1162
+ ],
1163
+ })
1164
+ export class ServerModule {}
1165
+ ```
1166
+
1167
+ > **@deprecated** This 3-parameter signature is deprecated for new projects.
1168
+ > Use the single-parameter signature for new projects.
1169
+
1170
+ **Features:**
1171
+ - Both Legacy Auth and BetterAuth run in parallel
1172
+ - Bidirectional password synchronization
1173
+ - Gradual user migration to IAM
1174
+
1175
+ ---
1176
+
1177
+ ## Migration Roadmap (Legacy Auth → BetterAuth)
1178
+
1179
+ Better-Auth is designed as the successor to Legacy Auth. This section describes the migration path.
1180
+
1181
+ ### Scenario Overview
1182
+
1183
+ | Scenario | Signature | Description |
1184
+ |----------|-----------|-------------|
1185
+ | **1. Legacy Only** | 3-parameter | Existing projects, no IAM integration |
1186
+ | **2. Migration** | 3-parameter | Legacy + IAM parallel operation |
1187
+ | **3. IAM Only** | 1-parameter | New projects, BetterAuth only |
1188
+
1189
+ ### Migration Steps (Scenario 2)
1190
+
1191
+ 1. **Enable BetterAuth**
1192
+ - Follow the [Project Integration Guide](#project-integration-guide-required-steps)
1193
+ - Both systems run in parallel
1194
+
1195
+ 2. **Monitor Migration Progress**
1196
+ ```graphql
1197
+ query {
1198
+ betterAuthMigrationStatus {
1199
+ totalUsers
1200
+ fullyMigratedUsers
1201
+ pendingMigrationUsers
1202
+ migrationPercentage
1203
+ canDisableLegacyAuth
1204
+ }
1205
+ }
1206
+ ```
1207
+ Users migrate automatically when signing in via BetterAuth (IAM).
1208
+
1209
+ 3. **Disable Legacy Endpoints** (when `canDisableLegacyAuth: true`)
1210
+ ```typescript
1211
+ // config.env.ts
1212
+ auth: {
1213
+ legacyEndpoints: {
1214
+ enabled: false // Disables signIn, signUp, logout, refreshToken
1215
+ }
1216
+ }
1217
+ ```
1218
+
1219
+ 4. **Switch to IAM-Only Signature** (optional)
1220
+ ```typescript
1221
+ // Before (deprecated)
1222
+ CoreModule.forRoot(AuthService, AuthModule.forRoot(envConfig.jwt), envConfig)
1223
+
1224
+ // After (recommended)
1225
+ CoreModule.forRoot(envConfig)
1226
+ ```
1227
+
1228
+ ### Why No Automatic Migration Script?
1229
+
1230
+ | System | Password Hash Algorithm |
1231
+ |--------|------------------------|
1232
+ | Legacy Auth | `bcrypt(sha256(password))` |
1233
+ | BetterAuth | `scrypt(sha256(password))` |
1234
+
1235
+ These are **one-way hashes** - there's no way to convert between them without the plain password.
1236
+ Users must sign in at least once via BetterAuth to create a compatible hash.
1237
+
1238
+ ### Detailed Documentation
1239
+
1240
+ See `.claude/rules/module-deprecation.md` for complete migration documentation.
1241
+
1242
+ ---
1243
+
1058
1244
  ## Parallel Operation with Legacy Auth
1059
1245
 
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.
1246
+ Better-Auth runs **parallel to Legacy JWT authentication** without conflicts. Both systems are fully compatible because they share the same users collection.
1061
1247
 
1062
1248
  ### How It Works
1063
1249
 
@@ -1097,6 +1283,224 @@ const legacyToken = await authService.signIn({ email, password });
1097
1283
 
1098
1284
  **No migration is required** - users can authenticate with either system immediately.
1099
1285
 
1286
+ ## Bidirectional Password Synchronization
1287
+
1288
+ ### Overview
1289
+
1290
+ When both Legacy Auth and BetterAuth (IAM) are active, passwords are automatically synchronized between the systems. This ensures users can sign in via either system after any password change or reset.
1291
+
1292
+ ### How Password Sync Works
1293
+
1294
+ | Scenario | Source | Target | Auto-Synced? |
1295
+ |----------|--------|--------|--------------|
1296
+ | Sign up via BetterAuth | IAM | Legacy | ✅ Yes |
1297
+ | Sign up via Legacy Auth | Legacy | IAM | ⚠️ On first IAM sign-in |
1298
+ | Password reset via Legacy | Legacy | IAM | ✅ Yes |
1299
+ | Password reset via BetterAuth | IAM | Legacy | ⚠️ See below |
1300
+ | Password change via user update | Legacy | IAM | ✅ Yes |
1301
+
1302
+ #### IAM Password Reset → Legacy Sync
1303
+
1304
+ When a user resets their password via BetterAuth's native `/iam/reset-password` endpoint, the password is hashed with scrypt before storage. Since we don't have access to the plain password after hashing, we **cannot** automatically sync to Legacy Auth.
1305
+
1306
+ **Solutions:**
1307
+
1308
+ 1. **Recommended: Custom Password Reset Flow**
1309
+ Override the password reset to capture the plain password for sync. See [Custom Password Reset with Sync](#custom-password-reset-with-sync).
1310
+
1311
+ 2. **Use Legacy Password Reset Only**
1312
+ Direct users to the Legacy Auth password reset flow, which syncs to IAM automatically.
1313
+
1314
+ 3. **Re-authenticate After Reset**
1315
+ After IAM password reset, users can sign in via IAM. On next Legacy sign-in attempt, they'll need to reset via Legacy too.
1316
+
1317
+ ### Automatic Sync (No Configuration Required)
1318
+
1319
+ The following sync operations happen automatically when `BetterAuthUserMapper` is injected in `UserService`:
1320
+
1321
+ #### 1. IAM Sign-Up → Legacy
1322
+ When a user signs up via BetterAuth (`/iam/sign-up/email`), the password is hashed with bcrypt and stored in `users.password`, enabling Legacy Auth sign-in.
1323
+
1324
+ #### 2. Legacy Password Reset → IAM
1325
+ When a user resets their password via `CoreUserService.resetPassword()`, the new password is synced to the BetterAuth `account` collection (if the user has a credential account).
1326
+
1327
+ #### 3. Legacy Password Update → IAM
1328
+ When a user changes their password via `UserService.update()`, the new password is synced to BetterAuth (if the user has a credential account).
1329
+
1330
+ #### 4. Legacy → IAM Migration (On First Sign-In)
1331
+ When a legacy user signs in via BetterAuth for the first time, their account is migrated:
1332
+ - Their `iamId` is set
1333
+ - A credential account is created in the `account` collection with the scrypt hash
1334
+
1335
+ ### BetterAuth Password Reset Configuration
1336
+
1337
+ BetterAuth provides native password reset via `/iam/forgot-password` and `/iam/reset-password` endpoints. To enable this, configure the `sendResetPassword` callback:
1338
+
1339
+ ```typescript
1340
+ // config.env.ts
1341
+ betterAuth: {
1342
+ options: {
1343
+ emailAndPassword: {
1344
+ sendResetPassword: async ({ user, url, token }) => {
1345
+ // Send password reset email
1346
+ // 'url' contains the full reset URL with token
1347
+ await emailService.sendEmail({
1348
+ to: user.email,
1349
+ subject: 'Reset Your Password',
1350
+ html: `<a href="${url}">Click here to reset your password</a>`,
1351
+ });
1352
+ },
1353
+ },
1354
+ },
1355
+ },
1356
+ ```
1357
+
1358
+ #### Password Reset Flow (BetterAuth)
1359
+
1360
+ 1. User requests reset: `POST /iam/forgot-password` with `{ email }`
1361
+ 2. BetterAuth generates token and calls `sendResetPassword` callback
1362
+ 3. User clicks link in email → navigates to reset page
1363
+ 4. Frontend submits: `POST /iam/reset-password` with `{ token, newPassword }`
1364
+ 5. BetterAuth updates password in `account` collection
1365
+ 6. Password is automatically synced to Legacy Auth (`users.password`)
1366
+
1367
+ ### Password Hashing Algorithms
1368
+
1369
+ | System | Algorithm | Format |
1370
+ |--------|-----------|--------|
1371
+ | Legacy Auth | bcrypt(sha256(password)) | `$2b$10$...` (60 chars) |
1372
+ | BetterAuth (IAM) | scrypt | `salt:hash` (hex encoded) |
1373
+
1374
+ **Important:** These hashes are NOT interchangeable. Password sync requires re-hashing the plain password with the target algorithm.
1375
+
1376
+ ### Email Change Synchronization
1377
+
1378
+ Email changes are also synchronized bidirectionally:
1379
+
1380
+ | Scenario | Effect |
1381
+ |----------|--------|
1382
+ | Email changed via Legacy (`UserService.update()`) | IAM sessions invalidated (forces re-auth) |
1383
+ | Email changed via IAM | Legacy refresh tokens cleared |
1384
+
1385
+ ### User Deletion Cleanup
1386
+
1387
+ When a user is deleted:
1388
+
1389
+ | Via | Effect |
1390
+ |-----|--------|
1391
+ | Legacy (`UserService.delete()`) | IAM accounts and sessions are cleaned up |
1392
+ | IAM | Legacy user record is removed |
1393
+
1394
+ ### Troubleshooting Password Sync
1395
+
1396
+ #### Password not syncing to IAM
1397
+
1398
+ 1. Verify `BetterAuthUserMapper` is injected in `UserService`:
1399
+ ```typescript
1400
+ constructor(
1401
+ @Optional() private readonly betterAuthUserMapper?: BetterAuthUserMapper,
1402
+ ) {
1403
+ super(..., { betterAuthUserMapper });
1404
+ }
1405
+ ```
1406
+
1407
+ 2. Check if user has an IAM credential account:
1408
+ ```javascript
1409
+ // MongoDB query
1410
+ db.account.findOne({ userId: ObjectId("..."), providerId: "credential" })
1411
+ ```
1412
+
1413
+ 3. Check server logs for sync warnings:
1414
+ ```
1415
+ [CoreUserService] Failed to sync password to IAM...
1416
+ ```
1417
+
1418
+ #### Password reset email not sending
1419
+
1420
+ 1. Configure `sendResetPassword` callback in `betterAuth.options`
1421
+ 2. Check that your email service is working
1422
+ 3. Verify the callback receives `user`, `url`, and `token` parameters
1423
+
1424
+ #### Legacy user can't sign in via IAM
1425
+
1426
+ The user needs to sign in via Legacy Auth first with their password. This triggers the automatic migration on first IAM sign-in attempt.
1427
+
1428
+ Alternatively, use `BetterAuthUserMapper.migrateAccountToIam()` to migrate the user programmatically:
1429
+
1430
+ ```typescript
1431
+ await betterAuthUserMapper.migrateAccountToIam(email, plainPassword);
1432
+ ```
1433
+
1434
+ ### Custom Password Reset with Sync
1435
+
1436
+ To enable bidirectional password reset sync, implement a custom password reset endpoint that captures the plain password and syncs to both systems:
1437
+
1438
+ ```typescript
1439
+ // src/server/modules/better-auth/better-auth.controller.ts
1440
+ import { Body, Controller, Post } from '@nestjs/common';
1441
+ import { CoreBetterAuthController, BetterAuthUserMapper, Roles, RoleEnum } from '@lenne.tech/nest-server';
1442
+
1443
+ @Controller('iam')
1444
+ export class BetterAuthController extends CoreBetterAuthController {
1445
+ constructor(
1446
+ // ... other dependencies
1447
+ private readonly betterAuthUserMapper: BetterAuthUserMapper,
1448
+ ) {
1449
+ super(...);
1450
+ }
1451
+
1452
+ /**
1453
+ * Custom password reset that syncs to both auth systems
1454
+ */
1455
+ @Post('reset-password-sync')
1456
+ @Roles(RoleEnum.S_EVERYONE)
1457
+ async resetPasswordWithSync(
1458
+ @Body() input: { token: string; newPassword: string },
1459
+ ): Promise<{ success: boolean }> {
1460
+ // 1. Reset password via BetterAuth native API
1461
+ const api = this.betterAuthService.getApi();
1462
+ await api.resetPassword({
1463
+ body: { token: input.token, newPassword: input.newPassword },
1464
+ });
1465
+
1466
+ // 2. Sync to Legacy Auth using the plain password
1467
+ // Get user email from the token (you may need to decode it or lookup)
1468
+ const userEmail = await this.getUserEmailFromToken(input.token);
1469
+ if (userEmail) {
1470
+ await this.betterAuthUserMapper.syncPasswordToLegacy(
1471
+ '', // iamUserId not needed for email lookup
1472
+ userEmail,
1473
+ input.newPassword,
1474
+ );
1475
+ }
1476
+
1477
+ return { success: true };
1478
+ }
1479
+
1480
+ private async getUserEmailFromToken(token: string): Promise<string | null> {
1481
+ // Implementation depends on your token structure
1482
+ // You may need to query the database or decode the token
1483
+ return null;
1484
+ }
1485
+ }
1486
+ ```
1487
+
1488
+ **Alternative: Use Legacy Password Reset**
1489
+
1490
+ The simpler approach is to use the Legacy Auth password reset flow, which automatically syncs to IAM:
1491
+
1492
+ ```typescript
1493
+ // Frontend points to Legacy Auth password reset
1494
+ const passwordResetUrl = config.email.passwordResetLink;
1495
+ // e.g., 'http://localhost:4200/user/password-reset'
1496
+
1497
+ // User submits new password via Legacy Auth
1498
+ // → CoreUserService.resetPassword() is called
1499
+ // → Automatically syncs to IAM via syncPasswordChangeToIam()
1500
+ ```
1501
+
1502
+ This is the recommended approach for projects in migration phase where both auth systems are active.
1503
+
1100
1504
  ## Testing
1101
1505
 
1102
1506
  The module provides a `reset()` method for testing:
@@ -1268,121 +1672,35 @@ const config = {
1268
1672
 
1269
1673
  ## Extending Better-Auth (Custom Resolver)
1270
1674
 
1271
- Better-Auth uses the same extension pattern as Legacy Auth. You can extend `CoreBetterAuthResolver` to add custom logic before/after authentication operations.
1675
+ See the [Project Integration Guide](#project-integration-guide-required-steps) for complete setup instructions. This section covers additional customization options.
1272
1676
 
1273
- ### Creating a Custom Resolver
1677
+ ### Adding Custom Logic
1274
1678
 
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
- }
1679
+ Override any method to add custom behavior (e.g., sending welcome emails, analytics):
1298
1680
 
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;
1681
+ ```typescript
1682
+ // In your BetterAuthResolver (see Step 2 in Integration Guide)
1683
+
1684
+ @Mutation(() => BetterAuthAuthModel, { description: 'Sign up via Better-Auth (email/password)' })
1685
+ @Roles(RoleEnum.S_EVERYONE)
1686
+ override async betterAuthSignUp(
1687
+ @Args('email') email: string,
1688
+ @Args('password') password: string,
1689
+ @Args('name', { nullable: true }) name?: string,
1690
+ ): Promise<BetterAuthAuthModel> {
1691
+ // Call original implementation
1692
+ const result = await super.betterAuthSignUp(email, password, name);
1693
+
1694
+ // Add custom logic after successful sign-up
1695
+ if (result.success && result.user) {
1696
+ await this.emailService.sendWelcomeEmail(result.user.email, result.user.name);
1697
+ await this.analyticsService.trackSignUp(result.user.id);
1317
1698
  }
1318
1699
 
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
- }
1700
+ return result;
1339
1701
  }
1340
1702
  ```
1341
1703
 
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
1704
  ### Available Override Methods
1387
1705
 
1388
1706
  All methods in `CoreBetterAuthResolver` can be overridden: