import type { Permission, PermissionContext } from "auth/authorize/Permission"; import { $console, threw } from "bknd/utils"; import type { Context, Hono } from "hono"; import type { RouterRoute } from "hono/types"; import { createMiddleware } from "hono/factory"; import type { ServerEnv } from "modules/Controller"; import type { MaybePromise } from "core/types"; function getPath(reqOrCtx: Request | Context) { const req = reqOrCtx instanceof Request ? reqOrCtx : reqOrCtx.req.raw; return new URL(req.url).pathname; } const permissionSymbol = Symbol.for("permission"); type PermissionMiddlewareOptions

> = { onGranted?: (c: Context) => MaybePromise; onDenied?: (c: Context) => MaybePromise; } & (P extends Permission ? PC extends undefined ? { context?: never; } : { context: (c: Context) => MaybePromise>; } : { context?: never; }); export function permission

>( permission: P, options: PermissionMiddlewareOptions

, ) { // @ts-ignore (middlewares do not always return) const handler = createMiddleware(async (c, next) => { const app = c.get("app"); const authCtx = c.get("auth"); if (!authCtx) { throw new Error("auth ctx not found"); } // in tests, app is not defined if (!authCtx.registered || !app) { const msg = `auth middleware not registered, cannot check permissions for ${getPath(c)}`; if (app?.module.auth.enabled) { throw new Error(msg); } else { $console.warn(msg); } } else if (!authCtx.skip) { const guard = app.modules.ctx().guard; const context = (await options?.context?.(c)) ?? ({} as any); if (options?.onGranted || options?.onDenied) { let returned: undefined | void | Response; if (threw(() => guard.granted(permission, c, context))) { returned = await options?.onDenied?.(c); } else { returned = await options?.onGranted?.(c); } if (returned instanceof Response) { return returned; } } else { guard.granted(permission, c, context); } } await next(); }); return Object.assign(handler, { [permissionSymbol]: { permission, options }, }); } export function getPermissionRoutes(hono: Hono) { const routes: { route: RouterRoute; permission: Permission; options: PermissionMiddlewareOptions; }[] = []; for (const route of hono.routes) { if (permissionSymbol in route.handler) { routes.push({ route, ...(route.handler[permissionSymbol] as any), }); } } return routes; }