Address production-readiness review: clearer errors, safer votes, and ops hardening.
Distinguish auth/lookup failures, make votes idempotent on visible questions, bound shutdown, page admin users, LRU throttle, trusted-proxy CIDRs, avatar cleanup, versioned migrations, and session cleanup logging.
This commit is contained in:
+23
-5
@@ -2,6 +2,9 @@ package web
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
@@ -79,8 +82,16 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
u, err := s.store.UserByUsername(r.Context(), username)
|
||||
hash := loginDummyHash
|
||||
if err == nil {
|
||||
switch {
|
||||
case err == nil:
|
||||
hash = []byte(u.PasswordHash)
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
// unknown user — still bcrypt against dummy hash
|
||||
default:
|
||||
log.Printf("login lookup: %v", err)
|
||||
_ = bcrypt.CompareHashAndPassword(loginDummyHash, []byte(password))
|
||||
http.Error(w, "service temporarily unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if err != nil || bcrypt.CompareHashAndPassword(hash, []byte(password)) != nil {
|
||||
s.loginFail.record(loginFailKey(ip, userKey))
|
||||
@@ -138,7 +149,7 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
role := store.RoleUser
|
||||
if setupSecretMatches(s.cfg.AdminSetupSecret, setupSecret) {
|
||||
role = store.RoleAdmin // store downgrades if an admin already exists
|
||||
role = store.RoleAdmin
|
||||
}
|
||||
u := &store.User{
|
||||
Username: username,
|
||||
@@ -146,12 +157,19 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
Role: role,
|
||||
}
|
||||
if err := s.store.CreateUser(r.Context(), u); err != nil {
|
||||
p.Error = "That username is taken."
|
||||
s.exec(w, "register", p)
|
||||
if errors.Is(err, store.ErrDuplicateUsername) {
|
||||
p.Error = "That username is taken."
|
||||
s.exec(w, "register", p)
|
||||
return
|
||||
}
|
||||
log.Printf("register create: %v", err)
|
||||
http.Error(w, "could not create account", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := s.sessions.RenewToken(r.Context()); err != nil {
|
||||
http.Error(w, "could not start session", http.StatusInternalServerError)
|
||||
log.Printf("register session: %v", err)
|
||||
s.sessions.Put(r.Context(), "flash", "Account created — please sign in.")
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
s.sessions.Put(r.Context(), "user_id", u.ID)
|
||||
|
||||
Reference in New Issue
Block a user