Okay.
So I want to show you how we can easily add role functionality to our authentication and users right
now so that we can provide different roles to our users and ensure that only users with those roles
can access certain routes in our system.
So the first step in this will be going into auth and going to the user DTO in the create user, DTO,
we're now going to accept in addition to an email and password, we're also going to accept an array
of roles that a user can pass in.
So this will be an optional parameter which will just be a array of strings and we'll provide an is
optional decorator from class validator to mark this as optional so that if it's not provided it won't
apply the rest of our validators.
Otherwise we're going to make sure this is an array and then we'll call the IS string validator to make
sure that each value is a string and to make sure each value is validated.
Here we will call each which specifies of the validated value is an array.
Each of the items must be validated, which is what we want.
So each item will be a string.
And importantly we can also say is not empty for each value to make sure that the empty string is not
provided.
So in addition to our DTO, let's go ahead and copy this property, the roles property and add it to
our user model which we know lives in libs.
Common Source models User dot Schema.
Let's go ahead and add this roles property to the actual schema here by calling at prop.
Finally, we'll update the common user DTO to add the new roles array.
So I have my containers running in the background.
I ran Docker compose up and now let's try creating a new user with this roles array.
Firstly, I'll just show that we can still create a new dummy user without roles.
That works fine and you can see by default the roles will be empty for that user.
However, we want to go ahead and supply some custom roles to make sure that they get saved.
So let's provide a new Roles property, which is an array, and I'll have a new admin role that gets
created along with this user.
And you can see we get that user back here.
We can also then try to get this user by going to the auth slash login route with the user credentials,
with the roles, user, log in with this guy and then come back and try to get users.
We can see we have our roles array saved.
So now to actually implement our roles functionality, let's go into Libs common source decorators and
we're going to add a new decorator.
We'll call this roles dot decorator dot TS, and we're going to use Nestjs functionality to easily create
a new class or route decorator.
So to do this we'll export a new const called Roles set it equal to a function.
And so this function is going to take n number of roles, which will just be an array of string that
will spread here.
And then we're going to call set metadata from Nestjs Common that allows us to assign metadata to a
class or function using a certain key.
And you can see the exact example we're using here is what they're doing.
So we'll set a metadata called roles and then pass in the the array of role strings we have.
And then finally, don't forget to export our new decorator by calling export everything from roles
dot decorator.
Now let's actually see this decorator in action.
We'll go directly to our reservations dot controller.
And let's say that we only want to allow reservations to be deleted if we have an admin role.
So we can now call add roles, import this from app slash common and now we can enter in any number
of string roles that this route needs to have so we can have admin owner or any number of custom roles.
I'm going to go ahead and just specify the admin role.
So I want to go ahead and actually use the admin roles decorator now and actually verify that the user
does have this role.
And the perfect place to do that is in our JWT auth guard, the common common auth guard that all of
our protected routes are already using.
We know we're getting back the user from our auth call.
So when we have this user we can check the roles on the user and make sure that they match.
So in our auth guard we can get access to the decorator and its metadata by using the reflector from
nestjs slash core.
This is a new dependency we'll inject.
So injecting new dependency called private read only reflector of type reflector from nestjs core.
Now to get the roles assigned with this route.
In the can activate method in addition to getting and checking the JWT.
I'm also going to go ahead and get the roles by calling this dot reflector dot get and getting.
The role's key, which we set earlier, and then providing context, which is the execution context
passed into, can activate, already get handler.
We can also provide some type information to make sure this knows we're getting back an array of strings.
Now we have the roles assigned to the route handler.
In this case, it will be the controller route that we are calling.
So now, before setting the user on the request, we can check to see if our roles are valid on this
user given the current route we're on.
To do this, I'll loop through a new for loop on our route roles by calling for const role of roles.
So now I'm going to check to see if not rez dot roles.
Dot includes roll, then I'm going to throw a new unauthorized.
Then I want to throw a new unauthorized exception.
And I also want to log out an error so that we can actually know that the user didn't have valid roles.
So to do that, I'll declare a new private read only logger set equal to new logger from Nestjs common
and provide the JDBC auth guard.name for some context.
And then we can call this dot logger dot error where we specify the user does not have valid roles.
Additionally, let's add a new if check to make sure that roles exists before we actually loop through
the roles to do this check.
And I also want to add to our catch error block here to actually get the error in question and log it
out just to make debugging a little bit easier in case we never know why we are getting unauthorized
exceptions is so make sure we still return the error here as an observable as well.
So now we can test this out.
I'm going to log in as the user I created with the admin role here.
So we get the JWT cookie set and then I want to go ahead and get all the reservations in the system
so we can try deleting one with our admin role.
So I'll take the ID off of this reservation, pass it as a root parameter and then call delete to delete
the root.
You can see we have a 200.
Okay status code.
And now if we get all reservations again we should see that we have only one now.
So let's go ahead and try the same thing out with a new user that now doesn't have the admin role.
So I'll create a new user with no roles and send a request to create this user.
Make sure we send a post route to create the user so you can see we have no roles.
Now let's copy this payload to the log in route so we get the JDBC cookie set on that guy log in and
now let's go ahead and get all the reservations and we'll try deleting this one with the delete route.
And if we send this off, we can see we have a 403 forbidden.
And if we look in the logs, we can actually see that we have an unauthorized exception being thrown.
And if we scroll up a bit, we can see the error in the reservation service that we logged out saying
the user does not have valid roles with all of the requests contexts after it.
So this is exactly what we want.
We have basic role functionality now implemented.