feat: integrate module authorization #11
@@ -17,9 +17,11 @@ function sanitizedDenial(c: Context, status: 403 | 503, code: string): Response
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Hono middleware that enforces `permissionId`, resolved exclusively from Core's own
|
||||
* trusted, server-side permission registry — a route never supplies its own baseline, and no
|
||||
* request input can influence which baseline is checked. The principal is resolved exclusively
|
||||
* Builds a Hono middleware that enforces `permissionId`, resolved exclusively from the injected,
|
||||
* trusted, server-side `PermissionRegistry` — in production, the complete platform registry
|
||||
* `startApi` composes from Core's own declarations plus every installed module's, never a
|
||||
* Core-only fallback. A route never supplies its own baseline, and no request input can influence
|
||||
* which baseline is checked. The principal is resolved exclusively
|
||||
* through the injected identity runtime (`resolvePrincipal`), the same one `/api/v1/principal`
|
||||
* already uses; nothing here ever trusts a client-supplied claim about identity, ownership, or
|
||||
* grants. A permission id that is not registered fails closed (403) exactly like a denied
|
||||
|
||||
@@ -891,9 +891,11 @@ describe.sequential("live API — platform owner and delegated grant management"
|
||||
const ownerEmail = "[email protected]";
|
||||
const managerEmail = "[email protected]";
|
||||
const password = "correct horse battery staple";
|
||||
// Module permission registration does not exist yet, so `core.authorization.grants.manage` —
|
||||
// the one permission `CORE_PERMISSION_REGISTRY` actually declares — stands in for "some
|
||||
// registered permission" throughout this scenario's explicit-grant lifecycle.
|
||||
// This scenario intentionally proves out the explicit-grant lifecycle (create, list, use,
|
||||
// revoke) against Core's own grant-management permission rather than a module's — a module
|
||||
// permission would exercise the exact same route and evaluator code, adding coverage breadth
|
||||
// without coverage depth. The "live API — installed module permission end-to-end" scenario
|
||||
// below is where a real installed module's declared permission is proven end-to-end instead.
|
||||
const testPermission = "core.authorization.grants.manage";
|
||||
// Well-formed (passes `PermissionId` shape validation) but never registered — used to prove the
|
||||
// live route rejects it even for a caller who otherwise has full grant-management authority.
|
||||
|
||||
@@ -25,8 +25,13 @@ const subjectUserId = parseEkklesiaUserId("subject_1");
|
||||
// (or, for listing, still return) an unregistered permission id rather than assuming registration.
|
||||
const permission = parsePermissionId("calendar.events.read");
|
||||
// A permission `CORE_PERMISSION_REGISTRY` actually declares, so tests that must reach
|
||||
// `store.createGrant`/`store.revokeGrant` can do so without also depending on module permission
|
||||
// registration, which does not exist yet.
|
||||
// `store.createGrant`/`store.revokeGrant` can do so. This suite exercises `createGrantsRoutes` in
|
||||
// isolation and intentionally injects `CORE_PERMISSION_REGISTRY` directly rather than a composed
|
||||
// platform registry — the routes have no notion of "Core" versus "module" permissions, they only
|
||||
// ever consult whichever registry they are given. Coverage proving a module-declared permission
|
||||
// works through the exact same route code lives in
|
||||
// `apps/api/tests/authorization-middleware.test.ts` (unit) and the "installed module permission
|
||||
// end-to-end" scenario in `apps/api/tests/api-http.integration.ts` (live).
|
||||
const registeredPermission = CORE_AUTHORIZATION_GRANTS_MANAGE;
|
||||
const grantId = parseGrantId("11111111-1111-1111-1111-111111111111");
|
||||
|
||||
|
||||
@@ -46,9 +46,10 @@ export class PermissionRegistryError extends Error {
|
||||
* Builds an immutable permission registry from a fixed list of declarations, rejecting any
|
||||
* duplicate permission id outright at composition time. Deliberately the smallest mechanism that
|
||||
* distinguishes "registered" from "well-formed": no module discovery, no dynamic registration, no
|
||||
* network or filesystem I/O — a future module registration mechanism can merge its own
|
||||
* declarations into a combined list and still call this same constructor, inheriting duplicate
|
||||
* detection for free instead of reimplementing it.
|
||||
* network or filesystem I/O. The module integration layer's composer
|
||||
* (`composePlatformPermissionRegistry`, `core/src/modules/permissions.ts`) merges Core's own
|
||||
* declarations with every discovered module's into one combined list and calls this same
|
||||
* constructor, inheriting duplicate detection for free rather than reimplementing it.
|
||||
*/
|
||||
export function createPermissionRegistry(
|
||||
declarations: readonly PermissionDeclaration[],
|
||||
@@ -98,11 +99,12 @@ export const CORE_PERMISSION_DECLARATIONS: readonly PermissionDeclaration[] = Ob
|
||||
]);
|
||||
|
||||
/**
|
||||
* Core's own permission registry — the trusted source of truth `apps/api`'s authorization
|
||||
* enforcement consults so an HTTP route can never supply its own baseline. Contains exactly the
|
||||
* one Core-owned permission this slice needs; a future module registration mechanism is
|
||||
* deliberately out of scope here and will compose its own declarations through
|
||||
* `createPermissionRegistry` rather than through this constant.
|
||||
* Core's own permission registry, containing exactly the Core-owned permissions declared above.
|
||||
* Production `apps/api` does not consult this constant directly: it enforces requests against the
|
||||
* composed platform registry (`composePlatformPermissionRegistry`,
|
||||
* `core/src/modules/permissions.ts`), which merges these same declarations with every installed
|
||||
* module's. `CORE_PERMISSION_REGISTRY` remains for Core-only callers and tests that need Core's
|
||||
* permissions in isolation, without composing in any module.
|
||||
*/
|
||||
export const CORE_PERMISSION_REGISTRY: PermissionRegistry = createPermissionRegistry(
|
||||
CORE_PERMISSION_DECLARATIONS,
|
||||
|
||||
Reference in New Issue
Block a user