@actinode/express-permission
v0.1.4
Published
Express middleware for role-based permissions — roles, direct permissions, and permission groups
Downloads
591
Maintainers
Readme
@actinode/express-permission
Role-based permissions for Express — roles, direct permissions, and permission groups.
📚 Full documentation at docs.actinode.com
Installation
npm install @actinode/express-permission
# or
pnpm add @actinode/express-permissionFor detailed installation guide visit docs.actinode.com
Setup with Prisma
Add the following models to your schema.prisma:
model Role {
id String @id @default(cuid())
name String @unique
permissions Permission[]
users UserRole[]
createdAt DateTime @default(now())
}
model Permission {
id String @id @default(cuid())
name String @unique
roles Role[]
users UserPermission[]
}
model PermissionGroup {
id String @id @default(cuid())
name String @unique
permissions String[]
createdAt DateTime @default(now())
}
model UserRole {
userId String
roleId String
role Role @relation(fields: [roleId], references: [id])
@@id([userId, roleId])
}
model UserPermission {
userId String
permissionId String
permission Permission @relation(fields: [permissionId], references: [id])
@@id([userId, permissionId])
}
model UserGroup {
userId String
group String
@@id([userId, group])
}Then set up the instance:
import { createPermission, prismaAdapter } from '@actinode/express-permission'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
const permission = createPermission({
adapter: prismaAdapter(prisma),
getUserId: (req) => req.user?.id,
})Setup with Mongoose
Define your models and pass them to the adapter:
import mongoose from 'mongoose'
import { createPermission, mongooseAdapter } from '@actinode/express-permission'
import type { MongooseModels } from '@actinode/express-permission'
const RoleSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
permissions: [{ type: String }],
createdAt: { type: Date, default: Date.now },
})
const PermissionSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
})
const PermissionGroupSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
permissions: [{ type: String }],
createdAt: { type: Date, default: Date.now },
})
const UserRoleSchema = new mongoose.Schema({ userId: String, roleName: String })
UserRoleSchema.index({ userId: 1, roleName: 1 }, { unique: true })
const UserPermissionSchema = new mongoose.Schema({ userId: String, permissionName: String })
UserPermissionSchema.index({ userId: 1, permissionName: 1 }, { unique: true })
const UserGroupSchema = new mongoose.Schema({ userId: String, group: String })
UserGroupSchema.index({ userId: 1, group: 1 }, { unique: true })
const models: MongooseModels = {
Role: mongoose.model('Role', RoleSchema),
Permission: mongoose.model('Permission', PermissionSchema),
PermissionGroup: mongoose.model('PermissionGroup', PermissionGroupSchema),
UserRole: mongoose.model('UserRole', UserRoleSchema),
UserPermission: mongoose.model('UserPermission', UserPermissionSchema),
UserGroup: mongoose.model('UserGroup', UserGroupSchema),
}
const permission = createPermission({
adapter: mongooseAdapter(models),
getUserId: (req) => req.user?.id,
})Middleware Usage
Protect routes by attaching middleware:
// Require a single permission
app.get('/posts', permission.can('view-posts'), handler)
// Require a role
app.delete('/posts/:id', permission.hasRole('admin'), handler)
// Require any one of multiple permissions
app.put('/posts/:id', permission.canAny(['edit-posts', 'manage-posts']), handler)
// Require all permissions
app.put('/posts/:id', permission.canAll(['edit-posts', 'publish-posts']), handler)Unauthorized (no userId): 401 { error: 'Unauthorized', message: 'User not authenticated' }
Forbidden (lacks permission): 403 { error: 'Forbidden', message: '...' }
For full API reference and more examples visit docs.actinode.com
Helper Functions
assign
await permission.assign.role(userId, 'admin')
await permission.assign.permission(userId, 'edit-posts')
await permission.assign.group(userId, 'content-managers')revoke
await permission.revoke.role(userId, 'admin')
await permission.revoke.permission(userId, 'edit-posts')check
await permission.check.can(userId, 'edit-posts') // boolean
await permission.check.hasRole(userId, 'admin') // boolean
await permission.check.canAny(userId, ['edit', 'manage']) // boolean
await permission.check.canAll(userId, ['edit', 'publish']) // booleanget
await permission.get.roles(userId) // string[]
await permission.get.permissions(userId) // string[]
await permission.get.groups(userId) // string[]Permission Groups
Group multiple permissions under a single name for easy bulk assignment:
// Create a group
await permission.groups.create('content-managers', [
'view-posts',
'edit-posts',
'publish-posts',
])
// Assign a group to a user
await permission.groups.assign(userId, 'content-managers')permission.check.can() and all middleware automatically check group membership.
Custom getUserId
By default the middleware reads req.user?.id. Override it to match your auth setup:
const permission = createPermission({
adapter: prismaAdapter(prisma),
getUserId: (req) => req.auth?.sub, // e.g. JWT payload
})Error Handling
Errors thrown by helper functions are instances of PermissionError:
import { PermissionError } from '@actinode/express-permission'
try {
await permission.revoke.role(userId, 'ghost-role')
} catch (err) {
if (err instanceof PermissionError) {
console.log(err.code) // 'NOT_FOUND' | 'UNAUTHORIZED' | 'UNAUTHENTICATED'
console.log(err.message) // human-readable description
}
}Full API Reference
| Method | Description |
|---|---|
| permission.can(perm) | Middleware: require a permission |
| permission.hasRole(role) | Middleware: require a role |
| permission.canAny(perms[]) | Middleware: require any permission |
| permission.canAll(perms[]) | Middleware: require all permissions |
| permission.assign.role(uid, role) | Assign a role to a user |
| permission.assign.permission(uid, perm) | Assign a direct permission to a user |
| permission.assign.group(uid, group) | Assign a permission group to a user |
| permission.revoke.role(uid, role) | Remove a role from a user |
| permission.revoke.permission(uid, perm) | Remove a direct permission from a user |
| permission.check.can(uid, perm) | Check permission (direct + role + group) |
| permission.check.hasRole(uid, role) | Check role membership |
| permission.check.canAny(uid, perms[]) | Check any permission |
| permission.check.canAll(uid, perms[]) | Check all permissions |
| permission.get.roles(uid) | List all roles for user |
| permission.get.permissions(uid) | List all direct permissions for user |
| permission.get.groups(uid) | List all groups for user |
| permission.groups.create(name, perms[]) | Create a permission group |
| permission.groups.assign(uid, group) | Assign a group to a user |
Issues & Support
- 🐛 Found a bug? Open a bug report
- 🚀 Have a feature idea? Open a feature request
- 📦 Want a new package? Open a package request
- 💬 Questions and discussions? Join the community
- 📚 Full documentation: docs.actinode.com
Documentation
Full documentation, guides and API reference is available at docs.actinode.com
Contributing
Contributions are welcome! Please visit our GitHub repository to get started.
License
MIT © actinode
