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:
2026-08-22 12:16:59 -07:00
parent 5bdaa8977f
commit 29b0536215
26 changed files with 612 additions and 146 deletions
+23 -5
View File
@@ -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)