mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
refactor: extracted auth as middleware to be added manually to endpoints
This commit is contained in:
26
app/src/modules/Controller.ts
Normal file
26
app/src/modules/Controller.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { auth, permission } from "auth/middlewares";
|
||||
import { Hono } from "hono";
|
||||
import type { ServerEnv } from "modules/Module";
|
||||
|
||||
export class Controller {
|
||||
protected middlewares = {
|
||||
auth,
|
||||
permission
|
||||
}
|
||||
|
||||
protected create({ auth }: { auth?: boolean } = {}): Hono<ServerEnv> {
|
||||
const server = Controller.createServer();
|
||||
if (auth !== false) {
|
||||
server.use(this.middlewares.auth);
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
static createServer(): Hono<ServerEnv> {
|
||||
return new Hono<ServerEnv>();
|
||||
}
|
||||
|
||||
getController(): Hono<ServerEnv> {
|
||||
return this.create();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { App } from "App";
|
||||
import type { Guard } from "auth";
|
||||
import { SchemaObject } from "core";
|
||||
import type { EventManager } from "core/events";
|
||||
@@ -5,9 +6,17 @@ import type { Static, TSchema } from "core/utils";
|
||||
import type { Connection, EntityManager } from "data";
|
||||
import type { Hono } from "hono";
|
||||
|
||||
export type ServerEnv = {
|
||||
Variables: {
|
||||
app: App;
|
||||
auth_resolved: boolean;
|
||||
html?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ModuleBuildContext = {
|
||||
connection: Connection;
|
||||
server: Hono<any>;
|
||||
server: Hono<ServerEnv>;
|
||||
em: EntityManager;
|
||||
emgr: EventManager<any>;
|
||||
guard: Guard;
|
||||
@@ -78,6 +87,10 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
|
||||
return this._schema;
|
||||
}
|
||||
|
||||
getMiddleware() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get ctx() {
|
||||
if (!this._ctx) {
|
||||
throw new Error("Context not set");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { App } from "App";
|
||||
import { Guard } from "auth";
|
||||
import { BkndError, DebugLogger } from "core";
|
||||
import { EventManager } from "core/events";
|
||||
@@ -33,7 +34,7 @@ import { AppAuth } from "../auth/AppAuth";
|
||||
import { AppData } from "../data/AppData";
|
||||
import { AppFlows } from "../flows/AppFlows";
|
||||
import { AppMedia } from "../media/AppMedia";
|
||||
import type { Module, ModuleBuildContext } from "./Module";
|
||||
import type { Module, ModuleBuildContext, ServerEnv } from "./Module";
|
||||
|
||||
export type { ModuleBuildContext };
|
||||
|
||||
@@ -79,6 +80,8 @@ export type ModuleManagerOptions = {
|
||||
onFirstBoot?: () => Promise<void>;
|
||||
// base path for the hono instance
|
||||
basePath?: string;
|
||||
// callback after server was created
|
||||
onServerInit?: (server: Hono<ServerEnv>) => void;
|
||||
// doesn't perform validity checks for given/fetched config
|
||||
trustFetched?: boolean;
|
||||
// runs when initial config provided on a fresh database
|
||||
@@ -124,15 +127,12 @@ export class ModuleManager {
|
||||
__em!: EntityManager<T_INTERNAL_EM>;
|
||||
// ctx for modules
|
||||
em!: EntityManager;
|
||||
server!: Hono;
|
||||
server!: Hono<ServerEnv>;
|
||||
emgr!: EventManager;
|
||||
guard!: Guard;
|
||||
|
||||
private _version: number = 0;
|
||||
private _built = false;
|
||||
private _fetched = false;
|
||||
|
||||
// @todo: keep? not doing anything with it
|
||||
private readonly _booted_with?: "provided" | "partial";
|
||||
|
||||
private logger = new DebugLogger(false);
|
||||
@@ -204,10 +204,13 @@ export class ModuleManager {
|
||||
}
|
||||
|
||||
private rebuildServer() {
|
||||
this.server = new Hono();
|
||||
this.server = new Hono<ServerEnv>();
|
||||
if (this.options?.basePath) {
|
||||
this.server = this.server.basePath(this.options.basePath);
|
||||
}
|
||||
if (this.options?.onServerInit) {
|
||||
this.options.onServerInit(this.server);
|
||||
}
|
||||
|
||||
// @todo: this is a current workaround, controllers must be reworked
|
||||
objectEach(this.modules, (module) => {
|
||||
@@ -547,4 +550,4 @@ export function getDefaultConfig(): ModuleConfigs {
|
||||
});
|
||||
|
||||
return config as any;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
import type { App } from "App";
|
||||
import { type ClassController, isDebug } from "core";
|
||||
import { isDebug } from "core";
|
||||
import { addFlashMessage } from "core/server/flash";
|
||||
import { Hono } from "hono";
|
||||
import { html } from "hono/html";
|
||||
import { Fragment } from "hono/jsx";
|
||||
import { Controller } from "modules/Controller";
|
||||
import * as SystemPermissions from "modules/permissions";
|
||||
|
||||
const htmlBkndContextReplace = "<!-- BKND_CONTEXT -->";
|
||||
@@ -17,11 +17,13 @@ export type AdminControllerOptions = {
|
||||
forceDev?: boolean | { mainPath: string };
|
||||
};
|
||||
|
||||
export class AdminController implements ClassController {
|
||||
export class AdminController extends Controller {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private options: AdminControllerOptions = {}
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get ctx() {
|
||||
return this.app.modules.ctx();
|
||||
@@ -32,19 +34,16 @@ export class AdminController implements ClassController {
|
||||
}
|
||||
|
||||
private withBasePath(route: string = "") {
|
||||
return (this.basepath + route).replace(/\/+$/, "/");
|
||||
return (this.basepath + route).replace(/(?<!:)\/+/g, "/");
|
||||
}
|
||||
|
||||
getController(): Hono<any> {
|
||||
override getController() {
|
||||
const hono = this.create().basePath(this.withBasePath());
|
||||
const auth = this.app.module.auth;
|
||||
const configs = this.app.modules.configs();
|
||||
// if auth is not enabled, authenticator is undefined
|
||||
const auth_enabled = configs.auth.enabled;
|
||||
const hono = new Hono<{
|
||||
Variables: {
|
||||
html: string;
|
||||
};
|
||||
}>().basePath(this.withBasePath());
|
||||
|
||||
const authRoutes = {
|
||||
root: "/",
|
||||
success: configs.auth.cookie.pathSuccess ?? "/",
|
||||
@@ -80,8 +79,7 @@ export class AdminController implements ClassController {
|
||||
return c.redirect(authRoutes.success);
|
||||
}
|
||||
|
||||
const html = c.get("html");
|
||||
return c.html(html);
|
||||
return c.html(c.get("html")!);
|
||||
});
|
||||
|
||||
hono.get(authRoutes.logout, async (c) => {
|
||||
@@ -96,8 +94,7 @@ export class AdminController implements ClassController {
|
||||
return c.redirect(authRoutes.login);
|
||||
}
|
||||
|
||||
const html = c.get("html");
|
||||
return c.html(html);
|
||||
return c.html(c.get("html")!);
|
||||
});
|
||||
|
||||
return hono;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/// <reference types="@cloudflare/workers-types" />
|
||||
|
||||
import type { App } from "App";
|
||||
import type { ClassController } from "core";
|
||||
import { tbValidator as tb } from "core";
|
||||
import { StringEnum, Type, TypeInvalidError } from "core/utils";
|
||||
import { type Context, Hono } from "hono";
|
||||
import type { Context, Hono } from "hono";
|
||||
import { Controller } from "modules/Controller";
|
||||
|
||||
import {
|
||||
MODULE_NAMES,
|
||||
type ModuleConfigs,
|
||||
@@ -27,21 +28,20 @@ export type ConfigUpdateResponse<Key extends ModuleKey = ModuleKey> =
|
||||
| ConfigUpdate<Key>
|
||||
| { success: false; type: "type-invalid" | "error" | "unknown"; error?: any; errors?: any };
|
||||
|
||||
export class SystemController implements ClassController {
|
||||
constructor(private readonly app: App) {}
|
||||
export class SystemController extends Controller {
|
||||
constructor(private readonly app: App) {
|
||||
super();
|
||||
}
|
||||
|
||||
get ctx() {
|
||||
return this.app.modules.ctx();
|
||||
}
|
||||
|
||||
private registerConfigController(client: Hono<any>): void {
|
||||
const hono = new Hono();
|
||||
const hono = this.create();
|
||||
const { permission } = this.middlewares;
|
||||
|
||||
/*hono.use("*", async (c, next) => {
|
||||
//this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead);
|
||||
console.log("perm?", this.ctx.guard.hasPermission(SystemPermissions.configRead));
|
||||
return next();
|
||||
});*/
|
||||
hono.use(permission(SystemPermissions.configRead));
|
||||
|
||||
hono.get(
|
||||
"/:module?",
|
||||
@@ -57,7 +57,6 @@ export class SystemController implements ClassController {
|
||||
const { secrets } = c.req.valid("query");
|
||||
const { module } = c.req.valid("param");
|
||||
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead);
|
||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
||||
|
||||
const config = this.app.toJSON(secrets);
|
||||
@@ -96,6 +95,7 @@ export class SystemController implements ClassController {
|
||||
|
||||
hono.post(
|
||||
"/set/:module",
|
||||
permission(SystemPermissions.configWrite),
|
||||
tb(
|
||||
"query",
|
||||
Type.Object({
|
||||
@@ -107,8 +107,6 @@ export class SystemController implements ClassController {
|
||||
const { force } = c.req.valid("query");
|
||||
const value = await c.req.json();
|
||||
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
||||
|
||||
return await handleConfigUpdateResponse(c, async () => {
|
||||
// you must explicitly set force to override existing values
|
||||
// because omitted values gets removed
|
||||
@@ -131,14 +129,12 @@ export class SystemController implements ClassController {
|
||||
}
|
||||
);
|
||||
|
||||
hono.post("/add/:module/:path", async (c) => {
|
||||
hono.post("/add/:module/:path", permission(SystemPermissions.configWrite), async (c) => {
|
||||
// @todo: require auth (admin)
|
||||
const module = c.req.param("module") as any;
|
||||
const value = await c.req.json();
|
||||
const path = c.req.param("path") as string;
|
||||
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
||||
|
||||
const moduleConfig = this.app.mutateConfig(module);
|
||||
if (moduleConfig.has(path)) {
|
||||
return c.json({ success: false, path, error: "Path already exists" }, { status: 400 });
|
||||
@@ -155,14 +151,12 @@ export class SystemController implements ClassController {
|
||||
});
|
||||
});
|
||||
|
||||
hono.patch("/patch/:module/:path", async (c) => {
|
||||
hono.patch("/patch/:module/:path", permission(SystemPermissions.configWrite), async (c) => {
|
||||
// @todo: require auth (admin)
|
||||
const module = c.req.param("module") as any;
|
||||
const value = await c.req.json();
|
||||
const path = c.req.param("path");
|
||||
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
||||
|
||||
return await handleConfigUpdateResponse(c, async () => {
|
||||
await this.app.mutateConfig(module).patch(path, value);
|
||||
return {
|
||||
@@ -173,14 +167,12 @@ export class SystemController implements ClassController {
|
||||
});
|
||||
});
|
||||
|
||||
hono.put("/overwrite/:module/:path", async (c) => {
|
||||
hono.put("/overwrite/:module/:path", permission(SystemPermissions.configWrite), async (c) => {
|
||||
// @todo: require auth (admin)
|
||||
const module = c.req.param("module") as any;
|
||||
const value = await c.req.json();
|
||||
const path = c.req.param("path");
|
||||
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
||||
|
||||
return await handleConfigUpdateResponse(c, async () => {
|
||||
await this.app.mutateConfig(module).overwrite(path, value);
|
||||
return {
|
||||
@@ -191,13 +183,11 @@ export class SystemController implements ClassController {
|
||||
});
|
||||
});
|
||||
|
||||
hono.delete("/remove/:module/:path", async (c) => {
|
||||
hono.delete("/remove/:module/:path", permission(SystemPermissions.configWrite), async (c) => {
|
||||
// @todo: require auth (admin)
|
||||
const module = c.req.param("module") as any;
|
||||
const path = c.req.param("path")!;
|
||||
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
||||
|
||||
return await handleConfigUpdateResponse(c, async () => {
|
||||
await this.app.mutateConfig(module).remove(path);
|
||||
return {
|
||||
@@ -211,13 +201,15 @@ export class SystemController implements ClassController {
|
||||
client.route("/config", hono);
|
||||
}
|
||||
|
||||
getController(): Hono {
|
||||
const hono = new Hono();
|
||||
override getController() {
|
||||
const hono = this.create();
|
||||
const { permission } = this.middlewares;
|
||||
|
||||
this.registerConfigController(hono);
|
||||
|
||||
hono.get(
|
||||
"/schema/:module?",
|
||||
permission(SystemPermissions.schemaRead),
|
||||
tb(
|
||||
"query",
|
||||
Type.Object({
|
||||
@@ -228,7 +220,7 @@ export class SystemController implements ClassController {
|
||||
async (c) => {
|
||||
const module = c.req.param("module") as ModuleKey | undefined;
|
||||
const { config, secrets } = c.req.valid("query");
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.schemaRead);
|
||||
|
||||
config && this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead);
|
||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
||||
|
||||
@@ -300,8 +292,7 @@ export class SystemController implements ClassController {
|
||||
return c.json({
|
||||
version: this.app.version(),
|
||||
test: 2,
|
||||
// @ts-ignore
|
||||
app: !!c.var.app
|
||||
app: c.get("app").version()
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user