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 5a71a2de..7d1ba631 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 f1527223..04801585 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 00000000..b61d9b81
--- /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 7e82a964..74954222 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 c8ab4f38..274cdf27 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)
}