@lenne.tech/nest-server 11.26.0 → 11.26.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.
@@ -0,0 +1,193 @@
1
+ # Migration Guide: 11.26.0 → 11.26.1
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | None |
8
+ | **Bugfixes** | BetterAuth REST sign-in/sign-up/get-session responses now include the user's `roles` from the synced legacy user (previously silently dropped) |
9
+ | **New Features** | New optional field `roles?: string[]` on `CoreBetterAuthUserResponse` (additive, Swagger-documented) |
10
+ | **Migration Effort** | 0 minutes (automatic) — optional cleanup if your project shipped a `mapUser()` override as a workaround |
11
+
12
+ ---
13
+
14
+ ## Quick Migration
15
+
16
+ No code changes required. The fix activates automatically.
17
+
18
+ ```bash
19
+ # Update package
20
+ pnpm add @lenne.tech/nest-server@11.26.1
21
+
22
+ # Verify build
23
+ pnpm run build
24
+
25
+ # Run tests
26
+ pnpm test
27
+ ```
28
+
29
+ ---
30
+
31
+ ## What's Fixed in 11.26.1
32
+
33
+ ### `roles` is now exposed on BetterAuth REST auth responses
34
+
35
+ **Affects:** Every project that uses BetterAuth (IAM) and reads `response.user` from the
36
+ REST endpoints `POST /iam/sign-in/email`, `POST /iam/sign-up/email`, or `GET /iam/session`.
37
+
38
+ **Symptom (before 11.26.1):**
39
+ The sign-in response body looked like this regardless of the user's role assignment:
40
+
41
+ ```json
42
+ {
43
+ "success": true,
44
+ "user": {
45
+ "id": "ba-user-1",
46
+ "email": "admin@example.com",
47
+ "emailVerified": true,
48
+ "name": "Admin"
49
+ },
50
+ "token": "..."
51
+ }
52
+ ```
53
+
54
+ Frontend consumers (e.g. `useLtAuth().setUser()` in `@lenne.tech/nuxt-extensions`,
55
+ the `lt-auth-state` cookie cache) persisted a roles-less user. Client-side admin gating
56
+ (e.g. routing the `nest-server-starter` setup flow into `/admin/*` based on
57
+ `roles: ['admin']`) silently failed for every freshly authenticated user.
58
+
59
+ **Fix (in 11.26.1):**
60
+ `CoreBetterAuthController.mapUser()` now forwards `roles` from the DB-synced legacy user:
61
+
62
+ ```json
63
+ {
64
+ "success": true,
65
+ "user": {
66
+ "id": "ba-user-1",
67
+ "email": "admin@example.com",
68
+ "emailVerified": true,
69
+ "name": "Admin",
70
+ "roles": ["admin"]
71
+ },
72
+ "token": "..."
73
+ }
74
+ ```
75
+
76
+ **Where the roles come from:**
77
+
78
+ `CoreBetterAuthController.mapUser()` reads `roles` from the result of
79
+ `CoreBetterAuthUserMapper.mapSessionUser()`, which looks the user up in the
80
+ `users` collection (`findOne({ $or: [{ email }, { iamId }] })`) and returns
81
+ `Array.isArray(dbUser.roles) ? dbUser.roles : []`. The client cannot influence
82
+ the value — roles remain server-authoritative.
83
+
84
+ **Defensive defaults:**
85
+
86
+ | `mappedUser` value | Response `roles` |
87
+ |-----------------------------------|------------------|
88
+ | `{ roles: ['admin'] }` | `['admin']` |
89
+ | `{ roles: [] }` | `[]` |
90
+ | `null` / `undefined` | `[]` |
91
+ | `{ roles: 'admin' }` (not array) | `[]` |
92
+ | `{}` (no `roles` field) | `[]` |
93
+
94
+ ---
95
+
96
+ ## Compatibility Notes
97
+
98
+ - **Frontend consumers (additive change):** Frontends that previously ignored
99
+ `response.user.roles` because it was always missing continue to work. Frontends
100
+ that now want to use it (admin gating, role-based UI) can read it directly —
101
+ no extra `/api/users/me` round-trip required after login.
102
+ - **Swagger / OpenAPI clients:** The generated schema gains `roles?: string[]`
103
+ on `CoreBetterAuthUserResponse`. Regenerate any SDK that consumes the OpenAPI
104
+ spec (e.g. `nuxt-base-starter` runs `pnpm run generate:types` against the API
105
+ schema).
106
+ - **Existing project-level `mapUser()` overrides (optional cleanup):**
107
+ If your project shipped a `mapUser()` override solely to add `roles` to the
108
+ response as a workaround, you can now delete it. See **Cleanup** below.
109
+ - **Subclasses that override `mapUser()`:** The second positional parameter is
110
+ unchanged in shape but was renamed `_mappedUser` → `mappedUser` (the leading
111
+ underscore previously signalled "unused"). Existing overrides keep working —
112
+ parameter names are local to each function signature.
113
+ - **Projects without BetterAuth:** Not affected. Legacy `signIn` GraphQL/REST
114
+ responses already included `user.roles` and are unchanged.
115
+
116
+ ---
117
+
118
+ ## Cleanup (Optional)
119
+
120
+ If your project previously added a `mapUser()` override in its
121
+ `BetterAuthController` subclass solely to surface `roles`, the override is now
122
+ redundant and can be removed. Example pattern that can be deleted:
123
+
124
+ ```typescript
125
+ // Project: src/server/modules/better-auth/better-auth.controller.ts
126
+ //
127
+ // REMOVE this override — the core now does the same thing.
128
+ protected override mapUser(sessionUser: BetterAuthSessionUser, mappedUser: any) {
129
+ return {
130
+ ...super.mapUser(sessionUser, mappedUser),
131
+ roles: Array.isArray(mappedUser?.roles) ? mappedUser.roles : [],
132
+ };
133
+ }
134
+ ```
135
+
136
+ Keep the override if it adds project-specific fields beyond `roles` (e.g.
137
+ `status`, `type`, `avatar`). In that case, drop only the `roles` line — `roles`
138
+ is now part of the base response.
139
+
140
+ ---
141
+
142
+ ## Troubleshooting
143
+
144
+ ### `roles` is still missing in the response
145
+
146
+ Check, in this order:
147
+
148
+ 1. **You actually pulled the new version.** Run `pnpm list @lenne.tech/nest-server`
149
+ and confirm 11.26.1 (or via the upstream-core-updater agent for vendored projects).
150
+ 2. **The user has been synced to the `users` collection.** `mapSessionUser()` looks
151
+ up the user via `$or: [{ email }, { iamId }]`. A user signed up exclusively via
152
+ BetterAuth but never created in the legacy `users` collection will fall through
153
+ to the "no DB user" branch which returns `roles: []` — assign roles in the
154
+ `users` collection (e.g. via `CoreUserService.setRoles()`), not in the
155
+ `iam_user` collection.
156
+ 3. **The role is a `S_`-prefixed system role.** Per project rule, `S_USER`,
157
+ `S_VERIFIED`, `S_CREATOR`, `S_SELF` etc. are runtime checks and must never be
158
+ stored in `user.roles`. They will not appear in the response.
159
+ 4. **A custom `mapUser()` override strips `roles`.** Search your project for
160
+ `mapUser` and confirm the override either calls `super.mapUser(...)` or
161
+ forwards `roles` explicitly.
162
+ 5. **The user-mapper cache is stale.** In production the mapper caches DB lookups
163
+ for 15s per `iamId`. `CoreUserService.setRoles()` / `update()` already invalidate
164
+ the cache; manual DB writes do not. Force a re-read by calling
165
+ `userMapper.invalidateUserCache(iamId)` after a direct write, or wait 15s.
166
+
167
+ ### Swagger schema shows `roles` as `required`
168
+
169
+ It is not — the property is declared as `required: false`. If your generated
170
+ client marks it required, regenerate after pulling 11.26.1.
171
+
172
+ ---
173
+
174
+ ## Module Documentation
175
+
176
+ ### BetterAuth Module
177
+
178
+ - **README:** [src/core/modules/better-auth/README.md](../src/core/modules/better-auth/README.md)
179
+ - **Integration Checklist:** [src/core/modules/better-auth/INTEGRATION-CHECKLIST.md](../src/core/modules/better-auth/INTEGRATION-CHECKLIST.md)
180
+ - **Customization Guide:** [src/core/modules/better-auth/CUSTOMIZATION.md](../src/core/modules/better-auth/CUSTOMIZATION.md) — patterns for overriding controller / resolver
181
+ - **Reference Implementation:** `src/server/modules/better-auth/`
182
+ - **Key Files:**
183
+ - `src/core/modules/better-auth/core-better-auth.controller.ts` — `mapUser()` (the fix), `CoreBetterAuthUserResponse` (the response DTO with the new `roles` field)
184
+ - `src/core/modules/better-auth/core-better-auth-user.mapper.ts` — `mapSessionUser()` (source of `roles`, with 15s TTL cache and `invalidateUserCache()`)
185
+
186
+ ---
187
+
188
+ ## References
189
+
190
+ - [Role System](../.claude/rules/role-system.md) — Real roles vs. `S_`-prefixed runtime checks
191
+ - [Module Inheritance Pattern](../.claude/rules/module-inheritance.md) — How to extend `CoreBetterAuthController.mapUser()` cleanly
192
+ - [Migration Guide 11.25.x → 11.26.0](./11.25.x-to-11.26.0.md) — Previous release
193
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.26.0",
3
+ "version": "11.26.1",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -75,6 +75,14 @@ export class CoreBetterAuthUserResponse {
75
75
  @ApiProperty({ description: 'User display name' })
76
76
  name: string;
77
77
 
78
+ @ApiProperty({
79
+ description: 'Roles of the user (e.g. ["admin"]) — populated from the synced legacy user.',
80
+ isArray: true,
81
+ required: false,
82
+ type: String,
83
+ })
84
+ roles?: string[];
85
+
78
86
  @ApiProperty({ description: 'Whether 2FA is enabled', required: false })
79
87
  twoFactorEnabled?: boolean;
80
88
  }
