@lenne.tech/nest-server 11.42.13 → 11.42.15
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/testing.md +32 -13
- package/FRAMEWORK-API.md +1 -1
- package/dist/core/modules/ai/models/core-ai-budget-limit.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-connection-preference.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-connection.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-conversation.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-interaction.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-mode.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-prompt-hint.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-prompt.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-slot.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-tool-grant.model.d.ts +5 -1
- package/dist/core/modules/ai/models/core-ai-tool-policy.model.d.ts +5 -1
- package/dist/core/modules/auth/guards/auth.guard.js +14 -0
- package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
- package/dist/server/modules/file/file-info.model.d.ts +5 -1
- package/dist/server/modules/user/user.model.d.ts +5 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +8 -0
- package/migration-guides/11.42.13-to-11.42.14.md +74 -0
- package/migration-guides/11.42.14-to-11.42.15.md +117 -0
- package/package.json +14 -14
- package/src/core/modules/api-token/README.md +5 -0
- package/src/core/modules/auth/guards/auth.guard.ts +31 -1
- package/src/core/modules/better-auth/README.md +1 -1
- package/src/core/modules/hub/README.md +1 -1
|
@@ -781,6 +781,14 @@ their public-route shortcut, so the policy holds whichever guard runs first or a
|
|
|
781
781
|
4. A request that carries a token credential but reaches a guard without a token context (the
|
|
782
782
|
middleware did not run) is refused with 401 rather than treated as anonymous.
|
|
783
783
|
|
|
784
|
+
A route-level `@UseGuards(AuthGuard(AuthGuardStrategy.JWT | BETTER_AUTH))` (redundant next to
|
|
785
|
+
`@Roles()`, but common — nest-server-starter's `UserController` carries it on six routes) accepts the
|
|
786
|
+
token `CoreApiTokenMiddleware` authenticated instead of re-verifying the `ltt_…` credential, which
|
|
787
|
+
neither Passport nor `BetterAuthTokenService` knows; before 11.42.14 that re-verification answered
|
|
788
|
+
401 on every released route. It calls `enforceApiTokenRoute()` itself, so it never opens a route the
|
|
789
|
+
role guards would refuse, even where it runs without one. `JWT_REFRESH` and project-defined strategies
|
|
790
|
+
never accept a token.
|
|
791
|
+
|
|
784
792
|
#### System Roles (S_ prefix)
|
|
785
793
|
|
|
786
794
|
System roles are evaluated at runtime and must **never** be stored in `user.roles`.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Migration Guide: 11.42.13 → 11.42.14
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| **Bugfixes** | A route-level `@UseGuards(AuthGuard(...))` no longer refuses every API token with 401 on a route released via `@ApiTokenScopes()` (§1) |
|
|
8
|
+
| **New Features** | None |
|
|
9
|
+
| **Framework API** | Unchanged — no new exports, no changed signatures |
|
|
10
|
+
| **Dependencies** | None for the package (development tooling only) |
|
|
11
|
+
| **Breaking Changes** | None. Tokens on routes that do not release them are refused exactly as before |
|
|
12
|
+
| **Migration Effort** | None. Projects that removed an `AuthGuard` to make tokens work may keep it removed (§1) |
|
|
13
|
+
|
|
14
|
+
## Quick Migration
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm update @lenne.tech/nest-server
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Vendor-mode projects pick it up with the core update. No code or configuration change is required.
|
|
21
|
+
|
|
22
|
+
## 1. An explicit `AuthGuard` no longer closes a released route to API tokens
|
|
23
|
+
|
|
24
|
+
Many projects put `@UseGuards(AuthGuard(AuthGuardStrategy.BETTER_AUTH))` (or `AuthGuard(AuthGuardStrategy.JWT)`) on
|
|
25
|
+
routes next to `@Roles()`. nest-server-starter's `UserController` does so on six routes. The guard is redundant
|
|
26
|
+
there, because `@Roles()` already authenticates, but it was not harmless: it re-verified every request unless the
|
|
27
|
+
user came from Better-Auth, and a user authenticated by an API token does not. So it read the `ltt_…` credential as a
|
|
28
|
+
session or a JWT, failed, and answered **401** — on a route the role guard had just admitted via
|
|
29
|
+
`@ApiTokenScopes()`. Opening such a route for tokens therefore never worked while the guard was on it.
|
|
30
|
+
|
|
31
|
+
The guard now accepts the token `CoreApiTokenMiddleware` already authenticated, for the `JWT` and `BETTER_AUTH`
|
|
32
|
+
strategies. It applies the same route policy as the role guards (`enforceApiTokenRoute()`: deny by default, scopes,
|
|
33
|
+
tenant binding) itself, so it never admits a token on a route that does not release it — also in a setup where it
|
|
34
|
+
runs without a framework role guard in front of it.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
@ApiTokenScopes('read')
|
|
38
|
+
@Get(':id')
|
|
39
|
+
@Roles(RoleEnum.S_USER)
|
|
40
|
+
@UseGuards(AuthGuard(AuthGuardStrategy.BETTER_AUTH)) // 11.42.13: 401 for every token; 11.42.14: works
|
|
41
|
+
async getUser(@CurrentUser() currentUser: User, @Param('id') id: string): Promise<User> { … }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`AuthGuard(AuthGuardStrategy.JWT_REFRESH)`, `RefreshTokenGuard` and project-defined strategies never accept a token.
|
|
45
|
+
|
|
46
|
+
**What to check:**
|
|
47
|
+
|
|
48
|
+
- **You removed an `AuthGuard` from a route to make tokens work:** nothing to do. The guard stays redundant next to
|
|
49
|
+
`@Roles()`; restoring it is optional.
|
|
50
|
+
- **A test expected 401 for a token on a released route that carries an `AuthGuard`:** it now gets the route's
|
|
51
|
+
answer. A token on a route WITHOUT `@ApiTokenScopes()` is still refused with 403, as before.
|
|
52
|
+
|
|
53
|
+
## Compatibility Notes
|
|
54
|
+
|
|
55
|
+
| Pattern | Status |
|
|
56
|
+
| ------- | ------ |
|
|
57
|
+
| Routes with `@Roles()` only | Unchanged |
|
|
58
|
+
| Routes with `@Roles()` + `@UseGuards(AuthGuard(BETTER_AUTH \| JWT))` + `@ApiTokenScopes()` | Tokens now pass (§1) |
|
|
59
|
+
| Routes with `@UseGuards(AuthGuard(...))` but no `@ApiTokenScopes()` | Tokens refused with 403, as before |
|
|
60
|
+
| `refreshToken` / `AuthGuard(JWT_REFRESH)` / custom Passport strategies | Never accept a token, as before |
|
|
61
|
+
| A project guard that extends `AuthGuard(...)` and overrides `handleRequest()` | Compatible; the override also runs for token users |
|
|
62
|
+
|
|
63
|
+
## Troubleshooting
|
|
64
|
+
|
|
65
|
+
**A token still gets 401 on a route with an `AuthGuard`**
|
|
66
|
+
Check that the route (or its class) declares `@ApiTokenScopes()` with a scope the token holds — without it, the role
|
|
67
|
+
guard refuses the token with 403 before the `AuthGuard` runs. A 401 means the token itself did not authenticate
|
|
68
|
+
(revoked, expired, wrong prefix); the token middleware answers that before any guard.
|
|
69
|
+
|
|
70
|
+
## Module Documentation
|
|
71
|
+
|
|
72
|
+
- [API token module README](../src/core/modules/api-token/README.md) — "Opening a route"
|
|
73
|
+
- [API token integration checklist](../src/core/modules/api-token/INTEGRATION-CHECKLIST.md)
|
|
74
|
+
- [Request lifecycle](../docs/REQUEST-LIFECYCLE.md) — "API tokens (`@ApiTokenScopes`)"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Migration Guide: 11.42.14 → 11.42.15
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| **Bugfixes** | No framework code changes; security release of the runtime dependency `undici` (§3) |
|
|
8
|
+
| **New Features** | None |
|
|
9
|
+
| **Framework API** | Unchanged — no new exports, no changed signatures |
|
|
10
|
+
| **Dependencies** | `mongoose` 9.10.4 → 9.11.2 and `mongodb` 7.6.0 → 7.7.0 (both `dependencies`, raised together, §1); **`undici` 7.30.0 → 7.30.1 (security release, §3)**; `nodemailer` 10.1.0 → 10.1.2; `@getbrevo/brevo` 6.0.3 → 6.2.0; overrides `@graphql-tools/utils` → 12.0.4, `undici` → 7.30.1, `qs` → 6.16.1 |
|
|
11
|
+
| **Breaking Changes** | None |
|
|
12
|
+
| **Migration Effort** | Low, but **one step is required**: a project that keeps `mongodb` in `devDependencies` (the starter default) MUST raise it to 7.7.0 — by hand, or with the starter's new update script — or sign-in fails with 401 (§1). Typed `lean()` / `bulkWrite()` code may need a type adjustment (§2) |
|
|
13
|
+
|
|
14
|
+
## Quick Migration
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm run update # starter-based projects: raises every pin the framework raised
|
|
18
|
+
pnpm why mongodb # must show ONE version: 7.7.0
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If `pnpm why mongodb` lists 7.6.0 next to 7.7.0, raise the project's own `mongodb` entry to `7.7.0` (§1). If your
|
|
22
|
+
project has its own `undici` override, raise it to 7.30.1 (§3) — `pnpm audit` does not flag this one yet.
|
|
23
|
+
|
|
24
|
+
Vendor-mode projects pick up the core with `/lt-dev:backend:update-nest-server-core`; raise `mongoose` and `mongodb`
|
|
25
|
+
in the project's `package.json` to the versions above in the same step.
|
|
26
|
+
|
|
27
|
+
## 1. mongoose 9.11 and mongodb 7.7 move together
|
|
28
|
+
|
|
29
|
+
mongoose 9.11.x requires `mongodb ~7.7`, so both are raised in this release. nest-server declares `mongodb` under
|
|
30
|
+
`dependencies`. nest-server-starter keeps it under `devDependencies` — and that pin is **not test-only**: Better-Auth's
|
|
31
|
+
MongoDB adapter (`@better-auth/mongo-adapter`, used by nest-server's IAM module) resolves its `mongodb` peer from the
|
|
32
|
+
project's own `mongodb` entry, in the production image too.
|
|
33
|
+
|
|
34
|
+
Until this release, `pnpm run update` (the starter's `extras/sync-packages.mjs`) raised a pin only when the framework
|
|
35
|
+
declared it **in the same section**. A project with `mongodb` in `devDependencies` therefore stayed on 7.6.0 while the
|
|
36
|
+
framework's mongoose brought 7.7.0: two driver copies. Where the project's lockfile still holds the older `bson` for
|
|
37
|
+
the old driver (any lockfile not refreshed since bson 7.3), there are two `bson` copies as well. The adapter checks ids
|
|
38
|
+
with `instanceof ObjectId` against ITS driver's bson, the ids come from the framework's — so the check fails, the
|
|
39
|
+
adapter throws `MongoAdapterError: Invalid id value`, and authenticated requests answer **401**. With a single shared
|
|
40
|
+
bson it stays a typing problem in tests (`Db` / `MongoClient` from the test helpers no longer match the project's own
|
|
41
|
+
import). Neither is acceptable, and which one a project gets depends on its lockfile history, so raise the pin.
|
|
42
|
+
|
|
43
|
+
The starter's update script now raises a package the framework **ships** (`dependencies`) in whichever section the
|
|
44
|
+
project keeps it. A package the framework only develops and tests with (`devDependencies`) still raises only the
|
|
45
|
+
project's own `devDependencies` — never a runtime pin. That keeps the better-auth trio where it belongs: the framework
|
|
46
|
+
holds it as devDependencies (its test versions, with a peer range as the contract), the project holds it as
|
|
47
|
+
dependencies and owns that version, because its api and app must resolve the same better-auth. The rule lives in
|
|
48
|
+
`extras/align-package-versions.mjs`, and nest-server's consumer gate (`pnpm run check:consumer`) applies the identical
|
|
49
|
+
rule before every release.
|
|
50
|
+
|
|
51
|
+
**What to do:**
|
|
52
|
+
|
|
53
|
+
- **Your project still has the old `extras/sync-packages.mjs`:** raise `mongodb` to `7.7.0` by hand (also
|
|
54
|
+
`json-to-graphql-query`, if you keep it in `devDependencies` and it lags), or copy `extras/sync-packages.mjs` and
|
|
55
|
+
`extras/align-package-versions.mjs` from nest-server-starter. **This step is required** — skipping it breaks sign-in.
|
|
56
|
+
- **Your project has `mongodb` in `dependencies`:** `pnpm run update` already raised it; nothing to do.
|
|
57
|
+
- **Check either way:** `pnpm why mongodb` must list exactly one version.
|
|
58
|
+
|
|
59
|
+
## 2. mongoose 9.11 type changes
|
|
60
|
+
|
|
61
|
+
No runtime behaviour your code relies on changed, but two typing changes can surface in a project's own code:
|
|
62
|
+
|
|
63
|
+
- `lean()` now passes its result type into the query, so a `lean<T>()` result is narrowed to `T`. Code that assigned
|
|
64
|
+
the result to an incompatible type now gets a compile error instead of a silent mismatch.
|
|
65
|
+
- `UpdateOneModel` / `UpdateManyModel` (`bulkWrite()` operations) are typed like `UpdateQuery`. An update document that
|
|
66
|
+
was accepted by the looser type may now need its operators spelled out (`$set`, `$push`, …).
|
|
67
|
+
|
|
68
|
+
Runtime fixes that come along (no action needed): validators on document arrays under nested paths no longer run
|
|
69
|
+
twice, `bulkWrite()` casts array filter values, cursors await driver cleanup on destroy. The mongodb 7.7 driver
|
|
70
|
+
recovers from a failed OIDC token fetch instead of failing every later operation on that client.
|
|
71
|
+
|
|
72
|
+
## 3. undici 7.30.1 is a security release
|
|
73
|
+
|
|
74
|
+
undici 7.30.1 fixes four high and four medium advisories (GHSA-qrxm-fqhp-jwvm, GHSA-2m5v-pch6-74hx,
|
|
75
|
+
GHSA-4w7w-5xrc-9h26, GHSA-xjj6-f33q-929c high; GHSA-55wc-3pg8-jfw6, GHSA-f72v-6w45-q472, GHSA-4qv8-2p9j-2685,
|
|
76
|
+
GHSA-9fm3-97px-rjf6 medium). They were published as repository advisories, so **`pnpm audit` does not report them
|
|
77
|
+
yet** — a green audit says nothing about this release. nest-server uses undici at runtime for `outboundFetch()` (the
|
|
78
|
+
SSRF-guarded fetch for user-entered URLs), and one of the fixes concerns function-valued Agent options, which that
|
|
79
|
+
helper's guarded connector is.
|
|
80
|
+
|
|
81
|
+
**What to do:**
|
|
82
|
+
|
|
83
|
+
- **Nothing for nest-server's own dependency** — `pnpm run update` raises it.
|
|
84
|
+
- **Your project carries its own `undici` override** (starter-derived projects do, for the
|
|
85
|
+
`@compodoc/compodoc > cheerio > undici` path): raise key and target together, e.g.
|
|
86
|
+
`'undici@>=7.0.0 <7.30.0': 7.30.0` → `'undici@>=7.0.0 <7.30.1': 7.30.1`. The old entry would otherwise keep that path
|
|
87
|
+
on the vulnerable 7.30.0, because cheerio's `^7.19.0` intersects the old key.
|
|
88
|
+
- **Check:** `pnpm why undici` lists only 7.30.1.
|
|
89
|
+
|
|
90
|
+
Also raised in the same way (no advisory, removes a hold-back): `qs` override `<6.16.0` → `<6.16.1` (6.16.1), and
|
|
91
|
+
`nodemailer` 10.1.2, which strips credentials on a cross-origin redirect.
|
|
92
|
+
|
|
93
|
+
## Compatibility Notes
|
|
94
|
+
|
|
95
|
+
| Pattern | Status |
|
|
96
|
+
| ------- | ------ |
|
|
97
|
+
| `mongodb` in `dependencies` of the project | Raised by `pnpm run update` as before |
|
|
98
|
+
| `mongodb` in `devDependencies` of the project (starter default) | Raised only by the new update script — otherwise raise by hand, **required** (§1) |
|
|
99
|
+
| better-auth / `@better-auth/*` in the project's `dependencies` | Never raised from the framework's devDependencies — the project owns that version, as before |
|
|
100
|
+
| `lean<T>()` with an explicit result type | May need a type adjustment (§2) |
|
|
101
|
+
| `bulkWrite()` with typed `updateOne` / `updateMany` operations | May need operators spelled out (§2) |
|
|
102
|
+
| Routes with `@Roles()` + `@UseGuards(AuthGuard(BETTER_AUTH))` | Unchanged. nest-server-starter dropped these redundant guards from its `UserController`; removing them in your project is optional |
|
|
103
|
+
|
|
104
|
+
## Troubleshooting
|
|
105
|
+
|
|
106
|
+
**Sign-in or every authenticated request answers 401, the log shows `MongoAdapterError: Invalid id value`**
|
|
107
|
+
Two mongodb (and bson) versions are installed, and Better-Auth's MongoDB adapter uses the project's older one.
|
|
108
|
+
`pnpm why mongodb` shows both; raise the project's own `mongodb` entry to 7.7.0, run `pnpm install`, and rebuild the
|
|
109
|
+
image (§1).
|
|
110
|
+
|
|
111
|
+
**Type errors around `Db`, `MongoClient` or `Collection` in tests after the update**
|
|
112
|
+
Same cause, same fix: two mongodb versions are installed. Raise the project's own entry to 7.7.0 and run
|
|
113
|
+
`pnpm install`.
|
|
114
|
+
|
|
115
|
+
## Module Documentation
|
|
116
|
+
|
|
117
|
+
- [nest-server-starter `extras/align-package-versions.mjs`](https://github.com/lenneTech/nest-server-starter/blob/main/extras/align-package-versions.mjs)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.42.
|
|
3
|
+
"version": "11.42.15",
|
|
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",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"dependencies": {
|
|
92
92
|
"@apollo/server": "5.5.1",
|
|
93
93
|
"@as-integrations/express5": "1.1.2",
|
|
94
|
-
"@getbrevo/brevo": "6.0
|
|
94
|
+
"@getbrevo/brevo": "6.2.0",
|
|
95
95
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
96
96
|
"@nestjs/apollo": "13.4.5",
|
|
97
97
|
"@nestjs/common": "11.2.7",
|
|
@@ -125,11 +125,11 @@
|
|
|
125
125
|
"js-sha256": "1.0.0",
|
|
126
126
|
"json-to-graphql-query": "2.3.0",
|
|
127
127
|
"lodash": "4.18.1",
|
|
128
|
-
"mongodb": "7.
|
|
129
|
-
"mongoose": "9.
|
|
128
|
+
"mongodb": "7.7.0",
|
|
129
|
+
"mongoose": "9.11.2",
|
|
130
130
|
"multer": "2.4.0",
|
|
131
131
|
"node-mailjet": "6.0.11",
|
|
132
|
-
"nodemailer": "10.1.
|
|
132
|
+
"nodemailer": "10.1.2",
|
|
133
133
|
"passport": "0.7.0",
|
|
134
134
|
"passport-jwt": "4.0.1",
|
|
135
135
|
"reflect-metadata": "0.2.2",
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
"rxjs": "7.8.2",
|
|
138
138
|
"supertest": "7.3.1",
|
|
139
139
|
"ts-morph": "28.0.0",
|
|
140
|
-
"undici": "7.30.
|
|
140
|
+
"undici": "7.30.1",
|
|
141
141
|
"ws": "8.21.3",
|
|
142
142
|
"yuml-diagram": "1.2.0"
|
|
143
143
|
},
|
|
@@ -182,9 +182,9 @@
|
|
|
182
182
|
}
|
|
183
183
|
},
|
|
184
184
|
"devDependencies": {
|
|
185
|
-
"@aws-sdk/client-s3": "3.
|
|
186
|
-
"@aws-sdk/lib-storage": "3.
|
|
187
|
-
"@aws-sdk/s3-request-presigner": "3.
|
|
185
|
+
"@aws-sdk/client-s3": "3.1152.0",
|
|
186
|
+
"@aws-sdk/lib-storage": "3.1152.0",
|
|
187
|
+
"@aws-sdk/s3-request-presigner": "3.1152.0",
|
|
188
188
|
"@better-auth/core": "1.7.7",
|
|
189
189
|
"@better-auth/passkey": "1.7.7",
|
|
190
190
|
"@compodoc/compodoc": "2.0.0",
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
"@nestjs/schematics": "11.1.0",
|
|
193
193
|
"@nestjs/testing": "11.2.7",
|
|
194
194
|
"@swc/cli": "0.8.1",
|
|
195
|
-
"@swc/core": "1.16.
|
|
195
|
+
"@swc/core": "1.16.18",
|
|
196
196
|
"@tus/s3-store": "2.0.7",
|
|
197
197
|
"@types/compression": "1.8.1",
|
|
198
198
|
"@types/cookie-parser": "1.4.10",
|
|
@@ -204,16 +204,16 @@
|
|
|
204
204
|
"@types/passport": "1.0.17",
|
|
205
205
|
"@vitest/coverage-v8": "4.1.11",
|
|
206
206
|
"better-auth": "1.7.7",
|
|
207
|
-
"bullmq": "6.3.
|
|
207
|
+
"bullmq": "6.3.15",
|
|
208
208
|
"cross-env": "10.1.0",
|
|
209
209
|
"ioredis": "6.0.0",
|
|
210
210
|
"nodemon": "3.1.14",
|
|
211
211
|
"npm-watch": "0.13.0",
|
|
212
212
|
"otpauth": "9.5.2",
|
|
213
|
-
"oxfmt": "0.
|
|
214
|
-
"oxlint": "1.
|
|
213
|
+
"oxfmt": "0.72.0",
|
|
214
|
+
"oxlint": "1.88.0",
|
|
215
215
|
"rimraf": "6.1.3",
|
|
216
|
-
"smtp-server": "3.19.
|
|
216
|
+
"smtp-server": "3.19.23",
|
|
217
217
|
"ts-node": "10.9.2",
|
|
218
218
|
"tsconfig-paths": "4.2.0",
|
|
219
219
|
"tsx": "4.23.15",
|
|
@@ -60,6 +60,11 @@ What an opened route means per kind:
|
|
|
60
60
|
membership role. Losing a membership removes the token's access at once — rights are read per
|
|
61
61
|
request, never copied into the token.
|
|
62
62
|
|
|
63
|
+
An explicit `@UseGuards(AuthGuard(AuthGuardStrategy.BETTER_AUTH))` or `AuthGuard(AuthGuardStrategy.JWT)`
|
|
64
|
+
on the route does not close it to tokens (since 11.42.14; before, it answered 401): it accepts the token
|
|
65
|
+
the middleware authenticated and applies the same route policy. `AuthGuard(JWT_REFRESH)` and custom
|
|
66
|
+
strategies never accept a token.
|
|
67
|
+
|
|
63
68
|
`request.user`:
|
|
64
69
|
|
|
65
70
|
- TENANT token: `{ id: <token id>, name, scopes, tenantId, roles: [], hasRole: () => false }`
|
|
@@ -6,6 +6,7 @@ import { defaultOptions } from '@nestjs/passport/dist/options.js';
|
|
|
6
6
|
import { memoize } from '@nestjs/passport/dist/utils/memoize.util.js';
|
|
7
7
|
import passport = require('passport');
|
|
8
8
|
|
|
9
|
+
import { enforceApiTokenRoute, getApiTokenContext } from '../../api-token/core-api-token.helpers';
|
|
9
10
|
import { BetterAuthTokenService } from '../../better-auth/better-auth-token.service';
|
|
10
11
|
import { BetterAuthenticatedUser } from '../../better-auth/better-auth.types';
|
|
11
12
|
import { CoreBetterAuthService } from '../../better-auth/core-better-auth.service';
|
|
@@ -21,6 +22,21 @@ const NO_STRATEGY_ERROR =
|
|
|
21
22
|
'In order to use "defaultStrategy", please, ensure to import PassportModule in each ' +
|
|
22
23
|
"place where AuthGuard() is being used. Otherwise, passport won't work correctly.";
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Strategies that authenticate a person by an access credential. An API token authenticated by
|
|
27
|
+
* `CoreApiTokenMiddleware` satisfies them; `JWT_REFRESH` and project-defined strategies authenticate
|
|
28
|
+
* something else (a refresh token, a custom credential), so a token never stands in for those.
|
|
29
|
+
*/
|
|
30
|
+
const API_TOKEN_SATISFIED_STRATEGIES: ReadonlySet<string> = new Set([
|
|
31
|
+
AuthGuardStrategy.BETTER_AUTH,
|
|
32
|
+
AuthGuardStrategy.JWT,
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
function isSatisfiedByApiToken(strategy: string | string[] | undefined): boolean {
|
|
36
|
+
const strategies = Array.isArray(strategy) ? strategy : [strategy];
|
|
37
|
+
return strategies.some((value) => !!value && API_TOKEN_SATISFIED_STRATEGIES.has(value));
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
/**
|
|
25
41
|
* Interface for auth guard
|
|
26
42
|
*/
|
|
@@ -61,6 +77,9 @@ const createPassportContext =
|
|
|
61
77
|
* - If not, validates the token via BetterAuthTokenService (JWT or session token)
|
|
62
78
|
* - Loads the full user from MongoDB with hasRole() capability
|
|
63
79
|
*
|
|
80
|
+
* API tokens (JWT and BETTER_AUTH only): a token authenticated by CoreApiTokenMiddleware is accepted as is
|
|
81
|
+
* and the route is decided by enforceApiTokenRoute(), exactly as in the role guards.
|
|
82
|
+
*
|
|
64
83
|
* Can be removed when pull request is merged:
|
|
65
84
|
* https://github.com/nestjs/passport/pull/66
|
|
66
85
|
*/
|
|
@@ -118,11 +137,22 @@ function createAuthGuard(type?: AuthGuardStrategy | string | string[]): Type<IAu
|
|
|
118
137
|
|
|
119
138
|
const options = { ...defaultOptions, ...this.options };
|
|
120
139
|
const request = this.getRequest(context);
|
|
140
|
+
const existingUser = request?.[options.property || defaultOptions.property];
|
|
141
|
+
|
|
142
|
+
// An API token was already authenticated by CoreApiTokenMiddleware. Neither Passport nor
|
|
143
|
+
// BetterAuthTokenService knows its credential, so verifying it again here refused every token with
|
|
144
|
+
// 401 — also on a route that released it via @ApiTokenScopes(). The route is decided here with the
|
|
145
|
+
// same function the role guards use (deny by default, scopes, tenant binding), so the policy holds
|
|
146
|
+
// even where this guard runs without a framework role guard in front of it.
|
|
147
|
+
if (getApiTokenContext(existingUser) && isSatisfiedByApiToken(type || this.options?.defaultStrategy)) {
|
|
148
|
+
enforceApiTokenRoute({ controllerClass: context.getClass(), handler: context.getHandler(), request });
|
|
149
|
+
request[options.property || defaultOptions.property] = this.handleRequest(null, existingUser, null, context);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
121
152
|
|
|
122
153
|
// Check if user is already authenticated via Better-Auth middleware
|
|
123
154
|
// Only skip Passport for Better-Auth users (marked with _authenticatedViaBetterAuth)
|
|
124
155
|
// This ensures JWT_REFRESH guard still validates refresh tokens properly
|
|
125
|
-
const existingUser = request?.[options.property || defaultOptions.property];
|
|
126
156
|
if (existingUser && existingUser._authenticatedViaBetterAuth === true) {
|
|
127
157
|
// User is authenticated via Better-Auth - skip Passport authentication
|
|
128
158
|
// Validate through handleRequest to ensure role checks work
|
|
@@ -1040,7 +1040,7 @@ better-auth 1.7.0–1.7.2 keyed accounts by `(issuer, accountId)`; 1.7.3 restore
|
|
|
1040
1040
|
recognised by shape, so a renamed collection or field is covered. Existing `issuer` values stay;
|
|
1041
1041
|
they are inert. The backfill marker is deleted, so a rollback to a better-auth that needs the
|
|
1042
1042
|
issuer runs the backfill again. Log line: `Dropped the unique index "…" on account(issuer,
|
|
1043
|
-
accountId)`. A failure is logged at `error` and the boot continues.
|
|
1043
|
+
accountId)`. A failure is logged at `error` and the boot continues.
|
|
1044
1044
|
- **better-auth 1.7.0–1.7.2: `backfillAccountIssuers()`, described below.** Kept for a project that
|
|
1045
1045
|
has not raised its better-auth pin yet.
|
|
1046
1046
|
|
|
@@ -121,7 +121,7 @@ Every action writes an audit line: `[HUB-ACTION] <action> by user <id>`.
|
|
|
121
121
|
whose `env` is not `local`/`development`/`test`/`ci`/`e2e`) — it suppresses outgoing mail.
|
|
122
122
|
- **Public-access guard**: `roles: false` (no auth check — public config viewer, logs and destructive
|
|
123
123
|
actions) throws at startup in any **reachable** environment unless `hub.allowPublicAccessInProduction:
|
|
124
|
-
true` is set explicitly. This closes the single most dangerous Hub misconfiguration (a `roles: false`
|
|
124
|
+
true` is set explicitly. This closes the single most dangerous Hub misconfiguration (a `roles: false`
|
|
125
125
|
copied from a local config into a reachable environment). The reachable check is fail-safe: it treats
|
|
126
126
|
every env name except the known local/test set as reachable, so a custom name (`prod`, `preprod`,
|
|
127
127
|
`staging-2`, …) cannot bypass it. Only acknowledge public access behind a fully-controlled network
|