import type { Context } from "@wrnexus/core"; import { decideFor } from "./middleware.ts"; export interface AuthorizedHandlerOptions { permission: string; resource?: (ctx: Context) => Resource | null | undefined | Promise; exposeReason?: boolean; mayDiscover?: (ctx: Context) => boolean | Promise; handle(ctx: Context & { resource: Resource }): Result | Promise; } /** Define an API handler whose resource loading and permission check cannot be skipped. */ export function defineAuthorizedHandler( options: AuthorizedHandlerOptions, ): (ctx: Context) => Promise { return async (ctx) => { if (!ctx.user) return Response.json({ ok: false, error: "Unauthorized" }, { status: 401 }); const resource = options.resource ? await options.resource(ctx) : (undefined as Resource); if (options.resource && resource == null) { const mayDiscover = await options.mayDiscover?.(ctx); return mayDiscover ? Response.json({ ok: false, error: "Not Found" }, { status: 404 }) : Response.json({ ok: false, error: "Forbidden" }, { status: 403 }); } const decision = await decideFor(ctx, options.permission, resource); if (!decision.allowed) { return ctx.authz!.forbidden(decision, { exposeReason: options.exposeReason }); } ctx.resource = resource; return options.handle(ctx as Context & { resource: Resource }); }; } export interface OwnedResourceDefinition { name: string; owner(resource: Resource): string | null | undefined; permissions: { read?: string; create?: string; update?: string; delete?: string; readAny?: string; writeAny?: string; }; } /** Shared ownership and override vocabulary for application resource modules. */ export function defineOwnedResource(definition: OwnedResourceDefinition) { return Object.freeze({ ...definition, attributes(resource: Resource) { return { ownerId: definition.owner(resource) }; }, isOwner(subjectId: string, resource: Resource) { const ownerId = definition.owner(resource); return Boolean(subjectId && ownerId && subjectId === ownerId); }, }); }