@lenne.tech/nest-server 12.3.0 → 12.3.2

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.
Files changed (41) hide show
  1. package/.claude/rules/configurable-features.md +2 -1
  2. package/.claude/rules/package-management.md +5 -1
  3. package/.claude/rules/testing.md +3 -3
  4. package/FRAMEWORK-API.md +2 -2
  5. package/dist/core/common/interceptors/check-security.interceptor.js +19 -2
  6. package/dist/core/common/interceptors/check-security.interceptor.js.map +1 -1
  7. package/dist/core/common/interfaces/server-options.interface.d.ts +1 -0
  8. package/dist/core/common/services/email.service.d.ts +3 -0
  9. package/dist/core/common/services/email.service.js +20 -0
  10. package/dist/core/common/services/email.service.js.map +1 -1
  11. package/dist/core/modules/ai/core-ai.constants.d.ts +1 -0
  12. package/dist/core/modules/ai/core-ai.constants.js +1 -0
  13. package/dist/core/modules/ai/core-ai.constants.js.map +1 -1
  14. package/dist/core/modules/ai/core-ai.controller.d.ts +3 -0
  15. package/dist/core/modules/ai/core-ai.controller.js +18 -1
  16. package/dist/core/modules/ai/core-ai.controller.js.map +1 -1
  17. package/dist/core/modules/ai/core-ai.module.js +2 -1
  18. package/dist/core/modules/ai/core-ai.module.js.map +1 -1
  19. package/dist/core/modules/ai/index.d.ts +1 -0
  20. package/dist/core/modules/ai/index.js +1 -0
  21. package/dist/core/modules/ai/index.js.map +1 -1
  22. package/dist/core/modules/ai/models/core-ai-status.model.d.ts +3 -0
  23. package/dist/core/modules/ai/models/core-ai-status.model.js +25 -0
  24. package/dist/core/modules/ai/models/core-ai-status.model.js.map +1 -0
  25. package/dist/core/modules/better-auth/better-auth.config.js +7 -3
  26. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
  27. package/docs/REQUEST-LIFECYCLE.md +3 -3
  28. package/migration-guides/12.3.0-to-12.3.1.md +136 -0
  29. package/migration-guides/12.3.1-to-12.3.2.md +52 -0
  30. package/package.json +7 -7
  31. package/src/core/common/interceptors/check-security.interceptor.ts +26 -2
  32. package/src/core/common/interfaces/server-options.interface.ts +15 -0
  33. package/src/core/common/services/email.service.ts +33 -1
  34. package/src/core/modules/ai/README.md +15 -0
  35. package/src/core/modules/ai/core-ai.constants.ts +4 -0
  36. package/src/core/modules/ai/core-ai.controller.ts +20 -1
  37. package/src/core/modules/ai/core-ai.module.ts +2 -0
  38. package/src/core/modules/ai/index.ts +1 -0
  39. package/src/core/modules/ai/models/core-ai-status.model.ts +16 -0
  40. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +5 -2
  41. package/src/core/modules/better-auth/better-auth.config.ts +9 -4
@@ -1,4 +1,4 @@
1
- import { Body, Controller, Delete, Get, Inject, Param, Post, Put, Query, Res } from '@nestjs/common';
1
+ import { Body, Controller, Delete, Get, Inject, Optional, Param, Post, Put, Query, Res } from '@nestjs/common';
2
2
  import type { Response } from 'express';
3
3
 
4
4
  import { RESTServiceOptions } from '../../common/decorators/rest-service-options.decorator.js';
@@ -6,6 +6,7 @@ import { Roles } from '../../common/decorators/roles.decorator.js';
6
6
  import { RoleEnum } from '../../common/enums/role.enum.js';
7
7
  import type { ServiceOptions } from '../../common/interfaces/service-options.interface.js';
8
8
  import { RequestContext } from '../../common/services/request-context.service.js';
9
+ import { AI_MCP_ENABLED } from './core-ai.constants.js';
9
10
  import { CoreAiBudgetLimitCreateInput } from './inputs/core-ai-budget-limit-create.input.js';
10
11
  import { CoreAiBudgetLimitInput } from './inputs/core-ai-budget-limit.input.js';
