@lenne.tech/nest-server 11.42.7 → 11.42.9
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 +24 -0
- package/.claude/rules/configurable-features.md +5 -3
- package/.claude/rules/testing.md +4 -4
- package/FRAMEWORK-API.md +3 -2
- package/dist/core/common/decorators/api-methods.decorator.d.ts +2 -0
- package/dist/core/common/decorators/api-methods.decorator.js +8 -0
- package/dist/core/common/decorators/api-methods.decorator.js.map +1 -0
- package/dist/core/common/helpers/swagger.helper.js +324 -15
- package/dist/core/common/helpers/swagger.helper.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +1 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js +15 -1
- package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js +8 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.registry.d.ts +7 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.registry.js +12 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.registry.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth.config.js +12 -0
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +2 -2
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-openapi.helper.d.ts +62 -0
- package/dist/core/modules/better-auth/core-better-auth-openapi.helper.js +95 -0
- package/dist/core/modules/better-auth/core-better-auth-openapi.helper.js.map +1 -0
- 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 +10 -2
- package/dist/core/modules/better-auth/core-better-auth.service.js +89 -0
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/core/modules/tus/core-tus.controller.js +3 -0
- package/dist/core/modules/tus/core-tus.controller.js.map +1 -1
- package/dist/core/modules/tus/core-tus.service.d.ts +3 -0
- package/dist/core/modules/tus/core-tus.service.js +58 -25
- package/dist/core/modules/tus/core-tus.service.js.map +1 -1
- package/dist/core/modules/tus/interfaces/tus-config.interface.js +1 -0
- package/dist/core/modules/tus/interfaces/tus-config.interface.js.map +1 -1
- package/dist/core/modules/tus/tus.module.d.ts +2 -0
- package/dist/core/modules/tus/tus.module.js +23 -1
- package/dist/core/modules/tus/tus.module.js.map +1 -1
- package/dist/core.module.js +1 -1
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +9 -1
- package/migration-guides/11.42.7-to-11.42.8.md +74 -0
- package/migration-guides/11.42.8-to-11.42.9.md +186 -0
- package/package.json +2 -2
- package/src/core/common/decorators/api-methods.decorator.ts +25 -0
- package/src/core/common/helpers/swagger.helper.ts +479 -19
- package/src/core/common/interfaces/server-options.interface.ts +46 -8
- package/src/core/common/interfaces/swagger-setup-options.interface.ts +5 -2
- package/src/core/modules/ai/README.md +7 -0
- package/src/core/modules/ai/core-ai-mcp.controller.ts +19 -2
- package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +12 -2
- package/src/core/modules/ai/helpers/ai-mcp-oauth.registry.ts +29 -0
- package/src/core/modules/better-auth/README.md +34 -0
- package/src/core/modules/better-auth/better-auth.config.ts +27 -1
- package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +4 -17
- package/src/core/modules/better-auth/core-better-auth-openapi.helper.ts +219 -0
- package/src/core/modules/better-auth/core-better-auth.controller.ts +1 -0
- package/src/core/modules/better-auth/core-better-auth.service.ts +166 -2
- package/src/core/modules/tus/README.md +50 -14
- package/src/core/modules/tus/core-tus.controller.ts +6 -0
- package/src/core/modules/tus/core-tus.service.ts +97 -26
- package/src/core/modules/tus/interfaces/tus-config.interface.ts +1 -0
- package/src/core/modules/tus/tus.module.ts +43 -1
- package/src/core.module.ts +2 -1
- package/src/index.ts +1 -0
|
@@ -2801,7 +2801,11 @@ export interface IServerOptions {
|
|
|
2801
2801
|
};
|
|
2802
2802
|
|
|
2803
2803
|
/**
|
|
2804
|
-
* Whether health check
|
|
2804
|
+
* Whether the health check endpoints are registered. The block's presence enables them; `false`
|
|
2805
|
+
* leaves them out (GET /health-check and the `healthCheck` query answer 404 / are absent) while
|
|
2806
|
+
* keeping the configured checks for later. Honoured since 11.42.9 — before, any `healthCheck`
|
|
2807
|
+
* object registered the endpoints, `enabled: false` included.
|
|
2808
|
+
* @default true (when `healthCheck` is set)
|
|
2805
2809
|
*/
|
|
2806
2810
|
enabled?: boolean;
|
|
2807
2811
|
};
|
|
@@ -3455,6 +3459,10 @@ export interface IServerOptions {
|
|
|
3455
3459
|
* Follows the "Enabled by Default" pattern - tus is automatically enabled
|
|
3456
3460
|
* without any configuration. Set `tus: false` to explicitly disable.
|
|
3457
3461
|
*
|
|
3462
|
+
* Read by `TusModule.forRoot()` when it gets no `config` of its own; an explicit `config` wins.
|
|
3463
|
+
* Before 11.42.9 this key was never read — `tus: false`, `allowedTypes`, `maxSize` and `roles` here
|
|
3464
|
+
* did nothing.
|
|
3465
|
+
*
|
|
3458
3466
|
* Accepts:
|
|
3459
3467
|
* - `true` or `undefined`: Enable with defaults (enabled by default)
|
|
3460
3468
|
* - `false`: Disable TUS uploads
|
|
@@ -3756,28 +3764,29 @@ export interface ITusConfig {
|
|
|
3756
3764
|
|
|
3757
3765
|
/**
|
|
3758
3766
|
* Checksum extension configuration.
|
|
3759
|
-
*
|
|
3767
|
+
* Has no effect with the built-in stores (file system, S3): neither supports the extension.
|
|
3760
3768
|
* @default true
|
|
3761
3769
|
*/
|
|
3762
3770
|
checksum?: boolean;
|
|
3763
3771
|
|
|
3764
3772
|
/**
|
|
3765
3773
|
* Concatenation extension configuration.
|
|
3766
|
-
*
|
|
3774
|
+
* Has no effect with the built-in stores (file system, S3): neither supports the extension.
|
|
3767
3775
|
* @default true
|
|
3768
3776
|
*/
|
|
3769
3777
|
concatenation?: boolean;
|
|
3770
3778
|
|
|
3771
3779
|
/**
|
|
3772
3780
|
* Creation extension configuration.
|
|
3773
|
-
* Allows creating new uploads via POST.
|
|
3781
|
+
* Allows creating new uploads via POST. `false`: POST answers 501 and OPTIONS no longer advertises it
|
|
3782
|
+
* (enforced since 11.42.9 — `@tus/server` itself ignores the flag).
|
|
3774
3783
|
* @default true
|
|
3775
3784
|
*/
|
|
3776
3785
|
creation?: boolean | ITusCreationConfig;
|
|
3777
3786
|
|
|
3778
3787
|
/**
|
|
3779
3788
|
* Creation With Upload extension configuration.
|
|
3780
|
-
* Allows sending data in the initial POST request.
|
|
3789
|
+
* Allows sending data in the initial POST request. `false`: a POST carrying data answers 501 (11.42.9+).
|
|
3781
3790
|
* @default true
|
|
3782
3791
|
*/
|
|
3783
3792
|
creationWithUpload?: boolean;
|
|
@@ -3802,11 +3811,30 @@ export interface ITusConfig {
|
|
|
3802
3811
|
maxSize?: number;
|
|
3803
3812
|
|
|
3804
3813
|
/**
|
|
3805
|
-
* Base path for tus endpoints
|
|
3814
|
+
* Base path for tus endpoints (11.42.9+):
|
|
3815
|
+
* - a new route (`'/uploads'`): the controller is mounted there — a project controller too, as with
|
|
3816
|
+
* `roles`. Like any controller route it sits BELOW a global prefix and a URI version.
|
|
3817
|
+
* - a path ending with the controller's own route (`'/api/tus'` for `tus`): the URL the route is
|
|
3818
|
+
* publicly reachable under — a global prefix, or a proxy that strips a prefix. The controller stays
|
|
3819
|
+
* and upload URLs use this path. That is how `path` had to be set before 11.42.9, and it still works.
|
|
3820
|
+
* - unset: follows the controller's own route.
|
|
3821
|
+
*
|
|
3822
|
+
* Otherwise the URL handed to a client for a new upload is built from the path the upload was created
|
|
3823
|
+
* at, so it is correct below a global prefix or a URI version without any `path`.
|
|
3806
3824
|
* @default '/tus'
|
|
3807
3825
|
*/
|
|
3808
3826
|
path?: string;
|
|
3809
3827
|
|
|
3828
|
+
/**
|
|
3829
|
+
* Hand out upload URLs (`Location`) without scheme and host — `/tus/<id>` instead of
|
|
3830
|
+
* `https://api.example.com/tus/<id>`. For a frontend that reaches TUS through a same-origin proxy:
|
|
3831
|
+
* an absolute URL names the origin the API saw, which the browser cannot reach. A project passing
|
|
3832
|
+
* @tus/server's own `relativeLocation` is honoured too.
|
|
3833
|
+
* @default false
|
|
3834
|
+
* @since 11.42.9
|
|
3835
|
+
*/
|
|
3836
|
+
relativeLocation?: boolean;
|
|
3837
|
+
|
|
3810
3838
|
/**
|
|
3811
3839
|
* Roles allowed to use the tus endpoints (create, write, read offset, terminate).
|
|
3812
3840
|
*
|
|
@@ -3849,7 +3877,8 @@ export interface ITusConfig {
|
|
|
3849
3877
|
|
|
3850
3878
|
/**
|
|
3851
3879
|
* Termination extension configuration.
|
|
3852
|
-
* Allows deleting uploads via DELETE.
|
|
3880
|
+
* Allows deleting uploads via DELETE. `false`: DELETE answers 501 and OPTIONS no longer advertises it
|
|
3881
|
+
* (enforced since 11.42.9 — before, the flag was only logged).
|
|
3853
3882
|
* @default true
|
|
3854
3883
|
*/
|
|
3855
3884
|
termination?: boolean;
|
|
@@ -4071,6 +4100,8 @@ interface IBetterAuthBase {
|
|
|
4071
4100
|
* Disable user registration (sign-up) via BetterAuth.
|
|
4072
4101
|
* Passed through to better-auth's native emailAndPassword.disableSignUp.
|
|
4073
4102
|
* Custom endpoints (GraphQL + REST) also check this flag early.
|
|
4103
|
+
* The Swagger document built by `setupSwagger()` then leaves out `POST /iam/sign-up/email`,
|
|
4104
|
+
* which would answer `SIGNUP_DISABLED` to every caller.
|
|
4074
4105
|
* @default false
|
|
4075
4106
|
*/
|
|
4076
4107
|
disableSignUp?: boolean;
|
|
@@ -4091,6 +4122,8 @@ interface IBetterAuthBase {
|
|
|
4091
4122
|
* Set `false` to withhold the hook, which makes that route answer `RESET_PASSWORD_DISABLED`
|
|
4092
4123
|
* again. For deployments whose reset policy is support-mediated or SSO-primary, and which
|
|
4093
4124
|
* therefore do not want an unauthenticated, token-minting, mail-sending endpoint at all.
|
|
4125
|
+
* The Swagger document built by `setupSwagger()` then leaves out `/iam/request-password-reset`,
|
|
4126
|
+
* `/iam/reset-password` and `/iam/reset-password/{token}`.
|
|
4094
4127
|
*
|
|
4095
4128
|
* @default true
|
|
4096
4129
|
* @since 11.36.1
|
|
@@ -4243,7 +4276,12 @@ interface IBetterAuthBase {
|
|
|
4243
4276
|
|
|
4244
4277
|
/**
|
|
4245
4278
|
* Additional Better-Auth plugins to include.
|
|
4246
|
-
* These will be merged with the built-in plugins (jwt, twoFactor, passkey).
|
|
4279
|
+
* These will be merged with the built-in plugins (jwt, twoFactor, passkey). Their routes appear in the
|
|
4280
|
+
* Swagger document built by `setupSwagger()` automatically.
|
|
4281
|
+
*
|
|
4282
|
+
* Better-Auth's `openAPI()` plugin is registered internally (it feeds `setupSwagger()`), with its routes
|
|
4283
|
+
* kept off the router. Add `openAPI()` here only if the project wants those routes public; it then
|
|
4284
|
+
* replaces the internal registration.
|
|
4247
4285
|
* @see https://www.better-auth.com/docs/plugins
|
|
4248
4286
|
* @example
|
|
4249
4287
|
* ```typescript
|
|
@@ -6,10 +6,13 @@ import type { DocumentBuilder, SwaggerCustomOptions, SwaggerDocumentOptions } fr
|
|
|
6
6
|
* @since 11.42.7
|
|
7
7
|
*/
|
|
8
8
|
export interface ISwaggerOperationInfo {
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Class name of the controller that serves the operation. For a route Better-Auth serves itself, the
|
|
11
|
+
* project's Better-Auth controller (`BetterAuthController` / `CoreBetterAuthController`).
|
|
12
|
+
*/
|
|
10
13
|
controller: string;
|
|
11
14
|
|
|
12
|
-
/** Name of the handler method */
|
|
15
|
+
/** Name of the handler method; for a route Better-Auth serves itself, Better-Auth's operation id */
|
|
13
16
|
handler: string;
|
|
14
17
|
|
|
15
18
|
/** True when neither the role guard nor the tenant guard requires a sign-in */
|
|
@@ -693,6 +693,13 @@ import { mountAiMcpOAuth } from '@lenne.tech/nest-server';
|
|
|
693
693
|
await mountAiMcpOAuth(app);
|
|
694
694
|
```
|
|
695
695
|
|
|
696
|
+
**Swagger (11.42.9):** `setupSwagger()` documents `/ai/mcp` (POST/GET/DELETE, with the bearer
|
|
697
|
+
requirement the endpoint enforces itself) whenever the MCP server is registered, and — because the
|
|
698
|
+
OAuth endpoints are an Express router, invisible to `@nestjs/swagger` — exactly the OAuth endpoints
|
|
699
|
+
`mountAiMcpOAuth()` mounted (`/.well-known/oauth-authorization-server`,
|
|
700
|
+
`/.well-known/oauth-protected-resource/ai/mcp`, `/authorize`, `/token`, `/register`; `/revoke` only for a
|
|
701
|
+
provider with `revokeToken`). Without the call they are not documented, because they do not exist.
|
|
702
|
+
|
|
696
703
|
The issuer — and with it every endpoint URL in the discovery metadata, which MCP clients follow —
|
|
697
704
|
is the server's `baseUrl` (`NSC__BASE_URL` when deployed), resolved the same way BetterAuth and
|
|
698
705
|
CORS resolve it. `local` / `ci` / `e2e` fall back to `http://localhost:3000`; any other environment
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Controller, Delete, Get, Logger, OnModuleDestroy, Optional, Post, Req, Res } from '@nestjs/common';
|
|
2
|
-
import {
|
|
2
|
+
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
|
3
3
|
import { Request, Response } from 'express';
|
|
4
4
|
import { hostname } from 'node:os';
|
|
5
5
|
|
|
@@ -62,7 +62,10 @@ interface McpSessionEntry {
|
|
|
62
62
|
user: AiToolUser;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
// The route guard admits everybody because the endpoint authenticates itself (session/JWT bearer, or an
|
|
66
|
+
// MCP OAuth token): the bearer requirement is declared for Swagger, which setupSwagger() keeps.
|
|
67
|
+
@ApiBearerAuth('bearer')
|
|
68
|
+
@ApiTags('AI MCP')
|
|
66
69
|
@Controller('ai/mcp')
|
|
67
70
|
@Roles(RoleEnum.S_EVERYONE)
|
|
68
71
|
export class CoreAiMcpController implements OnModuleDestroy {
|
|
@@ -108,6 +111,12 @@ export class CoreAiMcpController implements OnModuleDestroy {
|
|
|
108
111
|
@Optional() protected readonly redisService?: CoreRedisService,
|
|
109
112
|
) {}
|
|
110
113
|
|
|
114
|
+
@ApiOperation({
|
|
115
|
+
description:
|
|
116
|
+
'Model Context Protocol endpoint (Streamable HTTP) for MCP clients: JSON-RPC 2.0 messages, starting with `initialize`. ' +
|
|
117
|
+
'The response carries the `mcp-session-id` header that later requests send back. Requires a bearer token (session, JWT or MCP OAuth token).',
|
|
118
|
+
summary: 'MCP request',
|
|
119
|
+
})
|
|
111
120
|
@Post()
|
|
112
121
|
async handlePost(@Req() req: Request, @Res() res: Response): Promise<void> {
|
|
113
122
|
const user = await this.resolveUser(req);
|
|
@@ -206,11 +215,19 @@ export class CoreAiMcpController implements OnModuleDestroy {
|
|
|
206
215
|
}
|
|
207
216
|
}
|
|
208
217
|
|
|
218
|
+
@ApiOperation({
|
|
219
|
+
description: 'Server-sent event stream of an MCP session (`mcp-session-id` header). Requires a bearer token.',
|
|
220
|
+
summary: 'MCP event stream',
|
|
221
|
+
})
|
|
209
222
|
@Get()
|
|
210
223
|
async handleGet(@Req() req: Request, @Res() res: Response): Promise<void> {
|
|
211
224
|
await this.handleSessionRequest(req, res);
|
|
212
225
|
}
|
|
213
226
|
|
|
227
|
+
@ApiOperation({
|
|
228
|
+
description: 'Ends an MCP session (`mcp-session-id` header). Requires a bearer token.',
|
|
229
|
+
summary: 'End MCP session',
|
|
230
|
+
})
|
|
214
231
|
@Delete()
|
|
215
232
|
async handleDelete(@Req() req: Request, @Res() res: Response): Promise<void> {
|
|
216
233
|
await this.handleSessionRequest(req, res);
|
|
@@ -3,6 +3,7 @@ import { Logger } from '@nestjs/common';
|
|
|
3
3
|
import { resolveServerUrls } from '../../../common/helpers/cookies.helper';
|
|
4
4
|
import { ConfigService } from '../../../common/services/config.service';
|
|
5
5
|
import { CoreAiMcpOAuthService } from '../services/core-ai-mcp-oauth.service';
|
|
6
|
+
import { setMountedMcpOAuth } from './ai-mcp-oauth.registry';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Mount the MCP OAuth 2.1 router (`mcpAuthRouter`) on a NestJS application.
|
|
@@ -10,7 +11,9 @@ import { CoreAiMcpOAuthService } from '../services/core-ai-mcp-oauth.service';
|
|
|
10
11
|
* Call this in `main.ts` AFTER `app.init()` when `ai.mcp.oauth` is enabled. It
|
|
11
12
|
* lazy-imports `@modelcontextprotocol/sdk` and wires the OAuth provider built from
|
|
12
13
|
* {@link CoreAiMcpOAuthService}, exposing the standard discovery + token endpoints
|
|
13
|
-
* (`/.well-known/oauth-*`, `/authorize`, `/token`, `/register
|
|
14
|
+
* (`/.well-known/oauth-*`, `/authorize`, `/token`, `/register`; `/revoke` only for a provider that
|
|
15
|
+
* implements `revokeToken`, which the built-in one does not). `setupSwagger()` documents exactly the
|
|
16
|
+
* endpoints mounted here.
|
|
14
17
|
*
|
|
15
18
|
* The interactive consent step requires `CoreAiMcpOAuthService.authorizeConsent`
|
|
16
19
|
* to be overridden with your login/consent UI (see INTEGRATION-CHECKLIST).
|
|
@@ -46,10 +49,11 @@ export async function mountAiMcpOAuth(
|
|
|
46
49
|
const mcpPath = options.mcpPath ?? '/ai/mcp';
|
|
47
50
|
|
|
48
51
|
let router: unknown;
|
|
52
|
+
const provider = oauthService.buildOAuthProvider() as any;
|
|
49
53
|
try {
|
|
50
54
|
router = mcpAuthRouter({
|
|
51
55
|
issuerUrl,
|
|
52
|
-
provider
|
|
56
|
+
provider,
|
|
53
57
|
resourceServerUrl: new URL(`${baseUrl.replace(/\/$/, '')}${mcpPath}`),
|
|
54
58
|
});
|
|
55
59
|
} catch (error) {
|
|
@@ -63,6 +67,12 @@ export async function mountAiMcpOAuth(
|
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
app.use(router);
|
|
70
|
+
// The same conditions mcpAuthRouter() uses to decide whether /register and /revoke exist.
|
|
71
|
+
setMountedMcpOAuth({
|
|
72
|
+
mcpPath,
|
|
73
|
+
registration: !!provider?.clientsStore?.registerClient,
|
|
74
|
+
revocation: !!provider?.revokeToken,
|
|
75
|
+
});
|
|
66
76
|
new Logger('mountAiMcpOAuth').log(`MCP OAuth issuer: ${issuerUrl.href} (from ${source})`);
|
|
67
77
|
}
|
|
68
78
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `mountAiMcpOAuth()` mounted, for `setupSwagger()`.
|
|
3
|
+
*
|
|
4
|
+
* The MCP OAuth endpoints are an Express router mounted with `app.use()`, not Nest controllers, so
|
|
5
|
+
* `@nestjs/swagger` cannot see them. The mount records here what it put in place — only then are the
|
|
6
|
+
* endpoints documented, so the document follows the actual call in `main.ts`, not a config flag.
|
|
7
|
+
*
|
|
8
|
+
* Imports nothing on purpose: read by the common Swagger helper without pulling in the AI module.
|
|
9
|
+
*/
|
|
10
|
+
export interface IMountedMcpOAuth {
|
|
11
|
+
/** Path of the protected MCP resource (`/ai/mcp`) */
|
|
12
|
+
mcpPath: string;
|
|
13
|
+
/** Whether `/register` (dynamic client registration) is mounted */
|
|
14
|
+
registration: boolean;
|
|
15
|
+
/** Whether `/revoke` is mounted */
|
|
16
|
+
revocation: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let mounted: IMountedMcpOAuth | undefined;
|
|
20
|
+
|
|
21
|
+
/** Records the mounted MCP OAuth endpoints (called by `mountAiMcpOAuth()`). */
|
|
22
|
+
export function setMountedMcpOAuth(value: IMountedMcpOAuth | undefined): void {
|
|
23
|
+
mounted = value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The mounted MCP OAuth endpoints, or `undefined` when `mountAiMcpOAuth()` was not called. */
|
|
27
|
+
export function getMountedMcpOAuth(): IMountedMcpOAuth | undefined {
|
|
28
|
+
return mounted;
|
|
29
|
+
}
|
|
@@ -1146,6 +1146,40 @@ When enabled, Better-Auth exposes the following endpoints at the configured `bas
|
|
|
1146
1146
|
| `/iam/reset-password` | POST | Reset password with token |
|
|
1147
1147
|
| `/iam/verify-email` | POST | Verify email address |
|
|
1148
1148
|
|
|
1149
|
+
### In the Swagger document (since 11.42.9)
|
|
1150
|
+
|
|
1151
|
+
`setupSwagger()` documents every route this module makes reachable under the base path, as the server
|
|
1152
|
+
is configured:
|
|
1153
|
+
|
|
1154
|
+
- The routes `CoreBetterAuthController` handles itself (`sign-in/email`, `sign-up/email`, `sign-out`,
|
|
1155
|
+
`session`, `features`) come from the controller, as for any Nest controller.
|
|
1156
|
+
- The routes Better-Auth serves itself — everything the configured plugins add (two-factor, passkey,
|
|
1157
|
+
sessions, password change, …) — come from Better-Auth's own OpenAPI generator
|
|
1158
|
+
(`CoreBetterAuthService.getOpenApiSchema()`, prepared at bootstrap). They are documented under the
|
|
1159
|
+
project's Better-Auth controller: its tags, its `operationTags`, operation ids
|
|
1160
|
+
`<Controller>_betterAuth_<betterAuthOperationId>`, schemas prefixed `BetterAuth` (`BetterAuthUser`).
|
|
1161
|
+
- Only what can succeed is documented. `switchedOffBetterAuthPaths()` reads the nest-server switches
|
|
1162
|
+
(sign-in/sign-up under `emailAndPassword.enabled: false`, `sign-up/email` under `disableSignUp`, the
|
|
1163
|
+
reset routes under `passwordReset: false`); `unavailableBetterAuthOperations()` reads the LIVE
|
|
1164
|
+
instance at bootstrap — so `betterAuth.options` and project plugins count — and drops e.g. POST
|
|
1165
|
+
`/get-session` without `session.deferSessionRefresh`, social routes without a provider, two-factor OTP
|
|
1166
|
+
routes without `otpOptions.sendOTP`. Both apply to the controller's own routes as well.
|
|
1167
|
+
- Security says what Better-Auth enforces, not what its generator writes (every route bearer-only): a
|
|
1168
|
+
route guarded by one of Better-Auth's session middlewares — or one of the three social-account routes
|
|
1169
|
+
that check the session in their handler (`HANDLER_SESSION_PATHS`) — carries the document's global
|
|
1170
|
+
requirement; every other route is public (`security: []`): sign-in, password reset, get-session, the
|
|
1171
|
+
two-factor verification steps of a sign-in. Routes of a plugin the module does not register itself
|
|
1172
|
+
(`admin()`, `organization()`, anything in `betterAuth.plugins`) always keep the global requirement:
|
|
1173
|
+
such a plugin may check the session in a middleware of its own, which cannot be recognised from outside.
|
|
1174
|
+
- Under a global prefix or URI versioning the middleware does not forward Better-Auth's own routes, so
|
|
1175
|
+
they are not documented then (a warning says so).
|
|
1176
|
+
|
|
1177
|
+
Better-Auth exposes its generator only through its `openAPI()` plugin, so the module registers that plugin
|
|
1178
|
+
itself (`disableDefaultReference: true`). Its own route, `/iam/open-api/generate-schema`, is not part of
|
|
1179
|
+
the API: the plugin's endpoints are marked server-only, so Better-Auth never routes them (404, however the
|
|
1180
|
+
path is spelled) and the module calls the generator in-process. A project that adds `openAPI()` to
|
|
1181
|
+
`betterAuth.plugins` itself keeps those routes — then it is the project's decision.
|
|
1182
|
+
|
|
1149
1183
|
### JWT Token Endpoint
|
|
1150
1184
|
|
|
1151
1185
|
The `/iam/token` endpoint returns a fresh JWT token for the current session. Use this when your JWT has expired but your session is still valid.
|
|
@@ -2,7 +2,7 @@ import { passkey } from '@better-auth/passkey';
|
|
|
2
2
|
import { Logger } from '@nestjs/common';
|
|
3
3
|
import { betterAuth, BetterAuthPlugin } from 'better-auth';
|
|
4
4
|
import { mongodbAdapter } from 'better-auth/adapters/mongodb';
|
|
5
|
-
import { jwt, twoFactor } from 'better-auth/plugins';
|
|
5
|
+
import { jwt, openAPI, twoFactor } from 'better-auth/plugins';
|
|
6
6
|
import * as crypto from 'crypto';
|
|
7
7
|
import * as fs from 'fs';
|
|
8
8
|
import * as path from 'path';
|
|
@@ -11,6 +11,7 @@ import { resolveServerUrls, strippedApiHostname } from '../../common/helpers/coo
|
|
|
11
11
|
import { revalidateWsConnectionsOf } from '../../common/helpers/graphql-ws-connection.helper';
|
|
12
12
|
import { IBetterAuth, ICorsConfig } from '../../common/interfaces/server-options.interface';
|
|
13
13
|
import { detectCookiePrefixDrift, resolveBetterAuthCookiePrefix } from './better-auth-cookie-prefix.helper';
|
|
14
|
+
import { projectProvidesOpenApiPlugin } from './core-better-auth-openapi.helper';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Type for better-auth instance with plugins
|
|
@@ -978,6 +979,16 @@ function buildPlugins(
|
|
|
978
979
|
plugins.push(passkey(passkeyOptions));
|
|
979
980
|
}
|
|
980
981
|
|
|
982
|
+
// Better-Auth's OpenAPI generator, so setupSwagger() can document exactly the routes the configured
|
|
983
|
+
// plugins add (CoreBetterAuthService.getOpenApiSchema()). Better-Auth exposes the generator only
|
|
984
|
+
// through this plugin, and the plugin only as HTTP routes — which are not part of the API. They are
|
|
985
|
+
// kept off the router rather than refused by path: a path check sees the request as sent, while
|
|
986
|
+
// Better-Auth resolves `/iam/./open-api/…` or `/iam/x/%2e%2e/open-api/…` to the very same route.
|
|
987
|
+
// A project that registers openAPI() itself keeps its routes.
|
|
988
|
+
if (!projectProvidesOpenApiPlugin(config)) {
|
|
989
|
+
plugins.push(serverOnlyPlugin(openAPI({ disableDefaultReference: true })));
|
|
990
|
+
}
|
|
991
|
+
|
|
981
992
|
// Merge custom plugins from configuration
|
|
982
993
|
// This allows projects to add any Better-Auth plugin without modifying this package
|
|
983
994
|
if (config.plugins?.length) {
|
|
@@ -987,6 +998,21 @@ function buildPlugins(
|
|
|
987
998
|
return plugins;
|
|
988
999
|
}
|
|
989
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* Keeps every endpoint of a plugin off the HTTP router. better-call skips an endpoint whose metadata
|
|
1003
|
+
* carries `SERVER_ONLY` when it builds the router, whatever path a request names, while
|
|
1004
|
+
* `auth.api.<endpoint>()` still calls it in-process. Better-Auth's own generator leaves such endpoints
|
|
1005
|
+
* out of its schema too.
|
|
1006
|
+
*/
|
|
1007
|
+
function serverOnlyPlugin<T extends BetterAuthPlugin>(plugin: T): T {
|
|
1008
|
+
for (const endpoint of Object.values(plugin.endpoints ?? {}) as { options?: { metadata?: object } }[]) {
|
|
1009
|
+
if (endpoint?.options) {
|
|
1010
|
+
endpoint.options.metadata = { ...endpoint.options.metadata, SERVER_ONLY: true };
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return plugin;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
990
1016
|
/**
|
|
991
1017
|
* Builds the social providers configuration object dynamically.
|
|
992
1018
|
* Iterates over all configured providers and includes those that are enabled
|
|
@@ -6,26 +6,12 @@ import { ConfigService } from '../../common/services/config.service';
|
|
|
6
6
|
import { CoreBetterAuthChallengeService } from './core-better-auth-challenge.service';
|
|
7
7
|
import { BetterAuthCookieHelper, createCookieHelper } from './core-better-auth-cookie.helper';
|
|
8
8
|
import { wrapBetterAuthErrorResponse } from './core-better-auth-error-codes.helper';
|
|
9
|
+
import { isControllerHandledPath } from './core-better-auth-openapi.helper';
|
|
9
10
|
import { runWithResetPassword } from './core-better-auth-password-reset.registry';
|
|
10
11
|
import { CoreBetterAuthUserMapper } from './core-better-auth-user.mapper';
|
|
11
12
|
import { extractSessionToken, sendWebResponse, signCookieValue, toWebRequest } from './core-better-auth-web.helper';
|
|
12
13
|
import { CoreBetterAuthService } from './core-better-auth.service';
|
|
13
14
|
|
|
14
|
-
/**
|
|
15
|
-
* List of paths that are handled by CoreBetterAuthController
|
|
16
|
-
* These should NOT be forwarded to Better Auth's native handler
|
|
17
|
-
*
|
|
18
|
-
* Only paths with nest-server-specific logic belong here:
|
|
19
|
-
* - sign-in/email: Legacy user migration, password normalization
|
|
20
|
-
* - sign-up/email: User linking to own DB, password sync
|
|
21
|
-
* - sign-out: Custom cookie clearing
|
|
22
|
-
* - session: Custom response format with mapped user
|
|
23
|
-
*
|
|
24
|
-
* All other paths (Passkey, 2FA, etc.) go directly to Better Auth's
|
|
25
|
-
* native handler via this middleware for maximum compatibility.
|
|
26
|
-
*/
|
|
27
|
-
const CONTROLLER_HANDLED_PATHS = ['/features', '/sign-in/email', '/sign-up/email', '/sign-out', '/session'];
|
|
28
|
-
|
|
29
15
|
/**
|
|
30
16
|
* Native Better-Auth routes that set a NEW password from a token or OTP, with the body
|
|
31
17
|
* field each of them carries it in.
|
|
@@ -227,8 +213,9 @@ export class CoreBetterAuthApiMiddleware implements NestMiddleware {
|
|
|
227
213
|
// Get the path relative to the base path
|
|
228
214
|
const relativePath = requestPath.slice(basePath.length);
|
|
229
215
|
|
|
230
|
-
// Skip paths that are handled by CoreBetterAuthController (nest-server-specific logic)
|
|
231
|
-
|
|
216
|
+
// Skip paths that are handled by CoreBetterAuthController (nest-server-specific logic).
|
|
217
|
+
// All other paths (Passkey, 2FA, etc.) go directly to Better Auth's native handler.
|
|
218
|
+
if (isControllerHandledPath(relativePath)) {
|
|
232
219
|
return next();
|
|
233
220
|
}
|
|
234
221
|
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { IBetterAuth } from '../../common/interfaces/server-options.interface';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What the Swagger document needs to know about the routes under the Better-Auth base path.
|
|
5
|
+
*
|
|
6
|
+
* Two kinds of route live there. The few with nest-server-specific logic are handled by
|
|
7
|
+
* `CoreBetterAuthController` (`CONTROLLER_HANDLED_PATHS`); every other request is forwarded by
|
|
8
|
+
* `CoreBetterAuthApiMiddleware` to Better-Auth's own handler, which serves whatever the configured
|
|
9
|
+
* plugins add (two-factor, passkey, sessions, password change, …). Those forwarded routes have no
|
|
10
|
+
* Nest handler, so `@nestjs/swagger` cannot see them; `setupSwagger()` documents them from Better-Auth's
|
|
11
|
+
* OpenAPI generator instead (see `CoreBetterAuthService.getOpenApiSchema()`).
|
|
12
|
+
*
|
|
13
|
+
* This file imports types only, so the middleware, the plugin builder and the Swagger helper can all
|
|
14
|
+
* read the same rules without importing each other.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Paths (relative to the base path) handled by `CoreBetterAuthController` rather than forwarded.
|
|
19
|
+
*
|
|
20
|
+
* Only paths with nest-server-specific logic belong here:
|
|
21
|
+
* - sign-in/email: legacy user migration, password normalization
|
|
22
|
+
* - sign-up/email: user linking to own DB, password sync
|
|
23
|
+
* - sign-out: custom cookie clearing
|
|
24
|
+
* - session: custom response format with mapped user
|
|
25
|
+
* - features: nest-server's own feature report
|
|
26
|
+
*/
|
|
27
|
+
export const CONTROLLER_HANDLED_PATHS = ['/features', '/sign-in/email', '/sign-up/email', '/sign-out', '/session'];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Routes Better-Auth guards with a session check in the HANDLER instead of a session middleware: the
|
|
31
|
+
* social-account routes resolve their caller through `resolveUserId()` (better-auth `api/routes/account`),
|
|
32
|
+
* which refuses an HTTP caller without a session. Every other route without a session middleware is
|
|
33
|
+
* public. Pinned against better-auth's source by tests/unit/better-auth-openapi-availability.spec.ts.
|
|
34
|
+
*/
|
|
35
|
+
export const HANDLER_SESSION_PATHS = ['/account-info', '/get-access-token', '/refresh-token'];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The plugins `buildPlugins()` registers itself. Their routes — like Better-Auth's core routes — are
|
|
39
|
+
* classified by the session middleware they carry. Any other plugin's routes keep the global security
|
|
40
|
+
* requirement: such a plugin may check the session in a middleware of its own (`admin()` does), which
|
|
41
|
+
* nothing outside it can recognise, and a session route documented as public sends clients without one.
|
|
42
|
+
*/
|
|
43
|
+
export const BUILT_IN_BETTER_AUTH_PLUGIN_IDS = ['jwt', 'lt-ws-session-revocation', 'open-api', 'passkey', 'two-factor'];
|
|
44
|
+
|
|
45
|
+
/** Better-Auth's `:param` route syntax as OpenAPI's `{param}`, which its generator writes. */
|
|
46
|
+
export function toOpenApiPath(path: string): string {
|
|
47
|
+
return path.replace(/:([^/]+)/g, '{$1}');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Base of the routes Better-Auth's `openAPI()` plugin adds (`/open-api/generate-schema`). */
|
|
51
|
+
export const BETTER_AUTH_OPEN_API_PATH = '/open-api';
|
|
52
|
+
|
|
53
|
+
/** Id of Better-Auth's `openAPI()` plugin */
|
|
54
|
+
export const BETTER_AUTH_OPEN_API_PLUGIN_ID = 'open-api';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Minimal shape of the document Better-Auth's OpenAPI generator returns (OpenAPI 3.1, paths relative to
|
|
58
|
+
* the base path).
|
|
59
|
+
*/
|
|
60
|
+
export interface IBetterAuthOpenApiDocument {
|
|
61
|
+
components?: { schemas?: Record<string, unknown> };
|
|
62
|
+
paths: Record<string, Record<string, unknown>>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Whether a path relative to the base path is handled by `CoreBetterAuthController`. */
|
|
66
|
+
export function isControllerHandledPath(relativePath: string): boolean {
|
|
67
|
+
return CONTROLLER_HANDLED_PATHS.some((path) => relativePath === path || relativePath.startsWith(`${path}/`));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Whether a path relative to the base path belongs to Better-Auth's `openAPI()` plugin. */
|
|
71
|
+
export function isBetterAuthOpenApiPath(relativePath: string): boolean {
|
|
72
|
+
return relativePath === BETTER_AUTH_OPEN_API_PATH || relativePath.startsWith(`${BETTER_AUTH_OPEN_API_PATH}/`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Whether the project registered `openAPI()` itself in `betterAuth.plugins`. Then its routes are the
|
|
77
|
+
* project's decision and stay reachable; otherwise nest-server registers the plugin only to feed the
|
|
78
|
+
* Swagger document, with its endpoints kept off the router (`serverOnlyPlugin()` in better-auth.config.ts).
|
|
79
|
+
*/
|
|
80
|
+
export function projectProvidesOpenApiPlugin(config: IBetterAuth | null | undefined): boolean {
|
|
81
|
+
return !!config?.plugins?.some(
|
|
82
|
+
(plugin) => (plugin as { id?: unknown } | null)?.id === BETTER_AUTH_OPEN_API_PLUGIN_ID,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The parts of a running Better-Auth instance (`await auth.$context`) that decide whether a route it
|
|
88
|
+
* serves can succeed. Typed loosely on purpose: these are Better-Auth's resolved options, including
|
|
89
|
+
* whatever a project passed through `betterAuth.options` or added as a plugin.
|
|
90
|
+
*/
|
|
91
|
+
export interface IBetterAuthRuntime {
|
|
92
|
+
options?: {
|
|
93
|
+
account?: { accountLinking?: { allowUnlinkingAll?: boolean } };
|
|
94
|
+
emailAndPassword?: { disableSignUp?: boolean; enabled?: boolean; sendResetPassword?: unknown };
|
|
95
|
+
emailVerification?: { sendVerificationEmail?: unknown };
|
|
96
|
+
plugins?: {
|
|
97
|
+
id?: string;
|
|
98
|
+
options?: Record<string, any>;
|
|
99
|
+
schema?: { session?: { fields?: Record<string, { input?: boolean }> } };
|
|
100
|
+
}[];
|
|
101
|
+
session?: { additionalFields?: Record<string, { input?: boolean }>; deferSessionRefresh?: boolean };
|
|
102
|
+
user?: { changeEmail?: { enabled?: boolean }; deleteUser?: { enabled?: boolean } };
|
|
103
|
+
};
|
|
104
|
+
socialProviders?: unknown[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Operations Better-Auth serves but answers with an error for EVERY caller under the running
|
|
109
|
+
* configuration. Better-Auth's OpenAPI generator lists each registered endpoint regardless of the options
|
|
110
|
+
* it checks at request time, so `CoreBetterAuthService` removes these from the description it hands to
|
|
111
|
+
* `setupSwagger()`. Keys: `/path` (every method) or `<method> /path`.
|
|
112
|
+
*
|
|
113
|
+
* Each row mirrors a check in better-auth's own source (1.7.7, `dist/…`), pinned by
|
|
114
|
+
* `tests/unit/better-auth-openapi-availability.spec.ts`:
|
|
115
|
+
*
|
|
116
|
+
* | Unavailable | Unless | Better-Auth answers |
|
|
117
|
+
* |---|---|---|
|
|
118
|
+
* | `post /get-session` | `session.deferSessionRefresh` | 405 `METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED` (api/routes/session.mjs) |
|
|
119
|
+
* | update-session | a session field accepts input (`session.additionalFields` or a plugin's session schema) | "No fields to update" (update-session.mjs) |
|
|
120
|
+
* | social sign-in, callback, link, provider tokens | a social provider is configured | `PROVIDER_NOT_FOUND` / "Provider … is not supported" (sign-in, callback, account.mjs) |
|
|
121
|
+
* | unlink-account | a social provider, or `account.accountLinking.allowUnlinkingAll` | `FAILED_TO_UNLINK_LAST_ACCOUNT` — without a provider a user has one account (account.mjs) |
|
|
122
|
+
* | send-verification-email, verify-email | `emailVerification.sendVerificationEmail` | "Verification email isn't enabled" (email-verification.mjs) |
|
|
123
|
+
* | request + reset password | `emailAndPassword.sendResetPassword` | `RESET_PASSWORD_DISABLED` (password.mjs) |
|
|
124
|
+
* | sign-in + sign-up with email | `emailAndPassword.enabled` | `EMAIL_PASSWORD_DISABLED` (sign-in.mjs, sign-up.mjs). Not change/verify password: Better-Auth does not check `enabled` there |
|
|
125
|
+
* | `/sign-up/email` | not `emailAndPassword.disableSignUp` | "Email and password sign up is not enabled" (sign-up.mjs) |
|
|
126
|
+
* | delete-user (+ callback) | `user.deleteUser.enabled` | error (update-user.mjs) |
|
|
127
|
+
* | change-email | `user.changeEmail.enabled` | `CHANGE_EMAIL_DISABLED` (update-user.mjs) |
|
|
128
|
+
* | two-factor OTP send/verify | the two-factor plugin's `otpOptions.sendOTP` | `OTP_NOT_CONFIGURED` (plugins/two-factor) |
|
|
129
|
+
* | two-factor TOTP uri/verify | not the two-factor plugin's `totpOptions.disable` | `TOTP_NOT_CONFIGURED` (plugins/two-factor) |
|
|
130
|
+
*
|
|
131
|
+
* Add a row when an upgrade of better-auth introduces a new option-dependent refusal.
|
|
132
|
+
*/
|
|
133
|
+
export function unavailableBetterAuthOperations(runtime: IBetterAuthRuntime | null | undefined): Set<string> {
|
|
134
|
+
const options = runtime?.options ?? {};
|
|
135
|
+
const unavailable = new Set<string>();
|
|
136
|
+
const add = (...keys: string[]) => keys.forEach((key) => unavailable.add(key));
|
|
137
|
+
|
|
138
|
+
if (!options.session?.deferSessionRefresh) {
|
|
139
|
+
add('post /get-session');
|
|
140
|
+
}
|
|
141
|
+
const acceptsInput = (fields: Record<string, { input?: boolean }> | undefined) =>
|
|
142
|
+
Object.values(fields ?? {}).some((field) => field?.input !== false);
|
|
143
|
+
if (
|
|
144
|
+
!acceptsInput(options.session?.additionalFields) &&
|
|
145
|
+
!options.plugins?.some((plugin) => acceptsInput(plugin?.schema?.session?.fields))
|
|
146
|
+
) {
|
|
147
|
+
add('/update-session');
|
|
148
|
+
}
|
|
149
|
+
if (!runtime?.socialProviders?.length) {
|
|
150
|
+
add('/sign-in/social', '/callback/{id}', '/link-social', '/refresh-token', '/get-access-token', '/account-info');
|
|
151
|
+
if (!options.account?.accountLinking?.allowUnlinkingAll) {
|
|
152
|
+
add('/unlink-account');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!options.emailVerification?.sendVerificationEmail) {
|
|
156
|
+
add('/send-verification-email', '/verify-email');
|
|
157
|
+
}
|
|
158
|
+
if (!options.emailAndPassword?.sendResetPassword) {
|
|
159
|
+
add('/request-password-reset', '/reset-password', '/reset-password/{token}');
|
|
160
|
+
}
|
|
161
|
+
if (!options.emailAndPassword?.enabled) {
|
|
162
|
+
add('/sign-in/email', '/sign-up/email');
|
|
163
|
+
}
|
|
164
|
+
if (options.emailAndPassword?.disableSignUp) {
|
|
165
|
+
add('/sign-up/email');
|
|
166
|
+
}
|
|
167
|
+
if (!options.user?.deleteUser?.enabled) {
|
|
168
|
+
add('/delete-user', '/delete-user/callback');
|
|
169
|
+
}
|
|
170
|
+
if (!options.user?.changeEmail?.enabled) {
|
|
171
|
+
add('/change-email');
|
|
172
|
+
}
|
|
173
|
+
const twoFactor = options.plugins?.find((plugin) => plugin?.id === 'two-factor');
|
|
174
|
+
if (twoFactor) {
|
|
175
|
+
if (!twoFactor.options?.otpOptions?.sendOTP) {
|
|
176
|
+
add('/two-factor/send-otp', '/two-factor/verify-otp');
|
|
177
|
+
}
|
|
178
|
+
if (twoFactor.options?.totpOptions?.disable) {
|
|
179
|
+
add('/two-factor/get-totp-uri', '/two-factor/verify-totp');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return unavailable;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Whether an operation is listed by `unavailableBetterAuthOperations()` (or `switchedOffBetterAuthPaths()`). */
|
|
186
|
+
export function isUnavailableOperation(unavailable: Set<string>, method: string, relativePath: string): boolean {
|
|
187
|
+
return unavailable.has(relativePath) || unavailable.has(`${method.toLowerCase()} ${relativePath}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Paths (relative to the base path) the configuration switches off for every caller. Documented, they
|
|
192
|
+
* could only ever answer an error, so the Swagger document leaves them out — explicit controller routes
|
|
193
|
+
* and generated ones alike. Each rule mirrors the switch it reads:
|
|
194
|
+
*
|
|
195
|
+
* | Configuration | Paths | Why they cannot succeed |
|
|
196
|
+
* |---|---|---|
|
|
197
|
+
* | `emailAndPassword.enabled: false` | sign-in + sign-up with email | `EMAIL_PASSWORD_DISABLED` (the only routes Better-Auth checks it on) |
|
|
198
|
+
* | `emailAndPassword.disableSignUp: true` | `/sign-up/email` | `SIGNUP_DISABLED` (`isSignUpEnabled()`) |
|
|
199
|
+
* | `emailAndPassword.passwordReset: false` | request + reset | no reset hook, so no token can be minted |
|
|
200
|
+
*/
|
|
201
|
+
export function switchedOffBetterAuthPaths(config: IBetterAuth | null | undefined): Set<string> {
|
|
202
|
+
const emailAndPassword = config?.emailAndPassword;
|
|
203
|
+
const paths = new Set<string>();
|
|
204
|
+
if (emailAndPassword?.enabled === false) {
|
|
205
|
+
// Better-Auth checks `enabled` in sign-in and sign-up only; password change/verify/reset keep
|
|
206
|
+
// working for accounts that have a password.
|
|
207
|
+
paths.add('/sign-in/email');
|
|
208
|
+
paths.add('/sign-up/email');
|
|
209
|
+
}
|
|
210
|
+
if (emailAndPassword?.disableSignUp === true) {
|
|
211
|
+
paths.add('/sign-up/email');
|
|
212
|
+
}
|
|
213
|
+
if (emailAndPassword?.passwordReset === false) {
|
|
214
|
+
for (const path of ['/request-password-reset', '/reset-password', '/reset-password/{token}']) {
|
|
215
|
+
paths.add(path);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return paths;
|
|
219
|
+
}
|
|
@@ -753,6 +753,7 @@ export class CoreBetterAuthController {
|
|
|
753
753
|
* @see README.md "Architecture: Why Custom Controllers?"
|
|
754
754
|
*/
|
|
755
755
|
@All('*path')
|
|
756
|
+
@ApiExcludeEndpoint() // A pass-through to Better-Auth: one `/iam/{path}` per HTTP method, with nothing to document
|
|
756
757
|
@Roles(RoleEnum.S_EVERYONE)
|
|
757
758
|
async handlePluginRoutes(@Req() req: Request, @Res() res: Response): Promise<void> {
|
|
758
759
|
return this.handleBetterAuthPlugins(req, res);
|