Rebuilding Jagr.Co, Password Reset & Account Locking

In this livestream, we'll add a system to lock users' accounts after so many bad login attempts and we'll also add in the password reset flow.

Published
Nov 16, 21
Duration
2h 23m

Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.

Adocasts

Burlington, KY

Join me live as I begin rebuilding Jagr.Co with AdonisJS. In this livestream, we'll add a system to lock users' accounts after so many bad login attempts. We'll add in the password reset flow. And, we'll start on adding social authentication. I have some off-screen work to do on the social auth to ensure my tokens and secrets are correct and will continue this next weekend!

Transcript

Looking for something specific? Feel free to search the page for keywords to find a specific portion of the live stream that interests you most.

0:02

okay let's try this again yeah there we go this time it seems like it's working

0:08

all right that was weird um so let me go back

0:19

and

0:25

let's see because yeah now that one is a live replay and it's just me talking about how the stream wasn't starting so

0:31

let me delete that one it's gone and now we're

0:37

live and now i need to retweet out the new link my goodness

1:10

oh my goodness let's see

1:57

okay sorry about that all right

2:03

so as i attempted to say the first time um before i accidentally had my

2:09

environment variables up which is not good that's not the right thing um

2:15

kind of have a brief agenda for today's stream so first and foremost i want to fix the course so that it's working and

2:21

then get the course field added into all the existing forms and then we'll be working on social

2:26

authentication and then bad auth attempt lockout so if a user tries to lock in or log in and they get their password wrong

2:32

so many times it kind of forces the user to need to reset their password and then we need to have in order for

2:38

that to kind of be feasible a password reset flow and then we'll add an auth and roll

2:44

check to studio i'm not sure if we're going to get to all of that today but at least it gives us enough to work with

2:51

um so let's go ahead and get started with the course

2:56

um so let's see so

3:02

last stream i was having an issue where i was trying to add in the csrf field

3:07

and it was saying that it wasn't actually available so here i can recreate it really quick so if we go

3:13

back to the auth and sign in and we add in

3:20

csrf field um also i did some ui updates so this is what the

3:25

it's now got a header it kind of looks giant with my screen scale but bear with me here but so if i try to go to log in

3:32

here i was getting the csrf field is not a function so the issue there is that it's not a function that's registered

3:39

because i did not have the ioc adonis add-ons shield middleware set

3:44

within my kernel.ts so that needs to be added into the global middleware there

3:49

so as soon as i save that come back in refresh now that csrf field is valid and

3:56

there and i think i need to move this down just a smidge there we go

4:02

and so now if i try to log in with a user

4:07

it works so that middleware is checking that csrf field making sure that the csr

4:13

value that's within that field is valid if it isn't kick the user out with an error otherwise

4:19

we'll get what we just had right here which is a continuation of whatever we have the logic going there

4:25

so let's go ahead and get that field added into all of our forms real quick so we just added it on the sign in

4:34

and you also see that i have a honeypot component commented out that's a custom component that i carry through project

4:40

to project i will be adding that in at some point as well i might have it in there and comment it out as a reminder

4:46

so there's the one for our sign up and then i think the only other feel that we have so far is the crete and edit for

4:52

the post so all right i did not stub it in here so

4:57

we'll just do csrf field

5:02

if i can spell and there we go

5:08

oh i got the wrong chat up sorry got the old video up

5:15

okay now i'll get the right chat

5:21

cool so that should do it for the csrf stuff we should now be a okay on that front um so

5:28

we can go ahead and move into social authentication i could go ahead and test everything but i'm fairly certain that

5:34

everything would work okay um and so this is what the

5:40

uh studio now looks like for the time being at least uh so it's got a header

5:46

and then i kind of stubbed the table so i created an actual table component uh that looks kind of like this so it

5:53

can either take a specific t head or it can take an array of columns and it will spit out

5:59

those columns for you and then it can either take uh the actual paginated data

6:05

rows or it can take a t body as well and then later on i'll be adding in a node data state as well

6:13

and then if it is paginated there's a pagination component to this as well which is

6:19

mostly just checking whether or not stuff is a page and redirecting stuff to that applicable page

6:25

um using the get urls for range as the main thing so

6:32

that's what populates the url for these little numbers here so yeah not not too much work but just a

6:38

little bit to make things a little nicer looking so yeah we can go ahead and test

6:46

let's see so testing csrf just to make sure that this is okay um

6:52

and then as i remembered last time safari requires each one of these to be defined at some

7:00

point i'll put a if it's not uh provided to default to the current

7:05

date time and then i put a wysiwyg in here it's not styled or anything so it's kind of hidden um but it's something basic

7:12

so there we go and there we go and oh i broke it

7:19

what do we got here cannot define publish that date on post model since oh i'm trying to save

7:25

all right so let's take a look at i that we left without working but

7:31

evidently not no okay so yeah i think i guess we put it

7:36

in the validator and yeah publish that date publish at time

7:41

is not an actual member of the table we could make it a member but

7:48

yes um yeah this will be open source

7:53

and the code's actually out on github github right now so

8:00

um let's see so we need to take the publish that date

8:06

publish at time out and instead mutate it into what the column is which

8:11

i think is just publish at for the actual post creation so

8:21

publish at date publish at

8:29

time and oh yeah gotta spread out the data too

8:35

okay and then

8:40

oh okay that's what i did i did it for the update but not for the uh yeah cause i thought that we had it

8:46

working so i just need to kind of copy and paste what i did for the update and then move that into a service here

8:52

later on i kind of want to focus more on the social auth stuff today so we'll just

8:58

get it going for right now so then yeah

9:04

spread in the data and then plop the publish at at the end okay

9:10

so now that should be good if i copy and pasted everything over from our publish

9:16

so let's try this one more time all right cool kept all my stuff

9:22

and there we go cool so we got our testing csrf right down here so csrf is working a okay now

9:29

okay so on to the social authentication stuff i'm going to pull up the documentation for it

9:35

i don't quite remember what the package is exactly called it was called ally um but i don't remember it's just

9:41

adonisjs ally or did i pass it i must have passed it off

9:48

social off there we go okay yeah it is just the dos.js shall we so we'll install that npm i

10:01

already plopped the environment variables in here so hopefully it won't overwrite them

10:07

there is if i can spell

10:16

configure oh what do i have um i know i got github and then i think the

10:22

other one's just google

10:27

okay so grab google because i did not

10:33

put the rules in envy.ts

10:41

and github where are you there okay

10:51

so we have that done next

10:59

we have the config which it should have created already and then we got the drivers all right so

11:05

where's my code here we go so i'm going to create

11:11

let me get this stuff cleaned up here another controller uh specifically apart

11:17

from the off controller specifically for social auth so i guess i'll name this off social

11:22

controller so we'll do node ace make configure node ace make controller

11:29

off social controller okay

11:35

and then within here let's see we need

11:43

the redirect and the callback route

11:50

sorry so the redirect is pretty straightforward i think so we got

11:57

do github redirect

12:03

have i been putting spaces after them i can't remember yes okay i'll stick with that

12:10

and we should now have ally within our http contacts contract so let me go ahead and

12:15

actually import or uncomment that out http context contract

12:22

okay is that right i think that's right

12:29

no not quite not driver use okay there we go

12:35

and then we'll have another one for the callback which

12:41

no no no for this response and off

12:47

um and then let's see

12:52

i think i'm going to extract most of the functionality for this out into a service

12:57

so oh i don't have any services yet so i'm going to create the folder okay so we'll do new

13:02

um services slash

13:08

social service i guess we'll stick with the same naming pattern so auth social service dot ts

13:14

and then i want to do another service as well that i tend to like to

13:19

extend off of let me pull that up on the side real quick just so i can take a look at it

13:31

oh i'm looking at the wrong project okay here we go

13:38

you could use a route param romances you could use a route param to know the provider that's very true i

13:44

could oh tried switching

13:51

let's see so yeah

13:56

all right i'll i will keep that in mind and alter to match that that's a good

14:03

idea thank you um so let's oh now i want to create

14:10

another one that i like to extend off of so this one is http context service

14:17

and this uses uh let's see what's the name of that

14:28

property use async local storage there we go

14:35

which allows me to get my um http context kind of in an asynchronous

14:41

manner synchronously but you know outside of the route um normal route flow

14:51

so within here and i just closed it let me pull that back up there we go so i have export default class http contact

14:59

service and this will take a protected

15:06

ctx of type http context contract

15:25

there we go all right and then for the constructor

15:33

we will do this dot ctx equals and see i also need to import http context

15:39

from that nope it's a default export

15:44

there we go httpcontext.

15:50

get or fail okay so then we'll do another get off of

15:56

this for the user return this ctx off user

16:04

and then i put some validation stuff on here as well but um we'll leave it at this for right now

16:10

and then for this export default class of social service

16:20

extends http context service

16:31

uh let's close it sorry

16:36

okay

16:43

so then we have let's see

16:50

for this we want to do

17:06

so we'll have a check for errors call that kind of takes care of this checking

17:12

and then we'll have another one to actually get the user

17:19

which

17:29

yeah i don't know how i don't know how i pieced this together but all right

17:38

so we'll have public and let's see i'm changing it up here so this will no longer be

17:44

