Various quick fixes

* Signup: if entering an existing email, don't admit that the email
  exists. Instead, send a specialized email to its address.
* Search: no longer search for users by email address.
* Login: always hash the incoming password on user not found, to take
  constant time compared to when the user did exist.
* Fix a pagination bug when a private (shy account) views a non-friend's
  photo gallery.
This commit is contained in:
Noah Petherbridge 2023-08-15 17:33:33 -07:00
parent 868aef6fb0
commit 1ee8acf060
8 changed files with 82 additions and 24 deletions

View File

@ -10,6 +10,7 @@ import (
"code.nonshy.com/nonshy/website/pkg/ratelimit"
"code.nonshy.com/nonshy/website/pkg/session"
"code.nonshy.com/nonshy/website/pkg/templates"
"golang.org/x/crypto/bcrypt"
)
// Login controller.
@ -29,6 +30,11 @@ func Login() http.HandlerFunc {
// Look up their account.
user, err := models.FindUser(username)
if err != nil {
// The user wasn't found, but still hash the incoming password to take time:
// so a mischievous user can't infer whether the username was valid based
// on the server response time.
bcrypt.GenerateFromPassword([]byte(password), config.BcryptCost)
session.FlashError(w, r, "Incorrect username or password.")
templates.Redirect(w, r.URL.Path)
return

View File

@ -26,7 +26,7 @@ func Search() http.HandlerFunc {
// Search filters.
var (
isCertified = r.FormValue("certified")
username = r.FormValue("username") // email or username
username = r.FormValue("username") // username search
gender = r.FormValue("gender")
orientation = r.FormValue("orientation")
maritalStatus = r.FormValue("marital_status")
@ -73,14 +73,14 @@ func Search() http.HandlerFunc {
pager.ParsePage(r)
users, err := models.SearchUsers(currentUser, &models.UserSearch{
EmailOrUsername: username,
Gender: gender,
Orientation: orientation,
MaritalStatus: maritalStatus,
Certified: isCertified != "false",
InnerCircle: isCertified == "circle",
AgeMin: ageMin,
AgeMax: ageMax,
Username: username,
Gender: gender,
Orientation: orientation,
MaritalStatus: maritalStatus,
Certified: isCertified != "false",
InnerCircle: isCertified == "circle",
AgeMin: ageMin,
AgeMax: ageMax,
}, pager)
if err != nil {
session.FlashError(w, r, "Couldn't search users: %s", err)

View File

@ -126,7 +126,22 @@ func Signup() http.HandlerFunc {
// Already an account?
if _, err := models.FindUser(email); err == nil {
session.FlashError(w, r, "There is already an account with that e-mail address.")
// We don't want to admit that the email already is registered, so send an email to the
// address in case the user legitimately forgot, but flash the regular success message.
err := mail.Send(mail.Message{
To: email,
Subject: "You already have a nonshy account",
Template: "email/already_signed_up.html",
Data: map[string]interface{}{
"Title": config.Title,
"URL": config.Current.BaseURL + "/forgot-password",
},
})
if err != nil {
session.FlashError(w, r, "Error sending an email: %s", err)
}
session.Flash(w, r, "We have sent an e-mail to %s with a link to continue signing up your account. Please go and check your e-mail.", email)
templates.Redirect(w, r.URL.Path)
return
}

View File

@ -61,7 +61,7 @@ func UserPhotos() http.HandlerFunc {
"User": user,
"Photos": []*models.Photo{},
"PhotoCount": models.CountPhotos(user.ID),
"Pager": models.Pagination{},
"Pager": &models.Pagination{},
}
if err := tmpl.Execute(w, r, vars); err != nil {

View File

@ -184,14 +184,14 @@ func (u *User) IsShyFrom(other *User) bool {
// UserSearch config.
type UserSearch struct {
EmailOrUsername string
Gender string
Orientation string
MaritalStatus string
Certified bool
InnerCircle bool
AgeMin int
AgeMax int
Username string
Gender string
Orientation string
MaritalStatus string
Certified bool
InnerCircle bool
AgeMin int
AgeMax int
}
// SearchUsers from the perspective of a given user.
@ -213,10 +213,10 @@ func SearchUsers(user *User, search *UserSearch, pager *Pagination) ([]*User, er
placeholders = append(placeholders, blockedUserIDs)
}
if search.EmailOrUsername != "" {
ilike := "%" + strings.TrimSpace(strings.ToLower(search.EmailOrUsername)) + "%"
wheres = append(wheres, "(email LIKE ? OR username LIKE ?)")
placeholders = append(placeholders, ilike, ilike)
if search.Username != "" {
ilike := "%" + strings.TrimSpace(strings.ToLower(search.Username)) + "%"
wheres = append(wheres, "username LIKE ?")
placeholders = append(placeholders, ilike)
}
if search.Gender != "" {

View File

@ -57,7 +57,7 @@
<div class="column">
<div class="field">
<label class="label">Email or username:</label>
<label class="label">Partial username:</label>
<input type="text" class="input"
name="username"
autocomplete="off"

View File

@ -0,0 +1,35 @@
{{define "content"}}
<html>
<body bakground="#ffffff" color="#000000" link="#0000FF" vlink="#990099" alink="#FF0000">
<basefont face="Arial,Helvetica,sans-serif" size="3" color="#000000"></basefont>
<h1>You already have a {{.Data.Title}} account</h1>
<p>
Somebody (hopefully you) has tried to sign up a new account by entering your e-mail address.
We already have an account for this e-mail address, but we didn't admit that to whoever
just signed up.
</p>
<p>
If it was not you, then you can disregard this e-mail.
</p>
<p>
If you have forgotten your password, you can request a password reset at the link below:
</p>
<p>
<a href="{{.Data.URL}}" target="_blank">{{.Data.URL}}</a>
</p>
<p>
You may sign in to the website using your e-mail address and account password.
</p>
<p>
This is an automated e-mail; do not reply to this message.
</p>
</body>
</html>
{{end}}

View File

@ -10,6 +10,7 @@ added. Should be suitable for most pagers that don't need any specialized logic.
See also: template_funcs.go for the SimplePager wrapper function.
-->
{{define "SimplePager"}}
{{if .Pager.Pages}}
<nav class="pagination" role="navigation" aria-label="pagination">
<a class="pagination-previous{{if not .Pager.HasPrevious}} is-disabled{{end}}" title="Previous"
href="{{.Request.URL.Path}}?{{QueryPlus "page" .Pager.Previous}}">Previous</a>
@ -28,4 +29,5 @@ See also: template_funcs.go for the SimplePager wrapper function.
{{end}}
</ul>
</nav>
{{end}}
{{end}}