refactor: extracted auth as middleware to be added manually to endpoints

This commit is contained in:
dswbx
2025-01-07 13:32:50 +01:00
parent 064bbba8aa
commit 7d3d1e811f
13 changed files with 211 additions and 178 deletions

View File

@@ -1,6 +1,6 @@
import { type AuthAction, Authenticator, type ProfileExchange, Role, type Strategy } from "auth";
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 Entity, EntityIndex, type EntityManager } from "data";
import { type FieldSchema, entity, enumm, make, text } from "data/prototype";
@@ -17,6 +17,7 @@ declare module "core" {
}
type AuthSchema = Static<typeof authConfigSchema>;
export type CreateUserPayload = { email: string; password: string; [key: string]: any };
export class AppAuth extends Module<typeof authConfigSchema> {
private _authenticator?: Authenticator;
@@ -36,8 +37,12 @@ export class AppAuth extends Module<typeof authConfigSchema> {
return to;
}
get enabled() {
return this.config.enabled;
}
override async build() {
if (!this.config.enabled) {
if (!this.enabled) {
this.setBuilt();
return;
}
@@ -84,14 +89,6 @@ export class AppAuth extends Module<typeof authConfigSchema> {
return this._controller;
}
getMiddleware() {
if (!this.config.enabled) {
return;
}
return new AuthController(this).getMiddleware;
}
getSchema() {
return authConfigSchema;
}
@@ -287,11 +284,7 @@ export class AppAuth extends Module<typeof authConfigSchema> {
} catch (e) {}
}
async createUser({
email,
password,
...additional
}: { email: string; password: string; [key: string]: any }) {
async createUser({ email, password, ...additional }: CreateUserPayload): Promise<DB["users"]> {
const strategy = "password";
const pw = this.authenticator.strategy(strategy) as PasswordStrategy;
const strategy_value = await pw.hash(password);

View File

@@ -1,42 +1,17 @@
import type { AppAuth } from "auth";
import { type ClassController, isDebug } from "core";
import { Hono, type MiddlewareHandler } from "hono";
import { Controller } from "modules/Controller";
export class AuthController implements ClassController {
constructor(private auth: AppAuth) {}
export class AuthController extends Controller {
constructor(private auth: AppAuth) {
super();
}
get guard() {
return this.auth.ctx.guard;
}
getMiddleware: MiddlewareHandler = async (c, next) => {
// @todo: ONLY HOTFIX
// 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();
override getController() {
const hono = this.create();
const strategies = this.auth.authenticator.getStrategies();
for (const [name, strategy] of Object.entries(strategies)) {

View 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();
});