@lenne.tech/nest-server 11.24.4 → 11.25.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 (64) hide show
  1. package/.claude/rules/configurable-features.md +2 -0
  2. package/CLAUDE.md +13 -0
  3. package/FRAMEWORK-API.md +14 -2
  4. package/README.md +15 -0
  5. package/dist/config.env.js +100 -81
  6. package/dist/config.env.js.map +1 -1
  7. package/dist/core/common/helpers/cookies.helper.d.ts +19 -0
  8. package/dist/core/common/helpers/cookies.helper.js +109 -0
  9. package/dist/core/common/helpers/cookies.helper.js.map +1 -0
  10. package/dist/core/common/interfaces/server-options.interface.d.ts +11 -1
  11. package/dist/core/modules/auth/core-auth.controller.js +4 -16
  12. package/dist/core/modules/auth/core-auth.controller.js.map +1 -1
  13. package/dist/core/modules/auth/core-auth.resolver.js +4 -16
  14. package/dist/core/modules/auth/core-auth.resolver.js.map +1 -1
  15. package/dist/core/modules/auth/tokens.decorator.d.ts +1 -1
  16. package/dist/core/modules/better-auth/better-auth.config.d.ts +24 -1
  17. package/dist/core/modules/better-auth/better-auth.config.js +22 -2
  18. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
  19. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +3 -0
  20. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
  21. package/dist/core/modules/better-auth/core-better-auth-cookie.helper.d.ts +3 -1
  22. package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js +7 -3
  23. package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js.map +1 -1
  24. package/dist/core/modules/better-auth/core-better-auth.controller.js +7 -3
  25. package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
  26. package/dist/core/modules/better-auth/core-better-auth.module.d.ts +2 -1
  27. package/dist/core/modules/better-auth/core-better-auth.module.js +4 -1
  28. package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
  29. package/dist/core/modules/better-auth/core-better-auth.service.js +5 -4
  30. package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
  31. package/dist/core/modules/migrate/templates/migration-project.template.ts +16 -2
  32. package/dist/core.module.d.ts +3 -1
  33. package/dist/core.module.js +10 -7
  34. package/dist/core.module.js.map +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/main.js +17 -3
  39. package/dist/main.js.map +1 -1
  40. package/dist/test/test.helper.d.ts +1 -0
  41. package/dist/test/test.helper.js +2 -1
  42. package/dist/test/test.helper.js.map +1 -1
  43. package/dist/tsconfig.build.tsbuildinfo +1 -1
  44. package/docs/REQUEST-LIFECYCLE.md +78 -3
  45. package/migration-guides/11.24.x-to-11.25.0.md +438 -0
  46. package/package.json +23 -21
  47. package/src/config.env.ts +116 -111
  48. package/src/core/common/helpers/cookies.helper.ts +298 -0
  49. package/src/core/common/interfaces/server-options.interface.ts +141 -2
  50. package/src/core/modules/auth/core-auth.controller.ts +11 -23
  51. package/src/core/modules/auth/core-auth.resolver.ts +11 -23
  52. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +18 -0
  53. package/src/core/modules/better-auth/README.md +7 -0
  54. package/src/core/modules/better-auth/better-auth.config.ts +53 -15
  55. package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +6 -3
  56. package/src/core/modules/better-auth/core-better-auth-cookie.helper.ts +33 -7
  57. package/src/core/modules/better-auth/core-better-auth.controller.ts +12 -3
  58. package/src/core/modules/better-auth/core-better-auth.module.ts +16 -1
  59. package/src/core/modules/better-auth/core-better-auth.service.ts +26 -10
  60. package/src/core/modules/migrate/templates/migration-project.template.ts +16 -2
  61. package/src/core.module.ts +40 -12
  62. package/src/index.ts +1 -0
  63. package/src/main.ts +32 -5
  64. package/src/test/test.helper.ts +15 -1
@@ -42,6 +42,8 @@ The `CoreModule` is a dynamic module that bootstraps the entire framework:
42
42
  | **Mongoose Plugins** | Auto-registration of ID, password, audit, and role guard plugins |
43
43
  | **GraphQL Subscriptions** | WebSocket support with JWT/session authentication |
44
44
  | **Configuration System** | `config.env.ts` with ENV variables, `NEST_SERVER_CONFIG` JSON, `NSC__*` prefixes |
45
+ | **Cookie Handling** | Enabled by default (`cookies: true`), configurable via `ICookiesConfig` with `exposeTokenInBody` option |
46
+ | **Unified CORS** | Single `cors` config propagates to GraphQL, REST, and BetterAuth layers |
45
47
  | **Dual Auth Modes** | IAM-Only (BetterAuth) or Legacy+IAM for migration periods |
46
48
 
47
49
  ### Authentication & Authorization
@@ -290,7 +292,25 @@ The following diagram shows the exact order of execution from HTTP request to re
290
292
  +----------+----------+
291
293
  |
292
294
  +----------------------------v----------------------------+
293
- | MIDDLEWARE CHAIN |
295
+ | EXPRESS-LEVEL MIDDLEWARE |
296
+ | (registered in main.ts) |
297
+ | |
298
+ | 0a. cookie-parser [if cookies enabled, default: yes] |
299
+ | - Parses Cookie header into req.cookies |
300
+ | - Required for session cookie authentication |
301
+ | |
302
+ | 0b. compression [if configured] |
303
+ | - gzip response compression |
304
+ | |
305
+ | 0c. CORS [if not disabled] |
306
+ | - credentials: true when cookies enabled |
307
+ | - Origins from appUrl/baseUrl/cors.allowedOrigins |
308
+ | - Propagated to BetterAuth trustedOrigins |
309
+ +----------------------------+----------------------------+
310
+ |
311
+ +----------------------------v----------------------------+
312
+ | NESTJS MIDDLEWARE CHAIN |
313
+ | (registered in CoreModule.configure()) |
294
314
  | |