static public async async get user

17:53

taken social provider

18:00

uh let's see i already have the http context so i don't need that

18:06

um const social equals ctx and that nope this

18:13

dot ct x dot we should have ally use

18:18

social provider and then we need to have another one to check for the errors so we'll do

18:27

private check for errors

18:32

which at that point no it doesn't need to take that in at all we'll take in the social provider though

18:47

of type google driver contract or github driver no not config

18:54

my bed driver contract there we go i don't think that imported

19:04

github driver contract and then i don't want that but instead

19:10

we'll do it if social dot

19:15

access denied this ctx

19:23

session flash error

19:29

keep it basic if social dot

19:37

mismatch this ctx

19:48

request expired please try again

19:58

if social has error

20:07

that's pretty close yeah we'll stick with that um

20:13

instead of just error to get air

20:25

okay and then if it doesn't run into any of those we'll just return true

20:31

so then up here we will do let's see we already got the use so then we will do if not this

20:39

check for errors social

20:45

return false

20:51

const and then we need to get the find or create user

20:57

so we'll add that in as well so we'll do

21:02

another private method here this one will be async find or

21:08

create user which will also take in the social of the same

21:14

contract types um and then this one also

21:19

looks like i need the social provider

21:30

which is of type key of social providers that's just auto fill i'm going to turn

21:36

that off uh

21:42

pilot toggle off there we go

21:50

okay so we'll do const user equals await social dot

21:55

no yes yes i didn't turn it off i guess i needed to click one of those

22:03

typescript okay and then

22:08

we'll do const username equals await this

22:14

oh i got another method get unique username

22:20

okay gotta do collision checking so user dot name there and then we'll add in another

22:27

private method async get unique username username

22:35

string username equals

22:42

oh i was using that same sluggify package here did i leave that installed

22:49

let's see

22:58

no it does not look like it all right let me install that again real quick

23:18

okay

23:31

so username equals and then we'll try to slogify the username

23:39

just to make sure that it's a valid username within our system const

23:45

cur well no this isn't needed is it

23:51

no it is not sorry about that

23:57

so last uh stream on

24:03

the user itself we added where is it

24:11

it should be on the username

24:17

i thought i added it oh no it's on the post it's not on the username okay

24:22

well i guess we could add it to the username too

24:30

maybe and we'll see we'll see what happens

24:39

oh let's see no i think that would be for like a different field

24:54

look at the documentation for real quick

25:13

you

25:34

well it doesn't hurt to try

25:39

so we'll see if we can tack it on to the same field

25:45

essentially what i want is if the social authenticated user

25:51

if their username is already taken and the email doesn't match then i want

25:56

the username to then be incremented via a sluggification um

26:02

so previously i was doing that manually but we'll see if we'll see if um adonai sluggify can do it for me

26:08

if not then we'll fall back to doing it manually that's no biggie where was i

26:15

here okay so i'll go ahead and type this out i

26:21

guess just in case that way we have it there equals

26:27

sluggify username const

26:32

currencies equals await database from

26:39

users where raw

26:45

username like

27:04

all right oh i started it with a change you to a double

27:09

and you to a double and what am i missing now this

27:17

where did this guy come from

27:23

0-9 there we go

27:29

okay and then here if it did everything right is where

27:34

we tack in the array with the username it says extra

27:41

yes okay and then username equals

27:46

occurrences dot length

27:53

username occurrences otherwise when that comic oh

28:00

oh that's nope yep sorry that was a hair on my screen i thought it was a comma

28:05

otherwise username okay and did i not import it no i didn't okay

28:13

i'm gonna go look and see what that name username was import

28:18

slider five from it okay so we'll we'll leave that there

28:25

just in case we need it but we'll try

28:31

using adonis slugify for this first so username would then be

28:38

just user.username in that case so i will just for right now comment that out and we will just try

28:46

uh user.name okay

28:52

and then we will want the token key so this will need the social provider

29:02

access token and then we will return

29:07

our user find or uh

29:13

first or create sorry

29:19

so we'll search for the email user dot email

29:27

squiggly is for oh

29:32

yes okay there we go

29:38

username the username

29:44

well we're gonna do that then let's just comment the whole line out let's do username user.name

29:51

and then avatar url is user dot

29:57

avatar url otherwise under defined undefined

30:02

uh sorry if i seem a little out of it um yesterday i fasted the whole day for

30:10

blood work um i ended up fasting for a full 24 hours and so

30:16

i didn't really eat until really late last night and

30:21

sleep was just a whole nother story so

30:26

might be a little out of it today

30:31

roll.user oh wait we got a squiggly here what's the squiggly

30:38

oh okay i don't know if i added the avatar url to

30:44

my user evidently not i bet you that was in a

30:51

later migration that i had not yet copied over uh we'll take a look at that here in a

30:56

second but for right now we'll just ignore this quickly uh so

31:01

i don't know did my role not import oh uh no i want the enum

31:07

right yeah import

31:13

from app enums we got one for roll yeah

31:19

all right so i'll actually name that rolling them so that i know that's not the role model but it's the role enum

31:25

um and then that will take the token key user dot

31:31

dot okay so let's take a look at what's going on with this user url uh let's see

31:38

so i think i still have my old project over here so let's take a look

31:44

migrations migrations migrations database migrations

31:51

anything in here that would state i did not really name these all that well so

31:57

bear with me here for a second

32:06

double check and make sure it wasn't a member of the initial user no

32:12

there's the auth attempts

32:17

and post add post ad post add user access tokens ah there it is

32:23

okay so i could just copy over this migration i

32:29

think i might go ahead and just tack this onto the initial user

32:35

migration and just um rerun my migrations

32:45

since we don't have any data currently that we really need to keep besides our test users which

32:50

aren't really all that important so the user here just tack these on

32:56

see i ended up not doing twitter but i'll leave it on there just in case i decide to add it in the future

33:02

um yeah so that should be good and then just need to add those to

33:10

the model so make sure i still have those copied here we'll go ahead and add those onto the model so let's see user

33:17

model

33:22

put it before they created an updated app okay

33:36

um oh what casing is that camel

33:42

there we go

33:49

oh why did i copy password

33:57

i do not know that would already be defined let's make sure that i didn't do that on the migration too go and define it twice

34:06

no reason for a user to need two different passwords yeah i did okay there we go

34:14

what i must have changed something with that right oh yep made it nullable

34:21

because the user signs up with social authentication and they don't really need a password in our system now do they so

34:28

change that from not nullable to just nullable

34:34

okay i figured there must have been a reason why password was defined in there again okay so let's go ahead and just rerun

34:40

the migrations with the sign up again but that's no big deal so node ace

34:47

migration roll back i i think everything's already just on

34:53

batch one but we'll go ahead and just specify batch zero just in case my creation run

35:00

okay so and then i have a seed don't i what did i name well just do dbc then

35:08

i'll run it asdb seed there we go

35:15

and then we will need to log back in

35:24

sign up there we go or should we just wait and test the um

35:30

oh i didn't look at that i didn't make this scrollable how did i do that

35:37

okay well we'll wait and just try using a social user

35:44

so we'll go ahead and finish that up so we have let's see did we finish

35:50

this all right now why is roll id

35:56

yeah there we go okay let's see so we never finished the

36:02

thought we finished our methods that we needed but we never finished the thought with the actual get

36:08

user here so let's finish that up so const user equals awaits

36:13

this dot find or create user for social for the social provider

36:21

and then we will return back um see previously i was doing an array with

36:27

uh the whether it was successful and then returning back the user but i think this time it would make more sense to do

36:36

is success true and then tack on the user and i

36:42

will do is success false and

36:48

do user of i don't know i guess null

36:53

so we'll try to get the user using the provided social provider we'll check if

36:59

there were any errors for their login uh if there were then we will kick it

37:04

out as unsuccessful if there was then we will try to create

37:09

finder create the user within our system so we'll try to find the first based off of the provided email for the social

37:15

authentication excuse me if it cannot be found then it will create it using the following parameters

37:24

and then we are not currently using this but we have it there just in case we end up needing it

37:29

because we're going to try to see if the adonis slugify

37:35

will take care of the username for us so the service should now be good so let's

37:41

go ahead and actually work on the controller here

37:58

so like roman said we could use a route parameter to know what the

38:04

provider is so let's see let's take a look at the routes here i'm gonna

38:10

take a look at the old project see what i was doing so you can see with the old one i had individual routes defined for github google and i never

38:18

did twitter because i couldn't get the token or i never got the token

38:24

for it but we still have google and github individually defined so

38:31

instead of individually defining it like that where's my auth at here's my auth

38:37

um by using a route parameter what we could do is instead of

38:42

manually having github we could do just um something like provider there

38:49

and provide there we go i spelt it right provider

38:55

there which kind of gets cuts back on the redundancy that i had

39:00

and then instead of having this be github redirect and github callback it would just be

39:07

just i guess redirect callback and then this would

39:12

be well i wouldn't make much sense to just have a route called redirect uh

39:18

social see we got all of the other ones prefixed with auth so let's do auth

39:23

social redirect social

39:29

callback i should really put all of the auth routes inside of a group um

39:35

so there we go we got that with the route param there really should be a matcher on there too

39:42

