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:
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -218,20 +219,28 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// reserveLoginAttempt counts one sign-in try for this visitor BEFORE the password is compared and reports whether it
|
||||
// may go ahead (and the count it found). Check and count happen under one lock, so parallel tries cannot all pass a
|
||||
// check that none of them has counted yet. A window that has passed starts clean.
|
||||
// check that none of them has counted yet. A window that has passed starts clean. The escrow wizard's re-auth uses it
|
||||
// too (the same counter: a wrong password is a wrong password, wherever it was typed).
|
||||
func (s *Server) reserveLoginAttempt(ip string) (int, bool) {
|
||||
s.loginAttemptMu.Lock()
|
||||
defer s.loginAttemptMu.Unlock()
|
||||
if s.loginAttempts == nil {
|
||||
s.loginAttempts = map[string]*loginAttempt{}
|
||||
return reserveAttempt(&s.loginAttemptMu, &s.loginAttempts, ip)
|
||||
}
|
||||
|
||||
// reserveAttempt is the one body of the 5-per-minute password limiters: check and count under mu, in one step. Each
|
||||
// limiter keeps its OWN map (the share password's is not the dashboard's, so a guest and the owner never share a
|
||||
// counter); the map is allocated lazily, so struct-literal test servers that skip NewServer still work.
|
||||
func reserveAttempt(mu *sync.Mutex, attempts *map[string]*loginAttempt, key string) (int, bool) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if *attempts == nil {
|
||||
*attempts = map[string]*loginAttempt{}
|
||||
}
|
||||
a := s.loginAttempts[ip]
|
||||
a := (*attempts)[key]
|
||||
if a != nil && time.Since(a.lastFail) > loginWindowDuration {
|
||||
a = nil
|
||||
}
|
||||
if a == nil {
|
||||
a = &loginAttempt{}
|
||||
s.loginAttempts[ip] = a
|
||||
(*attempts)[key] = a
|
||||
}
|
||||
if a.count >= loginMaxAttempts {
|
||||
return a.count, false
|
||||
@@ -241,7 +250,8 @@ func (s *Server) reserveLoginAttempt(ip string) (int, bool) {
|
||||
return a.count, true
|
||||
}
|
||||
|
||||
// comparePassword is bcrypt's compare; passwordCompare replaces it in tests (a slow compare makes a race measurable).
|
||||
// comparePassword is bcrypt's compare for the dashboard sign-in, the escrow re-auth and the share password;
|
||||
// passwordCompare replaces it in tests (a slow compare makes a race measurable).
|
||||
func (s *Server) comparePassword(hash, pw []byte) error {
|
||||
if s.passwordCompare != nil {
|
||||
return s.passwordCompare(hash, pw)
|
||||
|
||||
Reference in New Issue
Block a user