Auto-generated transcript Hello everyone, welcome back to this series where we're building a Redis database clone inside Go, right. In the previous part we implemented checksum protection for the RDB snapshots that we create, right. And the way we did this is we basically just write all of the database encoding stuff to the buffer over here in memory. We then write the buffer to the file, right. And we just match we just compare the checksums of the buffer in memory and the file contents that we write on disk and we compare both of their hashes to see if they match right so by doing this we have checksum protection on our RDB snapshots that's what we built in the previous part of this series and in this video we're going to implement the auth command which is basically a Redis command that allows you to authenticate your requests with a password. So the way to do this is first of all we go over to the config file over here and let me just add a comment for auth. The command or the configuration directive that we need to add is require pass and then a password. I don't know what to call this I guess something random... dolphins. Let's go with dolphins. So the password for this server is dolphins just because I can't come up with anything else. Now we have this in our configuration. Let's go over to the cont.go file over here. We're going to add a require pass variable over here and we're going to add a password as well which is going to be a string. All right then let's go down here all the way down to the parse line function inside this cont.go file add another case which will be require pass and we know that if this um if this configuration directive is present in the config file that means that a password is required so we'll say cont.requirepass equals true and we'll say that the password is the second argument over here all right the second argument so the first one is just going to be the require pass um string right the second one is going to be this password the dolphins string that's all we need to do to add the password to the config object over here so now we can actually implement this whole thing and authenticate every single client that we connect to the server Now the way to do this is to create a new client struct that you can use to tell whether each client is Authenticated or not, right? So if I go back to main dot go over here and down here You can see that the way we handle every single connection is we don't even like Create a new struct for it. We just use the connection that we get and we just pass it in over here and Let our code handle it for us, right? So basically we don't add any extra information to every connection, right? Now we need to do that. So let's create a client struct over here. Very simple, at least for now. This is going to have a connection which will be net.con and it will have an authenticated boolean to tell whether this client is authenticated or not. add a function new client which will take a connection and return a reference a pointer reference to a client so let's say return client pass in the connection as well and that's all we need to do so now that we have this in the handle car handle con function up here we should create a new client for every single connection that we get right so let's call the new client function over here pass in the connection and then in the handle function which is used to handle every single connection and execute all of the commands right over here instead of passing the connection directly we should pass in the client struct that we created right let's save this and accept the client over here as well this will be a pointer to the client there we go and down here in the new writer function just pass in c.con to fix this error there we go all good so now let's implement the auth command go over here to the handlers list and add the auth command over here then down here let's implement this by doing by saying auth this will take value state and return a value pointer just like that let grab the arguments as well i believe how exactly did i get these um yeah like this awesome now the auth command should only accept one argument which will be the password so let's make sure that that is the case if the length of this arguments list is not one exactly one then return an error value type error and the error message will be error invalid number of arguments for auth command there we go all right so assuming we get only one argument that argument is going to be the let's say args0.bulk this is going to be the password right and it's a string let's say if the state.configuration.password matches this password that we get from the client right in this argument if this is the case then let's return an a value of type string the string will just be a simple okay success message otherwise we will return another value which will be an error and this will just say error invalid password now that's basically all we need to do but um we also have to somehow authenticate the client right so if i go back to main.go we created this client struct and we want to change this authenticated field in this client to true right if the password matches right so in order to do that in the handler over here what we need to do is we basically just need to accept a client over here as well so this is going to take a bit more boilerplate because we need to add this to every single um handler that we create but um yeah let's do that so basically and up here as well in the handler type over here let's change this so it accepts a client as well there we go and then let's just add this to every single command handler so the get command the set command the delete command exists um keys save and bg save flush db db size and the command command there we go and let's solve this reference error as well over here in the BG say command this C variable is supposed to be the copy of the database so let's just call this CP there we go CP stands for copy not something else guys all right and the handler function over here is going to accept a client as as well there we go and down here we have another error the state.db copy is going to be the cp variable again this is just a copy nothing else all right don't don't don't read too much into the cp thing all right it's nothing trust me um okay so let me just think about everything we just built so we've created the auth command over here now down here we need to just authenticate the client right so let's say c.authenticated equals true and down here if the password was wrong let's say c.authenticated equals false there we go and now we have the auth command right but there's one more thing we need to do if and by by the way all clients are going to be unauthenticated by default right until they run the auth command and give us the correct password they're all going to be unauthenticated which means they should not be able to execute any commands at all right but at the same time there are a bunch of safe commands that even unauthenticated clients can execute right one of those is the auth command because even unauthenticated clients need to be able to run the auth command so that they can give us the password and authenticate themselves right so what we need to do is we need to go up here to the handle function and we want to do two things we want to allow the unauthenticated clients to access save commands right that don't modify the database in any way and we want to block them from executing any of the other commands that are going to modify our database in any sort of way right so let do that first let create the save commands list this is going to be save commands There we go This is going to be a list of strings basically and the only save commands that we have right now are the command command. This is what every single Redis client runs when you start the client right. So if I close this down and if I run this this client again the very first thing it's going to do behind the scenes is going to run the command command so we need to allow this as well as well as the auth command so that these clients can authenticate themselves if they want to right so now we have a list of saved commands awesome now let's say if the state.conf.require pass so if the configuration actually requires a password and this client is not authenticated right this exclamation mark stands for the not symbol and finally if this save commands list contains the command that we just run that we just got from the client then we can basically allow this allow this command to run so let's say contains save commands and pass in the command over here now contains is not a function right this is not some built-in function we need to actually create this but let's just do this and what I'm basically saying over here is if we require a configuration if we require a password in the configuration and this client is not connected and let's add a not symbol over here as well and the command that this client tried to run is not present in the save commands list then we should return an error over here. So let's say writer and let's move the writer up over here. Write this new writer function call. Let's move it all the way up here and say w.write and pass in a value of type error. The error will be no authentication. Authentication required. That's what Redis usually calls this error. Make sure to flush this so it gets written to the network connection and then return because we don't want to execute any of the handlers, anything else if the password was incorrect or whatever, right? Now let's implement the contains command as well. I'm going to do this in a separate file. We'll call this utils.go. This will have package main and let's declare the function over here. this will take a slice which will be of type string list and it will take an item which will just be a string there we go and it will return a boolean so let's say for I in range slice if the item equals I then return true otherwise at the end over here just return false so that's all we need to do and now we can actually try to run this as well and by the way before we do that um in the invalid command handler over here we should return an error to the client because otherwise if i if i run an invalid command let's just say right not only do i not get an error over here but i can't even execute any future commands because i don't get a reply from the server so the client just hangs over here right and that's obviously not good so what we can do to fix that is we can return an error message by saying value type error and just return something like error invalid command make sure to flush this always flush whatever output you write on a network connection and yeah that's all we need to do so close this down and restart the server okay there's an error over here um in the aof file let me just open that up let's see okay so the set command is yeah we need to pass in a client as well to this set command over here so that the aof.sync function can run um i don't want to i don't want to create a new client for this because we don't have a connection over here so what we're going to do is just create a blank client just like we do with the state over here right and just say client there we go and pass that in over here as such awesome okay now let run the server again over here it running successfully perfect Let run the Redis CLI First of all let just see if we can pass in invalid commands And yes, it returns an error just like we configured over here. So now I can actually, you know, run a bunch of invalid commands and it's not going to hang the client for me forever. Right. It's going to return an error message so I can, you know, work with that. Now let's try setting the name to, I don't know, Michael or something. And it's going to return an error because I'm not authenticated. So I can't run any command, right? I can't run a single command until I actually authenticate myself. So to do that, let's run the auth command. And we can, you know, we can try passing in the wrong password as well over here. And it's going to say invalid password. So the correct password is dolphins. run that it's going to say okay now if I try to you know get the name it's going to return the name value get the age it's going to return that I can change the name to again Michael or something and there we go that works as well so yeah that's all we need to do to implement the odd command and everything in this server and with that our Redis clone now has password protection as well. I did some research and the way Redis actually handles this is they have basically their own cloud infrastructure and it has something called ACL. I have no idea what any of these things are. Basically they're a lot more complex and have tons of new features to limit your connections, limit your clients to certain commands, certain access privileges and so on and so forth right but the and this is kind of a new feature i guess but the basic configuration for the auth command is just the you know the password protection right you just pass in a password and that's about it right so i might actually implement this i don't know how the acl stuff works right now but in a future part of this video or this series i guess i might actually implement all of the ACL stuff as well, the access control list. And in that scenario, we'll be able to create, you know, these user accounts with a username and the password. And each of these user accounts will have different permissions, different kinds of access to the database. So that'll be cool as well. But yeah, for now, the auth command works perfectly fine for our needs. And yeah, we're done with this part of the video. Thank you for watching like subscribe share and hopefully you're learning something from this series hopefully you're following along and just really learning new things and understanding how databases like redis work under the hood i honestly i love building projects like these it just you know it just teaches me how all of these complicated systems work under the hood because like you can you can read a bunch of tutorials read a bunch of documentation play around with these tools and develop a pretty solid understanding of these tools right of all of these systems but when you actually build something like this on your own right from scratch that's when you develop the best understanding of all of these systems and how they work under the hood and like it's it's that's that's just so valuable to me you know so i just love building projects like these where i just build a complicated system from the ground up so i can understand exactly how it works under the hood and yeah hopefully you're enjoying this as much as i am um i've got like five or six more parts already ready to be filmed um so yeah this series is going to continue for quite some time right now i'm enjoying building this i'm learning so many new things and i hope you are as well so So thank you for watching and I will see you in the next video in which we are going to probably implement the expire and TTL command so the time to live command expire and If I can spell it correctly and the TTL Time to live command. This is super awesome because this this will allow us to Set keys in our database to automatically expire after some time. Alright, so you can say something like like um like an authentication token for instance right for your users you can say that each authentication token should just expire after one day or one hour or something like that right and these commands will allow us to do that so we're going to build these probably in the next part of this series so stay tuned for that and again yeah thank you for watching this one like share subscribe all those wonderful things and i will see you in the next video until then bye