Refactor module middleware initialization logic.

Replaced `getMiddleware` with `onServerInit` for streamlined middleware registration. Updated `AppAuth` to automatically register its authentication middleware. Added a test case to verify middleware registration. Removed redundant cookie renewal logic from `AdminController` and made related adjustments across modules.
This commit is contained in:
dswbx
2025-01-09 10:59:48 +01:00
parent 7d3d1e811f
commit 47f48be514
9 changed files with 58 additions and 27 deletions

View File

@@ -1,4 +1,5 @@
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test"; import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
import { createApp } from "../../src";
import { AuthController } from "../../src/auth/api/AuthController"; import { AuthController } from "../../src/auth/api/AuthController";
import { AppAuth, type ModuleBuildContext } from "../../src/modules"; import { AppAuth, type ModuleBuildContext } from "../../src/modules";
import { disableConsoleLog, enableConsoleLog } from "../helper"; import { disableConsoleLog, enableConsoleLog } from "../helper";
@@ -76,4 +77,29 @@ describe("AppAuth", () => {
expect(users[0].email).toBe("some@body.com"); expect(users[0].email).toBe("some@body.com");
} }
}); });
test("registers auth middleware automatically", async () => {
const app = createApp({
initialConfig: {
auth: {
enabled: true,
jwt: {
secret: "123456"
}
}
}
});
await app.build();
const spy = spyOn(app.module.auth.authenticator, "requestCookieRefresh");
// register custom route
app.server.get("/test", async (c) => c.text("test"));
// call a system api and then the custom route
await app.server.request("/api/system/ping");
await app.server.request("/test");
expect(spy.mock.calls.length).toBe(2);
});
}); });

View File

@@ -74,7 +74,7 @@ export class App {
server.use(async (c, next) => { server.use(async (c, next) => {
c.set("app", this); c.set("app", this);
await next(); await next();
}) });
} }
}); });
this.modules.ctx().emgr.registerEvents(AppEvents); this.modules.ctx().emgr.registerEvents(AppEvents);
@@ -108,7 +108,6 @@ 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 }));
server.all("/api/*", async (c) => c.notFound()); server.all("/api/*", async (c) => c.notFound());
if (options?.save) { if (options?.save) {
@@ -130,6 +129,10 @@ export class App {
return this.modules.server; return this.modules.server;
} }
get em() {
return this.modules.ctx().em;
}
get fetch(): any { get fetch(): any {
return this.server.fetch; return this.server.fetch;
} }
@@ -194,4 +197,4 @@ export function createApp(config: CreateAppConfig = {}) {
} }
return new App(connection, config.initialConfig, config.plugins, config.options); return new App(connection, config.initialConfig, config.plugins, config.options);
} }

View File

@@ -1,9 +1,11 @@
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 { auth } from "auth/middlewares";
import { type DB, 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";
import type { Hono } from "hono";
import { pick } from "lodash-es"; import { pick } from "lodash-es";
import { Module } from "modules/Module"; import { Module } from "modules/Module";
import { AuthController } from "./api/AuthController"; import { AuthController } from "./api/AuthController";
@@ -89,6 +91,10 @@ export class AppAuth extends Module<typeof authConfigSchema> {
return this._controller; return this._controller;
} }
override onServerInit(hono: Hono<any>) {
hono.use(auth);
}
getSchema() { getSchema() {
return authConfigSchema; return authConfigSchema;
} }

View File

@@ -243,7 +243,6 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
if (this.config.cookie.renew) { if (this.config.cookie.renew) {
const token = await this.getAuthCookie(c); const token = await this.getAuthCookie(c);
if (token) { if (token) {
console.log("renewing cookie", c.req.url);
await this.setAuthCookie(c, token); await this.setAuthCookie(c, token);
} }
} }

View File

@@ -16,17 +16,24 @@ async function resolveAuth(app: ServerEnv["Variables"]["app"], c: Context<Server
const guard = app.modules.ctx().guard; const guard = app.modules.ctx().guard;
guard.setUserContext(await authenticator.resolveAuthFromRequest(c)); guard.setUserContext(await authenticator.resolveAuthFromRequest(c));
// renew cookie if applicable
authenticator.requestCookieRefresh(c);
} }
export const auth = createMiddleware<ServerEnv>(async (c, next) => { export const auth = createMiddleware<ServerEnv>(async (c, next) => {
// make sure to only register once
if (c.get("auth_registered")) {
return;
}
await resolveAuth(c.get("app"), c); await resolveAuth(c.get("app"), c);
c.set("auth_registered", true);
await next(); await next();
}); });
export const permission = (...permissions: Permission[]) => export const permission = (...permissions: Permission[]) =>
createMiddleware<ServerEnv>(async (c, next) => { createMiddleware<ServerEnv>(async (c, next) => {
const app = c.get("app"); const app = c.get("app");
await resolveAuth(app, c);
const p = Array.isArray(permissions) ? permissions : [permissions]; const p = Array.isArray(permissions) ? permissions : [permissions];
const guard = app.modules.ctx().guard; const guard = app.modules.ctx().guard;

View File

@@ -6,14 +6,10 @@ export class Controller {
protected middlewares = { protected middlewares = {
auth, auth,
permission permission
} };
protected create({ auth }: { auth?: boolean } = {}): Hono<ServerEnv> { protected create(): Hono<ServerEnv> {
const server = Controller.createServer(); return Controller.createServer();
if (auth !== false) {
server.use(this.middlewares.auth);
}
return server;
} }
static createServer(): Hono<ServerEnv> { static createServer(): Hono<ServerEnv> {
@@ -23,4 +19,4 @@ export class Controller {
getController(): Hono<ServerEnv> { getController(): Hono<ServerEnv> {
return this.create(); return this.create();
} }
} }

View File

@@ -10,6 +10,7 @@ export type ServerEnv = {
Variables: { Variables: {
app: App; app: App;
auth_resolved: boolean; auth_resolved: boolean;
auth_registered: boolean;
html?: string; html?: string;
}; };
}; };
@@ -87,9 +88,9 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
return this._schema; return this._schema;
} }
getMiddleware() { // action performed when server has been initialized
return undefined; // can be used to assign global middlewares
} onServerInit(hono: Hono<ServerEnv>) {}
get ctx() { get ctx() {
if (!this._ctx) { if (!this._ctx) {

View File

@@ -212,14 +212,9 @@ export class ModuleManager {
this.options.onServerInit(this.server); this.options.onServerInit(this.server);
} }
// @todo: this is a current workaround, controllers must be reworked // optional method for each module to register global middlewares, etc.
objectEach(this.modules, (module) => { objectEach(this.modules, (module) => {
if ("getMiddleware" in module) { module.onServerInit(this.server);
const middleware = module.getMiddleware();
if (middleware) {
this.server.use(middleware);
}
}
}); });
} }
@@ -550,4 +545,4 @@ export function getDefaultConfig(): ModuleConfigs {
}); });
return config as any; return config as any;
} }

View File

@@ -65,8 +65,6 @@ export class AdminController extends Controller {
} }
c.set("html", html); c.set("html", html);
// refresh cookie if needed
await auth.authenticator?.requestCookieRefresh(c);
await next(); await next();
}); });