v0.325.0: share + escrow limiters reserve first; family sessions as fingerprints; samba renames apply; path guard widened; gates in a worktree
gates / gates (push) Successful in 45s

R-1111 reserveAttempt is the one limiter body (share password, escrow re-auth, dashboard sign-in). R-1112
family.json holds fingerprints; old files are converted at load. R-1113 the household's pass reuses its
family session. R-1109 the samba compose carries the smb.conf hash. R-1110 TestNoBareDrivePathToPathHelpers.
R-1114 shared_scripts.py resolves the felhom.eu sibling from a worktree. Each red-proved.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
admin committed 2026-10-17 09:36:34 +02:00
1 parent 7d41c2973c
commit 94f44931dd
28 files changed
+945 -190

No files matched your search

+6 -31
View File
@@ -9,7 +9,6 @@ import (
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
"golang.org/x/crypto/bcrypt"
)
// Controller-driven escrow ceremony wizard (v0.127.0, agent ≥ v0.88.0; mechanics validated by
@@ -185,14 +184,15 @@ func (s *Server) escrowStartAPIHandler(w http.ResponseWriter, r *http.Request) {
return
}
ip := rateKey(r)
if s.escrowRateLimited(ip) {
// R-1111: the try is reserved on the login counter BEFORE the slow compare (the R-983 shape); a right password
// clears it below. Pinned by TestR1111_ParallelEscrowReauthsHitTheLimit.
if _, ok := s.reserveLoginAttempt(ip); !ok {
s.logger.Printf("[WARN] [web] escrow start rate limited for %s", ip)
escrowJSON(w, http.StatusTooManyRequests, nil, s.msg(r, "api.escrow.too_many_attempts"))
return
}
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(r.FormValue("password"))); err != nil {
if err := s.comparePassword([]byte(hash), []byte(r.FormValue("password"))); err != nil {
s.logger.Printf("[WARN] [web] escrow start: failed re-auth from %s", r.RemoteAddr)
s.recordEscrowAuthFailure(ip)
escrowJSON(w, http.StatusUnauthorized, nil, "Hibás jelszó.")
return
}
@@ -313,33 +313,8 @@ func (s *Server) escrowClaimAPIHandler(w http.ResponseWriter, r *http.Request) {
_ = code
}
// escrowRateLimited / recordEscrowAuthFailure / clearAuthFailures ride the SAME per-IP counter as
// the login form (loginAttempts, loginMaxAttempts, loginWindowDuration) — a wrong wizard password
// is a wrong password, wherever it was typed.
func (s *Server) escrowRateLimited(ip string) bool {
s.loginAttemptMu.Lock()
defer s.loginAttemptMu.Unlock()
attempt := s.loginAttempts[ip]
if attempt != nil && time.Since(attempt.lastFail) > loginWindowDuration {
delete(s.loginAttempts, ip)
return false
}
return attempt != nil && attempt.count >= loginMaxAttempts
}
func (s *Server) recordEscrowAuthFailure(ip string) {
s.loginAttemptMu.Lock()
defer s.loginAttemptMu.Unlock()
if s.loginAttempts == nil {
s.loginAttempts = map[string]*loginAttempt{}
}
if s.loginAttempts[ip] == nil {
s.loginAttempts[ip] = &loginAttempt{}
}
s.loginAttempts[ip].count++
s.loginAttempts[ip].lastFail = time.Now()
}
// clearAuthFailures clears the login counter after a right re-auth password — the counter reserveLoginAttempt counts
// on, shared with the login form (a wrong wizard password is a wrong password, wherever it was typed).
func (s *Server) clearAuthFailures(ip string) {
s.loginAttemptMu.Lock()
defer s.loginAttemptMu.Unlock()