feat: add authorization bootstrap and HTTP enforcement

Wires Core's identity and authorization domains into apps/api over one
shared, validated PostgreSQL pool; adds an explicit, single-use,
concurrency-safe platform-owner bootstrap CLI; adds a smallest-possible
immutable permission registry with one Core-owned restricted permission
(core.authorization.grants.manage); and adds reusable Hono authorization
enforcement plus a narrowly scoped /api/v1/grants HTTP API for managing
explicit grants only, gated by an exact trusted Origin on every mutation.
This commit is contained in:
ekklesia
2026-07-23 11:16:32 -05:00
parent 72e082ff2f
commit 1363608552
33 changed files with 2962 additions and 63 deletions
+27 -2
View File
@@ -1,13 +1,27 @@
import { isAuthenticatedPrincipal } from "@ekklesia/contracts";
import {
type AuthorizationEvaluator,
type AuthorizationStore,
CORE_PERMISSION_REGISTRY,
} from "@ekklesia/core/authorization";
import { type EkklesiaIdentityRuntime, IdentityRuntimeError } from "@ekklesia/core/identity";
import { Hono } from "hono";
import type { Context } from "hono";
import { cors } from "hono/cors";
import { createGrantsRoutes } from "./grants-routes.js";
export interface CreateApiAppAuthorizationOptions {
readonly evaluator: AuthorizationEvaluator;
readonly store: AuthorizationStore;
}
export interface CreateApiAppOptions {
readonly authorization: CreateApiAppAuthorizationOptions;
readonly runtime: EkklesiaIdentityRuntime;
/** Exact allowed browser origins. Never a wildcard; combined with `credentials: true`, only an
* exact, reflected match is ever returned in `Access-Control-Allow-Origin`. */
* exact, reflected match is ever returned in `Access-Control-Allow-Origin`. Also the trusted set
* `requireTrustedOrigin` checks for grant-mutation routes — there is no second, competing origin
* configuration. */
readonly trustedOrigins: readonly string[];
}
@@ -20,7 +34,7 @@ function sanitizedErrorResponse(c: Context, error: unknown, status: 500 | 503) {
/**
* Pure Hono application factory: no I/O, no environment reads, no process side effects. Suitable
* for unit and integration tests via `app.fetch` directly, with a fully injected identity runtime
* standing in for real Better Auth/PostgreSQL where appropriate.
* and authorization evaluator/store standing in for real Better Auth/PostgreSQL where appropriate.
*/
export function createApiApp(options: CreateApiAppOptions): Hono {
const { runtime } = options;
@@ -62,6 +76,17 @@ export function createApiApp(options: CreateApiAppOptions): Hono {
}
});
app.route(
"/",
createGrantsRoutes({
evaluator: options.authorization.evaluator,
registry: CORE_PERMISSION_REGISTRY,
runtime,
store: options.authorization.store,
trustedOrigins: allowedOrigins,
}),
);
app.notFound((c) => {
c.header("Cache-Control", "no-store");
return c.json({ code: "NOT_FOUND" }, 404);