295
315
  | 1. RequestContextMiddleware |
296
316
  | - AsyncLocalStorage context |
@@ -397,9 +417,45 @@ The following diagram shows the exact order of execution from HTTP request to re
397
417
 
398
418
  ## Phase 1: Incoming Request
399
419
 
400
- ### Middleware Chain
420
+ ### Express-Level Middleware (main.ts)
421
+
422
+ These run **before NestJS** processes the request. They are registered in the application bootstrap (`main.ts`), not in `CoreModule`:
401
423
 
402
- Middleware runs for **every request** before any NestJS component. Registration happens in `CoreModule.configure()`:
424
+ #### 0a. cookie-parser
425
+
426
+ Parses the `Cookie` header into `req.cookies`. Loaded when cookies are enabled (default: `true` since v11.25.0).
427
+
428
+ ```typescript
429
+ // main.ts
430
+ if (isCookiesEnabled(envConfig.cookies)) {
431
+ server.use(cookieParser());
432
+ }
433
+ ```
434
+
435
+ Without `cookie-parser`, session cookie authentication in `CoreBetterAuthMiddleware` falls back to manual header parsing. With it, `req.cookies` is a parsed object — more reliable and required for signed cookie verification.
436
+
437
+ #### 0b. CORS
438
+
439
+ CORS headers are configured based on the unified `cors` config (since v11.25.0). The same origin list is propagated to GraphQL (Apollo), REST (Express), and BetterAuth (`trustedOrigins`).
440
+
441
+ ```typescript
442
+ // main.ts — uses helpers from cookies.helper.ts
443
+ if (!isCorsDisabled(envConfig.cors)) {
444
+ server.enableCors({ credentials: cookiesEnabled, origin: resolvedOrigins });
445
+ }
446
+ ```
447
+
448
+ | Config | REST (Express) | GraphQL (Apollo) | BetterAuth |
449
+ |--------|----------------|-------------------|------------|
450
+ | `cors: { allowAll: true }` | `origin: true` | `origin: true` | `trustedOrigins: undefined` |
451
+ | `cors: { allowedOrigins: [...] }` | `origin: [merged list]` | `origin: [merged list]` | `trustedOrigins: [merged list]` |
452
+ | `cors: { enabled: false }` | No CORS headers | No CORS headers | `trustedOrigins: []` |
453
+
454
+ > **Note:** GraphQL CORS is configured in `CoreModule.buildCorsConfig()` using raw config values. BetterAuth derives `trustedOrigins` using resolved URLs (auto-derived from `baseUrl`). In edge cases (no explicit `appUrl`), these may differ slightly. Set `appUrl` explicitly for consistent behavior across all layers.
455
+
456
+ ### NestJS Middleware Chain (CoreModule)
457
+
458
+ NestJS middleware runs for every request after Express-level middleware. Registration happens in `CoreModule.configure()`:
403
459
 
