Roles and Permissions
Marblism integrates powerful authorization and permissions system.
Simple Admin use-case
If your app have users and admin, everything is already built-in.
Verify User Admin Status
import { useUserContext } from '@/core/context'
export default function ProfilePage() {
const { user, isLoggedIn, checkRole } = useUserContext()
const userId = user?.id
return <>{checkRole('admin') && <div>You are admin!</div>}</>
}
Protect back-end for Admin only
models/document.zmodel
model Document {
id String @id @default(uuid())
url String
// allow only admin to create documents
@@allow("create", auth().roles?[name == 'admin'])
}
Marketplace use-case
Let's say you have a marketplace with Freelancers and Clients. Here is how to make it work.
Set-up
- Front-end: When a User register they can choose between 'Freelancer' and 'Client'
/src/app/(non-authenticated)/register/page.tsx
<Form.Item label="Role" name="role">
<Radio.Group
options={[
{ label: 'Freelancer', value: 'freelancer' },
{ label: 'Client', value: 'client' },
]}
optionType="button"
buttonStyle="solid"
/>
</Form.Item>
- Back-end: Link the role chose by the User
Update the User Registration Endpoint to allow for the new 'role' field to be included in the body request:
src/server/routers/authentication.router.ts
export const AuthenticationRouter = Trpc.createRouter({
register: Trpc.procedurePublic
.input(
z.object({
email: z.string().email(),
name: z.string(),
pictureUrl: z.string().optional(),
password: z.string(),
role: z.enum(['freelancer','client']) // add the allowed list of roles
}),
)
.mutation(async ({ ctx, input }) => {
...
const user = await ctx.databaseUnprotected.user.create({
data: { ...input, password: passwordHashed },
})
...
//associate the role
await ctx.databaseUnprotected.role.create({
data:{userId: user.id, name: role}
})
return { id: user.id }
}),
})
Using it
Check if a User is a Freelancer in the front-end:
import { useUserContext } from '@/core/context'
export default function ProfilePage() {
const { user, isLoggedIn, checkRole } = useUserContext()
const userId = user?.id
return <>{checkRole('freelancer') && <div>You are a freelancer!</div>}</>
}
Protect a back-end endpoint
models/document.zmodel
model Document {
id String @id @default(uuid())
url String
// allow only freelancers to create documents
@@allow("create", auth().roles?[name == 'freelancer'])
}
SaaS Use-case
When you pick "SaaS" on Marblism, it comes already with a multi-tenant system where users can create different Organizations, invite other users in these Organizations and define different roles for their organizations members.
To secure the back-end of your SaaS you will have to edit your models
with the relevant business logic, for example:
model User {
id String @id @default(uuid())
email String
password String
// allow everyone to sign up
@@allow("create", true)
// allow the logged-in user to be able to do anything related to its own user account
@@allow("all", auth() == this)
}
model Role {
id String @id @default(uuid())
name String
userId String
organizationId String
user User @relation(fields: [userId], references: [id])
organization Organization @relation(fields: [organizationId], references: [id])
// allow the owner of a organization to add, update and remove users from that organization
@@allow('create,update,delete', organization.roles?[user == auth() && name == "owner"])
// allow user to see their own roles
@@allow('read', user == auth())
}
model Organization {
id String @id @default(uuid())
name String
roles Role[] @relation("role")
documents Document[] @relation("document")
// allow everyone to create a organization
@@allow("create", true)
// allow members of a organization to access it
@@allow("read", this.roles?[user == auth()])
// allow only the owner of a organization to edit it and delete it
@@allow("update, delete, ", this.roles?[user == auth() && name == "owner"])
}
model Document {
id String @id @default(uuid())
url String
topSecret Boolean
userId String
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], name:"organization")
user User @relation(fields: [userId], references: [id], name:"user")
// allow members of a organization to upload a document to this organization
@@allow("create", organization.roles?[user == auth()])
// allow members of a organization to upload a document to this organization
@@allow("create", organization.roles?[user == auth()])
// allow the user to edit its own tweets
@@allow("update", auth() == user)
// allow an admin to do everything
@@allow("all", auth().roles?[name == 'admin'])
}
Permissions
Your data models is the source of truth of your permission system.
You just have to define how you want to protect your different endpoints ("create", "update", "read", "delete" or "all") and all your API endpoints will be protected.
model Tweet {
id String @id @default(uuid())
content String?
userId String
user User @relation(fields: [userId], references: [id], name:"user")
// allow everyone to create a tweet
@@allow("create", true)
// allow the user to edit and delete its own tweets
@@allow("update, delete", auth() == user)
// allow an admin to do everything
@@allow("all", auth().roles?[name == 'admin'])
}
auth()
refers to the current logged-in user performing the query.
Advanced Permissions
For more complex use cases, refer to the Zenstack documentation.