@lenne.tech/nest-server 11.43.0 → 11.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,73 @@
1
+ # Migration Guide: 11.43.0 → 11.43.1
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ | -------------------- | -------------------------------------------------------------------------------------------------------- |
7
+ | **Bugfixes** | Bearer JWTs issued by Better-Auth are accepted again when only the server's `baseUrl` is set (`NSC__BASE_URL`, every `lt dev up`) — they answered **401** on every request since 11.10.3 (§1) |
8
+ | **New Features** | None |
9
+ | **Framework API** | `CoreBetterAuthService.getBaseUrl()` returns the ORIGIN of the base URL the Better-Auth instance runs on (§1). No changed signatures, no new exports |
10
+ | **Dependencies** | Unchanged |
11
+ | **Breaking Changes** | None |
12
+ | **Migration Effort** | None. A workaround that set `betterAuth.baseUrl` next to `baseUrl` can be 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. Bearer JWTs answered 401 when only the server `baseUrl` was set
23
+
24
+ Better-Auth signs every JWT with `iss` and `aud` set to the base URL it runs on. `createBetterAuthInstance()`
25
+ resolves that base URL as `betterAuth.baseUrl` → the server's `baseUrl` → the localhost default, and Better-Auth
26
+ stores the **origin** of the result. The framework verified Bearer JWTs against
27
+ `CoreBetterAuthService.getBaseUrl()`, which read only `betterAuth.baseUrl` and otherwise answered
28
+ `http://localhost:3000`.
29
+
30
+ So as soon as a deployment set the server's `baseUrl` without also setting `betterAuth.baseUrl`, the two disagreed,
31
+ and every Bearer JWT was refused with `401 #LTNS_0100` (`unexpected "iss" claim value` in the debug log). That is the
32
+ configuration `nest-server-starter` ships (`baseUrl: process.env.BASE_URL`), every stage that sets `NSC__BASE_URL`,
33
+ and every `lt dev up`. Cookie sessions were unaffected, so the defect surfaced only for clients in JWT mode — for
34
+ example `@lenne.tech/nuxt-extensions` once it switches to JWT after the login, which then received 401 on every
35
+ request.
36
+
37
+ `getBaseUrl()` now reads the base URL off the live Better-Auth instance and returns its origin — exactly the value
38
+ Better-Auth signs with. A trailing slash, a path or a default port in the configured value
39
+ (`https://api.example.com/`, `https://example.com/api`, `https://api.example.com:443`) is not part of the claim and
40
+ no longer causes a mismatch either.
41
+
42
+ **What to check:**
43
+
44
+ - **You set `betterAuth.baseUrl` (or `NSC__BETTER_AUTH__BASE_URL`) only to work around this:** it can go. It still
45
+ wins over the server's `baseUrl` when set, so leaving it in place changes nothing.
46
+ - **Your own code calls `getBaseUrl()`:** it now returns the URL Better-Auth actually runs on, as an origin. Code
47
+ that joined an absolute path onto it (`new URL('/iam/sign-in/email', getBaseUrl())`) keeps working; code that
48
+ relied on a path being part of the returned value must append that path itself. With only the server's `baseUrl`
49
+ set, it now answers that URL instead of `http://localhost:3000`.
50
+ - **A vendor-mode project patched `getBaseUrl()` locally** for the same reason: the patch becomes obsolete with this
51
+ core update and can be dropped.
52
+
53
+ ## Compatibility Notes
54
+
55
+ | Pattern | Status |
56
+ | ------- | ------ |
57
+ | Only `baseUrl` / `NSC__BASE_URL` set (starter default, `lt dev up`) | Bearer JWTs accepted again |
58
+ | `betterAuth.baseUrl` set explicitly | Unchanged |
59
+ | Neither set (`local` / `ci` / `e2e` defaults) | Unchanged — both sides use `http://localhost:3000` |
60
+ | Cookie-only clients | Unchanged |
61
+ | A JWT naming another issuer | Still refused |
62
+
63
+ ## Troubleshooting
64
+
65
+ **Bearer requests still answer 401 after the update**
66
+ Decode the token (`iss` claim) and compare it with `getBaseUrl()`. They must be identical. If they differ, check
67
+ whether `betterAuth.options.baseURL` overrides the base URL with something that is not an absolute URL — then the
68
+ framework falls back to the configured value.
69
+
70
+ ## Module Documentation
71
+
72
+ - [Better-Auth module](../src/core/modules/better-auth/README.md)
73
+ - Implementation: `src/core/modules/better-auth/core-better-auth.service.ts` → `getBaseUrl()`, `verifyJwksToken()`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.43.0",
3
+ "version": "11.43.1",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -697,9 +697,27 @@ export class CoreBetterAuthService implements OnApplicationBootstrap, OnModuleIn
697
697
  }
698
698
 
699
699
  /**
700
- * Gets the base URL for better-auth
700
+ * Gets the base URL Better-Auth runs on: the ORIGIN of the `baseURL` the live instance was built with.
701
+ *
702
+ * Read off the instance rather than off `betterAuth.baseUrl`, because `createBetterAuthInstance()`
703
+ * resolves it as `betterAuth.baseUrl` → the server's `baseUrl` → localhost default. Better-Auth signs
704
+ * every JWT with the origin of that value as `iss` and `aud` (`createAuthContext()` stores
705
+ * `new URL(baseURL).origin`), so this answers the origin too — a trailing slash, a path or a default
706
+ * port in the configured string is not part of the claim. Until 11.43.1 this read only
707
+ * `betterAuth.baseUrl` and answered `http://localhost:3000` whenever just the server's `baseUrl` was
708
+ * set (`NSC__BASE_URL`, and every `lt dev up`), so the framework refused every Bearer JWT Better-Auth
709
+ * had issued. Without an instance (Better-Auth disabled) it falls back to the configured value.
701
710
  */
702
711
  getBaseUrl(): string {
712
+ const instanceBaseUrl = (this.getInstance() as unknown as { options?: { baseURL?: unknown } } | null)?.options
713
+ ?.baseURL;
714
+ if (typeof instanceBaseUrl === 'string' && instanceBaseUrl) {
715
+ try {
716
+ return new URL(instanceBaseUrl).origin;
717
+ } catch {
718
+ // Not an absolute URL: Better-Auth cannot derive an origin from it either, so use the configured value.
719
+ }
720
+ }
703
721
  return this.config.baseUrl || 'http://localhost:3000';
704
722
  }
705
723
 
@@ -710,8 +728,8 @@ export class CoreBetterAuthService implements OnApplicationBootstrap, OnModuleIn
710
728
  * `createBetterAuthInstance()` pins `useSecureCookies: false` (for the cookie NAME) and restores
711
729
  * the transport flag through `advanced.defaultCookieAttributes.secure` on an `https://` base URL;
712
730
  * a project may override either. Reading the instance rather than re-deriving from a URL gives
713
- * exactly the answer Better-Auth applies — including the base URL it actually uses, which
714
- * {@link getBaseUrl} does not know when only the server's `baseUrl` is set.
731
+ * exactly the answer Better-Auth applies — including the base URL it actually uses, the same source
732
+ * {@link getBaseUrl} reads since 11.43.1.
715
733
  *
716
734
  * The framework's own cookie helper mirrors this, so a session cookie carries `Secure` no matter
717
735
  * which path wrote it.