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
+29 -5
View File
@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"log"
"net"
"net/http"
"os"
"os/signal"
@@ -56,7 +57,7 @@ func newHandler(db *sql.DB, sessions *store.SessionStore, uploader blob.Uploader
srv, err := web.New(store.NewPostgres(db), sessions.Store(), plumber.TemplateFS, plumber.StaticFS, web.Config{
AdminSetupSecret: strings.TrimSpace(os.Getenv("ADMIN_SETUP_SECRET")),
SecureCookie: secureCookieFromEnv(),
TrustProxy: os.Getenv("TRUST_PROXY") == "1",
TrustedProxies: parseTrustedProxies(os.Getenv("TRUSTED_PROXY_CIDRS")),
Blob: uploader,
})
if err != nil {
@@ -65,6 +66,22 @@ func newHandler(db *sql.DB, sessions *store.SessionStore, uploader blob.Uploader
return srv.Handler()
}
func parseTrustedProxies(raw string) []*net.IPNet {
var out []*net.IPNet
for _, part := range strings.Split(raw, ",") {
part = strings.TrimSpace(part)
if part == "" {
continue
}
_, n, err := net.ParseCIDR(part)
if err != nil {
log.Fatalf("TRUSTED_PROXY_CIDRS: bad CIDR %q: %v", part, err)
}
out = append(out, n)
}
return out
}
// secureCookieFromEnv defaults to secure when PORT is set (PaaS/production)
// and refuses an explicit disable in that environment.
func secureCookieFromEnv() bool {
@@ -96,12 +113,19 @@ func run(httpSrv *http.Server) {
case sig := <-sigCh:
log.Printf("shutdown signal: %v", sig)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := httpSrv.Shutdown(ctx); err != nil {
err := httpSrv.Shutdown(ctx)
cancel()
if err != nil {
log.Printf("shutdown: %v", err)
_ = httpSrv.Close()
}
if err := <-errCh; err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
select {
case err := <-errCh:
if err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Printf("server exit: %v", err)
}
case <-time.After(3 * time.Second):
log.Printf("server exit: timed out waiting for ListenAndServe")
}
}
}