but we'll test it just like this first to make sure i got everything working and then let's see we named this just

39:48

redirect and then this one just callback and then the

39:54

params params.provider would be this right here so params.provider

40:01

and then let me take a look and see what i have for my callback so uh

40:07

this is what i've been referencing off to the side um is my old code base just seeing what i was doing

40:15

so off social here so for the callback i'm just going to go

40:22

ahead and copy and paste this and we'll alter it to match what we need here

40:31

so first and foremost we need our auth social service within our auth social controller

40:37

so i'm just going to go ahead and inject it

40:46

public social service

40:52

is off social service

41:02

and then instead of having that like that it would be

41:08

and then instead of this being a manual provider name it would be coming from the params

41:14

so instead of github here it'd be params.provider

41:19

and then instead of returning back an array i now have is success and user as an

41:26

object and then

41:32

why did i do this where's you even used

41:38

i'm not seeing a you i was down here so i'm doing is getting the id for that

41:44

kind of looks like it yeah okay so we'll cut that out

41:52

and if it's not successful then we'll redirect back to login uh really that should be whether

41:59

they're trying to sign up or log in then it should redirect back to the appropriate one of those but we'll change that here in a little bit

42:05

let's get everything working first um and then we did we do the profile yet

42:13

don't think we did sign up no no profiles being created yet so

42:19

we'll skip on that for right now we'll go back through and add that in

42:24

later and let's see

42:31

what do we need here argument type user null is not assignable to parameter of type user

42:38

okay so that the cast was just given over squiggly because user is not imported so there we go

42:45

cast that to a user and yeah so let's go ahead and test it um

42:53

hopefully i'm already logged into a provider so this will be a nice easy step oh

42:58

hold on a minute we got to figure out why this won't allow me to scroll because i can't even get down to the buttons um

43:06

i don't know how i did that let's see uh

43:11

all right let's take a look at that would probably be in the layout i think that i messed that up in let's take a

43:17

look no no not in the layout well maybe

43:24

just tack on overflow auto on that see what that does so let me scroll now

43:40

try here well that's not quite what i'm going for

43:46

either ah

44:05

no i'm not enough style here let's do i'm just gonna break it just to make it work um for

44:12

right now so max height 200 pixels there we go now we can get to it

44:19

okay so i'll try google first i guess so um let me go ahead and make sure that i

44:26

have those routes so yeah so i have these manually defined um

44:35

which sure um but let's go ahead and use

44:40

so let's see let's do route and then i name this off social

44:46

redirect and then the param of the provider here would be

44:52

uh what is this one google google

44:59

and then there's there's the sad little twitter commented out and then down here is

45:04

github so we'll do the same thing for github off social

45:10

redirect provider and

45:16

github okay so let's try google

45:23

nope oh social controller what do i got going on here can i find module

45:29

oh oh okay yeah no has a s on it than auth socials

45:34

i'm gonna go ahead and rename that um to auth social

45:43

okay

45:49

so let's try that again okay

45:55

oh okay that thing that i was running into again with that package let me go ahead and remove that import

46:06

and i'm just going to go ahead and comment that method out really not sure what's up with that i

46:13

haven't looked into it yet at all okay let's try this one more time

46:21

that's not the error i was hoping to see [Laughter] i don't know how to take a look at my

46:26

emv without showing it to you guys um

46:31

shucks let's see i don't have a second screen on here is the issue otherwise i'd drag

46:38

this off to the second screen

46:45

um

46:52

i'm gonna play peek-a-boo all right so here you can take a look at my old project while i take a look real

46:58

quick in my emv oh i can't scroll down far enough

47:05

all right come on you

47:12

i'm gonna make that super small so i can squeeze it down in the blank area

47:18

and where is my env here it is all right

47:29

yeah okay i never wrote so let me see if i can no i can't undo

47:34

shucks all right so i'm going to do the same thing with this screen

47:40

this is oh i can't make this one super small shoot

47:48

fudge well i tried um

47:56

all right well all right let me put over my other screen here my desktop and take a look and see how long these are maybe

48:01

i could just type it out real quick ah that's not too bad okay

48:09

github client id

48:18

okay there's the github client id github client secret

48:23

i don't know i'm telling you what i'm typing in

48:43

i really hope i type that in right oh google you're killing me

48:50

it's long

49:02

chances i get this right pretty slim but we'll try

49:17

okay

49:40

okay so here's the hoping that i got that right let me expand this back out

49:48

and let's try that again

49:53

refresh just to make sure fingers crossed

49:58

oh my gosh all right ah

50:07

all right why am i unauthorized

50:28

so let's see

50:37

first and foremost is this a me error is this something i'm throwing i don't remember typing

50:44

no i've just got to redirect back so if it was a me thing then we would have just went back to the login page

50:52

um unless i don't recall putting it and i'm not

50:57

throwing any errors in here it's just all session stuff okay

51:05

so i did could be

51:11

that i either got the id wrong i don't

51:18

see so we got the call back if we're getting to the callback i would

51:23

think that the id and secret would have been all right i didn't pay any attention to see what it was

51:30

trying to log me in as um uh let's see sign up let me try

51:38

the other one um github missing token

51:44

missing invalid oauth 2 response missing auth token

51:54

social user

52:06

so that is throwing no not in here in the service

52:11

right here

52:18

missing access token so let me take a look at my old project

52:24

real quick

52:29

be in service be in the service where's the service here's the services

52:37

okay

52:48

all right i don't think i'm doing anything all right typed anything in wrong there

52:57

all right so let's i guess make sure that the um providers coming in is github okay

53:09

so let's see we did not get to the callback at all on oh we did we got to call back on github

53:15

okay well yeah okay yeah because it got to the get user call

53:22

um well yeah okay so yeah the params would not be there no it

53:28

would it would so console.log

53:35

params make sure that we got that um and within the get user

53:40

let's follow that flow use provider social provider

53:49

which would be that string let me make sure that that's right

54:00

ally used and then it would be the provider name which gives you back what i have stored is social

54:08

which then you could do github.user or in my case social.user to get the user

54:15

so use so that should be github so let's go ahead and test to make sure that we are

54:20

actually getting the provider back we should be but for sanity's sake

54:27

hit that okay check this let me scroll down to the bottom i don't

54:33

know how i accidentally scrolled all the way up to the top uh params provider github

54:39

okay so that is coming through right am i passing it in right

54:46

params provider yep okay

55:32

yeah so we'll see what social has that

55:45

sorry um

55:53

my keys so we won't take a look at that after

55:59

all

56:10

i'm gonna have to go and clear out my um social keys after this stream

56:18

um all right let's see so without taking a look at

56:24

that then what could we do so we've got our finder create user

56:31

here

56:41

kind of at a loss at how to debug this without showing my tokens again

56:47

um maybe i will take a look at this off stream and come back to it next week

56:53

like i did with the other thing um and we'll just move forward

56:59

as opposed to wasting too much time trying to figure out stuff that i can't currently take a look at without showing

57:05

you guys too that needs to be a secret um so let's see what was next

57:12

bad off attempts that's something that doesn't deal with any tokens so i don't think

57:20

um don't think i have that

57:26

in here no so let me take a look and see what i had for the migration for that um

57:37

bad attempts there you are so i'll go ahead and just copy this and we'll make a new migration and all that

57:42

for it and

57:48

let's see what did i actually have that table called it was just bad attempt off attempts oftentimes to ace make

57:55

model off attempts of off attempt

58:01

and we will want a migration and we don't need a controller because that'll just be handled with the actual

58:07

auth attempt so there we go

58:13

and then on that migration if we get back to the new project here

58:18

database off the temps paste that in

58:24

uh don't need timestamps i already got that down there give that a save

58:30

and go ahead and copy these

58:37

and we'll plop that on the auth attempt model

58:54

only deleted that is nullable okay

59:00

so we'll do that and then i'll go ahead and tag the ordinal on deleted app just so i don't forget it

59:06

and instead of timestamp you will be a datetime and instead of an integer you will be

59:13

a number

59:26

okay and then let's see

59:36

okay so that should be good now and then we'll want a service for that oh let me go ahead and run my migration before i

59:42

forget to do that notice migration run

59:48

there we go so we got our off attempts um

59:54

and then we'll go ahead and make a service for it so we'll do off attempt service so file

1:00:02

off attempt

1:00:07

service

1:00:21

and i don't remember if i need http context on this at all let me take a

1:00:26

look nope okay

1:00:32

so i'm going to go ahead and place a public static property

1:00:37

of the allowed attempts at the top

1:00:42

so we'll just allow three for right now so if a user gets their password wrong three times then we'll need to go through the password reset flow

1:00:50

um so we'll have a pub if i can spell public static so this

1:00:57

will be all of these methods will be static so we won't need to instantiate a new instance of the service

1:01:04

async attempts take the email string will return back

1:01:11

a promise with a number and this will return back

1:01:16

uh the number of current attempts that the user has so const

1:01:22

what the heck did i just spell period date time dot

1:01:29

nail dot so within the past two days

1:01:41

i didn't import daytime there we go

1:01:47

okay so within the past two days uh so we'll get a date for two days ago

1:01:52

the const attempts so awaits auth

1:01:58

attempt query dot where

1:02:04

