@lenne.tech/nest-server 12.2.0 → 12.3.0
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 +31 -4
- package/.claude/rules/configurable-features.md +7 -6
- package/.claude/rules/package-management.md +13 -1
- package/.claude/rules/persistence.md +2 -2
- package/.claude/rules/testing.md +5 -5
- package/.claude/rules/versioning.md +13 -2
- package/CLAUDE.md +5 -0
- package/FRAMEWORK-API.md +4 -3
- package/README.md +2 -2
- package/dist/config.env.js +2 -2
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/helpers/graphql-ws-connection.helper.d.ts +4 -0
- package/dist/core/common/helpers/graphql-ws-connection.helper.js +17 -3
- package/dist/core/common/helpers/graphql-ws-connection.helper.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +8 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.d.ts +3 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js +29 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
- package/dist/core/modules/ai/core-ai.constants.d.ts +2 -0
- package/dist/core/modules/ai/core-ai.constants.js +2 -0
- package/dist/core/modules/ai/core-ai.constants.js.map +1 -1
- package/dist/core/modules/ai/core-ai.controller.d.ts +6 -0
- package/dist/core/modules/ai/core-ai.controller.js +31 -1
- package/dist/core/modules/ai/core-ai.controller.js.map +1 -1
- package/dist/core/modules/ai/core-ai.module.d.ts +2 -0
- package/dist/core/modules/ai/core-ai.module.js +8 -1
- package/dist/core/modules/ai/core-ai.module.js.map +1 -1
- package/dist/core/modules/ai/core-ai.resolver.d.ts +6 -0
- package/dist/core/modules/ai/core-ai.resolver.js +32 -0
- package/dist/core/modules/ai/core-ai.resolver.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.d.ts +65 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.js +6 -3
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js +48 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-tool-authorization.helper.d.ts +9 -0
- package/dist/core/modules/ai/helpers/ai-tool-authorization.helper.js +54 -0
- package/dist/core/modules/ai/helpers/ai-tool-authorization.helper.js.map +1 -0
- package/dist/core/modules/ai/index.d.ts +7 -0
- package/dist/core/modules/ai/index.js +6 -0
- package/dist/core/modules/ai/index.js.map +1 -1
- package/dist/core/modules/ai/inputs/core-ai-settings.input.d.ts +3 -0
- package/dist/core/modules/ai/inputs/core-ai-settings.input.js +31 -0
- package/dist/core/modules/ai/inputs/core-ai-settings.input.js.map +1 -0
- package/dist/core/modules/ai/interfaces/ai-tool.interface.d.ts +2 -0
- package/dist/core/modules/ai/models/core-ai-settings.model.d.ts +4 -0
- package/dist/core/modules/ai/models/core-ai-settings.model.js +30 -0
- package/dist/core/modules/ai/models/core-ai-settings.model.js.map +1 -0
- package/dist/core/modules/ai/models/core-ai-user-setting.model.d.ts +8 -0
- package/dist/core/modules/ai/models/core-ai-user-setting.model.js +45 -0
- package/dist/core/modules/ai/models/core-ai-user-setting.model.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai-conversation.service.d.ts +4 -1
- package/dist/core/modules/ai/services/core-ai-conversation.service.js +16 -4
- package/dist/core/modules/ai/services/core-ai-conversation.service.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js +6 -6
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-mcp.service.d.ts +27 -2
- package/dist/core/modules/ai/services/core-ai-mcp.service.js +118 -11
- package/dist/core/modules/ai/services/core-ai-mcp.service.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-prompt-builder.service.js +9 -6
- package/dist/core/modules/ai/services/core-ai-prompt-builder.service.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-user-setting.service.d.ts +17 -0
- package/dist/core/modules/ai/services/core-ai-user-setting.service.js +72 -0
- package/dist/core/modules/ai/services/core-ai-user-setting.service.js.map +1 -0
- package/dist/core/modules/ai/services/core-ai.service.d.ts +8 -3
- package/dist/core/modules/ai/services/core-ai.service.js +68 -29
- package/dist/core/modules/ai/services/core-ai.service.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.config.js +3 -1
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +2 -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-path-guard.middleware.d.ts +8 -0
- package/dist/core/modules/better-auth/core-better-auth-path-guard.middleware.js +64 -0
- package/dist/core/modules/better-auth/core-better-auth-path-guard.middleware.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js +2 -1
- package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.d.ts +1 -1
- package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js +5 -3
- package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.module.js +2 -0
- package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
- package/dist/core/modules/better-auth/index.d.ts +1 -0
- package/dist/core/modules/better-auth/index.js +1 -0
- package/dist/core/modules/better-auth/index.js.map +1 -1
- package/dist/core/modules/error-code/error-codes.d.ts +18 -0
- package/dist/core/modules/error-code/error-codes.js +16 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -1
- package/dist/core/modules/sync/core-sync.service.d.ts +1 -0
- package/dist/core/modules/sync/core-sync.service.js +31 -2
- package/dist/core/modules/sync/core-sync.service.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.guard.js +10 -0
- package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.service.d.ts +21 -1
- package/dist/core/modules/tenant/core-tenant.service.js +153 -1
- package/dist/core/modules/tenant/core-tenant.service.js.map +1 -1
- package/dist/server/modules/error-code/error-codes.d.ts +2 -0
- package/dist/test/test.helper.d.ts +1 -0
- package/dist/test/test.helper.js +9 -2
- package/dist/test/test.helper.js.map +1 -1
- package/docs/REQUEST-LIFECYCLE.md +3 -2
- package/docs/error-codes.md +14 -0
- package/docs/offline-sync-protocol.md +17 -8
- package/migration-guides/12.0.x-to-12.1.x.md +0 -1
- package/migration-guides/12.2.x-to-12.3.x.md +440 -0
- package/package.json +20 -20
- package/src/config.env.ts +6 -2
- package/src/core/common/helpers/graphql-ws-connection.helper.ts +32 -4
- package/src/core/common/interfaces/server-options.interface.ts +80 -8
- package/src/core/common/persistence/sync-model.ts +1 -1
- package/src/core/modules/ai/INTEGRATION-CHECKLIST.md +36 -15
- package/src/core/modules/ai/README.md +163 -25
- package/src/core/modules/ai/core-ai-mcp.controller.ts +48 -1
- package/src/core/modules/ai/core-ai.constants.ts +6 -0
- package/src/core/modules/ai/core-ai.controller.ts +36 -1
- package/src/core/modules/ai/core-ai.module.ts +12 -0
- package/src/core/modules/ai/core-ai.resolver.ts +36 -0
- package/src/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.ts +35 -4
- package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +68 -1
- package/src/core/modules/ai/helpers/ai-tool-authorization.helper.ts +99 -0
- package/src/core/modules/ai/index.ts +18 -0
- package/src/core/modules/ai/inputs/core-ai-settings.input.ts +24 -0
- package/src/core/modules/ai/interfaces/ai-hook.interface.ts +1 -1
- package/src/core/modules/ai/interfaces/ai-tool.interface.ts +38 -26
- package/src/core/modules/ai/models/core-ai-mode.model.ts +1 -1
- package/src/core/modules/ai/models/core-ai-settings.model.ts +23 -0
- package/src/core/modules/ai/models/core-ai-tool-policy.model.ts +1 -1
- package/src/core/modules/ai/models/core-ai-user-setting.model.ts +46 -0
- package/src/core/modules/ai/services/core-ai-conversation.service.ts +31 -6
- package/src/core/modules/ai/services/core-ai-mcp-oauth.service.ts +12 -9
- package/src/core/modules/ai/services/core-ai-mcp.service.ts +206 -13
- package/src/core/modules/ai/services/core-ai-prompt-builder.service.ts +13 -6
- package/src/core/modules/ai/services/core-ai-user-setting.service.ts +85 -0
- package/src/core/modules/ai/services/core-ai.service.ts +134 -35
- package/src/core/modules/api-token/README.md +4 -2
- package/src/core/modules/better-auth/CUSTOMIZATION.md +1 -1
- package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +7 -0
- package/src/core/modules/better-auth/README.md +32 -1
- package/src/core/modules/better-auth/better-auth.config.ts +12 -1
- package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +5 -2
- package/src/core/modules/better-auth/core-better-auth-path-guard.middleware.ts +85 -0
- package/src/core/modules/better-auth/core-better-auth-rate-limit.middleware.ts +5 -2
- package/src/core/modules/better-auth/core-better-auth-rate-limiter.service.ts +14 -7
- package/src/core/modules/better-auth/core-better-auth.module.ts +12 -2
- package/src/core/modules/better-auth/index.ts +1 -0
- package/src/core/modules/error-code/error-codes.ts +18 -0
- package/src/core/modules/sync/README.md +11 -1
- package/src/core/modules/sync/core-sync.service.ts +48 -3
- package/src/core/modules/tenant/INTEGRATION-CHECKLIST.md +12 -1
- package/src/core/modules/tenant/README.md +49 -3
- package/src/core/modules/tenant/core-tenant.guard.ts +18 -2
- package/src/core/modules/tenant/core-tenant.service.ts +287 -2
- package/src/test/test.helper.ts +17 -3
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# Migration Guide: 12.2.x → 12.3.x
|
|
2
|
+
|
|
3
|
+
## 12.3.0 contains breaking changes
|
|
4
|
+
|
|
5
|
+
The version number moves only the MINOR digit (the MAJOR follows NestJS). Read §1–§3, §7 and §8 before you update: none
|
|
6
|
+
of them needs code in a typical project, but each one changes what a running application does.
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
| Category | Details |
|
|
11
|
+
|---|---|
|
|
12
|
+
| **Breaking Changes** | E-mail verification no longer signs the user in (§1). A prompt continues only the caller's own AI conversation, `CoreAiConversationService.appendMessage()` takes the owner and the protected `CoreAiService.persistTurn()` the run as a third parameter (§2). Non-canonical paths under the Better-Auth base path answer 400, and the IAM rate limit tells routes apart, with half the limit on sign-in and sign-up (§3). `emailAndPassword.disableSignUp: true` closes social sign-up too (§7). `AiTool.authorize()` runs on every path, and MCP asks before a destructive tool or refuses a client that cannot ask (§8) |
|
|
13
|
+
| **New Features** | `socialProviders.<name>.disableSignUp` opens or closes sign-up for one provider (§7). `ai.mcp.confirmation` and a per-user switch (`GET/PUT /ai/settings`), `context.authorization` for tools, `destructiveHint` annotations in `tools/list` (§8). `ai.nativeToolCatalog`, a warning for a crowded context window (§9). `ai.mcp.stateless`, consent page `texts` / `styles` (§10). `CoreTenantService.purgeTenantData()` and `deleteAllMembers()` remove a tenant's data (§11) |
|
|
14
|
+
| **Bugfixes** | Login CSRF through a forwarded verification link (§1), turns written into another user's AI conversation (§2), rate limiter bypass through dot segments and an IAM rate limit that never told routes apart over HTTP (§3), a planned end of the SSE hint stream logged as a lost session (§4), a sync push that applied a change chained to a refused one in the same request on another writer's version (§5), an open redirect through `/authorize` of the MCP OAuth endpoints (§6), new accounts through social login while sign-up was switched off (§7), tool checks and admin tool policies that MCP and auto mode skipped (§8), tool descriptions sent twice to native connections and native tool definitions missing from the context budget (§9), cached memberships that kept a deleted tenant reachable for up to `cacheTtlMs` after `invalidateTenant()` (§11) |
|
|
15
|
+
| **Dependencies** | `@nestjs/graphql` 14.1.0 → 14.1.2, `@nestjs/apollo` 14.1.1 → 14.1.2, `@nestjs/swagger` 12.0.5 → 12.1.0, `@tus/server` 2.4.6 → 2.4.7, `@tus/file-store` 2.1.1 → 2.1.2, `jose` 6.2.13 → 6.2.15, `nodemailer` 10.3.0 → 10.3.1. Tested optional peers raised: `bullmq` 6.5.1 → 6.5.4, `@aws-sdk/client-s3`, `@aws-sdk/lib-storage`, `@aws-sdk/s3-request-presigner` 3.1159.0 → 3.1161.0, `@tus/s3-store` 2.0.9 → 2.1.0, `kysely` 0.29.7 → 0.29.8 — `pnpm run update` raises them where a project pins them. Development: `vitest` 5.0.5 → 5.1.0 together with `vite` 8.3.4 → 8.3.5 (Dependencies) |
|
|
16
|
+
| **Migration Effort** | Usually none. A frontend whose verify page expects a session: one redirect. A project calling `appendMessage()` or overriding `persistTurn()`: one argument. A deployment with a tight `betterAuth.rateLimit.max`: check the value (§3). A project that closed sign-up but wants new users through a social provider: one flag on that provider (§7). A project using destructive AI tools over MCP: check the clients, offer users the switch (§8) |
|
|
17
|
+
|
|
18
|
+
## Quick Migration
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @lenne.tech/nest-server@12.3.0
|
|
22
|
+
pnpm run update # nest-server-starter based projects: aligns the pins the framework raised
|
|
23
|
+
pnpm run build
|
|
24
|
+
pnpm test
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Vendor mode**: run `/lt-dev:backend:update-nest-server-core` as usual.
|
|
28
|
+
|
|
29
|
+
### Dependencies
|
|
30
|
+
|
|
31
|
+
`pnpm run update` raises the pins a project shares with the framework: `@nestjs/graphql`, `@nestjs/apollo` and
|
|
32
|
+
`@nestjs/swagger` (patch and minor releases of NestJS, no API change), the optional peers it tests against, and the
|
|
33
|
+
development tooling. Nothing else in a project changes.
|
|
34
|
+
|
|
35
|
+
**`vite` may leave 8.3.4 now.** The 12.0.x guide (§12) asked to keep `vite` at 8.3.4 while `vitest` was 5.0.5: that
|
|
36
|
+
combination crashed a run with a `globalSetup` or an own reporter after every test was green (`code.charCodeAt is not a
|
|
37
|
+
function`, vitest-dev/vitest#11682). `vitest` 5.1.0 carries the fix. Raise both together — `vite` 8.3.5 with `vitest`
|
|
38
|
+
below 5.1.0 still crashes.
|
|
39
|
+
|
|
40
|
+
## 1. Auto sign-in after e-mail verification is off by default
|
|
41
|
+
|
|
42
|
+
`betterAuth.emailVerification.autoSignInAfterVerification` defaulted to `true`; it is now `false`, which is
|
|
43
|
+
Better-Auth's own default.
|
|
44
|
+
|
|
45
|
+
With the option on, `GET /iam/verify-email` creates a session for the account the link belongs to in whatever browser
|
|
46
|
+
opens the link. A verification link travels by mail, so it can be forwarded: someone who signs up with their own
|
|
47
|
+
address and sends the unopened link to someone else has that person's browser signed in to the sender's account, and
|
|
48
|
+
what the recipient enters there (payment data, documents, messages) lands with the sender (login CSRF).
|
|
49
|
+
|
|
50
|
+
**What changes:** after clicking the link the user is verified but not signed in, and signs in once.
|
|
51
|
+
`CoreBetterAuthEmailVerificationService.shouldAutoSignIn()` answers `false` accordingly.
|
|
52
|
+
|
|
53
|
+
**What to check:** a frontend whose verify page assumes a session afterwards should send the user to the sign-in, for
|
|
54
|
+
example with a "verified, please sign in" message. The nuxt-base-starter's verify page already does ("Jetzt
|
|
55
|
+
anmelden").
|
|
56
|
+
|
|
57
|
+
**To keep the previous behaviour** — only where a verification link reaches nobody but its owner:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// config.env.ts
|
|
61
|
+
betterAuth: {
|
|
62
|
+
emailVerification: {
|
|
63
|
+
autoSignInAfterVerification: true,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Only the boolean `true` switches it on (`NSC__*` variables turn `true` into a boolean). Any other value — the string
|
|
69
|
+
`"true"` in `NEST_SERVER_CONFIG`, `1`, `yes` — is read as off, in what Better-Auth receives and in what
|
|
70
|
+
`shouldAutoSignIn()` answers alike.
|
|
71
|
+
|
|
72
|
+
## 2. A prompt continues only the caller's own AI conversation
|
|
73
|
+
|
|
74
|
+
`POST /ai/prompt`, `POST /ai/stream` and the GraphQL `aiPrompt` accepted any `conversationId` and wrote the new turns
|
|
75
|
+
into that conversation, also when it belonged to another user. Now a `conversationId` the caller did not create —
|
|
76
|
+
another user's, or one that does not exist — counts as no conversation: the run reads no history, writes nothing, uses
|
|
77
|
+
no conversation-scoped tool grants, and its response carries no `conversationId`. **Administrators are no exception**:
|
|
78
|
+
they still read every conversation (`getAiConversation`, `findAiConversations` with `all`), but continue only their
|
|
79
|
+
own.
|
|
80
|
+
|
|
81
|
+
`CoreAiConversationService.appendMessage()` checks the owner itself and has a third parameter:
|
|
82
|
+
|
|
83
|
+
**Before:**
|
|
84
|
+
```typescript
|
|
85
|
+
await this.conversationService.appendMessage(conversationId, { content, role: 'user' });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**After:**
|
|
89
|
+
```typescript
|
|
90
|
+
// The id of the user the turn belongs to; anyone else's turn is not written.
|
|
91
|
+
await this.conversationService.appendMessage(conversationId, { content, role: 'user' }, currentUser.id);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A call without it fails to compile (TS2554). A subclass that overrides `appendMessage()` and does not call `super`
|
|
95
|
+
must check the owner itself — `isOwnedBy(id, userId)` is the new public helper for it.
|
|
96
|
+
|
|
97
|
+
The protected `CoreAiService.persistTurn()`, which calls `appendMessage()`, takes the run as a third parameter for the
|
|
98
|
+
same reason — the owner comes from `run.currentUser`:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Before: protected async persistTurn(input, response)
|
|
102
|
+
protected override async persistTurn(input: CoreAiPromptInput, response: CoreAiResponse, run: AiRunContext) {
|
|
103
|
+
// … your own steps …
|
|
104
|
+
await super.persistTurn(input, response, run);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
An override that still calls `super.persistTurn(input, response)` fails to compile (TS2554).
|
|
109
|
+
|
|
110
|
+
## 3. Requests under the Better-Auth base path
|
|
111
|
+
|
|
112
|
+
### Non-canonical paths answer 400
|
|
113
|
+
|
|
114
|
+
Every check under the base path (`/iam` by default) — rate limiter, controller routes, the forwarding to Better-Auth —
|
|
115
|
+
read the path as sent, while Better-Auth routes the path after `new URL()` resolved it. `/iam/callback/../sign-in/email`
|
|
116
|
+
therefore passed the rate limiter as a skipped OAuth callback and ran as a sign-in.
|
|
117
|
+
|
|
118
|
+
`CoreBetterAuthPathGuardMiddleware` now runs first under the base path and answers **400** (`#LTNS_0305: Invalid
|
|
119
|
+
request path`) for a path with a `.` or `..` segment (also `%2e`-encoded), a backslash, an encoded `/` or `\`, or a
|
|
120
|
+
`#`. No Better-Auth route needs any of them; a client that sends one has a bug. It is registered by
|
|
121
|
+
`CoreBetterAuthModule` itself — nothing to add.
|
|
122
|
+
|
|
123
|
+
### The IAM rate limit tells routes apart
|
|
124
|
+
|
|
125
|
+
The rate-limit middleware read `req.path`, which is `/` for a middleware Nest mounts under the base path. So every IAM
|
|
126
|
+
request of a client shared one counter with the full limit, the skip list (`/session`, `/callback`) and the halved
|
|
127
|
+
limit for sign-in, sign-up and the password routes never applied over HTTP.
|
|
128
|
+
|
|
129
|
+
Now a client IP has one counter per route NAME — the last path segment — so `/sign-in/email` and `/sign-up/email` (both
|
|
130
|
+
`email`) share one counter. `/session` and `/callback` are not counted, and the strict routes (sign-in, sign-up, the
|
|
131
|
+
password reset and change routes) get half of `betterAuth.rateLimit.max`, rounded up. The skip and strict lists match
|
|
132
|
+
whole path segments, case-insensitively, as Express routes.
|
|
133
|
+
|
|
134
|
+
**What to check:** a deployment that set `betterAuth.rateLimit.max` (the nest-server-starter reads it from
|
|
135
|
+
`IAM_RATE_LIMIT_MAX`) gets half of it for the e-mail sign-in and sign-up now, and the two count together — with
|
|
136
|
+
`max: 10`, five attempts per window for both combined. Behind a shared NAT or proxy, sign-ups use up the same budget as
|
|
137
|
+
sign-ins. Raise the value if that is too tight. A single-page app that polls `/iam/session` no longer uses up the
|
|
138
|
+
limit.
|
|
139
|
+
|
|
140
|
+
## 4. SSE hint stream: a planned end logs on debug
|
|
141
|
+
|
|
142
|
+
`GET /sync/hints` ends its stream on every re-check (`graphQl.subscriptionRevalidationMs`) and the client reconnects.
|
|
143
|
+
Every such planned end logged `Closing GraphQL WebSocket of user …: Session no longer valid` at log level. It now logs
|
|
144
|
+
`Closing SSE hint stream of user …` on debug; an end an invalidation causes (role change, sign-out) stays at log level.
|
|
145
|
+
|
|
146
|
+
`registerWsConnection()` takes two optional fields for a long-lived channel of a project's own: `channel` (the name in
|
|
147
|
+
the log line, default `GraphQL WebSocket`) and `endsOnRecheck` (every re-check ends the connection). GraphQL WebSockets
|
|
148
|
+
log as before.
|
|
149
|
+
|
|
150
|
+
## 5. Sync push: a change chained to a refused one is held back
|
|
151
|
+
|
|
152
|
+
A client sends the changes of one record with chained base versions, also in one push (protocol §6.2). The server
|
|
153
|
+
applied every item on its own: when the first item conflicted with another writer's version 2, the second — base
|
|
154
|
+
`2` — matched that version and was stored on data the client never saw, while the client dropped it locally.
|
|
155
|
+
|
|
156
|
+
Once an item of a record is not stored (`conflict`, `rejected`, `not_found`, `retry`), the later items of that record
|
|
157
|
+
in the same push are now held back: answered `retry` behind a `retry`, otherwise `conflict` with the current record
|
|
158
|
+
(`not_found` when the caller may not see one). Items of other records are unaffected.
|
|
159
|
+
|
|
160
|
+
The server holds back within one request only. Protocol §11 now says what a client does across requests: it sends a
|
|
161
|
+
later change of a record only in the same request as the change before it, or after that one came back `applied` or
|
|
162
|
+
`merged`, and drops or rebases the later changes of a record whose change was `rejected` or `not_found`. **Check your
|
|
163
|
+
client** if it splits its queue into several requests (the protocol caps a push at
|
|
164
|
+
`sync.push.maxItems`, 100 by default): after a `retry` it must keep the record's later changes back until the next
|
|
165
|
+
sync. A client that sent a single change per record and request to avoid the defect may send chains in one request
|
|
166
|
+
again; across requests it still waits for each answer.
|
|
167
|
+
|
|
168
|
+
## 6. MCP OAuth: no error redirect to a self-registered URI
|
|
169
|
+
|
|
170
|
+
Only with `ai.mcp.oauth: true`. Dynamic client registration accepts any redirect URI, and the MCP SDK sent an error
|
|
171
|
+
it found after the client check (a missing `code_challenge`, a wrong `response_type`) to that URI with a 302. A client
|
|
172
|
+
registered with a foreign URI turned one crafted `/authorize` link into a redirect from the API's own domain to any
|
|
173
|
+
site. Such an error is now answered with `400` and `{ error, error_description }`. Registration stays open, so
|
|
174
|
+
claude.ai, ChatGPT, Cursor and Claude Code connect as before; the user's Allow and Deny on the consent page still
|
|
175
|
+
redirect.
|
|
176
|
+
|
|
177
|
+
The built-in Deny now needs the consent form's CSRF token, as Allow always did; a Deny without it shows the form again.
|
|
178
|
+
A consent UI of your own (`authorizeConsent()` override) should check its token on Deny too — see the AI module's
|
|
179
|
+
INTEGRATION-CHECKLIST.
|
|
180
|
+
|
|
181
|
+
## 7. Social login creates no account while sign-up is switched off
|
|
182
|
+
|
|
183
|
+
Only with `betterAuth.emailAndPassword.disableSignUp: true` and at least one social provider. The flag closed
|
|
184
|
+
`/sign-up/email` only. Better-Auth decides a social sign-up per provider, and each provider got only its `clientId` and
|
|
185
|
+
`clientSecret`, so whoever signed in with Google, GitHub etc. for the first time got an account in a project that had
|
|
186
|
+
closed self-registration (accounts only through an admin or an invitation).
|
|
187
|
+
|
|
188
|
+
**What changes:** every configured provider now receives the flag. A first sign-in through a provider by somebody who
|
|
189
|
+
has no account is refused (the OAuth callback redirects with `error=signup_disabled`, `/sign-in/social` with an
|
|
190
|
+
`idToken` answers 401 `signup disabled`). Users who already have an account keep signing in, also through a provider
|
|
191
|
+
they have not used before: Better-Auth checks the flag only when no user exists for the address.
|
|
192
|
+
|
|
193
|
+
**What to check:** a project that closed sign-up on purpose needs nothing. A project that closed e-mail sign-up but
|
|
194
|
+
wants new users through a provider opens that one deliberately:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// config.env.ts
|
|
198
|
+
betterAuth: {
|
|
199
|
+
emailAndPassword: { disableSignUp: true },
|
|
200
|
+
socialProviders: {
|
|
201
|
+
google: { clientId: '...', clientSecret: '...', disableSignUp: false }, // new users through Google
|
|
202
|
+
github: { clientId: '...', clientSecret: '...' }, // follows disableSignUp: closed
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`disableSignUp: true` on a provider closes sign-up for that provider alone, while sign-up stays open elsewhere. Only a
|
|
208
|
+
boolean counts there: any other value, such as the string `"false"` in `NEST_SERVER_CONFIG`, follows
|
|
209
|
+
`emailAndPassword.disableSignUp`. `GET /iam/features` keeps reporting `signUpEnabled` from the global flag. Plugins a
|
|
210
|
+
project registers itself through `betterAuth.options.plugins` (Generic OAuth, Magic Link, …) are not covered; set their
|
|
211
|
+
own sign-up options.
|
|
212
|
+
|
|
213
|
+
## 8. AI tools: `authorize()` on every path, confirmation over MCP
|
|
214
|
+
|
|
215
|
+
Only with the `ai` module. Two gates held in the chat's plan mode alone:
|
|
216
|
+
|
|
217
|
+
- **`AiTool.authorize()`** ran in plan mode only. Auto mode (the default) and `/ai/mcp` called `execute()` directly,
|
|
218
|
+
so a data-level check a tool placed there ("may this user change record 42?") did not run for most callers.
|
|
219
|
+
- **`destructive` / `mutating`** were read by the chat only. Over MCP a destructive tool ran on the first call, and an
|
|
220
|
+
admin's `aiToolPolicies` rules (`deny`, `ask`) did not apply at all.
|
|
221
|
+
|
|
222
|
+
**What changes:**
|
|
223
|
+
|
|
224
|
+
- `authorize()` runs before every execution — auto mode, plan mode (for the whole plan, and again per step), MCP — on
|
|
225
|
+
the arguments that will run, after the PreToolUse hooks. A thrown `authorize()`, a missing answer or an `allowed`
|
|
226
|
+
that is not exactly `true` refuses the call. The model receives `TOOL_NOT_AVAILABLE` with the `reason` (keep it
|
|
227
|
+
free of anything the user may not know); a refusal is no longer a learning signal. Return `data` to hand what the
|
|
228
|
+
check loaded to `execute()` as `context.authorization.data`.
|
|
229
|
+
- `tools/call` applies the admin's tool policies (`deny` refuses, `ask` asks), then `authorize()`, then asks the user
|
|
230
|
+
through **MCP elicitation** before a `destructive` tool, and before a `mutating` one when `ai.confirmation.mutating`
|
|
231
|
+
says so (`enforced`, else `default`). Only an explicit yes runs the tool. **A client without the `elicitation`
|
|
232
|
+
capability gets an error result for such a tool.**
|
|
233
|
+
- Each user may switch the question off for themselves, like a "bypass permissions" mode: `PUT /ai/settings` with
|
|
234
|
+
`{ "mcpConfirmation": false }` (GraphQL `updateAiSettings`), read with `GET /ai/settings` (`aiSettings`). The choice
|
|
235
|
+
is stored per user in the new `aiUserSettings` collection / table, created at boot like every AI model.
|
|
236
|
+
`authorize()` and the admin's `deny` / `ask` rules apply whatever the user chose.
|
|
237
|
+
- `tools/list` carries `annotations` (`destructiveHint`, `readOnlyHint: false`) for `destructive` and `mutating`
|
|
238
|
+
tools. A tool with neither flag carries none, so it is never advertised as read-only.
|
|
239
|
+
|
|
240
|
+
**What to check:**
|
|
241
|
+
|
|
242
|
+
1. **Tools with `authorize()`:** it now runs in auto mode and over MCP too. A check written for plan mode only (for
|
|
243
|
+
example one that refuses whatever it does not expect) refuses those callers from now on. A tool without
|
|
244
|
+
`authorize()` is unchanged.
|
|
245
|
+
2. **MCP clients:** whether yours support elicitation. Where a client you need cannot ask, choose the default for the
|
|
246
|
+
deployment:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// config.env.ts
|
|
250
|
+
ai: {
|
|
251
|
+
mcp: {
|
|
252
|
+
confirmation: true, // default: asks, each user may switch it off
|
|
253
|
+
// confirmation: false, // asks nobody, each user may switch it on
|
|
254
|
+
// confirmation: { default: true, enforced: true } // asks everybody, nobody may switch it off (403 #LTNS_0609)
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Only `false` / `default: false` switch the default off and only `enforced: true` enforces; any other value keeps
|
|
260
|
+
asking.
|
|
261
|
+
3. **Your frontend:** offer the switch where users manage their AI settings — `mcpConfirmationEnforced: true` in the
|
|
262
|
+
answer means it is fixed and should be shown disabled.
|
|
263
|
+
|
|
264
|
+
**API changes:** `CoreAiMcpService.mcpCallTool()` takes an optional fourth parameter (`{ confirm }`), its constructor
|
|
265
|
+
two optional services (`CoreAiUserSettingService`, `CoreAiToolPolicyService`). `CoreAiController` and
|
|
266
|
+
`CoreAiResolver` receive `CoreAiUserSettingService` as an injected property, so a project subclass keeps its
|
|
267
|
+
constructor. `CoreModule.forRoot(env, { ai: { userSettingService } })` replaces the new store.
|
|
268
|
+
|
|
269
|
+
## 9. AI: names-only tool catalog for native connections, a context window that counts the tools
|
|
270
|
+
|
|
271
|
+
Only with the `ai` module and a connection that uses native tool calling (`supportsNativeTools: true`, detected
|
|
272
|
+
automatically).
|
|
273
|
+
|
|
274
|
+
**What changes:**
|
|
275
|
+
|
|
276
|
+
- The system prompt's tool catalog lists **tool names only** for such a connection in auto mode. Descriptions and
|
|
277
|
+
parameter schemas reach the model through the native `tools` payload as before. Until now the catalog repeated them,
|
|
278
|
+
so every tool was sent twice. Emulated connections and plan mode keep the full catalog.
|
|
279
|
+
- The native tool definitions count against the context budget, so older turns are summarized or dropped before the
|
|
280
|
+
window overflows rather than after.
|
|
281
|
+
- When the system prompt and the tool definitions take more than half of a connection's input budget, the framework
|
|
282
|
+
logs one warning per connection and process. It names the numbers and says whether the window is the connection's
|
|
283
|
+
own or the `ai.contextWindow` fallback. Until now the only trace was a debug line, and the user's message was cut
|
|
284
|
+
without notice.
|
|
285
|
+
|
|
286
|
+
**What to check:**
|
|
287
|
+
|
|
288
|
+
1. Nothing for most projects. A model that picks tools noticeably worse without the descriptions in the prompt gets
|
|
289
|
+
the old catalog back:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// config.env.ts
|
|
293
|
+
ai: {
|
|
294
|
+
nativeToolCatalog: 'full', // default 'names'
|
|
295
|
+
},
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
2. A project test that guards the prompt size: it measures against the `ai.contextWindow` fallback, so it guards
|
|
299
|
+
connections without a window of their own. Keep it, and name it that way. Raising its limit removes the guard.
|
|
300
|
+
3. `deferToolSummaryChars` never applied to native connections in auto mode and still does not. It applies to emulated
|
|
301
|
+
connections and plan mode.
|
|
302
|
+
|
|
303
|
+
## 10. MCP without sessions, and the consent page's wording and look
|
|
304
|
+
|
|
305
|
+
Both are opt-in; nothing changes without configuration.
|
|
306
|
+
|
|
307
|
+
**`ai.mcp.stateless: true`** serves `/ai/mcp` without sessions.
|
|
308
|
+
- Every POST builds its own server and transport for the authenticated user, and no `mcp-session-id` is issued.
|
|
309
|
+
- A session id a client still sends is ignored. GET and DELETE answer `405` with `Allow: POST`.
|
|
310
|
+
- Any replica answers any request, so `/ai/mcp` no longer needs sticky sessions behind a load balancer.
|
|
311
|
+
- There are no server-initiated requests in this mode, so the server cannot ask the user (§8). A tool that needs
|
|
312
|
+
confirmation is refused unless the user switched the question off (`PUT /ai/settings`) or
|
|
313
|
+
`ai.mcp.confirmation` is `false`.
|
|
314
|
+
- `testHelper.mcpSession()` follows the setting: no session id is expected or sent, and `sessionId` is `''`.
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// config.env.ts
|
|
318
|
+
ai: { mcp: { oauth: true, stateless: true } },
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Consent page wording and CSS.** A subclass of `CoreAiMcpOAuthService` overriding `renderConsentPage(view)` calls
|
|
322
|
+
`renderMcpOAuthConsentPage(view, { texts, styles })`.
|
|
323
|
+
- `texts` replaces single texts per locale. They stay escaped.
|
|
324
|
+
- `styles` is CSS appended inside the page's `<style>` element. A `<` in it is neutralized, so the CSS cannot end that
|
|
325
|
+
element, and the page's CSP blocks external loads.
|
|
326
|
+
- Fill both from code or configuration only.
|
|
327
|
+
- The page's scripts and security headers stay internal and are not exported.
|
|
328
|
+
|
|
329
|
+
## 11. Removing a tenant
|
|
330
|
+
|
|
331
|
+
The core had no way to delete a tenant's data: a project had to find every place itself, and the ones that are not a
|
|
332
|
+
`tenantId` field (API tokens, the AI settings with `scope: 'tenant'`, the AI connections' `tenantIds`) were the easy ones
|
|
333
|
+
to miss. When a project deletes its tenant record, it now calls:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
const report = await this.tenantService.purgeTenantData(tenantId); // { TenantMember: 4, Invoice: 120, ApiToken: 2 }
|
|
337
|
+
await this.tenantService.purgeTenantData(tenantId, { dryRun: true }); // counts only
|
|
338
|
+
await this.tenantService.purgeTenantData(tenantId, { exclude: ['AuditEntry'] });
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
- Every way in goes first: the memberships (`deleteAllMembers()`, new as well), with every member's cached access
|
|
342
|
+
ending at once, and the tenant's API tokens, which need no member.
|
|
343
|
+
- Then every registered model with a `tenantId` path, on MongoDB and SQL, the project's own models included; the core
|
|
344
|
+
models that name the tenant in another field; and the AI connections — one only this tenant could use is deleted,
|
|
345
|
+
from a shared one the tenant is removed.
|
|
346
|
+
- Not deleted: models in `multiTenancy.excludeSchemas` or `exclude`, users, and files. Delete a tenant's files through
|
|
347
|
+
`CoreFileService` first; only the project knows which ones are the tenant's. Users stay also where `User` carries a
|
|
348
|
+
`tenantId`: delete per-tenant users through the user service first, which also ends their Better-Auth sessions.
|
|
349
|
+
- A project reference the core does not know of: override the protected `purgeTenantRecords()` and call `super`.
|
|
350
|
+
- The purge runs as a system operation. Call it only from a route that checked who may delete the tenant.
|
|
351
|
+
|
|
352
|
+
**Also changed:** `CoreTenantGuard.invalidateTenant()` clears the tenant's cached memberships and every cached tenant
|
|
353
|
+
list that contains it, on every replica with Redis. Before, it forgot only the `isTenantActive` answer: where a project
|
|
354
|
+
removed a tenant's memberships without calling `invalidateUser()` (a deleted tenant) and no `isTenantActive` answered
|
|
355
|
+
`false`, the cached memberships kept the members in for up to `cacheTtlMs`. A tenant switched off through
|
|
356
|
+
`isTenantActive` was already refused at once. Nothing to change in a project.
|
|
357
|
+
|
|
358
|
+
## Compatibility Notes
|
|
359
|
+
|
|
360
|
+
| Pattern | Status |
|
|
361
|
+
|---|---|
|
|
362
|
+
| Frontend verify page that leads to the sign-in | Unchanged |
|
|
363
|
+
| Frontend verify page that expects a session after the link | Redirect to the sign-in, or set `autoSignInAfterVerification: true` (§1) |
|
|
364
|
+
| Project calling `appendMessage()` | Pass the owner id (§2) |
|
|
365
|
+
| Project subclass of `CoreAiService` overriding `prompt()` | Call `super.prompt()`, or the conversation is not scoped (§2) |
|
|
366
|
+
| Project subclass of `CoreAiService` overriding `persistTurn()` | Take `run` as third parameter and pass it to `super` (§2) |
|
|
367
|
+
| Project controller extending `CoreBetterAuthController` | Unchanged; the path guard runs before it |
|
|
368
|
+
| Custom `betterAuth.rateLimit.skipEndpoints` / `strictEndpoints` | Matched by whole segments now: `/session` no longer covers `/sessions-export` |
|
|
369
|
+
| Sync client pushing chained changes of one record in one request | Unchanged; behind a refused item the rest of the chain is answered `conflict` (or `retry`) instead of being applied (§5) |
|
|
370
|
+
| MCP client sending a malformed `/authorize` request | Gets a `400` page in the browser instead of an error redirect (§6) |
|
|
371
|
+
| Own MCP OAuth consent UI (`authorizeConsent()` override) | Unchanged; check the CSRF token on Deny too (§6) |
|
|
372
|
+
| Sync client splitting a record's changes across requests | Keep a later change back until the one before it was stored, drop it after `rejected` / `not_found` (§5, protocol §11) |
|
|
373
|
+
| `disableSignUp: true` without social providers | Unchanged |
|
|
374
|
+
| `disableSignUp: true` with social providers, new users only through admins or invitations | Unchanged in intent; social login no longer creates accounts (§7) |
|
|
375
|
+
| `disableSignUp: true` with a provider meant for self-registration | Set `disableSignUp: false` on that provider (§7) |
|
|
376
|
+
| AI tool without `authorize()` | Unchanged |
|
|
377
|
+
| AI tool whose `authorize()` was written for plan mode | Runs in auto mode and over MCP too (§8) |
|
|
378
|
+
| Destructive AI tool used through an MCP client with elicitation | The user is asked first (§8) |
|
|
379
|
+
| Destructive AI tool used through an MCP client without elicitation | Refused until the user switches confirmation off, or `ai.mcp.confirmation` is `false` (§8) |
|
|
380
|
+
| Project subclass of `CoreAiController` / `CoreAiResolver` | Unchanged; the settings service is property-injected (§8) |
|
|
381
|
+
| Native AI connection | Catalog lists names only; `ai.nativeToolCatalog: 'full'` restores it (§9) |
|
|
382
|
+
| `/ai/mcp` behind a load balancer without sticky sessions | Set `ai.mcp.stateless: true` (§10) |
|
|
383
|
+
| Destructive AI tool over MCP with `ai.mcp.stateless` | Refused unless the user or `ai.mcp.confirmation` switched the question off (§10) |
|
|
384
|
+
| Override of `fitMessagesToContext()` / `compactMessages()` | Unchanged; both take an optional third parameter, the tokens reserved for native tool definitions (§9) |
|
|
385
|
+
| Project deleting tenants with its own clean-up | Replace it with `purgeTenantData()`, or keep it and add what it misses (§11) |
|
|
386
|
+
| Project subclass of `CoreTenantService` | Unchanged; the MongoDB connection the purge reads is property-injected (§11) |
|
|
387
|
+
|
|
388
|
+
## Troubleshooting
|
|
389
|
+
|
|
390
|
+
| Symptom | Cause | Fix |
|
|
391
|
+
|---|---|---|
|
|
392
|
+
| User not signed in after clicking the verification link | §1 | Expected; lead to the sign-in, or opt in |
|
|
393
|
+
| TS2554 on `appendMessage()` or `super.persistTurn()` | §2 | Pass the owner id, or the run, as third argument |
|
|
394
|
+
| A continued AI conversation answers without history, no `conversationId` in the response | The conversation belongs to another user (also for admins) | Continue only conversations the caller created |
|
|
395
|
+
| 400 `#LTNS_0305` on an IAM request | The path carries a dot segment, a backslash, an encoded separator or a `#` | Fix the client's URL |
|
|
396
|
+
| 429 on sign-in earlier than before | The strict limit (half of `max`) applies now, shared by e-mail sign-in and sign-up | Raise `betterAuth.rateLimit.max` |
|
|
397
|
+
| New users land on the error page with `error=signup_disabled` after a social login | §7: `disableSignUp: true` covers social providers | Expected; or set `disableSignUp: false` on the provider |
|
|
398
|
+
| MCP answers "needs the user's confirmation, and this MCP client cannot ask for it" | §8: destructive tool, client without elicitation | Use a client with elicitation, or let the user switch it off (`PUT /ai/settings`) |
|
|
399
|
+
| `TOOL_NOT_AVAILABLE` in auto mode for a tool that worked before | §8: its `authorize()` refuses | Fix the check, or return `{ allowed: true }` where it should pass |
|
|
400
|
+
| 403 `#LTNS_0609` on `PUT /ai/settings` | §8: `ai.mcp.confirmation.enforced` | Expected |
|
|
401
|
+
| Warning "the system prompt and tool definitions take ~N of M input tokens" | §9: the window is crowded | Set the connection's `contextWindow`, or shrink the catalog |
|
|
402
|
+
|
|
403
|
+
## Module Documentation
|
|
404
|
+
|
|
405
|
+
### Better-Auth
|
|
406
|
+
|
|
407
|
+
- **README:** [src/core/modules/better-auth/README.md](../src/core/modules/better-auth/README.md) — "Email Verification", "Rate Limiting", "Disable Sign-Up"
|
|
408
|
+
- **Integration Checklist:** [src/core/modules/better-auth/INTEGRATION-CHECKLIST.md](../src/core/modules/better-auth/INTEGRATION-CHECKLIST.md)
|
|
409
|
+
- **Key Files:** `better-auth.config.ts` (the verification options Better-Auth receives, `buildSocialProviders()`),
|
|
410
|
+
`core-better-auth-path-guard.middleware.ts`, `core-better-auth-rate-limiter.service.ts`
|
|
411
|
+
|
|
412
|
+
### AI
|
|
413
|
+
|
|
414
|
+
- **README:** [src/core/modules/ai/README.md](../src/core/modules/ai/README.md)
|
|
415
|
+
- **Key Files:** `services/core-ai.service.ts` (`scopeConversation()`, `executeToolCall()`), `services/core-ai-conversation.service.ts`
|
|
416
|
+
(`isOwnedBy()`, `appendMessage()`), `helpers/ai-tool-authorization.helper.ts`, `services/core-ai-mcp.service.ts`
|
|
417
|
+
(`mcpCallTool()`), `services/core-ai-user-setting.service.ts`
|
|
418
|
+
- **README sections:** "Plan mode", "Authorization and confirmation over MCP"
|
|
419
|
+
|
|
420
|
+
### AI (MCP OAuth)
|
|
421
|
+
|
|
422
|
+
- **README:** [src/core/modules/ai/README.md](../src/core/modules/ai/README.md) — "OAuth 2.1", consent page
|
|
423
|
+
- **Integration Checklist:** [src/core/modules/ai/INTEGRATION-CHECKLIST.md](../src/core/modules/ai/INTEGRATION-CHECKLIST.md) — own consent UI
|
|
424
|
+
- **Key Files:** `helpers/ai-mcp-oauth.helper.ts` (`mountAiMcpOAuthRouter()`), `services/core-ai-mcp-oauth.service.ts` (`authorizeConsent()`)
|
|
425
|
+
|
|
426
|
+
### Multi-Tenancy
|
|
427
|
+
|
|
428
|
+
- **README:** [src/core/modules/tenant/README.md](../src/core/modules/tenant/README.md) — "Removing a Tenant"
|
|
429
|
+
- **Key Files:** `core-tenant.service.ts` (`purgeTenantData()`, `purgeTenantRecords()`, `deleteAllMembers()`),
|
|
430
|
+
`core-tenant.guard.ts` (`clearTenant()`)
|
|
431
|
+
|
|
432
|
+
### Sync
|
|
433
|
+
|
|
434
|
+
- **README:** [src/core/modules/sync/README.md](../src/core/modules/sync/README.md) — hint stream log lines, held-back push items
|
|
435
|
+
- **Protocol:** [docs/offline-sync-protocol.md](../docs/offline-sync-protocol.md) §6.2, §6.3
|
|
436
|
+
|
|
437
|
+
## References
|
|
438
|
+
|
|
439
|
+
- nest-server-starter: no code change; it keeps Better-Auth's defaults, configures neither `disableSignUp` nor a social
|
|
440
|
+
provider, does not call `appendMessage()`, and configures no `ai` module
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Server framework in TypeScript on top of NestJS: GraphQL and REST APIs on MongoDB, PostgreSQL, MySQL or MariaDB, with authentication, field-level permissions, multi-tenancy, offline sync and an AI assistant.",
|
|
6
6
|
"keywords": [
|
|
@@ -104,18 +104,18 @@
|
|
|
104
104
|
"@as-integrations/express5": "1.1.2",
|
|
105
105
|
"@getbrevo/brevo": "6.2.0",
|
|
106
106
|
"@modelcontextprotocol/sdk": "1.32.2",
|
|
107
|
-
"@nestjs/apollo": "14.1.
|
|
107
|
+
"@nestjs/apollo": "14.1.2",
|
|
108
108
|
"@nestjs/common": "12.3.2",
|
|
109
109
|
"@nestjs/core": "12.3.2",
|
|
110
|
-
"@nestjs/graphql": "14.1.
|
|
110
|
+
"@nestjs/graphql": "14.1.2",
|
|
111
111
|
"@nestjs/jwt": "12.0.3",
|
|
112
112
|
"@nestjs/passport": "12.0.0",
|
|
113
113
|
"@nestjs/platform-express": "12.3.2",
|
|
114
114
|
"@nestjs/schedule": "12.0.2",
|
|
115
|
-
"@nestjs/swagger": "12.0
|
|
115
|
+
"@nestjs/swagger": "12.1.0",
|
|
116
116
|
"@nestjs/terminus": "12.1.0",
|
|
117
|
-
"@tus/file-store": "2.1.
|
|
118
|
-
"@tus/server": "2.4.
|
|
117
|
+
"@tus/file-store": "2.1.2",
|
|
118
|
+
"@tus/server": "2.4.7",
|
|
119
119
|
"@types/supertest": "7.2.1",
|
|
120
120
|
"bcrypt": "6.0.0",
|
|
121
121
|
"class-transformer": "0.5.1",
|
|
@@ -131,13 +131,13 @@
|
|
|
131
131
|
"graphql-subscriptions": "3.0.0",
|
|
132
132
|
"graphql-upload": "17.1.0",
|
|
133
133
|
"graphql-ws": "6.3.0",
|
|
134
|
-
"jose": "6.2.
|
|
134
|
+
"jose": "6.2.15",
|
|
135
135
|
"js-sha256": "1.0.0",
|
|
136
136
|
"json-to-graphql-query": "2.3.0",
|
|
137
137
|
"lodash": "4.18.1",
|
|
138
138
|
"multer": "2.5.0",
|
|
139
139
|
"node-mailjet": "6.0.11",
|
|
140
|
-
"nodemailer": "10.3.
|
|
140
|
+
"nodemailer": "10.3.1",
|
|
141
141
|
"passport": "0.7.0",
|
|
142
142
|
"passport-jwt": "4.0.1",
|
|
143
143
|
"reflect-metadata": "0.2.2",
|
|
@@ -226,9 +226,9 @@
|
|
|
226
226
|
}
|
|
227
227
|
},
|
|
228
228
|
"devDependencies": {
|
|
229
|
-
"@aws-sdk/client-s3": "3.
|
|
230
|
-
"@aws-sdk/lib-storage": "3.
|
|
231
|
-
"@aws-sdk/s3-request-presigner": "3.
|
|
229
|
+
"@aws-sdk/client-s3": "3.1161.0",
|
|
230
|
+
"@aws-sdk/lib-storage": "3.1161.0",
|
|
231
|
+
"@aws-sdk/s3-request-presigner": "3.1161.0",
|
|
232
232
|
"@better-auth/core": "1.7.14",
|
|
233
233
|
"@better-auth/passkey": "1.7.14",
|
|
234
234
|
"@compodoc/compodoc": "2.0.0",
|
|
@@ -237,8 +237,8 @@
|
|
|
237
237
|
"@nestjs/schematics": "12.0.12",
|
|
238
238
|
"@nestjs/testing": "12.3.2",
|
|
239
239
|
"@swc/cli": "0.8.2",
|
|
240
|
-
"@swc/core": "1.16.
|
|
241
|
-
"@tus/s3-store": "2.0
|
|
240
|
+
"@swc/core": "1.16.21",
|
|
241
|
+
"@tus/s3-store": "2.1.0",
|
|
242
242
|
"@types/compression": "1.8.1",
|
|
243
243
|
"@types/cookie-parser": "1.4.10",
|
|
244
244
|
"@types/ejs": "3.1.5",
|
|
@@ -248,32 +248,32 @@
|
|
|
248
248
|
"@types/node": "26.6.6",
|
|
249
249
|
"@types/passport": "1.0.17",
|
|
250
250
|
"@types/pg": "8.23.1",
|
|
251
|
-
"@vitest/coverage-v8": "5.0
|
|
251
|
+
"@vitest/coverage-v8": "5.1.0",
|
|
252
252
|
"better-auth": "1.7.14",
|
|
253
|
-
"bullmq": "6.5.
|
|
253
|
+
"bullmq": "6.5.4",
|
|
254
254
|
"cross-env": "10.1.0",
|
|
255
255
|
"drizzle-orm": "1.0.0-rc.5",
|
|
256
256
|
"ioredis": "6.0.0",
|
|
257
|
-
"kysely": "0.29.
|
|
257
|
+
"kysely": "0.29.8",
|
|
258
258
|
"mongodb": "7.7.0",
|
|
259
259
|
"mongoose": "9.11.4",
|
|
260
260
|
"mysql2": "3.24.6",
|
|
261
261
|
"nodemon": "3.1.14",
|
|
262
|
-
"otpauth": "9.
|
|
262
|
+
"otpauth": "9.6.0",
|
|
263
263
|
"oxfmt": "0.75.0",
|
|
264
264
|
"oxlint": "1.90.0",
|
|
265
265
|
"oxlint-tsgolint": "7.0.2005",
|
|
266
266
|
"pg": "8.23.1",
|
|
267
267
|
"playwright-core": "1.64.1",
|
|
268
268
|
"rimraf": "6.1.3",
|
|
269
|
-
"smtp-server": "3.21.
|
|
269
|
+
"smtp-server": "3.21.3",
|
|
270
270
|
"tsx": "4.23.15",
|
|
271
271
|
"tus-js-client": "4.3.1",
|
|
272
272
|
"typeorm": "1.1.2",
|
|
273
273
|
"typescript": "6.0.3",
|
|
274
274
|
"unplugin-swc": "2.0.0",
|
|
275
|
-
"vite": "8.3.
|
|
276
|
-
"vitest": "5.0
|
|
275
|
+
"vite": "8.3.5",
|
|
276
|
+
"vitest": "5.1.0"
|
|
277
277
|
},
|
|
278
278
|
"main": "dist/index.js",
|
|
279
279
|
"types": "dist/index.d.ts",
|
package/src/config.env.ts
CHANGED
|
@@ -59,7 +59,9 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
59
59
|
rpId: 'localhost',
|
|
60
60
|
rpName: 'Nest Server Local',
|
|
61
61
|
},
|
|
62
|
-
|
|
62
|
+
// 200: sign-in and sign-up get the strict half (100), the headroom the suites had while the limiter
|
|
63
|
+
// counted every IAM request of a client on one shared counter of 100
|
|
64
|
+
rateLimit: { enabled: true, max: 200, windowSeconds: 60 },
|
|
63
65
|
secret: process.env.BETTER_AUTH_SECRET || 'BETTER_AUTH_SECRET_LOCAL_32_CHARS_M',
|
|
64
66
|
// Social providers disabled in local environment (no credentials)
|
|
65
67
|
socialProviders: {
|
|
@@ -367,7 +369,9 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
367
369
|
rpId: 'localhost',
|
|
368
370
|
rpName: 'Nest Server Local',
|
|
369
371
|
},
|
|
370
|
-
|
|
372
|
+
// 200: sign-in and sign-up get the strict half (100), the headroom the suites had while the limiter
|
|
373
|
+
// counted every IAM request of a client on one shared counter of 100
|
|
374
|
+
rateLimit: { enabled: true, max: 200, windowSeconds: 60 },
|
|
371
375
|
secret: process.env.BETTER_AUTH_SECRET || 'BETTER_AUTH_SECRET_LOCAL_32_CHARS_M',
|
|
372
376
|
// Social providers disabled in local environment (no credentials)
|
|
373
377
|
socialProviders: {
|