feat: establish core identity foundation
Adds Core-owned identity contracts, a Core-owned Better Auth database schema (ekklesia_core_user/session/account/verification), a first-class Core-owned migration source that coexists with module-owned migrations under the exact existing migration system, and an explicit Better Auth construction boundary. No authentication endpoint or sign-in UI is exposed yet (ADR 0010). Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
df0f20d8af
commit
679a5d225c
@@ -0,0 +1,70 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
ANONYMOUS_PRINCIPAL,
|
||||
createAuthenticatedPrincipal,
|
||||
type EkklesiaPrincipal,
|
||||
isAnonymousPrincipal,
|
||||
isAuthenticatedPrincipal,
|
||||
parseEkklesiaUserId,
|
||||
safeParseEkklesiaUserId,
|
||||
} from "../src/index.js";
|
||||
|
||||
describe("Ekklesia identity contracts", () => {
|
||||
it("parses a valid opaque user id", () => {
|
||||
expect(parseEkklesiaUserId("user_123")).toBe("user_123");
|
||||
});
|
||||
|
||||
it("rejects an empty or oversized user id", () => {
|
||||
expect(() => parseEkklesiaUserId("")).toThrow();
|
||||
expect(() => parseEkklesiaUserId(" ")).toThrow();
|
||||
expect(() => parseEkklesiaUserId("x".repeat(129))).toThrow();
|
||||
expect(() => parseEkklesiaUserId(42)).toThrow();
|
||||
expect(() => parseEkklesiaUserId(null)).toThrow();
|
||||
expect(() => parseEkklesiaUserId(undefined)).toThrow();
|
||||
});
|
||||
|
||||
it("exposes a safe-parse variant that never throws", () => {
|
||||
expect(safeParseEkklesiaUserId("user_123").success).toBe(true);
|
||||
const failed = safeParseEkklesiaUserId("");
|
||||
expect(failed.success).toBe(false);
|
||||
});
|
||||
|
||||
it("returns a frozen, stable anonymous principal singleton", () => {
|
||||
expect(ANONYMOUS_PRINCIPAL).toEqual({ kind: "anonymous" });
|
||||
expect(Object.isFrozen(ANONYMOUS_PRINCIPAL)).toBe(true);
|
||||
expect(isAnonymousPrincipal(ANONYMOUS_PRINCIPAL)).toBe(true);
|
||||
expect(isAuthenticatedPrincipal(ANONYMOUS_PRINCIPAL)).toBe(false);
|
||||
});
|
||||
|
||||
it("creates a frozen authenticated principal carrying only the opaque user id", () => {
|
||||
const principal = createAuthenticatedPrincipal("user_123");
|
||||
|
||||
expect(principal.kind).toBe("authenticated");
|
||||
expect(principal.userId).toBe("user_123");
|
||||
expect(Object.isFrozen(principal)).toBe(true);
|
||||
expect(isAuthenticatedPrincipal(principal)).toBe(true);
|
||||
expect(isAnonymousPrincipal(principal)).toBe(false);
|
||||
expect(Object.keys(principal).sort()).toEqual(["kind", "userId"]);
|
||||
});
|
||||
|
||||
it("rejects an invalid user id when creating an authenticated principal", () => {
|
||||
expect(() => createAuthenticatedPrincipal("")).toThrow();
|
||||
expect(() => createAuthenticatedPrincipal(42)).toThrow();
|
||||
});
|
||||
|
||||
it("discriminates principals on kind without any role, permission, or profile fields", () => {
|
||||
const principals: EkklesiaPrincipal[] = [
|
||||
ANONYMOUS_PRINCIPAL,
|
||||
createAuthenticatedPrincipal("user_123"),
|
||||
];
|
||||
|
||||
for (const principal of principals) {
|
||||
expect(["anonymous", "authenticated"]).toContain(principal.kind);
|
||||
expect(Object.keys(principal)).not.toContain("email");
|
||||
expect(Object.keys(principal)).not.toContain("roles");
|
||||
expect(Object.keys(principal)).not.toContain("permissions");
|
||||
expect(Object.keys(principal)).not.toContain("organizationId");
|
||||
expect(Object.keys(principal)).not.toContain("sessionToken");
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user