created at is greater than our

1:02:11

period and where email is our

1:02:16

provided email where

1:02:22

null deleted that and i'm going to go ahead and

1:02:29

drop those down onto separate lines there we go then just return back the

1:02:36

attempts well why am i doing that let's get a count

1:02:44

count the id um and then instead of

1:02:49

doing that we will do dot extras

1:03:07

all right so what is attempts here yes that is not what attempt is

1:03:15

so let's see we got the count we're getting the first record so that we could read the extras

1:03:24

all right i'm just going to ignore that everything's going red there try to finish this up oh gotcha optional might not exist

1:03:34

because i didn't do first well on that we do want it to be optional i guess right

1:03:40

or or zero um so we're getting a so here we're doing a query for

1:03:46

come on tooltips where the created at for the i collapsed it down

1:03:54

off attempt so we have created that that's created with the record

1:03:59

um too many tabs open

1:04:06

so we're querying where the created ad is greater than two days ago so three days ago if i got my password

1:04:13

wrong those won't count against you two days ago if you got your password wrong then it will so give you kind of a grace period

1:04:20

um and then where the email is the provided email for the attempt to call

1:04:26

and then we're deleted at is null and then we're going to get the count of

1:04:31

the number of records that come back based off of the id then we're just going to get the first one because whenever you call account

1:04:38

you'll kind of get back an array with a single record with the count on it or wait

1:04:45

uh to select two there we go

1:04:51

all right so we'll select just the id and then we'll count just the id and we'll get the first record that has the actual account on it

1:04:59

thinking about that right i think so we'll see

1:05:06

return back and then attempts would then be our entire number so

1:05:11

if extras then is empty because our first record cannot find any records

1:05:17

then we'll just default back to zero because if i mean user hasn't had any attempt records created

1:05:24

then their count would be zero and then we need to be able to get the

1:05:30

remaining attempts so we'll do public static async

1:05:36

remaining attempts

1:05:48

this one will also return back a number

1:06:10

why am i doing that so the allowed attempts

1:06:17

the actual attempts that the user has so that's all that that is and then we have

1:06:24

a helper function to delete all of the bad attempts so public static async

1:06:31

delete bad attempts this will take an email as well

1:06:36

and it will return back just a promise of type void

1:06:42

and we will await our off attempt

1:06:49

where email is our email all right i'm gonna go ahead and break that down

1:06:58

uh where null is deleted at

1:07:06

and we want to update all of those matching records

1:07:12

with a value of deleted display right yeah deleted that of date

1:07:20

time now dot to format

1:07:27

why not just do two sql time yeah let's just do two sql

1:07:33

okay so that should be good

1:07:38

um and then

1:07:43

see previously had poorly named this just login but instead what it does is it logs a login

1:07:50

attempt so instead of naming that just log in we will want it to public static async so

1:07:57

previously i had this called just logged log in but this doesn't perform an actual login

1:08:03

instead what would make more sense is login attempt or log login attempt or record

1:08:11

login yeah record log attempt email string

1:08:23

okay and all that this does is on the

1:08:28

off attempt it will create a new record for the email

1:08:35

and then evidently i have an enum called auth attempt purpose

1:08:40

which will set the purpose id um let's see if i do indeed have that auth attempt

1:08:47

yep purpose as

1:08:53

log in and then we will have another one for change email so public static

1:08:59

async again i'd probably name that with the action that it was just recording for so we'll

1:09:05

do record change email

1:09:12

see so this is for recording a bad authentication attempt on change email so we will do record change email

1:09:20

attempt because it records an attempt to change the email

1:09:31

author create email

1:09:36

purpose id off attempt purpose dot

1:09:42

and change email there we go all right so that's all we need for the service um

1:09:47

that's actually everything that the service has in it is for the actual functionality

1:09:57

so on login what we would want to do is record a login attempt anytime that

1:10:04

the user gets a login wrong and then anytime that they get their login correct we want to make sure that

1:10:10

the bad attempts are deleted so that if a user logs in in the morning

1:10:17

gets their login wrong twice and then finally logs in correct the third time i have a tendency to do that

1:10:25

uh that those two bad attempts don't carry over to them whenever they try to log back in later in the day should they

1:10:30

log out in between those times uh so within our auth controller here on not

1:10:38

on sign up because they won't have any ability to log in yet so if they get it wrong so we have this catch down here

1:10:48

is that right yes

1:10:53

so within our catch we will want to do and this returns back

1:10:58

void avoid promise um we'll want to do off attempt no not purpose

1:11:06

attempt service dot law record login attempt there we go

1:11:14

and the email so which we have destructured right here

1:11:19

and that would be it for the login no no we need to check it so prior to allowing

1:11:25

them to log in we need to check whether or not they are locked so we will do

1:11:30

const um login attempts

1:11:37

remaining wait off

1:11:44

attempt service dot remaining attempts should really be get

1:11:49

remaining attempts email i'm going to go ahead and change the name of that

1:12:05

okay and this one will be get attempts not just attempts

1:12:12

okay oh and then that service calls that so now i just invalidated that so

1:12:19

get attempts there we go all right and then if

1:12:26

login attempts remaining is less than or equal to zero

1:12:33

just doing the less than check just for safety session

1:12:38

flash air your

1:12:43

account has been locked due to repeated

1:12:48

bad login attempts please reset your

1:12:55

password and now that i'm thinking about it i don't really like that two-day grease period

1:13:01

return response redirect

1:13:07

to path which we don't currently have yet

1:13:13

but this would be the forgot password so instead i'm just going to i guess manually define that path so

1:13:20

forgot password so redirect them to the forgot password page if

1:13:25

they get that wrong too many times and then this error message will show to them on that page as well so that they

1:13:31

know why they got redirected but as i was saying now that i'm

1:13:36

thinking about it i don't really like the two day grace period that i have

1:13:41

here

1:13:47

i'll think about it i'll bump it up a 7 for now at least but i'll think about getting rid of that

1:13:59

yeah better safe than sorry let's just get rid of it

1:14:04

take date out of consideration

1:14:09

okay so that should be it for login um

1:14:16

shouldn't need to do anything on sign up because you can't really get your password wrong if you don't have a

1:14:21

password yet so that should be it so let's go ahead and test well we don't have any users so we have to sign up real quick

1:14:27

let's go sign up real quick so we'll do let's get our test user one back

1:14:42

okay and then now that we have that user let's log out and then we will go to the sign up and

1:14:48

we will get their credentials or their we'll get

1:14:53

let's see i did change this now because you can log in with your username so we do need to change this a little

1:14:59

bit from how i had it because now email here could be the email or it could be the username

1:15:07

so instead of just providing the email into get remaining attempts or maybe we

1:15:15

should probably change that to uid so that i don't fall into a trap where i assume that that's an email in the

1:15:22

future so let's change that to uid let's go to the

1:15:28

sign in page change the name for the field here to

1:15:33

uid and then that would also mean that we need to update the

1:15:41

uh value there for the flash message getter

1:15:46

and then do i do have a validator for this is it an actual validator is it no it's inline

1:15:53

so back up to the auth controller here right nope that's for sign no i don't validate

1:16:00

sign in so then this would just change to uid and this would change the uid

1:16:08

and that would change the uid and everything just changes the uid

1:16:22

okay and then all of the auth service stuff

1:16:27

or auth attempt service stuff that we just did we need to change email to uid as well so uid there

1:16:36

and then this would change into where equals u where email equals uid or

1:16:43

username equals uid um so do that here so

1:16:48

do a callback query here to do that query dot

1:16:53

where email is uid or where

1:17:00

user name is uid

1:17:09

and we will want that for the bad auth attempts as well

1:17:14

and is that every email reference no right here

1:17:19

uid uid uid

1:17:27

a couple more well actually for these login

1:17:32

um records i need to change that

1:17:38

actually for the change email that one will be an email because they'll be providing their actual email if i remember right

1:17:45

and then for the auth attempt record we'll need to change that so that it's not actually just email but maybe

1:17:54

maybe uid instead

1:18:03

okay we'll change the model for that as well so off attempt email goes to uid

1:18:10

and then we'll need to roll back uh just one and then re-migrate that one so node ace

1:18:17

migration roll back right yeah roll back

1:18:24

one piece migration run okay

1:18:31

so we still have our test user one in the database because we didn't go back to batch zero

1:18:36

um everything should now be changed to uid if i remember that i did everything correctly that's that's the social

1:18:43

service let's go take a look now i gotta update you to uid as well so we'll do uid

1:18:48

pretty sure that the change email attempt will actually be an email instead of the uid

1:18:55

but keep that in mind for the future then everything else

1:19:01

should be good go recheck the auth controller real quick

1:19:08

uid yeah okay

1:19:14

so then we should be able to incorrectly provide just the username now

1:19:24

and let's see what we got going on here column oh email does not exist that's very true

1:19:30

fill into that trap so i'm trying to query yeah so we could simplify that really

1:19:36

too for some reason i was thinking i was querying off the user

1:19:41

so where uid is

1:19:48

uid okay let's try that again

1:20:03

all right count from off attempts for uid is one

1:20:08

deleted act is known two all the time must appear and the group by clause are we used in an aggregate function

1:20:18

