From 368e657a79ecd9a586839343f876c5c16b142fd8 Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Wed, 19 Jul 2023 17:36:00 -0500 Subject: [PATCH] Send an email if you sign up with an existing email --- src/email/email.go | 46 +++++++++++++++++-- src/hmnurl/urls.go | 6 ++- src/templates/src/email_account_existing.html | 16 +++++++ src/templates/src/email_registration.html | 2 +- src/website/auth.go | 34 +++++++++----- 5 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 src/templates/src/email_account_existing.html diff --git a/src/email/email.go b/src/email/email.go index 5a71a2d..7d1ba63 100644 --- a/src/email/email.go +++ b/src/email/email.go @@ -48,7 +48,47 @@ func SendRegistrationEmail( perf.EndBlock() perf.StartBlock("EMAIL", "Sending email") - err = sendMail(toAddress, toName, "[handmade.network] Registration confirmation", contents) + err = sendMail(toAddress, toName, "[Handmade Network] Registration confirmation", contents) + if err != nil { + return oops.New(err, "Failed to send email") + } + perf.EndBlock() + + perf.EndBlock() + + return nil +} + +type ExistingAccountEmailData struct { + Name string + Username string + HomepageUrl string + LoginUrl string +} + +func SendExistingAccountEmail( + toAddress string, + toName string, + username string, + destination string, + perf *perf.RequestPerf, +) error { + perf.StartBlock("EMAIL", "Existing account email") + + perf.StartBlock("EMAIL", "Rendering template") + contents, err := renderTemplate("email_account_existing.html", ExistingAccountEmailData{ + Name: toName, + Username: username, + HomepageUrl: hmnurl.BuildHomepage(), + LoginUrl: hmnurl.BuildLoginPage(destination), + }) + if err != nil { + return err + } + perf.EndBlock() + + perf.StartBlock("EMAIL", "Sending email") + err = sendMail(toAddress, toName, "[Handmade Network] You already have an account!", contents) if err != nil { return oops.New(err, "Failed to send email") } @@ -80,7 +120,7 @@ func SendPasswordReset(toAddress string, toName string, username string, resetTo perf.EndBlock() perf.StartBlock("EMAIL", "Sending email") - err = sendMail(toAddress, toName, "[handmade.network] Your password reset request", contents) + err = sendMail(toAddress, toName, "[Handmade Network] Your password reset request", contents) if err != nil { return oops.New(err, "Failed to send email") } @@ -118,7 +158,7 @@ func SendTimeMachineEmail(profileUrl, username, userEmail, discordUsername strin return err } - err = sendMail("team@handmade.network", "HMN Team", "[time machine] New submission", contents) + err = sendMail("team@handmade.network", "HMN Team", "[Time Machine] New submission", contents) if err != nil { return oops.New(err, "Failed to send email") } diff --git a/src/hmnurl/urls.go b/src/hmnurl/urls.go index f152722..0480158 100644 --- a/src/hmnurl/urls.go +++ b/src/hmnurl/urls.go @@ -165,7 +165,11 @@ var RegexLoginPage = regexp.MustCompile("^/login$") func BuildLoginPage(redirectTo string) string { defer CatchPanic() - return Url("/login", []Q{{Name: "redirect", Value: redirectTo}}) + var q []Q + if redirectTo != "" { + q = append(q, Q{Name: "redirect", Value: redirectTo}) + } + return Url("/login", q) } var RegexLoginWithDiscord = regexp.MustCompile("^/login-with-discord$") diff --git a/src/templates/src/email_account_existing.html b/src/templates/src/email_account_existing.html new file mode 100644 index 0000000..b61d9b8 --- /dev/null +++ b/src/templates/src/email_account_existing.html @@ -0,0 +1,16 @@ +

+ Hello {{ .Name }} - you already have a Handmade Network account. Welcome back! +

+

+ Your username is {{ .Username }}. To sign in, please visit the following link and try signing in with your existing username: +

+

+ {{ .LoginUrl }} +

+

Thanks,
+The Handmade Network staff.

+ +
+

+You are receiving this email because someone tried creating a new account with your email address at handmade.network. If that wasn't you, kindly ignore this email. +

diff --git a/src/templates/src/email_registration.html b/src/templates/src/email_registration.html index 7e82a96..7495422 100644 --- a/src/templates/src/email_registration.html +++ b/src/templates/src/email_registration.html @@ -5,7 +5,7 @@ To complete the registration process, please use the following link:

- {{ .CompleteRegistrationUrl }}. + {{ .CompleteRegistrationUrl }}

Thanks,
The Handmade Network staff.

diff --git a/src/website/auth.go b/src/website/auth.go index c8ab4f3..274cdf2 100644 --- a/src/website/auth.go +++ b/src/website/auth.go @@ -232,26 +232,38 @@ func RegisterNewUserSubmit(c *RequestContext) ResponseData { return c.RejectRequest(fmt.Sprintf("Username (%s) already exists.", username)) } - emailAlreadyExists := true - _, err = db.QueryOneScalar[int](c, c.Conn, + existingUser, err := db.QueryOne[models.User](c, c.Conn, ` - SELECT id + SELECT $columns FROM hmn_user WHERE LOWER(email) = LOWER($1) `, emailAddress, ) - if err != nil { - if errors.Is(err, db.NotFound) { - emailAlreadyExists = false - } else { - return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch user")) - } + if errors.Is(err, db.NotFound) { + // this is fine + } else if err != nil { + return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch user")) } c.Perf.EndBlock() - if emailAlreadyExists { - // NOTE(asaf): Silent rejection so we don't allow attackers to harvest emails. + if existingUser != nil { + // Render the page as if it was a successful new registration, but + // instead send an email to the duplicate email address containing + // their actual username. Spammers won't be able to harvest emails, but + // normal users will be able to find and access their old accounts. + + err := email.SendExistingAccountEmail( + existingUser.Email, + existingUser.BestName(), + existingUser.Username, + destination, + c.Perf, + ) + if err != nil { + return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to send existing account email")) + } + return c.Redirect(hmnurl.BuildRegistrationSuccess(), http.StatusSeeOther) }