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.
+
+
+
+
+
+
+{{ 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
+
+ have at least 10 characters
+ have at least 1 digit
+ have at least 1 symbol
+ have at least 1 uppercase character
+ have at least 1 lowercase character
+
+
+
+
+
+{{ template "footer.html" .}}
\ No newline at end of file