right so i got i'm assuming it's this query wrong

1:20:27

because that's the only one that has the count on it

1:20:37

so i'm gonna go tinker with that real quick uh let's open it with a tab

1:20:42

and no ace rebel

1:20:48

load models

1:20:54

wait no lower case models dot auth attempt

1:21:00

query dot see if i can copy and paste this so i'll

1:21:06

cut out the extras portion and just get the raw query here

1:21:12

see if i can paste this in without it breaking yeah that broke it i think

1:21:20

let me go back into it

1:21:31

okay um so await models

1:21:36

auth attempt query where uid is

1:21:43

and then we provided test user one there where null

1:21:50

deleted app select the id

1:21:58

and we'll try to do the whole query without the first see what we get

1:22:05

now shoot didn't call query as a function

1:22:10

okay and yeah we're getting the same error

1:22:16

so let me take the count off see what we get empty array

1:22:23

and let me take the select off and put the count back on

1:22:31

i think that's what i meant to do so let's try doing what i had in there

1:22:38

so extras um

1:22:43

hold on a minute before we do that let's tack the first back on

1:22:50

there we go okay so now let's put it back in parentheses and then do our

1:22:56

extras let me leave off the count and there's our count

1:23:01

cool cool cool so take the select off i was overthinking that and now we should be good

1:23:10

is that still yeah that's still nullable let's count though so it should be there so we should be able to do first or fail

1:23:20

okay one of those instances where ruffle

1:23:25

comes in super handy because you can just jump in there and see exactly what you're getting back and just tinker around with

1:23:30

stuff so let's try this one more time should work this time

1:23:37

there we go friday username email password is incorrect let me jump into

1:23:48

we go here instead of users i want auth attempts i think i got

1:23:54

to refresh the hole yeah there we go whoops whoops whoops there we go and there we

1:24:00

go we got our auth attempt for the uid test user one the purpose is one which

1:24:05

would be um log in it's not deleted

1:24:10

and it was created today at three o'clock

1:24:15

which is the current time so all that looks good um then we need to

1:24:21

just get it wrong a couple more times and verify that we actually get logged out or we could get it right verify that it

1:24:28

clears and deletes and then yeah let's go and do that test user one

1:24:34

get it correct so now this should be deleted no i didn't add that in did i

1:24:41

nope sure did not so this should not be deleted because we didn't add it in yet we gotta go do that

1:24:47

um and that's why you test

1:24:53

so within our sign in just after the attempt call we will also do await

1:25:00

auth attempt service and this is where we want to do what what did i name it

1:25:07

remove delete delete bad attempts and we provided that uid

1:25:12

and we'll delete all of the bad attempts for that uid so now let's head back

1:25:19

log out log back in

1:25:29

and now this deleted that should be populated and there it is

1:25:34

okay so now if we log out

1:25:40

uh go to log back in get the password wrong again

1:25:50

wrong again we should have two so there should be our two marker

1:25:57

so we should be able to get it wrong one more time

1:26:04

and now if we get it wrong again we should be log locked out

1:26:09

so that kind of just verifies that that one's not in play

1:26:15

so let's try that again test user one

1:26:20

yeah so we got locked out because it tried to redirect us to the forgot password which is

1:26:26

right within this block right here

1:26:32

which takes us to the uh forgot password flow to get drink real quick sorry i can't

1:26:37

help you with that on apple watch

1:26:43

okay sorry about that okay so that takes to the

1:26:50

forgot password flow um so we're gonna need pages for that

1:26:56

um i don't exactly remember remember the naming schema that

1:27:02

i got for that i think it's forgot password for the actual form to enter your password in which would

1:27:08

then give you the signed url in your email with a

1:27:14

link that you can then go to to go to the actual reset password page

1:27:20

so these right here i'm going to go ahead and copy and paste these just so i don't

1:27:27

forget a step here

1:27:36

and i need to take that honeypot middleware off of this guy so i'm just going to comment that off so

1:27:41

don't forget to put it back on

1:27:46

okay and then we need a forgot password page to render the forgot password that

1:27:52

page so with an auth controller or we could do another controller for it actually

1:27:59

keep the auth controller nice and super clean let's do that

1:28:05

so i'm going to exit oh it's dot exit in that

1:28:11

come back over here and let's do node ace make controller

1:28:19

password reset controller i should have passed in the exact flag

1:28:31

kind of like the authentication based stuff to be singular okay so

1:28:36

what was it we need public say sync async there we go uh reset password i think it was was the first

1:28:42

one which would then take in the view

1:28:56

so return view render um and where do i want to put this

1:29:02

so we have resources views put it off that would be okay

1:29:12

although we have this in a separate folder so maybe we'd put it in password

1:29:22

let's do off password there we go passwords password

1:29:30

password reset and that will be the form for that so

1:29:36

we'll go ahead and create that page

1:29:42

and i'm just going to copy the sign in

1:29:51

reset your password

1:29:58

remember your

1:30:08

okay and then we already have the csrf field

1:30:14

the heck is this from forward oh

1:30:20

yeah okay yeah sorry that's to redirect the user back to where they were

1:30:26

uh after login we'll take care of that later i forgot i put that in there sorry we don't need that for here um

1:30:32

so we can get rid of it and we'll keep the errors and then all we need is the

1:30:38

email and we don't need this

1:30:44

and uh what's the button for this usually say yeah we'll go with what i thought i

1:30:50

turned oh i turned it off for typescript okay let's just say i thought it turned co-pilot off

1:30:57

um and then there will be no social auth for this because you would just go to that social provider if you forgot your password for

1:31:04

that provider okay so that should be it for the page render there

1:31:10

did i save the routes i did not so we're going to go ahead and save that and actually this needs to change from off

1:31:16

controller to password reset controller

1:31:23

and really we could simplify this since we put it in some controller now from forgot password to

1:31:32

yeah we'll be specific with it for right now i'll change my mind about that later um okay so let's verify that we can

1:31:38

actually see that page so really just refresh here missing

1:31:44

method forgot password on password reset controller that name something different

1:31:54

reset password forgot password now okay

1:32:05

oh okay yeah reset password is the one where you actually reset the password

1:32:12

forgot password is the one where you get the uh signed url

1:32:18

there we go okay so now we need to get the form action for this

1:32:26

which would be called forgot password sent oh no i don't want to print

1:32:35

so public async forgot password sent

1:32:40

for this one we need to request

1:32:49

[Music] yeah yeah

1:33:00

okay all right well we'll start with that and then the http context contract

1:33:10

okay

1:33:36

okay so is this an actual page then do i have that as a separate page

1:33:43

trying to remember what i did

1:33:50

let's see off yeah i guess forgot password sent would be an actual page

1:33:59

and then so then what do i need for the actual form submission

1:34:04

one should be a post forgot password oh send forgot password send

1:34:10

it's those minor things that you read whenever you're not fully there sorry

1:34:16

look async forgot password

1:34:21

send all right this is the one that would need to request in the response

1:34:29

http context contract okay

1:34:34

and then in this case all that this one needs is i think the view let me go take a look

1:34:40

just to make sure i'm remembering that correctly auth controller

1:34:47

scroll down forgot password renders the forgot password page sent just yeah

1:34:55

and then this don't mind me i'm just going to go ahead and

1:35:04

so this one was send okay

1:35:09

so we need our user we need our route

1:35:26

and i don't have that in there yet and looks like we also need our session

1:35:34

and we don't have that quite yet

1:35:41

okay and then we need the actual sent to page so it would be pretty much the same

1:35:48

thing as well it would be like the same layout and everything as reset

1:35:55

so copy this paste it in rename it to sent

1:36:05

this is the page that will tell them to check their email i'm gonna go see exactly what i wrote so password reset email sent

1:36:11

sure that's fine maybe check your email

1:36:18

instead and then i don't really know that there would be a

1:36:24

sub paragraph for that and we don't need a form

1:36:33

and i'm just going to copy and paste whatever

1:36:39

okay

1:36:45

so there is the sent page so now what we need is

1:36:51

no not the post controller the auth controller here no not the auth controller now the password reset controller sorry i'll get

1:36:58

with it eventually we need i can we can either use see i

1:37:04

think all that this event was doing was kicking off the email which there is

1:37:09

now a specific like send email later function i think so we can just like

1:37:14

send it straight from the function instead of taking it off to an event to do it

1:37:20

take a look

1:37:25

down to start events there's welcome

1:37:33

for got password

1:37:40

yeah okay yeah it's just kicking off the email so we're going to use the event system

1:37:45

or we can just kick it off here i really don't want to deal with emails

1:37:52

so i i'm just going to go ahead and copy the email directory

1:38:00

from um my old project and paste it in here i uh emails are a pain

1:38:06

so so for example like the password reset email it's just a full html document so

1:38:12

you're not missing anything for me typing this stuff out again just simple

1:38:18

html email that's populating using edge

1:38:24

and we'll go ahead and just kick that email off because i'm pretty sure that there is

1:38:30

we don't have the mailer installed yet so i guess that would be step one

1:38:45

so let's take a look at that npm and don's jazz mailer

1:39:00

oh mail sorry

1:39:06

put letters in there in my own head

1:39:11

and then we would configure it and that will add all of this fun stuff in

