LongCut logo

Better Auth Organizations – Members, Roles & Permissions (Part 2)

By OrcDev

Summary

Topics Covered

  • Custom Roles Unlock Flexible Access
  • Relations Power Organization Members Table
  • Server Actions Simplify Add/Remove Members
  • Permission Checks Block Unauthorized Actions
  • Role-Based Enforcement Secures Organizations

Full Transcript

In this video, we are going to implement member management roles and permissions to our better o starter. We're going to create a single organization page with

all the members from that organization inside one table. Me as an owner here, I can add other users inside of our

organization and I can also remove them.

But if we come in as totally another user, in this case we are this orc which is just an ordinary member. And if we click remove, we are getting you are not

authorized to remove members message.

This video is part of the better oath starter series. You have the playlist

starter series. You have the playlist link in the description below and you have also the link to the better off starter GitHub repository. It is open

source so you can use it to learn about Nex.js, JS Shad CNN and better o and let's start building this thing.

Currently in our better o starter we are able to create our own organization where we become the owner of that organization and we have this organ organization switcher where we are

putting which organization is currently active. So now we are going to the

active. So now we are going to the better o documentation again. So here

clicking to docs then we are going to plugins here clicking on organization and then in this right sidebar we are

clicking to access control. So here we have all the roles and everything that is needed for the features that we need to build. So the organization plug-in

to build. So the organization plug-in provider a very flexible access control system. You can control the access of

system. You can control the access of the user based on the role they have in the organization. So what we need to do

the organization. So what we need to do from here is to create our own custom permissions. So we are going to use all

permissions. So we are going to use all the defaults from better o here. So

first thing we need to do is to create the permissions.ts file and then inside we need to create our roles. So we are going to create

our roles. So we are going to create member admin and owner. We are not going to create any custom roles yet. We are

probably going to do that in the future.

And we have create, share, updates, and delete permissions. So I'm going to copy

delete permissions. So I'm going to copy this file permissions.ts.

And that one needs to go inside of our O directory. So we can see it here when we

directory. So we can see it here when we are later passing the rows to our plug-in. We are importing it from

plug-in. We are importing it from O/permissions.

So we are going to our lib directory where we have our o.ts file. And here

I'm going to create o directory. You

don't have to do this of course. This

can be anywhere. It just needs to be imported inside the o.ts. And here we are creating permissions.ts file. So I'm going to copy and paste the

file. So I'm going to copy and paste the entire thing from the documentation except this my custom role. We don't

need this part because we are going to keep only the member admin and owner.

And now we are going to export everything from our file. I don't know why AI is not doing its thing but we are exporting here me admin member and owner

from our permissions file and not only that but also the AC here and the statement. So we are going to export

statement. So we are going to export everything because we need that one for our o.tes if we check it here inside of

our o.tes if we check it here inside of our documentation. So now we need to

our documentation. So now we need to pass this whole object to our organization plug-in. So we are going

organization plug-in. So we are going inside the O. TS here. We are going to the organization and I'm pasting in the object that we took from there and we

are just going to import everything from our o/permissions except this custom role. We also going to delete that from here. So we don't

need that part. And that's it. We

basically already implemented roles inside of our application but we now need to use them and to create a good

infrastructure for managing our members and everything else that comes. So now

when we implemented this let's go to the dashboard page and here we are going to put all the organizations and next to them we're going to put a button as a

link to go to a single organization page. So for that we need inside of our

page. So for that we need inside of our server actions here we need get organizations. So it's this one and we

organizations. So it's this one and we are going to get all the organizations that we belong to. So here we are getting actually our user and we are checking in which organizations we

belong. So we are going to call that one

belong. So we are going to call that one inside of our dashboard organizations.

Here we are just going to get the organizations and below our create organization button. We are going to map

organization button. We are going to map through each of those organizations and we are going to put a button instead of

this div. And that one is going to be a

this div. And that one is going to be a link like this. We're going to use next link. We are putting it inside of our

link. We are putting it inside of our button.

And we are going to put here as child like this. And the link is going to go

like this. And the link is going to go to the dashboard slashorganization and then slashorganization slug. We have

our slug. So we are going to use that one. And here we are going to leave the

one. And here we are going to leave the organization name. So let's see if we

organization name. So let's see if we have everything working. Here it is. So

we have my organization orcs. Those are

two organizations that I belong to. And

we are just going to put this button to be type not type the varant outline like

this. So it looks nice. And here we are

