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:
+27
-14
@@ -1,15 +1,15 @@
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
createIdentityDrizzleDatabase,
|
||||
createIdentityRuntime,
|
||||
type IdentityDatabaseHandle,
|
||||
type IdentityRateLimitOverride,
|
||||
} from "@ekklesia/core/identity";
|
||||
createAuthorizationEvaluator,
|
||||
createAuthorizationStore,
|
||||
} from "@ekklesia/core/authorization";
|
||||
import { createIdentityRuntime, type IdentityRateLimitOverride } from "@ekklesia/core/identity";
|
||||
import { serve, type ServerType } from "@hono/node-server";
|
||||
import type { Hono } from "hono";
|
||||
import { createApiApp } from "./app.js";
|
||||
import { type ApiRuntimeConfig, createApiRuntimeConfig } from "./config.js";
|
||||
import { type ApiDatabaseHandle, createApiDatabase } from "./database.js";
|
||||
|
||||
const connectionTimeoutMs = 10_000;
|
||||
const operationTimeoutMs = 10_000;
|
||||
@@ -26,7 +26,7 @@ export interface StartApiOptions {
|
||||
readonly rateLimitOverride?: IdentityRateLimitOverride;
|
||||
}
|
||||
|
||||
async function safeCloseDatabase(handle: IdentityDatabaseHandle): Promise<void> {
|
||||
async function safeCloseDatabase(handle: ApiDatabaseHandle): Promise<void> {
|
||||
try {
|
||||
await handle.close();
|
||||
} catch {
|
||||
@@ -54,34 +54,47 @@ function bindServer(app: Hono, host: string, port: number): Promise<ServerType>
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicit runtime composition: constructs the PostgreSQL pool and Drizzle database, the Core
|
||||
* identity runtime, and the Hono application, then starts the Node HTTP server. Returns a handle
|
||||
* with an idempotent `shutdown()` so both the executable entry point and tests can stop the
|
||||
* server and close the database pool deterministically. Never applies migrations or generates
|
||||
* schema — the database must already be migrated through `pnpm migrations:apply`.
|
||||
* Explicit runtime composition: constructs the one shared PostgreSQL pool (identity and
|
||||
* authorization each get a typed Drizzle view over it — see `./database.js`), the Core identity
|
||||
* runtime, the Core authorization store and evaluator, and the Hono application, then starts the
|
||||
* Node HTTP server. Returns a handle with an idempotent `shutdown()` so both the executable entry
|
||||
* point and tests can stop the server and close the shared pool deterministically. Never applies
|
||||
* migrations or generates schema — the database must already be migrated through
|
||||
* `pnpm migrations:apply`.
|
||||
*/
|
||||
export async function startApi(
|
||||
config: ApiRuntimeConfig,
|
||||
options: StartApiOptions = {},
|
||||
): Promise<RunningApi> {
|
||||
const databaseHandle = createIdentityDrizzleDatabase(config.databaseUrl, {
|
||||
const databaseHandle = createApiDatabase(config.databaseUrl, {
|
||||
connectionTimeoutMs,
|
||||
operationTimeoutMs,
|
||||
});
|
||||
|
||||
let runtime: ReturnType<typeof createIdentityRuntime>;
|
||||
let authorizationStore: ReturnType<typeof createAuthorizationStore>;
|
||||
try {
|
||||
runtime = createIdentityRuntime({
|
||||
config: config.identity,
|
||||
db: databaseHandle.db,
|
||||
db: databaseHandle.identity,
|
||||
...(options.rateLimitOverride ? { rateLimitOverride: options.rateLimitOverride } : {}),
|
||||
});
|
||||
authorizationStore = createAuthorizationStore(databaseHandle.authorization);
|
||||
} catch (error) {
|
||||
await safeCloseDatabase(databaseHandle);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const app = createApiApp({ runtime, trustedOrigins: config.identity.trustedOrigins });
|
||||
const evaluator = createAuthorizationEvaluator({
|
||||
grants: authorizationStore,
|
||||
platformOwners: authorizationStore,
|
||||
});
|
||||
|
||||
const app = createApiApp({
|
||||
authorization: { evaluator, store: authorizationStore },
|
||||
runtime,
|
||||
trustedOrigins: config.identity.trustedOrigins,
|
||||
});
|
||||
|
||||
let server: ServerType;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user