11
12
  import { CoreAiConnectionCreateInput } from './inputs/core-ai-connection-create.input.js';
@@ -31,6 +32,7 @@ import { CoreAiPrompt } from './models/core-ai-prompt.model.js';
31
32
  import { CoreAiSlot } from './models/core-ai-slot.model.js';
32
33
  import { CoreAiResponse } from './models/core-ai-response.model.js';
33
34
  import { CoreAiSettings } from './models/core-ai-settings.model.js';
35
+ import { CoreAiStatus } from './models/core-ai-status.model.js';
34
36
  import { CoreAiUsageInfo } from './models/core-ai-usage-info.model.js';
35
37
  import { CoreAiBudgetService } from './services/core-ai-budget.service.js';
36
38
  import { CoreAiConnectionPreferenceService } from './services/core-ai-connection-preference.service.js';
@@ -48,6 +50,7 @@ import { CoreAiService } from './services/core-ai.service.js';
48
50
  /**
49
51
  * REST controller for AI prompts and connection management.
50
52
  *
53
+ * - `GET /ai/status`: everyone, also without a session.
51
54
  * - `POST /ai/prompt`: any authenticated user.
52
55
  * - `/ai/connections*`: admin only.
53
56
  *
@@ -77,6 +80,22 @@ export class CoreAiController {
77
80
  @Inject(CoreAiUserSettingService)
78
81
  protected readonly userSettingService: CoreAiUserSettingService;
79
82
 
83
+ /** Whether the MCP endpoint is registered (`ai.mcp`, 12.3.1+). A property for the same reason as the settings. */
84
+ @Optional()
85
+ @Inject(AI_MCP_ENABLED)
86
+ protected readonly mcpEnabled?: boolean;
87
+
88
+ /**
89
+ * What the AI module offers on this server (12.3.1+). Public on purpose: a frontend asks before anybody signs in,
90
+ * to decide whether it shows the assistant at all. The route exists only while the module is registered, so a
91
+ * `404` is the answer "AI is off"; the body names no user data.
92
+ */
93
+ @Get('status')
94
+ @Roles(RoleEnum.S_EVERYONE)
95
+ async status(): Promise<CoreAiStatus> {
96
+ return { mcp: this.mcpEnabled === true };
97
+ }
98
+
80
99
  /**
81
100
  * Send a prompt to the AI assistant.
82
101
  */
@@ -8,6 +8,7 @@ import { CoreAiMcpOAuthService } from './services/core-ai-mcp-oauth.service.js';
8
8
  import { CoreAiMcpService } from './services/core-ai-mcp.service.js';
9
9
  import { CorePersistenceModule } from '../../common/persistence/core-persistence.module.js';