this. So it looks nice. And here we are going to put one simple H2 organizations like this. So now we have like some

like this. So now we have like some distinction between create organization button and these links. So now we need

that actual page inside of our app directory and then dashboard I'm going to create a new directory that is going to be called organization and inside of

that again new one which is going to be called slug and there we are creating page.tsx

page.tsx file. Nice. And here we're just going to

file. Nice. And here we're just going to start export default like this. So AI

finishes the work for us. Or maybe not.

Here it is. Awesome. And now the params, it's actually not up to date. So we need our type params to be a promise like

this. So we can await it inside of our

this. So we can await it inside of our server component. Nice. So now we have

server component. Nice. So now we have our param. And now we need get active

our param. And now we need get active organization. But this one is searching

organization. But this one is searching organization by ID. So we are not using that. We are going to create a new get

that. We are going to create a new get organization by slug like this. Awesome.

So we are going to put here try catch like this. Nice. And here organization

like this. Nice. And here organization by slug instead of this one because it's messing up the naming. And there it is.

So now we are searching the organization by slug and we can use this one inside of our slug page. So here importing that

one removing the get active organization and now we can put actually here our organization name. So let's see if this

organization name. So let's see if this one is working. I'm going to click on the organization orcs. We are going to that page organization orcs. And here in the top left corner we can see that it

is actually working. We are getting the organization by slug. Awesome. So now we need here a nicel looking table with all the members from this org's organization. And for that we first need

organization. And for that we first need to create our relations. So we are going to our schema file. And here below the

organization we are going to create export const then organization relations like this. And we are going to

put here members many. So we need all the members from our organizations and we are importing relations from Drizzle

and then below the member we are going to create just the reversed organization one which is belonging to each member.

And now we are just going to export also those relations here for our Drizzle config. And we can now actually inside

config. And we can now actually inside of our organizations by slug we can call here with members

and we are going to get all the members from our organization. And this is also a good thing from AI. It needs to be with user also. So let's add this one

also to the relations here for our member. So each user belongs to one

member. So each user belongs to one member. We need there we have email,

member. We need there we have email, username etc. So we are going to add that one as well and add it here to our

export. So adding it come on AI the

export. So adding it come on AI the member relations. Oh we already have it.

member relations. Oh we already have it.

Okay my bad. So now if you go back to organizations we are getting the relation members and then users. So here

inside of our slug page we can actually try to call it like this JSON stringify organization and we are going to see there if we have

any users. So here it's null I'm doing

any users. So here it's null I'm doing something wrong. Let's see what's

something wrong. Let's see what's happening. Let's see actually if we had

happening. Let's see actually if we had any errors. Yeah we have some issues. So

any errors. Yeah we have some issues. So

we there is not enough information to infer relation organization members.

Okay. So that one is inside of our schema. So here in the member relations,

schema. So here in the member relations, we need to put something like this. AI

knows it. So it's fields. We just need to reference what are we shooting here for our organization. And we are going to do the same for our user. So like

this member user ID and here it references user ID. Yeah. Now it should work. Let's

user ID. Yeah. Now it should work. Let's

see. Yeah, there it is. Awesome. So now

we have here we can see actually my user being inside of the members array. So

that's nice. We have all the members.

Now we can just display them inside of some nice looking table. So we are opening of course sheden and we are searching here for the table and we are

just going to install it inside of our project. So we're going to ghosty here.

project. So we're going to ghosty here.

I'm running latest add table and then we are going here to import this table. So that one is going to

this table. So that one is going to slug. But we are not going to just put

slug. But we are not going to just put it here because we want this one to stay a server component. We're probably going to turn this inside the to be a client

component. So inside of our components

component. So inside of our components I'm going to create members table.csx.

And there we are going to create that whole table file separately and we are going to copy paste everything from

the usage of shed CN as always and we are going to receive here members. So do

we have here member? No, we have only from the organization plug-in. We don't

want that. We are going to our schema and we are going to export type members.

So we are inferring our member table here that we have. We don't need better o for something simple as this. So we

are now importing member from our DB schema and we are going to map through our members. So here we are going to put

our members. So here we are going to put we don't have name we have username and email ro joined. No there we're going to

put actions. So we are going to put

put actions. So we are going to put remove from the organization there. And

now here we are going to map through our members. And here we are actually

members. And here we are actually missing user inside of our member type.

So here we need to add it. We are just going to put here and and to add that we have user inside of our member when we are calling it. So now we don't have

