Skip to main content

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

  1. 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>
  1. 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.