1:39:17

uh it might overwrite my emv variables again i already plopped those in there so if it does then we will just um

1:39:25

start it in this stream and fix it in the next stream because i'll plot my emv variables in in between

1:39:32

and then we'll do node ace configuration now just configure

1:39:44

is just mail to smtp

1:39:52

copy the smtp rules back into here scroll on down to our emv.ts

1:40:00

and we'll paste those in okay

1:40:11

so now we need to go back into our password reset controller and we should

1:40:16

now have a

1:40:23

mail i believe it's called

1:40:33

yeah mail so and that is imported from

1:40:47

dos just add-ons mail so import

1:40:53

mail from ioc donnis

1:40:59

add-ons mail

1:41:05

wake up computer

1:41:11

okay thanks john jay appreciate it

1:41:18

all right so now we have the mail module so we will

1:41:25

try to see let's see i'm pretty sure that there's like a sent later i'm not getting auto complete

1:41:34

no why am i not getting autocomplete

1:41:45

all right well then we'll just scope out the documentation

1:41:55

i'm pretty sure that there's like a like a asynchronous sense where i wouldn't

1:42:01

block the response

1:42:06

yeah defer here we go send later that's what it is okay

1:42:12

so you still use a weight and then we will do mail dot

1:42:18

really not sure why i must have imported it wrong import mail from ioc

1:42:25

adonis add-ons mail

1:42:35

hmm

1:42:48

it's probably just a visual studio code actum funky so i will excuse the red squiggly for

1:42:55

right now let's go take a look back at the sun later so mail.send later and it takes in

1:43:00

or a callback that is provided the message and then

1:43:05

we do message.from who we're sending from which usually i put that

1:43:11

in the emv um i think maybe

1:43:18

now i just usually have it at the top okay so message

1:43:25

dot from right don't have intellisense i'm going to

1:43:30

keep checking back to the documentation make sure i get it right

1:43:36

and then it would be two whoever the user's email is so if we have a user

1:43:43

we can get it officially email which should be correct but we'll get it directly off of the user just in

1:43:48

case user.email

1:43:54

and then subject yeah subject

1:44:00

uh

1:44:09

something like that and then html view

1:44:16

and then we reference the path to the edge partial that we just copy and

1:44:22

pasted over from my old project so let's say we got emails off and it should be password reset so

1:44:30

that would be emails off

1:44:35

password reset and i i would assume i have to pass something in there it would it'll definitely be

1:44:41

the signed url take a look take a look and see what else i require for that

1:44:48

the user okay so just a user in the signed url is all i'm using for that so we will pass just those into user

1:44:55

and signed url and

1:45:01

we can get rid of that line there now

1:45:06

and we can comment that one out because that one was being used by the logger

1:45:13

it's really irrelevant anyway i should take that out of the try right up here um

1:45:20

okay so i don't know whether that is let me try reloading

1:45:25

visual studio code maybe that will fix how do you reload it it was one of these

1:45:38

man it's no big deal um so we'll go ahead and try it let's see if it works

1:45:45

i kind of have a feeling that since the social authentication environment variables got overwritten that these will too but we'll try

1:45:52

so let's see uh well no i had it all going to mail trap so it

1:45:59

won't actually send to somebody so okay i'm gonna say i don't own test user one at gmail.com but

1:46:04

it'll send to mailchimp if it does work so all right

1:46:10

provided email username or password oh the action i never updated the action for

1:46:15

the page so no not post collapse it down so i

1:46:21

don't go to it not layouts not emails

1:46:26

but password reset so i still have this go into the sign in so instead we need to do auth

1:46:33

password reset

1:46:44

well i'm going to change the name

1:47:13

oh wait a minute yeah there i was falling into that trap again i'm calling this password reset

1:47:21

this forgot password so this one should be forgot

1:47:26

and then this one would be auth password forgot sent and then this one would be

1:47:34

off password forgot send and then now this one will be off password reset

1:47:43

and this one will be auth password reset store

1:47:51

okay i don't like how long the name is but but it'll work for now and then i think i referenced those names in the

1:47:57

actual controller too so let me take a look at that real quick

1:48:03

yeah so auth password forgot sent

1:48:12

and that should now that should be it okay

1:48:18

so let's go ahead and update the action now with the actual correct post route

1:48:23

so auth password reset send

1:48:29

so now that should send off to the correct action should not try to sign us in so let's

1:48:35

try that again oh cannot find routes off password reset

1:48:41

oh it's forgot isn't it keep doing that

1:48:47

it's a surefire sign that i named that poorly all right

1:48:57

missing method forgot password send on auth controller

1:49:05

i didn't yeah i didn't update the controller for these either

1:49:15

okay let's go ahead and update it for all of them

1:49:20

okay i'm gonna go ahead and line them up too like i have all the others

1:49:28

there we go

1:49:37

and i'll go back first

1:49:46

all right so we got redirected back to the same page so something went awry

1:49:53

let's take a look password reset controller

1:50:00

so it really could have been anything don't have the error logging out so

1:50:05

let's just console.log the error real quick

1:50:12

try it again and see what we get

1:50:25

find by expects a value received undefined oh i

1:50:31

copied this from login so this is uh uid not email so we need to update that

1:50:39

sorry about that reset yep right there so just change that from

1:50:46

uid to email and there we go oh hey mr uh yes this video will be

1:50:53

available on demand after the stream test user one

1:51:03

okay try this again so still no joy

1:51:10

definitely a different error cannot find route uh reset password

1:51:18

so um

1:51:33

see so that was the old error which means that we got to here that was the update so i changed that so

1:51:38

then this is the actual error so cannot find a route so where am i trying to go to the route

1:51:45

reset password

1:51:51

or is that something that's referenced on this page

1:51:57

it doesn't look like it just search for the word route no

1:52:04

so it must be something that i'm trying to get to in the controller oh yeah no it's the

1:52:11

make signed url um all right so what did i have called

1:52:16

reset password i think that would now be the off

1:52:23

password reset which would be the page

1:52:28

that the user would be taken to once clicking the email link that they are sent to the signed url link

1:52:36

so that would be this guy right here so yeah off password

1:52:41

reset okay let's try this again

1:52:50

slowly but surely uh why are you trying to download

1:52:58

i'll allow it let's see what we get sent

1:53:03

okay i got a file called sent

1:53:13

that's interesting what did i do wrong to get a file called sent

1:53:23

don't think i necessarily need to return from that

1:53:40

it's probably the render page right oh yeah i'm not doing anything with it

1:53:45

all right auth password

1:53:52

uh sent that's going to need a name change

1:53:59

but we'll roll with it for right now so let's try this again fingers crossed it does not download a

1:54:06

file again there we go okay cool so now we got a page saying check your email um

1:54:17

see if it actually sent uh what's the name of that service was it was it mailtrap or was

1:54:24

that a mail testing service

1:54:32

is that right i don't think that's right

1:54:38

smtp testing

1:54:44

service i guess it is mailchimp okay i guess they changed their ui up

1:54:51

massively it doesn't look familiar at all

1:55:00

yeah sure enough that's it so no we did not get the email for it

1:55:14

all right so let's see

1:55:21

it could be an env issue um i guess i'll crunch this up and just

1:55:28

take a peek at it real quick

1:55:39

yeah okay i don't think mail traps credentials are too bad to type in let

1:55:46

me give that a go

1:55:54

let's see so

1:56:05

pardon me for just a second

1:56:19

okay

1:56:31

not that these credentials would be massively horrible if they got out but

1:56:40

okay should be good now for that i did save here it

1:56:45

yeah i did save okay so let's try that one more time

1:56:54

so we'll go back to the forgot password page

1:57:01

send it off see if we got something

1:57:09

still looking like i know let's see if maybe i need to refresh yeah it's still looking like a now

1:57:14

so let's get rid of the scent later and it's got to be something with the fact

1:57:19

that auto complete doesn't want to work for this [Music]

1:57:28

let me double check make sure i did install and configure it correctly

1:57:36

so package.json adonisjs mail

1:57:41

let me verify that i configured it let me double check and see what all it was supposed to configure and i'll verify that those are actually there

1:57:52

config mail contracts mail this should be good i just did the emv ts config could be the t well that would

1:57:59

be the typescript portion um the actual functionality portion would be within the adonis rc within the commands

1:58:06

and the provider so we'll take a look at the src and make sure that it's in there make sure that i actually did run it i

1:58:13

didn't get the command wrong or anything yeah so there's the provider and then we

1:58:19

haven't used anything with the command and that wouldn't break anything with actually sending but there's the command right there so it is

1:58:25

configured on that portion mail

1:58:31

mailers smtp all right let me go ahead and take a look at the typescript

1:58:38

ts config.json under types it should be a downstream mail

1:58:46

yep it's there too so the red squiggly i really have a feeling that it's just visual studio code um

1:58:55

so i was kind of testing out sublime um a little bit seeing whether or not i'd

1:59:00

be interested in upgrading to their version four so let me try opening it up in here and seeing if this

1:59:07

comes up with air as well on that or whether or not autocomplete works in this

1:59:14

so let's see code right here

1:59:20

okay and then we were within app app controllers http

1:59:27

and we were within password reset controller

1:59:33