problems here and we just need to import the button from shed cen. We're going to put it as

shed cen. We're going to put it as destructive. Nice. Okay. Yeah, it can be

destructive. Nice. Okay. Yeah, it can be also small. Nice. So this one looks now

also small. Nice. So this one looks now okay. We can go back to our slug page.

okay. We can go back to our slug page.

Instead of this JSON stringify, we're going to put members table and we are going to send all the members from our organization. Now here this is also a

organization. Now here this is also a good thing. We need to put some let's

good thing. We need to put some let's say maximum width of 3 XL and MX auto

and maybe padding of let's say 10. Okay.

And we are going to put organization name here. So let's see how is this one

name here. So let's see how is this one looking here. Awesome. We just need to

looking here. Awesome. We just need to put a little bit more of padding. But we

have our user which is only me currently in our organization. So we can see my username, my email, my role which is

owner and we have action remove. So now

we need to add another user to our organization which is going to be a member. And because we are not going to

member. And because we are not going to implement invitations to organizations yet, we are just going to list here all the users that we have inside of our application. So inside of our

application. So inside of our components, let's get back here. I'm creating a new file and we are going to call it all

users.tsx.

users.tsx.

So here I'm going to export that one and we are going to receive all the users inside

of our component. So I'm going to call the user from our schema again. So we

are going here export type user from the user infer select. Okay, nice. So here

I'm just going to import that one from our DB schema like this. And this one is going to be a client component because we are going to add that add member

button on our users here. So now we are just going to map through all of our users. We are going to put our username

users. We are going to put our username and then right next to it we are going to put a button

like this add to organization. Nice. So

now we are going to our slug page again. And here we are going to put all users but we need another

server action because we don't want to add users that are already in organization. we need inside of our

organization. we need inside of our users server here we are just going to create

get users like this. So we are going to get all the users that we have inside of our database and we are going to call

that one inside of our slug page. So

here I'm going to call all the users and that one is coming from the server users like this. So now instead of calling it

like this. So now instead of calling it like this we have two await calls. So we

are going to add here organization comma users and we are going to use promise all. So this one is

going to be faster. And now here we are going to send all of the users inside of our new component right here. So we have

now here orgdev which is me and that means that we need to remove all the users that belong to this organization.

That one is going to be easy inside of our slugs page. We are just going to send here organization ID and this await promise all was for nothing after all

because in order to have the organization ID we first have to await our organization just like this. And now

inside of our get users we are going to pass in the organization ID which is a string. And here we are going to call

string. And here we are going to call for our members like this. We need to import our member table from our schema.

And AI already knows it. So here we are putting not from drizzle and in array.

So all the members that don't belong to this organization ID, they're not going to be displayed here. So there it is. My

user is gone. And now we need to create a new one. So we don't have here a logout button. Actually I'm going to the

logout button. Actually I'm going to the header. And let's put here a div.

header. And let's put here a div.

And how it knows now that I want here a logout button. So we have log out

logout button. So we have log out like this. We are going to put it to

like this. We are going to put it to look nice. So we have the log out

look nice. So we have the log out button. Nice. We can now log out and we

button. Nice. We can now log out and we can sign up a new user. That one is going to be called orc and it's going to be the orgdev plus testgmail.com.

And here some password. I'm signing up.

There it is. Nice. Now I need to verify my account. I copied the link. I'm

my account. I copied the link. I'm

pasting it in. And now I'm going to log back to my owner account. So that's the normal org dev logging in. And now if we go to any

logging in. And now if we go to any organization let's say orcs we see the new user orc and we have the add to organization button and here we need to

create that add member logic to our organization. So we're going to create

organization. So we're going to create now inside of our survey directory a new file that is going to be called members.

So we're going here creating new file members.ts.

members.ts.

That one is also going to be uh server actions file. And we are going to create a new add member function just

like this. But we are not going to use

like this. But we are not going to use our database directory. Although we

could because there shouldn't be any problem. We have everything. But let's

problem. We have everything. But let's

use our better o API. So here we're going to add member. And this one is really easy. We're just calling this

really easy. We're just calling this await API add member. So let's put that one inside of here. So I'm passing this

one adding o from our lib/oth. We don't

need team ID. We are still not using teams. And we are here going to just

hardcode the member role and user ID is going to come from our params here. So

we are passing the user ID, organization ID and we are going to hardcode it to member for now. But before doing that we

