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