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
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:
1 parent
7d41c2973c
commit
94f44931dd
28 files changed
+945
-190
No files matched your search
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user