casbin RBAC enforcer: working versions (tested 2026-10-17)
Build a casbin RBAC model from a string, add policies and roles in code, then assert allow, deny, role inheritance and dynamic policy removal decisions offline.
passed7 passed · 0 failed2026-10-17 05:58 UTC- Status
- passed
- Last passed
- 2026-10-17 05:58 UTC
- Tests
- 7 / 7
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions3
casbin@5.51.1@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 39008163431
The code
Exactly the files that passed. Your agent can pull them with get_recipe("casbin-rbac-enforcer") (install), or see them on GitHub.
package.json
{
"name": "casbin-rbac-enforcer",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"casbin": "5.51.1"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
src/rbac.ts
import { newEnforcer, newModelFromString, type Enforcer } from "casbin";
export const RBAC_MODEL = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`;
export const ALLOW = "allow";
export const DENY = "deny";
export async function createEnforcer(): Promise<Enforcer> {
const model = newModelFromString(RBAC_MODEL);
const enforcer = await newEnforcer(model);
await enforcer.addPolicy("alice", "data1", "read", ALLOW);
await enforcer.addPolicy("alice", "data1", "write", ALLOW);
await enforcer.addPolicy("data_admin", "data2", "read", ALLOW);
await enforcer.addPolicy("data_admin", "data2", "write", ALLOW);
await enforcer.addPolicy("banned", "data2", "read", DENY);
await enforcer.addGroupingPolicy("bob", "data_admin");
await enforcer.addGroupingPolicy("carol", "data_admin");
await enforcer.addGroupingPolicy("dave", "carol");
await enforcer.addGroupingPolicy("eve", "banned");
return enforcer;
}
export async function can(
enforcer: Enforcer,
sub: string,
obj: string,
act: string,
): Promise<boolean> {
return enforcer.enforce(sub, obj, act);
}
test/rbac.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { can, createEnforcer } from "../src/rbac.ts";
test("allows a direct policy match", async () => {
const e = await createEnforcer();
assert.equal(await can(e, "alice", "data1", "read"), true);
assert.equal(await can(e, "alice", "data1", "write"), true);
});
test("denies when no policy matches", async () => {
const e = await createEnforcer();
assert.equal(await can(e, "alice", "data2", "read"), false);
assert.equal(await can(e, "mallory", "data1", "read"), false);
});
test("resolves role inheritance for direct and nested roles", async () => {
const e = await createEnforcer();
assert.equal(await can(e, "bob", "data2", "write"), true);
assert.equal(await can(e, "carol", "data2", "read"), true);
assert.equal(await can(e, "dave", "data2", "read"), true);
assert.equal(await can(e, "dave", "data2", "write"), true);
});
test("applies a deny rule that overrides an inherited allow", async () => {
const e = await createEnforcer();
assert.equal(await can(e, "eve", "data2", "read"), false);
assert.equal(await can(e, "data_admin", "data2", "read"), true);
});
test("removes a policy at runtime and denies later requests", async () => {
const e = await createEnforcer();
assert.equal(await can(e, "alice", "data1", "write"), true);
const removed = await e.removePolicy("alice", "data1", "write", "allow");
assert.equal(removed, true);
assert.equal(await can(e, "alice", "data1", "write"), false);
assert.equal(await can(e, "alice", "data1", "read"), true);
const again = await e.removePolicy("alice", "data1", "write", "allow");
assert.equal(again, false);
});
test("removes a grouping policy at runtime and revokes inherited access", async () => {
const e = await createEnforcer();
assert.equal(await can(e, "bob", "data2", "read"), true);
const removed = await e.removeGroupingPolicy("bob", "data_admin");
assert.equal(removed, true);
assert.equal(await can(e, "bob", "data2", "read"), false);
});
test("adds a policy at runtime and grants new access", async () => {
const e = await createEnforcer();
assert.equal(await can(e, "frank", "data3", "read"), false);
const added = await e.addPolicy("frank", "data3", "read", "allow");
assert.equal(added, true);
assert.equal(await can(e, "frank", "data3", "read"), true);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.