From b4de8e58f90b5ed50555c238871fcdecaf968999 Mon Sep 17 00:00:00 2001 From: mikestefanello Date: Wed, 15 Dec 2021 21:17:39 -0500 Subject: [PATCH] Generate password reset tokens upon submission. --- auth/auth.go | 30 ++++++++++++++++++++++++++++++ controller/page.go | 1 + routes/forgot_password.go | 17 ++++++++++++++--- routes/logout.go | 2 ++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 70b7c0f..b71db89 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,6 +1,8 @@ package auth import ( + "crypto/rand" + "encoding/hex" "errors" "goweb/config" @@ -83,3 +85,31 @@ func (c *Client) HashPassword(password string) (string, error) { func (c *Client) CheckPassword(password, hash string) error { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) } + +func (c *Client) GeneratePasswordResetToken(ctx echo.Context, userID int) (string, *ent.PasswordToken, error) { + // Generate the token, which is what will go in the URL, but not the database + token := c.RandomToken(64) + + // Hash the token, which is what will be stored in the database + hash, err := c.HashPassword(token) + if err != nil { + return "", nil, err + } + + // Create and save the password reset token + pt, err := c.orm.PasswordToken. + Create(). + SetHash(hash). + SetUserID(userID). + Save(ctx.Request().Context()) + + return token, pt, err +} + +func (c *Client) RandomToken(length int) string { + b := make([]byte, length) + if _, err := rand.Read(b); err != nil { + return "" + } + return hex.EncodeToString(b) +} diff --git a/controller/page.go b/controller/page.go index bc948c3..ed89a40 100644 --- a/controller/page.go +++ b/controller/page.go @@ -62,6 +62,7 @@ func NewPage(c echo.Context) Page { p.CSRF = csrf.(string) } + // TODO: Use container? if u := c.Get(context.AuthenticatedUserKey); u != nil { p.IsAuth = true } diff --git a/routes/forgot_password.go b/routes/forgot_password.go index 50346eb..0994900 100644 --- a/routes/forgot_password.go +++ b/routes/forgot_password.go @@ -1,6 +1,8 @@ package routes import ( + "fmt" + "goweb/context" "goweb/controller" "goweb/ent" @@ -42,6 +44,7 @@ func (f *ForgotPassword) Post(c echo.Context) error { } succeed := func() error { + c.Set(context.FormKey, nil) msg.Success(c, "An email containing a link to reset your password will be sent to this address if it exists in our system.") return f.Get(c) } @@ -74,10 +77,18 @@ func (f *ForgotPassword) Post(c echo.Context) error { } } - // TODO: generate and email a token - if u != nil { + // Generate the token + token, _, err := f.Container.Auth.GeneratePasswordResetToken(c, u.ID) + if err != nil { + return fail("error generating password reset token", err) + } + c.Logger().Infof("generated password reset token for user %d", u.ID) + // Email the user + err = f.Container.Mail.Send(c, u.Email, fmt.Sprintf("Go here to reset your password: %s", token)) // TODO: route + if err != nil { + return fail("error sending password reset email", err) } - return f.Redirect(c, "home") + return succeed() } diff --git a/routes/logout.go b/routes/logout.go index 2338339..5b55876 100644 --- a/routes/logout.go +++ b/routes/logout.go @@ -14,6 +14,8 @@ type Logout struct { func (l *Logout) Get(c echo.Context) error { if err := l.Container.Auth.Logout(c); err == nil { msg.Success(c, "You have been logged out successfully.") + } else { + msg.Danger(c, "An error occurred. Please try again.") } return l.Redirect(c, "home") }