mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
refactor: extracted auth as middleware to be added manually to endpoints
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import type { CreateUserPayload } from "auth/AppAuth";
|
||||||
import { Event } from "core/events";
|
import { Event } from "core/events";
|
||||||
import { Connection, type LibSqlCredentials, LibsqlConnection } from "data";
|
import { Connection, type LibSqlCredentials, LibsqlConnection } from "data";
|
||||||
import {
|
import {
|
||||||
@@ -68,6 +69,12 @@ export class App {
|
|||||||
onFirstBoot: async () => {
|
onFirstBoot: async () => {
|
||||||
console.log("[APP] first boot");
|
console.log("[APP] first boot");
|
||||||
this.trigger_first_boot = true;
|
this.trigger_first_boot = true;
|
||||||
|
},
|
||||||
|
onServerInit: async (server) => {
|
||||||
|
server.use(async (c, next) => {
|
||||||
|
c.set("app", this);
|
||||||
|
await next();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.modules.ctx().emgr.registerEvents(AppEvents);
|
this.modules.ctx().emgr.registerEvents(AppEvents);
|
||||||
@@ -87,9 +94,11 @@ export class App {
|
|||||||
//console.log("syncing", syncResult);
|
//console.log("syncing", syncResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { guard, server } = this.modules.ctx();
|
||||||
|
|
||||||
// load system controller
|
// load system controller
|
||||||
this.modules.ctx().guard.registerPermissions(Object.values(SystemPermissions));
|
guard.registerPermissions(Object.values(SystemPermissions));
|
||||||
this.modules.server.route("/api/system", new SystemController(this).getController());
|
server.route("/api/system", new SystemController(this).getController());
|
||||||
|
|
||||||
// load plugins
|
// load plugins
|
||||||
if (this.plugins.length > 0) {
|
if (this.plugins.length > 0) {
|
||||||
@@ -99,8 +108,8 @@ export class App {
|
|||||||
//console.log("emitting built", options);
|
//console.log("emitting built", options);
|
||||||
await this.emgr.emit(new AppBuiltEvent({ app: this }));
|
await this.emgr.emit(new AppBuiltEvent({ app: this }));
|
||||||
|
|
||||||
// not found on any not registered api route
|
|
||||||
this.modules.server.all("/api/*", async (c) => c.notFound());
|
server.all("/api/*", async (c) => c.notFound());
|
||||||
|
|
||||||
if (options?.save) {
|
if (options?.save) {
|
||||||
await this.modules.save();
|
await this.modules.save();
|
||||||
@@ -158,6 +167,10 @@ export class App {
|
|||||||
static create(config: CreateAppConfig) {
|
static create(config: CreateAppConfig) {
|
||||||
return createApp(config);
|
return createApp(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createUser(p: CreateUserPayload) {
|
||||||
|
return this.module.auth.createUser(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createApp(config: CreateAppConfig = {}) {
|
export function createApp(config: CreateAppConfig = {}) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type AuthAction, Authenticator, type ProfileExchange, Role, type Strategy } from "auth";
|
import { type AuthAction, Authenticator, type ProfileExchange, Role, type Strategy } from "auth";
|
||||||
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
||||||
import { Exception, type PrimaryFieldType } from "core";
|
import { type DB, Exception, type PrimaryFieldType } from "core";
|
||||||
import { type Static, secureRandomString, transformObject } from "core/utils";
|
import { type Static, secureRandomString, transformObject } from "core/utils";
|
||||||
import { type Entity, EntityIndex, type EntityManager } from "data";
|
import { type Entity, EntityIndex, type EntityManager } from "data";
|
||||||
import { type FieldSchema, entity, enumm, make, text } from "data/prototype";
|
import { type FieldSchema, entity, enumm, make, text } from "data/prototype";
|
||||||
@@ -17,6 +17,7 @@ declare module "core" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthSchema = Static<typeof authConfigSchema>;
|
type AuthSchema = Static<typeof authConfigSchema>;
|
||||||
|
export type CreateUserPayload = { email: string; password: string; [key: string]: any };
|
||||||
|
|
||||||
export class AppAuth extends Module<typeof authConfigSchema> {
|
export class AppAuth extends Module<typeof authConfigSchema> {
|
||||||
private _authenticator?: Authenticator;
|
private _authenticator?: Authenticator;
|
||||||
@@ -36,8 +37,12 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
|||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get enabled() {
|
||||||
|
return this.config.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
override async build() {
|
override async build() {
|
||||||
if (!this.config.enabled) {
|
if (!this.enabled) {
|
||||||
this.setBuilt();
|
this.setBuilt();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -84,14 +89,6 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
|||||||
return this._controller;
|
return this._controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMiddleware() {
|
|
||||||
if (!this.config.enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AuthController(this).getMiddleware;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSchema() {
|
getSchema() {
|
||||||
return authConfigSchema;
|
return authConfigSchema;
|
||||||
}
|
}
|
||||||
@@ -287,11 +284,7 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async createUser({
|
async createUser({ email, password, ...additional }: CreateUserPayload): Promise<DB["users"]> {
|
||||||
email,
|
|
||||||
password,
|
|
||||||
...additional
|
|
||||||
}: { email: string; password: string; [key: string]: any }) {
|
|
||||||
const strategy = "password";
|
const strategy = "password";
|
||||||
const pw = this.authenticator.strategy(strategy) as PasswordStrategy;
|
const pw = this.authenticator.strategy(strategy) as PasswordStrategy;
|
||||||
const strategy_value = await pw.hash(password);
|
const strategy_value = await pw.hash(password);
|
||||||
|
|||||||
@@ -1,42 +1,17 @@
|
|||||||
import type { AppAuth } from "auth";
|
import type { AppAuth } from "auth";
|
||||||
import { type ClassController, isDebug } from "core";
|
import { Controller } from "modules/Controller";
|
||||||
import { Hono, type MiddlewareHandler } from "hono";
|
|
||||||
|
|
||||||
export class AuthController implements ClassController {
|
export class AuthController extends Controller {
|
||||||
constructor(private auth: AppAuth) {}
|
constructor(private auth: AppAuth) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
get guard() {
|
get guard() {
|
||||||
return this.auth.ctx.guard;
|
return this.auth.ctx.guard;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMiddleware: MiddlewareHandler = async (c, next) => {
|
override getController() {
|
||||||
// @todo: ONLY HOTFIX
|
const hono = this.create();
|
||||||
// middlewares are added for all routes are registered. But we need to make sure that
|
|
||||||
// only HTML/JSON routes are adding a cookie to the response. Config updates might
|
|
||||||
// also use an extension "syntax", e.g. /api/system/patch/data/entities.posts
|
|
||||||
// This middleware should be extracted and added by each Controller individually,
|
|
||||||
// but it requires access to the auth secret.
|
|
||||||
// Note: This doesn't mean endpoints aren't protected, just the cookie is not set.
|
|
||||||
const url = new URL(c.req.url);
|
|
||||||
const last = url.pathname.split("/")?.pop();
|
|
||||||
const ext = last?.includes(".") ? last.split(".")?.pop() : undefined;
|
|
||||||
if (
|
|
||||||
!this.auth.authenticator.isJsonRequest(c) &&
|
|
||||||
["GET", "HEAD", "OPTIONS"].includes(c.req.method) &&
|
|
||||||
ext &&
|
|
||||||
["js", "css", "png", "jpg", "jpeg", "svg", "ico"].includes(ext)
|
|
||||||
) {
|
|
||||||
isDebug() && console.log("Skipping auth", { ext }, url.pathname);
|
|
||||||
} else {
|
|
||||||
const user = await this.auth.authenticator.resolveAuthFromRequest(c);
|
|
||||||
this.auth.ctx.guard.setUserContext(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
await next();
|
|
||||||
};
|
|
||||||
|
|
||||||
getController(): Hono<any> {
|
|
||||||
const hono = new Hono();
|
|
||||||
const strategies = this.auth.authenticator.getStrategies();
|
const strategies = this.auth.authenticator.getStrategies();
|
||||||
|
|
||||||
for (const [name, strategy] of Object.entries(strategies)) {
|
for (const [name, strategy] of Object.entries(strategies)) {
|
||||||
|
|||||||
38
app/src/auth/middlewares.ts
Normal file
38
app/src/auth/middlewares.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import type { Permission } from "core";
|
||||||
|
import type { Context } from "hono";
|
||||||
|
import { createMiddleware } from "hono/factory";
|
||||||
|
import type { ServerEnv } from "modules/Module";
|
||||||
|
|
||||||
|
async function resolveAuth(app: ServerEnv["Variables"]["app"], c: Context<ServerEnv>) {
|
||||||
|
const resolved = c.get("auth_resolved") ?? false;
|
||||||
|
if (resolved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!app.module.auth.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authenticator = app.module.auth.authenticator;
|
||||||
|
const guard = app.modules.ctx().guard;
|
||||||
|
|
||||||
|
guard.setUserContext(await authenticator.resolveAuthFromRequest(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const auth = createMiddleware<ServerEnv>(async (c, next) => {
|
||||||
|
await resolveAuth(c.get("app"), c);
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
export const permission = (...permissions: Permission[]) =>
|
||||||
|
createMiddleware<ServerEnv>(async (c, next) => {
|
||||||
|
const app = c.get("app");
|
||||||
|
await resolveAuth(app, c);
|
||||||
|
|
||||||
|
const p = Array.isArray(permissions) ? permissions : [permissions];
|
||||||
|
const guard = app.modules.ctx().guard;
|
||||||
|
for (const permission of p) {
|
||||||
|
guard.throwUnlessGranted(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
});
|
||||||
@@ -35,9 +35,11 @@ async function action(action: "create" | "update", options: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function create(app: App, options: any) {
|
async function create(app: App, options: any) {
|
||||||
const config = app.module.auth.toJSON(true);
|
|
||||||
const strategy = app.module.auth.authenticator.strategy("password") as PasswordStrategy;
|
const strategy = app.module.auth.authenticator.strategy("password") as PasswordStrategy;
|
||||||
const users_entity = config.entity_name as "users";
|
|
||||||
|
if (!strategy) {
|
||||||
|
throw new Error("Password strategy not configured");
|
||||||
|
}
|
||||||
|
|
||||||
const email = await $text({
|
const email = await $text({
|
||||||
message: "Enter email",
|
message: "Enter email",
|
||||||
@@ -65,16 +67,11 @@ async function create(app: App, options: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mutator = app.modules.ctx().em.mutator(users_entity);
|
const created = await app.createUser({
|
||||||
mutator.__unstable_toggleSystemEntityCreation(false);
|
|
||||||
const res = await mutator.insertOne({
|
|
||||||
email,
|
email,
|
||||||
strategy: "password",
|
password: await strategy.hash(password as string)
|
||||||
strategy_value: await strategy.hash(password as string)
|
})
|
||||||
});
|
console.log("Created:", created);
|
||||||
mutator.__unstable_toggleSystemEntityCreation(true);
|
|
||||||
|
|
||||||
console.log("Created:", res.data);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error", e);
|
console.error("Error", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,26 @@
|
|||||||
import { type ClassController, isDebug, tbValidator as tb } from "core";
|
import { isDebug, tbValidator as tb } from "core";
|
||||||
import { StringEnum, Type, objectCleanEmpty, objectTransform } from "core/utils";
|
import { StringEnum, Type } from "core/utils";
|
||||||
import {
|
import {
|
||||||
DataPermissions,
|
DataPermissions,
|
||||||
type EntityData,
|
type EntityData,
|
||||||
type EntityManager,
|
type EntityManager,
|
||||||
FieldClassMap,
|
|
||||||
type MutatorResponse,
|
type MutatorResponse,
|
||||||
PrimaryField,
|
|
||||||
type RepoQuery,
|
type RepoQuery,
|
||||||
type RepositoryResponse,
|
type RepositoryResponse,
|
||||||
TextField,
|
|
||||||
querySchema
|
querySchema
|
||||||
} from "data";
|
} from "data";
|
||||||
import { Hono } from "hono";
|
|
||||||
import type { Handler } from "hono/types";
|
import type { Handler } from "hono/types";
|
||||||
import type { ModuleBuildContext } from "modules";
|
import type { ModuleBuildContext } from "modules";
|
||||||
|
import { Controller } from "modules/Controller";
|
||||||
import * as SystemPermissions from "modules/permissions";
|
import * as SystemPermissions from "modules/permissions";
|
||||||
import { type AppDataConfig, FIELDS } from "../data-schema";
|
import type { AppDataConfig } from "../data-schema";
|
||||||
|
|
||||||
export class DataController implements ClassController {
|
export class DataController extends Controller {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly ctx: ModuleBuildContext,
|
private readonly ctx: ModuleBuildContext,
|
||||||
private readonly config: AppDataConfig
|
private readonly config: AppDataConfig
|
||||||
) {
|
) {
|
||||||
/*console.log(
|
super();
|
||||||
"data controller",
|
|
||||||
this.em.entities.map((e) => e.name)
|
|
||||||
);*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get em(): EntityManager<any> {
|
get em(): EntityManager<any> {
|
||||||
@@ -74,8 +68,9 @@ export class DataController implements ClassController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getController(): Hono<any> {
|
override getController() {
|
||||||
const hono = new Hono();
|
const hono = this.create();
|
||||||
|
const { permission } = this.middlewares;
|
||||||
const definedEntities = this.em.entities.map((e) => e.name);
|
const definedEntities = this.em.entities.map((e) => e.name);
|
||||||
const tbNumber = Type.Transform(Type.String({ pattern: "^[1-9][0-9]{0,}$" }))
|
const tbNumber = Type.Transform(Type.String({ pattern: "^[1-9][0-9]{0,}$" }))
|
||||||
.Decode(Number.parseInt)
|
.Decode(Number.parseInt)
|
||||||
@@ -89,10 +84,7 @@ export class DataController implements ClassController {
|
|||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
|
|
||||||
hono.use("*", async (c, next) => {
|
hono.use("*", permission(SystemPermissions.accessApi));
|
||||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.accessApi);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// info
|
// info
|
||||||
hono.get(
|
hono.get(
|
||||||
@@ -104,9 +96,7 @@ export class DataController implements ClassController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// sync endpoint
|
// sync endpoint
|
||||||
hono.get("/sync", async (c) => {
|
hono.get("/sync", permission(DataPermissions.databaseSync), async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.databaseSync);
|
|
||||||
|
|
||||||
const force = c.req.query("force") === "1";
|
const force = c.req.query("force") === "1";
|
||||||
const drop = c.req.query("drop") === "1";
|
const drop = c.req.query("drop") === "1";
|
||||||
//console.log("force", force);
|
//console.log("force", force);
|
||||||
@@ -126,10 +116,9 @@ export class DataController implements ClassController {
|
|||||||
// fn: count
|
// fn: count
|
||||||
.post(
|
.post(
|
||||||
"/:entity/fn/count",
|
"/:entity/fn/count",
|
||||||
|
permission(DataPermissions.entityRead),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
tb("param", Type.Object({ entity: Type.String() })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
|
||||||
|
|
||||||
const { entity } = c.req.valid("param");
|
const { entity } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
@@ -143,10 +132,9 @@ export class DataController implements ClassController {
|
|||||||
// fn: exists
|
// fn: exists
|
||||||
.post(
|
.post(
|
||||||
"/:entity/fn/exists",
|
"/:entity/fn/exists",
|
||||||
|
permission(DataPermissions.entityRead),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
tb("param", Type.Object({ entity: Type.String() })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
|
||||||
|
|
||||||
const { entity } = c.req.valid("param");
|
const { entity } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
@@ -163,8 +151,7 @@ export class DataController implements ClassController {
|
|||||||
*/
|
*/
|
||||||
hono
|
hono
|
||||||
// read entity schema
|
// read entity schema
|
||||||
.get("/schema.json", async (c) => {
|
.get("/schema.json", permission(DataPermissions.entityRead), async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
|
||||||
const $id = `${this.config.basepath}/schema.json`;
|
const $id = `${this.config.basepath}/schema.json`;
|
||||||
const schemas = Object.fromEntries(
|
const schemas = Object.fromEntries(
|
||||||
this.em.entities.map((e) => [
|
this.em.entities.map((e) => [
|
||||||
@@ -183,6 +170,7 @@ export class DataController implements ClassController {
|
|||||||
// read schema
|
// read schema
|
||||||
.get(
|
.get(
|
||||||
"/schemas/:entity/:context?",
|
"/schemas/:entity/:context?",
|
||||||
|
permission(DataPermissions.entityRead),
|
||||||
tb(
|
tb(
|
||||||
"param",
|
"param",
|
||||||
Type.Object({
|
Type.Object({
|
||||||
@@ -191,8 +179,6 @@ export class DataController implements ClassController {
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
|
||||||
|
|
||||||
//console.log("request", c.req.raw);
|
//console.log("request", c.req.raw);
|
||||||
const { entity, context } = c.req.param();
|
const { entity, context } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
@@ -216,11 +202,10 @@ export class DataController implements ClassController {
|
|||||||
// read many
|
// read many
|
||||||
.get(
|
.get(
|
||||||
"/:entity",
|
"/:entity",
|
||||||
|
permission(DataPermissions.entityRead),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
tb("param", Type.Object({ entity: Type.String() })),
|
||||||
tb("query", querySchema),
|
tb("query", querySchema),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
|
||||||
|
|
||||||
//console.log("request", c.req.raw);
|
//console.log("request", c.req.raw);
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
@@ -238,6 +223,7 @@ export class DataController implements ClassController {
|
|||||||
// read one
|
// read one
|
||||||
.get(
|
.get(
|
||||||
"/:entity/:id",
|
"/:entity/:id",
|
||||||
|
permission(DataPermissions.entityRead),
|
||||||
tb(
|
tb(
|
||||||
"param",
|
"param",
|
||||||
Type.Object({
|
Type.Object({
|
||||||
@@ -246,11 +232,7 @@ export class DataController implements ClassController {
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
tb("query", querySchema),
|
tb("query", querySchema),
|
||||||
/*zValidator("param", z.object({ entity: z.string(), id: z.coerce.number() })),
|
|
||||||
zValidator("query", repoQuerySchema),*/
|
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
|
||||||
|
|
||||||
const { entity, id } = c.req.param();
|
const { entity, id } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
@@ -264,6 +246,7 @@ export class DataController implements ClassController {
|
|||||||
// read many by reference
|
// read many by reference
|
||||||
.get(
|
.get(
|
||||||
"/:entity/:id/:reference",
|
"/:entity/:id/:reference",
|
||||||
|
permission(DataPermissions.entityRead),
|
||||||
tb(
|
tb(
|
||||||
"param",
|
"param",
|
||||||
Type.Object({
|
Type.Object({
|
||||||
@@ -274,8 +257,6 @@ export class DataController implements ClassController {
|
|||||||
),
|
),
|
||||||
tb("query", querySchema),
|
tb("query", querySchema),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
|
||||||
|
|
||||||
const { entity, id, reference } = c.req.param();
|
const { entity, id, reference } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
@@ -292,11 +273,10 @@ export class DataController implements ClassController {
|
|||||||
// func query
|
// func query
|
||||||
.post(
|
.post(
|
||||||
"/:entity/query",
|
"/:entity/query",
|
||||||
|
permission(DataPermissions.entityRead),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
tb("param", Type.Object({ entity: Type.String() })),
|
||||||
tb("json", querySchema),
|
tb("json", querySchema),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
|
||||||
|
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
@@ -314,9 +294,11 @@ export class DataController implements ClassController {
|
|||||||
*/
|
*/
|
||||||
// insert one
|
// insert one
|
||||||
hono
|
hono
|
||||||
.post("/:entity", tb("param", Type.Object({ entity: Type.String() })), async (c) => {
|
.post(
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityCreate);
|
"/:entity",
|
||||||
|
permission(DataPermissions.entityCreate),
|
||||||
|
tb("param", Type.Object({ entity: Type.String() })),
|
||||||
|
async (c) => {
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
@@ -325,14 +307,14 @@ export class DataController implements ClassController {
|
|||||||
const result = await this.em.mutator(entity).insertOne(body);
|
const result = await this.em.mutator(entity).insertOne(body);
|
||||||
|
|
||||||
return c.json(this.mutatorResult(result), 201);
|
return c.json(this.mutatorResult(result), 201);
|
||||||
})
|
}
|
||||||
|
)
|
||||||
// update one
|
// update one
|
||||||
.patch(
|
.patch(
|
||||||
"/:entity/:id",
|
"/:entity/:id",
|
||||||
|
permission(DataPermissions.entityUpdate),
|
||||||
tb("param", Type.Object({ entity: Type.String(), id: tbNumber })),
|
tb("param", Type.Object({ entity: Type.String(), id: tbNumber })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityUpdate);
|
|
||||||
|
|
||||||
const { entity, id } = c.req.param();
|
const { entity, id } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
@@ -346,6 +328,8 @@ export class DataController implements ClassController {
|
|||||||
// delete one
|
// delete one
|
||||||
.delete(
|
.delete(
|
||||||
"/:entity/:id",
|
"/:entity/:id",
|
||||||
|
|
||||||
|
permission(DataPermissions.entityDelete),
|
||||||
tb("param", Type.Object({ entity: Type.String(), id: tbNumber })),
|
tb("param", Type.Object({ entity: Type.String(), id: tbNumber })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityDelete);
|
this.guard.throwUnlessGranted(DataPermissions.entityDelete);
|
||||||
@@ -363,11 +347,10 @@ export class DataController implements ClassController {
|
|||||||
// delete many
|
// delete many
|
||||||
.delete(
|
.delete(
|
||||||
"/:entity",
|
"/:entity",
|
||||||
|
permission(DataPermissions.entityDelete),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
tb("param", Type.Object({ entity: Type.String() })),
|
||||||
tb("json", querySchema.properties.where),
|
tb("json", querySchema.properties.where),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityDelete);
|
|
||||||
|
|
||||||
//console.log("request", c.req.raw);
|
//console.log("request", c.req.raw);
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { type ClassController, tbValidator as tb } from "core";
|
import { tbValidator as tb } from "core";
|
||||||
import { Type } from "core/utils";
|
import { Type } from "core/utils";
|
||||||
import { Hono } from "hono";
|
|
||||||
import { bodyLimit } from "hono/body-limit";
|
import { bodyLimit } from "hono/body-limit";
|
||||||
import type { StorageAdapter } from "media";
|
import type { StorageAdapter } from "media";
|
||||||
import { StorageEvents } from "media";
|
import { StorageEvents } from "media";
|
||||||
import { getRandomizedFilename } from "media";
|
import { getRandomizedFilename } from "media";
|
||||||
|
import { Controller } from "modules/Controller";
|
||||||
import type { AppMedia } from "../AppMedia";
|
import type { AppMedia } from "../AppMedia";
|
||||||
import { MediaField } from "../MediaField";
|
import { MediaField } from "../MediaField";
|
||||||
|
|
||||||
@@ -12,8 +12,10 @@ const booleanLike = Type.Transform(Type.String())
|
|||||||
.Decode((v) => v === "1")
|
.Decode((v) => v === "1")
|
||||||
.Encode((v) => (v ? "1" : "0"));
|
.Encode((v) => (v ? "1" : "0"));
|
||||||
|
|
||||||
export class MediaController implements ClassController {
|
export class MediaController extends Controller {
|
||||||
constructor(private readonly media: AppMedia) {}
|
constructor(private readonly media: AppMedia) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
private getStorageAdapter(): StorageAdapter {
|
private getStorageAdapter(): StorageAdapter {
|
||||||
return this.getStorage().getAdapter();
|
return this.getStorage().getAdapter();
|
||||||
@@ -23,11 +25,11 @@ export class MediaController implements ClassController {
|
|||||||
return this.media.storage;
|
return this.media.storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
getController(): Hono<any> {
|
override getController() {
|
||||||
// @todo: multiple providers?
|
// @todo: multiple providers?
|
||||||
// @todo: implement range requests
|
// @todo: implement range requests
|
||||||
|
|
||||||
const hono = new Hono();
|
const hono = this.create();
|
||||||
|
|
||||||
// get files list (temporary)
|
// get files list (temporary)
|
||||||
hono.get("/files", async (c) => {
|
hono.get("/files", async (c) => {
|
||||||
|
|||||||
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 type { Guard } from "auth";
|
||||||
import { SchemaObject } from "core";
|
import { SchemaObject } from "core";
|
||||||
import type { EventManager } from "core/events";
|
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 { Connection, EntityManager } from "data";
|
||||||
import type { Hono } from "hono";
|
import type { Hono } from "hono";
|
||||||
|
|
||||||
|
export type ServerEnv = {
|
||||||
|
Variables: {
|
||||||
|
app: App;
|
||||||
|
auth_resolved: boolean;
|
||||||
|
html?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type ModuleBuildContext = {
|
export type ModuleBuildContext = {
|
||||||
connection: Connection;
|
connection: Connection;
|
||||||
server: Hono<any>;
|
server: Hono<ServerEnv>;
|
||||||
em: EntityManager;
|
em: EntityManager;
|
||||||
emgr: EventManager<any>;
|
emgr: EventManager<any>;
|
||||||
guard: Guard;
|
guard: Guard;
|
||||||
@@ -78,6 +87,10 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
|
|||||||
return this._schema;
|
return this._schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMiddleware() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
get ctx() {
|
get ctx() {
|
||||||
if (!this._ctx) {
|
if (!this._ctx) {
|
||||||
throw new Error("Context not set");
|
throw new Error("Context not set");
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { App } from "App";
|
||||||
import { Guard } from "auth";
|
import { Guard } from "auth";
|
||||||
import { BkndError, DebugLogger } from "core";
|
import { BkndError, DebugLogger } from "core";
|
||||||
import { EventManager } from "core/events";
|
import { EventManager } from "core/events";
|
||||||
@@ -33,7 +34,7 @@ import { AppAuth } from "../auth/AppAuth";
|
|||||||
import { AppData } from "../data/AppData";
|
import { AppData } from "../data/AppData";
|
||||||
import { AppFlows } from "../flows/AppFlows";
|
import { AppFlows } from "../flows/AppFlows";
|
||||||
import { AppMedia } from "../media/AppMedia";
|
import { AppMedia } from "../media/AppMedia";
|
||||||
import type { Module, ModuleBuildContext } from "./Module";
|
import type { Module, ModuleBuildContext, ServerEnv } from "./Module";
|
||||||
|
|
||||||
export type { ModuleBuildContext };
|
export type { ModuleBuildContext };
|
||||||
|
|
||||||
@@ -79,6 +80,8 @@ export type ModuleManagerOptions = {
|
|||||||
onFirstBoot?: () => Promise<void>;
|
onFirstBoot?: () => Promise<void>;
|
||||||
// base path for the hono instance
|
// base path for the hono instance
|
||||||
basePath?: string;
|
basePath?: string;
|
||||||
|
// callback after server was created
|
||||||
|
onServerInit?: (server: Hono<ServerEnv>) => void;
|
||||||
// doesn't perform validity checks for given/fetched config
|
// doesn't perform validity checks for given/fetched config
|
||||||
trustFetched?: boolean;
|
trustFetched?: boolean;
|
||||||
// runs when initial config provided on a fresh database
|
// runs when initial config provided on a fresh database
|
||||||
@@ -124,15 +127,12 @@ export class ModuleManager {
|
|||||||
__em!: EntityManager<T_INTERNAL_EM>;
|
__em!: EntityManager<T_INTERNAL_EM>;
|
||||||
// ctx for modules
|
// ctx for modules
|
||||||
em!: EntityManager;
|
em!: EntityManager;
|
||||||
server!: Hono;
|
server!: Hono<ServerEnv>;
|
||||||
emgr!: EventManager;
|
emgr!: EventManager;
|
||||||
guard!: Guard;
|
guard!: Guard;
|
||||||
|
|
||||||
private _version: number = 0;
|
private _version: number = 0;
|
||||||
private _built = false;
|
private _built = false;
|
||||||
private _fetched = false;
|
|
||||||
|
|
||||||
// @todo: keep? not doing anything with it
|
|
||||||
private readonly _booted_with?: "provided" | "partial";
|
private readonly _booted_with?: "provided" | "partial";
|
||||||
|
|
||||||
private logger = new DebugLogger(false);
|
private logger = new DebugLogger(false);
|
||||||
@@ -204,10 +204,13 @@ export class ModuleManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private rebuildServer() {
|
private rebuildServer() {
|
||||||
this.server = new Hono();
|
this.server = new Hono<ServerEnv>();
|
||||||
if (this.options?.basePath) {
|
if (this.options?.basePath) {
|
||||||
this.server = this.server.basePath(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
|
// @todo: this is a current workaround, controllers must be reworked
|
||||||
objectEach(this.modules, (module) => {
|
objectEach(this.modules, (module) => {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/** @jsxImportSource hono/jsx */
|
/** @jsxImportSource hono/jsx */
|
||||||
|
|
||||||
import type { App } from "App";
|
import type { App } from "App";
|
||||||
import { type ClassController, isDebug } from "core";
|
import { isDebug } from "core";
|
||||||
import { addFlashMessage } from "core/server/flash";
|
import { addFlashMessage } from "core/server/flash";
|
||||||
import { Hono } from "hono";
|
|
||||||
import { html } from "hono/html";
|
import { html } from "hono/html";
|
||||||
import { Fragment } from "hono/jsx";
|
import { Fragment } from "hono/jsx";
|
||||||
|
import { Controller } from "modules/Controller";
|
||||||
import * as SystemPermissions from "modules/permissions";
|
import * as SystemPermissions from "modules/permissions";
|
||||||
|
|
||||||
const htmlBkndContextReplace = "<!-- BKND_CONTEXT -->";
|
const htmlBkndContextReplace = "<!-- BKND_CONTEXT -->";
|
||||||
@@ -17,11 +17,13 @@ export type AdminControllerOptions = {
|
|||||||
forceDev?: boolean | { mainPath: string };
|
forceDev?: boolean | { mainPath: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AdminController implements ClassController {
|
export class AdminController extends Controller {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly app: App,
|
private readonly app: App,
|
||||||
private options: AdminControllerOptions = {}
|
private options: AdminControllerOptions = {}
|
||||||
) {}
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
get ctx() {
|
get ctx() {
|
||||||
return this.app.modules.ctx();
|
return this.app.modules.ctx();
|
||||||
@@ -32,19 +34,16 @@ export class AdminController implements ClassController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private withBasePath(route: string = "") {
|
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 auth = this.app.module.auth;
|
||||||
const configs = this.app.modules.configs();
|
const configs = this.app.modules.configs();
|
||||||
// if auth is not enabled, authenticator is undefined
|
// if auth is not enabled, authenticator is undefined
|
||||||
const auth_enabled = configs.auth.enabled;
|
const auth_enabled = configs.auth.enabled;
|
||||||
const hono = new Hono<{
|
|
||||||
Variables: {
|
|
||||||
html: string;
|
|
||||||
};
|
|
||||||
}>().basePath(this.withBasePath());
|
|
||||||
const authRoutes = {
|
const authRoutes = {
|
||||||
root: "/",
|
root: "/",
|
||||||
success: configs.auth.cookie.pathSuccess ?? "/",
|
success: configs.auth.cookie.pathSuccess ?? "/",
|
||||||
@@ -80,8 +79,7 @@ export class AdminController implements ClassController {
|
|||||||
return c.redirect(authRoutes.success);
|
return c.redirect(authRoutes.success);
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = c.get("html");
|
return c.html(c.get("html")!);
|
||||||
return c.html(html);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hono.get(authRoutes.logout, async (c) => {
|
hono.get(authRoutes.logout, async (c) => {
|
||||||
@@ -96,8 +94,7 @@ export class AdminController implements ClassController {
|
|||||||
return c.redirect(authRoutes.login);
|
return c.redirect(authRoutes.login);
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = c.get("html");
|
return c.html(c.get("html")!);
|
||||||
return c.html(html);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return hono;
|
return hono;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
/// <reference types="@cloudflare/workers-types" />
|
/// <reference types="@cloudflare/workers-types" />
|
||||||
|
|
||||||
import type { App } from "App";
|
import type { App } from "App";
|
||||||
import type { ClassController } from "core";
|
|
||||||
import { tbValidator as tb } from "core";
|
import { tbValidator as tb } from "core";
|
||||||
import { StringEnum, Type, TypeInvalidError } from "core/utils";
|
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 {
|
import {
|
||||||
MODULE_NAMES,
|
MODULE_NAMES,
|
||||||
type ModuleConfigs,
|
type ModuleConfigs,
|
||||||
@@ -27,21 +28,20 @@ export type ConfigUpdateResponse<Key extends ModuleKey = ModuleKey> =
|
|||||||
| ConfigUpdate<Key>
|
| ConfigUpdate<Key>
|
||||||
| { success: false; type: "type-invalid" | "error" | "unknown"; error?: any; errors?: any };
|
| { success: false; type: "type-invalid" | "error" | "unknown"; error?: any; errors?: any };
|
||||||
|
|
||||||
export class SystemController implements ClassController {
|
export class SystemController extends Controller {
|
||||||
constructor(private readonly app: App) {}
|
constructor(private readonly app: App) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
get ctx() {
|
get ctx() {
|
||||||
return this.app.modules.ctx();
|
return this.app.modules.ctx();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerConfigController(client: Hono<any>): void {
|
private registerConfigController(client: Hono<any>): void {
|
||||||
const hono = new Hono();
|
const hono = this.create();
|
||||||
|
const { permission } = this.middlewares;
|
||||||
|
|
||||||
/*hono.use("*", async (c, next) => {
|
hono.use(permission(SystemPermissions.configRead));
|
||||||
//this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead);
|
|
||||||
console.log("perm?", this.ctx.guard.hasPermission(SystemPermissions.configRead));
|
|
||||||
return next();
|
|
||||||
});*/
|
|
||||||
|
|
||||||
hono.get(
|
hono.get(
|
||||||
"/:module?",
|
"/:module?",
|
||||||
@@ -57,7 +57,6 @@ export class SystemController implements ClassController {
|
|||||||
const { secrets } = c.req.valid("query");
|
const { secrets } = c.req.valid("query");
|
||||||
const { module } = c.req.valid("param");
|
const { module } = c.req.valid("param");
|
||||||
|
|
||||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead);
|
|
||||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
||||||
|
|
||||||
const config = this.app.toJSON(secrets);
|
const config = this.app.toJSON(secrets);
|
||||||
@@ -96,6 +95,7 @@ export class SystemController implements ClassController {
|
|||||||
|
|
||||||
hono.post(
|
hono.post(
|
||||||
"/set/:module",
|
"/set/:module",
|
||||||
|
permission(SystemPermissions.configWrite),
|
||||||
tb(
|
tb(
|
||||||
"query",
|
"query",
|
||||||
Type.Object({
|
Type.Object({
|
||||||
@@ -107,8 +107,6 @@ export class SystemController implements ClassController {
|
|||||||
const { force } = c.req.valid("query");
|
const { force } = c.req.valid("query");
|
||||||
const value = await c.req.json();
|
const value = await c.req.json();
|
||||||
|
|
||||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
|
||||||
|
|
||||||
return await handleConfigUpdateResponse(c, async () => {
|
return await handleConfigUpdateResponse(c, async () => {
|
||||||
// you must explicitly set force to override existing values
|
// you must explicitly set force to override existing values
|
||||||
// because omitted values gets removed
|
// 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)
|
// @todo: require auth (admin)
|
||||||
const module = c.req.param("module") as any;
|
const module = c.req.param("module") as any;
|
||||||
const value = await c.req.json();
|
const value = await c.req.json();
|
||||||
const path = c.req.param("path") as string;
|
const path = c.req.param("path") as string;
|
||||||
|
|
||||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
|
||||||
|
|
||||||
const moduleConfig = this.app.mutateConfig(module);
|
const moduleConfig = this.app.mutateConfig(module);
|
||||||
if (moduleConfig.has(path)) {
|
if (moduleConfig.has(path)) {
|
||||||
return c.json({ success: false, path, error: "Path already exists" }, { status: 400 });
|
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)
|
// @todo: require auth (admin)
|
||||||
const module = c.req.param("module") as any;
|
const module = c.req.param("module") as any;
|
||||||
const value = await c.req.json();
|
const value = await c.req.json();
|
||||||
const path = c.req.param("path");
|
const path = c.req.param("path");
|
||||||
|
|
||||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
|
||||||
|
|
||||||
return await handleConfigUpdateResponse(c, async () => {
|
return await handleConfigUpdateResponse(c, async () => {
|
||||||
await this.app.mutateConfig(module).patch(path, value);
|
await this.app.mutateConfig(module).patch(path, value);
|
||||||
return {
|
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)
|
// @todo: require auth (admin)
|
||||||
const module = c.req.param("module") as any;
|
const module = c.req.param("module") as any;
|
||||||
const value = await c.req.json();
|
const value = await c.req.json();
|
||||||
const path = c.req.param("path");
|
const path = c.req.param("path");
|
||||||
|
|
||||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
|
||||||
|
|
||||||
return await handleConfigUpdateResponse(c, async () => {
|
return await handleConfigUpdateResponse(c, async () => {
|
||||||
await this.app.mutateConfig(module).overwrite(path, value);
|
await this.app.mutateConfig(module).overwrite(path, value);
|
||||||
return {
|
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)
|
// @todo: require auth (admin)
|
||||||
const module = c.req.param("module") as any;
|
const module = c.req.param("module") as any;
|
||||||
const path = c.req.param("path")!;
|
const path = c.req.param("path")!;
|
||||||
|
|
||||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.configWrite);
|
|
||||||
|
|
||||||
return await handleConfigUpdateResponse(c, async () => {
|
return await handleConfigUpdateResponse(c, async () => {
|
||||||
await this.app.mutateConfig(module).remove(path);
|
await this.app.mutateConfig(module).remove(path);
|
||||||
return {
|
return {
|
||||||
@@ -211,13 +201,15 @@ export class SystemController implements ClassController {
|
|||||||
client.route("/config", hono);
|
client.route("/config", hono);
|
||||||
}
|
}
|
||||||
|
|
||||||
getController(): Hono {
|
override getController() {
|
||||||
const hono = new Hono();
|
const hono = this.create();
|
||||||
|
const { permission } = this.middlewares;
|
||||||
|
|
||||||
this.registerConfigController(hono);
|
this.registerConfigController(hono);
|
||||||
|
|
||||||
hono.get(
|
hono.get(
|
||||||
"/schema/:module?",
|
"/schema/:module?",
|
||||||
|
permission(SystemPermissions.schemaRead),
|
||||||
tb(
|
tb(
|
||||||
"query",
|
"query",
|
||||||
Type.Object({
|
Type.Object({
|
||||||
@@ -228,7 +220,7 @@ export class SystemController implements ClassController {
|
|||||||
async (c) => {
|
async (c) => {
|
||||||
const module = c.req.param("module") as ModuleKey | undefined;
|
const module = c.req.param("module") as ModuleKey | undefined;
|
||||||
const { config, secrets } = c.req.valid("query");
|
const { config, secrets } = c.req.valid("query");
|
||||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.schemaRead);
|
|
||||||
config && this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead);
|
config && this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead);
|
||||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
||||||
|
|
||||||
@@ -300,8 +292,7 @@ export class SystemController implements ClassController {
|
|||||||
return c.json({
|
return c.json({
|
||||||
version: this.app.version(),
|
version: this.app.version(),
|
||||||
test: 2,
|
test: 2,
|
||||||
// @ts-ignore
|
app: c.get("app").version()
|
||||||
app: !!c.var.app
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export function AuthIndex() {
|
|||||||
config: { roles, strategies, entity_name, enabled }
|
config: { roles, strategies, entity_name, enabled }
|
||||||
} = useBkndAuth();
|
} = useBkndAuth();
|
||||||
const users_entity = entity_name;
|
const users_entity = entity_name;
|
||||||
const $q = useApiQuery((api) => api.data.count(users_entity));
|
const $q = useApiQuery((api) => api.data.count(users_entity), {
|
||||||
|
enabled
|
||||||
|
});
|
||||||
const usersTotal = $q.data?.count ?? 0;
|
const usersTotal = $q.data?.count ?? 0;
|
||||||
const rolesTotal = Object.keys(roles ?? {}).length ?? 0;
|
const rolesTotal = Object.keys(roles ?? {}).length ?? 0;
|
||||||
const strategiesTotal = Object.keys(strategies ?? {}).length ?? 0;
|
const strategiesTotal = Object.keys(strategies ?? {}).length ?? 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user