@mastra/koa 1.2.3 → 1.2.4
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/CHANGELOG.md +78 -0
- package/LICENSE.md +15 -0
- package/dist/index.cjs +134 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +134 -144
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/dist/auth-middleware.d.ts +0 -4
- package/dist/auth-middleware.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Busboy } from '@fastify/busboy';
|
|
2
|
+
import { isProtectedCustomRoute } from '@mastra/server/auth';
|
|
2
3
|
import { formatZodError } from '@mastra/server/handlers/error';
|
|
3
4
|
import { MastraServer as MastraServer$1, redactStreamChunk, normalizeQueryParams } from '@mastra/server/server-adapter';
|
|
4
|
-
import { isDevPlaygroundRequest, isProtectedPath, canAccessPublicly, checkRules, defaultAuthConfig } from '@mastra/server/auth';
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
|
|
@@ -211,148 +211,39 @@ ZodError.create = (issues) => {
|
|
|
211
211
|
const error = new ZodError(issues);
|
|
212
212
|
return error;
|
|
213
213
|
};
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (!
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (isDevPlaygroundRequest(path, method, getHeader, authConfig, customRouteAuthConfig)) {
|
|
225
|
-
return next();
|
|
226
|
-
}
|
|
227
|
-
if (!isProtectedPath(path, method, authConfig, customRouteAuthConfig)) {
|
|
228
|
-
return next();
|
|
229
|
-
}
|
|
230
|
-
if (canAccessPublicly(path, method, authConfig)) {
|
|
231
|
-
return next();
|
|
232
|
-
}
|
|
233
|
-
const authHeader = ctx.headers.authorization;
|
|
234
|
-
let token = authHeader ? authHeader.replace("Bearer ", "") : null;
|
|
235
|
-
const query = ctx.query;
|
|
236
|
-
if (!token && query.apiKey) {
|
|
237
|
-
token = query.apiKey || null;
|
|
238
|
-
}
|
|
239
|
-
if (!token) {
|
|
240
|
-
ctx.status = 401;
|
|
241
|
-
ctx.body = { error: "Authentication required" };
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
try {
|
|
245
|
-
let user;
|
|
246
|
-
if (typeof authConfig.authenticateToken === "function") {
|
|
247
|
-
user = await authConfig.authenticateToken(token, ctx.request);
|
|
248
|
-
} else {
|
|
249
|
-
throw new Error("No token verification method configured");
|
|
250
|
-
}
|
|
251
|
-
if (!user) {
|
|
252
|
-
ctx.status = 401;
|
|
253
|
-
ctx.body = { error: "Invalid or expired token" };
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
ctx.state.requestContext.set("user", user);
|
|
257
|
-
return next();
|
|
258
|
-
} catch (err) {
|
|
259
|
-
mastra.getLogger()?.error("Authentication error", {
|
|
260
|
-
error: err instanceof Error ? { message: err.message, stack: err.stack } : err
|
|
214
|
+
|
|
215
|
+
// src/index.ts
|
|
216
|
+
var _hasPermissionPromise;
|
|
217
|
+
function loadHasPermission() {
|
|
218
|
+
if (!_hasPermissionPromise) {
|
|
219
|
+
_hasPermissionPromise = import('@mastra/core/auth/ee').then((m) => m.hasPermission).catch(() => {
|
|
220
|
+
console.error(
|
|
221
|
+
"[@mastra/koa] Auth features require @mastra/core >= 1.6.0. Please upgrade: npm install @mastra/core@latest"
|
|
222
|
+
);
|
|
223
|
+
return void 0;
|
|
261
224
|
});
|
|
262
|
-
ctx.status = 401;
|
|
263
|
-
ctx.body = { error: "Invalid or expired token" };
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
var authorizationMiddleware = async (ctx, next) => {
|
|
268
|
-
const mastra = ctx.state.mastra;
|
|
269
|
-
const authConfig = mastra.getServer()?.auth;
|
|
270
|
-
const customRouteAuthConfig = ctx.state.customRouteAuthConfig;
|
|
271
|
-
if (!authConfig) {
|
|
272
|
-
return next();
|
|
273
|
-
}
|
|
274
|
-
const path = String(ctx.path || "/");
|
|
275
|
-
const method = String(ctx.method || "GET");
|
|
276
|
-
const getHeader = (name) => ctx.headers[name.toLowerCase()];
|
|
277
|
-
if (isDevPlaygroundRequest(path, method, getHeader, authConfig, customRouteAuthConfig)) {
|
|
278
|
-
return next();
|
|
279
|
-
}
|
|
280
|
-
if (!isProtectedPath(path, method, authConfig, customRouteAuthConfig)) {
|
|
281
|
-
return next();
|
|
282
|
-
}
|
|
283
|
-
if (canAccessPublicly(path, method, authConfig)) {
|
|
284
|
-
return next();
|
|
285
225
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
});
|
|
300
|
-
ctx.status = 500;
|
|
301
|
-
ctx.body = { error: "Authorization error" };
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if ("authorize" in authConfig && typeof authConfig.authorize === "function") {
|
|
306
|
-
try {
|
|
307
|
-
const context = {
|
|
308
|
-
get: (key) => {
|
|
309
|
-
if (key === "mastra") return ctx.state.mastra;
|
|
310
|
-
if (key === "requestContext") return ctx.state.requestContext;
|
|
311
|
-
if (key === "tools") return ctx.state.tools;
|
|
312
|
-
if (key === "taskStore") return ctx.state.taskStore;
|
|
313
|
-
if (key === "customRouteAuthConfig") return ctx.state.customRouteAuthConfig;
|
|
314
|
-
return void 0;
|
|
315
|
-
},
|
|
316
|
-
req: ctx.request
|
|
317
|
-
};
|
|
318
|
-
const isAuthorized = await authConfig.authorize(path, method, user, context);
|
|
319
|
-
if (isAuthorized) {
|
|
320
|
-
return next();
|
|
226
|
+
return _hasPermissionPromise;
|
|
227
|
+
}
|
|
228
|
+
function toWebRequest(ctx) {
|
|
229
|
+
const protocol = ctx.protocol || "http";
|
|
230
|
+
const host = ctx.host || "localhost";
|
|
231
|
+
const url = `${protocol}://${host}${ctx.url}`;
|
|
232
|
+
const headers = new Headers();
|
|
233
|
+
for (const [key, value] of Object.entries(ctx.headers)) {
|
|
234
|
+
if (value) {
|
|
235
|
+
if (Array.isArray(value)) {
|
|
236
|
+
value.forEach((v) => headers.append(key, v));
|
|
237
|
+
} else {
|
|
238
|
+
headers.set(key, value);
|
|
321
239
|
}
|
|
322
|
-
ctx.status = 403;
|
|
323
|
-
ctx.body = { error: "Access denied" };
|
|
324
|
-
return;
|
|
325
|
-
} catch (err) {
|
|
326
|
-
mastra.getLogger()?.error("Authorization error in authorize", {
|
|
327
|
-
error: err instanceof Error ? { message: err.message, stack: err.stack } : err,
|
|
328
|
-
path,
|
|
329
|
-
method
|
|
330
|
-
});
|
|
331
|
-
ctx.status = 500;
|
|
332
|
-
ctx.body = { error: "Authorization error" };
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
if ("rules" in authConfig && authConfig.rules && authConfig.rules.length > 0) {
|
|
337
|
-
const isAuthorized = await checkRules(authConfig.rules, path, method, user);
|
|
338
|
-
if (isAuthorized) {
|
|
339
|
-
return next();
|
|
340
|
-
}
|
|
341
|
-
ctx.status = 403;
|
|
342
|
-
ctx.body = { error: "Access denied" };
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
if (defaultAuthConfig.rules && defaultAuthConfig.rules.length > 0) {
|
|
346
|
-
const isAuthorized = await checkRules(defaultAuthConfig.rules, path, method, user);
|
|
347
|
-
if (isAuthorized) {
|
|
348
|
-
return next();
|
|
349
240
|
}
|
|
350
241
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
242
|
+
return new globalThis.Request(url, {
|
|
243
|
+
method: ctx.method,
|
|
244
|
+
headers
|
|
245
|
+
});
|
|
246
|
+
}
|
|
356
247
|
var MastraServer = class extends MastraServer$1 {
|
|
357
248
|
async init() {
|
|
358
249
|
this.registerErrorMiddleware();
|
|
@@ -610,7 +501,8 @@ var MastraServer = class extends MastraServer$1 {
|
|
|
610
501
|
async sendResponse(route, ctx, result, prefix) {
|
|
611
502
|
const resolvedPrefix = prefix ?? this.prefix ?? "";
|
|
612
503
|
if (route.responseType === "json") {
|
|
613
|
-
ctx.
|
|
504
|
+
ctx.type = "application/json";
|
|
505
|
+
ctx.body = result === null || result === void 0 ? JSON.stringify(null) : result;
|
|
614
506
|
} else if (route.responseType === "stream") {
|
|
615
507
|
await this.stream(route, ctx, result);
|
|
616
508
|
} else if (route.responseType === "datastream-response") {
|
|
@@ -713,7 +605,9 @@ var MastraServer = class extends MastraServer$1 {
|
|
|
713
605
|
method: String(ctx.method || "GET"),
|
|
714
606
|
getHeader: (name) => ctx.headers[name.toLowerCase()],
|
|
715
607
|
getQuery: (name) => ctx.query[name],
|
|
716
|
-
requestContext: ctx.state.requestContext
|
|
608
|
+
requestContext: ctx.state.requestContext,
|
|
609
|
+
request: toWebRequest(ctx),
|
|
610
|
+
buildAuthorizeContext: () => toWebRequest(ctx)
|
|
717
611
|
});
|
|
718
612
|
if (authError) {
|
|
719
613
|
ctx.status = authError.status;
|
|
@@ -800,6 +694,22 @@ var MastraServer = class extends MastraServer$1 {
|
|
|
800
694
|
abortSignal: ctx.state.abortSignal,
|
|
801
695
|
routePrefix: prefix
|
|
802
696
|
};
|
|
697
|
+
const authConfig = this.mastra.getServer()?.auth;
|
|
698
|
+
if (authConfig) {
|
|
699
|
+
const hasPermission = await loadHasPermission();
|
|
700
|
+
if (hasPermission) {
|
|
701
|
+
const userPermissions = ctx.state.requestContext.get("userPermissions");
|
|
702
|
+
const permissionError = this.checkRoutePermission(route, userPermissions, hasPermission);
|
|
703
|
+
if (permissionError) {
|
|
704
|
+
ctx.status = permissionError.status;
|
|
705
|
+
ctx.body = {
|
|
706
|
+
error: permissionError.error,
|
|
707
|
+
message: permissionError.message
|
|
708
|
+
};
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
803
713
|
try {
|
|
804
714
|
const result = await route.handler(handlerParams);
|
|
805
715
|
await this.sendResponse(route, ctx, result, prefix);
|
|
@@ -844,6 +754,54 @@ var MastraServer = class extends MastraServer$1 {
|
|
|
844
754
|
async registerCustomApiRoutes() {
|
|
845
755
|
if (!await this.buildCustomRouteHandler()) return;
|
|
846
756
|
this.app.use(async (ctx, next) => {
|
|
757
|
+
const path = String(ctx.path || "/");
|
|
758
|
+
const method = String(ctx.method || "GET");
|
|
759
|
+
if (isProtectedCustomRoute(path, method, this.customRouteAuthConfig)) {
|
|
760
|
+
const serverRoute = {
|
|
761
|
+
method,
|
|
762
|
+
path,
|
|
763
|
+
responseType: "json",
|
|
764
|
+
handler: async () => {
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
const authError = await this.checkRouteAuth(serverRoute, {
|
|
768
|
+
path,
|
|
769
|
+
method,
|
|
770
|
+
getHeader: (name) => ctx.headers[name.toLowerCase()],
|
|
771
|
+
getQuery: (name) => ctx.query[name],
|
|
772
|
+
requestContext: ctx.state.requestContext,
|
|
773
|
+
request: toWebRequest(ctx),
|
|
774
|
+
buildAuthorizeContext: () => toWebRequest(ctx)
|
|
775
|
+
});
|
|
776
|
+
if (authError) {
|
|
777
|
+
ctx.status = authError.status;
|
|
778
|
+
ctx.body = { error: authError.error };
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const authConfig = this.mastra.getServer()?.auth;
|
|
782
|
+
if (authConfig) {
|
|
783
|
+
let hasPermission;
|
|
784
|
+
try {
|
|
785
|
+
({ hasPermission } = await import('@mastra/core/auth/ee'));
|
|
786
|
+
} catch {
|
|
787
|
+
console.error(
|
|
788
|
+
"[@mastra/koa] Auth features require @mastra/core >= 1.6.0. Please upgrade: npm install @mastra/core@latest"
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
if (hasPermission) {
|
|
792
|
+
const userPermissions = ctx.state.requestContext.get("userPermissions");
|
|
793
|
+
const permissionError = this.checkRoutePermission(serverRoute, userPermissions, hasPermission);
|
|
794
|
+
if (permissionError) {
|
|
795
|
+
ctx.status = permissionError.status;
|
|
796
|
+
ctx.body = {
|
|
797
|
+
error: permissionError.error,
|
|
798
|
+
message: permissionError.message
|
|
799
|
+
};
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
847
805
|
const response = await this.handleCustomRouteRequest(
|
|
848
806
|
`${ctx.protocol}://${ctx.host}${ctx.originalUrl || ctx.url}`,
|
|
849
807
|
ctx.method,
|
|
@@ -860,12 +818,44 @@ var MastraServer = class extends MastraServer$1 {
|
|
|
860
818
|
this.app.use(this.createContextMiddleware());
|
|
861
819
|
}
|
|
862
820
|
registerAuthMiddleware() {
|
|
863
|
-
|
|
864
|
-
|
|
821
|
+
}
|
|
822
|
+
registerHttpLoggingMiddleware() {
|
|
823
|
+
if (!this.httpLoggingConfig?.enabled) {
|
|
865
824
|
return;
|
|
866
825
|
}
|
|
867
|
-
this.app.use(
|
|
868
|
-
|
|
826
|
+
this.app.use(async (ctx, next) => {
|
|
827
|
+
if (!this.shouldLogRequest(ctx.path)) {
|
|
828
|
+
return next();
|
|
829
|
+
}
|
|
830
|
+
const start = Date.now();
|
|
831
|
+
const method = ctx.method;
|
|
832
|
+
const path = ctx.path;
|
|
833
|
+
await next();
|
|
834
|
+
const duration = Date.now() - start;
|
|
835
|
+
const status = ctx.status;
|
|
836
|
+
const level = this.httpLoggingConfig?.level || "info";
|
|
837
|
+
const logData = {
|
|
838
|
+
method,
|
|
839
|
+
path,
|
|
840
|
+
status,
|
|
841
|
+
duration: `${duration}ms`
|
|
842
|
+
};
|
|
843
|
+
if (this.httpLoggingConfig?.includeQueryParams) {
|
|
844
|
+
logData.query = ctx.query;
|
|
845
|
+
}
|
|
846
|
+
if (this.httpLoggingConfig?.includeHeaders) {
|
|
847
|
+
const headers = { ...ctx.headers };
|
|
848
|
+
const redactHeaders = this.httpLoggingConfig.redactHeaders || [];
|
|
849
|
+
redactHeaders.forEach((h) => {
|
|
850
|
+
const key = h.toLowerCase();
|
|
851
|
+
if (headers[key] !== void 0) {
|
|
852
|
+
headers[key] = "[REDACTED]";
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
logData.headers = headers;
|
|
856
|
+
}
|
|
857
|
+
this.logger[level](`${method} ${path} ${status} ${duration}ms`, logData);
|
|
858
|
+
});
|
|
869
859
|
}
|
|
870
860
|
};
|
|
871
861
|
|