oRPC
Learn how to use Permix with oRPC
Overview
Permix provides a middleware for oRPC that allows you to easily check permissions in your middlewares. The middleware can be created using the createPermix
function.
Before getting started with oRPC integration, make sure you've completed the initial setup steps in the Quick Start guide.
Setup
Here's a basic example of how to use the Permix middleware with oRPC:
import { os } from '@orpc/server'
import { createPermix } from 'permix/orpc'
interface Post {
id: string
title: string
}
// Initialize oRPC with context
interface Context {
user: {
id: string
role: string
}
}
const orpcPermix = os.$context<Context>()
// Create your Permix instance
const permix = createPermix<{
post: {
dataType: Post
action: 'create' | 'read' | 'update'
}
user: {
action: 'delete'
}
}>()
// Create a protected middleware with Permix
const protectedMiddleware = orpcPermix.use(({ context, next }) => {
// You can access context.user or other properties to determine permissions
const isAdmin = context.user.role === 'admin'
const p = permix.setup({
post: {
create: true,
read: true,
update: isAdmin
},
user: {
delete: isAdmin
}
})
return next({
context: {
permix: p
}
})
})
The middleware preserves the context and input types from your oRPC middlewares, ensuring end-to-end type safety in your API.
Checking Permissions
Use the checkMiddleware
function in your oRPC middlewares to check permissions:
const router = orpcPermix.router({
createPost: protectedMiddleware
.use(permix.checkMiddleware('post', 'create'))
.handler(({ context }) => {
// Create post logic here
return { success: true }
}),
updatePost: protectedMiddleware
.use(permix.checkMiddleware('post', ['read', 'update']))
.handler(({ context }) => {
// Update post logic here
return { success: true }
}),
deleteUser: protectedMiddleware
.use(permix.checkMiddleware('user', 'delete'))
.handler(({ context }) => {
// Delete user logic here
return { success: true }
})
})
Accessing Permix in Middlewares
Permix is automatically added to your oRPC context, so you can access it directly:
const router = orpcPermix.router({
getPosts: protectedMiddleware
.handler(({ context }) => {
// Check permissions manually
if (context.permix.check('post', 'read')) {
// User has permission to read posts
return getAllPosts()
}
// If not explicitly blocked by middleware, you can handle permission failures here
throw new ORPCError('FORBIDDEN', {
message: 'You do not have permission to read posts'
})
})
})
The context.permix
object contains one method:
check
: Synchronously check a permission
Using Templates
Permix provides a template helper to create reusable permission rule sets:
// Create a template for admin permissions
const adminTemplate = permix.template({
post: {
create: true,
read: true,
update: true
},
user: {
delete: true
}
})
// Create a template for regular user permissions
const userTemplate = permix.template({
post: {
create: true,
read: true,
update: false
},
user: {
delete: false
}
})
// Use templates in your middleware
const protectedMiddleware = orpcPermix.use(({ context, next }) => {
const p = permix.setup(
context.user.role === 'admin'
? adminTemplate
: userTemplate
)
return next({
context: {
permix: p
}
})
})
Custom Error Handling
By default, the middleware throws an ORPCError
with code FORBIDDEN
. You can customize this behavior by providing a forbiddenError
option:
Static Error
const permix = createPermix({
forbiddenError: new ORPCError('FORBIDDEN', {
message: 'Custom forbidden message'
})
})
Dynamic Error
You can also provide a function that returns different errors based on the entity and actions:
const permix = createPermix<Definition>({
forbiddenError: ({ entity, actions, context }) => {
if (entity === 'post' && actions.includes('create')) {
return new ORPCError('FORBIDDEN', {
message: `User ${context.user.id} doesn't have permission to ${actions.join('/')} a ${entity}`
})
}
return new ORPCError('FORBIDDEN', {
message: 'You do not have permission to perform this action'
})
}
})
The forbiddenError
handler receives:
context
: Your oRPC context objectentity
: The entity that was checkedactions
: Array of actions that were checked
Advanced Usage
Async Permission Rules
You can use async functions in your permission setup:
const protectedMiddleware = orpcPermix.use(async ({ context, next }) => {
// Fetch user permissions from database
const userPermissions = await getUserPermissions(context.user.id)
const p = permix.setup({
post: {
create: userPermissions.canCreatePosts,
read: userPermissions.canReadPosts,
update: userPermissions.canUpdatePosts
},
user: {
delete: userPermissions.canDeleteUsers
}
})
return next({
context: {
permix: p
}
})
})
Dynamic Data-Based Permissions
You can check permissions based on the specific data being accessed:
const router = orpcPermix.router({
updatePost: protectedMiddleware
.handler(async ({ input, context }) => {
const post = await getPostById(input.postId)
// Check if user can update this specific post
if (context.permix.check('post', 'update', post)) {
// Update post logic
return { success: true }
}
throw new ORPCError('FORBIDDEN', {
message: 'You cannot update this specific post'
})
})
})