After the previous lesson, our application is now locked down, only allowing users with specific permissions to perform specific actions. Particularly here, we’re interested in our Admins ability to delete events. But we’re left with a problem. Our app currently doesn’t know if someone is an Admin or not, so we just show a Delete button to anyone and let it fail if they’re not. Bummer.
The good news is we can use the Appwrite Get Teams endpoint where we can first look at all the Teams our user is currently included in and try to see if Admin is in that list. This will allow us to tack on some additional information to our global state, which we can take advantage of to dynamically show the Admin-only bits in our app.
In this lesson, we’ll look up all of the Teams for our current user. We’ll look through that list and see if we find a Team object where the ID matches the ID of our Admin team which we can handily find in the Appwrite console. And once we confirm whether or not they’re an Admin, we’ll leave the Delete button hidden, or show it, depending on their access level.
What You’ll Learn
Instructor: [0:00] we've done a great job locking down our application, to make sure that people can't perform certain actions that they don't actually have access to do.
[0:06] Even if they can't technically do something, we're still not giving the greatest experience, where we're showing you why that they can't actually use. Now, on top of that, if they even try to use it, we're not even showing any errors, which we'll get to in another lesson.
[0:19] For now, let's not show that button to begin with. In the last lesson, we learned how to create a team and add users to that team. In particular, we wanted to be able to add somebody as an admin.
[0:30] Now, in order to get this information into our application, we can use the teams API, where because we don't know the teams ahead of time, we have a few options for how we can do this. We can either use the getTeam endpoint, where if we pass an ID, we can try to read that team object. If we don't, it'll just fail as a request.
[0:47] Alternatively, we can use the listTeams endpoint, which will list all the teams available for the current user, which is a little bit more flexible in how we can think about using this in the future. It'll also avoid a failed request, which in my opinion, might just look a little bit cleaner.
[1:01] To do this, we're going to use the teamService, and we're going to use the list method on teams. Now, heading into our code, the first thing we want to do is make the teamService available to use.
[1:11] I'm going to head over to libAppwrite.ts, where inside of our Appwrite import, we're going to additionally add teams. Now, like any of the other services, we're going to now export a new instance of that called teams, that's going to create a new instance of teams.
[1:26] Now, once that's available, we kind of have some options for how we organize this code, where technically you could probably use libAuth.ts. I want to create a new user file, which in my mind is getting information about the active user, as opposed to the authentication methods.
[1:41] I'm going to create a new file under lib called user.ts, where first I'm going to import teams from at libAppwrite. Then I'm going to export a new async function called getTeams, where inside, I'm going to say constant data = awaitsTeams.list, where I know that inside of data, I'm going to get this teams property that technically I could destructure, but we can see that's going to give a conflict.
[2:06] I'm going to return a new object where I'm going to specify teams as a property, where I'm going to pass in data.teams. Now, in order to use this, we want to be able to put this in a position that's going to allow us to globally take advantage of that information. The perfect place to do that would be inside of our newUseAuth hook.
[2:23] I'm going to first import getTeams from at libUser, where if we head down to useAuthState, the very first thing that we do is we try to make a request to get the currentSession.
[2:34] Now it probably only makes sense to get the team information if we have a current session to begin with. While technically we could probably just stuff it underneath this request, that doesn't give us a lot of flexibility for say, for instance, if the session changes. What I'm going to do is I'm going to clone this useEffect instance.
[2:50] I'm going to first get rid of the code inside, so we don't run that twice. What I'm going to do is I want to say, I want this to run every time my session ID changes, but then I'll say, I only want it to run if I have a session ID. As our useEffect dependency, let's addSession, Optional chain, $ID.
[3:08] Before we even run that asynchronous code, I'm going to say, if we don't have a SessionID, I'm going to bail out. If we do have that session, I'm going to say constantDestructureTeams from awaitGetTeams. Let's go ahead and just console log that out, just to make sure it's working.
[3:24] If we look inside of the code, we should be able to see that teamsArray, which we should only have one team right now, which is that admin. Now we want the application to know if this person is an admin and we can do one of two things. We can either say, I want to just check if that name admin is there, or I can check the actual ID.
[3:41] Now, while it's probably easiest to just go by the name and just use the word admin, that might change depending on how somebody wants to reflect that. It's probably just a safer bet generally to go by this ID. The way that we can find this ID is heading back to our Appwrite dashboard, going to teams.
[3:56] If we go to our team, we should be able to see and copy this team ID right at the top of the page. Now, you know me. I don't like to just store these random ID values inside of the code. I want to store it inside of an environment variable file, just in case that we have a different environment or if we want to change it.
[4:11] I'm going to open up .env.local. I'm going to add a new variable and let's call this myTeamAdminID, where then I can go ahead and paste that ID. Now we can say constant is admin, is equal to teams, and let's use the find method. For each team, I want to find if this team.$ID is equal to my import.meta.env, where I'm going to paste in that environment variable name.
[4:38] Now what's going to happen is if it doesn't find a team, this is going to be undefined. If it does find a team, we're going to get an object. Just to make sure that this is going to give us a Boolean return, we can add bang-bang to the front of this, which will make this either true or .
[4:52] Let's go ahead and log this out. Inside of my browser where I'm logged in as an admin, I can see that it says true. Inside of my incognito session, I don't even get that logged out because I don't have a session. Once I do have a session with my non-admin account, I can see IsAdmin is . Perfect.
[5:09] Now we want to persist that throughout the application. I'm going to create a new state instance, where I'm going to simply call that IsAdmin, where then I can say SetIsAdmin. Now this isn't going to be a session, this is just simply going to be a Boolean, where the safest bet is to start this off as .
[5:26] We don't want to assume that somebody is an admin before we actually try to figure out if they are. Once we find out if they are a admin, let's run "Set is admin," where we'll go ahead and pass in that value.
[5:37] To actually persist this globally, we'll go ahead and go down to our return object where we can pass this through our return statement. If we remember from an earlier lesson, we need to make sure that we also type this out. Otherwise this isn't going to work. Up inside of our auth context, we can add "is admin" where we'll make it optional. We'll call it a Boolean.
[5:55] Now let's actually use this thing. Inside of the event file, we're not even currently getting the authentication status because we didn't have anything on the page that we needed it for. First, I'm going to import "use auth" from "at hooks use auth." Then I'm going to say "constant." I'm going to destructure "is admin." Where that's from, "use auth."
[6:16] The place that I want to use this is I only want to show this delete event button if they are an admin. Let's say "is admin" and where I'll move my paragraph with that button right inside. Once ready, back in my admin account we can see that I still see that button. Back in my user where I am not an admin, I no longer see that button.
[6:36] Further, if I log out and even try to refresh the page, we see that I no longer get that button. The nice thing with this is Teams gave us an easy way to be able to create a group of people from within our application. In particular, we wanted to recognize people who are specifically admins. Because of the flexibility with this, we can create as many teams as we want.
[6:55] As we saw earlier, we can even get more granular with roles if we wanted. For now, this is perfect for our admin use case. We still have some issues within this application. Technically, we shouldn't know how to get to events.new. Even if we do and we try to submit an event, we can see that this just silently fails in the console.
[7:12] Whether it's because this person isn't permitted to submit this in the first place, or maybe there's some kind of other error, we want to make sure that we're able to actually show these exceptions or these errors inside of the UI so we can give some feedback to our users for what's going on within the application.
[7:28] Next up, we're going to talk all about error handling, where we're going to see how we can read errors from AppWrite, catch unexpected errors, and even see how we can persist that in the UI to make sure we're delivering as great of an experience as we can.