are going to our schema. So here inside of our member we have a role which is just a simple text role and by default it's a member. So what are we going to

do? We are going to export just like

do? We are going to export just like this role which is going to be posgress enum from drizzle and we are going to put there member admin and owner and

instead of that simple text we are going to put that enum that we created right here and we are also going to export that type ro to use it throughout our

app. So now for example we could come

app. So now for example we could come back here to this add member and we can add here ro and that one is going to be type ro that we have inside of our

schema. So we can actually send it here

schema. So we can actually send it here when we are adding our member like this.

And now we are just going to add quickly try catch to catch if we have any errors inside of our app like this. Nice. We

don't need actually these messages. We

are just going to throw a new error. And

now we can put this method inside of our all users right here. So on our button we are going to put on click add member

and we are going to send the user ID and also the organization ID. We are just going to pass it here inside of our component. So we are calling this one

component. So we are calling this one inside of our slug page. We are just going to pass the organization ID as well. And now we can here put the

well. And now we can here put the organization ID like this. And we are sending our role to be a member. And we

can quickly put just the standard stuff.

So is loading. We are adding our state handle add member. AI knows exactly how I love these things to be done. So here

we are adding if is loading then we are adding loader two with size four and

animate spin. Nice. So here we are just

animate spin. Nice. So here we are just going to put also try catch and finally we are going to put set is loading to

false like this.

And we are also going to put here toast messages from soner. And also in case of the

from soner. And also in case of the error we are going to put fail to add member to organization. And we can also

here put router refresh. We are going to put router from use router from next navigation like this. So we actually refresh our table and let's try it out.

So we are going here. We have the orc and we need to add him inside of our organization. Currently, we're not doing

organization. Currently, we're not doing any permission stuff. So now anyone can add to organization. We need to fix that later. So now I'm clicking add and that

later. So now I'm clicking add and that user is added successfully to organization and that part is working.

So this is great. Now we need to add the remove functionality and after that we are going to check permissions if we can actually remove and add users to our

organization. So let's quickly implement

organization. So let's quickly implement this remove user. So we are going again to our members and here we are going to

create a new remove member method and this one it's really easy instead of using the o API remove member we are

just going to call DB and then import like this and just delete where member equals to this user ID. Now, we could do

this also through the better off API, but I wanted to show you exactly how it's done also from our side because it is really easy. It's nothing special.

It's literally just deleting one row from our database. So, we are deleting the member. We could also of course

the member. We could also of course implement soft deletion or some things like that in future and we definitely will. We are building this project

will. We are building this project slowly. So now we are actually deleting

slowly. So now we are actually deleting the member where user ID is this one that we are sending to our remove member

server action and we are going back to our table. So here we have this remove

our table. So here we have this remove button. We cannot put it here because

button. We cannot put it here because this one is not a client component and it's not really a good use case here to turn the whole component to the client

component only because of one button.

And I had this question on one of my previous tutorials for this project. Is

it really good to create one separated component for something like this? And I

think it is not maybe for one remove button. But in this case, we can put

button. But in this case, we can put here bunch of some actions like remove, addit or something else. So we are going to create inside of our components here

members table action.tsx

table action.tsx and there we are going to put actually this button. So we are putting the

this button. So we are putting the button inside of our actions right here.

So this one we are importing it from shed CN. And now here we are going to

shed CN. And now here we are going to call on click and we are putting remove member from our server actions. And here

we are putting use client because we need to turn this component inside to be a client component. And we are sending the user ID. So we need to send actually

this one to our member stable action component. And here we can play around.

component. And here we can play around.

We can put here state. We can set all that loading to true and everything that we need for our client side to look nice. We can also add toast messages

nice. We can also add toast messages removed from organization etc. And we can just call that handle remove member

here and where is our loading also the router. So everything the same AI

router. So everything the same AI already knows it. What am I doing usually in these kind of components and here it needs to be disabled if loading

and to put loader two in case it is loading with size four enemies spin.

Nice. Everything looks great. So now in the members table we are putting here member stable action and we are sending

our member ID from here. So let's see what's wrong. It's something inside the

what's wrong. It's something inside the member stable action. Let's see some. We

are not closing probably this one. Yeah, we're not closing here

this one. Yeah, we're not closing here the brackets. Nice. So now it should

the brackets. Nice. So now it should work. There it is. And we have here

work. There it is. And we have here remove button. So now if we try to