404
460
  ```typescript
405
461
  // src/core.module.ts
@@ -1194,6 +1250,25 @@ const request = gqlContext?.req || context.switchToHttp().getRequest();
1194
1250
 
1195
1251
  All security features are configured in `config.env.ts` under the `security` key:
1196
1252
 
1253
+ ### Cookies & CORS (since v11.25.0)
1254
+
1255
+ | Config Path | Type | Default | Description |
1256
+ |-------------|------|---------|-------------|
1257
+ | `cookies` | `boolean \| ICookiesConfig` | `true` | Enable cookie-parser and session cookies |
1258
+ | `cookies.exposeTokenInBody` | `boolean` | `false` | Keep token in response body alongside cookies |
1259
+ | `cors` | `boolean \| ICorsConfig` | `undefined` (enabled) | Unified CORS across GraphQL, REST, BetterAuth |
1260
+ | `cors.allowAll` | `boolean` | `false` | Allow all origins (mirrors request origin) |
1261
+ | `cors.allowedOrigins` | `string[]` | `[]` | Additional origins beyond appUrl/baseUrl |
1262
+ | `cors.enabled` | `boolean` | `true` | Enable/disable CORS on all layers |
1263
+
1264
+ **Cookie modes:**
1265
+
1266
+ | Mode | Config | Token in body | Cookie set | JWT via header |
1267
+ |------|--------|:---:|:---:|:---:|
1268
+ | Cookie-only (default) | `cookies: true` | No | Yes | Yes (always) |
1269
+ | JWT-only | `cookies: false` | Yes | No | Yes |
1270
+ | Hybrid | `cookies: { exposeTokenInBody: true }` | Yes | Yes | Yes |
1271
+
1197
1272
  ### Guardian Gates
1198
1273
 
1199
1274
  | Config Path | Type | Default | Description |
@@ -0,0 +1,438 @@
1
+ # Migration Guide: 11.24.x → 11.25.0
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | `cookies` default changed from `false` to `true`; secrets moved from `config.env.ts` to env vars; MongoDB URI no longer falls back to localhost in production |
8
+ | **New Features** | Unified CORS configuration (`cors`), configurable token exposure (`exposeTokenInBody`), Brevo transactional overlay via `BREVO_API_KEY`, Dockerfile HEALTHCHECK |
9
+ | **Security Fixes** | `secure` cookie flag now also honors app-level `env: 'staging'`; `cors: false` fully disables Apollo CORS (previously silently opened it); `cookieParser` signs with `betterAuth.secret` in IAM-only mode; JWT conversion now runs in hybrid cookie mode (previously skipped) |
10
+ | **Bugfixes** | Fixed inconsistency between nest-server and BetterAuth cookie defaults; fixed `resolveJwtToken` incorrectly skipping conversion when `exposeTokenInBody: true` |
11
+ | **Migration Effort** | 10–30 minutes (add env vars, review cookie/CORS behavior, update `.env`) |
12
+
13
+ ---
14
+
15
+ ## Quick Migration (If You Don't Want Cookies)
16
+
17
+ If your project was running without cookies and you want to keep it that way:
18
+
19
+ ```typescript
20
+ // config.env.ts — add cookies: false to your environment config
21
+ production: {
22
+ cookies: false, // Explicit opt-out (was the implicit default before 11.25.0)
23
+ // ... rest of config
24
+ }
25
+ ```
26
+
27
+ If you want to adopt the new cookie default, no changes are needed — cookies are now enabled by default.
28
+
29
+ ---
30
+
31
+ ## What's New in 11.25.0
32
+
33
+ ### 1. Cookies Enabled by Default
34
+
35
+ Cookies are now enabled by default, consistent with BetterAuth's existing behavior. This means:
36
+
37
+ - `cookie-parser` middleware is automatically loaded
38
+ - CORS is configured with `credentials: true`
39
+ - Session cookies are set on authentication responses
40
+ - JWT via `Authorization: Bearer` header continues to work independently
41
+
42
+ **Before (11.24.x):**
43
+ ```typescript
44
+ // cookies was undefined → treated as false
45
+ // No cookie-parser, no CORS credentials
46
+ ```
47
+
48
+ **After (11.25.0):**
49
+ ```typescript
50
+ // cookies is undefined → treated as true (default)
51
+ // cookie-parser loaded, CORS with credentials: true
52
+ // To disable: cookies: false
53
+ ```
54
+
55
+ ### 2. Cookie Configuration Object (`ICookiesConfig`)
56
+
57
+ The `cookies` property now accepts a configuration object (Boolean Shorthand Pattern):
58
+
59
+ ```typescript
60
+ // Boolean (same as before)
61
+ cookies: false // Disabled
62
+ cookies: true // Enabled (default)
63
+
64
+ // Object (new)
65
+ cookies: {
66
+ enabled: true, // Whether cookies are enabled (default: true)
67
+ exposeTokenInBody: true, // Keep token in response body alongside cookies (default: false)
68
+ }
69
+ ```
70
+
71
+ #### `exposeTokenInBody` Option
72
+
73
+ By default, when cookies are enabled, the token is **removed from the response body** (the httpOnly cookie provides XSS protection — exposing the token in the body would negate this benefit).
74
+
75
+ Set `exposeTokenInBody: true` when clients need both JWT and cookies in parallel:
76
+
77
+ ```typescript
78
+ // Hybrid mode: JWT + Cookies
79
+ cookies: { exposeTokenInBody: true }
80
+
81
+ // Login response includes:
82
+ // Response Body: { token: "eyJ...", session: {...}, user: {...} }
83
+ // Set-Cookie: iam.session_token=signed_value
84
+ ```
85
+
86
+ ### 3. Brevo Transactional API — Auto-Config via `BREVO_API_KEY`
87
+
88
+ The reference `config.env.ts` now auto-wires a `brevo` config block in `production`, `development`, and `local` environments when `BREVO_API_KEY` is set:
89
+
90
+ ```typescript
91
+ // Automatically added to config.env.ts (no manual brevo: { ... } needed)
92
+ ...(process.env.BREVO_API_KEY
93
+ ? {
94
+ brevo: {
95
+ apiKey: process.env.BREVO_API_KEY,
96
+ sender: { email: EMAIL_DEFAULT_SENDER, name: EMAIL_DEFAULT_SENDER_NAME },
97
+ },
98
+ }
99
+ : {})
100
+ ```
101
+
102
+ **How it integrates:** Brevo runs IN PARALLEL to SMTP. It is used only when a module explicitly references a `brevoTemplateId`:
103
+
104
+ ```typescript
105
+ // config.env.ts
106
+ betterAuth: {
107
+ emailVerification: {
108
+ brevoTemplateId: 123, // Uses Brevo Transactional API with server-side template
109
+ locale: 'de',
110
+ },
111
+ }
112
+ ```
113
+
114
+ All other emails (password resets without a template, custom app mails, 2FA codes) continue to flow through the SMTP transport configured via `SMTP_HOST/PORT/USER/PASS`.
115
+
116
+ **Activation:**
117
+ ```bash
118
+ # .env
119
+ BREVO_API_KEY=xkeysib-your-api-key
120
+ ```
121
+
122
+ `ci` and `e2e` environments intentionally skip Brevo — tests never make external API calls.
123
+
124
+ ### 4. Unified CORS Configuration (`ICorsConfig`)
125
+
126
+ A new `cors` property controls CORS across all three layers: GraphQL (Apollo), REST (Express), and BetterAuth (`trustedOrigins`).
127
+
128
+ ```typescript
129
+ // Allow all origins (development)
130
+ cors: { allowAll: true }
131
+
132
+ // Specific additional origins (production)
133
+ cors: { allowedOrigins: ['https://admin.example.com'] }
134
+ // Combined with appUrl and baseUrl automatically
135
+
136
+ // Disable CORS on all layers including BetterAuth
137
+ cors: { enabled: false }
138
+
139
+ // Environment variable support
140
+ cors: {
141
+ allowedOrigins: process.env.CORS_ALLOWED_ORIGINS?.split(',').filter(Boolean),
142
+ }
143
+ ```
144
+
145
+ **Origin resolution priority:**
146
+ 1. `cors.allowAll: true` → mirrors request origin (allows all)
147
+ 2. `cors.allowedOrigins` + `appUrl` + `baseUrl` → deduplicated list
148
+ 3. Only `appUrl`/`baseUrl` → those origins
149
+ 4. Nothing configured → **no credentialed CORS** (secure default — same-origin requests still work; cross-origin browsers will fail the preflight). The legacy "allow all" fallback was removed in v11.25.0 because it combined with `credentials: true` would allow any website to perform credentialed requests. Configure `appUrl`/`baseUrl` or `cors.allowedOrigins` explicitly.
150
+
151
+ **BetterAuth integration:**
152
+ - `cors.allowAll` → BetterAuth `trustedOrigins` is set to `undefined` (allows all)
153
+ - `cors.allowedOrigins` → merged with `appUrl`/`baseUrl` for BetterAuth `trustedOrigins`
154
+ - `cors.enabled: false` → BetterAuth `trustedOrigins` set to `[]` (no origins allowed)
155
+ - Explicit `betterAuth.trustedOrigins` always takes precedence (backward compatible)
156
+
157
+ ---
158
+
159
+ ## Breaking Changes
160
+
161
+ ### Change 1: `cookies` Default from `false` to `true`
162
+
163
+ **Impact:** Projects that did not explicitly set `cookies` will now have cookie handling enabled.
164
+
165
+ **Symptoms if not addressed:**
166
+ - Session cookies will be set on authentication responses
167
+ - CORS will include `credentials: true`
168
+ - Tokens may be removed from response body (if your code reads `response.token`)
169
+
170
+ **Fix:**
171
+ ```typescript
172
+ // config.env.ts — if you want to keep the old behavior
173
+ production: {
174
+ cookies: false, // Explicit opt-out
175
+ }
176
+
177
+ // OR if you want cookies but need tokens in body
178
+ production: {
179
+ cookies: { exposeTokenInBody: true },
180
+ }
181
+ ```
182
+
183
+ ### Change 2: Hardcoded Secret Fallbacks Removed from Production Config
184
+
185
+ **Impact:** Most of these environment variables were already read in 11.24.x. What changed in 11.25.0 is that the **hardcoded fallback strings in the production block** (e.g. `'SECRET_OR_PRIVATE_KEY_LOCAL'`, `'mongodb://127.0.0.1/nest-server-prod'`) were removed. Production deployments that previously relied on the reference config without setting env vars will now fail fast at startup with a clearer misconfiguration signal instead of silently booting with insecure defaults.
186
+
187
+ - **No action needed** if you already set all required env vars in your `.env` / secret store.
188
+ - **Action required** if you depended on the reference-config fallbacks — add the listed variables to your production environment.
189
+
190
+ Test environments (`ci`/`e2e`/`local`/`development`) still use hardcoded fallback secrets so tests run without a `.env` file (see the dedicated warning section further below).
191
+
192
+ **Variables read from the environment in the production config block:**
193
+
194
+ | Config Path | Environment Variable | Notes |
195
+ |-------------|---------------------|-------|
196
+ | `mongoose.uri` | `MONGODB_URI` | Required in production (no fallback) |
197
+ | `baseUrl` | `BASE_URL` | Used for CORS / Passkey / email links / OAuth callbacks |
198
+ | `jwt.secret` | `JWT_SECRET` | Required only for Legacy Auth |
199
+ | `jwt.refresh.secret` | `JWT_REFRESH_SECRET` | Required only for Legacy Auth |
200
+ | `betterAuth.secret` | `BETTER_AUTH_SECRET` | Required, min. 32 chars |
201
+ | `email.defaultSender.email` | `EMAIL_DEFAULT_SENDER` | |
202
+ | `email.defaultSender.name` | `EMAIL_DEFAULT_SENDER_NAME` | Default: `"Nest Server"` |
203
+ | `email.smtp.host` | `SMTP_HOST` | |
204
+ | `email.smtp.port` | `SMTP_PORT` | Default: `587` |
205
+ | `email.smtp.secure` | `SMTP_SECURE` | Default: `true` |
206
+ | `email.smtp.auth.user` | `SMTP_USER` | |
207
+ | `email.smtp.auth.pass` | `SMTP_PASS` | |
208
+ | `cors.allowedOrigins` | `CORS_ALLOWED_ORIGINS` | Comma-separated |
209
+ | `brevo.apiKey` | `BREVO_API_KEY` | **NEW** — activates Brevo overlay automatically |
210
+
211
+ When `BREVO_API_KEY` is set, the production config auto-configures a `brevo` config block with sender derived from `EMAIL_DEFAULT_SENDER` / `EMAIL_DEFAULT_SENDER_NAME`. No manual `brevo: { ... }` in `config.env.ts` is required anymore.
212
+
213
+ **Removed from the reference config (still supported in `IServerOptions`):**
214
+ - `email.mailjet` — the `MailjetService` remains available; only the hardcoded reference-config entry was removed. If your project actively uses it, keep your `email.mailjet` block.
215
+ - `email.passwordResetLink` / `email.verificationLink` — removed from the reference config. Both keys are still accepted by `IServerOptions` and read from `ConfigService` at runtime; derive them from `baseUrl`/`appUrl` if you don't need deployment-specific overrides.
216
+
217
+ **Fix:** Update your `.env` file with the required variables. See `.env.example` in the repo root — it documents all variables (required ones uncommented, optional ones commented out), including SMTP baseline and the optional Brevo Transactional API overlay for template-based mails.
218
+
219
+ ```bash
220
+ # Production .env
221
+ MONGODB_URI=mongodb://host:27017/my-app
222
+ BASE_URL=https://api.example.com
223
+ BETTER_AUTH_SECRET=<32+ char secret>
224
+ SMTP_HOST=smtp.example.com
225
+ SMTP_USER=user
226
+ SMTP_PASS=password
227
+ EMAIL_DEFAULT_SENDER=noreply@example.com
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Detailed Migration Steps
233
+
234
+ ### Step 1: Update Package
235
+
236
+ ```bash
237
+ pnpm install @lenne.tech/nest-server@11.25.0
238
+
239
+ # If package.json dependencies changed
240
+ pnpm run update
241
+ ```
242
+
243
+ ### Step 2: Decide on Cookie Strategy
244
+
245
+ | Strategy | Config | Use Case |
246
+ |----------|--------|----------|
247
+ | **Cookie-only** (new default) | No config needed | Web apps with browser-based auth |
248
+ | **JWT-only** (old default) | `cookies: false` | Mobile apps, pure API clients |
249
+ | **Hybrid** | `cookies: { exposeTokenInBody: true }` | Apps needing both JWT + cookies |
250
+
251
+ ### Step 3: Configure CORS (Optional)
252
+
253
+ ```typescript
254
+ // Production: restrict to specific origins
255
+ cors: {
256
+ allowedOrigins: process.env.CORS_ALLOWED_ORIGINS?.split(',').filter(Boolean),
257
+ }
258
+
259
+ // Development: allow all
260
+ cors: { allowAll: true }
261
+ ```
262
+
263
+ ### Step 4: Verify Build and Tests
264
+
265
+ ```bash
266
+ pnpm run build
267
+ pnpm test
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Compatibility Notes
273
+
274
+ ### JWT Authentication
275
+ JWT via `Authorization: Bearer <token>` continues to work independently of cookie configuration. The middleware token priority remains:
276
+ 1. Authorization header (JWT) → always processed first
277
+ 2. JWT from cookie (`lt-jwt-token`) → fallback when no auth header
278
+ 3. Session cookie → final fallback
279
+
280
+ ### Existing `betterAuth.trustedOrigins`
281
+ If your project explicitly sets `betterAuth.trustedOrigins`, it takes precedence over the new `cors` configuration. No changes needed.
282
+
283
+ ### Legacy Auth
284
+ The Legacy Auth controller and resolver also respect `exposeTokenInBody`. Both `token` and `refreshToken` are kept in the response body when `exposeTokenInBody: true`.
285
+
286
+ Legacy auth cookies (`token`, `refreshToken`) are now set with **secure defaults**:
287
+ - `httpOnly: true` (no JavaScript access — XSS mitigation)
288
+ - `sameSite: 'lax'` (CSRF mitigation)
289
+ - `secure: true` in production (HTTPS-only)
290
+
291
+ ### `secure` Cookie Flag — Staging Coverage
292
+
293
+ Auth cookies' `secure` flag is now `true` when EITHER:
294
+ - `config.env === 'production'` or `config.env === 'staging'` (app-level), OR
295
+ - `process.env.NODE_ENV === 'production'` (runtime)
296
+
297
+ Previously only `NODE_ENV` was checked. Staging deployments that set `config.env: 'staging'` but forgot `NODE_ENV=production` were silently sending auth cookies over HTTP.
298
+
299
+ ### Apollo GraphQL CORS — `cors: false` Now Fully Disables
300
+
301
+ In v11.24.x, setting `cors: false` passed `cors: {}` to Apollo, which Apollo interprets as "CORS with default origin behavior" (i.e. it sent CORS headers with `Access-Control-Allow-Origin: *`). The fix in v11.25.0 passes `cors: false` to Apollo directly, which fully disables Apollo's CORS handling. No backward-compatible workaround is possible — projects that relied on the old permissive behavior must now explicitly set `cors: { allowAll: true }` instead.
302
+
303
+ ### `cookieParser` Signing Secret — Fallback Chain
304
+
305
+ `main.ts` now picks a signing secret for the `cookie-parser` middleware via a fallback chain:
306
+
307
+ 1. `jwt.secret` (Legacy Auth)
308
+ 2. `betterAuth.secret` (IAM-only mode)
309
+ 3. Unsigned cookies (no secret anywhere)
310
+
311
+ IAM-only projects previously had `cookieParser()` called without a signing secret even though a `betterAuth.secret` was configured. Signed cookies (`req.signedCookies`) are now verifiable in IAM-only mode.
312
+
313
+ ### Hybrid Mode JWT Conversion (`resolveJwtToken`)
314
+
315
+ `CoreBetterAuthService.resolveJwtToken()` now converts opaque session tokens to proper JWTs when BOTH:
316
+ - `cookies.exposeTokenInBody: true` (hybrid mode)
317
+ - `betterAuth.jwt.enabled: true`
318
+
319
+ In 11.24.x the conversion was unconditionally skipped as soon as cookies were enabled, which meant hybrid clients received an opaque session token in the body instead of the expected JWT. Pure JWT-only mode (`cookies: false`) and cookie-only mode (default) behave unchanged.
320
+
321
+ ### Production Startup Guard for `exposeTokenInBody`
322
+
323
+ Since v11.25.0, the framework **throws at startup** if `cookies.exposeTokenInBody: true` is set in `production` or `staging` environments. This prevents accidental XSS-risk misconfiguration (the httpOnly cookie protection is negated when the token is also in the body). Test environments (`ci`, `e2e`, `development`, `local`) are unaffected.
324
+
325
+ If you need hybrid JWT+Cookie in production, keep `exposeTokenInBody: false` and use the separate `GET /iam/token` endpoint on the client to fetch a JWT on demand.
326
+
327
+ ### Secret Fallbacks in Dev/CI/E2E Environments
328
+
329
+ The `ci`, `e2e`, `development`, and `local` environment configs use **hardcoded fallback secrets** (e.g., `'SECRET_OR_PRIVATE_KEY_LOCAL'`, `'BETTER_AUTH_SECRET_LOCAL_32_CHARS_M'`) so that tests run without a `.env` file. These values are **publicly visible in this repository**.
330
+
331
+ > **Warning:** Do NOT deploy to production with `NODE_ENV=development` (or `ci`/`e2e`/`local`) AND missing real secrets. Any attacker can forge JWTs signed with the public fallback key. In production, `NODE_ENV=production` MUST be set, and `BETTER_AUTH_SECRET`, `JWT_SECRET`, `JWT_REFRESH_SECRET` MUST come from environment variables (the production config has no fallbacks — missing values cause startup failure).
332
+
333
+ ### Dockerfile HEALTHCHECK
334
+
335
+ v11.25.0 adds a `HEALTHCHECK` directive to the default `Dockerfile`:
336
+
337
+ ```dockerfile
338
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
339
+ CMD wget -qO- http://127.0.0.1:3000/health || exit 1
340
+ ```
341
+
342
+ **Requirement:** The `/health` endpoint is provided by `HealthCheckModule`, which is enabled via `healthCheck` in `config.env.ts`. If your project disables it, Docker will keep reporting the container as `unhealthy` and orchestrators (Docker Swarm, Kubernetes readiness/liveness, docker-compose `depends_on.service.condition: service_healthy`) will refuse to route traffic or will restart the container.
343
+
344
+ **To enable:**
345
+ ```typescript
346
+ // config.env.ts
347
+ production: {
348
+ healthCheck: {}, // Uses defaults — enables /health endpoint
349
+ // or opt out: healthCheck: false
350
+ }
351
+ ```
352
+
353
+ **If you already had a custom Dockerfile:** The HEALTHCHECK is optional — remove the directive if your orchestrator performs its own probes.
354
+
355
+ ### Deprecated: Static CoreModule Cookie Helpers
356
+
357
+ The static helpers `CoreModule.isCookiesEnabled()` and `CoreModule.isExposeTokenInBodyEnabled()` are now marked `@deprecated`. Use the standalone exports from the framework instead — they are framework-scoped (no `CoreModule` import), tree-shakeable, and consistent with the other helpers in `cookies.helper.ts`.
358
+
359
+ ```typescript
360
+ // Before (11.24.x and earlier)
361
+ import { CoreModule } from '@lenne.tech/nest-server';
362
+ if (CoreModule.isCookiesEnabled(config.cookies)) { ... }
363
+
364
+ // After (11.25.0+)
365
+ import { isCookiesEnabled, isExposeTokenInBodyEnabled } from '@lenne.tech/nest-server';
366
+ if (isCookiesEnabled(config.cookies)) { ... }
367
+ ```
368
+
369
+ Both static methods still work (they delegate internally) — they will be removed in a future MINOR release.
370
+
371
+ ### MongoDB URI — No Production Fallback
372
+
373
+ Production config no longer falls back to `mongodb://127.0.0.1/nest-server-prod` when `MONGODB_URI` is unset. Starting the server without a valid `MONGODB_URI` in production will now fail fast. This prevents silent misconfiguration where the app would otherwise connect to an unintended local database.
374
+
375
+ ---
376
+
377
+ ## Troubleshooting
378
+
379
+ ### Tokens missing from API responses
380
+ **Cause:** Cookies are now enabled by default, which removes tokens from the response body.
381
+ **Fix:** Set `cookies: { exposeTokenInBody: true }` or `cookies: false`.
382
+
383
+ ### CORS errors in browser
384
+ **Cause:** CORS now includes `credentials: true` by default, which requires explicit origins.
385
+ **Fix:** Set `cors: { allowAll: true }` for development, or configure `cors.allowedOrigins` for production.
386
+
387
+ ### Tests failing with "token is null"
388
+ **Cause:** Test environment needs `exposeTokenInBody: true` if tests read tokens from response body.
389
+ **Fix:** Set `cookies: { exposeTokenInBody: true }` in test/CI/E2E config.
390
+
391
+ ### `Error: SECURITY: cookies.exposeTokenInBody must not be true in production or staging`
392
+ **Cause:** The new production safety guard — `exposeTokenInBody: true` is blocked in `production`/`staging` envs.
393
+ **Fix:** Either remove `exposeTokenInBody` from those env blocks, or use `GET /iam/token` on the client to fetch a JWT on demand.
394
+
395
+ ### Hybrid client receives opaque session token instead of JWT
396
+ **Cause:** Fixed in 11.25.0 — `resolveJwtToken` previously skipped conversion whenever cookies were enabled.
397
+ **Fix:** Upgrading to 11.25.0 fixes this automatically. Ensure `betterAuth.jwt.enabled: true` is set.
398
+
399
+ ### Container stays in `unhealthy` state
400
+ **Cause:** The new Dockerfile HEALTHCHECK probes `/health`, but `HealthCheckModule` is disabled.
401
+ **Fix:** Enable `healthCheck: {}` in your `config.env.ts`, or remove the `HEALTHCHECK` directive if you don't use it.
402
+
403
+ ### MongoDB connection refused after upgrade in production
404
+ **Cause:** The production config no longer falls back to `mongodb://127.0.0.1/nest-server-prod` when `MONGODB_URI` is unset.
405
+ **Fix:** Set `MONGODB_URI` explicitly in your production `.env` / secret store.
406
+
407
+ ---
408
+
409
+ ## Module Documentation
410
+
411
+ ### Core Module
412
+ - **Shared Helper (NEW):** [src/core/common/helpers/cookies.helper.ts](../src/core/common/helpers/cookies.helper.ts) — `isCookiesEnabled`, `isExposeTokenInBodyEnabled`, `isCorsDisabled`, `isProductionLikeEnv`, `getDefaultAuthCookieOptions`, `setLegacyAuthCookies`, `assertCookiesProductionSafe`, `buildCorsConfig`, `shouldConvertSessionTokenToJwt`
413
+ - **Core Module:** [src/core.module.ts](../src/core.module.ts) — `buildCorsConfig()` integration, `assertCookiesProductionSafe()` guard, deprecated static helpers
414
+ - **Interface:** [src/core/common/interfaces/server-options.interface.ts](../src/core/common/interfaces/server-options.interface.ts) — `ICookiesConfig`, `ICorsConfig`
415
+ - **Bootstrap:** [src/main.ts](../src/main.ts) — Conditional `cookieParser` with fallback chain, tri-case CORS logic
416
+
417
+ ### BetterAuth Module
418
+ - **Config:** [src/core/modules/better-auth/better-auth.config.ts](../src/core/modules/better-auth/better-auth.config.ts) — `buildTrustedOrigins()` with `serverCorsConfig`
419
+ - **Cookie Helper:** [src/core/modules/better-auth/core-better-auth-cookie.helper.ts](../src/core/modules/better-auth/core-better-auth-cookie.helper.ts) — `exposeTokenInBody` parameter, `env` field for staging `secure` flag
420
+ - **Service:** [src/core/modules/better-auth/core-better-auth.service.ts](../src/core/modules/better-auth/core-better-auth.service.ts) — `resolveJwtToken()` hybrid-mode fix
421
+ - **Integration Checklist:** [src/core/modules/better-auth/INTEGRATION-CHECKLIST.md](../src/core/modules/better-auth/INTEGRATION-CHECKLIST.md)
422
+
423
+ ### Legacy Auth Module
424
+ - **Controller:** [src/core/modules/auth/core-auth.controller.ts](../src/core/modules/auth/core-auth.controller.ts) — Updated `processCookies()`
425
+ - **Resolver:** [src/core/modules/auth/core-auth.resolver.ts](../src/core/modules/auth/core-auth.resolver.ts) — Updated `processCookies()`
426
+
427
+ ### Infrastructure
428
+ - **Dockerfile:** [Dockerfile](../Dockerfile) — New `HEALTHCHECK` directive
429
+ - **Env Example:** [.env.example](../.env.example) — Consolidated (required + optional in one file)
430
+ - **Request Lifecycle:** [docs/REQUEST-LIFECYCLE.md](../docs/REQUEST-LIFECYCLE.md) — Express-middleware section documents `cookie-parser` and CORS ordering
431
+ - **API Reference:** [FRAMEWORK-API.md](../FRAMEWORK-API.md) — Auto-generated, includes `ICookiesConfig` + `ICorsConfig` field lists
432
+
433
+ ---
434
+
435
+ ## References
436
+
437
+ - [Configurable Features Pattern](./../.claude/rules/configurable-features.md)
438
+ - [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.24.4",
3
+ "version": "11.25.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",
@@ -79,17 +79,17 @@
79
79
  "@better-auth/passkey": "1.5.5",
80
80
  "@getbrevo/brevo": "3.0.1",
81
81
  "@nestjs/apollo": "13.2.5",
82
- "@nestjs/common": "11.1.18",
83
- "@nestjs/core": "11.1.18",
82
+ "@nestjs/common": "11.1.19",
83
+ "@nestjs/core": "11.1.19",
84
84
  "@nestjs/graphql": "13.2.5",
85
85
  "@nestjs/jwt": "11.0.2",
86
86
  "@nestjs/mongoose": "11.0.4",
87
87
  "@nestjs/passport": "11.0.5",
88
- "@nestjs/platform-express": "11.1.18",
89
- "@nestjs/schedule": "6.1.1",
90
- "@nestjs/swagger": "11.2.7",
88
+ "@nestjs/platform-express": "11.1.19",
89
+ "@nestjs/schedule": "6.1.3",
90
+ "@nestjs/swagger": "11.3.0",
91
91
  "@nestjs/terminus": "11.1.1",
92
- "@nestjs/websockets": "11.1.18",
92
+ "@nestjs/websockets": "11.1.19",
93
93
  "@tus/file-store": "2.0.0",
94
94
  "@tus/server": "2.3.0",
95
95
  "@types/supertest": "7.2.0",
@@ -99,8 +99,8 @@
99
99
  "class-validator": "0.15.1",
100
100
  "compression": "1.8.1",
101
101
  "cookie-parser": "1.4.7",
102
- "dotenv": "17.4.1",
103
- "ejs": "5.0.1",
102
+ "dotenv": "17.4.2",
103
+ "ejs": "5.0.2",
104
104
  "express": "5.2.1",
105
105
  "graphql": "16.13.2",
106
106
  "graphql-query-complexity": "1.1.0",
@@ -125,11 +125,11 @@
125
125
  },
