@lenne.tech/nest-server 11.42.11 → 11.42.13
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/.claude/rules/package-management.md +12 -5
- package/.claude/rules/testing.md +4 -4
- package/FRAMEWORK-API.md +1 -1
- package/dist/core/common/helpers/cookies.helper.d.ts +3 -2
- package/dist/core/common/helpers/cookies.helper.js +13 -4
- package/dist/core/common/helpers/cookies.helper.js.map +1 -1
- package/dist/core/common/helpers/outbound-url.helper.js +2 -5
- package/dist/core/common/helpers/outbound-url.helper.js.map +1 -1
- package/dist/core/modules/auth/core-auth.controller.js +2 -1
- package/dist/core/modules/auth/core-auth.controller.js.map +1 -1
- package/dist/core/modules/auth/core-auth.resolver.js +2 -1
- package/dist/core/modules/auth/core-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +1 -0
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-cookie.helper.d.ts +2 -0
- package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js +2 -1
- package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +7 -2
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.js +1 -0
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.d.ts +1 -0
- package/dist/core/modules/better-auth/core-better-auth.service.js +4 -0
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/core/modules/error-code/error-codes.d.ts +45 -0
- package/dist/core/modules/error-code/error-codes.js +40 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -1
- package/dist/core/modules/file/core-file.service.d.ts +1 -0
- package/dist/core/modules/file/core-file.service.js +27 -0
- package/dist/core/modules/file/core-file.service.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.guard.js +7 -7
- package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
- package/dist/core/modules/tus/core-tus.service.d.ts +1 -0
- package/dist/core/modules/tus/core-tus.service.js +10 -4
- package/dist/core/modules/tus/core-tus.service.js.map +1 -1
- package/dist/core/modules/tus/tus.constants.d.ts +1 -0
- package/dist/core/modules/tus/tus.constants.js +2 -1
- package/dist/core/modules/tus/tus.constants.js.map +1 -1
- package/dist/server/modules/error-code/error-codes.d.ts +5 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +1 -1
- package/docs/error-codes.md +70 -0
- package/migration-guides/11.42.10-to-11.42.11.md +18 -2
- package/migration-guides/11.42.11-to-11.42.12.md +142 -0
- package/migration-guides/11.42.12-to-11.42.13.md +154 -0
- package/package.json +2 -2
- package/src/core/common/helpers/cookies.helper.ts +50 -6
- package/src/core/common/helpers/outbound-url.helper.ts +4 -10
- package/src/core/modules/auth/core-auth.controller.ts +2 -1
- package/src/core/modules/auth/core-auth.resolver.ts +2 -1
- package/src/core/modules/better-auth/README.md +1 -0
- package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +1 -0
- package/src/core/modules/better-auth/core-better-auth-cookie.helper.ts +24 -5
- package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +20 -10
- package/src/core/modules/better-auth/core-better-auth.controller.ts +4 -0
- package/src/core/modules/better-auth/core-better-auth.service.ts +24 -0
- package/src/core/modules/error-code/error-codes.ts +49 -0
- package/src/core/modules/file/README.md +1 -0
- package/src/core/modules/file/core-file.service.ts +39 -0
- package/src/core/modules/tenant/INTEGRATION-CHECKLIST.md +2 -2
- package/src/core/modules/tenant/README.md +17 -1
- package/src/core/modules/tenant/core-tenant.guard.ts +7 -7
- package/src/core/modules/tus/INTEGRATION-CHECKLIST.md +2 -1
- package/src/core/modules/tus/README.md +42 -3
- package/src/core/modules/tus/core-tus.service.ts +32 -6
- package/src/core/modules/tus/tus.constants.ts +11 -0
|
@@ -167,7 +167,7 @@ JWT-based authentication for existing projects:
|
|
|
167
167
|
| **REST Endpoints** | `GET /files/id/:id`, `GET /files/:filename` (core, gated by `file.downloadRoles`, default ADMIN); `POST /files/upload`, `DELETE /files/:id` (project-specific) |
|
|
168
168
|
| **GraphQL Endpoints** | `getFileInfo` (`file.downloadRoles`), `uploadFile` / `uploadFiles` (`file.uploadRoles`), `deleteFile` (`file.deleteRoles`) — all default ADMIN |
|
|
169
169
|
| **File access control** | Roles are the coarse filter; per-file rules go in `CoreFileService.checkRights()` using metadata written at upload time. Cover BOTH the `id` and the `filename` branch — the filename route authorizes on the by-name lookup alone when presigned S3 downloads are on, and `deleteFileByName()` always does. Working reference: `src/server/modules/file/file.service.ts` (with `file.downloadRoles: [S_USER]` in `src/config.env.ts`, so the rule is actually reached). Both file classes carry `@SkipTenantCheck()` — GridFS is not tenant-scoped |
|
|
170
|
-
| **TUS Module** | Resumable uploads via tus.io protocol (creation, termination, expiration), gated by `tus.roles` (default `S_USER`); `OPTIONS` stays public for the CORS preflight. Records owner and, with `multiTenancy` only, the validated tenant at upload creation; a custom service plugs in via `TusModule.forRoot({ service })`. Configured by `TusModule.forRoot({ config })`, else the server config's `tus` key (11.42.9+). Switched-off `creation` / `termination` answer 501 (enforced by `CoreTusService`, not `@tus/server`); upload URLs follow the path the upload was created at |
|
|
170
|
+
| **TUS Module** | Resumable uploads via tus.io protocol (creation, termination, expiration), gated by `tus.roles` (default `S_USER`); `OPTIONS` stays public for the CORS preflight. Records owner and, with `multiTenancy` only, the validated tenant at upload creation; a custom service plugs in via `TusModule.forRoot({ service })`. Configured by `TusModule.forRoot({ config })`, else the server config's `tus` key (11.42.9+). Switched-off `creation` / `termination` answer 501 (enforced by `CoreTusService`, not `@tus/server`); upload URLs follow the path the upload was created at. The response that completes an upload carries the stored file's ID as `X-File-Id` (11.42.13+, listed in `Access-Control-Expose-Headers`) — the last segment of the TUS URL is the upload id, not the file id |
|
|
171
171
|
| **GridFS Migration** | Completed TUS uploads auto-migrate to GridFS |
|
|
172
172
|
| **CORS Support** | Automatic CORS headers for browser uploads |
|
|
173
173
|
|
package/docs/error-codes.md
CHANGED
|
@@ -162,6 +162,76 @@ All error codes follow the format: `#PREFIX_XXXX: Description`
|
|
|
162
162
|
|
|
163
163
|
---
|
|
164
164
|
|
|
165
|
+
### LTNS_0103: notTenantMember
|
|
166
|
+
|
|
167
|
+
**Message:** Not a member of this tenant
|
|
168
|
+
|
|
169
|
+
**Description:** Thrown by the CoreTenantGuard when the `X-Tenant-Id` header names a tenant the user has no active membership in
|
|
170
|
+
|
|
171
|
+
**Solution:** Send the header of a tenant the user belongs to, or add the membership
|
|
172
|
+
|
|
173
|
+
**Translations:**
|
|
174
|
+
- DE: Sie sind kein Mitglied dieses Mandanten.
|
|
175
|
+
- EN: You are not a member of this tenant.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### LTNS_0104: insufficientTenantRole
|
|
180
|
+
|
|
181
|
+
**Message:** Insufficient tenant role
|
|
182
|
+
|
|
183
|
+
**Description:** Thrown by the CoreTenantGuard when the membership role in the header's tenant does not satisfy the required tenant role
|
|
184
|
+
|
|
185
|
+
**Solution:** Raise the membership role or use an endpoint with a lower requirement
|
|
186
|
+
|
|
187
|
+
**Translations:**
|
|
188
|
+
- DE: Ihre Rolle in diesem Mandanten reicht für diese Aktion nicht aus.
|
|
189
|
+
- EN: Your role in this tenant does not allow this action.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### LTNS_0105: insufficientRole
|
|
194
|
+
|
|
195
|
+
**Message:** Insufficient role
|
|
196
|
+
|
|
197
|
+
**Description:** Thrown by the CoreTenantGuard when `user.roles` does not meet the required role — without a tenant header, or on a `@SkipTenantCheck` route
|
|
198
|
+
|
|
199
|
+
**Solution:** Grant the role, or send the tenant header so the membership role is checked
|
|
200
|
+
|
|
201
|
+
**Translations:**
|
|
202
|
+
- DE: Ihre Rolle reicht für diese Aktion nicht aus.
|
|
203
|
+
- EN: Your role does not allow this action.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### LTNS_0106: tenantInactive
|
|
208
|
+
|
|
209
|
+
**Message:** Tenant is inactive
|
|
210
|
+
|
|
211
|
+
**Description:** Thrown by the CoreTenantGuard when `multiTenancy.isTenantActive` reports the header's tenant as switched off
|
|
212
|
+
|
|
213
|
+
**Solution:** Reactivate the tenant, then call `invalidateTenant(tenantId)`
|
|
214
|
+
|
|
215
|
+
**Translations:**
|
|
216
|
+
- DE: Dieser Mandant ist deaktiviert.
|
|
217
|
+
- EN: This tenant is deactivated.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### LTNS_0107: verificationRequired
|
|
222
|
+
|
|
223
|
+
**Message:** Verification required
|
|
224
|
+
|
|
225
|
+
**Description:** Thrown by the CoreTenantGuard on an `S_VERIFIED` endpoint for a user who is not verified
|
|
226
|
+
|
|
227
|
+
**Solution:** Complete the verification of the account
|
|
228
|
+
|
|
229
|
+
**Translations:**
|
|
230
|
+
- DE: Für diese Aktion muss Ihr Konto verifiziert sein.
|
|
231
|
+
- EN: Your account must be verified for this action.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
165
235
|
## User Errors (LTNS_0200-LTNS_0299)
|
|
166
236
|
|
|
167
237
|
### LTNS_0200: emailAlreadyExists
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
| -------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
7
7
|
| **Framework API** | No change |
|
|
8
8
|
| **Test tooling** | e2e low-resource cap fixed for Vitest 4, e2e run governor hardened, two `check.mjs` false greens closed (§1–§3) — reaches your project only through this guide |
|
|
9
|
-
| **Tests** | A race in tests that upload to a route a role guard refuses (§4)
|
|
9
|
+
| **Tests** | A race in tests that upload to a route a role guard refuses (§4); a red unit file in starter-derived monorepos (§7) |
|
|
10
10
|
| **Documentation** | `lt dev` exports `NSC__MONGOOSE__URI`, never `MONGODB_URI`; two older guides corrected (§5) |
|
|
11
11
|
| **Dependencies** | `nodemailer` 10.1.0 (minor, no API change); dev tooling (§6) |
|
|
12
12
|
| **Breaking Changes** | None |
|
|
@@ -88,7 +88,9 @@ release.
|
|
|
88
88
|
|
|
89
89
|
## 4. Tests that upload to a refused route: send no file
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
For the avatar upload test, already in projects generated from **starter 11.41.9 or later**. The two upload
|
|
92
|
+
refusals in `tests/modules/file-rest.e2e-spec.ts` (401 and 403 on `/files/upload`) still attached a file until
|
|
93
|
+
**starter 11.42.11**.
|
|
92
94
|
|
|
93
95
|
When a role guard refuses a multipart upload route, it answers before the file interceptor reads the body. A
|
|
94
96
|
test that still attaches a file races that answer: the 401/403 arrives while the client is writing the
|
|
@@ -137,6 +139,20 @@ redoing unless you adapted a script to the wrong name.
|
|
|
137
139
|
Unchanged on purpose: `express` stays 5.2.1, because `@nestjs/platform-express` 11.2.7 pins it exactly — a
|
|
138
140
|
direct bump would put two copies of express into your tree. `better-auth` stays on 1.7.7, the current release.
|
|
139
141
|
|
|
142
|
+
## 7. Starter-derived monorepos: a red `audit-suppression-scope.spec.ts`
|
|
143
|
+
|
|
144
|
+
Fixed in **starter 11.42.11**; affects projects generated from **starter 11.41.7 or later** inside a
|
|
145
|
+
fullstack monorepo.
|
|
146
|
+
|
|
147
|
+
`tests/unit/audit-suppression-scope.spec.ts` read `pnpm-workspace.yaml` from the API project at COLLECTION
|
|
148
|
+
time. In a monorepo the API has no `pnpm-workspace.yaml` of its own (it lives at the workspace root), so the
|
|
149
|
+
whole file failed to load with `ENOENT` and the unit suite went red without a single test running. The
|
|
150
|
+
starter now reads the file inside the test and guards the monorepo layout with
|
|
151
|
+
`tests/unit/hoisted-layout.spec.ts`; copy both from the current starter.
|
|
152
|
+
|
|
153
|
+
Optional, also new in starter 11.42.11: its `scripts/check.mjs` ends a hanging `pnpm audit` after a timeout
|
|
154
|
+
(`CHECK_AUDIT_TIMEOUT`) and reports it as not checked, instead of stalling the whole `check`.
|
|
155
|
+
|
|
140
156
|
## Compatibility Notes
|
|
141
157
|
|
|
142
158
|
| Pattern | Status |
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Migration Guide: 11.42.11 → 11.42.12
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| **Security** | Password-reset and verification links are no longer printed on deployed `develop` / `test` stages (§1); auth cookies carry `Secure` on every https deployment, whatever the environment is called (§2) |
|
|
8
|
+
| **Framework API** | Additive only: `isLocalEnv()`, `CoreBetterAuthService.usesSecureCookies()`, optional `secureTransport` / `baseUrl` arguments on the cookie helpers |
|
|
9
|
+
| **Dependencies** | None for the package. The repository's own audit suppression for GHSA-ch52-4w7c-c8xp is gone (§3) |
|
|
10
|
+
| **Breaking Changes** | None |
|
|
11
|
+
| **Migration Effort** | None for most projects. Check §1 if you read auth links from a log outside `local` / `ci` / `e2e`, and §2 if you override a cookie-writing method |
|
|
12
|
+
|
|
13
|
+
## Quick Migration
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm update @lenne.tech/nest-server
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Vendor-mode projects pick it up with the core update. No code or configuration change is required unless one
|
|
20
|
+
of the checks in §1 or §2 applies to you.
|
|
21
|
+
|
|
22
|
+
## 1. Auth links are printed only in local environments
|
|
23
|
+
|
|
24
|
+
`CoreBetterAuthEmailVerificationService.logAuthUrlForDevelopment()` prints the verification and password-reset
|
|
25
|
+
link to stdout so a developer can follow it without a mail server. The URL carries a live bearer token — a reset
|
|
26
|
+
link changes the password outright.
|
|
27
|
+
|
|
28
|
+
Until this release the link was printed everywhere except `production` and `staging`. The deployed `develop` and
|
|
29
|
+
`test` stages of a starter-based project are neither, so they wrote live reset links into their log pipeline,
|
|
30
|
+
where read access to the logs was enough to take over an account.
|
|
31
|
+
|
|
32
|
+
| Environment (`env`, else `NODE_ENV`) | Before | After |
|
|
33
|
+
| ------------------------------------ | ------ | ----- |
|
|
34
|
+
| `local`, `ci`, `e2e` | printed | printed |
|
|
35
|
+
| `develop`, `test`, `development`, any other name, none at all | printed | **not printed** |
|
|
36
|
+
| `production`, `staging`, `NODE_ENV=production` | not printed | not printed |
|
|
37
|
+
|
|
38
|
+
`LT_LOG_AUTH_URLS` gained a value:
|
|
39
|
+
|
|
40
|
+
| Value | Effect |
|
|
41
|
+
| ----- | ------ |
|
|
42
|
+
| unset | the table above |
|
|
43
|
+
| `1` | also print in an environment that is not in the list — for a developer machine with a different environment name |
|
|
44
|
+
| `0` | print nowhere, not even locally |
|
|
45
|
+
|
|
46
|
+
`production` and `staging` never print, whatever the variable says.
|
|
47
|
+
|
|
48
|
+
**What to check:**
|
|
49
|
+
|
|
50
|
+
- **Your local environment has another name** (e.g. `development`): set `LT_LOG_AUTH_URLS=1` in your local
|
|
51
|
+
`.env`. Never set it on a deployment.
|
|
52
|
+
- **A script or CI job reads `[EMAIL VERIFICATION]` / `[PASSWORD RESET]` from the API log** — fine as long as it
|
|
53
|
+
runs the API as `local`, `ci` or `e2e` (`lt dev up`, `lt dev test` and the starter's e2e scripts do). Against a
|
|
54
|
+
deployed stage, read the mail instead (Hub mailbox, Mailpit), which is what that stage should have been doing.
|
|
55
|
+
|
|
56
|
+
The decision uses the new exported helper `isLocalEnv(env)`, which reads the existing `LOCALHOST_URL_ENVS`
|
|
57
|
+
(`local`, `ci`, `e2e`). The SSRF guard's private-target allowance (`privateOutboundTargetsAllowed()`) now reads
|
|
58
|
+
the same helper; its behaviour is unchanged.
|
|
59
|
+
|
|
60
|
+
## 2. Auth cookies carry `Secure` on every https deployment
|
|
61
|
+
|
|
62
|
+
The session cookie is written by two paths: Better-Auth's own handlers (2FA, social callback, passkey) and the
|
|
63
|
+
framework's cookie helper (sign-in and sign-up through `CoreBetterAuthController`, the API middleware, and the
|
|
64
|
+
legacy `token` / `refreshToken` cookies). Better-Auth sets `Secure` from an `https://` base URL. The framework
|
|
65
|
+
helper set it only for `production` / `staging` — so on an https `develop` or `test` stage the cookie that
|
|
66
|
+
establishes the session went out without `Secure`, while Better-Auth's handlers set it on the same deployment.
|
|
67
|
+
|
|
68
|
+
Now:
|
|
69
|
+
|
|
70
|
+
| Cookie | `Secure` when |
|
|
71
|
+
| ------ | ------------- |
|
|
72
|
+
| Better-Auth session cookie written by the framework | Better-Auth's own cookies are secure (read off the instance: `CoreBetterAuthService.usesSecureCookies()`), or the environment is production-like |
|
|
73
|
+
| Legacy `token` / `refreshToken` | `baseUrl` starts with `https://`, or the environment is production-like |
|
|
74
|
+
|
|
75
|
+
`Secure` is only ever added, never removed. A project that switched it off on purpose
|
|
76
|
+
(`betterAuth.options.advanced.defaultCookieAttributes.secure: false`) keeps that outside production and staging.
|
|
77
|
+
|
|
78
|
+
**What to check:**
|
|
79
|
+
|
|
80
|
+
- **A deployment whose `baseUrl` says `https://` but whose browsers reach the API over plain http** now loses the
|
|
81
|
+
framework-written session cookie too — browsers drop a `Secure` cookie received over http. Better-Auth's own
|
|
82
|
+
handlers already behaved this way, so such a deployment had broken 2FA and social sign-in before. Fix the
|
|
83
|
+
`baseUrl` or serve https.
|
|
84
|
+
- **You override a cookie-writing method.** The new inputs reach the helpers only if the caller passes them:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// A CoreAuthController / CoreAuthResolver subclass that overrides processCookies()
|
|
88
|
+
const baseUrl = this.configService.getFastButReadOnly<string>('baseUrl');
|
|
89
|
+
return setLegacyAuthCookies(res, result, cookiesConfig, env, baseUrl);
|
|
90
|
+
|
|
91
|
+
// Code that builds its own Better-Auth cookie helper
|
|
92
|
+
createCookieHelper(basePath, { ...options, secureTransport: this.betterAuthService.usesSecureCookies() });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Without the argument the helper keeps the previous, environment-only rule — nothing breaks, the fix just does
|
|
96
|
+
not reach that path.
|
|
97
|
+
- **Your tests stub `CoreBetterAuthService`**: no change needed. The controller calls `usesSecureCookies()`
|
|
98
|
+
optionally, so a stub without it keeps the previous behaviour.
|
|
99
|
+
|
|
100
|
+
## 3. Audit suppression GHSA-ch52-4w7c-c8xp removed (repository only)
|
|
101
|
+
|
|
102
|
+
This repository no longer lists GHSA-ch52-4w7c-c8xp (`http-cache-semantics <= 4.2.0`, dev tooling only:
|
|
103
|
+
`@swc/cli` > … > `cacheable-request`) in `auditConfig.ignoreGhsas`. Its lockfile now resolves 4.3.0, which
|
|
104
|
+
`cacheable-request`'s `^4.2.0` takes on any fresh resolve. Nothing here reaches the published package.
|
|
105
|
+
|
|
106
|
+
4.3.0 is **not a fix**: its max-stale code is identical to 4.2.0's. It only lies outside the advisory's declared
|
|
107
|
+
range, because the advisory was written as "up to the newest release" and never followed. The maintainer disputes
|
|
108
|
+
the report (kornelski/http-cache-semantics#56); a withdrawal request is open (github/advisory-database#10139),
|
|
109
|
+
and an independent re-test there reproduces the behaviour through 4.3.0, so a range extension is possible.
|
|
110
|
+
|
|
111
|
+
**For starter-derived projects** that carry the same entry (nest-server-starter 11.41.7 – 11.42.11):
|
|
112
|
+
|
|
113
|
+
- Lockfile on 4.3.0 (`pnpm why http-cache-semantics`): remove the entry from `ignoreGhsas` together with its row in
|
|
114
|
+
`tests/unit/audit-suppression-scope.spec.ts`. On 4.3.0 the entry hides nothing and would swallow the advisory
|
|
115
|
+
unseen the day its range is extended; without it, that day turns `pnpm audit` red, which is the moment to
|
|
116
|
+
re-argue the dev-only case.
|
|
117
|
+
- Lockfile still on 4.2.0: `pnpm update http-cache-semantics` first, then as above. Keep the entry instead if
|
|
118
|
+
your resolve cannot move.
|
|
119
|
+
|
|
120
|
+
## Compatibility Notes
|
|
121
|
+
|
|
122
|
+
| Pattern | Status |
|
|
123
|
+
| ------- | ------ |
|
|
124
|
+
| npm-mode and vendor-mode projects without cookie-related overrides | Nothing to do |
|
|
125
|
+
| `lt dev up` / `lt dev test` / starter e2e scripts reading auth links from the log | Unchanged — they run as `local` / `e2e` |
|
|
126
|
+
| Subclass overriding `processCookies()` (legacy) | Pass `baseUrl` (§2), otherwise unchanged behaviour |
|
|
127
|
+
| Custom `createCookieHelper()` call | Pass `secureTransport` (§2), otherwise unchanged behaviour |
|
|
128
|
+
| Test stubs of `CoreBetterAuthService` | Compatible |
|
|
129
|
+
|
|
130
|
+
## Troubleshooting
|
|
131
|
+
|
|
132
|
+
**The `[PASSWORD RESET]` / `[EMAIL VERIFICATION]` line disappeared from your local log**
|
|
133
|
+
Your environment is not named `local`, `ci` or `e2e`. Set `LT_LOG_AUTH_URLS=1` locally (§1).
|
|
134
|
+
|
|
135
|
+
**After the update, sign-in succeeds but the next request is anonymous on a non-production stage**
|
|
136
|
+
The browser dropped the now-`Secure` session cookie because the page or API is served over http while `baseUrl`
|
|
137
|
+
says https. Serve https or correct `baseUrl` (§2).
|
|
138
|
+
|
|
139
|
+
## Module Documentation
|
|
140
|
+
|
|
141
|
+
- [Better-Auth module README](../src/core/modules/better-auth/README.md) — `usesSecureCookies()`
|
|
142
|
+
- [Configurable features](../.claude/rules/configurable-features.md) — Cookies row, Outbound URL Guard row
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Migration Guide: 11.42.12 → 11.42.13
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| **Bugfixes** | `CoreTenantGuard` refusals carry translatable error codes (§1); `GET /files/id/:id` answers 404 instead of 500 for an id that is not an ObjectId (§3) |
|
|
8
|
+
| **New Features** | A finished TUS upload names the stored file in the `X-File-Id` response header (§2) |
|
|
9
|
+
| **Framework API** | Additive only: `ErrorCode.NOT_TENANT_MEMBER` … `VERIFICATION_REQUIRED` (`LTNS_0103`–`LTNS_0107`), `TUS_FILE_ID_HEADER`, `CoreFileService.isMalformedFileId()` (protected) |
|
|
10
|
+
| **Dependencies** | None for the package |
|
|
11
|
+
| **Breaking Changes** | None. Status codes and access decisions are unchanged; only the message text of five tenant refusals gains a prefix (§1) |
|
|
12
|
+
| **Migration Effort** | None for most projects. Check §1 if you compare a tenant refusal message for exact equality, and §2 if your frontend derives a file id from the TUS URL |
|
|
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 unless one of
|
|
21
|
+
the checks below applies to you.
|
|
22
|
+
|
|
23
|
+
## 1. Tenant guard refusals are translatable
|
|
24
|
+
|
|
25
|
+
Five of the guard's 403 refusals were thrown as plain English strings. Without the `#LTNS_XXXX:` prefix the
|
|
26
|
+
frontend error translation could not map them, so users saw the raw English text in a toast.
|
|
27
|
+
|
|
28
|
+
| Code | Message (unchanged wording) | Refused because |
|
|
29
|
+
| ----------- | ----------------------------- | ---------------------------------------------------------------------------------------- |
|
|
30
|
+
| `LTNS_0103` | `Not a member of this tenant` | the tenant header names a tenant the user has no active membership in |
|
|
31
|
+
| `LTNS_0104` | `Insufficient tenant role` | the membership role does not satisfy the required tenant role |
|
|
32
|
+
| `LTNS_0105` | `Insufficient role` | `user.roles` does not meet the required role (no header, or a `@SkipTenantCheck` route) |
|
|
33
|
+
| `LTNS_0106` | `Tenant is inactive` | `multiTenancy.isTenantActive` reports the header's tenant as switched off |
|
|
34
|
+
| `LTNS_0107` | `Verification required` | an `S_VERIFIED` endpoint, and the user is not verified |
|
|
35
|
+
|
|
36
|
+
The response `message` is now e.g. `#LTNS_0103: Not a member of this tenant`. Status code (403), exception class
|
|
37
|
+
and the decision itself are unchanged. German and English translations ship in the error catalog
|
|
38
|
+
(`GET /i18n/errors/:locale`), so `nuxt-extensions` translates them without any change.
|
|
39
|
+
|
|
40
|
+
**What to check:**
|
|
41
|
+
|
|
42
|
+
- **You compare the message for exact equality** (`toBe('Insufficient role')`, `message === '…'`): switch to a
|
|
43
|
+
substring match or, better, to the code:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Before
|
|
47
|
+
expect(res.body.message).toBe('Not a member of this tenant');
|
|
48
|
+
|
|
49
|
+
// After
|
|
50
|
+
expect(res.body.message).toBe(ErrorCode.NOT_TENANT_MEMBER);
|
|
51
|
+
// or, tolerant of both versions
|
|
52
|
+
expect(res.body.message).toContain('Not a member of this tenant');
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Substring matches (`toContain`, log filters) keep working unchanged — the original wording stays behind the
|
|
56
|
+
code.
|
|
57
|
+
- **Your product does not call them tenants:** override the translations in your own error registry
|
|
58
|
+
(`errorCode.additionalErrorRegistry`). It is registered after the core, and translations are merged per code,
|
|
59
|
+
so your entry for `LTNS_0103` wins.
|
|
60
|
+
|
|
61
|
+
## 2. A finished TUS upload names the stored file (`X-File-Id`)
|
|
62
|
+
|
|
63
|
+
Until now a client knew only the TUS URL after an upload. Its last segment is the TUS **upload** id — a key of the
|
|
64
|
+
staging store, not of the file store — and `/files/id/<upload-id>` cannot answer with the file. The response that
|
|
65
|
+
completes an upload now carries the ID of the stored file:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
HTTP/1.1 204 No Content
|
|
69
|
+
Upload-Offset: 52428800
|
|
70
|
+
X-File-Id: 6720c1f0a2b4c8e9d0f1a2b3
|
|
71
|
+
Access-Control-Expose-Headers: …, X-File-Id
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- Sent on the completing request only: the last `PATCH`, or the `POST` of a creation-with-upload. Creation and
|
|
75
|
+
partial `PATCH`es store nothing yet and carry no header.
|
|
76
|
+
- Listed in `Access-Control-Expose-Headers`, so a browser on a separate app origin can read it.
|
|
77
|
+
- The name is exported as `TUS_FILE_ID_HEADER`.
|
|
78
|
+
|
|
79
|
+
With `tus-js-client`:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
let fileId: string | undefined;
|
|
83
|
+
const upload = new Upload(file, {
|
|
84
|
+
endpoint: '/tus',
|
|
85
|
+
onAfterResponse: (_req, res) => {
|
|
86
|
+
const id = res.getHeader('X-File-Id');
|
|
87
|
+
if (id) {
|
|
88
|
+
fileId = id;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
onSuccess: () => {
|
|
92
|
+
// use `/files/id/${fileId}` — NOT the last segment of `upload.url`
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**What to check:**
|
|
98
|
+
|
|
99
|
+
- **Your frontend takes the last segment of the TUS URL as the file id:** read `X-File-Id` instead. Treat a
|
|
100
|
+
missing header (an older server, see the next point) as "no file id known" — never fall back to the URL.
|
|
101
|
+
- **You override `onUploadComplete()`:** nothing to change as long as the override calls
|
|
102
|
+
`super.onUploadComplete(upload)` with the `upload` it received — both `await super…` and `return super…` keep
|
|
103
|
+
compiling and keep the header. An override that never calls `super` (it stores the file itself) completes the
|
|
104
|
+
upload without `X-File-Id`. The method's signature did not change.
|
|
105
|
+
|
|
106
|
+
## 3. `GET /files/id/:id` answers 404 for an id that is not an ObjectId
|
|
107
|
+
|
|
108
|
+
An id that is not a 24-character hex string — typically a TUS upload id (32 hex characters) taken for the file id,
|
|
109
|
+
see §2 — reached `new ObjectId()` and threw a `BSONError`, so the route answered **500**. It now answers exactly
|
|
110
|
+
like an unknown id: `404` with `ErrorCode.FILE_NOT_FOUND`.
|
|
111
|
+
|
|
112
|
+
The guard sits in `CoreFileService` (`getFileInfo`, `resolveFile`, `getFileStream`, `getBuffer`,
|
|
113
|
+
`getDownloadUrl`, `getRawFileInfo`, `deleteFile`, `duplicateById`), so every route built on those methods
|
|
114
|
+
benefits — including a project's own `GET /files/info/:id` / `DELETE /files/:id`. Only strings are judged; an
|
|
115
|
+
`ObjectId` instance keeps its previous path.
|
|
116
|
+
|
|
117
|
+
**What to check:**
|
|
118
|
+
|
|
119
|
+
- **Your files use ids that are not ObjectIds** (unusual — every core store keys by ObjectId): override the new
|
|
120
|
+
protected `isMalformedFileId(id)` in your `FileService` to accept them.
|
|
121
|
+
- **A monitor or test expected the 500:** it now gets 404.
|
|
122
|
+
|
|
123
|
+
## Compatibility Notes
|
|
124
|
+
|
|
125
|
+
| Pattern | Status |
|
|
126
|
+
| ------- | ------ |
|
|
127
|
+
| npm-mode and vendor-mode projects without tenant-message comparisons or TUS overrides | Nothing to do |
|
|
128
|
+
| Tests or frontends matching tenant refusals with `toContain` / a substring | Compatible |
|
|
129
|
+
| Tests comparing a tenant refusal message with `toBe` / `===` | Compare against `ErrorCode.*` (§1) |
|
|
130
|
+
| `TusService extends CoreTusService` overriding `onUploadComplete()` and calling `super` | Compatible, header included |
|
|
131
|
+
| `onUploadComplete()` override that does not call `super` | Compatible, no `X-File-Id` (§2) |
|
|
132
|
+
| `FileService extends CoreFileService` with a `checkRights()` that reads `getRawFileInfo()` | Compatible; a malformed id now reaches it as `null` |
|
|
133
|
+
|
|
134
|
+
## Troubleshooting
|
|
135
|
+
|
|
136
|
+
**The browser never sees `X-File-Id` although the server sends it**
|
|
137
|
+
The header is exposed by the tus server itself. If a proxy rewrites `Access-Control-Expose-Headers`, add
|
|
138
|
+
`X-File-Id` there as well.
|
|
139
|
+
|
|
140
|
+
**`X-File-Id` is missing on the completing response**
|
|
141
|
+
Check for an `onUploadComplete()` override that does not call `super.onUploadComplete(upload)`, or that passes it a
|
|
142
|
+
copy of `upload` (§2).
|
|
143
|
+
|
|
144
|
+
**A tenant refusal shows in English although the frontend translates errors**
|
|
145
|
+
The frontend translation loads the catalog from `GET /i18n/errors/:locale`. Make sure the error-code module is
|
|
146
|
+
registered (it is by default) and the catalog contains `LTNS_0103`–`LTNS_0107`.
|
|
147
|
+
|
|
148
|
+
## Module Documentation
|
|
149
|
+
|
|
150
|
+
- [Tenant module README](../src/core/modules/tenant/README.md) — "Refusal Codes"
|
|
151
|
+
- [Error codes](../docs/error-codes.md) — `LTNS_0103`–`LTNS_0107`
|
|
152
|
+
- [TUS module README](../src/core/modules/tus/README.md) — "The ID of the Stored File", "Custom Upload Handler"
|
|
153
|
+
- [TUS integration checklist](../src/core/modules/tus/INTEGRATION-CHECKLIST.md)
|
|
154
|
+
- [File module README](../src/core/modules/file/README.md) — "Error responses"
|
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.13",
|
|
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",
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
"oxfmt": "0.71.0",
|
|
214
214
|
"oxlint": "1.87.0",
|
|
215
215
|
"rimraf": "6.1.3",
|
|
216
|
-
"smtp-server": "3.19.
|
|
216
|
+
"smtp-server": "3.19.21",
|
|
217
217
|
"ts-node": "10.9.2",
|
|
218
218
|
"tsconfig-paths": "4.2.0",
|
|
219
219
|
"tsx": "4.23.15",
|
|
@@ -51,23 +51,37 @@ export interface AuthCookieDefaultOptions {
|
|
|
51
51
|
*
|
|
52
52
|
* - `httpOnly: true` — prevents JavaScript access (XSS mitigation)
|
|
53
53
|
* - `sameSite: 'lax'` — CSRF mitigation
|
|
54
|
-
* - `secure
|
|
54
|
+
* - `secure` — HTTPS-only on an `https://` `baseUrl`, and always in production/staging
|
|
55
55
|
*
|
|
56
|
-
* Used by
|
|
56
|
+
* Used by the Legacy Auth cookie helper. (The BetterAuth helper decides `secure` the same way, but
|
|
57
|
+
* reads the https half off the Better-Auth instance.)
|
|
57
58
|
*
|
|
58
59
|
* @param env - App-level environment from `IServerOptions.env` (e.g. `'production'`, `'staging'`, `'ci'`).
|
|
59
60
|
* Falls back to `process.env.NODE_ENV === 'production'` if absent.
|
|
61
|
+
* @param baseUrl - The server's `IServerOptions.baseUrl`. An `https://` value sets `secure` whatever
|
|
62
|
+
* the environment is called — since 11.42.12, because the deployed `develop` / `test`
|
|
63
|
+
* stages of a starter-based project are not production-like and got cookies without it.
|
|
60
64
|
*
|
|
61
65
|
* @since 11.25.0
|
|
62
66
|
*/
|
|
63
|
-
export function getDefaultAuthCookieOptions(env?: string): AuthCookieDefaultOptions {
|
|
67
|
+
export function getDefaultAuthCookieOptions(env?: string, baseUrl?: string): AuthCookieDefaultOptions {
|
|
64
68
|
return {
|
|
65
69
|
httpOnly: true,
|
|
66
70
|
sameSite: 'lax',
|
|
67
|
-
secure: isProductionLikeEnv(env),
|
|
71
|
+
secure: isProductionLikeEnv(env) || isHttpsUrl(baseUrl),
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Whether a URL uses https, the transport signal for the `Secure` cookie attribute (Better-Auth
|
|
77
|
+
* derives its own the same way). Trimmed and case-insensitive; anything that is not a string is not.
|
|
78
|
+
*
|
|
79
|
+
* @since 11.42.12
|
|
80
|
+
*/
|
|
81
|
+
function isHttpsUrl(url: unknown): boolean {
|
|
82
|
+
return typeof url === 'string' && url.trim().toLowerCase().startsWith('https://');
|
|
83
|
+
}
|
|
84
|
+
|
|
71
85
|
/**
|
|
72
86
|
* Asserts that the cookies configuration is safe for production/staging environments.
|
|
73
87
|
*
|
|
@@ -114,6 +128,7 @@ export function assertCookiesProductionSafe(
|
|
|
114
128
|
* @param result - Auth result containing `token` and `refreshToken` (modified in place)
|
|
115
129
|
* @param cookies - The `cookies` config value from IServerOptions
|
|
116
130
|
* @param env - App-level env from `IServerOptions.env` (used for `secure` flag derivation)
|
|
131
|
+
* @param baseUrl - `IServerOptions.baseUrl`; an `https://` value sets `secure` too (since 11.42.12)
|
|
117
132
|
* @returns The (possibly modified) result object
|
|
118
133
|
*
|
|
119
134
|
* @since 11.25.0
|
|
@@ -123,12 +138,13 @@ export function setLegacyAuthCookies<T extends { refreshToken?: string; token?:
|
|
|
123
138
|
result: T,
|
|
124
139
|
cookies: boolean | ICookiesConfig | undefined,
|
|
125
140
|
env?: string,
|
|
141
|
+
baseUrl?: string,
|
|
126
142
|
): T {
|
|
127
143
|
if (!isCookiesEnabled(cookies)) {
|
|
128
144
|
return result;
|
|
129
145
|
}
|
|
130
146
|
|
|
131
|
-
const cookieOptions = getDefaultAuthCookieOptions(env);
|
|
147
|
+
const cookieOptions = getDefaultAuthCookieOptions(env, baseUrl);
|
|
132
148
|
|
|
133
149
|
// If result is absent or not an object, clear any existing cookies (logout path)
|
|
134
150
|
if (!result || typeof result !== 'object') {
|
|
@@ -261,12 +277,40 @@ export const LOCALHOST_URL_DEFAULTS = {
|
|
|
261
277
|
} as const;
|
|
262
278
|
|
|
263
279
|
/**
|
|
264
|
-
*
|
|
280
|
+
* The LOCAL environments: they run on a developer machine or in a test job, never as a deployment.
|
|
281
|
+
*
|
|
282
|
+
* They fall back to {@link LOCALHOST_URL_DEFAULTS} when no URLs are configured, and
|
|
283
|
+
* {@link isLocalEnv} reads this list for every other "local only" decision (the SSRF guard's
|
|
284
|
+
* private targets, the auth-link log). One list, so the decisions cannot drift apart.
|
|
285
|
+
*
|
|
286
|
+
* Deliberately NOT `development` or `test`: in projects built from nest-server-starter `test` is a
|
|
287
|
+
* DEPLOYED stage (often the customer's QA stage), and `develop`/`development` name a deployed dev
|
|
288
|
+
* stage as often as a laptop.
|
|
265
289
|
*
|
|
266
290
|
* @since 11.27.5
|
|
267
291
|
*/
|
|
268
292
|
export const LOCALHOST_URL_ENVS: readonly string[] = ['ci', 'e2e', 'local'];
|
|
269
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Checks whether the given environment is local ({@link LOCALHOST_URL_ENVS}: `local`, `ci`, `e2e`),
|
|
296
|
+
* matched trimmed and case-insensitively.
|
|
297
|
+
*
|
|
298
|
+
* Not the complement of {@link isProductionLikeEnv}. That one answers "must this be hardened?" and
|
|
299
|
+
* names only the environments that obviously hold real users; everything it does not name counts
|
|
300
|
+
* as non-production, including the deployed `develop` and `test` stages of a starter-based project.
|
|
301
|
+
* Use this one when the question is the opposite — "may this run without protection?" — so that an
|
|
302
|
+
* environment nobody listed stays protected. An unknown or missing name answers `false`.
|
|
303
|
+
*
|
|
304
|
+
* @since 11.42.12
|
|
305
|
+
*/
|
|
306
|
+
export function isLocalEnv(env?: string): boolean {
|
|
307
|
+
return LOCALHOST_URL_ENVS.includes(
|
|
308
|
+
String(env ?? '')
|
|
309
|
+
.trim()
|
|
310
|
+
.toLowerCase(),
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
270
314
|
/**
|
|
271
315
|
* The hostname label stripped from `baseUrl` to derive `appUrl`.
|
|
272
316
|
*/
|
|
@@ -3,6 +3,7 @@ import { BlockList, isIP } from 'node:net';
|
|
|
3
3
|
import { Agent, buildConnector, fetch as undiciFetch } from 'undici';
|
|
4
4
|
|
|
5
5
|
import { ConfigService } from '../services/config.service';
|
|
6
|
+
import { isLocalEnv } from './cookies.helper';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Requests to a URL a USER entered — a webhook, an export target, an ERP address.
|
|
@@ -53,12 +54,6 @@ for (const [network, prefix] of [
|
|
|
53
54
|
blocked.addSubnet(network, prefix, 'ipv6');
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
/**
|
|
57
|
-
* Environments in which internal targets are allowed unless configured otherwise. Deliberately NOT
|
|
58
|
-
* `test`: in projects built from nest-server-starter that is a DEPLOYED stage.
|
|
59
|
-
*/
|
|
60
|
-
const LOCAL_ENVIRONMENTS = new Set(['ci', 'e2e', 'local']);
|
|
61
|
-
|
|
62
57
|
/** Thrown for a target that is, or resolves to, an internal address. */
|
|
63
58
|
export class OutboundUrlBlockedError extends Error {
|
|
64
59
|
constructor(
|
|
@@ -93,10 +88,9 @@ export function privateOutboundTargetsAllowed(): boolean {
|
|
|
93
88
|
if (flag === false || flag === 'false') {
|
|
94
89
|
return false;
|
|
95
90
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return LOCAL_ENVIRONMENTS.has(env);
|
|
91
|
+
// Internal targets are allowed only in the local environments (`isLocalEnv`), deliberately not
|
|
92
|
+
// in `test`: in projects built from nest-server-starter that is a DEPLOYED stage.
|
|
93
|
+
return isLocalEnv(config?.env ?? process.env.NODE_ENV);
|
|
100
94
|
}
|
|
101
95
|
|
|
102
96
|
/** Whether an IP address is internal. A hostname (not an IP) answers false — resolve it first. */
|
|
@@ -192,6 +192,7 @@ export class CoreAuthController {
|
|
|
192
192
|
protected processCookies(res: ResponseType, result: any) {
|
|
193
193
|
const cookiesConfig = this.configService.getFastButReadOnly<boolean | ICookiesConfig>('cookies');
|
|
194
194
|
const env = this.configService.getFastButReadOnly<string>('env');
|
|
195
|
-
|
|
195
|
+
const baseUrl = this.configService.getFastButReadOnly<string>('baseUrl');
|
|
196
|
+
return setLegacyAuthCookies(res, result, cookiesConfig, env, baseUrl);
|
|
196
197
|
}
|
|
197
198
|
}
|
|
@@ -174,6 +174,7 @@ export class CoreAuthResolver {
|
|
|
174
174
|
protected processCookies(ctx: { res: ResponseType }, result: any) {
|
|
175
175
|
const cookiesConfig = this.configService.getFastButReadOnly<boolean | ICookiesConfig>('cookies');
|
|
176
176
|
const env = this.configService.getFastButReadOnly<string>('env');
|
|
177
|
-
|
|
177
|
+
const baseUrl = this.configService.getFastButReadOnly<string>('baseUrl');
|
|
178
|
+
return setLegacyAuthCookies(ctx.res, result, cookiesConfig, env, baseUrl);
|
|
178
179
|
}
|
|
179
180
|
}
|
|
@@ -1505,6 +1505,7 @@ export class MyService {
|
|
|
1505
1505
|
| `getBasePath()` | Get the base path for endpoints |
|
|
1506
1506
|
| `getBaseUrl()` | Get the base URL |
|
|
1507
1507
|
| `getCookieDomain()` | Get resolved cross-subdomain cookie domain |
|
|
1508
|
+
| `usesSecureCookies()` | Whether Better-Auth's cookies carry Secure |
|
|
1508
1509
|
| `getSession(req)` | Get current session from request |
|
|
1509
1510
|
| `revokeSession(token)` | Revoke a session (logout) |
|
|
1510
1511
|
| `isSessionExpiringSoon(session, t?)` | Check if session is expiring soon |
|
|
@@ -172,6 +172,7 @@ export class CoreBetterAuthApiMiddleware implements NestMiddleware {
|
|
|
172
172
|
env,
|
|
173
173
|
legacyCookieEnabled: false, // Middleware doesn't need legacy cookie
|
|
174
174
|
secret: config?.secret, // Required for cookie signing
|
|
175
|
+
secureTransport: this.betterAuthService.usesSecureCookies?.() === true, // Same `Secure` rule as Better-Auth
|
|
175
176
|
},
|
|
176
177
|
this.logger,
|
|
177
178
|
);
|