@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.
- package/FRAMEWORK-API.md +1 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +2 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.js +12 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/migration-guides/11.26.0-to-11.26.1.md +193 -0
- package/package.json +1 -1
- package/src/core/modules/better-auth/core-better-auth.controller.ts +20 -3
|
@@ -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.
|
|
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
|
|
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
|
|