@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/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
- response.status(exception.status).json({ error: exception.message });
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 is enforced
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 authConfig = this.mastra.getServer()?.auth;
632
- if (!authConfig) return;
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 (!this.authService.isEnabled()) return;
11013
- const user = await this.authService.authenticate(req, {
11014
- requestContext,
11015
- response: res,
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
- customRoute: true
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.