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/ratelimit"
"code.nonshy.com/nonshy/website/pkg/session" "code.nonshy.com/nonshy/website/pkg/session"
"code.nonshy.com/nonshy/website/pkg/templates" "code.nonshy.com/nonshy/website/pkg/templates"
"golang.org/x/crypto/bcrypt"
) )
// Login controller. // Login controller.
@ -29,6 +30,11 @@ func Login() http.HandlerFunc {
// Look up their account. // Look up their account.
user, err := models.FindUser(username) user, err := models.FindUser(username)
if err != nil { 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.") session.FlashError(w, r, "Incorrect username or password.")
templates.Redirect(w, r.URL.Path) templates.Redirect(w, r.URL.Path)
return return

View File

@ -26,7 +26,7 @@ func Search() http.HandlerFunc {
// Search filters. // Search filters.
var ( var (
isCertified = r.FormValue("certified") isCertified = r.FormValue("certified")
username = r.FormValue("username") // email or username username = r.FormValue("username") // username search
gender = r.FormValue("gender") gender = r.FormValue("gender")
orientation = r.FormValue("orientation") orientation = r.FormValue("orientation")
maritalStatus = r.FormValue("marital_status") maritalStatus = r.FormValue("marital_status")
@ -73,7 +73,7 @@ func Search() http.HandlerFunc {
pager.ParsePage(r) pager.ParsePage(r)
users, err := models.SearchUsers(currentUser, &models.UserSearch{ users, err := models.SearchUsers(currentUser, &models.UserSearch{
EmailOrUsername: username, Username: username,
Gender: gender, Gender: gender,
Orientation: orientation, Orientation: orientation,
MaritalStatus: maritalStatus, MaritalStatus: maritalStatus,

View File

@ -126,7 +126,22 @@ func Signup() http.HandlerFunc {
// Already an account? // Already an account?
if _, err := models.FindUser(email); err == nil { 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) templates.Redirect(w, r.URL.Path)
return return
} }

View File

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

View File

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

View File

@ -57,7 +57,7 @@
<div class="column"> <div class="column">
<div class="field"> <div class="field">
<label class="label">Email or username:</label> <label class="label">Partial username:</label>
<input type="text" class="input" <input type="text" class="input"
name="username" name="username"
autocomplete="off" 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. See also: template_funcs.go for the SimplePager wrapper function.
--> -->
{{define "SimplePager"}} {{define "SimplePager"}}
{{if .Pager.Pages}}
<nav class="pagination" role="navigation" aria-label="pagination"> <nav class="pagination" role="navigation" aria-label="pagination">
<a class="pagination-previous{{if not .Pager.HasPrevious}} is-disabled{{end}}" title="Previous" <a class="pagination-previous{{if not .Pager.HasPrevious}} is-disabled{{end}}" title="Previous"
href="{{.Request.URL.Path}}?{{QueryPlus "page" .Pager.Previous}}">Previous</a> href="{{.Request.URL.Path}}?{{QueryPlus "page" .Pager.Previous}}">Previous</a>
@ -29,3 +30,4 @@ See also: template_funcs.go for the SimplePager wrapper function.
</ul> </ul>
</nav> </nav>
{{end}} {{end}}
{{end}}