import { ANONYMOUS_PRINCIPAL, createAuthenticatedPrincipal, parseAuthorizationScope, parseEkklesiaUserId, parsePermissionId, } from "@ekklesia/contracts"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { type ActiveGrantQuery, AuthorizationEvaluationError, type AuthorizationGrantPort, type AuthorizationPlatformOwnerPort, type AuthorizeRequest, createAuthorizationEvaluator, } from "../src/authorization/index.js"; interface FakeGrant { readonly userId: string; readonly permission: string; readonly scope?: string; readonly expiresAt?: Date; readonly revokedAt?: Date; } /** An in-memory double implementing the exact scope/expiry/revocation matching semantics the real * Postgres store implements in SQL, so the evaluator's own decision logic can be exercised in * isolation. The live integration suite separately proves the SQL performs the same matching. */ class FakeGrantStore implements AuthorizationGrantPort { readonly grants: FakeGrant[] = []; readonly calls: ActiveGrantQuery[] = []; async hasActiveGrant(query: ActiveGrantQuery): Promise { this.calls.push(query); return this.grants.some((grant) => { if (grant.userId !== query.userId || grant.permission !== query.permission) { return false; } if (grant.revokedAt !== undefined) { return false; } if (grant.expiresAt !== undefined && grant.expiresAt <= query.now) { return false; } if (grant.scope === undefined) { return true; // an unscoped grant satisfies any request for the same permission } return grant.scope === query.scope; // a scoped grant never satisfies an unscoped request }); } } class FakeOwnerStore implements AuthorizationPlatformOwnerPort { private readonly owners = new Set(); readonly calls: string[] = []; makeOwner(userId: string): void { this.owners.add(userId); } async isPlatformOwner(userId: string): Promise { this.calls.push(userId); return this.owners.has(userId); } } const userId = parseEkklesiaUserId("user_1"); const otherUserId = parseEkklesiaUserId("user_2"); const permission = parsePermissionId("calendar.events.read"); const scope = parseAuthorizationScope("calendar-event:123"); const otherScope = parseAuthorizationScope("calendar-event:456"); describe("Core authorization evaluator", () => { let grants: FakeGrantStore; let owners: FakeOwnerStore; beforeEach(() => { grants = new FakeGrantStore(); owners = new FakeOwnerStore(); }); function evaluator(now?: () => Date) { return createAuthorizationEvaluator({ grants, platformOwners: owners, ...(now ? { now } : {}), }); } describe("public baseline", () => { it("allows an anonymous principal", async () => { const decision = await evaluator().authorize({ baseline: "public", permission, principal: ANONYMOUS_PRINCIPAL, }); expect(decision).toEqual({ allowed: true }); }); it("allows an authenticated principal", async () => { const decision = await evaluator().authorize({ baseline: "public", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: true }); }); it("never queries grants or platform-owner state for a public check", async () => { await evaluator().authorize({ baseline: "public", permission, principal: ANONYMOUS_PRINCIPAL, }); expect(grants.calls).toHaveLength(0); expect(owners.calls).toHaveLength(0); }); }); describe("authenticated baseline", () => { it("allows an authenticated principal", async () => { const decision = await evaluator().authorize({ baseline: "authenticated", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: true }); }); it("rejects an anonymous principal", async () => { const decision = await evaluator().authorize({ baseline: "authenticated", permission, principal: ANONYMOUS_PRINCIPAL, }); expect(decision).toEqual({ allowed: false }); }); it("never queries grants or platform-owner state for an authenticated check", async () => { await evaluator().authorize({ baseline: "authenticated", permission, principal: createAuthenticatedPrincipal(userId), }); expect(grants.calls).toHaveLength(0); expect(owners.calls).toHaveLength(0); }); }); describe("restricted baseline", () => { it("denies an anonymous principal without ever consulting grants or ownership", async () => { const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: ANONYMOUS_PRINCIPAL, }); expect(decision).toEqual({ allowed: false }); expect(grants.calls).toHaveLength(0); expect(owners.calls).toHaveLength(0); }); it("denies an authenticated principal with no grant and no ownership", async () => { const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); }); it("allows an authenticated principal holding an active explicit grant", async () => { grants.grants.push({ permission, userId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: true }); }); it("allows an authenticated principal holding an active relationship-derived grant", async () => { // The evaluator only distinguishes "active grant or not" through the port; source kind is a // store/audit concern, not an evaluation input — proven here by a grant the fake store // exposes with no source-kind field at all still being honored. grants.grants.push({ permission, userId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: true }); }); it("matches permissions exactly and does not treat a namespace prefix as inherited authority", async () => { grants.grants.push({ permission: parsePermissionId("calendar.events.read.detail"), userId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); }); it("denies a grant that belongs to a different user", async () => { grants.grants.push({ permission, userId: otherUserId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); }); it("lets an unscoped grant satisfy an unscoped request", async () => { grants.grants.push({ permission, userId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: true }); }); it("lets an unscoped grant satisfy a scoped request for the same permission", async () => { grants.grants.push({ permission, userId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), scope, }); expect(decision).toEqual({ allowed: true }); }); it("lets a scoped grant satisfy a request for the exact matching scope", async () => { grants.grants.push({ permission, scope, userId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), scope, }); expect(decision).toEqual({ allowed: true }); }); it("denies a scoped grant against a different scope", async () => { grants.grants.push({ permission, scope, userId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), scope: otherScope, }); expect(decision).toEqual({ allowed: false }); }); it("never lets a scoped grant satisfy an unscoped request", async () => { grants.grants.push({ permission, scope, userId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); }); it("denies an expired grant", async () => { const fixedNow = new Date("2026-01-01T00:00:00.000Z"); grants.grants.push({ expiresAt: new Date("2025-12-31T00:00:00.000Z"), permission, userId, }); const decision = await evaluator(() => fixedNow).authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); }); it("allows a grant that has not yet expired, evaluated deterministically against an injected clock", async () => { const fixedNow = new Date("2026-01-01T00:00:00.000Z"); grants.grants.push({ expiresAt: new Date("2026-01-02T00:00:00.000Z"), permission, userId, }); const decision = await evaluator(() => fixedNow).authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: true }); }); it("denies a revoked grant even though it has not expired", async () => { grants.grants.push({ expiresAt: new Date(Date.now() + 86_400_000), permission, revokedAt: new Date(), userId, }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); }); it("denies an unknown or malformed permission without throwing", async () => { const decision = await evaluator().authorize({ baseline: "restricted", permission: "not a valid permission id", principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); expect(grants.calls).toHaveLength(0); }); it("denies a wildcard-shaped permission without throwing", async () => { const decision = await evaluator().authorize({ baseline: "restricted", permission: "media.*", principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); }); it("denies a non-string permission (number, object, null, undefined) without throwing", async () => { for (const malformed of [42, {}, null, undefined, ["calendar.events.read"]]) { const decision = await evaluator().authorize({ baseline: "restricted", permission: malformed, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); } }); it("denies a malformed scope without throwing", async () => { grants.grants.push({ permission, userId }); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), scope: "", }); expect(decision).toEqual({ allowed: false }); }); it("allows a platform owner with no matching grant at all", async () => { owners.makeOwner(userId); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: true }); }); it("allows a platform owner for a scoped restricted permission with no scoped grant", async () => { owners.makeOwner(userId); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), scope, }); expect(decision).toEqual({ allowed: true }); }); it("does not grant ownership authority to a different user", async () => { owners.makeOwner(otherUserId); const decision = await evaluator().authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); }); }); describe("an unknown or malformed baseline", () => { it("denies rather than defaulting to any known baseline", async () => { for (const malformed of ["superuser", "", 42, null, undefined, "Public", "PUBLIC"]) { const decision = await evaluator().authorize({ baseline: malformed, permission, principal: createAuthenticatedPrincipal(userId), }); expect(decision).toEqual({ allowed: false }); } }); }); describe("a client cannot inject owner or grant state", () => { it("ignores extra caller-supplied fields such as isOwner/hasGrant and re-derives authority from the injected ports", async () => { // Simulates a caller that smuggles extra fields the AuthorizeRequest type does not // declare — going through `unknown` rather than `any` proves the object still satisfies // AuthorizeRequest structurally; the extra fields are simply never read. const hostileRequest = { baseline: "restricted", hasGrant: true, isOwner: true, permission, principal: createAuthenticatedPrincipal(userId), } as unknown as AuthorizeRequest; const decision = await evaluator().authorize(hostileRequest); expect(decision).toEqual({ allowed: false }); // The evaluator still had to actually ask the injected ports — it did not shortcut on the // hostile flags. expect(grants.calls).toHaveLength(1); expect(owners.calls).toHaveLength(1); }); }); describe("decision contract", () => { it("returns a frozen, minimal decision with no extra fields", async () => { const decision = await evaluator().authorize({ baseline: "public", permission, principal: ANONYMOUS_PRINCIPAL, }); expect(Object.isFrozen(decision)).toBe(true); expect(Object.keys(decision)).toEqual(["allowed"]); }); }); describe("sanitized failures", () => { it("propagates a genuine grant-lookup infrastructure failure rather than silently denying", async () => { const failingGrants: AuthorizationGrantPort = { hasActiveGrant: vi.fn().mockRejectedValue(new Error("connection terminated unexpectedly")), }; const failingEvaluator = createAuthorizationEvaluator({ grants: failingGrants, platformOwners: owners, }); await expect( failingEvaluator.authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }), ).rejects.toMatchObject({ code: "AUTHORIZATION_EVALUATION_FAILED", }); }); it("propagates a genuine platform-owner lookup infrastructure failure rather than silently denying", async () => { const failingOwners: AuthorizationPlatformOwnerPort = { isPlatformOwner: vi.fn().mockRejectedValue(new Error("connection terminated unexpectedly")), }; const failingEvaluator = createAuthorizationEvaluator({ grants, platformOwners: failingOwners, }); await expect( failingEvaluator.authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }), ).rejects.toBeInstanceOf(AuthorizationEvaluationError); }); it("never leaks a raw infrastructure error message through the thrown error", async () => { const hostileMessage = "password=hunter2 host=db.internal.example port=5432"; const failingGrants: AuthorizationGrantPort = { hasActiveGrant: vi.fn().mockRejectedValue(new Error(hostileMessage)), }; const failingEvaluator = createAuthorizationEvaluator({ grants: failingGrants, platformOwners: owners, }); let captured: unknown; try { await failingEvaluator.authorize({ baseline: "restricted", permission, principal: createAuthenticatedPrincipal(userId), }); } catch (error) { captured = error; } const publicText = `${String(captured)}\n${ captured instanceof Error ? (captured.stack ?? "") : "" }`; expect(publicText).not.toContain("hunter2"); expect(publicText).not.toContain("db.internal.example"); }); }); });