Permissions
A permission is a fine-grained capability string — edit-posts, delete-users, publish-comments. Permissions can be attached to users directly, or indirectly through Roles and Permission Groups.
When permission.can() runs a check it looks through all three vectors in order:
- Direct permissions assigned to the user
- Permissions on all roles the user holds
- Permissions on all groups the user belongs to
Assigning permissions directly to users
await permission.assign.permission(userId, 'edit-posts')
await permission.assign.permission(userId, 'view-drafts')Assigning a permission the user already has is safe — it is a no-op.
Revoking permissions
await permission.revoke.permission(userId, 'edit-posts')WARNING
Revoking a permission that was never assigned (or does not exist) throws a PermissionError with code NOT_FOUND.
import { PermissionError } from '@actinode/express-permission'
try {
await permission.revoke.permission(userId, 'ghost-perm')
} catch (err) {
if (err instanceof PermissionError) {
console.log(err.code) // 'NOT_FOUND'
console.log(err.message) // 'Permission "ghost-perm" not found'
}
}Getting all direct permissions for a user
This returns only the permissions assigned directly to the user — not those inherited through roles or groups.
const perms = await permission.get.permissions(userId)
// ['edit-posts', 'view-drafts']Checking permissions in middleware
Use permission.can() to protect a route:
app.get('/posts',
permission.can('view-posts'),
(req, res) => res.json({ posts: [] }),
)
app.post('/posts',
permission.can('create-posts'),
(req, res) => res.json({ created: true }),
)
app.delete('/posts/:id',
permission.can('delete-posts'),
(req, res) => res.json({ deleted: true }),
)Unauthorized (no userId):
{ "error": "Unauthorized", "message": "User not authenticated" }HTTP status: 401
Forbidden (lacks permission):
{ "error": "Forbidden", "message": "Missing permission: delete-posts" }HTTP status: 403
Checking permissions via helpers
Use permission.check.can() in business logic:
const canEdit = await permission.check.can(userId, 'edit-posts')
if (!canEdit) {
throw new Error('Not allowed')
}canAny — require at least one permission
canAny passes if the user holds at least one of the listed permissions. Use it when multiple roles or levels should all have access.
Middleware:
app.put('/posts/:id',
permission.canAny(['edit-posts', 'manage-posts']),
async (req, res) => {
await updatePost(req.params.id, req.body)
res.json({ updated: true })
},
)Helper:
const canProceed = await permission.check.canAny(userId, ['edit-posts', 'manage-posts'])TIP
canAny is equivalent to an OR check. Great for situations like: "editors or admins can do this."
canAll — require every permission
canAll passes only if the user holds every listed permission. Use it when an action requires multiple capabilities together.
Middleware:
app.post('/posts/:id/publish',
permission.canAll(['edit-posts', 'publish-posts']),
async (req, res) => {
await publishPost(req.params.id)
res.json({ published: true })
},
)Helper:
const canPublish = await permission.check.canAll(userId, ['edit-posts', 'publish-posts'])TIP
canAll is equivalent to an AND check. Great for two-factor capabilities: "you must be able to edit and publish."
canAny vs canAll — quick comparison
canAny | canAll | |
|---|---|---|
| Passes when | user has at least one | user has all |
| Equivalent to | OR | AND |
| Typical use | multiple roles allowed | compound capability required |
Assigning permissions to roles
See Roles → Assigning permissions to a role.
Real-world example
// Direct permissions for a service account
await permission.assign.permission(serviceAccountId, 'read-metrics')
await permission.assign.permission(serviceAccountId, 'read-logs')
// Route: only users who can both read AND export
app.get('/reports/export',
permission.canAll(['read-metrics', 'export-data']),
async (req, res) => {
const report = await buildReport()
res.json(report)
},
)
// Route: editors or admins can update
app.patch('/content/:id',
permission.canAny(['edit-content', 'admin-content']),
async (req, res) => {
const content = await updateContent(req.params.id, req.body)
res.json(content)
},
)