@lenne.tech/nest-server 11.42.16 → 11.43.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 +22 -0
- package/.claude/rules/configurable-features.md +3 -3
- package/.claude/rules/testing.md +4 -4
- package/FRAMEWORK-API.md +2 -2
- package/dist/core/common/interfaces/server-options.interface.d.ts +3 -0
- package/dist/core/modules/ai/core-ai-mcp-oauth.initializer.d.ts +19 -0
- package/dist/core/modules/ai/core-ai-mcp-oauth.initializer.js +145 -0
- package/dist/core/modules/ai/core-ai-mcp-oauth.initializer.js.map +1 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js +6 -5
- package/dist/core/modules/ai/core-ai-mcp.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 +6 -1
- package/dist/core/modules/ai/core-ai.module.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.d.ts +33 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.js +282 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.js.map +1 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.d.ts +15 -2
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js +73 -19
- 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 +5 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.registry.js.map +1 -1
- package/dist/core/modules/ai/index.d.ts +1 -0
- package/dist/core/modules/ai/index.js +1 -0
- package/dist/core/modules/ai/index.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.d.ts +30 -2
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js +222 -15
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.config.d.ts +4 -0
- package/dist/core/modules/better-auth/better-auth.config.js +29 -2
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +2 -0
- package/dist/core/modules/better-auth/core-better-auth.controller.js +36 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +1 -1
- package/migration-guides/11.42.x-to-11.43.x.md +195 -0
- package/package.json +4 -4
- package/src/core/common/helpers/swagger.helper.ts +4 -4
- package/src/core/common/interfaces/server-options.interface.ts +37 -9
- package/src/core/modules/ai/INTEGRATION-CHECKLIST.md +50 -24
- package/src/core/modules/ai/README.md +98 -13
- package/src/core/modules/ai/core-ai-mcp-oauth.initializer.ts +199 -0
- package/src/core/modules/ai/core-ai-mcp.controller.ts +13 -6
- package/src/core/modules/ai/core-ai.module.ts +14 -2
- package/src/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.ts +369 -0
- package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +149 -43
- package/src/core/modules/ai/helpers/ai-mcp-oauth.registry.ts +18 -5
- package/src/core/modules/ai/index.ts +1 -0
- package/src/core/modules/ai/services/core-ai-mcp-oauth.service.ts +385 -28
- package/src/core/modules/better-auth/better-auth.config.ts +49 -5
- package/src/core/modules/better-auth/core-better-auth.controller.ts +75 -1
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
BadRequestException,
|
|
4
4
|
Body,
|
|
5
5
|
Controller,
|
|
6
|
+
ForbiddenException,
|
|
6
7
|
Get,
|
|
7
8
|
HttpCode,
|
|
8
9
|
HttpException,
|
|
@@ -399,6 +400,9 @@ export class CoreBetterAuthController {
|
|
|
399
400
|
email: input.email,
|
|
400
401
|
password: normalizedPassword,
|
|
401
402
|
},
|
|
403
|
+
// With a request, Better-Auth answers a Response by default; this controller reads the data.
|
|
404
|
+
asResponse: false,
|
|
405
|
+
request: this.csrfCheckedRequest(req ?? (res as any)?.req, '/sign-in/email'),
|
|
402
406
|
})) as BetterAuthSignInResponse | null;
|
|
403
407
|
|
|
404
408
|
if (!response) {
|
|
@@ -436,7 +440,7 @@ export class CoreBetterAuthController {
|
|
|
436
440
|
const signInUrl = new URL(`${basePath}/sign-in/email`, baseUrl);
|
|
437
441
|
|
|
438
442
|
// Create a new Web Request for Better Auth's native handler
|
|
439
|
-
const webRequest = new Request(signInUrl.toString(), {
|
|
443
|
+
const webRequest = new globalThis.Request(signInUrl.toString(), {
|
|
440
444
|
body: modifiedBody,
|
|
441
445
|
headers: new Headers({
|
|
442
446
|
'Content-Type': 'application/json',
|
|
@@ -507,6 +511,7 @@ export class CoreBetterAuthController {
|
|
|
507
511
|
if (error instanceof UnauthorizedException) {
|
|
508
512
|
throw error;
|
|
509
513
|
}
|
|
514
|
+
this.rethrowCsrfRefusal(error, 'sign-in');
|
|
510
515
|
|
|
511
516
|
throw new UnauthorizedException(ErrorCode.INVALID_CREDENTIALS);
|
|
512
517
|
}
|
|
@@ -569,6 +574,9 @@ export class CoreBetterAuthController {
|
|
|
569
574
|
name: input.name || input.email.split('@')[0],
|
|
570
575
|
password: normalizedPassword,
|
|
571
576
|
},
|
|
577
|
+
// With a request, Better-Auth answers a Response by default; this controller reads the data.
|
|
578
|
+
asResponse: false,
|
|
579
|
+
request: this.csrfCheckedRequest((res as any)?.req, '/sign-up/email'),
|
|
572
580
|
});
|
|
573
581
|
|
|
574
582
|
if (!response) {
|
|
@@ -637,6 +645,7 @@ export class CoreBetterAuthController {
|
|
|
637
645
|
} catch (error) {
|
|
638
646
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
639
647
|
this.logger.debug(`Sign-up error: ${errorMessage}`);
|
|
648
|
+
this.rethrowCsrfRefusal(error, 'sign-up');
|
|
640
649
|
if (errorMessage.includes('already exists')) {
|
|
641
650
|
throw new BadRequestException(ErrorCode.EMAIL_ALREADY_EXISTS);
|
|
642
651
|
}
|
|
@@ -644,6 +653,71 @@ export class CoreBetterAuthController {
|
|
|
644
653
|
}
|
|
645
654
|
}
|
|
646
655
|
|
|
656
|
+
/**
|
|
657
|
+
* The Web `Request` handed to `api.signInEmail()` / `api.signUpEmail()`, so Better-Auth's own CSRF
|
|
658
|
+
* protection runs. Both endpoints carry `formCsrfMiddleware`: it refuses a cross-site NAVIGATION
|
|
659
|
+
* (`Sec-Fetch-Site: cross-site` + `Sec-Fetch-Mode: navigate`, i.e. a form a hostile page submits)
|
|
660
|
+
* and checks a present `Origin` / `Referer` against `trustedOrigins`. It reads them from
|
|
661
|
+
* `ctx.request` — and called without one, the API skipped the check entirely, so a hostile page
|
|
662
|
+
* could sign a visitor in to an account of the attacker's choosing (login CSRF): whatever the
|
|
663
|
+
* visitor did next, including approving an MCP client on the consent page, happened in the
|
|
664
|
+
* attacker's account.
|
|
665
|
+
*
|
|
666
|
+
* Only those signals are forwarded, and only from a request that comes from a browser — login CSRF
|
|
667
|
+
* needs a victim's browser, so a client without one has nothing to protect. A browser sends `Origin`
|
|
668
|
+
* on every POST and `Sec-Fetch-Site` with all fetch metadata; a request carrying neither (nor a
|
|
669
|
+
* `Referer`) is not one. That matters because Node's built-in `fetch` (undici) sends a lone
|
|
670
|
+
* `Sec-Fetch-Mode: cors` and no `Origin`, which Better-Auth reads as a browser request missing its
|
|
671
|
+
* origin and refuses — every server-side script, seeder or SSR route signing in with `fetch` would
|
|
672
|
+
* answer 403. Not the cookie either: its presence alone makes Better-Auth demand an `Origin`, which
|
|
673
|
+
* would refuse non-browser clients that keep a cookie jar (API tests, apps) — a change unrelated to
|
|
674
|
+
* CSRF, since every browser sends `Origin` on a cross-site POST anyway.
|
|
675
|
+
* Returns `undefined` without a request (a subclass calling `super` with a stub), which keeps the
|
|
676
|
+
* call working as before.
|
|
677
|
+
*/
|
|
678
|
+
protected csrfCheckedRequest(req: Request | undefined, path: string): globalThis.Request | undefined {
|
|
679
|
+
if (!req?.headers) {
|
|
680
|
+
return undefined;
|
|
681
|
+
}
|
|
682
|
+
const header = (name: string) => {
|
|
683
|
+
const value = req.headers[name];
|
|
684
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
685
|
+
};
|
|
686
|
+
const fromBrowser = ['origin', 'referer', 'sec-fetch-site'].some((name) => header(name));
|
|
687
|
+
const headers = new Headers();
|
|
688
|
+
if (fromBrowser) {
|
|
689
|
+
for (const name of ['origin', 'referer', 'sec-fetch-dest', 'sec-fetch-mode', 'sec-fetch-site']) {
|
|
690
|
+
const value = header(name);
|
|
691
|
+
if (value) {
|
|
692
|
+
headers.set(name, value);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
// The URL only names the endpoint; the checks read the headers. A missing baseUrl must not turn
|
|
697
|
+
// every sign-in into an error, so it falls back to a placeholder origin.
|
|
698
|
+
const url = new URL(
|
|
699
|
+
`${this.betterAuthService.getBasePath()}${path}`,
|
|
700
|
+
this.betterAuthService.getBaseUrl() || 'http://localhost',
|
|
701
|
+
);
|
|
702
|
+
return new globalThis.Request(url, { headers, method: 'POST' });
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Answers a request Better-Auth's CSRF protection refused with 403, not as bad credentials: no
|
|
707
|
+
* sign-in can ever make it succeed, and "invalid credentials" would send whoever debugs a missing
|
|
708
|
+
* `trustedOrigins` entry looking at the password.
|
|
709
|
+
*/
|
|
710
|
+
protected rethrowCsrfRefusal(error: unknown, action: string): void {
|
|
711
|
+
const code = (error as { body?: { code?: string } })?.body?.code;
|
|
712
|
+
if (code && ['CROSS_SITE_NAVIGATION_LOGIN_BLOCKED', 'INVALID_ORIGIN', 'MISSING_OR_NULL_ORIGIN'].includes(code)) {
|
|
713
|
+
this.logger.warn(
|
|
714
|
+
`Refused ${action}: ${code} (Better-Auth CSRF protection). If this is your own frontend, add its origin to ` +
|
|
715
|
+
'cors.allowedOrigins — an explicit betterAuth.trustedOrigins replaces the derived list.',
|
|
716
|
+
);
|
|
717
|
+
throw new ForbiddenException(ErrorCode.ACCESS_DENIED);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
647
721
|
/**
|
|
648
722
|
* Sign out (logout)
|
|
649
723
|
*
|