and yeah so that seems to work let's see if autocomplete works so male dot

1:59:40

yeah autocomplete works so the red squiggly does appear to just be a visual studio code

1:59:48

so that's good to know yeah don't save

2:00:03

so that should be working but let's go ahead and try not send later but send immediately

2:00:10

see whether or not we get any difference with that or i guess we could check and see

2:00:16

whether or not we actually get back an error or anything oh yeah okay i think right here is the

2:00:22

reason why it's not sending amp url is not a function

2:00:32

oh well if i look at the blue unable to deliver email

2:00:39

yeah okay so let's take a look at what i'm trying to do with app url

2:00:45

it's probably something that that's within the um email itself

2:00:54

this is going to be within here so let me see if i'm referencing app url in here yeah right there all right

2:01:00

so i must have a global sorry i just realized that that's

2:01:07

large there we go so within my old project i probably have

2:01:13

a global edge function defined within my provider so

2:01:19

let's go take a look and see what exactly i have going on there

2:01:24

providers providers providers this is the right project right yeah

2:01:29

app provider ignore all the ugliness so

2:01:34

okay so i have app url as an actual environment variable which i don't have currently in this project but

2:01:43

we could piecemeal it together so this is the main portion that it's looking for is right here

2:01:49

so we'll just apply that over here because i don't want to break anything with the emails i don't want to touch it right now

2:01:55

and we need to import this is within boot we need to also import view so that we can

2:02:01

actually apply the global so we'll do cost view equals uh this

2:02:07

can um this app container

2:02:17

what is it

2:02:22

what is it what is it what is it what is it

2:02:30

my goodness i gotta go take a look at one of my old tutorials and see exactly what i need to call here

2:02:39

nope all right um let's see which one do i cover that in

2:02:49

this would be one

2:02:56

oh use my gosh use

2:03:01

app uh core route so there we go we should now

2:03:08

have the view so we should be able to view dots global

2:03:13

and then app url we don't quite have i don't remember off the top of my head what all

2:03:19

is immediately within the environment variables so since i don't want to look on stream um

2:03:25

i'm just going to go ahead and hard code it fill it in later

2:03:30

so please forgive me and then the path i remember right should start with a slash

2:03:35

so that should do it okay so let's try that one more time now that we have that global variable

2:03:43

defined

2:03:49

okay that's fair okay

2:03:56

that's fair too oh what the heck tom come on i'm sorry

2:04:05

all sorts of out of it all today

2:04:14

donna's core route

2:04:21

is it not global anymore or no i don't want route i want view

2:04:32

that is that the right path for view yeah downloads core view there we go okay so now we can try it again

2:04:44

i didn't provide an email just provided a username but we'll see if it's sent

2:04:51

i'm not seeing anything that said that it didn't send so that's a good sign

2:04:57

let's go take a look nope

2:05:03

still looks like a no

2:05:16

it could be that i provided a username and not an email because it doesn't really have any emails too that would be

2:05:21

a use case where i need to handle that better um yeah so find by email there so if you

2:05:29

can't find a user it should return back something

2:05:37

so that should really be fined by or fail and there we go that should take care of that so we can test that make

2:05:43

sure that that's working so instead of appearing to succeed if we provide an email now it should

2:05:48

kick it yeah kicks it back i don't have a error message on this page yet we can actually should be able to

2:06:00

i'll take care of that later long story short if we provide an email let's see what happens

2:06:06

okay so now let's see

2:06:11

all right so that's the old error from whenever i tried to use a username and then we switched to finder fail

2:06:18

so down here i don't see anything saying that it didn't send yeah there we go okay so now it's sent

2:06:26

and we got back the reset password link so now if we go

2:06:32

to just copy the after portion of this

2:06:37

we don't have a page for this yet um but we can go ahead and take care of that real quick

2:06:43

so we have a whole hour before that token expires

2:06:51

so let's see what do we what that so i don't think we have a controller function actually rendering that out yet

2:06:57

no we do not what does my route say that that should be called reset password

2:07:02

so this is the form or this is the page that will handle that signed url and it will display

2:07:08

the password field and yeah it'll be just a simple password

2:07:14

form so we can copy

2:07:21

and collapse down my components we can copy the

2:07:27

password reset page

2:07:38

oh i'm looking at my old project sorry sorry about that

2:07:43

uh wasn't gonna say that looks like it's already done so that's the email

2:07:52

so we can copy the reset page is what i meant to do and

2:07:58

i think i need to rename this forgot don't i i've been doing that thing where i get those two mixed up i really need

2:08:05

to clean the name up for that so i don't do that yeah let me make more sense if that's

2:08:11

forgot and then these would be reset that we do so we'll go ahead since it'll be the same

2:08:17

page paste this one in and we'll rename this one i forgot

2:08:33

okay so we have our forgot page and then we just need to change the reset page too instead of displaying

2:08:39

the email um just be a password so i'm going to go into the sign in page

2:08:46

and well actually the sign up page because it needs to have

2:08:53

the behave similar to a sign up instead of

2:08:59

login and we will replace this component with that

2:09:05

component okay and then

2:09:12

i guess the button works um reset your password and get rid of this

2:09:19

it doesn't make much sense to have that there all right and we need to update the form

2:09:26

so instead of doing auth password reset or forgot set this would be just off password reset i think

2:09:34

yes

2:09:40

off password reset store okay

2:09:52

don't judge me i'll go back and clean those names up later they work for now all right

2:09:58

so there we go yeah

2:10:04

yeah that that'll work in which case we need to now define both

2:10:11

of those controller functions so we have forgot password forgot password sent

2:10:17

forgot password send now we need public async

2:10:24

reset password

2:10:31

and we're going to need to validate that the signature is valid

2:10:37

uh we can either do that in this handler or we can do it within a middleware

2:10:47

either way would really work

2:10:57

okay so there's that one i'm gonna go ahead and stub this one so i don't forget about it

2:11:02

um what reset password and then the other

2:11:08

one is for the actual mutation of the password so that would be reset password store okay

2:11:25

all right so i'm gonna i'm gonna cheat and take a peek and see what i was doing for these

2:11:32

i don't exactly remember all right so reset password and testing whether or not it has a valid

2:11:38

signature getting the email so that's all that i was doing there

2:11:47

really no no error handling or anything like that must be doing it within the view

2:12:14

i don't oh yeah okay yeah so i was

2:12:19

okay so if it's valid render the form if it's not then tell them that their link's invalid and they need to try

2:12:25

again that's fair that works

2:12:31

that's simple enough just copy and paste it

2:12:37

so instead of response we'll need view and params

2:12:43

okay and then for the actual store that would just uh

2:12:50

probably want to validate it too so let's see i'm going to copy and paste

2:12:57

this because several things going on here when i remembered them all

2:13:05

all right so what do we got going on here so first and foremost we're getting the email and the password so

2:13:10

that tells me that we also need to on our reset page add the email in as a

2:13:16

hidden field so let me do that up here

2:13:25

and just replace token with email

2:13:31

second that tells me that on the page that renders this we need to oh we are passing the email in so that's

2:13:37

fair okay

2:13:43

and then third we need to do an if

2:13:49

here if that should really be is um

2:13:58

signature valid

2:14:03

so if it's valid and we'll render the form otherwise i'm just going to copy what i had over here

2:14:10

which was something simple

2:14:22

okay and then we can get rid of that href and actually do a route here so route

2:14:28

auth passwords forgot to take them back to the forgot password

2:14:34

page so that they can start to flow over again since their token expired and

2:14:40

i think that should do it and then we need to finish up our controller so

2:14:46

let's take a look at the auth controller because we're going to want to validate that their password's valid again

2:14:51

so we'll do that we'll go ahead and validate the email too so instead of getting it this way

2:14:57

why don't we just do a full validation so we'll import

2:15:03

schema as schema and rules from

2:15:11

the validator and we don't need username

2:15:18

um instead of a unique check on the email instead it should be in exists

2:15:27

well would you want that to come back with that error that the email doesn't exist is the thing

2:15:34

really it never should reach that point unless the user deletes their account in between trying to reset their

2:15:40

password so i'll think about that more but for right now that'll work okay

2:15:47

so if it exists

2:15:52

um all right so then we just need to validate so const

2:15:58

email password equals await request validate

2:16:06

our schema um and then we'll do a user find by the

2:16:12

email to get the user and then we'll update their password and then the

2:16:17

hook that we have on save on our user we'll check whether or not the password's changed if it has it will rehash it so we don't need to worry

2:16:23

about that here and then we have an emission going out saying that your password has been

2:16:29

successfully reset something like that if you haven't reset your password click

2:16:35

here to undo it or something like that i think

2:16:44

so we'll comment that out for right now we'll take care of that here in a second because we'll use the same approach that we had for this one where we'll just

2:16:49

send it directly within here since there is that sent later and then we will automatically log the

2:16:55

user in since we know that they have access to their email so it's good enough

2:17:01

especially for our app where you're not dealing with anything financial so and then

2:17:08

clear the bad attempts out of the auth attempt service since you just reset your password

2:17:13

you get a clean slate there and that

2:17:19

is giving me a red squiggly which not sure why

2:17:26

oh the odd off attempt to purpose that needs to be off attempt

2:17:32

