Proto-to-Prod

This commit is contained in:
Pen Anderson 2026-05-26 11:52:32 -05:00
parent 82a8a6a21a
commit 5cabc31d37
63 changed files with 3783 additions and 602 deletions

View file

@ -16,7 +16,6 @@ import (
type server struct {
staticDir string
password string
qrKey string
secret []byte
secure bool
loginLimiter *rateLimiter
@ -26,20 +25,13 @@ func main() {
addr := flag.String("addr", ":8181", "listen address")
staticDir := flag.String("static", "../web/build", "static asset directory")
insecure := flag.Bool("insecure", false, "drop Secure flag from cookie (dev only)")
strict := flag.Bool("strict", false, "production mode: refuse to start without DOCENT_COOKIE_SECRET and DOCENT_QR_KEY")
strict := flag.Bool("strict", false, "production mode: refuse to start without DOCENT_COOKIE_SECRET")
flag.Parse()
password := os.Getenv("DOCENT_PASSWORD")
if password == "" {
log.Fatal("DOCENT_PASSWORD must be set")
}
qrKey := os.Getenv("DOCENT_QR_KEY")
if qrKey == "" {
if *strict {
log.Fatal("DOCENT_QR_KEY must be set in -strict mode")
}
log.Println("warning: DOCENT_QR_KEY not set; QR-code auto-login disabled")
}
cookieSecret := os.Getenv("DOCENT_COOKIE_SECRET")
if *strict && cookieSecret == "" {
@ -61,7 +53,6 @@ func main() {
s := &server{
staticDir: abs,
password: password,
qrKey: qrKey,
secret: secret,
secure: !*insecure,
loginLimiter: newRateLimiter(8, time.Minute),
@ -69,7 +60,6 @@ func main() {
mux := http.NewServeMux()
mux.HandleFunc("POST /api/login", s.handleLogin)
mux.HandleFunc("POST /api/key", s.handleKey)
mux.HandleFunc("POST /api/logout", s.handleLogout)
mux.HandleFunc("GET /api/me", s.handleMe)
mux.Handle("GET /audio/", s.requireAuth(http.HandlerFunc(s.serveStatic)))
@ -126,26 +116,6 @@ func (s *server) handleLogin(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
func (s *server) handleKey(w http.ResponseWriter, r *http.Request) {
if !s.loginLimiter.allow(clientIP(r)) {
w.Header().Set("Retry-After", "60")
http.Error(w, "too many attempts", http.StatusTooManyRequests)
return
}
var body struct {
Key string `json:"key"`
}
err := json.NewDecoder(r.Body).Decode(&body)
// An empty qrKey means the QR auto-login is disabled — never accept any value.
if err != nil || s.qrKey == "" || !constantTimeEqualString(body.Key, s.qrKey) {
time.Sleep(250 * time.Millisecond)
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
s.setSessionCookie(w)
w.WriteHeader(http.StatusNoContent)
}
func (s *server) handleLogout(w http.ResponseWriter, r *http.Request) {
s.clearSessionCookie(w)
w.WriteHeader(http.StatusNoContent)
@ -207,7 +177,6 @@ func (s *server) serveSPA(w http.ResponseWriter, r *http.Request) {
return
}
// Unknown path → SPA fallback.
w.Header().Set("Cache-Control", "no-cache")
http.ServeFile(w, r, filepath.Join(s.staticDir, "index.html"))
}