@mastra/nestjs 0.2.38 → 0.2.39-alpha.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/dist/controllers/mastra.controller.d.ts.map +1 -1
- package/dist/filters/mastra-exception.filter.d.ts.map +1 -1
- package/dist/index.cjs +101 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +102 -15
- package/dist/index.js.map +1 -1
- package/dist/mastra.service.d.ts +2 -0
- package/dist/mastra.service.d.ts.map +1 -1
- package/dist/services/auth.service.d.ts +6 -1
- package/dist/services/auth.service.d.ts.map +1 -1
- package/dist/services/custom-route.service.d.ts +4 -3
- package/dist/services/custom-route.service.d.ts.map +1 -1
- package/dist/services/route-handler.service.d.ts +15 -0
- package/dist/services/route-handler.service.d.ts.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "module";
|
|
2
2
|
import { All, BadRequestException, Catch, Controller, ForbiddenException, Get, HttpException, HttpStatus, Inject, Injectable, Logger, Module, NotFoundException, PayloadTooLargeException, Req, Res, Scope, ServiceUnavailableException, SetMetadata, UnauthorizedException, UseFilters, UseGuards, UseInterceptors } from "@nestjs/common";
|
|
3
|
-
import { HTTPException, MASTRA_CLIENT_TYPE_HEADER, MASTRA_IS_STUDIO_KEY, SERVER_ROUTES, getCustomHTTPExceptionResponse, isReservedRequestContextKey, isSafeObjectKey, isStudioClientTypeHeader, isZodError, normalizeQueryParams, redactStreamChunk, serializeStreamChunk } from "@mastra/server/server-adapter";
|
|
3
|
+
import { HTTPException, MASTRA_AUTH_MODE_KEY, MASTRA_CLIENT_TYPE_HEADER, MASTRA_IS_STUDIO_KEY, SERVER_ROUTES, checkRouteFGA, getCustomHTTPExceptionResponse, getCustomRouteHTTPExceptionResponse, getEffectivePermission, getHTTPErrorResponse, isReservedRequestContextKey, isSafeObjectKey, isStudioClientTypeHeader, isZodError, normalizeQueryParams, redactStreamChunk, serializeStreamChunk } from "@mastra/server/server-adapter";
|
|
4
4
|
import { formatZodError, isZodError as isZodError$1 } from "@mastra/server/handlers/error";
|
|
5
|
+
import { hasPermission } from "@mastra/core/auth/ee";
|
|
5
6
|
import { RequestContext } from "@mastra/core/request-context";
|
|
6
7
|
import { MASTRA_USER_KEY, coreAuthMiddleware } from "@mastra/server/auth";
|
|
7
8
|
import { Readable } from "stream";
|
|
@@ -203,6 +204,11 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
|
|
|
203
204
|
if (isZodError(error)) throw this.createValidationError(route, error, "body");
|
|
204
205
|
throw error;
|
|
205
206
|
}
|
|
207
|
+
await this.authorizeRoute(route, params.requestContext, {
|
|
208
|
+
...validatedPathParams,
|
|
209
|
+
...validatedQueryParams,
|
|
210
|
+
...typeof validatedBody === "object" && validatedBody !== null ? validatedBody : {}
|
|
211
|
+
});
|
|
206
212
|
const context = {
|
|
207
213
|
mastra: this.mastra,
|
|
208
214
|
requestContext: params.requestContext,
|
|
@@ -225,6 +231,39 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
|
|
|
225
231
|
sseFlushOnConnect: route.sseFlushOnConnect
|
|
226
232
|
};
|
|
227
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Enforce the route's RBAC permission and FGA check, matching the other server adapters.
|
|
236
|
+
* Relies on the auth guard having stored the user, permissions, and auth mode in the request context.
|
|
237
|
+
*/
|
|
238
|
+
async authorizeRoute(route, requestContext, params) {
|
|
239
|
+
await this.enforceRoutePermission(route, requestContext);
|
|
240
|
+
await this.enforceRouteFGA(route, requestContext, params);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Throws 403 when the user lacks the route's effective RBAC permission.
|
|
244
|
+
* Skipped when Mastra auth is explicitly disabled (`auth.enabled: false`), since no Mastra user is resolved.
|
|
245
|
+
*/
|
|
246
|
+
async enforceRoutePermission(route, requestContext) {
|
|
247
|
+
if (this.options.auth?.enabled === false) return;
|
|
248
|
+
const studio = this.mastra.getStudio?.();
|
|
249
|
+
const server = this.mastra.getServer();
|
|
250
|
+
if (!studio?.auth && !server?.auth) return;
|
|
251
|
+
const requiredPermission = (requestContext.get(MASTRA_AUTH_MODE_KEY) === "studio" ? studio?.rbac ?? server?.rbac : server?.rbac) ? getEffectivePermission(route) : null;
|
|
252
|
+
if (requiredPermission) {
|
|
253
|
+
const required = Array.isArray(requiredPermission) ? requiredPermission : [requiredPermission];
|
|
254
|
+
const userPermissions = requestContext.get("mastra__userPermissions");
|
|
255
|
+
if (!userPermissions || !required.some((permission) => hasPermission(userPermissions, permission))) throw new ForbiddenException(`Missing required permission: ${required.join(" or ")}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Throws when the FGA provider denies the route's resource.
|
|
260
|
+
* Skipped when Mastra auth is explicitly disabled (`auth.enabled: false`), since no Mastra user is resolved.
|
|
261
|
+
*/
|
|
262
|
+
async enforceRouteFGA(route, requestContext, params) {
|
|
263
|
+
if (this.options.auth?.enabled === false) return;
|
|
264
|
+
const fgaError = await checkRouteFGA(this.mastra, route, requestContext, params);
|
|
265
|
+
if (fgaError) throw new HttpException(fgaError.message, fgaError.status);
|
|
266
|
+
}
|
|
228
267
|
async parseBody(route, body) {
|
|
229
268
|
const bodySchema = route.bodySchema;
|
|
230
269
|
if (!bodySchema) return body;
|
|
@@ -318,7 +357,8 @@ let MastraExceptionFilter = _MastraExceptionFilter = class MastraExceptionFilter
|
|
|
318
357
|
return;
|
|
319
358
|
}
|
|
320
359
|
if (exception instanceof HTTPException) {
|
|
321
|
-
|
|
360
|
+
const { status, body } = getHTTPErrorResponse(exception);
|
|
361
|
+
response.status(status).json(body);
|
|
322
362
|
return;
|
|
323
363
|
}
|
|
324
364
|
const normalized = this.normalizeError(exception);
|
|
@@ -478,7 +518,7 @@ let CustomRouteService = class CustomRouteService {
|
|
|
478
518
|
* Runs a matched custom route and writes its response.
|
|
479
519
|
* Returns false if no custom route handled the request.
|
|
480
520
|
*
|
|
481
|
-
* `authenticate` runs after Hono's router selects the route, so auth
|
|
521
|
+
* `authenticate` runs after Hono's router selects the route, so auth, RBAC, and FGA are enforced
|
|
482
522
|
* for exactly the routes Hono dispatches (including regex-constrained params).
|
|
483
523
|
*/
|
|
484
524
|
async handle(req, res, requestContext, authenticate) {
|
|
@@ -539,6 +579,8 @@ let CustomRouteService = class CustomRouteService {
|
|
|
539
579
|
const serverOnError = mastra.getServer()?.onError;
|
|
540
580
|
app.onError((err, c) => {
|
|
541
581
|
if (serverOnError) return serverOnError(err, c);
|
|
582
|
+
const httpExceptionResponse = getCustomRouteHTTPExceptionResponse(err);
|
|
583
|
+
if (httpExceptionResponse) return httpExceptionResponse;
|
|
542
584
|
mastra.getLogger()?.error(`Custom route handler failed: ${c.req.method} ${c.req.path}`, { error: err instanceof Error ? err.stack ?? err.message : String(err) });
|
|
543
585
|
return c.json({ error: "Internal Server Error" }, 500);
|
|
544
586
|
});
|
|
@@ -549,7 +591,7 @@ let CustomRouteService = class CustomRouteService {
|
|
|
549
591
|
const requiresAuth = this.authConfig.get(`${route.method}:${route.path}`) ?? route.requiresAuth !== false;
|
|
550
592
|
const authMiddleware = async (c, next) => {
|
|
551
593
|
try {
|
|
552
|
-
await c.env.authenticate(requiresAuth);
|
|
594
|
+
await c.env.authenticate(route, requiresAuth, c.req.param());
|
|
553
595
|
} catch (err) {
|
|
554
596
|
c.env.authError = err;
|
|
555
597
|
return c.body(null, 401);
|
|
@@ -616,11 +658,29 @@ let AuthService = class AuthService {
|
|
|
616
658
|
}
|
|
617
659
|
/**
|
|
618
660
|
* Whether Mastra auth should run: module options enable it, or the Mastra
|
|
619
|
-
* server has auth configured (unless module options explicitly disable it).
|
|
661
|
+
* server or Studio has auth configured (unless module options explicitly disable it).
|
|
620
662
|
*/
|
|
621
663
|
isEnabled() {
|
|
622
664
|
if (this.options.auth?.enabled === false) return false;
|
|
623
|
-
return Boolean(this.options.auth?.enabled || this.mastra.getServer()?.auth);
|
|
665
|
+
return Boolean(this.options.auth?.enabled || this.mastra.getServer()?.auth || this.mastra.getStudio?.()?.auth);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Studio requests (`x-mastra-client-type: studio`) use `studio.auth` exclusively when it is
|
|
669
|
+
* configured, without falling back to `server.auth`; all other requests use `server.auth`.
|
|
670
|
+
*/
|
|
671
|
+
getEffectiveAuthConfig(request) {
|
|
672
|
+
const clientType = request.headers[MASTRA_CLIENT_TYPE_HEADER];
|
|
673
|
+
const isStudioRequest = isStudioClientTypeHeader(Array.isArray(clientType) ? clientType[0] : clientType);
|
|
674
|
+
const studioAuth = this.mastra.getStudio?.()?.auth;
|
|
675
|
+
if (isStudioRequest && studioAuth) return {
|
|
676
|
+
authConfig: studioAuth,
|
|
677
|
+
authMode: "studio"
|
|
678
|
+
};
|
|
679
|
+
const serverAuth = this.mastra.getServer()?.auth;
|
|
680
|
+
return serverAuth ? {
|
|
681
|
+
authConfig: serverAuth,
|
|
682
|
+
authMode: "server"
|
|
683
|
+
} : null;
|
|
624
684
|
}
|
|
625
685
|
/**
|
|
626
686
|
* Check authentication for a request.
|
|
@@ -628,9 +688,11 @@ let AuthService = class AuthService {
|
|
|
628
688
|
* Throws UnauthorizedException or ForbiddenException if auth fails.
|
|
629
689
|
*/
|
|
630
690
|
async authenticate(request, options = {}) {
|
|
631
|
-
const
|
|
632
|
-
if (!
|
|
691
|
+
const effectiveAuth = this.getEffectiveAuthConfig(request);
|
|
692
|
+
if (!effectiveAuth) return;
|
|
693
|
+
const { authConfig, authMode } = effectiveAuth;
|
|
633
694
|
const requestContext = options.requestContext ?? new RequestContext();
|
|
695
|
+
requestContext.set(MASTRA_AUTH_MODE_KEY, authMode);
|
|
634
696
|
const path = request.path;
|
|
635
697
|
const method = request.method;
|
|
636
698
|
let customRouteAuthConfig = this.customRoutes.customRouteAuthConfig;
|
|
@@ -11008,15 +11070,31 @@ let MastraController = class MastraController {
|
|
|
11008
11070
|
const routePath = getMastraRoutePath(path, this.options.prefix);
|
|
11009
11071
|
if (!routePath || !this.routeHandler.matchRoute(method, routePath)) {
|
|
11010
11072
|
const requestContext = this.requestContext.requestContext;
|
|
11011
|
-
const authenticate = async (requiresAuth) => {
|
|
11012
|
-
if (
|
|
11013
|
-
|
|
11014
|
-
|
|
11015
|
-
|
|
11073
|
+
const authenticate = async (customRoute, requiresAuth, pathParams) => {
|
|
11074
|
+
if (this.authService.isEnabled()) {
|
|
11075
|
+
const user = await this.authService.authenticate(req, {
|
|
11076
|
+
requestContext,
|
|
11077
|
+
response: res,
|
|
11078
|
+
requiresAuth,
|
|
11079
|
+
customRoute: true
|
|
11080
|
+
});
|
|
11081
|
+
if (user !== void 0) this.requestContext.setUser(user);
|
|
11082
|
+
}
|
|
11083
|
+
const serverRoute = {
|
|
11084
|
+
method: customRoute.method,
|
|
11085
|
+
path: customRoute.path,
|
|
11086
|
+
responseType: "json",
|
|
11087
|
+
handler: async () => {},
|
|
11016
11088
|
requiresAuth,
|
|
11017
|
-
|
|
11089
|
+
requiresPermission: customRoute.requiresPermission,
|
|
11090
|
+
fga: customRoute.fga
|
|
11091
|
+
};
|
|
11092
|
+
if (requiresAuth) await this.routeHandler.enforceRoutePermission(serverRoute, requestContext);
|
|
11093
|
+
if (customRoute.fga) await this.routeHandler.enforceRouteFGA(serverRoute, requestContext, {
|
|
11094
|
+
...typeof req.body === "object" && req.body !== null ? req.body : {},
|
|
11095
|
+
...req.query,
|
|
11096
|
+
...pathParams
|
|
11018
11097
|
});
|
|
11019
|
-
if (user !== void 0) this.requestContext.setUser(user);
|
|
11020
11098
|
};
|
|
11021
11099
|
if (await this.customRoutes.handle(req, res, requestContext, authenticate)) return;
|
|
11022
11100
|
}
|
|
@@ -11337,6 +11415,15 @@ let MastraService = _MastraService = class MastraService {
|
|
|
11337
11415
|
}
|
|
11338
11416
|
this.serverAdapter = new NestMastraServer(app, this.routeHandler);
|
|
11339
11417
|
this.mastra.setMastraServer(this.serverAdapter);
|
|
11418
|
+
this.warnIfServerMiddlewareConfigured();
|
|
11419
|
+
}
|
|
11420
|
+
/** Mastra server middleware uses Hono's `(c, next)` signature, so NestJS cannot run it. */
|
|
11421
|
+
warnIfServerMiddlewareConfigured() {
|
|
11422
|
+
const instanceMiddleware = this.mastra.getServerMiddleware?.() ?? [];
|
|
11423
|
+
const configMiddleware = this.mastra.getServer()?.middleware;
|
|
11424
|
+
const hasConfigMiddleware = Array.isArray(configMiddleware) ? configMiddleware.length > 0 : !!configMiddleware;
|
|
11425
|
+
if (instanceMiddleware.length === 0 && !hasConfigMiddleware) return;
|
|
11426
|
+
this.mastra.getLogger()?.warn("Mastra server middleware (`server.middleware` or `setServerMiddleware()`) is configured, but this server adapter cannot run Hono middleware handlers. The configured middleware will not run — register middleware through your server framework instead.");
|
|
11340
11427
|
}
|
|
11341
11428
|
/**
|
|
11342
11429
|
* Get the Mastra instance.
|