first commit
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { createContext } from "@wrnexus/core";
|
||||
import {
|
||||
defineRbac,
|
||||
hasRole,
|
||||
authorize,
|
||||
requireRole,
|
||||
requirePermission,
|
||||
any,
|
||||
all,
|
||||
attr,
|
||||
type Policy,
|
||||
} from "../src/index.ts";
|
||||
|
||||
const rbac = defineRbac({
|
||||
admin: ["*"],
|
||||
editor: ["post:read", "post:write"],
|
||||
viewer: ["post:read"],
|
||||
moderator: ["role:editor", "comment:delete"], // inherits editor
|
||||
});
|
||||
|
||||
test("RBAC: roles, wildcards, namespaces, inheritance", () => {
|
||||
expect(rbac.can({ roles: ["viewer"] }, "post:read")).toBe(true);
|
||||
expect(rbac.can({ roles: ["viewer"] }, "post:write")).toBe(false);
|
||||
expect(rbac.can({ roles: ["admin"] }, "anything:goes")).toBe(true); // "*"
|
||||
expect(rbac.can({ roles: ["moderator"] }, "post:write")).toBe(true); // inherited from editor
|
||||
expect(rbac.can({ roles: ["moderator"] }, "comment:delete")).toBe(true);
|
||||
expect(rbac.can(undefined, "post:read")).toBe(false);
|
||||
expect(defineRbac({ ed: ["post:*"] }).can({ roles: ["ed"] }, "post:write")).toBe(true); // ns wildcard
|
||||
});
|
||||
|
||||
test("hasRole", () => {
|
||||
expect(hasRole({ roles: ["a", "b"] }, "a")).toBe(true);
|
||||
expect(hasRole({ roles: ["a"] }, "a", "b")).toBe(false);
|
||||
});
|
||||
|
||||
interface User extends Record<string, unknown> {
|
||||
id?: string;
|
||||
roles?: string[];
|
||||
tenant?: string;
|
||||
}
|
||||
interface Post {
|
||||
authorId: string;
|
||||
}
|
||||
|
||||
test("PBAC/ABAC: policies compose (any/all) + attribute match", async () => {
|
||||
const ownsPost: Policy<User, Post> = (u, post) => u.id === post?.authorId;
|
||||
const isAdmin: Policy<User> = (u) => (u.roles ?? []).includes("admin");
|
||||
const canEdit = any(ownsPost, isAdmin);
|
||||
|
||||
expect(await canEdit({ id: "u1" }, { authorId: "u1" })).toBe(true); // owner
|
||||
expect(await canEdit({ id: "u2", roles: ["admin"] }, { authorId: "u1" })).toBe(true); // admin
|
||||
expect(await canEdit({ id: "u2" }, { authorId: "u1" })).toBe(false);
|
||||
|
||||
const sameTenant = all(isAdmin, attr<User>("tenant", "acme"));
|
||||
expect(await sameTenant({ roles: ["admin"], tenant: "acme" })).toBe(true);
|
||||
expect(await sameTenant({ roles: ["admin"], tenant: "other" })).toBe(false);
|
||||
});
|
||||
|
||||
function ctx(user?: unknown) {
|
||||
const url = new URL("http://x/admin");
|
||||
const c = createContext(new Request(url), url);
|
||||
c.user = user;
|
||||
return c;
|
||||
}
|
||||
|
||||
test("guards: authorize / requireRole / requirePermission", async () => {
|
||||
const ok = () => new Response("ok");
|
||||
expect((await requireRole("admin")(ctx({ roles: ["admin"] }), ok)).status).toBe(200);
|
||||
expect((await requireRole("admin")(ctx({ roles: ["viewer"] }), ok)).status).toBe(403);
|
||||
expect((await requirePermission(rbac, "post:write")(ctx({ roles: ["editor"] }), ok)).status).toBe(
|
||||
200,
|
||||
);
|
||||
expect((await requirePermission(rbac, "post:write")(ctx({ roles: ["viewer"] }), ok)).status).toBe(
|
||||
403,
|
||||
);
|
||||
expect(
|
||||
(await authorize((c) => (c.user as User)?.id === "u1")(ctx({ id: "u1" }), ok)).status,
|
||||
).toBe(200);
|
||||
});
|
||||
Reference in New Issue
Block a user