half broken
build / build-push (push) Failing after 23s

This commit is contained in:
developer committed 2026-09-27 17:17:53 +00:00
1 parent cc7466823b
commit e8c5b414d9
44 files changed
+7649 -344

No files matched your search

+28 -8
View File
@@ -9,7 +9,6 @@ package api
import (
"context"
"crypto/subtle"
"encoding/json"
"errors"
"fmt"
@@ -241,7 +240,7 @@ func (s *Server) routes(cfg *config.Config) *http.ServeMux {
http.Error(w, "metrics not available", http.StatusNotImplemented)
})))
}
mux.Handle("POST "+base+"auth/login", s.public(s.handleLogin))
mux.Handle("POST "+base+"auth/login", s.limited(s.handleLogin))
mux.Handle("POST "+base+"auth/logout", s.authed(s.handleLogout, false))
// Everything below requires a principal.
@@ -320,6 +319,22 @@ func (s *Server) cors(next http.Handler) http.Handler {
// public marks a handler as reachable without a principal.
func (s *Server) public(h http.HandlerFunc) http.Handler { return h }
// limited applies the per-IP rate limit to a handler that is reachable without
// a principal. The login endpoint is the reason the limit exists: guessing
// API keys is bounded by nothing but the attacker's patience, so the lockout
// the login handler records has to be able to stop the *next* attempt. It did
// not, because the limiter ran behind authed() and login is public, so N bad
// keys gave N 401s and the lockout only locked the caller out of the rest of
// the API.
func (s *Server) limited(h http.HandlerFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !s.allowRequest(w, r) {
return
}
h(w, r)
})
}
// cfg is a small accessor so handlers read the current config once.
// authed wraps a handler with authentication and, for mutations, a write
@@ -503,8 +518,17 @@ func (s *Server) recordLoginFailure(ip string) {
func (s *Server) clearLoginFailures(ip string) {
s.limMu.Lock()
delete(s.limits, ip)
s.limMu.Unlock()
defer s.limMu.Unlock()
st, ok := s.limits[ip]
if !ok {
return
}
// Only the auth-failure state is reset. Deleting the whole entry would
// also reset the per-minute request window, so a single successful login
// in the middle of a burst would hand the client a fresh budget — which
// is exactly what an attacker holding one valid key wants.
st.failures = 0
st.lockedTill = time.Time{}
}
// protectMetrics gates /metrics behind an API key when configured.
@@ -597,10 +621,6 @@ func queryTime(r *http.Request, key string) time.Time {
return time.Time{}
}
func constantTimeEqual(a, b string) bool {
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}
func isFileWritable(path string) bool {
if path == "" {
return false