@lenne.tech/nest-server 11.27.3 → 11.27.5
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/.claude/rules/configurable-features.md +1 -1
- package/FRAMEWORK-API.md +2 -1
- package/dist/config.env.js +1 -0
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/helpers/cookies.helper.d.ts +18 -0
- package/dist/core/common/helpers/cookies.helper.js +81 -4
- package/dist/core/common/helpers/cookies.helper.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +1 -0
- package/dist/core/modules/better-auth/better-auth.config.js +14 -33
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +8 -1
- package/migration-guides/11.27.3-to-11.27.4.md +236 -0
- package/migration-guides/11.27.4-to-11.27.5.md +241 -0
- package/package.json +7 -5
- package/src/config.env.ts +4 -0
- package/src/core/common/helpers/cookies.helper.ts +218 -12
- package/src/core/common/interfaces/server-options.interface.ts +22 -0
- package/src/core/modules/better-auth/better-auth.config.ts +20 -70
|
@@ -460,8 +460,15 @@ if (!isCorsDisabled(envConfig.cors)) {
|
|
|
460
460
|
| `cors: { allowAll: true }` | `origin: true` | `origin: true` | `trustedOrigins: undefined` |
|
|
461
461
|
| `cors: { allowedOrigins: [...] }` | `origin: [merged list]` | `origin: [merged list]` | `trustedOrigins: [merged list]` |
|
|
462
462
|
| `cors: { enabled: false }` | No CORS headers | No CORS headers | `trustedOrigins: []` |
|
|
463
|
+
| `cors: { deriveAppUrl: false }` | `appUrl` not derived from `baseUrl` | same | same |
|
|
463
464
|
|
|
464
|
-
|
|
465
|
+
**URL resolution (since v11.27.5):** all three layers resolve `appUrl`/`baseUrl` through the single `resolveServerUrls()` helper in `cookies.helper.ts`, so they can no longer drift:
|
|
466
|
+
|
|
467
|
+
1. `appUrl` set explicitly → used as-is
|
|
468
|
+
2. `env: 'local' | 'ci' | 'e2e'` with a localhost `baseUrl` → `appUrl` defaults to `http://localhost:3001`
|
|
469
|
+
3. otherwise derived from `baseUrl` by stripping a leading `api.` label (`https://api.example.com` → `https://example.com`), unless `cors.deriveAppUrl: false`
|
|
470
|
+
|
|
471
|
+
> **Security:** step 3 grants the derived origin credentialed CORS. If the apex domain is not trusted (e.g. a third-party-hosted marketing site whose XSS surface you do not control), set `cors.deriveAppUrl: false` and list the frontend origin explicitly via `appUrl` or `cors.allowedOrigins`. The derivation never yields a bare TLD (`https://api.dev` stays unchanged) and never emits the opaque `null` origin.
|
|
465
472
|
|
|
466
473
|
### NestJS Middleware Chain (CoreModule)
|
|
467
474
|
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Migration Guide: 11.27.3 → 11.27.4
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| **Breaking Changes** | None |
|
|
8
|
+
| **New Features** | Opt-in `CHECK_LOW_RESOURCE` e2e mode (keeps parallel test runs stable under load); `check.mjs` now labels signal-killed steps (`SIGTERM`/`SIGKILL`) instead of showing a bare exit code. |
|
|
9
|
+
| **Bugfixes** | Idle watchdog no longer false-kills output-buffering steps (build / typecheck / audit); a stray `SIGKILL` after PID reuse is prevented; an invalid `--idle-timeout` value now falls back to the default instead of silently disabling the watchdog. |
|
|
10
|
+
| **Migration Effort** | 0 minutes for npm consumers (`pnpm update` is enough). Optional: projects that copied `scripts/check.mjs` / `vitest-e2e.config.ts` from the starter can adopt the updated files. |
|
|
11
|
+
|
|
12
|
+
This is a **repo-internal tooling & test-infrastructure release**. It touches
|
|
13
|
+
**no framework source** (`src/` is unchanged) — only `scripts/check.mjs` and
|
|
14
|
+
`vitest-e2e.config.ts`. The npm package (`dist/`) is functionally identical to
|
|
15
|
+
11.27.3; consuming projects need no code or config changes.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick Migration
|
|
20
|
+
|
|
21
|
+
No code changes required.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Update package
|
|
25
|
+
pnpm add @lenne.tech/nest-server@11.27.4
|
|
26
|
+
|
|
27
|
+
# Verify build
|
|
28
|
+
pnpm run build
|
|
29
|
+
|
|
30
|
+
# Run tests
|
|
31
|
+
pnpm test
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> These files ship in the git repository / starter, **not** in the npm package.
|
|
35
|
+
> An npm-mode consumer is unaffected. A project that copied `scripts/check.mjs`
|
|
36
|
+
> and/or `vitest-e2e.config.ts` from the starter (11.27.1+) can copy the updated
|
|
37
|
+
> versions to get the improvements below — see **Compatibility Notes**.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## What's New in 11.27.4
|
|
42
|
+
|
|
43
|
+
### 1. Opt-in low-resource e2e mode (`CHECK_LOW_RESOURCE`)
|
|
44
|
+
|
|
45
|
+
Running several full e2e suites at the same time on one machine (e.g. multiple
|
|
46
|
+
parallel `lt dev` / `lt ticket` environments, or a second terminal) can saturate
|
|
47
|
+
CPU and the shared MongoDB. Under that load individual requests exceed the 30s
|
|
48
|
+
`testTimeout` and auth queries fail — surfacing as intermittent `401`s or
|
|
49
|
+
per-file timeouts. Data isolation was never the cause (every run already gets a
|
|
50
|
+
unique database); the cause is **physical resource contention**.
|
|
51
|
+
|
|
52
|
+
`vitest-e2e.config.ts` now supports an **opt-in** throttle. It changes nothing
|
|
53
|
+
by default — solo and moderately parallel runs stay at full speed:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Full speed (default) — nothing changes
|
|
57
|
+
pnpm test
|
|
58
|
+
|
|
59
|
+
# Opt in when you deliberately run MANY suites in parallel
|
|
60
|
+
CHECK_LOW_RESOURCE=1 pnpm test
|
|
61
|
+
|
|
62
|
+
# Pin the fork cap explicitly (otherwise ~1/3 of CPU cores)
|
|
63
|
+
CHECK_LOW_RESOURCE=1 CHECK_LOW_RESOURCE_FORKS=2 pnpm test
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
| `CHECK_LOW_RESOURCE` | Effect |
|
|
67
|
+
|----------------------|--------|
|
|
68
|
+
| unset / `0` / `false` (default) | No cap, no timeout change — full speed |
|
|
69
|
+
| `1` / `true` | `poolOptions.forks.maxForks` capped (`~cores/3`, min 2), `testTimeout` 30s → 60s, `hookTimeout` 120s → 240s |
|
|
70
|
+
| any value + `CHECK_LOW_RESOURCE_FORKS=<n>` | Fork cap pinned to `<n>` |
|
|
71
|
+
|
|
72
|
+
When active it prints a one-line notice at startup:
|
|
73
|
+
`[e2e] low-resource mode active: maxForks=…, timeouts raised`.
|
|
74
|
+
|
|
75
|
+
### 2. Signal-exit labelling in `scripts/check.mjs`
|
|
76
|
+
|
|
77
|
+
When a step's process is killed by a signal, the package manager surfaces it as
|
|
78
|
+
a numeric `Command failed with exit code 143` (SIGTERM) / `137` (SIGKILL) line,
|
|
79
|
+
while the outer shell reports only a generic exit `1`. The `check` report now
|
|
80
|
+
detects that and appends a clear reason so it isn't mistaken for a test-assertion
|
|
81
|
+
failure:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
[check] step ended via SIGTERM (exit 143) — the process was killed, not an
|
|
85
|
+
assertion failure. Usual cause: resource pressure (parallel checks/builds
|
|
86
|
+
swapping) or an external kill. Re-run this project's check alone to confirm.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The hint is suppressed when the step was killed by the check's own idle
|
|
90
|
+
watchdog (that path already carries its own `[watchdog]` note), and it only
|
|
91
|
+
matches the package manager's own failure line — never an `exit code 143` a test
|
|
92
|
+
happens to log — so real assertion failures are never mislabeled.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## What's Fixed in 11.27.4
|
|
97
|
+
|
|
98
|
+
All fixes are in `scripts/check.mjs`, refining the idle watchdog introduced in
|
|
99
|
+
11.27.x.
|
|
100
|
+
|
|
101
|
+
### Watchdog now applies to test steps only
|
|
102
|
+
|
|
103
|
+
The idle watchdog kills a step whose child produces no output for the timeout
|
|
104
|
+
window (default 300s). It previously watched **every** step. But `build`,
|
|
105
|
+
`typecheck` and `audit` legitimately buffer all their output to the end (and go
|
|
106
|
+
silent under a non-TTY pipe) — watching them risked false-killing a slow but
|
|
107
|
+
progressing run. The watchdog is now armed **only for test steps**, whose
|
|
108
|
+
runners stream progress continuously, so prolonged silence genuinely means
|
|
109
|
+
deadlocked workers. The run header reflects this: `watchdog: 5m 0s (tests)`.
|
|
110
|
+
|
|
111
|
+
### No stray `SIGKILL` after PID reuse
|
|
112
|
+
|
|
113
|
+
When the watchdog fires it sends `SIGTERM`, then escalates to `SIGKILL` after a
|
|
114
|
+
5s grace window. That escalation timer is now tracked and cancelled once the
|
|
115
|
+
child exits within the grace window — previously the stray timer could fire
|
|
116
|
+
later and `SIGKILL` an **unrelated** process that had reused the freed PID.
|
|
117
|
+
|
|
118
|
+
### Invalid `--idle-timeout` falls back to the default
|
|
119
|
+
|
|
120
|
+
An unparseable, negative, or unit-suffixed value (e.g. `--idle-timeout=30s`)
|
|
121
|
+
now keeps the watchdog at its default (300s) and prints a notice, instead of
|
|
122
|
+
silently evaluating to `0` and disabling the protection. Only an explicit `0`
|
|
123
|
+
disables the watchdog.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
--idle-timeout=120 # 120s
|
|
127
|
+
--idle-timeout=0 # explicitly disabled
|
|
128
|
+
--idle-timeout=30s # invalid → default 300s + "[check] ignoring invalid idle-timeout" notice
|
|
129
|
+
CHECK_IDLE_TIMEOUT=90 # env equivalent
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Clearer watchdog reason
|
|
133
|
+
|
|
134
|
+
The `[watchdog]` note now suggests re-running the **actual** failing command
|
|
135
|
+
(`Re-run the step directly to debug: <cmd>`) instead of a hard-coded
|
|
136
|
+
`pnpm run vitest`.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Breaking Changes
|
|
141
|
+
|
|
142
|
+
None.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Compatibility Notes
|
|
147
|
+
|
|
148
|
+
- **npm-mode consumers:** unaffected. `scripts/check.mjs` and
|
|
149
|
+
`vitest-e2e.config.ts` are not part of the npm package (`dist/`); no runtime
|
|
150
|
+
or API behavior changed. `pnpm update` is sufficient.
|
|
151
|
+
- **`IServerOptions` / `CoreModule.forRoot()` / all Core classes:** unchanged.
|
|
152
|
+
No source, export, or method signature changed in this release.
|
|
153
|
+
- **Starter-derived projects** (copied `scripts/check.mjs` / `vitest-e2e.config.ts`
|
|
154
|
+
from nest-server-starter 11.27.1+): copy the updated files to adopt the
|
|
155
|
+
test-only watchdog, the PID-reuse fix, the signal-exit hint, and the opt-in
|
|
156
|
+
`CHECK_LOW_RESOURCE` mode. All changes are backward compatible — the default
|
|
157
|
+
behavior (no env vars set) is unchanged, so an updated `check.mjs` /
|
|
158
|
+
`vitest-e2e.config.ts` is a drop-in replacement.
|
|
159
|
+
- **Vendor-mode consumers:** not applicable — `scripts/` and
|
|
160
|
+
`vitest-e2e.config.ts` are repo/starter tooling, not part of the vendored
|
|
161
|
+
`src/core/` file set. Nothing to sync.
|
|
162
|
+
- **`lt dev` / `lt ticket` parallel workflows:** the throttle is **manual**
|
|
163
|
+
(`CHECK_LOW_RESOURCE=1`). To have it apply automatically inside isolated
|
|
164
|
+
environments, the lt CLI can export it into the test env (the same way it
|
|
165
|
+
exports `LT_DEV_TEST_SHARDS` for the Playwright config). That is a CLI change,
|
|
166
|
+
not a framework change.
|
|
167
|
+
- **Nuxt / Playwright side:** no equivalent needed. The Playwright config
|
|
168
|
+
already uses `workers: 1` plus a `LT_DEV_TEST_SHARDS`-driven relaxed mode for
|
|
169
|
+
load, so the same class of contention is already handled there.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Troubleshooting
|
|
174
|
+
|
|
175
|
+
### My `build` / `typecheck` / `audit` step is no longer killed after 300s
|
|
176
|
+
|
|
177
|
+
Intended. Only **test** steps are watched now (they stream progress). Buffering
|
|
178
|
+
steps that legitimately go silent are no longer at risk of a false watchdog
|
|
179
|
+
kill. If such a step genuinely hangs, it still fails via its own tool timeout or
|
|
180
|
+
the overall run.
|
|
181
|
+
|
|
182
|
+
### The check aborted with "step ended via SIGTERM (exit 143)"
|
|
183
|
+
|
|
184
|
+
That step's process was killed by a signal, not by a failed assertion. The usual
|
|
185
|
+
cause is resource pressure (several heavy checks/builds/test suites running at
|
|
186
|
+
once and swapping the machine) or an external kill. Re-run that project's check
|
|
187
|
+
alone to confirm — it will typically pass.
|
|
188
|
+
|
|
189
|
+
### e2e tests flake (401 / timeouts) only when I run many suites in parallel
|
|
190
|
+
|
|
191
|
+
This is physical CPU/MongoDB contention, not a data-isolation bug (each run has
|
|
192
|
+
its own database). Opt into the throttle for those sessions:
|
|
193
|
+
`CHECK_LOW_RESOURCE=1 pnpm test`. It caps parallel forks and raises timeouts so
|
|
194
|
+
the suites share the machine without starving each other.
|
|
195
|
+
|
|
196
|
+
### `CHECK_LOW_RESOURCE` seems to have no effect
|
|
197
|
+
|
|
198
|
+
Confirm the value is truthy (`1` / `true`) and not `0` / `false` / empty. When
|
|
199
|
+
active, the run prints `[e2e] low-resource mode active: maxForks=…` at startup.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Repo-Internal Tooling Changes (no consumer action)
|
|
204
|
+
|
|
205
|
+
Summary of everything in this release, for projects that track the tooling
|
|
206
|
+
(all listed above in detail):
|
|
207
|
+
|
|
208
|
+
- `scripts/check.mjs`: idle watchdog restricted to test steps; stray-`SIGKILL`
|
|
209
|
+
(PID-reuse) fix; invalid `--idle-timeout` falls back to default instead of
|
|
210
|
+
disabling; watchdog reason shows the actual command; new `signalExitHint`
|
|
211
|
+
labelling `SIGTERM`/`SIGKILL` step exits.
|
|
212
|
+
- `vitest-e2e.config.ts`: opt-in `CHECK_LOW_RESOURCE` mode (fork cap + raised
|
|
213
|
+
timeouts) for stable parallel e2e runs; default behavior unchanged.
|
|
214
|
+
|
|
215
|
+
These carry forward the tooling introduced in
|
|
216
|
+
[11.27.1 → 11.27.2](./11.27.1-to-11.27.2.md) (chain-faithful audit) and
|
|
217
|
+
[11.27.2 → 11.27.3](./11.27.2-to-11.27.3.md) (idle watchdog + per-run test
|
|
218
|
+
databases).
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Module Documentation
|
|
223
|
+
|
|
224
|
+
No core module changed in this release. Relevant testing documentation:
|
|
225
|
+
|
|
226
|
+
- **Testing rules:** [.claude/rules/testing.md](../.claude/rules/testing.md) —
|
|
227
|
+
test framework, per-run database lifecycle, parallel execution
|
|
228
|
+
- **Reference implementation:** [nest-server-starter](https://github.com/lenneTech/nest-server-starter)
|
|
229
|
+
— carries the same `scripts/check.mjs` + `vitest-e2e.config.ts`
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## References
|
|
234
|
+
|
|
235
|
+
- [Migration Guide 11.27.2 → 11.27.3](./11.27.2-to-11.27.3.md) — Previous release (verification-email crash fix + idle watchdog + per-run test DBs)
|
|
236
|
+
- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) — reference implementation
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Migration Guide: 11.27.4 → 11.27.5
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| **Breaking Changes** | None (API-compatible). One **behavior change**: a config that sets only `baseUrl` now also trusts the derived app origin in the REST/GraphQL CORS allowlist — see [Behavior Change](#behavior-change-cors-allowlist-derives-appurl). |
|
|
8
|
+
| **New Features** | `cors.deriveAppUrl` opt-out; exported `resolveServerUrls()` and `deriveAppUrlFromBaseUrl()` helpers. |
|
|
9
|
+
| **Bugfixes** | REST/GraphQL CORS blocked the frontend origin on `api.<host>` deployments and in `local`/`ci`/`e2e`; the `api.`-strip could produce a bare TLD or the opaque `null` origin; trailing-slash `baseUrl` produced a never-matching origin entry. |
|
|
10
|
+
| **Security Updates** | `better-auth` + `@better-auth/passkey` `1.6.11` → `1.6.23` (GHSA-86j7-9j95-vpqj, High); `@xhmikosr/decompress` pinned to `11.1.3` via override (GHSA-mp2f-45pm-3cg9, Critical). |
|
|
11
|
+
| **Migration Effort** | 0 minutes for most projects (`pnpm update`). ~2 minutes if your apex domain is untrusted — see below. |
|
|
12
|
+
|
|
13
|
+
The three CORS layers (GraphQL/Apollo, REST/Express, BetterAuth `trustedOrigins`)
|
|
14
|
+
previously each resolved `appUrl`/`baseUrl` on their own and had drifted. They now
|
|
15
|
+
share a single `resolveServerUrls()` helper, so they cannot disagree.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick Migration
|
|
20
|
+
|
|
21
|
+
No code changes required for the common case.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Update package
|
|
25
|
+
pnpm add @lenne.tech/nest-server@11.27.5
|
|
26
|
+
|
|
27
|
+
# Verify build
|
|
28
|
+
pnpm run build
|
|
29
|
+
|
|
30
|
+
# Run tests
|
|
31
|
+
pnpm test
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## What's Fixed in 11.27.5
|
|
37
|
+
|
|
38
|
+
### 1. `api.<host>` deployments: the frontend origin was blocked
|
|
39
|
+
|
|
40
|
+
A deployment that sets only `baseUrl` (the typical `NSC__BASE_URL` /
|
|
41
|
+
`BASE_URL` setup) got a CORS allowlist containing just the API's own origin.
|
|
42
|
+
BetterAuth already derived `https://example.com` from
|
|
43
|
+
`https://api.example.com` for its `trustedOrigins`, but the REST and GraphQL
|
|
44
|
+
layers did not — so IAM endpoints worked cross-origin while every other
|
|
45
|
+
endpoint failed preflight.
|
|
46
|
+
|
|
47
|
+
Both layers now derive `appUrl` identically:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// config.env.ts — no appUrl needed
|
|
51
|
+
baseUrl: 'https://api.example.com',
|
|
52
|
+
// → CORS allowlist: ['https://example.com', 'https://api.example.com']
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. `local` / `ci` / `e2e`: localhost defaults were not applied to CORS
|
|
56
|
+
|
|
57
|
+
`resolveUrls()` in BetterAuth mapped these environments to
|
|
58
|
+
`appUrl = http://localhost:3001` (API on `:3000`, app on `:3001`).
|
|
59
|
+
`buildCorsConfig()` had no such branch, so the frontend was blocked. The
|
|
60
|
+
shared resolver now applies the same defaults on every layer.
|
|
61
|
+
|
|
62
|
+
> The framework's own `development` environment is **not** one of the
|
|
63
|
+
> localhost-default environments. `src/config.env.ts` now sets
|
|
64
|
+
> `appUrl: process.env.APP_URL || 'http://localhost:3001'` there explicitly.
|
|
65
|
+
> Projects that copied the `development` block from the starter and run their
|
|
66
|
+
> frontend on `:3001` should do the same.
|
|
67
|
+
|
|
68
|
+
### 3. Hardened `api.`-label stripping
|
|
69
|
+
|
|
70
|
+
| `baseUrl` | Before | After |
|
|
71
|
+
|-----------|--------|-------|
|
|
72
|
+
| `https://api.example.com` | `https://example.com` | unchanged |
|
|
73
|
+
| `https://api.dev.example.com` | `https://dev.example.com` | unchanged |
|
|
74
|
+
| `https://api.dev` | `https://dev` (bare TLD!) | `https://api.dev` (no strip) |
|
|
75
|
+
| `https://api.` | `https://api.` (silent no-op) | `https://api.` (explicit no-op) |
|
|
76
|
+
| `custom://api.example.com` | `"null"` (opaque origin!) | `custom://api.example.com` |
|
|
77
|
+
| `https://api.example.com/` | `https://example.com` + dead `…/` entry | both entries origin-normalized |
|
|
78
|
+
|
|
79
|
+
The `"null"` case was the most serious: `URL.origin` serializes opaque origins
|
|
80
|
+
to the literal string `"null"`, which is exactly the `Origin` header a
|
|
81
|
+
sandboxed iframe sends. In a `credentials: true` allowlist that would have
|
|
82
|
+
granted credentialed access to any site able to frame a sandboxed document.
|
|
83
|
+
Only `http:`/`https:` URLs are normalized to origins now.
|
|
84
|
+
|
|
85
|
+
### 4. Security updates in dependencies
|
|
86
|
+
|
|
87
|
+
| Package | Change | Advisory |
|
|
88
|
+
|---------|--------|----------|
|
|
89
|
+
| `better-auth` | `1.6.11` → `1.6.23` (direct) | [GHSA-86j7-9j95-vpqj](https://github.com/advisories/GHSA-86j7-9j95-vpqj) — High: stored XSS in the auth-server origin via a `javascript:` `redirect_uri` in `oidc-provider` and `mcp` |
|
|
90
|
+
| `@better-auth/passkey` | `1.6.11` → `1.6.23` (direct) | Kept in lockstep — peers on `better-auth@^1.6.23` |
|
|
91
|
+
| `@xhmikosr/decompress` | pinned to `11.1.3` via `pnpm.overrides` | [GHSA-mp2f-45pm-3cg9](https://github.com/advisories/GHSA-mp2f-45pm-3cg9) — Critical: archive extraction can create files/links outside the target directory |
|
|
92
|
+
|
|
93
|
+
`@xhmikosr/decompress` is transitive via `@swc/cli > @xhmikosr/bin-wrapper > @xhmikosr/downloader`.
|
|
94
|
+
`@swc/cli@0.8.1` is the latest release and still resolves the vulnerable range, so an override
|
|
95
|
+
with a fixed target is the only available fix.
|
|
96
|
+
|
|
97
|
+
Both `better-auth` bumps stay inside `1.6.x` — no BetterAuth API change. The full suite
|
|
98
|
+
(2019 tests) passes unchanged.
|
|
99
|
+
|
|
100
|
+
npm-mode consumers get all three fixes with a plain `pnpm update`. Vendor-mode consumers must
|
|
101
|
+
bump `better-auth`/`@better-auth/passkey` in their own `package.json` and copy the
|
|
102
|
+
`@xhmikosr/decompress` override — the vendored file set does not carry `package.json`.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Behavior Change: CORS allowlist derives `appUrl`
|
|
107
|
+
|
|
108
|
+
**Who is affected:** projects that configure `baseUrl` but neither `appUrl`
|
|
109
|
+
nor `cors.allowedOrigins`, and whose apex domain is not under their control.
|
|
110
|
+
|
|
111
|
+
Deriving `appUrl` means the apex origin (`https://example.com`) receives
|
|
112
|
+
**credentialed** cross-origin access (`Access-Control-Allow-Credentials: true`).
|
|
113
|
+
That is the intended, documented `appUrl` auto-detection and matches what
|
|
114
|
+
BetterAuth's `trustedOrigins` has always done — but it now also applies to the
|
|
115
|
+
REST and GraphQL layers.
|
|
116
|
+
|
|
117
|
+
If your apex is a third-party-hosted marketing site (WordPress, Webflow,
|
|
118
|
+
HubSpot) whose XSS surface you do not control, opt out:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// config.env.ts
|
|
122
|
+
{
|
|
123
|
+
baseUrl: 'https://api.example.com',
|
|
124
|
+
cors: {
|
|
125
|
+
// Do not trust https://example.com implicitly
|
|
126
|
+
deriveAppUrl: false,
|
|
127
|
+
// List the real frontend origin instead
|
|
128
|
+
allowedOrigins: ['https://app.example.com'],
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`deriveAppUrl: false` suppresses the derivation on **all three** layers
|
|
134
|
+
(GraphQL, REST, BetterAuth `trustedOrigins`). It has no effect when `appUrl`
|
|
135
|
+
is set explicitly, and it does not disable the `local`/`ci`/`e2e` localhost
|
|
136
|
+
defaults.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## What's New in 11.27.5
|
|
141
|
+
|
|
142
|
+
### `cors.deriveAppUrl`
|
|
143
|
+
|
|
144
|
+
| Value | Effect |
|
|
145
|
+
|-------|--------|
|
|
146
|
+
| `true` (default) | `appUrl` is derived from `baseUrl` by stripping a leading `api.` label |
|
|
147
|
+
| `false` | No derivation; configure `appUrl` / `cors.allowedOrigins` explicitly |
|
|
148
|
+
|
|
149
|
+
### Exported URL helpers
|
|
150
|
+
|
|
151
|
+
Both are exported from the package root (`cookies.helper.ts`):
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { deriveAppUrlFromBaseUrl, resolveServerUrls } from '@lenne.tech/nest-server';
|
|
155
|
+
|
|
156
|
+
deriveAppUrlFromBaseUrl('https://api.example.com'); // 'https://example.com'
|
|
157
|
+
|
|
158
|
+
resolveServerUrls({ baseUrl: 'https://api.example.com' });
|
|
159
|
+
// { appUrl: 'https://example.com', appUrlSource: 'derived',
|
|
160
|
+
// baseUrl: 'https://api.example.com', baseUrlSource: 'explicit' }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`appUrlSource` / `baseUrlSource` (`'explicit' | 'localhost-default' | 'derived' | 'none'`)
|
|
164
|
+
let callers emit accurate startup diagnostics without re-deriving the resolution logic.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Breaking Changes
|
|
169
|
+
|
|
170
|
+
None. `ICorsConfig` gained an optional field; no signature, export, or Core
|
|
171
|
+
class changed.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Compatibility Notes
|
|
176
|
+
|
|
177
|
+
- **npm-mode consumers:** `pnpm update` is sufficient. Review the
|
|
178
|
+
[behavior change](#behavior-change-cors-allowlist-derives-appurl) if your
|
|
179
|
+
apex domain is untrusted.
|
|
180
|
+
- **Vendor-mode consumers:** the change touches `src/core/common/helpers/cookies.helper.ts`,
|
|
181
|
+
`src/core/common/interfaces/server-options.interface.ts` and
|
|
182
|
+
`src/core/modules/better-auth/better-auth.config.ts` — all inside the vendored
|
|
183
|
+
`src/core/` file set. Sync via `/lt-dev:backend:update-nest-server-core`.
|
|
184
|
+
`better-auth.config.ts` now imports `resolveServerUrls` from
|
|
185
|
+
`../../common/helpers/cookies.helper` (still inside `src/core/`, so the
|
|
186
|
+
self-containment rule holds).
|
|
187
|
+
- **Projects that set `appUrl` explicitly:** unaffected. Explicit values are
|
|
188
|
+
never overridden by the derivation.
|
|
189
|
+
- **Projects that set `cors.allowAll: true`:** unaffected. `allowAll` short-circuits
|
|
190
|
+
before URL resolution.
|
|
191
|
+
- **Passkey / WebAuthn in `development`:** if you adopt the new
|
|
192
|
+
`appUrl: 'http://localhost:3001'` in the `development` block, the Passkey
|
|
193
|
+
`origin` becomes `http://localhost:3001` (where the browser actually runs)
|
|
194
|
+
instead of the API's `:3000`. This is a fix — WebAuthn ceremonies from the
|
|
195
|
+
frontend previously used the wrong origin.
|
|
196
|
+
- **A duplicate private `deriveAppUrlFromBaseUrl()` was removed** from
|
|
197
|
+
`better-auth.config.ts`. It was never exported; nothing to migrate.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Troubleshooting
|
|
202
|
+
|
|
203
|
+
### My frontend is still blocked by CORS
|
|
204
|
+
|
|
205
|
+
Check, in order:
|
|
206
|
+
|
|
207
|
+
1. `cors.enabled` is not `false`.
|
|
208
|
+
2. `cookies` is not disabled — `buildCorsConfig()` returns `{}` when cookies
|
|
209
|
+
are off (REST then falls back to permissive `enableCors()` without credentials).
|
|
210
|
+
3. Your frontend origin is actually derivable. `baseUrl: 'https://my-api.example.com'`
|
|
211
|
+
has no `api.` label to strip, so `appUrl` resolves to the API's own origin.
|
|
212
|
+
Set `appUrl` explicitly.
|
|
213
|
+
4. `env` is one of `local`/`ci`/`e2e` if you rely on the `localhost:3001` default.
|
|
214
|
+
`development` is not — set `appUrl` there.
|
|
215
|
+
|
|
216
|
+
### My apex domain unexpectedly appears in the allowlist
|
|
217
|
+
|
|
218
|
+
That is the `appUrl` derivation. Set `cors.deriveAppUrl: false` and list the
|
|
219
|
+
frontend origin explicitly. See
|
|
220
|
+
[Behavior Change](#behavior-change-cors-allowlist-derives-appurl).
|
|
221
|
+
|
|
222
|
+
### `https://api.dev` no longer derives an app origin
|
|
223
|
+
|
|
224
|
+
Intended. Stripping would leave the bare TLD `dev`, which is not a deployable
|
|
225
|
+
host. Set `appUrl` explicitly for such domains.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Module Documentation
|
|
230
|
+
|
|
231
|
+
- **Request lifecycle & CORS:** [docs/REQUEST-LIFECYCLE.md](../docs/REQUEST-LIFECYCLE.md) — section `0b. CORS`
|
|
232
|
+
- **Configurable features:** [.claude/rules/configurable-features.md](../.claude/rules/configurable-features.md) — `CORS` row
|
|
233
|
+
- **BetterAuth:** [src/core/modules/better-auth/README.md](../src/core/modules/better-auth/README.md)
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## References
|
|
238
|
+
|
|
239
|
+
- [Migration Guide 11.27.3 → 11.27.4](./11.27.3-to-11.27.4.md) — Previous release (test tooling)
|
|
240
|
+
- [Migration Guide 11.24.x → 11.25.0](./11.24.x-to-11.25.0.md) — Introduced the unified `cors` config
|
|
241
|
+
- [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.27.
|
|
3
|
+
"version": "11.27.5",
|
|
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",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@apollo/server": "5.5.1",
|
|
79
79
|
"@as-integrations/express5": "1.1.2",
|
|
80
|
-
"@better-auth/passkey": "1.6.
|
|
80
|
+
"@better-auth/passkey": "1.6.23",
|
|
81
81
|
"@getbrevo/brevo": "3.0.1",
|
|
82
82
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
83
83
|
"@nestjs/apollo": "13.4.2",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"@tus/server": "2.4.1",
|
|
97
97
|
"@types/supertest": "7.2.0",
|
|
98
98
|
"bcrypt": "6.0.0",
|
|
99
|
-
"better-auth": "1.6.
|
|
99
|
+
"better-auth": "1.6.23",
|
|
100
100
|
"class-transformer": "0.5.1",
|
|
101
101
|
"class-validator": "0.15.1",
|
|
102
102
|
"compression": "1.8.1",
|
|
@@ -216,7 +216,8 @@
|
|
|
216
216
|
"hono@<4.12.25": "Security: multiple CVEs <4.12.25 (prototype pollution, bodyLimit/Vary bypass, JWT NumericDate) - transitive via @nestjs/terminus>prisma>@prisma/dev",
|
|
217
217
|
"nodemailer@<9.0.1": "Security: email/header injection CVEs <9.0.1 - direct dependency",
|
|
218
218
|
"multer@<2.2.0": "Security: unhandled multipart errors / DoS <2.2.0 - transitive via @nestjs/platform-express",
|
|
219
|
-
"js-yaml@<4.2.0": "Security: special-character handling / prototype pollution (patched in 4.2.0; 4.1.2 was never published) - transitive via @nestjs/swagger"
|
|
219
|
+
"js-yaml@<4.2.0": "Security: special-character handling / prototype pollution (patched in 4.2.0; 4.1.2 was never published) - transitive via @nestjs/swagger",
|
|
220
|
+
"@xhmikosr/decompress@<11.1.3": "Security: archive extraction can create files/links outside the target directory (GHSA-mp2f-45pm-3cg9, critical) - transitive via @swc/cli>@xhmikosr/bin-wrapper>@xhmikosr/downloader; @swc/cli 0.8.1 is already the latest release and still resolves the vulnerable range, so an override is the only fix"
|
|
220
221
|
},
|
|
221
222
|
"overrides": {
|
|
222
223
|
"axios@<1.16.0": "1.16.0",
|
|
@@ -252,7 +253,8 @@
|
|
|
252
253
|
"hono@<4.12.25": "4.12.25",
|
|
253
254
|
"nodemailer@<9.0.1": "9.0.1",
|
|
254
255
|
"multer@<2.2.0": "2.2.0",
|
|
255
|
-
"js-yaml@<4.2.0": "4.2.0"
|
|
256
|
+
"js-yaml@<4.2.0": "4.2.0",
|
|
257
|
+
"@xhmikosr/decompress@<11.1.3": "11.1.3"
|
|
256
258
|
},
|
|
257
259
|
"//peerDependencyRules": "allowedVersions: deps lag behind our newer majors (graphql-upload wants @types/express@^4, the deprecated apollo playground plugin wants @apollo/server@^4) — both work with our v5. ignoreMissing: browser-only vis-network peers pulled in transitively via yuml-diagram (server-side UML generation never renders, so these are not needed).",
|
|
258
260
|
"peerDependencyRules": {
|
package/src/config.env.ts
CHANGED
|
@@ -170,6 +170,10 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
170
170
|
// Development environment
|
|
171
171
|
// ===========================================================================
|
|
172
172
|
development: {
|
|
173
|
+
// `development` is not one of the localhost-default environments (local/ci/e2e), so appUrl
|
|
174
|
+
// must be set explicitly: deriving it from baseUrl would yield the API's own :3000 origin
|
|
175
|
+
// and leave the frontend on :3001 outside the CORS allowlist.
|
|
176
|
+
appUrl: process.env.APP_URL || 'http://localhost:3001',
|
|
173
177
|
auth: {
|
|
174
178
|
legacyEndpoints: { enabled: true },
|
|
175
179
|
},
|