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.
This commit is contained in:
dswbx
2025-01-10 20:58:03 +01:00
parent 87e07570d4
commit 3bf92a8c65
7 changed files with 65 additions and 27 deletions

View File

@@ -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);
});
});

View File

@@ -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,

View File

@@ -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<Env = any>(
App.Events.AppBuiltEvent,
async () => {
if (serveStatic) {
if (Array.isArray(serveStatic)) {
const [path, handler] = serveStatic;
const [path, handler] = Array.isArray(serveStatic)
? serveStatic
: [$config.server.assets_path + "*", serveStatic];
app.modules.server.get(path, handler);
} else {
app.modules.server.get("/*", serveStatic);
}
}
await config.onBuilt?.(app);

View File

@@ -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,18 +21,27 @@ async function resolveAuth(app: ServerEnv["Variables"]["app"], c: Context<Server
authenticator.requestCookieRefresh(c);
}
export function shouldSkipAuth(c: { req: Request }) {
return new URL(c.req.url).pathname.startsWith(config.server.assets_path);
}
export const auth = createMiddleware<ServerEnv>(async (c, next) => {
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 next();
});
export const permission = (...permissions: Permission[]) =>
createMiddleware<ServerEnv>(async (c, next) => {
if (!shouldSkipAuth) {
const app = c.get("app");
if (app) {
const p = Array.isArray(permissions) ? permissions : [permissions];
@@ -43,6 +52,7 @@ export const permission = (...permissions: Permission[]) =>
} else {
console.warn("app not in context, skip permission check");
}
}
await next();
});

View File

@@ -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"

View File

@@ -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;
};

View File

@@ -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 = "<!-- BKND_CONTEXT -->";
// @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 {
<title>BKND</title>
{isProd ? (
<Fragment>
<script type="module" CrossOrigin src={"/" + assets?.js} />
<link rel="stylesheet" crossOrigin href={"/" + assets?.css} />
<script
type="module"
CrossOrigin
src={this.options.assets_path + assets?.js}
/>
<link
rel="stylesheet"
crossOrigin
href={this.options.assets_path + assets?.css}
/>
</Fragment>
) : (
<Fragment>