@lenne.tech/nest-server 11.41.6 → 11.41.8
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/better-auth.md +26 -22
- package/.claude/rules/configurable-features.md +1 -1
- package/.claude/rules/package-management.md +8 -0
- package/.claude/rules/testing.md +13 -10
- package/CLAUDE.md +6 -4
- package/FRAMEWORK-API.md +1 -1
- package/dist/core/common/helpers/file.helper.js.map +1 -1
- package/dist/core/common/services/core-s3.service.d.ts +8 -0
- package/dist/core/common/services/core-s3.service.js +44 -3
- package/dist/core/common/services/core-s3.service.js.map +1 -1
- package/dist/core/modules/ai/core-ai-mcp.controller.d.ts +1 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js +10 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-account-issuer.helper.d.ts +2 -0
- package/dist/core/modules/better-auth/core-better-auth-account-issuer.helper.js +13 -0
- package/dist/core/modules/better-auth/core-better-auth-account-issuer.helper.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +3 -2
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.constants.d.ts +1 -0
- package/dist/core/modules/better-auth/core-better-auth.constants.js +2 -1
- package/dist/core/modules/better-auth/core-better-auth.constants.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.d.ts +1 -0
- package/dist/core/modules/better-auth/core-better-auth.service.js +46 -3
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/core/modules/file/core-file.service.js +3 -2
- package/dist/core/modules/file/core-file.service.js.map +1 -1
- package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +1 -0
- package/dist/core/modules/file/s3-file.helper.d.ts +1 -0
- package/dist/core/modules/file/s3-file.helper.js +10 -4
- package/dist/core/modules/file/s3-file.helper.js.map +1 -1
- package/dist/core/modules/system-setup/core-system-setup.service.js +3 -2
- package/dist/core/modules/system-setup/core-system-setup.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +1 -1
- package/docs/security-overrides.md +1 -1
- package/migration-guides/11.41.6-to-11.41.7.md +192 -0
- package/migration-guides/11.41.7-to-11.41.8.md +135 -0
- package/package.json +24 -22
- package/src/core/common/helpers/file.helper.ts +4 -0
- package/src/core/common/helpers/gridfs.helper.ts +1 -1
- package/src/core/common/services/core-s3.service.ts +125 -7
- package/src/core/modules/ai/README.md +8 -0
- package/src/core/modules/ai/core-ai-mcp.controller.ts +33 -0
- package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +2 -2
- package/src/core/modules/better-auth/README.md +20 -6
- package/src/core/modules/better-auth/core-better-auth-account-issuer.helper.ts +25 -0
- package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +7 -7
- package/src/core/modules/better-auth/core-better-auth.constants.ts +20 -11
- package/src/core/modules/better-auth/core-better-auth.service.ts +87 -3
- package/src/core/modules/file/INTEGRATION-CHECKLIST.md +39 -11
- package/src/core/modules/file/README.md +30 -5
- package/src/core/modules/file/core-file.service.ts +15 -6
- package/src/core/modules/file/interfaces/file-upload.interface.ts +18 -0
- package/src/core/modules/file/s3-file.helper.ts +28 -9
- package/src/core/modules/system-setup/core-system-setup.service.ts +12 -8
|
@@ -42,7 +42,7 @@ 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 (cluster-wide when `redis` is configured — see the Subscriptions row under GraphQL Features) |
|
|
44
44
|
| **Central Redis (optional)** | `CoreRedisService` — globally provided **and exported** by `CoreModule`, always present but **inert unless `redis` is configured** ("presence implies enabled"). Injected with `@Optional()`; every consumer keeps a process-local fallback. One service serves all features: shared client (`getClient()`), one cached subscriber (`getSubscriber()` — a subscribing client cannot run commands), dedicated connections (`createClient(label)`); all tracked and quit on shutdown. Keys are namespaced by the framework per key via `key(...)`, **not** through ioredis's own `keyPrefix` (that would collide with BullMQ's prefix). Requires the OPTIONAL peer `ioredis` — configured but missing **fails the boot**. Switches on: exact cross-replica rate limits, cron deduplication, `CoreRedisPubSub` as `PUB_SUB`, tenant-cache invalidation broadcast, Hub collector mirroring, MCP session registry |
|
|
45
|
-
| **Central S3 (optional)** | `CoreS3Service` — globally provided **and exported** by `CoreModule`, inert unless `s3` is configured *and* names a `bucket` (a bucket-less block is ignored with a warning). Backs `file.storage: 's3'` and TUS staging (`tus.s3Staging`). Requires the OPTIONAL peers `@aws-sdk/client-s3` (+ `@aws-sdk/s3-request-presigner` for `presignedDownloads`) — configured but missing **fails the boot
|
|
45
|
+
| **Central S3 (optional)** | `CoreS3Service` — globally provided **and exported** by `CoreModule`, inert unless `s3` is configured *and* names a `bucket` (a bucket-less block is ignored with a warning). Backs `file.storage: 's3'` and TUS staging (`tus.s3Staging`). Requires the OPTIONAL peers `@aws-sdk/client-s3` (+ `@aws-sdk/s3-request-presigner` for `presignedDownloads`) — configured but missing **fails the boot**. `@aws-sdk/lib-storage` (optional) uploads streams of unknown length in parts; without it they are buffered in memory, with one boot warning |
|
|
46
46
|
| **Configuration System** | `config.env.ts` with ENV variables, `NEST_SERVER_CONFIG` JSON, `NSC__*` prefixes |
|
|
47
47
|
| **Cookie Handling** | Enabled by default (`cookies: true`), configurable via `ICookiesConfig` with `exposeTokenInBody` option |
|
|
48
48
|
| **Unified CORS** | Single `cors` config propagates to GraphQL, REST, and BetterAuth layers |
|
|
@@ -18,7 +18,7 @@ The framework pulls in three transitive packages that its `@nestjs/*` dependenci
|
|
|
18
18
|
|---------|----------|------------------------------------------|
|
|
19
19
|
| `ws` | [GHSA-96hv-2xvq-fx4p](https://github.com/advisories/GHSA-96hv-2xvq-fx4p) — high: memory-exhaustion DoS + uninitialized memory disclosure. Patched `>=8.21.0` | Older `@nestjs/graphql` releases declare `"ws": "8.20.1"` — an **exact pin**, not a caret. 13.4.5, the version this framework declares, pins `8.21.3` |
|
|
20
20
|
| `js-yaml` | [GHSA-pm4m-ph32-ghv5](https://github.com/advisories/GHSA-pm4m-ph32-ghv5) — high: exponential parsing time in flow collections (DoS), patched `>=5.2.2`; [GHSA-r3ph-w7gj-g6xm](https://github.com/advisories/GHSA-r3ph-w7gj-g6xm) — moderate: `maxTotalMergeKeys` does not bound CPU use for empty merge sources, covers `<=5.4.0`, patched `>=5.4.1` (published 2026-09-29) | `@nestjs/swagger` declares js-yaml as an **exact pin**, the same shape as the `ws` case: older releases `5.2.1`, 11.4.7 — the version this framework declares, and the newest 11.x — `5.3.0`. No swagger update reaches the fix, so **this entry is load-bearing again** |
|
|
21
|
-
| `multer` | [GHSA-wc9g-mqfw-jrwm](https://github.com/advisories/GHSA-wc9g-mqfw-jrwm), [GHSA-535w-7cp7-47q4](https://github.com/advisories/GHSA-535w-7cp7-47q4), [GHSA-qfvm-cv95-jqjf](https://github.com/advisories/GHSA-qfvm-cv95-jqjf) — high: DoS via crafted multipart input; [GHSA-qvfw-j98x-7q72](https://github.com/advisories/GHSA-qvfw-j98x-7q72) — low: file size limit bypass. Patched `>=2.3.0` | `@nestjs/platform-express` up to 11.2.5 declares `"multer": "2.2.0"` — an **exact pin**. A direct `multer` dependency does not move it: you get both copies, and FileInterceptor uses the vulnerable one. 11.2.6,
|
|
21
|
+
| `multer` | [GHSA-wc9g-mqfw-jrwm](https://github.com/advisories/GHSA-wc9g-mqfw-jrwm), [GHSA-535w-7cp7-47q4](https://github.com/advisories/GHSA-535w-7cp7-47q4), [GHSA-qfvm-cv95-jqjf](https://github.com/advisories/GHSA-qfvm-cv95-jqjf) — high: DoS via crafted multipart input; [GHSA-qvfw-j98x-7q72](https://github.com/advisories/GHSA-qvfw-j98x-7q72) — low: file size limit bypass. Patched `>=2.3.0` | `@nestjs/platform-express` up to 11.2.5 declares `"multer": "2.2.0"` — an **exact pin**. A direct `multer` dependency does not move it: you get both copies, and FileInterceptor uses the vulnerable one. 11.2.6, which this framework declared from 11.41.4, pins `2.4.0`, and so does 11.2.7, declared since 11.41.7 |
|
|
22
22
|
|
|
23
23
|
**Status since 11.41.5:** `ws` and `multer` resolve to a patched version on their own with the
|
|
24
24
|
`@nestjs/*` versions this framework declares; their entries below are insurance for a project whose
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Migration Guide: 11.41.6 → 11.41.7
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
| --------------------- | ------------------------------------------------------------------------------------------- |
|
|
7
|
+
| **Breaking Changes** | None in npm mode. Vendor mode: one package to install (§1) |
|
|
8
|
+
| **Bugfixes** | Uploads to S3 are no longer read into memory — a large upload could OOM-kill the container. A known length above 5 GiB (TUS) no longer fails on AWS S3. MCP clients reconnect on their own after a restart (§4) |
|
|
9
|
+
| **New optional peer** | `@aws-sdk/lib-storage` — uploads of unknown length go to S3 in parts |
|
|
10
|
+
| **New field** | `FileUploadSource.size` — the exact file length, when the caller knows it |
|
|
11
|
+
| **Migration Effort** | `pnpm run update` (raises the `@nestjs/*` pins, §6). Projects using the S3 driver: install one package. Starter-based projects: assess two dev-only advisories (§5) |
|
|
12
|
+
|
|
13
|
+
## Quick Migration
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm update @lenne.tech/nest-server
|
|
17
|
+
pnpm run update # starter-based projects: raises the @nestjs/* pins to 11.2.7 (§6)
|
|
18
|
+
pnpm add @aws-sdk/lib-storage@<your @aws-sdk/client-s3 version> # only with file.storage 's3'
|
|
19
|
+
pnpm run build && pnpm test
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Vendor-mode projects must install it, whether they use S3 or not.** The vendored core is compiled
|
|
23
|
+
as project code, and `core-s3.service.ts` now references the types of `@aws-sdk/lib-storage` — the
|
|
24
|
+
same way it already references `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner`. Without the
|
|
25
|
+
package the typecheck fails with `Cannot find module '@aws-sdk/lib-storage'`. A project converted to
|
|
26
|
+
vendor mode from now on gets it from the CLI's import scan; the core update of an EXISTING vendor
|
|
27
|
+
project does not add packages, so add it there yourself, next to `@aws-sdk/client-s3`, at the same
|
|
28
|
+
version.
|
|
29
|
+
|
|
30
|
+
## What's New in 11.41.7
|
|
31
|
+
|
|
32
|
+
### 1. Uploads reach S3 as a stream
|
|
33
|
+
|
|
34
|
+
Until now the S3 driver read every upload into a Buffer before sending it: `createFile()` did so for
|
|
35
|
+
every GraphQL and multer upload, and `CoreS3Service.putObject()` for every stream without a length.
|
|
36
|
+
The memory cost of an upload was therefore its size. A multi-GB file threw
|
|
37
|
+
`Array buffer allocation failed` or got the container OOM-killed at 100 % progress — taking every
|
|
38
|
+
other request on that replica with it.
|
|
39
|
+
|
|
40
|
+
Now nothing on that path buffers:
|
|
41
|
+
|
|
42
|
+
| Length | Request | Memory |
|
|
43
|
+
| ------ | ------- | ------ |
|
|
44
|
+
| known, up to 5 GiB — `size` on the upload, a TUS upload, a buffer | one streamed `PutObject` | constant |
|
|
45
|
+
| known, above 5 GiB | multipart upload via `@aws-sdk/lib-storage` | at most four parts |
|
|
46
|
+
| unknown — every GraphQL and streamed REST upload | multipart upload via `@aws-sdk/lib-storage` | at most four 5 MiB parts |
|
|
47
|
+
| `@aws-sdk/lib-storage` not installed | as before: an unknown length is read into memory, a known one is one `PutObject` | the file's size (unknown length) |
|
|
48
|
+
|
|
49
|
+
The second row is a fix in its own right: AWS S3 refuses a single `PutObject` above 5 GiB
|
|
50
|
+
(`EntityTooLarge`). A TUS upload that large — the default `tus.maxSize` is 50 GiB — used to fail at
|
|
51
|
+
finalization, after the client had sent every byte, on every retry. MinIO and RustFS accept such a
|
|
52
|
+
request, which is why test setups never showed it.
|
|
53
|
+
|
|
54
|
+
The length recorded for an upload of unknown length is read back from the stored object.
|
|
55
|
+
|
|
56
|
+
**What to do:** with `file.storage: 's3'` (or an `s3` config that makes it the derived driver),
|
|
57
|
+
install `@aws-sdk/lib-storage` at the same version as your `@aws-sdk/client-s3`:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pnpm add @aws-sdk/lib-storage@3.1145.0 # match your @aws-sdk/client-s3
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Without it nothing breaks — uploads keep the previous behaviour, and the boot log says once:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Streaming an S3 upload of unknown length falls back to buffering it in memory because the optional
|
|
67
|
+
peer dependency "@aws-sdk/lib-storage" is not installed. Run: pnpm add @aws-sdk/lib-storage
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use an S3-compatible store that supports multipart uploads (AWS S3, MinIO and RustFS do). Give the
|
|
71
|
+
bucket a lifecycle rule that aborts incomplete multipart uploads: a failed upload is aborted by the
|
|
72
|
+
framework, but a process killed mid-upload leaves its parts behind, and S3 keeps them until something
|
|
73
|
+
removes them.
|
|
74
|
+
|
|
75
|
+
### 2. `FileUploadSource.size`
|
|
76
|
+
|
|
77
|
+
A caller that knows the file's EXACT length can say so, and the S3 driver then uses one streamed
|
|
78
|
+
request instead of a multipart upload (up to 5 GiB; above that it stays a multipart upload, with
|
|
79
|
+
parts sized to the total):
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
await fileService.createFile({
|
|
83
|
+
createReadStream: () => createReadStream(path),
|
|
84
|
+
filename,
|
|
85
|
+
mimetype,
|
|
86
|
+
size: (await stat(path)).size,
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Leaving it out is always safe — it only costs the multipart round trips — and for data that is
|
|
91
|
+
already in memory it is the better choice: the multipart path sends Buffer parts, which the SDK
|
|
92
|
+
retries after a transient S3 error, while one streamed request is attempted once.
|
|
93
|
+
`multerFileToUpload()` therefore does not set it.
|
|
94
|
+
|
|
95
|
+
**Never pass the `Content-Length` of a multipart/form-data request.** It counts the whole envelope
|
|
96
|
+
(boundaries, part headers, other fields) and is always larger than the file, and a wrong length
|
|
97
|
+
breaks the upload. Anything but a positive integer is treated as unknown.
|
|
98
|
+
|
|
99
|
+
### 3. `CoreS3Service.statObject()`
|
|
100
|
+
|
|
101
|
+
New: size and content type of an object, or `null` when it does not exist — one HEAD request.
|
|
102
|
+
`objectExists()` now delegates to it.
|
|
103
|
+
|
|
104
|
+
### 4. MCP clients recover from a restart
|
|
105
|
+
|
|
106
|
+
`/ai/mcp` keeps its sessions in process memory, so every restart — every deploy — forgets the
|
|
107
|
+
session ids its clients still hold. A request carrying such an id used to get a fresh transport,
|
|
108
|
+
which answered the client's next call (a `tools/call`, never an `initialize`) with
|
|
109
|
+
`400 Bad Request: Server not initialized`. Clients do not recover from a 400: every connected
|
|
110
|
+
client stayed dead until somebody reconnected it by hand.
|
|
111
|
+
|
|
112
|
+
Such a request now gets **404** `Unknown or expired MCP session` — the status the MCP specification
|
|
113
|
+
tells clients to answer with a new `initialize`, and what the SDK's own server returns. An
|
|
114
|
+
`initialize` carrying a stale id still starts a new session. With `redis` configured, a session
|
|
115
|
+
that lives on another replica of the same user still answers **409**, as before.
|
|
116
|
+
|
|
117
|
+
Nothing to do. A client or proxy that treated the old 400 as "session gone" sees 404 instead.
|
|
118
|
+
|
|
119
|
+
### 5. Two advisories without a fix in the dev tooling
|
|
120
|
+
|
|
121
|
+
Two HIGH advisories surfaced in the audit on 2026-10-03, and neither has a patched release
|
|
122
|
+
anywhere yet:
|
|
123
|
+
|
|
124
|
+
| Advisory | Package | Reaches a project through |
|
|
125
|
+
| -------- | ------- | ------------------------- |
|
|
126
|
+
| GHSA-ch52-4w7c-c8xp | `http-cache-semantics` <= 4.2.0 | `@swc/cli` (also via `@nestjs/cli`) — its binary downloader |
|
|
127
|
+
| GHSA-vfj7-8cjw-p6xm | `braces` <= 3.0.3 | `nodemon` > `chokidar@3`; `cpy-cli` > `micromatch` |
|
|
128
|
+
|
|
129
|
+
Both paths are dev tooling, and no override can help while no fixed version exists. So
|
|
130
|
+
`pnpm audit` — and every `check` that runs it — is red in a starter-based project until you
|
|
131
|
+
assess them.
|
|
132
|
+
|
|
133
|
+
**What to do:** confirm neither package is in your production tree
|
|
134
|
+
(`pnpm why http-cache-semantics --prod`, `pnpm why braces --prod` — both must print nothing),
|
|
135
|
+
then suppress them in `pnpm-workspace.yaml` with the reasoning next to each entry. The starter's
|
|
136
|
+
file shows the shape:
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
auditConfig:
|
|
140
|
+
ignoreGhsas:
|
|
141
|
+
- GHSA-ch52-4w7c-c8xp # http-cache-semantics, dev-only via @swc/cli
|
|
142
|
+
- GHSA-vfj7-8cjw-p6xm # braces, dev-only via nodemon / cpy-cli
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
A suppression hides the advisory everywhere and for good, so nest-server-starter 11.41.7 also
|
|
146
|
+
ships the two guards that keep one honest: `scripts/check-overrides.mjs` (wired into `check` as
|
|
147
|
+
`check:overrides`; it fails once a patched version exists) and
|
|
148
|
+
`tests/unit/audit-suppression-scope.spec.ts` (fails once a suppressed package reaches the
|
|
149
|
+
production tree). Copy both along with the entries. A project generated as a monorepo already
|
|
150
|
+
has the first one at its root, from lt-monorepo.
|
|
151
|
+
|
|
152
|
+
### 6. Dependency updates
|
|
153
|
+
|
|
154
|
+
| Package | 11.41.6 | 11.41.7 |
|
|
155
|
+
| ------- | ------- | ------- |
|
|
156
|
+
| `@nestjs/common`, `@nestjs/core`, `@nestjs/platform-express` | 11.2.6 | **11.2.7** |
|
|
157
|
+
| dev: `@nestjs/testing` | 11.2.6 | 11.2.7 |
|
|
158
|
+
| dev / optional peers: `@aws-sdk/client-s3`, `@aws-sdk/lib-storage` (new), `@aws-sdk/s3-request-presigner` | 3.1143.0 | 3.1145.0 |
|
|
159
|
+
| dev: `@swc/core`, `@types/node`, `bullmq`, `vite` | 1.16.12, 26.6.3, 6.3.10, 8.3.1 | 1.16.13, 26.6.4, 6.3.11, 8.3.2 |
|
|
160
|
+
|
|
161
|
+
**Raise the `@nestjs/*` pins together with the framework.** Starter-based projects:
|
|
162
|
+
`pnpm run update` does it. A project that pins them itself must move all four to 11.2.7 — a
|
|
163
|
+
project still on 11.2.6 next to the framework's 11.2.7 gets two copies of `@nestjs/common`, and
|
|
164
|
+
then `CronJobs extends CoreCronJobs` stops type-checking with an error that does not name the cause.
|
|
165
|
+
|
|
166
|
+
## Compatibility Notes
|
|
167
|
+
|
|
168
|
+
| Pattern | Status |
|
|
169
|
+
| ------- | ------ |
|
|
170
|
+
| GraphQL / multer uploads with the S3 driver | Streamed — multipart with `@aws-sdk/lib-storage`, buffered without it |
|
|
171
|
+
| `CoreS3Service.putObject(key, stream)` without a length | Same as above |
|
|
172
|
+
| `CoreS3Service.putObject(key, stream, type, length)` | Unchanged up to 5 GiB: one streamed `PutObject`. Above: multipart (with `@aws-sdk/lib-storage`) |
|
|
173
|
+
| TUS uploads | Unchanged up to 5 GiB. Above, on AWS S3: fixed — they failed at finalization with `EntityTooLarge`, now multipart (with `@aws-sdk/lib-storage`) |
|
|
174
|
+
| GridFS and filesystem drivers | Unchanged — they always streamed |
|
|
175
|
+
| A subclass overriding `CoreS3Service.collect()` | Called only when `@aws-sdk/lib-storage` is missing |
|
|
176
|
+
| `@nestjs/common` / `core` / `platform-express` / `testing` pinned at 11.2.6 | Raise to 11.2.7 with the framework (§6) |
|
|
177
|
+
| An MCP request with a session id the process no longer knows | 404 instead of 400 — the client re-initializes (§4) |
|
|
178
|
+
| `pnpm audit` red on GHSA-ch52-4w7c-c8xp / GHSA-vfj7-8cjw-p6xm | No fix exists yet; dev-only — suppress with the guards (§5) |
|
|
179
|
+
|
|
180
|
+
## Troubleshooting
|
|
181
|
+
|
|
182
|
+
| Symptom | Cause | Fix |
|
|
183
|
+
| ------- | ----- | --- |
|
|
184
|
+
| Boot warning about `@aws-sdk/lib-storage` | The optional peer is not installed | `pnpm add @aws-sdk/lib-storage` |
|
|
185
|
+
| Peer warning: `@aws-sdk/lib-storage` wants a newer `@aws-sdk/client-s3` | The two packages are on different AWS SDK releases | Install both at the same version |
|
|
186
|
+
| S3 upload fails or never completes when `size` is passed | `size` is not the file's length — e.g. a request's `Content-Length` | Pass the file's own length, or leave `size` out |
|
|
187
|
+
|
|
188
|
+
## Module Documentation
|
|
189
|
+
|
|
190
|
+
- [File module README](../src/core/modules/file/README.md) — "How an upload reaches S3"
|
|
191
|
+
- [File module INTEGRATION-CHECKLIST](../src/core/modules/file/INTEGRATION-CHECKLIST.md) — step 6, switching to the S3 driver
|
|
192
|
+
- [AI module README](../src/core/modules/ai/README.md) — MCP
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Migration Guide: 11.41.7 → 11.41.8
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
| --------------------- | --------------------------------------------------------------------------------------------- |
|
|
7
|
+
| **Security** | better-auth 1.7.7 is now the minimum — critical account takeover in < 1.7.7 (§1) |
|
|
8
|
+
| **Breaking Changes** | None at runtime: 11.41.8 still works on better-auth 1.7.1 (§2). The peer floor rises to 1.7.7, so pnpm reports an unmet peer until you raise the pins |
|
|
9
|
+
| **Data** | Nothing to migrate. The boot drops one index that better-auth 1.7.0–1.7.2 left behind (§2) |
|
|
10
|
+
| **Tooling** | The override guard reports unused overrides; husky and its hooks are gone (§3) |
|
|
11
|
+
| **Migration Effort** | Raise three pins in the API — and two in the app of a fullstack project, in the same change |
|
|
12
|
+
|
|
13
|
+
## Quick Migration
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm update @lenne.tech/nest-server
|
|
17
|
+
pnpm add better-auth@1.7.7 @better-auth/passkey@1.7.7 @better-auth/core@1.7.7
|
|
18
|
+
pnpm run build && pnpm test
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Fullstack projects raise the app in the same change — `@lenne.tech/nuxt-extensions` 1.18.4 and
|
|
22
|
+
`better-auth` / `@better-auth/passkey` 1.7.7 in `projects/app` — see §1.
|
|
23
|
+
|
|
24
|
+
Vendor-mode projects pick up the core update, and raise the same pins.
|
|
25
|
+
|
|
26
|
+
## What's New in 11.41.8
|
|
27
|
+
|
|
28
|
+
### 1. better-auth 1.7.7 is the minimum
|
|
29
|
+
|
|
30
|
+
[GHSA-965c-763c-88jm](https://github.com/better-auth/better-auth/security/advisories/GHSA-965c-763c-88jm)
|
|
31
|
+
(critical): an OAuth state value can be used as a Magic Link to sign in as another user. It covers
|
|
32
|
+
`better-auth >= 1.4.0-beta.18, < 1.7.7`; 1.7.7 is fixed. A project is exposed when it registers the
|
|
33
|
+
`magicLink` plugin — through `betterAuth.options.plugins` — next to a social or Generic OAuth
|
|
34
|
+
provider, with OAuth state stored in the database (the default when a database is configured). An
|
|
35
|
+
attacker who knows a user's email address then gets a normal session for that user, without access
|
|
36
|
+
to the mailbox.
|
|
37
|
+
|
|
38
|
+
At the time of writing the advisory lives only in better-auth's own repository, not yet in the
|
|
39
|
+
database `pnpm audit` reads, so **your audit does not report it**. Do not wait for it to.
|
|
40
|
+
|
|
41
|
+
The framework's peer range is now `>=1.7.7 <1.8.0` for `better-auth`, `@better-auth/passkey` and
|
|
42
|
+
`@better-auth/core` (the floor is forced anyway: `@better-auth/passkey` 1.7.7 itself requires
|
|
43
|
+
`better-auth ^1.7.7`). A project still on 1.7.1 gets an unmet-peer warning, and stays vulnerable
|
|
44
|
+
if it uses Magic Link.
|
|
45
|
+
|
|
46
|
+
**What to do:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# projects/api (or a standalone API)
|
|
50
|
+
pnpm add better-auth@1.7.7 @better-auth/passkey@1.7.7 @better-auth/core@1.7.7
|
|
51
|
+
|
|
52
|
+
# projects/app of a fullstack project — in the SAME change
|
|
53
|
+
pnpm add @lenne.tech/nuxt-extensions@1.18.4 better-auth@1.7.7 @better-auth/passkey@1.7.7
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
API and app must resolve the same better-auth version: they speak one protocol, and a split
|
|
57
|
+
breaks flows like 2FA activation with errors that name neither side. In a monorepo,
|
|
58
|
+
`check-workspace-consistency` fails the check when they differ.
|
|
59
|
+
|
|
60
|
+
Upstream's upgrade note for 1.7.7, worth passing on to whoever deploys: upgrade every server that
|
|
61
|
+
shares verification storage at the same time, have users request new Magic Links, and expect
|
|
62
|
+
OAuth / SAML sign-ins that were in progress during the deploy to need a restart. No database
|
|
63
|
+
migration is required. Custom `verification.storeIdentifier.overrides` rules, and storage code that
|
|
64
|
+
inspects verification identifiers, must now expect the `magic-link:` and `auth-state:` prefixes.
|
|
65
|
+
|
|
66
|
+
### 2. better-auth dropped its issuer schema again — what that means for your data
|
|
67
|
+
|
|
68
|
+
better-auth 1.7.0 keyed accounts by `(issuer, accountId)`, and nest-server 11.37–11.41.7 followed:
|
|
69
|
+
every account it wrote carried an `issuer`, and the boot backfilled it on older rows. better-auth
|
|
70
|
+
1.7.3 restored the 1.6 schema — accounts keyed by `(providerId, accountId)` — and no longer reads or
|
|
71
|
+
writes the field. 11.41.8 follows whichever line is INSTALLED, so the upgrade stays compatible in
|
|
72
|
+
both directions:
|
|
73
|
+
|
|
74
|
+
| You run | nest-server 11.41.8 does |
|
|
75
|
+
| ------- | ------------------------ |
|
|
76
|
+
| better-auth 1.7.7 (the target) | writes no `issuer`; on boot drops the unique `(issuer, accountId)` index better-auth 1.7.0–1.7.2 left behind, and forgets the old backfill marker |
|
|
77
|
+
| better-auth 1.7.1 (pins not raised yet) | behaves exactly like 11.41.7: writes the `issuer`, runs the backfill. Still vulnerable (§1) — raise the pins |
|
|
78
|
+
|
|
79
|
+
**Why the index has to go.** With the field no longer written, the index treats every new account
|
|
80
|
+
as having the same, missing issuer, so a user who holds the same account ID at two providers can no
|
|
81
|
+
longer link the second one. MongoDB has no `NOT NULL`, so dropping the index is the whole cleanup —
|
|
82
|
+
upstream's 1.7 upgrade guide says the same. The log shows it once:
|
|
83
|
+
`Dropped the unique index "account_issuer_accountId_uidx" on account(issuer, accountId)`.
|
|
84
|
+
|
|
85
|
+
**Existing `issuer` values stay.** better-auth 1.7.3+ ignores them, and keeping them is what makes a
|
|
86
|
+
rollback safe.
|
|
87
|
+
|
|
88
|
+
**Rolling back to 11.41.7** (better-auth 1.7.1) works for password users: that version's backfill
|
|
89
|
+
runs again on boot — 11.41.8 deleted its completion marker for exactly this — and gives the issuer
|
|
90
|
+
back to every credential account written in the meantime. better-auth recreates its index on first
|
|
91
|
+
use. Social accounts LINKED while on 11.41.8 stay without an issuer; 11.41.7 reports them at boot,
|
|
92
|
+
and they need the issuer set per provider before their next social sign-in.
|
|
93
|
+
|
|
94
|
+
**Vendor mode on better-auth 1.7.1** builds as well: the core compiles against both lines.
|
|
95
|
+
|
|
96
|
+
**Code that imported `createLocalAccountIssuer`** from `@better-auth/core/db` stops compiling with
|
|
97
|
+
better-auth 1.7.3+, which removed it. Remove the call; accounts no longer carry an issuer.
|
|
98
|
+
|
|
99
|
+
### 3. Tooling (this repository)
|
|
100
|
+
|
|
101
|
+
- **The override guard reports unused overrides again.** `scripts/check-overrides.mjs` searched the
|
|
102
|
+
whole lockfile, where pnpm echoes every override into a top-level `overrides:` block — so an
|
|
103
|
+
override whose package had left the tree still counted as "in the tree". It now reads only the
|
|
104
|
+
resolved part. A project that copied the guard (nest-server-starter ships it since 11.41.7) may
|
|
105
|
+
see new `UNUSED` lines after updating it: warnings, not failures — remove the dead entry.
|
|
106
|
+
- **husky is gone.** Its `prepare` script had been removed in 2024, so the hooks never ran; the
|
|
107
|
+
`check` script is the gate. `.husky/`, `.lintstagedrc.json`, `extras/sync-version.ts` and the
|
|
108
|
+
devDependencies `husky` and `find-file-up` (used only by that hook) were removed. Nothing ships
|
|
109
|
+
in the package from any of them.
|
|
110
|
+
|
|
111
|
+
## Compatibility Notes
|
|
112
|
+
|
|
113
|
+
| Pattern | Status |
|
|
114
|
+
| ------- | ------ |
|
|
115
|
+
| `better-auth` / `@better-auth/*` pinned at 1.7.1 | Unmet peer — raise to 1.7.7 (§1) |
|
|
116
|
+
| API on 1.7.7, app still on 1.7.1 | Breaks auth flows — raise both in one change (§1) |
|
|
117
|
+
| `magicLink` plugin with an OAuth provider, on < 1.7.7 | Account takeover — raise now (§1) |
|
|
118
|
+
| nest-server 11.41.8 with better-auth still at 1.7.1 | Works as before (§2); unmet-peer warning, still vulnerable |
|
|
119
|
+
| Rollback to 11.41.7 after upgrading | Works — the backfill runs again (§2) |
|
|
120
|
+
| Own code calling `createLocalAccountIssuer` | Remove it — gone in better-auth 1.7.3+ (§2) |
|
|
121
|
+
|
|
122
|
+
## Troubleshooting
|
|
123
|
+
|
|
124
|
+
| Symptom | Cause | Fix |
|
|
125
|
+
| ------- | ----- | --- |
|
|
126
|
+
| `unmet peer better-auth@">=1.7.7 <1.8.0": found 1.7.1` | Old pin | `pnpm add better-auth@1.7.7 @better-auth/passkey@1.7.7 @better-auth/core@1.7.7` |
|
|
127
|
+
| 2FA activation or passkey sign-in fails with a generic client error after updating | API and app on different better-auth versions | Pin the same version in both |
|
|
128
|
+
| `check-workspace-consistency` fails on better-auth | Same | Same |
|
|
129
|
+
| New `UNUSED` lines from `check:overrides` | The guard sees dead overrides now (§3) | Remove the entry it names |
|
|
130
|
+
| `Could not drop the legacy unique (issuer, accountId) index` at boot | The database user may not drop indexes | `db.account.dropIndex("account_issuer_accountId_uidx")` once, by hand |
|
|
131
|
+
| `E11000 duplicate key … account_issuer_accountId_uidx` when linking an account | The legacy index is still there | Same |
|
|
132
|
+
|
|
133
|
+
## Module Documentation
|
|
134
|
+
|
|
135
|
+
- [BetterAuth INTEGRATION-CHECKLIST](../src/core/modules/better-auth/INTEGRATION-CHECKLIST.md) — Step 0, the peer dependencies
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.41.
|
|
3
|
+
"version": "11.41.8",
|
|
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",
|
|
@@ -46,9 +46,8 @@
|
|
|
46
46
|
"prestart:prod": "pnpm run build",
|
|
47
47
|
"preversion": "pnpm run lint",
|
|
48
48
|
"reinit": "rimraf pnpm-lock.yaml && rimraf node_modules && pnpm install && pnpm run lint && pnpm run test:e2e && pnpm run test:ci && pnpm run build",
|
|
49
|
-
"reinit:clean": "rimraf pnpm-lock.yaml && rimraf node_modules && pnpm
|
|
50
|
-
"reinit:
|
|
51
|
-
"reinit:legacy": "rimraf pnpm-lock.yaml && rimraf node_modules && pnpm store prune && pnpm install && pnpm run test:e2e",
|
|
49
|
+
"reinit:clean": "rimraf pnpm-lock.yaml && rimraf node_modules && pnpm install && pnpm run test:e2e && pnpm run build",
|
|
50
|
+
"reinit:legacy": "rimraf pnpm-lock.yaml && rimraf node_modules && pnpm install && pnpm run test:e2e",
|
|
52
51
|
"spectaql:sync": "node extras/update-spectaql-version.mjs",
|
|
53
52
|
"start": "pnpm run start:local",
|
|
54
53
|
"start:debug": "nodemon --config nodemon-debug.json",
|
|
@@ -95,13 +94,13 @@
|
|
|
95
94
|
"@getbrevo/brevo": "6.0.3",
|
|
96
95
|
"@modelcontextprotocol/sdk": "1.31.0",
|
|
97
96
|
"@nestjs/apollo": "13.4.5",
|
|
98
|
-
"@nestjs/common": "11.2.
|
|
99
|
-
"@nestjs/core": "11.2.
|
|
97
|
+
"@nestjs/common": "11.2.7",
|
|
98
|
+
"@nestjs/core": "11.2.7",
|
|
100
99
|
"@nestjs/graphql": "13.4.5",
|
|
101
100
|
"@nestjs/jwt": "11.0.2",
|
|
102
101
|
"@nestjs/mongoose": "11.0.4",
|
|
103
102
|
"@nestjs/passport": "11.0.5",
|
|
104
|
-
"@nestjs/platform-express": "11.2.
|
|
103
|
+
"@nestjs/platform-express": "11.2.7",
|
|
105
104
|
"@nestjs/schedule": "6.1.3",
|
|
106
105
|
"@nestjs/swagger": "11.4.7",
|
|
107
106
|
"@nestjs/terminus": "11.1.1",
|
|
@@ -143,11 +142,12 @@
|
|
|
143
142
|
},
|
|
144
143
|
"peerDependencies": {
|
|
145
144
|
"@aws-sdk/client-s3": ">=3.1045.0 <4",
|
|
145
|
+
"@aws-sdk/lib-storage": ">=3.1045.0 <4",
|
|
146
146
|
"@aws-sdk/s3-request-presigner": ">=3.1045.0 <4",
|
|
147
|
-
"@better-auth/core": ">=1.7.
|
|
148
|
-
"@better-auth/passkey": ">=1.7.
|
|
147
|
+
"@better-auth/core": ">=1.7.7 <1.8.0",
|
|
148
|
+
"@better-auth/passkey": ">=1.7.7 <1.8.0",
|
|
149
149
|
"@tus/s3-store": ">=2.0.5 <3",
|
|
150
|
-
"better-auth": ">=1.7.
|
|
150
|
+
"better-auth": ">=1.7.7 <1.8.0",
|
|
151
151
|
"bullmq": ">=5.16.0 <7",
|
|
152
152
|
"ioredis": ">=5.0.0 <7"
|
|
153
153
|
},
|
|
@@ -155,6 +155,9 @@
|
|
|
155
155
|
"@aws-sdk/client-s3": {
|
|
156
156
|
"optional": true
|
|
157
157
|
},
|
|
158
|
+
"@aws-sdk/lib-storage": {
|
|
159
|
+
"optional": true
|
|
160
|
+
},
|
|
158
161
|
"@aws-sdk/s3-request-presigner": {
|
|
159
162
|
"optional": true
|
|
160
163
|
},
|
|
@@ -178,16 +181,17 @@
|
|
|
178
181
|
}
|
|
179
182
|
},
|
|
180
183
|
"devDependencies": {
|
|
181
|
-
"@aws-sdk/client-s3": "3.
|
|
182
|
-
"@aws-sdk/
|
|
183
|
-
"@
|
|
184
|
-
"@better-auth/
|
|
184
|
+
"@aws-sdk/client-s3": "3.1145.0",
|
|
185
|
+
"@aws-sdk/lib-storage": "3.1145.0",
|
|
186
|
+
"@aws-sdk/s3-request-presigner": "3.1145.0",
|
|
187
|
+
"@better-auth/core": "1.7.7",
|
|
188
|
+
"@better-auth/passkey": "1.7.7",
|
|
185
189
|
"@compodoc/compodoc": "2.0.0",
|
|
186
190
|
"@nestjs/cli": "11.0.24",
|
|
187
191
|
"@nestjs/schematics": "11.1.0",
|
|
188
|
-
"@nestjs/testing": "11.2.
|
|
192
|
+
"@nestjs/testing": "11.2.7",
|
|
189
193
|
"@swc/cli": "0.8.1",
|
|
190
|
-
"@swc/core": "1.16.
|
|
194
|
+
"@swc/core": "1.16.13",
|
|
191
195
|
"@tus/s3-store": "2.0.7",
|
|
192
196
|
"@types/compression": "1.8.1",
|
|
193
197
|
"@types/cookie-parser": "1.4.10",
|
|
@@ -195,15 +199,13 @@
|
|
|
195
199
|
"@types/express": "5.0.6",
|
|
196
200
|
"@types/lodash": "4.17.25",
|
|
197
201
|
"@types/multer": "2.3.0",
|
|
198
|
-
"@types/node": "26.6.
|
|
202
|
+
"@types/node": "26.6.4",
|
|
199
203
|
"@types/passport": "1.0.17",
|
|
200
204
|
"@vitest/coverage-v8": "4.1.11",
|
|
201
205
|
"ansi-colors": "4.1.3",
|
|
202
|
-
"better-auth": "1.7.
|
|
203
|
-
"bullmq": "6.3.
|
|
206
|
+
"better-auth": "1.7.7",
|
|
207
|
+
"bullmq": "6.3.11",
|
|
204
208
|
"cross-env": "10.1.0",
|
|
205
|
-
"find-file-up": "2.0.1",
|
|
206
|
-
"husky": "9.1.7",
|
|
207
209
|
"ioredis": "6.0.0",
|
|
208
210
|
"nodemon": "3.1.14",
|
|
209
211
|
"npm-watch": "0.13.0",
|
|
@@ -218,7 +220,7 @@
|
|
|
218
220
|
"tus-js-client": "4.3.1",
|
|
219
221
|
"typescript": "5.9.3",
|
|
220
222
|
"unplugin-swc": "2.0.0",
|
|
221
|
-
"vite": "8.3.
|
|
223
|
+
"vite": "8.3.2",
|
|
222
224
|
"vite-plugin-node": "8.0.0",
|
|
223
225
|
"vitest": "4.1.11"
|
|
224
226
|
},
|
|
@@ -272,6 +272,10 @@ export function multerFileToUpload(file: {
|
|
|
272
272
|
encoding: '7bit',
|
|
273
273
|
filename: file.originalname || randomBytes(16).toString('hex'),
|
|
274
274
|
mimetype: file.mimetype || 'application/octet-stream',
|
|
275
|
+
// No `size`, although the buffer's length is exact: with a size, S3 receives one streamed
|
|
276
|
+
// PutObject, and the SDK does not retry a stream body after a transient error (503 SlowDown, a
|
|
277
|
+
// dropped socket). Without it the multipart path sends Buffer slices of this very buffer — no
|
|
278
|
+
// copy — which the SDK does retry.
|
|
275
279
|
};
|
|
276
280
|
}
|
|
277
281
|
|
|
@@ -80,7 +80,7 @@ export class GridFSHelper {
|
|
|
80
80
|
* process down. That is reachable from ordinary traffic — a client that aborts a
|
|
81
81
|
* GraphQL upload mid-body errors the capacitor stream — and it was
|
|
82
82
|
* driver-conditional: the same abort is a rejected promise under the S3 driver
|
|
83
|
-
* (
|
|
83
|
+
* (the multipart upload rejects) and under the filesystem driver (`pipeline()`
|
|
84
84
|
* forwards both ends), and a process crash under GridFS, the pre-11.33 default.
|
|
85
85
|
* The migration helper's `uploadFileToGridFS()` already carried this handler; the
|
|
86
86
|
* one on the request path did not.
|