Open-source authorization for TypeScriptMIT core · scoped RBAC · your own database

Authorization for TypeScript apps, declared in code.

Your applications declare their permissions in the code that enforces them. Your team grants them without an engineering ticket. Every check runs in your own database.

$ npm i @alfiz/core @alfiz/application
Declare it
src/authz/catalog.tsts
import { defineCatalog } from "@alfiz/core"; export const catalog = defineCatalog({  namespaces: ["docs"],  permissions: {    "docs.files.read":   { scopes: ["docs.folder", "docs.doc"] },    "docs.files.share":  { scopes: ["docs.folder", "docs.doc"] },    "docs.files.delete": { scopes: ["docs.folder"] },  },  scopeTypes: {    "docs.folder": { parent: null },    "docs.doc":    { parent: "docs.folder" },  },});
Check it
app/files/[id]/page.tsxts
// A grant on the folder covers every doc inside it.const ok = await alfiz.can(  { userId }, "docs.files.share", "docs.doc:123",);

Every key is a compile-time type, and alfiz-verify fails the build on a surface you left ungated.

Your apps declare what access existsin the code that enforces it
docs@1.4.0 · published from CIdocs.files.readdocs.files.share
Your team decides who gets itno engineering ticket
Jane Okafor → Project Leadscope · docs.folder:apolloapproved by M. Reyes, 12 Mar
Your auditor can see whyon any grant, on demand
explain() → allowedvia role Project Leadrequest #4182 · manager approval
The problem

Permissions kept outside the code inevitably drift away from it.

A catalog that lives in a console falls behind every time the application changes. Alfiz keeps permissions in the codebase that actually uses it.

Your applications — where access is defined
01

Declare

Your permission catalog lives inside the application it governs.

namespace: docsdocs.files.readdocs.files.sharedocs.files.delete
02

Publish

CI publishes the catalog to your provider as a versioned contract.

publish docs@1.4.0
publishes
Your team — where it is granted
03

Compose

Your team builds roles from published keys and assigns them to people, groups, or a place in the org chart.

role: Project Leaddocs.files.*billing.invoices.read

No field here for a permission your code doesn’t enforce.

04 the grant lands in your own database — can() answers there, in-process
The arrow runs outward from the code, not inward from a console.
Coding agents

Built to support Agent-driven development.

Alfiz is designed from the ground up to be as easy as possible for Agents to develop secure applications without compromising on development speed.

Adding one

Expanding permissions requires minimal context.

Agents only need to edit two lines to add a new permission: once at the catalog, once at the call site. Everything else builds on this foundation.

ts
// ...catalog.ts  "docs.files.export": { scopes: ["docs.folder"] }, // app/actions/export.tsawait alfiz.requirePermission(  { userId }, "docs.files.export", scope,);
Changing one

One search reveals the full blast radius.

One search returns the declaration and every gate that depends on it. The same property lets alfiz-verify work without a type-checker.

bash
$ grep -rn "docs.files.share" src/ authz/catalog.ts:14      "docs.files.share": { scopes: [...] }docs/[id]/page.tsx:22    snap.can("docs.files.share", scope)actions/share.ts:9       requirePermission(p, "docs.files.share")components/nav.ts:31     { permission: "docs.files.share" }

Trust, but Verify.

Agents respond to the immediate task at hand, often losing the bigger picture. alfiz-verify ensures that your agent never forgets to protect new routes.

A surface is not done until all four points hold:

1 · The page

Gated before a row is fetched.

2 · Navigation

Only items the principal matches are rendered at all.

3 · The server action

Gated before any work. The step that gets skipped, so it errors.

4 · Conditional UI

Gated at the scope of the thing it acts on.

ci — every commitbash
$ alfiz-verify  [ungated-action] app/actions/export.ts:14  exported server action "exportDocument" contains no gate  [visibility-as-gate] app/actions/share.ts:15  canAny() is a visibility affordance, never a gate  [unreferenced-leaf] "docs.files.read_history"  declared, but referenced by no gate or nav item

Your first check, in under ten minutes.

Install two packages, declare a catalog, wire the driver you already have, and call can().