service those look so similar for some reason all right and then we also need our session

2:17:40

which we really haven't taken much thought into um displaying messages at

2:17:45

this point but uh we'll take care of that later on and then we don't have this is a custom

2:17:51

logger so i'll log that out and log that out

2:17:57

or um comment sorry not log and

2:18:03

we need auth as well so that we can actually log the user in okay

2:18:09

so everything here should be good so far except for sending the user a message saying your password has actually been

2:18:15

reset you're sending these are an email saying that your password's been reset so

2:18:21

this should be fairly similar to this so i'm going to copy this

2:18:27

we already have the user because we're doing a find buyer fail so

2:18:32

we can again just use this directly off of the user we don't need to worry about assigned url at this point

2:18:37

i am going to check and make sure the old project was still using the user there i would assume it

2:18:43

would be but i'm going to check let me take a look at my start events

2:18:48

what was that event called password reset okay

2:18:59

oh right here okay yeah it's just using the user and that was using the view so i'm just

2:19:04

gonna copy this whole line since i copy and paste it over the actual email templates

2:19:12

so this will render the email out using edge so it works just like edge so i'll have a user available within that

2:19:19

edge kind of page and then this is the actual edge page that it will render which is a full html

2:19:25

email so yeah that should be okay

2:19:32

so we can go ahead and test it out if i didn't forget anything

2:19:37

and then if it all works we should get another email here saying i didn't change the actual email verbiage

2:19:43

um your password has been

2:19:53

successfully reset all right so we'll get that

2:19:59

email actually we need to go to this page copy the

2:20:04

signed url okay paste that in

2:20:10

go to it and what do we got going on here so it cannot find the page or the edge

2:20:16

file password reset dot edge that's very fair because it does not exist

2:20:23

we change that so instead of password reset

2:20:29

this would be password slash reset

2:20:35

there we go oh no

2:20:42

oh yeah so we changed in the template to be expecting is signature valid instead of

2:20:48

just is valid so we need to also change that within the controller so is signature valid

2:20:55

and is signature valid now we try it again there we go now we got a password field

2:21:01

so same rules apply as to when you're logging in so [Music]

2:21:09

let's try this out all right we got logged in so at least

2:21:14

that portion worked uh let's verify see whether or not we got an email there we go your password's

2:21:20

been successfully reset there we go so that seems to be working

2:21:26

all right so we got the password reset flow in our application now so if we log out

2:21:33

run through it all again once more so we forgot our password

2:21:38

i'm going to say our username oh that needs to just be email

2:21:46

i guess that could be username or email

2:21:52

but uh we'll stick it to email for right now so let's see

2:21:57

forgot change the label to just email

2:22:02

change the place order to enter the email

2:22:14

okay i need to alter the placeholder text color on that that's horrible

2:22:20

test user one reset the password

2:22:26

get a page i'll fix the padding on that i'm saying that you're good to go check your email

2:22:31

we get the email it has um

2:22:37

our assigned url if we click on that we're taken to a page where we can now reset our password

2:22:43

where we can reset our password and upon successfully doing so we get logged in

2:22:48

so cool so let's see if we can mark that done

2:22:54

and we can mark mark that done

2:22:59

and we can mark that done and then

2:23:05

i think we'll stop there for today and then in time for the next stream i will fix

2:23:11

my uh tokens for the social authentication and i'll see whether or not i'm still getting that same error

2:23:17

if i am i will take a look into it and have an answer for you next week

2:23:23

so thank you all for watching i appreciate your support thank you for being here and have a great day

Join The Discussion! (6 Comments)

Please sign in or sign up for free to join in on the dicussion.

  1. Commented 2 years ago

    Hey there,

    this video brought up a question to me as someone learning the framework at the moment - I hope I'm not annoying you yet. :)

    While the Adonis-Documentation is very good and comprehensive, I could not find much material about how exactly services are supposed to be used. It seems like there is no "convention over configuration" when it comes to them.

    Now, when testing around in my small project, I found 2 possibilities:

    1. I could simply import the service where I needed it - for example in a controller - from "App/Services/MyService". Of course, this would require me to instantiate the service every time before using it, so not that nice.

    2. I followed this tutorial https://medium.com/@shemsiu/ioc-container-and-dependency-injection-in-adonis-v5-216774c2a476 -> as far as I understand the essential part with this is to instantiate the service in "providers/AppProvider.ts" and register it to be used application wide. Afterwards, I was able to import it in my controller from "@ioc:Namespace/MyService".

    Now, apparently there is another method "@inject" that you used in your video.

    However, what is the magic behind this? Does it automatically instantiante (and bind it to the controller?) your service when you reference it in the controller's constructor?

    What is the difference between this method and option 2) described above? When would you use the AppProvider?

    0

    Please sign in or sign up for free to reply

    1. Commented 2 years ago

      Hi MrAvantiC!

      First off, both of the above are valid approaches to services, however, there are different instances in which you’d want to use approach 2 over 1.

      Approach 1

      This approach is great for services that are going to be used specifically for controllers. For example, typically if you’d have a PostsController you’d use a PostService to compliment the controller. You can then either make that controller:

      • Filled with static methods, so you don’t need to instantiate anything

      • Filled with non-static methods, so you need to instantiate the service to use it’s methods

      • Have a mix-and-match of both static and non-static depending on the method

      As for @inject, you’re absolutely correct. So, without inject, to use a service with non-static methods it’d look something like this:

      import MyService from 'App/Services/MyService'
      
      export default class MyController {
        public myService: MyService
      
        constructor() {
          this.myService = new MyService()
        }
      
        public async example ({}) {
          return this.myService.someMethod()
        }
      }

      However, @inject will instantiate and bind the service for me to my controller, so instead I can do this:

      import MyService from 'App/Services/MyService'
      import { inject } from '@adonisjs/fold';
      
      @inject()
      export default class MyController {
        constructor(public myService: MyService) {}
      
        public async example ({}) {
          return this.myService.someMethod()
        }
      }

      This also works within services as well!

      import MyOtherService from 'App/Services/MyOtherService'
      import { inject } from '@adonisjs/fold';
      
      @inject()
      export default class MyService {
        constructor(public myOtherService: MyOtherService) {}
      }

      Approach 2

      With approach 1, the service is instantiated for the controller for each request. Meaning, you know the service instance is specific to your user’s individual request. With approach 2, since they’re using singleton the service is instantiated once when the server is booted. So, a service instance is shared by all users and all their requests. This makes approach 2 great if you need to make a service that maintains connections, maintains a third-party package instance, performs actions shared by multiple users, or if it’s just your preference.

      Which approach you use is a personal/company preference. I personally feel more comfortable knowing the service instance is specific to a single user’s individual request. It eliminates the worry of having cross-user side effects. So, I only use approach 2 when I specifically need to have a long-lived service.

      0

      Please sign in or sign up for free to reply

      1. Commented 2 years ago

        Hey Tom, thanks for your response!

        So, when you have methods that do not depend on the user/request/context, we could simply define these methods as static , import the service and use it without getting an instance of the class first, got it!

        However, in case I do need an instance of the service because context is required (like your BaseHttpService for example), what is the difference between using the AppProvider :

          public register() {
            // Register your own bindings
            this.app.container.bind('MyNamespace/MyService', () => new MyService())
          }

        …and using inject() ?

        Is it personal preference only again?
        I get that when you need a singleton the AppProvider approach is more useful but what about using bind ? This would again create one instance per request, right?

        0

        Please sign in or sign up for free to reply

        1. Commented 2 years ago

          My understanding is that using this.app.container.bind is similar to doing:

          class MyService {
          }
          
          export default new MyService()

          In that, if you import the service at the top-level of your controller it’ll only be instantiated once for that controller on the first request that controller handles, then that same instances will be continuously used for all requests that controller handles.

          You can test this by adding the following to your AppProvider’s register method.

          public register () {
              // Register your own bindings
              class TestService {
                public count: number = 0
                
                constructor() {
                  console.log('TestService > constructor')
                }
          
                public testing() {
                  this.count += 1
                  console.log('testing', this.count)
                }
              }
              this.app.container.bind('Test/TestService', () => new TestService())
          }

          Then, import it within two controllers and call the testing method in one method on each controller.

          import TestService from '@ioc:Test/TestService'
          
          export default class ExampleController {
            public async index({ view }) {
              return view.render('example')
            }
          }

          Then request the pages and refresh each page. The console output will be something like this, depending on the order you refresh.

          TestService > constructor
          testing 1
          TestService > constructor
          testing 1
          testing 2
          testing 2

          So, it’s getting instantiated once per controller and keeping that instance for subsequent requests for that controller.

          0

          Please sign in or sign up for free to reply

          1. Commented 2 years ago

            I see, so basically we have:

            1. AppProvider + singleton() => One shared instance for all imports

            2. AppProvider + bind() => One instance per import (e.g. controller) which will be re-used for subsequent requests

            3. inject() => One instance per request which is great if you rely on request-specific data (e.g. HttpContext)

            Appreciate your help in understanding all of this! :)

            0

            Please sign in or sign up for free to reply

            1. Commented 2 years ago

              Exactly!! Anytime, happy to have been able to help! :)

              0

              Please sign in or sign up for free to reply