From 3bf92a8c65451d37d0866dfe41f1a9663e142b60 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 10 Jan 2025 20:58:03 +0100 Subject: [PATCH] Refactor asset handling and authentication logic (for node) Updated asset path configuration and server-side logic to standardize asset serving. Introduced `shouldSkipAuth` to bypass authentication for asset requests. Added test coverage for the new asset path handling logic. --- app/__test__/auth/middleware.spec.ts | 9 +++++ app/build.ts | 4 +-- app/src/adapter/index.ts | 11 +++---- app/src/auth/middlewares.ts | 38 ++++++++++++++-------- app/src/core/config.ts | 3 +- app/src/modules/Module.ts | 2 ++ app/src/modules/server/AdminController.tsx | 25 +++++++++++--- 7 files changed, 65 insertions(+), 27 deletions(-) create mode 100644 app/__test__/auth/middleware.spec.ts diff --git a/app/__test__/auth/middleware.spec.ts b/app/__test__/auth/middleware.spec.ts new file mode 100644 index 0000000..77ecfab --- /dev/null +++ b/app/__test__/auth/middleware.spec.ts @@ -0,0 +1,9 @@ +import { describe, expect, it } from "bun:test"; +import { shouldSkipAuth } from "../../src/auth/middlewares"; + +describe("auth middleware", () => { + it("should skip auth on asset paths", () => { + expect(shouldSkipAuth({ req: new Request("http://localhost/assets/test.js") })).toBe(true); + expect(shouldSkipAuth({ req: new Request("http://localhost/") })).toBe(false); + }); +}); diff --git a/app/build.ts b/app/build.ts index 7a9e519..c349986 100644 --- a/app/build.ts +++ b/app/build.ts @@ -58,7 +58,7 @@ const result = await esbuild.build({ sourcemap, entryPoints: ["src/ui/main.tsx"], entryNames: "[dir]/[name]-[hash]", - outdir: "dist/static", + outdir: "dist/static/assets", platform: "browser", bundle: true, splitting: true, @@ -224,4 +224,4 @@ await tsup.build({ await tsup.build({ ...baseConfig("astro") -}); \ No newline at end of file +}); diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index 8b86f0e..19bcdef 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -1,5 +1,6 @@ import type { IncomingMessage } from "node:http"; import { App, type CreateAppConfig, registries } from "bknd"; +import { config as $config } from "core"; import type { MiddlewareHandler } from "hono"; import { StorageLocalAdapter } from "media/storage/adapters/StorageLocalAdapter"; import type { AdminControllerOptions } from "modules/server/AdminController"; @@ -106,12 +107,10 @@ export async function createRuntimeApp( App.Events.AppBuiltEvent, async () => { if (serveStatic) { - if (Array.isArray(serveStatic)) { - const [path, handler] = serveStatic; - app.modules.server.get(path, handler); - } else { - app.modules.server.get("/*", serveStatic); - } + const [path, handler] = Array.isArray(serveStatic) + ? serveStatic + : [$config.server.assets_path + "*", serveStatic]; + app.modules.server.get(path, handler); } await config.onBuilt?.(app); diff --git a/app/src/auth/middlewares.ts b/app/src/auth/middlewares.ts index afefc94..d85d0d8 100644 --- a/app/src/auth/middlewares.ts +++ b/app/src/auth/middlewares.ts @@ -1,4 +1,4 @@ -import type { Permission } from "core"; +import { type Permission, config } from "core"; import type { Context } from "hono"; import { createMiddleware } from "hono/factory"; import type { ServerEnv } from "modules/Module"; @@ -21,27 +21,37 @@ async function resolveAuth(app: ServerEnv["Variables"]["app"], c: Context(async (c, next) => { - // make sure to only register once - if (c.get("auth_registered")) { - return; + if (!shouldSkipAuth) { + // make sure to only register once + if (c.get("auth_registered")) { + return; + } + + await resolveAuth(c.get("app"), c); + c.set("auth_registered", true); } - await resolveAuth(c.get("app"), c); - c.set("auth_registered", true); + await next(); }); export const permission = (...permissions: Permission[]) => createMiddleware(async (c, next) => { - const app = c.get("app"); - if (app) { - const p = Array.isArray(permissions) ? permissions : [permissions]; - const guard = app.modules.ctx().guard; - for (const permission of p) { - guard.throwUnlessGranted(permission); + if (!shouldSkipAuth) { + const app = c.get("app"); + if (app) { + const p = Array.isArray(permissions) ? permissions : [permissions]; + const guard = app.modules.ctx().guard; + for (const permission of p) { + guard.throwUnlessGranted(permission); + } + } else { + console.warn("app not in context, skip permission check"); } - } else { - console.warn("app not in context, skip permission check"); } await next(); diff --git a/app/src/core/config.ts b/app/src/core/config.ts index 9a70a5c..d4e56ba 100644 --- a/app/src/core/config.ts +++ b/app/src/core/config.ts @@ -10,7 +10,8 @@ export interface DB {} export const config = { server: { - default_port: 1337 + default_port: 1337, + assets_path: "/assets/" }, data: { default_primary_field: "id" diff --git a/app/src/modules/Module.ts b/app/src/modules/Module.ts index 21bc5d2..fe08341 100644 --- a/app/src/modules/Module.ts +++ b/app/src/modules/Module.ts @@ -10,7 +10,9 @@ import type { Hono } from "hono"; export type ServerEnv = { Variables: { app: App; + // to prevent resolving auth multiple times auth_resolved: boolean; + // to only register once auth_registered: boolean; html?: string; }; diff --git a/app/src/modules/server/AdminController.tsx b/app/src/modules/server/AdminController.tsx index 9323761..2bf65b2 100644 --- a/app/src/modules/server/AdminController.tsx +++ b/app/src/modules/server/AdminController.tsx @@ -1,7 +1,7 @@ /** @jsxImportSource hono/jsx */ import type { App } from "App"; -import { isDebug } from "core"; +import { config, isDebug } from "core"; import { addFlashMessage } from "core/server/flash"; import { html } from "hono/html"; import { Fragment } from "hono/jsx"; @@ -13,6 +13,7 @@ const htmlBkndContextReplace = ""; // @todo: add migration to remove admin path from config export type AdminControllerOptions = { basepath?: string; + assets_path?: string; html?: string; forceDev?: boolean | { mainPath: string }; }; @@ -20,7 +21,7 @@ export type AdminControllerOptions = { export class AdminController extends Controller { constructor( private readonly app: App, - private options: AdminControllerOptions = {} + private _options: AdminControllerOptions = {} ) { super(); } @@ -29,6 +30,14 @@ export class AdminController extends Controller { return this.app.modules.ctx(); } + get options() { + return { + ...this._options, + basepath: this._options.basepath ?? "/", + assets_path: this._options.assets_path ?? config.server.assets_path + }; + } + get basepath() { return this.options.basepath ?? "/"; } @@ -156,8 +165,16 @@ export class AdminController extends Controller { BKND {isProd ? ( -