remove button. So now if we try to remove this orc from our organization, it's loading and member removed from organization but it's not working. So

let's see what is happening. Actually I

know exactly what it is. So here in the members yes so we are removing by user ID and we need to do it by ID. So here

we are going to change to member ID actually. And then inside

actually. And then inside here we're also going to change it to member ID and we are calling this one

inside of our table. So here we are just going to call it me member ID. And now

when we return back here, if we remove the orc, there it is. It's working. So we removed him from the organization and we can add

him back inside with this button. So

this part is working and everything that is left is to set the permissions. So we

don't allow anyone who is inside the orcs organization to remove and add members. For that we are going back to

members. For that we are going back to the better documentation. And here under permissions we have access control usage. So this is the API that we are

usage. So this is the API that we are using actually to check the permissions from our users. So we are using the headers. There we have all the

headers. There we have all the information that we need if our user has permissions or not. So I'm going to copy this part right here and I'm going back

to the server directory and there I'm going to create permissions.ts ts file.

So that one is also going to be a server actions file and we are going to check here. So if can edit member this is good

here. So if can edit member this is good but we are going to make it more simple.

So I'm going to name it is admin. And

here we don't even need any parameters because we are using everything from headers. And we are going to put in the

headers. And we are going to put in the O API has permission. And we are going to put in headers from our next headers.

And here we can put create, delete, update, share. We have I think read and

update, share. We have I think read and delete like this. It's not even read.

Let's check in the other permissions file. So it's create, update and delete

file. So it's create, update and delete like this. And we are going to remove

like this. And we are going to remove the comment. So here we can actually get

the comment. So here we can actually get the success and error from this API call right here. Not errors

error like this. And this error right here is actually returning a nice looking message. It's not something like

looking message. It's not something like fatal error 500. So we are going to return that one. If error we are going to

return something like this success false and error we are going to send that error message from here like this. So

error or we are going to put fail to check permissions. If something happens

check permissions. If something happens and we don't have that error message and here we are just going to return success. Success is actually the

success. Success is actually the boolean. So we always know if it was

boolean. So we always know if it was successful or not. And again we are just going to put this inside a try catch

just in case if something happens. And

we are going to return the message also if something bad bad is happening with this one. So now we can use this is

this one. So now we can use this is admin on any API call that we need or in our case on our server action call. So

here inside of our members when we are removing the member first thing we are going to do is actually to check if the

user is admin. So we are here checking the permissions. So we are going to call

the permissions. So we are going to call this admin and if our user is not admin we are just going to return some message that you're not authorized to remove

members. Here we just need to import

members. Here we just need to import this is admin from our permissions and we can now test it out. So here we are

going to our orgs organization. I'm going to add this

orgs organization. I'm going to add this org to the organization. And now I'm going to remove him. So this user should do it. Yes, this one is working.

do it. Yes, this one is working.

Awesome. So I'm going to log out and I'm going to log in with that another user.

So I'm going to log in with my other user which is ordev plus testgmail.com and I'm going to try to remove the

orgdev user from orcs organization. So

this shouldn't work. Let's try remove from organization. Oh. Oh it's actually

from organization. Oh. Oh it's actually no it is good. It is working but we are not showing the right toast message. So

here in the remove that one is on our table actions. So we are calling remove

table actions. So we are calling remove member and we are not showing the right message. So we need here success and

message. So we need here success and error. Same like for that one. So we are

error. Same like for that one. So we are sending here like this. We need to send here success true and here also success

false. Okay. Nice. And now inside the

false. Okay. Nice. And now inside the actions again in case if it is not success then we're sending the toast

error and returning and doing nothing.

And here this one. Awesome. Let's see if this one is working. So now refreshing.

I'm trying to remove the owner. And

you're not authorized to remove members.

How cool is that? And we probably cannot remove ourselves also. Yeah. So this one is working. and permissions are working.

is working. and permissions are working.

So we have now this entire flow working.

We can remove the members. We can add new members to organization. We have

permissions and some rules. We can

create now of course can edit, can delete and some things like that for our permissions. But this one is for now

permissions. But this one is for now working fine and we have some beginning for all the next features that comes to our app. So tell me in the comments

our app. So tell me in the comments below what would you like to see next. I

was thinking about the invitation for our organization. So we can send an

our organization. So we can send an email to invite other members so they can come sign up then join the organization right after signing up. But

there are plenty of features that are left from better o. So, tell me in the comments what do you

Loading...

Loading video analysis...