From 754578f88521e72c0191441bf30bb45df5bd0d80 Mon Sep 17 00:00:00 2001
From: Mohamad Damaj <mohamad@damaj.tech>
Date: Fri, 21 Jul 2023 15:52:10 +0300
Subject: [PATCH 1/5] Sanitize the email input

Sanitize it using regex
---
 main.go | 24 +++++++++++++++---------
 1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/main.go b/main.go
index d909832..9a8049d 100644
--- a/main.go
+++ b/main.go
@@ -18,6 +18,7 @@ import (
 	"strconv"
 	"strings"
 	"time"
+	"regexp"
 )
 
 const (
@@ -160,18 +161,24 @@ func main() {
 
 	e.POST("/reset", func(c echo.Context) error {
 		mail := c.FormValue("email")
+		// Define a regular expression to match any non-alphanumeric characters
+		re := regexp.MustCompile("[^a-zA-Z0-9]+")
+
+		// Replace any non-alphanumeric characters with an empty string
+		sanitizedMail := re.ReplaceAllString(mail, "")
+		
 		go func() {
 			// Check if there is already a password reset
-			_, exists := passwordResetCache.Get(mail)
+			_, exists := passwordResetCache.Get(sanitizedMail)
 			if exists {
-				log.Printf("[Cache] Mail %q already exists in cache, ignoring\n", mail)
+				log.Printf("[Cache] Mail %q already exists in cache, ignoring\n", sanitizedMail)
 				return
 			}
 
 			// Check if it's exists in Maddy db
 			// It will return an error is there is no user found
 			var password string
-			err = db.QueryRow("SELECT value FROM passwords WHERE key = ?", mail).Scan(&password)
+			err = db.QueryRow("SELECT value FROM passwords WHERE key = ?", sanitizedMail).Scan(&password)
 			if err != nil {
 				log.Println("[Sqlite] An error occurred while trying to get password from Maddy database:", err)
 				return
@@ -179,14 +186,14 @@ func main() {
 
 			// Generating an unique key
 			random := randomString(10)
-			passwordResetCache.Set(random, mail, CacheTime)
+			passwordResetCache.Set(random, sanitizedMail, CacheTime)
 
 			// Connect to the server, authenticate, set the sender and recipient,
 			// and send the email all in one step.
-			to := []string{mail}
+			to := []string{sanitizedMail}
 
 			if !DebugBypassMailSending {
-				msg := strings.ReplaceAll(EmailTemplate, "$TO", mail)
+				msg := strings.ReplaceAll(EmailTemplate, "$TO", sanitizedMail)
 				msg = strings.ReplaceAll(msg, "$FROM", EmailFrom)
 				msg = strings.ReplaceAll(msg, "$SUBJECT", EmailSubject)
 				msg = strings.ReplaceAll(msg, "$MESSAGE", EmailMessage)
@@ -221,12 +228,12 @@ func main() {
 	e.POST("/reset/:key", func(c echo.Context) error {
 		key := c.Param("key")
 		password := c.FormValue("password")
-		mail, exists := passwordResetCache.Get(key)
+		sanitizedMail, exists := passwordResetCache.Get(key)
 		if exists {
 			passwordResetCache.Delete(key)
 		}
 
-		maddyExecCommand := exec.Command("maddy", "creds", "password", "-p", password, mail.(string))
+		maddyExecCommand := exec.Command("maddy", "creds", "password", "-p", password, sanitizedMail.(string))
 		err = maddyExecCommand.Run()
 		if err != nil {
 			log.Println("[maddyExecCommand] Failed to execute Maddy's password reset command - ", err)
@@ -235,7 +242,6 @@ func main() {
 
 		return c.String(http.StatusOK, "All good! Your password is now changed.")
 	})
-
 	log.Println("[echo] Starting Echo web server")
 	e.Logger.Fatal(e.Start(":" + strconv.Itoa(HTTPServerPort)))
 }

From d70831744a40451ee9bbe861b2598274bd307432 Mon Sep 17 00:00:00 2001
From: Mohamad Damaj <mohamad@damaj.tech>
Date: Fri, 21 Jul 2023 22:47:59 +0300
Subject: [PATCH 2/5] Attempt at the suggested way

---
 main.go | 124 ++++++++++++++++++++++++++++++++------------------------
 1 file changed, 70 insertions(+), 54 deletions(-)

diff --git a/main.go b/main.go
index 9a8049d..96af72b 100644
--- a/main.go
+++ b/main.go
@@ -3,22 +3,23 @@ package main
 import (
 	cryptorand "crypto/rand"
 	"database/sql"
-	"github.com/akyoto/cache"
-	"github.com/hugmouse/maddy-password-reset/templates"
-	"github.com/labstack/echo/v4"
-	"github.com/labstack/echo/v4/middleware"
+	"fmt"
 	"html/template"
 	"io"
 	"log"
 	"math/big"
-	_ "modernc.org/sqlite"
 	"net/http"
+	"net/mail"
 	"net/smtp"
 	"os/exec"
 	"strconv"
 	"strings"
 	"time"
-	"regexp"
+	_ "modernc.org/sqlite"
+	"github.com/akyoto/cache"
+	"github.com/hugmouse/maddy-password-reset/templates"
+	"github.com/labstack/echo/v4"
+	"github.com/labstack/echo/v4/middleware"
 )
 
 const (
@@ -99,6 +100,22 @@ func (t *Template) Render(w io.Writer, name string, data interface{}, _ echo.Con
 	return t.templates.ExecuteTemplate(w, name, data)
 }
 
+func isValidEmailAddress(email string) error {
+	// Parse the email address using addressparser
+	mail, err := mail.ParseAddress(email)
+	if err != nil {
+		return err
+	}
+
+	// Check if the parsed address is not nil and has a valid email format
+	if mail == nil || mail.Address == "" {
+		log.Fatalln("Invalid Email Address!")
+		return err
+	}
+
+	return nil
+}
+
 func main() {
 	var auth smtp.Auth
 	if !DebugBypassMailSending {
@@ -161,54 +178,52 @@ func main() {
 
 	e.POST("/reset", func(c echo.Context) error {
 		mail := c.FormValue("email")
-		// Define a regular expression to match any non-alphanumeric characters
-		re := regexp.MustCompile("[^a-zA-Z0-9]+")
-
-		// Replace any non-alphanumeric characters with an empty string
-		sanitizedMail := re.ReplaceAllString(mail, "")
-		
-		go func() {
-			// Check if there is already a password reset
-			_, exists := passwordResetCache.Get(sanitizedMail)
-			if exists {
-				log.Printf("[Cache] Mail %q already exists in cache, ignoring\n", sanitizedMail)
-				return
-			}
-
-			// Check if it's exists in Maddy db
-			// It will return an error is there is no user found
-			var password string
-			err = db.QueryRow("SELECT value FROM passwords WHERE key = ?", sanitizedMail).Scan(&password)
-			if err != nil {
-				log.Println("[Sqlite] An error occurred while trying to get password from Maddy database:", err)
-				return
-			}
-
-			// Generating an unique key
-			random := randomString(10)
-			passwordResetCache.Set(random, sanitizedMail, CacheTime)
-
-			// Connect to the server, authenticate, set the sender and recipient,
-			// and send the email all in one step.
-			to := []string{sanitizedMail}
-
-			if !DebugBypassMailSending {
-				msg := strings.ReplaceAll(EmailTemplate, "$TO", sanitizedMail)
-				msg = strings.ReplaceAll(msg, "$FROM", EmailFrom)
-				msg = strings.ReplaceAll(msg, "$SUBJECT", EmailSubject)
-				msg = strings.ReplaceAll(msg, "$MESSAGE", EmailMessage)
-				msg = strings.ReplaceAll(msg, "$RESET_LINK", HostingURL+"reset/"+random)
-
-				err := smtp.SendMail(MXServer, auth, SMTPMailUsername, to, []byte(msg))
-				if err != nil {
-					log.Println("[SMTP] Failed to send mail - ", err)
+		if err := isValidEmailAddress(mail); err != nil {
+			fmt.Println("Invalid email address:", err)
+		} else {
+			go func() {
+				// Check if there is already a password reset
+				_, exists := passwordResetCache.Get(mail)
+				if exists {
+					log.Printf("[Cache] Mail %q already exists in cache, ignoring\n", mail)
 					return
 				}
-			} else {
-				log.Println("[SMTP] Debug mode enabled, not sending email")
-				log.Println("[SMTP] Reset link:", HostingURL+"reset/"+random)
-			}
-		}()
+
+				// Check if it's exists in Maddy db
+				// It will return an error is there is no user found
+				var password string
+				err = db.QueryRow("SELECT value FROM passwords WHERE key = ?", mail).Scan(&password)
+				if err != nil {
+					log.Println("[Sqlite] An error occurred while trying to get password from Maddy database:", err)
+					return
+				}
+
+				// Generating an unique key
+				random := randomString(10)
+				passwordResetCache.Set(random, mail, CacheTime)
+
+				// Connect to the server, authenticate, set the sender and recipient,
+				// and send the email all in one step.
+				to := []string{mail}
+
+				if !DebugBypassMailSending {
+					msg := strings.ReplaceAll(EmailTemplate, "$TO", mail)
+					msg = strings.ReplaceAll(msg, "$FROM", EmailFrom)
+					msg = strings.ReplaceAll(msg, "$SUBJECT", EmailSubject)
+					msg = strings.ReplaceAll(msg, "$MESSAGE", EmailMessage)
+					msg = strings.ReplaceAll(msg, "$RESET_LINK", HostingURL+"reset/"+random)
+
+					err := smtp.SendMail(MXServer, auth, SMTPMailUsername, to, []byte(msg))
+					if err != nil {
+						log.Println("[SMTP] Failed to send mail - ", err)
+						return
+					}
+				} else {
+					log.Println("[SMTP] Debug mode enabled, not sending email")
+					log.Println("[SMTP] Reset link:", HostingURL+"reset/"+random)
+				}
+			}()
+		}
 		return c.Render(http.StatusOK, "reset.gohtml", map[string]any{
 			"Sent": true,
 		})
@@ -228,12 +243,12 @@ func main() {
 	e.POST("/reset/:key", func(c echo.Context) error {
 		key := c.Param("key")
 		password := c.FormValue("password")
-		sanitizedMail, exists := passwordResetCache.Get(key)
+		mail, exists := passwordResetCache.Get(key)
 		if exists {
 			passwordResetCache.Delete(key)
 		}
 
-		maddyExecCommand := exec.Command("maddy", "creds", "password", "-p", password, sanitizedMail.(string))
+		maddyExecCommand := exec.Command("maddy", "creds", "password", "-p", password, mail.(string))
 		err = maddyExecCommand.Run()
 		if err != nil {
 			log.Println("[maddyExecCommand] Failed to execute Maddy's password reset command - ", err)
@@ -242,6 +257,7 @@ func main() {
 
 		return c.String(http.StatusOK, "All good! Your password is now changed.")
 	})
+
 	log.Println("[echo] Starting Echo web server")
 	e.Logger.Fatal(e.Start(":" + strconv.Itoa(HTTPServerPort)))
 }

From 7d4a9f82c31c48922c4becdd3b6ebd4039d7b9fb Mon Sep 17 00:00:00 2001
From: Mohamad Damaj <mohamad301Damaj@duck.com>
Date: Sat, 22 Jul 2023 11:32:43 +0300
Subject: [PATCH 3/5] Satisfy requirements

---
 main.go | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/main.go b/main.go
index 96af72b..ac32a71 100644
--- a/main.go
+++ b/main.go
@@ -109,7 +109,7 @@ func isValidEmailAddress(email string) error {
 
 	// Check if the parsed address is not nil and has a valid email format
 	if mail == nil || mail.Address == "" {
-		log.Fatalln("Invalid Email Address!")
+		log.Println("[AddressParser]: Invalid Email Address: %v")
 		return err
 	}
 
@@ -178,9 +178,11 @@ func main() {
 
 	e.POST("/reset", func(c echo.Context) error {
 		mail := c.FormValue("email")
-		if err := isValidEmailAddress(mail); err != nil {
-			fmt.Println("Invalid email address:", err)
-		} else {
+		err = isValidEmailAddress(mail)
+		if err != nil {
+			log.Println("[AddressParser]: Invalid mail address: ", err)
+			return err
+}
 			go func() {
 				// Check if there is already a password reset
 				_, exists := passwordResetCache.Get(mail)
@@ -223,7 +225,7 @@ func main() {
 					log.Println("[SMTP] Reset link:", HostingURL+"reset/"+random)
 				}
 			}()
-		}
+
 		return c.Render(http.StatusOK, "reset.gohtml", map[string]any{
 			"Sent": true,
 		})

From 93b5066af81879730a0d0971206edd871e0b14ea Mon Sep 17 00:00:00 2001
From: Mohamad Damaj <mohamad@damaj.tech>
Date: Sun, 23 Jul 2023 12:28:10 +0000
Subject: [PATCH 4/5] format using go fmt

---
 main.go | 88 ++++++++++++++++++++++++++++-----------------------------
 1 file changed, 44 insertions(+), 44 deletions(-)

diff --git a/main.go b/main.go
index ac32a71..fab18f0 100644
--- a/main.go
+++ b/main.go
@@ -4,10 +4,15 @@ import (
 	cryptorand "crypto/rand"
 	"database/sql"
 	"fmt"
+	"github.com/akyoto/cache"
+	"github.com/hugmouse/maddy-password-reset/templates"
+	"github.com/labstack/echo/v4"
+	"github.com/labstack/echo/v4/middleware"
 	"html/template"
 	"io"
 	"log"
 	"math/big"
+	_ "modernc.org/sqlite"
 	"net/http"
 	"net/mail"
 	"net/smtp"
@@ -15,11 +20,6 @@ import (
 	"strconv"
 	"strings"
 	"time"
-	_ "modernc.org/sqlite"
-	"github.com/akyoto/cache"
-	"github.com/hugmouse/maddy-password-reset/templates"
-	"github.com/labstack/echo/v4"
-	"github.com/labstack/echo/v4/middleware"
 )
 
 const (
@@ -182,49 +182,49 @@ func main() {
 		if err != nil {
 			log.Println("[AddressParser]: Invalid mail address: ", err)
 			return err
-}
-			go func() {
-				// Check if there is already a password reset
-				_, exists := passwordResetCache.Get(mail)
-				if exists {
-					log.Printf("[Cache] Mail %q already exists in cache, ignoring\n", mail)
-					return
-				}
+		}
+		go func() {
+			// Check if there is already a password reset
+			_, exists := passwordResetCache.Get(mail)
+			if exists {
+				log.Printf("[Cache] Mail %q already exists in cache, ignoring\n", mail)
+				return
+			}
 
-				// Check if it's exists in Maddy db
-				// It will return an error is there is no user found
-				var password string
-				err = db.QueryRow("SELECT value FROM passwords WHERE key = ?", mail).Scan(&password)
+			// Check if it's exists in Maddy db
+			// It will return an error is there is no user found
+			var password string
+			err = db.QueryRow("SELECT value FROM passwords WHERE key = ?", mail).Scan(&password)
+			if err != nil {
+				log.Println("[Sqlite] An error occurred while trying to get password from Maddy database:", err)
+				return
+			}
+
+			// Generating an unique key
+			random := randomString(10)
+			passwordResetCache.Set(random, mail, CacheTime)
+
+			// Connect to the server, authenticate, set the sender and recipient,
+			// and send the email all in one step.
+			to := []string{mail}
+
+			if !DebugBypassMailSending {
+				msg := strings.ReplaceAll(EmailTemplate, "$TO", mail)
+				msg = strings.ReplaceAll(msg, "$FROM", EmailFrom)
+				msg = strings.ReplaceAll(msg, "$SUBJECT", EmailSubject)
+				msg = strings.ReplaceAll(msg, "$MESSAGE", EmailMessage)
+				msg = strings.ReplaceAll(msg, "$RESET_LINK", HostingURL+"reset/"+random)
+
+				err := smtp.SendMail(MXServer, auth, SMTPMailUsername, to, []byte(msg))
 				if err != nil {
-					log.Println("[Sqlite] An error occurred while trying to get password from Maddy database:", err)
+					log.Println("[SMTP] Failed to send mail - ", err)
 					return
 				}
-
-				// Generating an unique key
-				random := randomString(10)
-				passwordResetCache.Set(random, mail, CacheTime)
-
-				// Connect to the server, authenticate, set the sender and recipient,
-				// and send the email all in one step.
-				to := []string{mail}
-
-				if !DebugBypassMailSending {
-					msg := strings.ReplaceAll(EmailTemplate, "$TO", mail)
-					msg = strings.ReplaceAll(msg, "$FROM", EmailFrom)
-					msg = strings.ReplaceAll(msg, "$SUBJECT", EmailSubject)
-					msg = strings.ReplaceAll(msg, "$MESSAGE", EmailMessage)
-					msg = strings.ReplaceAll(msg, "$RESET_LINK", HostingURL+"reset/"+random)
-
-					err := smtp.SendMail(MXServer, auth, SMTPMailUsername, to, []byte(msg))
-					if err != nil {
-						log.Println("[SMTP] Failed to send mail - ", err)
-						return
-					}
-				} else {
-					log.Println("[SMTP] Debug mode enabled, not sending email")
-					log.Println("[SMTP] Reset link:", HostingURL+"reset/"+random)
-				}
-			}()
+			} else {
+				log.Println("[SMTP] Debug mode enabled, not sending email")
+				log.Println("[SMTP] Reset link:", HostingURL+"reset/"+random)
+			}
+		}()
 
 		return c.Render(http.StatusOK, "reset.gohtml", map[string]any{
 			"Sent": true,

From a353afa264793be0d2f525521325374d4f549f8f Mon Sep 17 00:00:00 2001
From: hugmouse <me@mysh.dev>
Date: Mon, 24 Jul 2023 18:23:01 +0200
Subject: [PATCH 5/5] Add error context

---
 main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/main.go b/main.go
index fab18f0..4985ee3 100644
--- a/main.go
+++ b/main.go
@@ -181,7 +181,7 @@ func main() {
 		err = isValidEmailAddress(mail)
 		if err != nil {
 			log.Println("[AddressParser]: Invalid mail address: ", err)
-			return err
+			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid mail address: %v", err))
 		}
 		go func() {
 			// Check if there is already a password reset