@ooneex/permission component is a fine-grained access control layer built on CASL. You describe what is allowed as a set of abilities — pairs of an action (read, update, delete, …) and a subject (User, Article, all, …) — then ask whether the current user can or cannot perform an action. Permissions are written as classes that extend the abstract Permission base, registered with the container, and attached to routes so authorization runs before your handler.
Why this component
- Ability-based, not flag-based. Authorize concrete
action+subjectpairs instead of scattering boolean role checks across your code. - User-aware.
setUserPermissions(context)grants abilities from the request context — the same permission class adapts to admins, owners, and guests. - Field-level control.
can()andcannot()take an optional field, so you can allow readingnamewhile forbiddingpassword. - Type-safe subjects and actions.
EPermissionActionships 60+ actions andEPermissionSubjectcommon subjects, with literal types you can extend per domain. - Container-managed. Register a permission class with a decorator and resolve it from the container or wire it to a route.
How it works
A permission is a class extendingPermission. You implement three methods, then build() compiles the rules into a CASL ability you query with can/cannot.
| Method | Purpose |
|---|---|
allow() | Declare baseline abilities with this.ability.can(...) / this.ability.cannot(...). Returns this. |
setUserPermissions(context) | Add abilities derived from the request context (the user, their roles, ownership). Returns this. |
check(context) | Custom gate run before the route handler; return false to deny outright. |
build() | Compile declared rules into the ability. Must run before can/cannot. Returns this. |
can(action, subject, field?) | Whether the action is allowed on the subject (optionally a field). |
cannot(action, subject, field?) | Whether the action is forbidden on the subject (optionally a field). |
can and cannot throw a PermissionException (NOT_BUILT) if called before build(). The MANAGE action and the all subject act as wildcards — this.ability.can("manage", "all") grants everything.
Decorator and usage
@decorator.permission()
Registers a permission class with the container. It accepts an optional scope (defaults to singleton).
Exceptions
The component throwsPermissionException when an ability is queried before the permission has been built. It carries a machine-readable key, a human-readable message, and a data object.
| Key | When |
|---|---|
NOT_BUILT | can() or cannot() is called before build(). |
Best practices
- Always
build()before checking. Compile the ability once after declaring rules; querying before throwsPermissionException(NOT_BUILT). - Keep the chain consistent. Run
setUserPermissions()beforeallow()andbuild()last so context-driven rules are in place. - Use the action and subject enums. Prefer
EPermissionActionandEPermissionSubjectover raw strings to stay type-safe and consistent. - Reserve
manage/allfor admins. The wildcard grants every action on every subject — scope it to trusted roles only. - Move ownership logic into the permission. Pass owner data through the context and decide inside
setUserPermissions()rather than in the controller. - Deny early in
check(). Use it for coarse gates (method, IP, headers) and letcan/cannothandle the fine-grained decisions. - Throw
PermissionExceptionwith a stablekey. Keep keys constant and put variable detail indata.
CLI command
Scaffold a permission class and its test file with the generator. It writes the class undermodules/<module>/src/permissions/<Name>Permission.ts and installs @ooneex/permission if it is missing.
| Option | Description | Default |
|---|---|---|
--name | Permission class name. The Permission suffix is appended automatically. | Prompted if omitted |
--module | Target module the class is generated into. | shared |
--override | Overwrite an existing class without prompting. | false |
Use with Claude and Codex
The generator ships a matchingpermission:create skill. It runs the scaffold and then guides your AI agent through completing the permission — implementing allow() with ability rules and setUserPermissions() with role-based logic. Initialize the skills once for your agent:
- Claude
- Codex
Prompt
permission:create --name=EditArticle, then implements allow() and setUserPermissions() so only the right users can edit articles.