@@ -799,16 +807,25 @@ export class CoreBetterAuthController {
799
807
 
800
808
  /**
801
809
  * Map user to response format
810
+ *
811
+ * The `mappedUser` parameter is the synced legacy user from `mapSessionUser()` — it
812
+ * carries DB-only fields like `roles` that are not part of the Better-Auth session
813
+ * payload but are required by consumers for admin gating / RBAC. The base
814
+ * implementation forwards `roles` so the client-side state (`useLtAuth().setUser()`
815
+ * cache, `lt-auth-state` cookie) can route admin areas without a second round-trip.
816
+ *
817
+ * Subclasses can override to add project-specific fields (e.g. `status`, `type`).
818
+ *
802
819
  * @param sessionUser - The user from Better-Auth session
803
- * @param _mappedUser - The synced user from legacy system (available for override customization)
820
+ * @param mappedUser - The synced user from legacy system (includes `roles`)
804
821
  */
805
-
806
- protected mapUser(sessionUser: BetterAuthSessionUser, _mappedUser: any): CoreBetterAuthUserResponse {
822
+ protected mapUser(sessionUser: BetterAuthSessionUser, mappedUser: any): CoreBetterAuthUserResponse {
807
823
  return {
808
824
  email: sessionUser.email,
809
825
  emailVerified: sessionUser.emailVerified || false,
810
826
  id: sessionUser.id,
811
827
  name: sessionUser.name || sessionUser.email.split('@')[0],
828
+ roles: Array.isArray(mappedUser?.roles) ? mappedUser.roles : [],
812
829
  };
813
830
  }
814
831