10
10
  import {
11
+ AI_MCP_ENABLED,
11
12
  AI_MCP_OAUTH_CLIENT_MODEL,
12
13
  AI_MCP_OAUTH_CODE_MODEL,
13
14
  AI_MCP_OAUTH_REFRESH_TOKEN_MODEL,
@@ -274,6 +275,7 @@ export class CoreAiModule {
274
275
  AskUserQuestionAiTool,
275
276
  SearchToolsAiTool,
276
277
  LlmProviderFactory,
278
+ { provide: AI_MCP_ENABLED, useValue: options.mcpEnabled === true },
277
279
  { provide: AI_BUDGET_LIMIT_CLASS, useValue: CoreAiBudgetLimit },
278
280
  { provide: AI_CONNECTION_CLASS, useValue: CoreAiConnection },
279
281
  { provide: AI_CONNECTION_PREFERENCE_CLASS, useValue: CoreAiConnectionPreference },
@@ -57,6 +57,7 @@ export * from './models/core-ai-tool-grant.model.js';
57
57
  export * from './models/core-ai-mode.model.js';
58
58
  export * from './models/core-ai-tool-policy.model.js';
59
59
  export * from './models/core-ai-settings.model.js';
60
+ export * from './models/core-ai-status.model.js';
60
61
  export * from './models/core-ai-user-setting.model.js';
61
62
  export * from './models/core-ai-response.model.js';
62
63
  export * from './models/core-ai-usage-info.model.js';
@@ -0,0 +1,16 @@
1
+ import { Field, ObjectType } from '@nestjs/graphql';
2
+
3
+ import { Restricted } from '../../../common/decorators/restricted.decorator.js';
4
+ import { RoleEnum } from '../../../common/enums/role.enum.js';
5
+
6
+ /**
7
+ * What the AI module offers on this server (12.3.1+). Answer of the public `GET /ai/status`, which exists only while
8
+ * the module is registered — a `404` there is the answer "AI is off". Names no user data.
9
+ */
10
+ @ObjectType({ description: 'What the AI module offers on this server' })
11
+ @Restricted(RoleEnum.S_EVERYONE)
12
+ export class CoreAiStatus {
13
+ /** Whether the MCP server answers at `/ai/mcp` (`ai.mcp`). */
14
+ @Field(() => Boolean, { description: 'Whether the MCP server answers at /ai/mcp' })
15
+ mcp: boolean;
16
+ }
@@ -18,14 +18,17 @@ From `@lenne.tech/nest-server` 11.37.0, better-auth is **not** a dependency of t
18
18
  non-optional **peer dependency**, so your project declares and owns the version:
19
19
 
20
20
  ```bash
21
- pnpm add better-auth@1.7.7 @better-auth/passkey@1.7.7 @better-auth/core@1.7.7
21
+ pnpm add better-auth@1.7.16 @better-auth/passkey@1.7.16 @better-auth/core@1.7.16
22
22
  ```
23
23
 
24
+ 1.7.16 is the floor since nest-server 12.3.1: it closes the better-auth advisories published on 2026-11-03 (social
25
+ sign-in, passkeys, e-mail change links, revoked sessions with a session cache).
26
+
24
27
  **Pin all three to the same exact version** — no `^`, no `~`. They are one release train, and a
25
28
  mixed set fails in ways the type checker does not catch.
26
29
 
27
30
  > **Do not rely on `pnpm install` to remind you.** On pnpm's default `autoInstallPeers: true` the
28
- > packages are installed silently at whatever version satisfies `>=1.7.7 <1.8.0` — your build stays
31
+ > packages are installed silently at whatever version satisfies `>=1.7.16 <1.8.0` — your build stays
29
32
  > green while the version is unpinned and free to drift. Verify with `pnpm why better-auth`.
30
33
 
31
34
  **Why peers rather than dependencies:** in a fullstack project the frontend talks to better-auth
@@ -1065,15 +1065,20 @@ function buildPlugins(
1065
1065
  rpName: normalizedPasskey.rpName,
1066
1066
  };
1067
1067
 
1068
- // Add optional WebAuthn configuration if specified
1068
+ // The plugin reads these three from `authenticatorSelection` only. Until 12.3.1 they were set at the top level and
1069
+ // ignored, so `userVerification: 'required'` enforced nothing; the plugin's own defaults fill what is not set
1070
+ const authenticatorSelection: Record<string, string> = {};
1069
1071
  if (normalizedPasskey.authenticatorAttachment) {
1070
- passkeyOptions.authenticatorAttachment = normalizedPasskey.authenticatorAttachment;
1072
+ authenticatorSelection.authenticatorAttachment = normalizedPasskey.authenticatorAttachment;
1071
1073
  }
1072
1074
  if (normalizedPasskey.residentKey) {
1073
- passkeyOptions.residentKey = normalizedPasskey.residentKey;
1075
+ authenticatorSelection.residentKey = normalizedPasskey.residentKey;
1074
1076
  }
1075
1077
  if (normalizedPasskey.userVerification) {
1076
- passkeyOptions.userVerification = normalizedPasskey.userVerification;
1078
+ authenticatorSelection.userVerification = normalizedPasskey.userVerification;
1079
+ }
1080
+ if (Object.keys(authenticatorSelection).length > 0) {
1081
+ passkeyOptions.authenticatorSelection = authenticatorSelection;
1077
1082
  }
1078
1083
  if (normalizedPasskey.webAuthnChallengeCookie) {
1079
1084
  // The plugin reads the name from `advanced`; a top-level option is ignored