126
126
  "devDependencies": {
127
127
  "@compodoc/compodoc": "1.2.1",
128
- "@nestjs/cli": "11.0.19",
129
- "@nestjs/schematics": "11.0.10",
130
- "@nestjs/testing": "11.1.18",
128
+ "@nestjs/cli": "11.0.21",
129
+ "@nestjs/schematics": "11.1.0",
130
+ "@nestjs/testing": "11.1.19",
131
131
  "@swc/cli": "0.8.1",
132
- "@swc/core": "1.15.24",
132
+ "@swc/core": "1.15.26",
133
133
  "@types/compression": "1.8.1",
134
134
  "@types/cookie-parser": "1.4.10",
135
135
  "@types/ejs": "3.1.5",
@@ -147,8 +147,8 @@
147
147
  "nodemon": "3.1.14",
148
148
  "npm-watch": "0.13.0",
149
149
  "otpauth": "9.5.0",
150
- "oxfmt": "0.44.0",
151
- "oxlint": "1.59.0",
150
+ "oxfmt": "0.45.0",
151
+ "oxlint": "1.60.0",
152
152
  "rimraf": "6.1.3",
153
153
  "ts-node": "10.9.2",
154
154
  "tsconfig-paths": "4.2.0",
@@ -188,7 +188,7 @@
188
188
  "minimatch@>=10.0.0 <10.2.5": "Security: RegExp DoS - transitive via @nestjs/apollo>ts-morph>@ts-morph/common and nodemon",
189
189
  "ajv@<6.14.0": "Security: prototype pollution - transitive via @getbrevo/brevo>rewire>eslint",
190
190
  "ajv@>=7.0.0-alpha.0 <8.18.0": "Security: prototype pollution - transitive via @nestjs/cli>@angular-devkit",
191
- "undici@>=7.0.0 <7.24.7": "Security: various CVEs - transitive via @compodoc/compodoc>cheerio",
191
+ "undici@>=7.0.0 <7.25.0": "Security: various CVEs - transitive via @compodoc/compodoc>cheerio",
192
192
  "srvx@<0.11.15": "Compatibility: @tus/server@2.3.0 requires ~0.8.2 but 0.11.15 needed for security - remove when @tus/server ships with >=0.11.15",
193
193
  "handlebars@>=4.0.0 <4.7.9": "Security: prototype pollution (GHSA-q42p-pg8m-cqh6) - transitive via @compodoc/compodoc",
194
194
  "brace-expansion@<1.1.13": "Security: RegExp DoS - transitive via eslint>minimatch",
@@ -196,9 +196,10 @@
196
196
  "picomatch@<2.3.2": "Security: ReDoS - transitive via @nestjs/graphql>fast-glob>micromatch and @compodoc/compodoc>chokidar",
197
197
  "picomatch@>=4.0.0 <4.0.4": "Security: ReDoS - transitive via vitest and vite",
198
198
  "path-to-regexp@>=8.0.0 <8.4.2": "Security: ReDoS (GHSA-rhx6-c78j-4q9w) - transitive via express>router",
199
- "kysely@>=0.26.0 <0.28.15": "Security: SQL injection - transitive via better-auth",
199
+ "kysely@>=0.26.0 <0.28.16": "Security: SQL injection - transitive via better-auth",
200
200
  "lodash@>=4.0.0 <4.18.0": "Security: CVE in lodash@4.17.x - transitive via @nestjs/graphql. 4.18.1 is the latest patched version",
201
- "defu@<=6.1.6": "Security: prototype pollution via __proto__ key - transitive via better-auth"
201
+ "defu@<=6.1.6": "Security: prototype pollution via __proto__ key - transitive via better-auth",
202
+ "follow-redirects@<=1.15.11": "Security: Custom Authentication Headers leak on cross-domain redirect (GHSA-r4q5-vmmm-2653) - transitive via axios>@getbrevo/brevo and axios>node-mailjet"
202
203
  },
