So let's go ahead and create the forgot password functionality. So for that I want to go back inside of my components out login form right here. And in here we have to find our password input. So this one right here which has the name password and the input which has the type password. And outside of form control here we're going to add a button component which we already have imported inside of components UI button.
And now inside of here let's go ahead and add a link component which we can import from next slash link. So make sure that you add this import. I'm going to move it up here with the other imports like that. And now let's go ahead and give this link an href of slash out slash reset and then let's go ahead and write forgot password here And let's go ahead and give this button a size of small. Let's go ahead and give it a variant of link.
And let's give it as child option so it properly uses the link inside. And let's also give it a class name of PX0 so it is aligned with the input here and let's also give it font normal so it's not bold like that and now when the user tries to click here nothing will happen because they are immediately redirected back to the login page so what we have to do is we have to add slash auth slash reset inside of our routes So let's find the routes.cs here and we're going to add them to the out routes. So go ahead and add slash out slash reset to the out routes array. And now if I click forgot password, I should be redirected. Let me go ahead and try this again.
There we go. So refresh your page and you should be redirected to a 404 page and your URL should be slash auth slash reset. So now let's go ahead and let's create that page. So I'm going to go inside of my app folder auth and let's create a new folder called reset and inside a page.dsx with a default export of reset page and a div reset page and there we go we now no longer have that error and we can click on forgot password here which redirects us to here. Perfect!
So now obviously instead of this div we're gonna create a reset form. Like that. And we can reuse a lot of elements from our login form. So I want to copy that one. So let me close everything and we will remember that we have that error inside of our reset page.
Let's go inside of components, auth, and let's copy the login form in the auth. And let's rename it to reset form like this Make sure that you are inside of reset form. So confirm right here that your reset form is open. First things first Let's go ahead and rename this to reset form like this and then let's go ahead and let's change the card wrapper here to ask us forgot your password the back button label let's change that to go back to login and the back button href is gonna go back to login like this. And now we can go back inside of the app function, sorry app folder, auth, Reset and we can import the reset form from components out reset form.
Just make sure that you have renamed the export constant to reset form here and it should look exactly the same as the login functionality. So now we're gonna go ahead and modify it. So let's go ahead and let's start by hiding socials. So I'm gonna remove the show social prop and there we go that's gonna remove the socials because that's not something we want to show here. Now what I want to do is I want to remove this useSearchParams so we're no longer going to need that error.
So remove the useSearchParams and now we have an error here so remove this URLError constant. Now we have to go down and find the form error and remove the usage of URL error like that. And now we can also remove the imports we no longer need which is use search params. So let's go ahead and remove that as well. Great and now the only thing we have to have for our inputs is the email.
So the user will enter the email for the account they forgot the password for. So we can find the actual password form field and completely remove that. So find the end of the self-closing tag like this and simply remove it so you should only have the email field inside like this. Great! Let's change the login button to write send reset email here like this.
There we go. So this is already looking a lot better. Perfect. So what I want to do now is I want to go back inside of my schemas. So let's go inside of the schemas folder, index.ts right here.
And let's copy and paste an existing schema, like login schema here. And let's just add an email and call this reset schema. So just an email with a message email is required like that. Now let's go back inside of reset form inside of components out the reset form and let's go ahead and we can remove this link it seems we no longer need that and let's import reset schema from schemas here so now we can fix all the errors where we are using the login schema So this is going to be a reset schema. This is going to be a reset schema.
And this is going to be a reset schema. And as you can see, we have an error for the password field in the default values because we don't need it for this one right here. And obviously we have some issues with our onSubmit function because it's calling the login server action. We're not going to use that server action. We can call the other one.
Perfect. So for now what we can do is log, comment this out and instead let's console log our values here and let's try this out. So I'm going to open inspect element and I'm going to send this to tutorialmailing at gmail.com so we're going to have to use that email from resend right because we're going to be sending some emails now and there we go when I click submit inside of my own submit I have the proper values of my email here and nothing else. Perfect and let's try back to login forgot password there we go both are working perfect so what we have to do now is we have to well you can bring this back and we have to create an equivalent action to actually reset the password. So let's go inside of our actions and let's create a new action reset.ts.
Remember to mark it as use server and let's go ahead and import reset schema from our schemas. Let's go ahead and let's import get user by email from data user and let's go ahead and import everything as z from Zot so we can do server-side validation from here as well. Great, And now export const reset function to accept the, let's make it asynchronous value, which accepts the values, which are a type of z.infer type of reset schema. And then inside, let's validate our fields. So validated fields are reset schema, save, parse, and pass in the values which will come from the props.
If we don't get a success message, so if exclamation point validated fields dot success, return an error, invalid email. So server-side validation in case they bypass the front-end side validation. And then we can safely extract the email from validated fields.data. And now let's find the existing user using await get user by email which we imported and pass in the email so make sure that you imported get user by email from data user and if there is no existing user let's go ahead and return an error which is very simply going to say email not found like that otherwise we can return a success message, reset email, send. Obviously, it's not here yet, so we're going to add a little to do, generate token and send email.
There we go. So now we have our reset function. So we can go back inside of components out and find the reset form here. Let's find where we import the login action and this time let's use the reset action. So import reset from actions reset and let's use the reset and send the values and there we go no more errors inside of our component.
So let's try this out by adding a random email which doesn't exist inside of our database and that should throw an error that the user, well the email is not found so we cannot reset a password for this one. But if I try and use my existing password here, I should get a success message Reset email sent like this. Great! So what we have to do now is we have to visit our schema Prisma and we have to create our password reset token. So let's go ahead and do that.
So I'm going to go inside of Prisma, schema.prisma and let's add a model password reset token. It's gonna have an ID, which is a type of string at ID and the default value of CUID. Let's give it an email of string. Let's give it a token of string and a unique value. And let's go ahead and give it an expires of date time.
And a unique combination of email and token like that. So very similar to our verification token. It has an ID, it has an email, token expires and unique. Yeah, and don't reuse the verification token, right? If you're thinking, oh, we could have just reused it.
It's simply safer to keep tokens separate, right? Security wise, it's better that you have an exact model. You can look up and know this is for password reset. This is for email verification, even if they're exactly the same. So what we have to do now is we have to create utils to fetch these from the database.
But before we can do that, we actually have to shut down our application. So let's shut down Prisma Studio. Let's go ahead and let's shut down npm run dev. Let's do npx prisma generate. So we add those new fields to node modules and npx prisma database push.
So we add them to our neon database right here. Once that is finished we can go ahead and run npm run dev again refresh your local host to ensure nothing breaks And now let's close the Prisma schema. Let's go inside of the data folder and create a new file, password reset token.ts. Let's go ahead and import the database from add slash lib database. Let's export const get password reset token by token to be an asynchronous function which accepts the token which is a string inside of here open a try and catch block in the catch we are simply going to return null and in here let's get the password token to be await database dot password reset token which we just created find unique and where matches the token like that If you're getting an error for DB.passwordResetToken, it can mean two things.
You didn't run npxprisma.generate, or in your schema, you didn't name the model correctly. Or if you did both, then go ahead and shut down Visual Studio Code and open it up again or use command shift p or control shift p and type in reload window right here and that is the equivalent of shutting down your Visual Studio Code and starting it again. And in here let's just return the password reset token. Let's call this properly password reset token like this. Great and now let's go ahead and copy and paste this.
And this one will be named get password reset token by email and it's going to accept the email prop and it's going to use find first to get the matching email and everything else can stay exactly the same. Great! So now what we have to do is we have to go back inside of our tokens lib. So let's go inside of lib tokens. So we have generate verification token.
Now let's go ahead and first let's import get password reset token by email from data password reset token which we just created and let's go ahead and write the function for this so export const generate password reset token you wait to be an asynchronous function which accepts the email which will be passed from this page right here using our server action and then let's go ahead and generate the token using UUID v4. So the same thing we are doing above. Let's, you can copy and paste the expires from the one above. So it will expire in one hour and let's get the existing token using await get password reset token by email and passing the email from the prop like that. If we have the existing token, in that case, let's await database.resetpasswordresettoken.delete And let's delete the one which has a matching ID of the existing token.id which we just found using the email that was sent.
Meaning that the user already has a password reset token, but they are requesting a new one. And finally, let's create a password reset token here using await database password reset token.create Let's give it a data of email token and expires so we have all the necessary information and simply return password reset token. Like that. Perfect! So now we have the ability to fetch the password reset token and the ability to generate a password reset token which is carefully going to look if we already have an existing one so we remove it on time.
And the last thing we have to do is we have to revisit our mail library. So in the lib let's go to mail here and it's going to be very similar. So well let's write it again just to you know ensure our knowledge here. So export const send password reset email is gonna be an asynchronous function which accepts the email, which is a type of string and a token, which is a type of string. And in here, let's write the reset link to be HTTP localhost 3000 slash out slash new password so this is a new route which we're gonna have to create later of course and the same thing we're passing the token and stringify the token like that and then await resent emails sent like this from onboarding at recent.dev to the email subject.
The subject is going to be, subject is going to be reset your password and HTML we can copy from below. So click here and instead of confirm your email, it's going to be to reset your password. So click here and instead of confirm link, it's going to be reset link. And instead of the confirm email, it's going to be to reset password. Like that.
There we go. So we have everything we need. Make sure you don't have any typos. Make sure that this exactly matches what you've been sending before. Later when we do deployment we can change this to go from whatever website or domain you want in production.
And make sure that when you're testing this you are sending this to that email which you have in Resend. Great so now we can finally go back inside of our Reset server action so actions reset here and let's go ahead and let's import send password reset email from Libmail and generate password reset token from LibTokens. And in here we have a little to do which we wrote. So let's go ahead and write const password reset token is await generate, await generate password reset token using the email, which was entered in the form and then await send reset send password reset email and in here let's use password reset token dot email as the first argument and password reset token dot token as the second argument and whenever you do this always double check that the first parameter is actually an email and the second one is a token so you don't have any unexpected bugs. And I believe this should already be working.
So make sure that you enter the email which you can use from Resend right here. Let me copy this and go inside of this button which we have right here forgot password. Make sure that your app is running. Let's send this right here and let's click Resend, send reset email. And that should actually be sending me an email now.
So let's just wait a second. There we go. Reset email sent. And let's look at my inbox here. There we go.
Reset your password. Obviously, if I click here, it's just going to bring me back to login because we have to add that page. And here's one more thing that we can check. So I think my local, my Prisma is not running. So let's go ahead and run our NPX Prisma Studio.
And you can now see that we also have a password reset token which was generated and which was sent to that email. So this is the token that is going to be inside of our URL being sent to this email. Perfect. So we just finished the first step which was sending the email for a new password. The next step will be to create this page which we defined inside of LibMail called AuthNewPassword And in here is going to be a very simple form which is going to, well, take in the new password that the user wants and also use this token to confirm that they have permissions to change their password.
Great, great job.