@lenne.tech/nest-server 11.26.1 → 11.26.3
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/common/decorators/unified-field.decorator.js +2 -7
- package/dist/core/common/decorators/unified-field.decorator.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth-cookie-prefix.helper.d.ts +3 -0
- package/dist/core/modules/better-auth/better-auth-cookie-prefix.helper.js +27 -0
- package/dist/core/modules/better-auth/better-auth-cookie-prefix.helper.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth.config.d.ts +1 -1
- package/dist/core/modules/better-auth/better-auth.config.js +13 -1
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js +2 -1
- package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-web.helper.js +3 -4
- package/dist/core/modules/better-auth/core-better-auth-web.helper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.js +1 -2
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.d.ts +2 -0
- package/dist/core/modules/better-auth/core-better-auth.service.js +10 -4
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/test/test.helper.js +5 -3
- package/dist/test/test.helper.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/migration-guides/11.26.1-to-11.26.2.md +234 -0
- package/migration-guides/11.26.2-to-11.26.3.md +186 -0
- package/package.json +1 -1
- package/src/core/common/decorators/unified-field.decorator.ts +13 -7
- package/src/core/modules/better-auth/better-auth-cookie-prefix.helper.ts +84 -0
- package/src/core/modules/better-auth/better-auth.config.ts +34 -3
- package/src/core/modules/better-auth/core-better-auth-cookie.helper.ts +12 -2
- package/src/core/modules/better-auth/core-better-auth-web.helper.ts +6 -7
- package/src/core/modules/better-auth/core-better-auth.controller.ts +1 -2
- package/src/core/modules/better-auth/core-better-auth.service.ts +29 -4
- package/src/test/test.helper.ts +13 -3
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Migration Guide: 11.26.1 → 11.26.2
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| **Breaking Changes** | None |
|
|
8
|
+
| **Bugfixes** | Latent BetterAuth session-cookie drift fixed — six call sites that independently derived the cookie name from `basePath` now share a single resolver, so a `cookiePrefix` override no longer breaks sign-in / authenticated requests / sign-out |
|
|
9
|
+
| **New Features** | New `COOKIE_PREFIX` env variable — isolates the BetterAuth session cookie name on shared hosts (e.g. several lenne.tech apps on `*.localhost`). New helper module `better-auth-cookie-prefix.helper.ts` exporting `resolveBetterAuthCookiePrefix()`, `resolveBetterAuthSessionCookieName()`, `detectCookiePrefixDrift()`. New `CoreBetterAuthService.getCookiePrefix()`. Drift warning when `options.advanced.cookiePrefix` is used programmatically |
|
|
10
|
+
| **Migration Effort** | 0 minutes (automatic) — opt-in `COOKIE_PREFIX` for multi-app shared-host deployments only |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Quick Migration
|
|
15
|
+
|
|
16
|
+
No code changes required. The fix and the new resolver activate automatically; `COOKIE_PREFIX` is opt-in.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Update package
|
|
20
|
+
pnpm add @lenne.tech/nest-server@11.26.2
|
|
21
|
+
|
|
22
|
+
# Verify build
|
|
23
|
+
pnpm run build
|
|
24
|
+
|
|
25
|
+
# Run tests
|
|
26
|
+
pnpm test
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## What's New in 11.26.2
|
|
32
|
+
|
|
33
|
+
### 1. `COOKIE_PREFIX` env variable — isolate auth cookies per app
|
|
34
|
+
|
|
35
|
+
**Use case:** Several lenne.tech apps deployed on the same host (e.g. via `lt dev` — `nest-server.localhost`, `offers.localhost`, `showroom.localhost` all served by Caddy on the same machine). Browser cookies are scoped by host, not port — without isolation a session cookie from one app collides with another and authentication breaks. The BetterAuth REST surface in 11.26.1 derives the cookie name from `basePath` (default: `iam.session_token`), which is identical across apps.
|
|
36
|
+
|
|
37
|
+
**Fix:** Set `COOKIE_PREFIX` per app — the BetterAuth session cookie becomes `<prefix>.session_token`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# App A
|
|
41
|
+
COOKIE_PREFIX=offers # → cookie name: offers.session_token
|
|
42
|
+
|
|
43
|
+
# App B
|
|
44
|
+
COOKIE_PREFIX=showroom # → cookie name: showroom.session_token
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Precedence (resolved by `resolveBetterAuthCookiePrefix`):**
|
|
48
|
+
|
|
49
|
+
1. **`COOKIE_PREFIX` env** (dedicated, always wins) — sanitised to the safe cookie-name subset `[A-Za-z0-9._-]`; if it sanitises to empty it is ignored
|
|
50
|
+
2. otherwise the **basePath-derived** prefix (`/iam` → `iam`, `/api/iam` → `api.iam`) — the previous default, fully backward compatible when `COOKIE_PREFIX` is unset
|
|
51
|
+
|
|
52
|
+
**Frontend pairing (required when set):** The frontend `@lenne.tech/nuxt-extensions` exposes `NUXT_PUBLIC_COOKIE_PREFIX` (same precedence, same sanitisation). The two sides MUST agree, otherwise the browser sends one name and the server reads another. The backend logs a `COOKIE_PREFIX override active → …` line on bootstrap to make the override visible in operator logs.
|
|
53
|
+
|
|
54
|
+
### 2. Single source of truth helper module
|
|
55
|
+
|
|
56
|
+
New file: `src/core/modules/better-auth/better-auth-cookie-prefix.helper.ts`. Dependency-free on purpose so it can be imported from the config builder, the service, controllers and helpers without any import cycle.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// All public API:
|
|
60
|
+
import {
|
|
61
|
+
resolveBetterAuthCookiePrefix, // → 'iam' (default) or 'acme' (under COOKIE_PREFIX=acme)
|
|
62
|
+
resolveBetterAuthSessionCookieName, // → 'iam.session_token' or 'acme.session_token'
|
|
63
|
+
detectCookiePrefixDrift, // → null | warning string
|
|
64
|
+
} from '@lenne.tech/nest-server';
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
A `@deprecated` re-export from `./better-auth.config` keeps old imports working — new code should import from the helper directly.
|
|
68
|
+
|
|
69
|
+
### 3. `CoreBetterAuthService.getCookiePrefix()` (new public method)
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
class CoreBetterAuthService {
|
|
73
|
+
/** Cached on first read so it cannot drift away from the Better-Auth instance,
|
|
74
|
+
* which captures the prefix once at bootstrap. */
|
|
75
|
+
getCookiePrefix(): string;
|
|
76
|
+
getSessionCookieName(): string; // unchanged signature, now backed by getCookiePrefix()
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 4. Drift warning when `options.advanced.cookiePrefix` is set programmatically
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
betterAuth: {
|
|
84
|
+
options: {
|
|
85
|
+
advanced: { cookiePrefix: 'custom' }, // ← BetterAuth would honour this, NestJS layer would not
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This pattern silently broke sign-in / read / sign-out in 11.26.1 and earlier. 11.26.2 detects the drift via `detectCookiePrefixDrift()` and emits a loud `logger.warn` at bootstrap:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
options.advanced.cookiePrefix="custom" overrides Better-Auth but the NestJS layer
|
|
94
|
+
still uses "iam". This will break sign-in / read / sign-out — use the COOKIE_PREFIX
|
|
95
|
+
env variable instead (honoured by every call site).
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## What's Fixed in 11.26.2
|
|
101
|
+
|
|
102
|
+
### Cross-layer cookie-prefix drift in the BetterAuth pipeline
|
|
103
|
+
|
|
104
|
+
**Affects:** Any project that set a non-default `cookiePrefix` via `options.advanced.cookiePrefix` or via the proposed `COOKIE_PREFIX` env (introduced here). Default deployments (no override) are not affected.
|
|
105
|
+
|
|
106
|
+
**Symptom (before 11.26.2):**
|
|
107
|
+
With a non-default cookie prefix, Better-Auth set `Set-Cookie: <prefix>.session_token=…` but the NestJS layer kept reading `iam.session_token`:
|
|
108
|
+
|
|
109
|
+
| Stage | Cookie name in 11.26.1 | Result |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| Sign-up / Sign-in | `<prefix>.session_token` (Better-Auth) | Cookie correctly set by Better-Auth |
|
|
112
|
+
| Authenticated request | `iam.session_token` ([core-better-auth.controller.ts:778-781](../src/core/modules/better-auth/core-better-auth.controller.ts)) | User reads as anonymous (`success: false`) |
|
|
113
|
+
| `getSession()` cookie-signing | `iam.session_token` ([core-better-auth.service.ts:437-438](../src/core/modules/better-auth/core-better-auth.service.ts)) | Session never resolved |
|
|
114
|
+
| `extractSessionToken` (middleware) | `iam.session_token` ([core-better-auth-web.helper.ts:98-103](../src/core/modules/better-auth/core-better-auth-web.helper.ts)) | 401 on protected routes |
|
|
115
|
+
| `BetterAuthCookieHelper` (set + clear) | `iam.session_token` ([core-better-auth-cookie.helper.ts:127-128](../src/core/modules/better-auth/core-better-auth-cookie.helper.ts)) | Sign-out leaves real cookie behind |
|
|
116
|
+
|
|
117
|
+
**Fix (in 11.26.2):**
|
|
118
|
+
All six call sites now resolve the cookie name through `resolveBetterAuthSessionCookieName(basePath)` — a single function that honours `COOKIE_PREFIX` and falls back to the basePath default. Cached on the service so a late `process.env` mutation (typical in tests) cannot drift the value away from the Better-Auth instance, which captures the prefix at bootstrap.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Compatibility Notes
|
|
123
|
+
|
|
124
|
+
- **Default deployments (no `COOKIE_PREFIX`, no programmatic override):** Cookie name remains `iam.session_token`, behaviour is bit-for-bit identical to 11.26.1. No change is observable.
|
|
125
|
+
- **Projects that read `iam.session_token` directly in tests (hardcoded strings):** Continue to work as long as `COOKIE_PREFIX` is unset. Under override the hardcoded name is wrong — prefer `resolveBetterAuthSessionCookieName('/iam')` or the `TestHelper.extractSessionToken()` default (which now resolves through the same helper).
|
|
126
|
+
- **`TestHelper.extractSessionToken(response, cookieName?)`:** The second parameter is now optional; when omitted it derives the cookie name through the shared resolver (so tests under `COOKIE_PREFIX=acme` automatically look at `acme.session_token`). Existing call sites that pass an explicit name are unaffected.
|
|
127
|
+
- **`TestHelper.buildBetterAuthCookies(token, basePath = 'iam')`:** Already honoured `COOKIE_PREFIX` since the prior release via the same resolver — no caller change required.
|
|
128
|
+
- **Programmatic `options.advanced.cookiePrefix`:** Still applied to the Better-Auth instance (no behaviour change in Better-Auth), but now emits a `logger.warn` drift notice on bootstrap. Switch to `COOKIE_PREFIX` (env) to remove the warning and the latent breakage.
|
|
129
|
+
- **`BetterAuthCookieHelper.getNormalizedBasePath()`:** Unchanged — still returns the basePath-derived string (e.g. `'iam'`). It is **NOT** a cookie prefix; the JSDoc now carries a loud warning so future code never reconstructs the cookie name as `` `${getNormalizedBasePath()}.session_token` `` (which would re-introduce the drift).
|
|
130
|
+
- **Vendor-mode consumers:** Same as npm consumers; the new file ships under `src/core/modules/better-auth/`.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Adopting `COOKIE_PREFIX` (Optional)
|
|
135
|
+
|
|
136
|
+
### Step 1: Decide the prefix per app
|
|
137
|
+
|
|
138
|
+
Pick a short, unique slug per app — typically the project slug (`offers`, `showroom`) or a kebab/dot subset that matches your environment hygiene.
|
|
139
|
+
|
|
140
|
+
| Pick | Valid? | Result |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `offers` | ✓ | `offers.session_token` |
|
|
143
|
+
| `kit-test_01` | ✓ | `kit-test_01.session_token` |
|
|
144
|
+
| `a;c=me x\r\n` | sanitised | `acmex.session_token` (semicolons, equals, whitespace, CR/LF stripped) |
|
|
145
|
+
| `;;==` | sanitises to empty → falls back to basePath | `iam.session_token` (the env is ignored) |
|
|
146
|
+
|
|
147
|
+
### Step 2: Set both sides in lockstep
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Backend (this package)
|
|
151
|
+
COOKIE_PREFIX=offers
|
|
152
|
+
|
|
153
|
+
# Frontend (@lenne.tech/nuxt-extensions)
|
|
154
|
+
NUXT_PUBLIC_COOKIE_PREFIX=offers
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Step 3: Verify
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Bootstrap should log:
|
|
161
|
+
# COOKIE_PREFIX override active → auth cookies use prefix "offers" (e.g. "offers.session_token").
|
|
162
|
+
# The frontend NUXT_PUBLIC_COOKIE_PREFIX MUST match.
|
|
163
|
+
|
|
164
|
+
# Sign in and inspect the Set-Cookie header — it must use the overridden name:
|
|
165
|
+
curl -i -X POST http://localhost:3000/iam/sign-in/email \
|
|
166
|
+
-H 'Content-Type: application/json' \
|
|
167
|
+
-d '{"email":"…","password":"…"}' \
|
|
168
|
+
| grep -i set-cookie
|
|
169
|
+
# Set-Cookie: offers.session_token=…; Path=/; HttpOnly; …
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Troubleshooting
|
|
175
|
+
|
|
176
|
+
### After setting `COOKIE_PREFIX`, sign-in succeeds but every follow-up request is unauthenticated
|
|
177
|
+
|
|
178
|
+
Verify the frontend `NUXT_PUBLIC_COOKIE_PREFIX` matches the backend value. The most common cause is one side set in `.env` and the other still inheriting the default. The backend log line `COOKIE_PREFIX override active → …` confirms the backend value; check the browser's DevTools → Application → Cookies for the actual cookie name the browser holds.
|
|
179
|
+
|
|
180
|
+
### Bootstrap log says `options.advanced.cookiePrefix="…" overrides Better-Auth but the NestJS layer still uses "…"`
|
|
181
|
+
|
|
182
|
+
You are setting the cookie prefix programmatically via `betterAuth.options.advanced.cookiePrefix`. Move the override to the `COOKIE_PREFIX` env variable — that is the only path that is honoured by every call site (sign-up, sign-in, get-session, sign-out, middleware). The programmatic override is left untouched but the NestJS layer cannot follow it, so sign-in writes one cookie and authenticated requests look for another.
|
|
183
|
+
|
|
184
|
+
### Existing browser cookies under the old name keep the user "logged in" after switching `COOKIE_PREFIX`
|
|
185
|
+
|
|
186
|
+
Expected. Browser cookies are persistent — changing the prefix does not retroactively rename them. Users will be silently signed out (the server no longer reads the old name) and sign in again, after which the new cookie is set. Old cookies fall out of the browser at their max-age. To force a clean state, instruct users to clear cookies for the affected host, or set a one-off `Clear-Site-Data: "cookies"` header during the rollout.
|
|
187
|
+
|
|
188
|
+
### Tests under `COOKIE_PREFIX=acme` return `{ success: false }` from `/iam/session` although sign-in succeeded
|
|
189
|
+
|
|
190
|
+
Most likely you are passing the JWT-converted token (sign-in response body's `token` field) instead of the raw session token. With `betterAuth.jwt` enabled the response token is a JWT (`eyJ…`), which goes in the `Authorization: Bearer` header — not in the session cookie. For cookie-based authenticated tests, read the raw token from the `session` collection:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const sessionRow = await db.collection('session').findOne(
|
|
194
|
+
{ $or: [{ userId: iamUser._id }, { userId: iamUser._id.toString() }] },
|
|
195
|
+
{ sort: { createdAt: -1 } },
|
|
196
|
+
);
|
|
197
|
+
const rawSessionToken = sessionRow.token;
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
See `tests/stories/better-auth-cookie-prefix.story.test.ts` for a complete e2e reference.
|
|
201
|
+
|
|
202
|
+
### `TestHelper.extractSessionToken(response)` returns `null` although the cookie is in the Set-Cookie header
|
|
203
|
+
|
|
204
|
+
You are likely on a `COOKIE_PREFIX=acme` setup but pass an explicit `cookieName: 'iam.session_token'` as the second argument. Either omit the argument (the new default goes through the resolver) or pass the resolved name explicitly.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Module Documentation
|
|
209
|
+
|
|
210
|
+
### BetterAuth Module
|
|
211
|
+
|
|
212
|
+
- **README:** [src/core/modules/better-auth/README.md](../src/core/modules/better-auth/README.md)
|
|
213
|
+
- **Integration Checklist:** [src/core/modules/better-auth/INTEGRATION-CHECKLIST.md](../src/core/modules/better-auth/INTEGRATION-CHECKLIST.md)
|
|
214
|
+
- **Customization Guide:** [src/core/modules/better-auth/CUSTOMIZATION.md](../src/core/modules/better-auth/CUSTOMIZATION.md)
|
|
215
|
+
- **Reference Implementation:** `src/server/modules/better-auth/`
|
|
216
|
+
- **Key Files:**
|
|
217
|
+
- `src/core/modules/better-auth/better-auth-cookie-prefix.helper.ts` — single source of truth for the prefix (`resolveBetterAuthCookiePrefix`, `resolveBetterAuthSessionCookieName`, `detectCookiePrefixDrift`)
|
|
218
|
+
- `src/core/modules/better-auth/better-auth.config.ts` — bootstrap-time resolution + drift warning + `@deprecated` re-export
|
|
219
|
+
- `src/core/modules/better-auth/core-better-auth.service.ts` — `getCookiePrefix()` (new, cached), `getSessionCookieName()` (now via cache)
|
|
220
|
+
- `src/core/modules/better-auth/core-better-auth.controller.ts`, `core-better-auth-cookie.helper.ts`, `core-better-auth-web.helper.ts` — all converted to the shared resolver
|
|
221
|
+
- **Reference tests:**
|
|
222
|
+
- `tests/unit/better-auth-cookie-prefix.spec.ts` — pure resolver + drift detector
|
|
223
|
+
- `tests/unit/better-auth-cookie-helper.spec.ts` — cross-layer set/read lockstep + service cache + `TestHelper` default
|
|
224
|
+
- `tests/stories/better-auth-cookie-prefix.story.test.ts` — e2e sign-up / authenticated request / sign-out against a real Mongo + Better-Auth boot under `COOKIE_PREFIX=acme`
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## References
|
|
229
|
+
|
|
230
|
+
- [Migration Guide 11.26.0 → 11.26.1](./11.26.0-to-11.26.1.md) — Previous release (roles in REST auth response)
|
|
231
|
+
- [Configurable Features](../.claude/rules/configurable-features.md) — Pattern notes for env-driven overrides
|
|
232
|
+
- [BetterAuth module rules](../.claude/rules/better-auth.md) — Standard-compliance & security baselines
|
|
233
|
+
- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
|
|
234
|
+
- [@lenne.tech/nuxt-extensions `resolveLtCookiePrefix`](https://github.com/lenneTech/nuxt-extensions) — frontend pendant (`NUXT_PUBLIC_COOKIE_PREFIX`)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Migration Guide: 11.26.2 → 11.26.3
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| **Breaking Changes** | None |
|
|
8
|
+
| **Bugfixes** | `@UnifiedField({ enum: … })` no longer emits a broken, unnamed `$ref` in the generated OpenAPI document under `@nestjs/swagger >= 11.4` — enum fields now produce proper named component schemas (or clean inline enums when `enumName: null` / auto-detection fails) instead of `allOf: [{ $ref: '#/components/schemas/' }]`, which crashed OpenAPI client generators like `@hey-api/openapi-ts` |
|
|
9
|
+
| **New Features** | None |
|
|
10
|
+
| **Migration Effort** | 0 minutes (automatic) — drop-in patch release |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Quick Migration
|
|
15
|
+
|
|
16
|
+
No code changes required. The fix applies automatically as soon as the package is updated.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Update package
|
|
20
|
+
pnpm add @lenne.tech/nest-server@11.26.3
|
|
21
|
+
|
|
22
|
+
# Verify build
|
|
23
|
+
pnpm run build
|
|
24
|
+
|
|
25
|
+
# Run tests
|
|
26
|
+
pnpm test
|
|
27
|
+
|
|
28
|
+
# (Optional) regenerate REST client SDK against the new OpenAPI document
|
|
29
|
+
pnpm --filter @your-app/api-sdk openapi-ts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What's Fixed in 11.26.3
|
|
35
|
+
|
|
36
|
+
### OpenAPI: broken empty `$ref` on enum-typed `@UnifiedField` properties
|
|
37
|
+
|
|
38
|
+
**Affects:** Any project that
|
|
39
|
+
|
|
40
|
+
- consumes `@lenne.tech/nest-server` together with `@nestjs/swagger >= 11.4` (this repo pinned to `11.4.2`), AND
|
|
41
|
+
- exposes REST endpoints whose DTOs declare enum fields via `@UnifiedField({ enum: … })`, AND
|
|
42
|
+
- runs an OpenAPI client generator (e.g. `@hey-api/openapi-ts`, `openapi-typescript`, `openapi-generator-cli`) against the bundled OpenAPI document.
|
|
43
|
+
|
|
44
|
+
**Symptom (before 11.26.3):**
|
|
45
|
+
|
|
46
|
+
The decorator passed `type: () => String` to `@nestjs/swagger` ALONGSIDE `enum` + `enumName`. `@nestjs/swagger <= 11.2` silently tolerated the combination, but `@nestjs/swagger >= 11.4` emits a broken, **unnamed** enum reference and never registers the enum under `components.schemas`:
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
// Generated OpenAPI document — BROKEN
|
|
50
|
+
{
|
|
51
|
+
"components": {
|
|
52
|
+
"schemas": {
|
|
53
|
+
"SomeInput": {
|
|
54
|
+
"properties": {
|
|
55
|
+
"status": {
|
|
56
|
+
"allOf": [
|
|
57
|
+
{ "$ref": "#/components/schemas/" } // ← empty target!
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// ← StatusEnum is missing entirely from components.schemas
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Downstream tools crash:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
@hey-api/openapi-ts: Missing $ref pointer "#/components/schemas/". Token "" does not exist.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Fix (in 11.26.3):**
|
|
75
|
+
|
|
76
|
+
`@UnifiedField` no longer sets `swaggerOpts.type` when the field is an enum. `@nestjs/swagger` derives the schema from `enum` + `enumName` correctly:
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
// Generated OpenAPI document — CORRECT
|
|
80
|
+
{
|
|
81
|
+
"components": {
|
|
82
|
+
"schemas": {
|
|
83
|
+
"StatusEnum": { "type": "string", "enum": ["draft", "published", "review"] },
|
|
84
|
+
"SomeInput": {
|
|
85
|
+
"properties": {
|
|
86
|
+
"status": {
|
|
87
|
+
"allOf": [
|
|
88
|
+
{ "$ref": "#/components/schemas/StatusEnum" } // ← named, resolvable
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Behaviour matrix:**
|
|
99
|
+
|
|
100
|
+
| `@UnifiedField` form | OpenAPI output before 11.26.3 | OpenAPI output in 11.26.3 |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `{ enum: MyEnum, enumName: 'MyEnum' }` | Empty `$ref`, `MyEnum` missing from `components.schemas` | Named `MyEnum` schema, property uses `$ref` |
|
|
103
|
+
| `{ enum: MyEnum }` + `registerEnum(MyEnum, { name: 'MyEnum' })` | Empty `$ref` | Named `MyEnum` schema, property uses `$ref` |
|
|
104
|
+
| `{ enum: MyEnum, enumName: null }` (opt out) | Empty `$ref` | Inline `enum: [...]`, no `$ref`, no named schema |
|
|
105
|
+
| `{ enum: MyEnum }` without registration | Empty `$ref` | Inline `enum: [...]`, no `$ref`, no named schema |
|
|
106
|
+
| Long-form `{ enum: { enum: MyEnum, enumName: 'MyEnum' } }` (deprecated) | Empty `$ref` | Named `MyEnum` schema, property uses `$ref` (deprecation warning unchanged) |
|
|
107
|
+
| Non-enum fields (`String`, `Number`, `Date`, custom classes, …) | Unchanged | Unchanged |
|
|
108
|
+
|
|
109
|
+
GraphQL schema, class-validator runtime validation (`IsEnum`), Mongoose `@Prop`, and field-level `@Restricted` / `@Roles` behaviour are all bit-for-bit identical to 11.26.2.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Compatibility Notes
|
|
114
|
+
|
|
115
|
+
- **`@nestjs/swagger <= 11.2` consumers:** The previously emitted `type: () => String` was redundant; removing it produces the same enum schema as before. No observable change.
|
|
116
|
+
- **`@nestjs/swagger >= 11.4` consumers:** The OpenAPI document for enum fields changes from a **broken** empty `$ref` to a **correct** named schema (or clean inline enum). This is strictly a defect fix — any client generator that was previously crashing now succeeds.
|
|
117
|
+
- **OpenAPI client generators / SDK consumers:** After updating, regenerate the SDK once. Enum properties that were previously typed `string` (when the generator silently dropped the broken `$ref`) will now be typed as the proper enum union — review the generated SDK once and adjust call-sites if you relied on the loose `string` type.
|
|
118
|
+
- **GraphQL consumers:** No change. The `Field(...)` factory and enum resolution are untouched.
|
|
119
|
+
- **`@UnifiedField` public API:** Unchanged. All option shapes (`enum: MyEnum`, `enumName`, deprecated long-form `{ enum: { … } }`, `enumName: null`) keep their documented semantics.
|
|
120
|
+
- **Mongoose / class-validator:** Unchanged. `@Prop({ type: baseType })` still applied for enum fields; `IsEnum(...)` is still the authoritative validator.
|
|
121
|
+
- **Vendor-mode consumers:** Same fix lands in `src/core/common/decorators/unified-field.decorator.ts`. Sync via `/lt-dev:backend:update-nest-server-core`. No flatten-fix change required.
|
|
122
|
+
- **Hidden / excluded enum fields (`@UnifiedField({ exclude: true })`):** Unaffected — those still hide from the OpenAPI document via `ApiHideProperty()`.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Verifying the Fix
|
|
127
|
+
|
|
128
|
+
If you previously hit the empty-`$ref` defect, confirm the regenerated OpenAPI document:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# 1. Boot the API and dump the OpenAPI document
|
|
132
|
+
pnpm start &
|
|
133
|
+
curl -s http://localhost:3000/api-json > /tmp/openapi.json
|
|
134
|
+
|
|
135
|
+
# 2. There must be no empty/unnamed component refs
|
|
136
|
+
grep -F '"$ref": "#/components/schemas/"' /tmp/openapi.json && echo 'BROKEN' || echo 'OK'
|
|
137
|
+
|
|
138
|
+
# 3. Every enum used in a DTO must appear in components.schemas
|
|
139
|
+
jq '.components.schemas | keys' /tmp/openapi.json
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
A complete regression test ships in `tests/unified-field-enum-swagger.e2e-spec.ts` and inspects the real document built by `SwaggerModule.createDocument()` for:
|
|
143
|
+
|
|
144
|
+
- no empty `$ref` anywhere in the document,
|
|
145
|
+
- a named component schema per enum (string / numeric / array / auto-detected / deprecated long-form),
|
|
146
|
+
- correct enum values and property references,
|
|
147
|
+
- inline-enum fallback for `enumName: null` and for unregistered enums (no empty `$ref`).
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Troubleshooting
|
|
152
|
+
|
|
153
|
+
### After updating, my generated SDK still has an empty `$ref` error
|
|
154
|
+
|
|
155
|
+
Make sure the SDK is regenerated against a **freshly rebuilt** API. Stale `openapi.json` artefacts checked into the consumer repo continue to be broken. Rebuild the API (`pnpm run build`) and re-export the document (`/api-json` or `SwaggerModule.createDocument` snapshot) before running your codegen.
|
|
156
|
+
|
|
157
|
+
### My enum properties used to be typed `string` in the generated client and now they're a strict union
|
|
158
|
+
|
|
159
|
+
That is the corrected behaviour — the previous client was generated against a broken document and silently widened the type. Update call-sites to use the enum union (or import the enum from your shared package). If you need the loose `string` type during the rollout, your codegen typically offers an `--enum-style` flag (e.g. `@hey-api/openapi-ts` → `enums: 'javascript'`) to keep the old shape.
|
|
160
|
+
|
|
161
|
+
### I rely on the deprecated long-form `enum: { enum: MyEnum, enumName: 'MyEnum' }`
|
|
162
|
+
|
|
163
|
+
It still works and now produces the same named schema as the shortcut form. The deprecation warning emitted at decoration time is unchanged. Plan to migrate to the shortcut form (`enum: MyEnum, enumName: 'MyEnum'`) before a future MINOR removes the long form.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Module Documentation
|
|
168
|
+
|
|
169
|
+
### Core Common — `@UnifiedField`
|
|
170
|
+
|
|
171
|
+
- **Decorator:** `src/core/common/decorators/unified-field.decorator.ts`
|
|
172
|
+
- **Architecture notes:** [.claude/rules/architecture.md](../.claude/rules/architecture.md) (Input Validation section)
|
|
173
|
+
- **Reference tests:**
|
|
174
|
+
- `tests/unified-field-enum-swagger.e2e-spec.ts` — OpenAPI schema regression guard (the contract this release restores)
|
|
175
|
+
- `tests/unified-field-enum.e2e-spec.ts` — metadata-level enum behaviour
|
|
176
|
+
- `tests/unified-field-enum-api.e2e-spec.ts` — runtime REST/GraphQL enum behaviour
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## References
|
|
181
|
+
|
|
182
|
+
- [Migration Guide 11.26.1 → 11.26.2](./11.26.1-to-11.26.2.md) — Previous release (`COOKIE_PREFIX` env, cross-layer cookie-prefix lockstep)
|
|
183
|
+
- [Architecture rules — Input Validation](../.claude/rules/architecture.md)
|
|
184
|
+
- [@nestjs/swagger 11.4 release notes](https://github.com/nestjs/swagger/releases) — context for the schema-emission change that surfaced the latent defect
|
|
185
|
+
- [@hey-api/openapi-ts](https://heyapi.dev/) — one of the OpenAPI client generators that was crashing on the broken document
|
|
186
|
+
- [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.3",
|
|
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",
|
|
@@ -408,13 +408,19 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
|
|
|
408
408
|
swaggerOpts.required = true;
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
// Set type for swagger
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
411
|
+
// Set type for swagger.
|
|
412
|
+
//
|
|
413
|
+
// For enum fields we deliberately do NOT set `type`: @nestjs/swagger derives
|
|
414
|
+
// the schema from `enum` + `enumName` (set further below). Passing
|
|
415
|
+
// `type: () => String` ALONGSIDE `enum`/`enumName` makes @nestjs/swagger
|
|
416
|
+
// >= 11.4 emit a broken, UNNAMED enum reference
|
|
417
|
+
// (`allOf: [{ $ref: '#/components/schemas/' }]`) and never adds the enum to
|
|
418
|
+
// `components.schemas`. That crashes OpenAPI client generators — e.g.
|
|
419
|
+
// @hey-api/openapi-ts fails with «Missing $ref pointer "#/components/schemas/"».
|
|
420
|
+
// (On @nestjs/swagger <= 11.2 the extra `type` was tolerated, which is why
|
|
421
|
+
// this only surfaced after a swagger bump.)
|
|
422
|
+
if (baseType && !normalizedEnum) {
|
|
423
|
+
swaggerOpts.type = baseType;
|
|
418
424
|
}
|
|
419
425
|
|
|
420
426
|
// Set description
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the Better-Auth session cookie prefix.
|
|
3
|
+
*
|
|
4
|
+
* The session cookie is named `<prefix>.session_token` (e.g. `iam.session_token`).
|
|
5
|
+
* The prefix is resolved here — and ONLY here — so every place that sets, reads,
|
|
6
|
+
* signs, extracts or clears the session cookie agrees on the exact name. (A
|
|
7
|
+
* previous bug derived the name from `basePath` independently in 6 places, so a
|
|
8
|
+
* `COOKIE_PREFIX` override broke the auth pipeline: Better-Auth set
|
|
9
|
+
* `acme.session_token` while the NestJS layer still looked for `iam.session_token`.)
|
|
10
|
+
*
|
|
11
|
+
* Dependency-free on purpose so it can be imported from the config builder, the
|
|
12
|
+
* service, controllers and helpers without any import cycle.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Characters allowed in a cookie-name token. A safe subset of RFC 6265 that is
|
|
17
|
+
* IDENTICAL to the frontend resolver (`@lenne.tech/nuxt-extensions`
|
|
18
|
+
* `resolveLtCookiePrefix`), so a shared `COOKIE_PREFIX` produces the SAME prefix
|
|
19
|
+
* on both sides. Everything else (spaces, `;`, `=`, `\r`, `\n`, …) is stripped so
|
|
20
|
+
* a typo can never corrupt the `Set-Cookie` header.
|
|
21
|
+
*/
|
|
22
|
+
function sanitizeCookiePrefix(raw: string): string {
|
|
23
|
+
return raw.trim().replace(/[^A-Za-z0-9._-]/g, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the Better-Auth cookie prefix (the `iam` in `iam.session_token`).
|
|
28
|
+
*
|
|
29
|
+
* Precedence:
|
|
30
|
+
* 1. **`COOKIE_PREFIX` env** (dedicated, always wins) — for fully autonomous
|
|
31
|
+
* cookie isolation on a shared host (several lenne.tech apps on the same
|
|
32
|
+
* host, where cookies collide by host, not port). Sanitised to valid
|
|
33
|
+
* cookie-name characters; if it sanitises to empty it is ignored.
|
|
34
|
+
* 2. otherwise the **basePath-derived** prefix (`/iam` → `iam`,
|
|
35
|
+
* `/api/iam` → `api.iam`) — the previous behaviour, fully backward
|
|
36
|
+
* compatible when `COOKIE_PREFIX` is unset.
|
|
37
|
+
*
|
|
38
|
+
* IMPORTANT: when `COOKIE_PREFIX` is set it MUST match the frontend
|
|
39
|
+
* `NUXT_PUBLIC_COOKIE_PREFIX` (see `@lenne.tech/nuxt-extensions`
|
|
40
|
+
* `resolveLtCookiePrefix`) — otherwise the two sides use different cookie names
|
|
41
|
+
* and authentication breaks.
|
|
42
|
+
*
|
|
43
|
+
* @param basePath - Better-Auth base path (e.g. `/iam`)
|
|
44
|
+
* @param env - environment to read `COOKIE_PREFIX` from (defaults to `process.env`)
|
|
45
|
+
*/
|
|
46
|
+
export function resolveBetterAuthCookiePrefix(basePath: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
47
|
+
const basePathPrefix = (basePath || '/iam').replace(/^\//, '').replace(/\//g, '.');
|
|
48
|
+
const explicit = sanitizeCookiePrefix(typeof env.COOKIE_PREFIX === 'string' ? env.COOKIE_PREFIX : '');
|
|
49
|
+
return explicit || basePathPrefix;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Convenience: the full session-cookie name (`<prefix>.session_token`) for the
|
|
54
|
+
* given basePath, honouring `COOKIE_PREFIX`. Use this wherever the session
|
|
55
|
+
* cookie name is needed so all call sites stay in lockstep.
|
|
56
|
+
*/
|
|
57
|
+
export function resolveBetterAuthSessionCookieName(basePath: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
58
|
+
return `${resolveBetterAuthCookiePrefix(basePath, env)}.session_token`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detect a Better-Auth cookie-prefix drift between the value the NestJS layer
|
|
63
|
+
* resolves (via {@link resolveBetterAuthCookiePrefix}) and a programmatic
|
|
64
|
+
* `options.advanced.cookiePrefix` passed straight to Better-Auth. Such a
|
|
65
|
+
* mismatch silently breaks the auth pipeline because Better-Auth sets the
|
|
66
|
+
* cookie under the programmatic prefix while the NestJS read/clear path still
|
|
67
|
+
* uses the resolved prefix.
|
|
68
|
+
*
|
|
69
|
+
* Returns `null` when there is no drift (no programmatic prefix, or it
|
|
70
|
+
* matches), otherwise a human-readable warning sentence ready for `logger.warn`.
|
|
71
|
+
*
|
|
72
|
+
* Extracted so the drift-detection logic is testable without booting the full
|
|
73
|
+
* Better-Auth instance (which requires Mongo).
|
|
74
|
+
*/
|
|
75
|
+
export function detectCookiePrefixDrift(resolvedPrefix: string, programmaticOptionsAdvanced: unknown): null | string {
|
|
76
|
+
if (!programmaticOptionsAdvanced || typeof programmaticOptionsAdvanced !== 'object') return null;
|
|
77
|
+
const programmaticPrefix = (programmaticOptionsAdvanced as Record<string, unknown>).cookiePrefix;
|
|
78
|
+
if (typeof programmaticPrefix !== 'string' || programmaticPrefix === resolvedPrefix) return null;
|
|
79
|
+
return (
|
|
80
|
+
`options.advanced.cookiePrefix="${programmaticPrefix}" overrides Better-Auth but the ` +
|
|
81
|
+
`NestJS layer still uses "${resolvedPrefix}". This will break sign-in / read / sign-out — ` +
|
|
82
|
+
`use the COOKIE_PREFIX env variable instead (honoured by every call site).`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -8,6 +8,7 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
|
|
10
10
|
import { IBetterAuth, ICorsConfig } from '../../common/interfaces/server-options.interface';
|
|
11
|
+
import { detectCookiePrefixDrift, resolveBetterAuthCookiePrefix } from './better-auth-cookie-prefix.helper';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Type for better-auth instance with plugins
|
|
@@ -325,9 +326,20 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Crea
|
|
|
325
326
|
// Build the base Better-Auth configuration
|
|
326
327
|
// Use resolved baseUrl (with local defaults) or fallback
|
|
327
328
|
const basePath = config.basePath || '/iam';
|
|
328
|
-
// Cookie prefix
|
|
329
|
-
//
|
|
330
|
-
|
|
329
|
+
// Cookie prefix for Better-Auth's session cookies (e.g. 'iam.session_token').
|
|
330
|
+
// COOKIE_PREFIX env (always wins) lets a project fully isolate its auth
|
|
331
|
+
// cookies on a shared host; otherwise derived from basePath. The SAME pure
|
|
332
|
+
// resolver is used by every session-cookie call site (service, controller,
|
|
333
|
+
// web/cookie helpers) so the name stays in lockstep.
|
|
334
|
+
const cookiePrefix = resolveBetterAuthCookiePrefix(basePath);
|
|
335
|
+
// Operator visibility: make a non-default COOKIE_PREFIX override obvious in the
|
|
336
|
+
// logs, and remind that the frontend must mirror it or auth will break.
|
|
337
|
+
if ((process.env.COOKIE_PREFIX || '').trim()) {
|
|
338
|
+
logger.log(
|
|
339
|
+
`COOKIE_PREFIX override active → auth cookies use prefix "${cookiePrefix}" ` +
|
|
340
|
+
`(e.g. "${cookiePrefix}.session_token"). The frontend NUXT_PUBLIC_COOKIE_PREFIX MUST match.`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
331
343
|
|
|
332
344
|
const betterAuthConfig: Record<string, unknown> = {
|
|
333
345
|
advanced: {
|
|
@@ -382,6 +394,14 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Crea
|
|
|
382
394
|
const { advanced: optionsAdvanced, ...restOptions } = config.options as Record<string, unknown>;
|
|
383
395
|
finalConfig = { ...betterAuthConfig, ...restOptions };
|
|
384
396
|
if (optionsAdvanced && typeof optionsAdvanced === 'object') {
|
|
397
|
+
// Drift guard: a programmatic `options.advanced.cookiePrefix` would only
|
|
398
|
+
// change what Better-Auth itself sets — the NestJS layer keeps resolving
|
|
399
|
+
// through `resolveBetterAuthCookiePrefix(basePath, env)`, so sign-in /
|
|
400
|
+
// read / sign-out would silently fall out of lockstep. Loud warning so
|
|
401
|
+
// operators see the misconfiguration immediately. Use `COOKIE_PREFIX`
|
|
402
|
+
// (env) instead — it is honoured by every call site.
|
|
403
|
+
const driftWarning = detectCookiePrefixDrift(cookiePrefix, optionsAdvanced);
|
|
404
|
+
if (driftWarning) logger.warn(driftWarning);
|
|
385
405
|
finalConfig.advanced = {
|
|
386
406
|
...(betterAuthConfig.advanced as Record<string, unknown>),
|
|
387
407
|
...(optionsAdvanced as Record<string, unknown>),
|
|
@@ -1081,6 +1101,17 @@ export interface ResolvedCrossSubDomainCookies {
|
|
|
1081
1101
|
enabled: boolean;
|
|
1082
1102
|
}
|
|
1083
1103
|
|
|
1104
|
+
/**
|
|
1105
|
+
* Re-exported for backward compatibility.
|
|
1106
|
+
*
|
|
1107
|
+
* @deprecated Import from `./better-auth-cookie-prefix.helper` directly. The
|
|
1108
|
+
* re-export exists only so callers that landed on `./better-auth.config`
|
|
1109
|
+
* before the helper was extracted keep working; new code MUST import from the
|
|
1110
|
+
* dedicated helper module to avoid pulling the heavy config-builder into the
|
|
1111
|
+
* dependency graph (which forces import cycles in some build setups).
|
|
1112
|
+
*/
|
|
1113
|
+
export { resolveBetterAuthCookiePrefix, resolveBetterAuthSessionCookieName } from './better-auth-cookie-prefix.helper';
|
|
1114
|
+
|
|
1084
1115
|
/**
|
|
1085
1116
|
* Resolves the cross-subdomain cookies configuration.
|
|
1086
1117
|
*
|
|
@@ -2,6 +2,7 @@ import { Logger } from '@nestjs/common';
|
|
|
2
2
|
import { Response } from 'express';
|
|
3
3
|
|
|
4
4
|
import { isProductionLikeEnv } from '../../common/helpers/cookies.helper';
|
|
5
|
+
import { resolveBetterAuthSessionCookieName } from './better-auth-cookie-prefix.helper';
|
|
5
6
|
import { signCookieValue } from './core-better-auth-web.helper';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -124,8 +125,10 @@ export class BetterAuthCookieHelper {
|
|
|
124
125
|
constructor(private readonly config: BetterAuthCookieHelperConfig) {
|
|
125
126
|
// Normalize basePath: remove leading slash, replace slashes with dots
|
|
126
127
|
this.normalizedBasePath = config.basePath.replace(/^\//, '').replace(/\//g, '.');
|
|
127
|
-
//
|
|
128
|
-
|
|
128
|
+
// Session cookie name via the single shared resolver: honours COOKIE_PREFIX
|
|
129
|
+
// (e.g. 'iam.session_token', or 'acme.session_token' when COOKIE_PREFIX=acme),
|
|
130
|
+
// so set/clear/read here always match the name Better-Auth itself uses.
|
|
131
|
+
this.cookieName = resolveBetterAuthSessionCookieName(config.basePath);
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
/**
|
|
@@ -153,6 +156,13 @@ export class BetterAuthCookieHelper {
|
|
|
153
156
|
|
|
154
157
|
/**
|
|
155
158
|
* Gets the normalized base path (e.g., 'iam' from '/iam').
|
|
159
|
+
*
|
|
160
|
+
* WARNING: This is the basePath-derived string, NOT the resolved cookie
|
|
161
|
+
* prefix — it intentionally ignores `COOKIE_PREFIX`. Do NOT use it to build
|
|
162
|
+
* a session cookie name (use {@link getCookieName} instead). Composing
|
|
163
|
+
* `${getNormalizedBasePath()}.session_token` would re-introduce the
|
|
164
|
+
* COOKIE_PREFIX drift bug where Better-Auth and the NestJS layer disagree
|
|
165
|
+
* on the cookie name (see better-auth-cookie-prefix.helper.ts).
|
|
156
166
|
*/
|
|
157
167
|
getNormalizedBasePath(): string {
|
|
158
168
|
return this.normalizedBasePath;
|