203
204
  "overrides": {
204
205
  "axios@<1.15.0": "1.15.0",
@@ -207,7 +208,7 @@
207
208
  "minimatch@>=10.0.0 <10.2.5": "10.2.5",
208
209
  "ajv@<6.14.0": "6.14.0",
209
210
  "ajv@>=7.0.0-alpha.0 <8.18.0": "8.18.0",
210
- "undici@>=7.0.0 <7.24.7": "7.24.7",
211
+ "undici@>=7.0.0 <7.25.0": "7.25.0",
211
212
  "srvx@<0.11.15": "0.11.15",
212
213
  "handlebars@>=4.0.0 <4.7.9": "4.7.9",
213
214
  "brace-expansion@<1.1.13": "1.1.13",
@@ -215,9 +216,10 @@
215
216
  "picomatch@<2.3.2": "2.3.2",
216
217
  "picomatch@>=4.0.0 <4.0.4": "4.0.4",
217
218
  "path-to-regexp@>=8.0.0 <8.4.2": "8.4.2",
218
- "kysely@>=0.26.0 <0.28.15": "0.28.15",
219
+ "kysely@>=0.26.0 <0.28.16": "0.28.16",
219
220
  "lodash@>=4.0.0 <4.18.0": "4.18.1",
220
- "defu@<=6.1.6": "6.1.7"
221
+ "defu@<=6.1.6": "6.1.7",
222
+ "follow-redirects@<=1.15.11": "1.16.0"
221
223
  },
222
224
  "onlyBuiltDependencies": [
223
225
  "bcrypt",