From e39a8c29a36dd3c6331fb791229c12f41c695f42 Mon Sep 17 00:00:00 2001 From: jackiehdinh Date: Mon, 24 Apr 2023 08:54:53 -0400 Subject: [PATCH] #142 --- application.go | 100 +++++++++++++++++++++++++++++++++ db/user.go | 89 ++++++++++++++++++++++++----- templates/forgot-password.html | 36 ++++++++++++ templates/reset-password.html | 42 ++++++++++++++ 4 files changed, 254 insertions(+), 13 deletions(-) create mode 100644 templates/forgot-password.html create mode 100644 templates/reset-password.html diff --git a/application.go b/application.go index 258320c..bd5c941 100644 --- a/application.go +++ b/application.go @@ -9,6 +9,7 @@ import ( "log" "math/rand" "net/http" + "net/smtp" "os" "strconv" "strings" @@ -157,6 +158,14 @@ func InitializeRoutes() { userRoutes.POST("/edit_device", middleware.EnsureLoggedIn(), editDevice) userRoutes.POST("/postCoordinates", middleware.EnsureLoggedIn(), changeDeviceCoordinates) + + userRoutes.GET("/forgot-password", middleware.EnsureNotLoggedIn(), showForgotPassword) + + userRoutes.POST("/forgot-password", middleware.EnsureNotLoggedIn(), performForgotPassword) + + userRoutes.GET("/reset-password", middleware.EnsureNotLoggedIn(), showResetPassword) + + userRoutes.POST("/reset-password", middleware.EnsureNotLoggedIn(), performResetPassword) } // Handle GET requests at /map, ensure user is logged in using middleware // Render the index page @@ -764,3 +773,94 @@ func getCurrentDevice() (deviceName string) { return currentDevice } +/* +Renders forgot password page +*/ +func showForgotPassword(c *gin.Context) { + Render(c, gin.H{ + "title": "Forgot Password"}, "forgot-password.html") +} + +/* +Renders reset password page +*/ +func showResetPassword(c *gin.Context) { + Render(c, gin.H{ + "title": "Reset Password"}, "reset-password.html") +} + +/* +Checks if inputted email is in database +If yes, returned to login page +If no, renders error +*/ +func performForgotPassword(c *gin.Context) { + username := c.PostForm("username") + email := c.PostForm("email") + if err := db.CheckUsername(username); err == nil { + c.HTML(http.StatusBadRequest, "forgot-password.html", gin.H{ + "title": "Forgot Password", + "Email": email, + "ErrorTitle": "Invalid Username", + "ErrorMessage": "Username not connected to user."}) + } else if err := db.CheckEmailValid(email); err != nil { + c.HTML(http.StatusBadRequest, "forgot-password.html", gin.H{ + "title": "Forgot Password", + "Username": username, + "ErrorTitle": "Invalid Email Address", + "ErrorMessage": "Please type a valid email address."}) + } else if err := db.CheckEmail(email); err != nil { + from := "bitcrunch2k23@gmail.com" + password := "gydhmmllmtsfjxal" + to := []string{email} + smtpHost := "smtp.gmail.com" + smtpPort := "587" + resetCode := db.GenerateResetCode(username) + message := []byte("Subject: Reset Code\n\nHere is your reset password code: " + resetCode) + auth := smtp.PlainAuth("", from, password, smtpHost) + err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, message) + if err != nil { + c.HTML(http.StatusBadRequest, "forgot-password.html", gin.H{ + "title": "Forgot Password", + "Username": username, + "Email": email, + "ErrorTitle": "Failed to Send Email", + "ErrorMessage": err.Error()}) + } else { + showResetPassword(c) + } + } else { + c.HTML(http.StatusBadRequest, "forgot-password.html", gin.H{ + "title": "Forgot Password", + "ErrorTitle": "Invalid Email Address", + "ErrorMessage": "Email not connected to a user."}) + } +} + +/* +Checks if password is valid +If yes, updates password +If not, renders error +*/ +func performResetPassword(c *gin.Context) { + reset_code := c.PostForm("reset-code") + username := c.PostForm("username") + password := c.PostForm("password") + confirm_password := c.PostForm("confirm_password") + + if err := db.CheckResetCode(reset_code, username); err != nil { + c.HTML(http.StatusBadRequest, "reset-password.html", gin.H{ + "ErrorTitle": "Reset Password Failed", + "ErrorMessage": err.Error()}) + } else if password != confirm_password { + c.HTML(http.StatusBadRequest, "reset-password.html", gin.H{ + "ErrorTitle": "Reset Password Failed", + "ErrorMessage": fmt.Sprintf("Passwords \"%s\" and \"%s\" do not match.", password, confirm_password)}) + } else if err := db.ResetPassword(username, password); err != nil { + c.HTML(http.StatusBadRequest, "reset-password.html", gin.H{ + "ErrorTitle": "Reset Password Failed", + "ErrorMessage": err.Error()}) + } else { + showLoginPage(c) + } +} diff --git a/db/user.go b/db/user.go index da18e12..15bf4e6 100644 --- a/db/user.go +++ b/db/user.go @@ -3,9 +3,11 @@ package db import ( "bufio" "fmt" + "math/rand" "os" "regexp" "strings" + "time" "golang.org/x/crypto/bcrypt" ) @@ -16,18 +18,20 @@ const ( ) type user struct { - username string - password []byte - email string - admin int + username string + password []byte + email string + reset_code string + admin int } func CreateUser(username, password, email string, admin int) (user, error) { u := user{ - username: "", - password: []byte(""), - email: "", - admin: 0, + username: "", + password: []byte(""), + email: "", + reset_code: "", + admin: 0, } if CheckUsername(username) != nil { return u, fmt.Errorf("Username \"%s\" is already in use.", username) @@ -71,7 +75,7 @@ func writeUser(u user) error { defer fil.Close() // Creating the string from the user details // to append to the file - writeString := fmt.Sprintf("%s\t%s\t%s\t%d\n", u.username, u.password, u.email, u.admin) + writeString := fmt.Sprintf("%s\t%s\t%s\t%s\t%d\n", u.username, u.password, u.email, u.reset_code, u.admin) _, err = fil.WriteString(writeString) if err != nil { return err @@ -101,10 +105,11 @@ func ReadUser(uname string) (u user, err error) { if line[0] == uname { // User found, creating it to return u = user{ - username: line[0], - password: []byte(line[1]), - email: line[2], - admin: 0, + username: line[0], + password: []byte(line[1]), + email: line[2], + reset_code: line[3], + admin: 0, } return u, nil } @@ -295,3 +300,61 @@ func CheckEmail(e string) error { func hash(password string) ([]byte, error) { return bcrypt.GenerateFromPassword([]byte(password), hashCost) } + +func ResetPassword(username string, password string) error { + u, err := ReadUser(username) + if err != nil { + return err + } + // User found, checking password + if err := CheckPassword(password); err != nil { + return err + } else { + // Password is valid, changing password + hashed, err := hash(password) + if err != nil { + return err + } + u.password = hashed + if err := DeleteUser(username); err != nil { + return err + } + if err := writeUser(u); err != nil { + return err + } + } + return nil +} + +func GenerateResetCode(username string) string { + u, err := ReadUser(username) + if err != nil { + return "" + } + charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) + b := make([]byte, 5) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + u.reset_code = string(b) + if err := DeleteUser(username); err != nil { + return "" + } + if err := writeUser(u); err != nil { + return "" + } + return string(b) +} + +func CheckResetCode(code string, username string) error { + u, err := ReadUser(username) + if err != nil { + return err + } + if u.reset_code != code { + return fmt.Errorf("Reset code does not match.") + } else { + return nil + } +} diff --git a/templates/forgot-password.html b/templates/forgot-password.html new file mode 100644 index 0000000..58e329f --- /dev/null +++ b/templates/forgot-password.html @@ -0,0 +1,36 @@ + + + +{{ template "header.html" .}} + + + + + +
+

Forgot Your Password?

+

If you have forgotten your password, please enter your account's username and email address below and click the "Reset Password" button. You will receive an email with a reset password code and be redirected to the reset password page.

+
+
+
+
+
+ {{ if .ErrorTitle}} +
+ {{.ErrorTitle}}: {{.ErrorMessage}} +
+ {{end}} +
+ +
+
+
+

Return to Login Page

+
+
+

Don't have an account?

+
+
+ + +{{ template "footer.html" .}} \ No newline at end of file diff --git a/templates/reset-password.html b/templates/reset-password.html new file mode 100644 index 0000000..96eb7d9 --- /dev/null +++ b/templates/reset-password.html @@ -0,0 +1,42 @@ + + + +{{ template "header.html" .}} + + + + + +
+

Reset Password

+

Please type in the reset password code, your username, and your new password below.

Remember that passwords must

+ +
+
+
+
+
+
+
+
+
+ {{ if .ErrorTitle}} +
+ {{.ErrorTitle}}: {{.ErrorMessage}} +
+ {{end}} +
+ +
+

+
+
+ + +{{ template "footer.html" .}} \ No newline at end of file