added additional permissions, implemented mcp authentication

This commit is contained in:
dswbx
2025-08-07 15:20:29 +02:00
parent 42db5f55c7
commit 170ea2c45b
16 changed files with 144 additions and 74 deletions

View File

@@ -1,4 +1,4 @@
import type { App, SafeUser } from "bknd";
import type { App, Permission, SafeUser } from "bknd";
import { type Context, type Env, Hono } from "hono";
import * as middlewares from "modules/middlewares";
import type { EntityManager } from "data/entities";
@@ -19,20 +19,6 @@ export interface ServerEnv extends Env {
[key: string]: any;
}
/* export type ServerEnv = Env & {
Variables: {
app: App;
// to prevent resolving auth multiple times
auth?: {
resolved: boolean;
registered: boolean;
skip: boolean;
user?: SafeUser;
};
html?: string;
};
}; */
export class Controller {
protected middlewares = middlewares;

View File

@@ -1,3 +1,4 @@
import type { App } from "bknd";
import type { EventManager } from "core/events";
import type { Connection } from "data/connection";
import type { EntityManager } from "data/entities";
@@ -11,6 +12,10 @@ import type { McpServer } from "bknd/utils";
type PartialRec<T> = { [P in keyof T]?: PartialRec<T[P]> };
export type ModuleBuildContextMcpContext = {
app: App;
ctx: () => ModuleBuildContext;
};
export type ModuleBuildContext = {
connection: Connection;
server: Hono<ServerEnv>;
@@ -20,7 +25,7 @@ export type ModuleBuildContext = {
logger: DebugLogger;
flags: (typeof Module)["ctx_flags"];
helper: ModuleHelper;
mcp: McpServer<{ ctx: () => ModuleBuildContext }>;
mcp: McpServer<ModuleBuildContextMcpContext>;
};
export abstract class Module<Schema extends object = object> {

View File

@@ -3,8 +3,11 @@ import { Entity } from "data/entities";
import type { EntityIndex, Field } from "data/fields";
import { entityTypes } from "data/entities/Entity";
import { isEqual } from "lodash-es";
import type { ModuleBuildContext } from "./Module";
import type { ModuleBuildContext, ModuleBuildContextMcpContext } from "./Module";
import type { EntityRelation } from "data/relations";
import type { Permission } from "core/security/Permission";
import { Exception } from "core/errors";
import { invariant } from "bknd/utils";
export class ModuleHelper {
constructor(protected ctx: Omit<ModuleBuildContext, "helper">) {}
@@ -110,4 +113,21 @@ export class ModuleHelper {
entity.__replaceField(name, newField);
}
async throwUnlessGranted(
permission: Permission | string,
c: { context: ModuleBuildContextMcpContext; request: Request },
) {
invariant(c.context.app, "app is not available in mcp context");
invariant(c.request instanceof Request, "request is not available in mcp context");
const user = await c.context.app.module.auth.authenticator.resolveAuthFromRequest(c.request);
if (!this.ctx.guard.granted(permission, user)) {
throw new Exception(
`Permission "${typeof permission === "string" ? permission : permission.name}" not granted`,
403,
);
}
}
}

View File

@@ -273,6 +273,11 @@ export class ModuleManager {
: new EntityManager([], this.connection, [], [], this.emgr);
this.guard = new Guard();
this.mcp = new McpServer(undefined as any, {
app: new Proxy(this, {
get: () => {
throw new Error("app is not available in mcp context");
},
}) as any,
ctx: () => this.ctx(),
});
}

View File

@@ -354,15 +354,19 @@ export class SystemController extends Controller {
const { mcp } = this.app.modules.ctx();
const { version, ...appConfig } = this.app.toJSON();
mcp.resource("system_config", "bknd://system/config", (c) =>
c.json(this.app.toJSON(), {
mcp.resource("system_config", "bknd://system/config", async (c) => {
await c.context.ctx().helper.throwUnlessGranted(SystemPermissions.configRead, c);
return c.json(this.app.toJSON(), {
title: "System Config",
}),
)
});
})
.resource(
"system_config_module",
"bknd://system/config/{module}",
(c, { module }) => {
async (c, { module }) => {
await this.ctx.helper.throwUnlessGranted(SystemPermissions.configRead, c);
const m = this.app.modules.get(module as any) as Module;
return c.json(m.toJSON(), {
title: `Config for ${module}`,
@@ -372,15 +376,19 @@ export class SystemController extends Controller {
list: Object.keys(appConfig),
},
)
.resource("system_schema", "bknd://system/schema", (c) =>
c.json(this.app.getSchema(), {
.resource("system_schema", "bknd://system/schema", async (c) => {
await this.ctx.helper.throwUnlessGranted(SystemPermissions.schemaRead, c);
return c.json(this.app.getSchema(), {
title: "System Schema",
}),
)
});
})
.resource(
"system_schema_module",
"bknd://system/schema/{module}",
(c, { module }) => {
async (c, { module }) => {
await this.ctx.helper.throwUnlessGranted(SystemPermissions.schemaRead, c);
const m = this.app.modules.get(module as any);
return c.json(m.getSchema().toJSON(), {
title: `Schema for ${module}`,