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
+106
View File
@@ -0,0 +1,106 @@
import path from "node:path";
import { pathToFileURL } from "node:url";
import { safeParseEkklesiaUserId } from "@ekklesia/contracts";
import {
bootstrapPlatformOwner,
createAuthorizationDrizzleDatabase,
PlatformOwnerBootstrapError,
} from "../authorization/index.js";
class BootstrapCliError extends Error {
constructor(message: string) {
super(message);
this.name = "BootstrapCliError";
}
}
const usage =
"Usage: pnpm authorization:bootstrap-owner -- --user-id <id> --confirm-user-id <same-id>";
interface ParsedArgs {
readonly confirmUserId: string;
readonly userId: string;
}
/** A minimal, hand-rolled flag parser — this command takes exactly two required, named flags, so
* a general-purpose argument-parsing dependency is not warranted. */
function parseArgs(argv: readonly string[]): ParsedArgs {
let userId: string | undefined;
let confirmUserId: string | undefined;
for (let index = 0; index < argv.length; index += 1) {
const flag = argv[index];
if (flag === "--user-id") {
userId = argv[index + 1];
index += 1;
} else if (flag === "--confirm-user-id") {
confirmUserId = argv[index + 1];
index += 1;
}
}
if (userId === undefined || confirmUserId === undefined) {
throw new BootstrapCliError(usage);
}
return { confirmUserId, userId };
}
async function main(): Promise<void> {
const { confirmUserId, userId: rawUserId } = parseArgs(process.argv.slice(2));
// Deliberately not "infer the first user" or "select by signup order" — the operator must name
// the exact, opaque Ekklesia user id twice, and both must match exactly, before anything else
// is even attempted.
if (rawUserId !== confirmUserId) {
throw new BootstrapCliError(
"--user-id and --confirm-user-id must match exactly. No changes were made.",
);
}
const parsedUserId = safeParseEkklesiaUserId(rawUserId);
if (!parsedUserId.success) {
throw new BootstrapCliError("The provided user id is not a valid Ekklesia user id.");
}
// Read only when this command actually executes — never merely by importing this module (see
// isDirectExecution() below) or any module it imports.
const databaseUrl = process.env.DATABASE_URL;
if (typeof databaseUrl !== "string" || databaseUrl.trim().length === 0) {
throw new BootstrapCliError("DATABASE_URL is required.");
}
const { close, db } = createAuthorizationDrizzleDatabase(databaseUrl, {
connectionTimeoutMs: 10_000,
operationTimeoutMs: 10_000,
});
try {
await bootstrapPlatformOwner(db, { userId: parsedUserId.data });
console.log(`Platform-owner bootstrap complete for user "${parsedUserId.data}".`);
} finally {
await close().catch(() => undefined);
}
}
function reportBootstrapCliError(error: unknown): void {
if (error instanceof BootstrapCliError) {
console.error(error.message);
return;
}
if (error instanceof PlatformOwnerBootstrapError) {
console.error(`[${error.code}] ${error.message}`);
return;
}
console.error("Platform-owner bootstrap failed unexpectedly.");
}
function isDirectExecution(): boolean {
const entrypoint = process.argv[1];
return (
entrypoint !== undefined && import.meta.url === pathToFileURL(path.resolve(entrypoint)).href
);
}
if (isDirectExecution()) {
main().catch((error: unknown) => {
reportBootstrapCliError(error);
process.exitCode = 1;
});
}