mirror of
https://git.vectorsigma.ru/public/AdGuardHome.git
synced 2026-08-03 23:39:30 +00:00
Merge in DNS/adguard-home from AGDNS-2743-auth-mw-usage to master
Squashed commit of the following:
commit 9e3054a42f1b04a00c207810a8dc08696e44c466
Merge: 610c6fc45 1317e296f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Wed Jul 9 17:09:26 2025 +0300
Merge branch 'master' into AGDNS-2743-auth-mw-usage
commit 610c6fc45d2d8848e9b89afb0037b98d83106afa
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Tue Jul 8 19:03:44 2025 +0300
home: imp docs
commit 4633c8991ca77ec182b17a299d9f7e75e145f2ee
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Thu Jul 3 21:01:54 2025 +0300
home: add tests
commit 586b714dafc342670b441fb29e3c7c8fde83ba4c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Thu Jul 3 14:25:28 2025 +0300
home: fix first run
commit 7c2e5d41f0feaae7b50ea0e69cf2767ba17d59df
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Wed Jul 2 23:52:16 2025 +0300
home: imp code
commit b90031495ae3b6fab7361528fb3a3013139a4965
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Wed Jul 2 14:06:42 2025 +0300
home: rm unused
commit 90cb29f2daac7b825b03857414bb04a692736e22
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Wed Jul 2 13:55:45 2025 +0300
home: auth mw usage
145 lines
3.7 KiB
Go
145 lines
3.7 KiB
Go
package home
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// failedAuthTTL is the period of time for which the failed attempt will stay in
|
|
// cache.
|
|
const failedAuthTTL = 1 * time.Minute
|
|
|
|
// loginRaateLimiter is an interface for rate limiting login attempts.
|
|
type loginRaateLimiter interface {
|
|
// check returns the duration of time left until a user is unblocked.
|
|
// A non-positive result indicates that the user is not blocked.
|
|
check(usrID string) (left time.Duration)
|
|
|
|
// inc records a failed login attempt for the specified user.
|
|
inc(usrID string)
|
|
|
|
// remove stops tracking and blocking of the specified user.
|
|
remove(usrID string)
|
|
}
|
|
|
|
// emptyRateLimiter is the [loginRateLimiter] interface implementation that does
|
|
// nothing.
|
|
type emptyRateLimiter struct{}
|
|
|
|
// type check
|
|
var _ emptyRateLimiter = emptyRateLimiter{}
|
|
|
|
// check implements the [loginRateLimiter] interface for emptyRateLimiter. It
|
|
// always returns zero.
|
|
func (rl emptyRateLimiter) check(_ string) (left time.Duration) {
|
|
return 0
|
|
}
|
|
|
|
// inc implements the [loginRateLimiter] interface for emptyRateLimiter.
|
|
func (rl emptyRateLimiter) inc(_ string) {}
|
|
|
|
// remove implements the [loginRateLimiter] interface for emptyRateLimiter.
|
|
func (rl emptyRateLimiter) remove(_ string) {}
|
|
|
|
// failedAuth is an entry of authRateLimiter's cache.
|
|
type failedAuth struct {
|
|
until time.Time
|
|
num uint
|
|
}
|
|
|
|
// authRateLimiter used to cache failed authentication attempts.
|
|
type authRateLimiter struct {
|
|
failedAuths map[string]failedAuth
|
|
// failedAuthsLock protects failedAuths.
|
|
failedAuthsLock sync.Mutex
|
|
blockDur time.Duration
|
|
maxAttempts uint
|
|
}
|
|
|
|
// newAuthRateLimiter returns properly initialized *authRateLimiter.
|
|
func newAuthRateLimiter(blockDur time.Duration, maxAttempts uint) (ab *authRateLimiter) {
|
|
return &authRateLimiter{
|
|
failedAuths: make(map[string]failedAuth),
|
|
blockDur: blockDur,
|
|
maxAttempts: maxAttempts,
|
|
}
|
|
}
|
|
|
|
// type check
|
|
var _ loginRaateLimiter = (*authRateLimiter)(nil)
|
|
|
|
// cleanupLocked checks each blocked users removing ones with expired TTL. For
|
|
// internal use only.
|
|
func (ab *authRateLimiter) cleanupLocked(now time.Time) {
|
|
for k, v := range ab.failedAuths {
|
|
if now.After(v.until) {
|
|
delete(ab.failedAuths, k)
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkLocked checks the attempter for it's state. For internal use only.
|
|
func (ab *authRateLimiter) checkLocked(usrID string, now time.Time) (left time.Duration) {
|
|
a, ok := ab.failedAuths[usrID]
|
|
if !ok {
|
|
return 0
|
|
}
|
|
|
|
if a.num < ab.maxAttempts {
|
|
return 0
|
|
}
|
|
|
|
return a.until.Sub(now)
|
|
}
|
|
|
|
// check implements the [loginRateLimiter] interface for *authRateLimiter.
|
|
func (ab *authRateLimiter) check(usrID string) (left time.Duration) {
|
|
now := time.Now()
|
|
|
|
ab.failedAuthsLock.Lock()
|
|
defer ab.failedAuthsLock.Unlock()
|
|
|
|
ab.cleanupLocked(now)
|
|
|
|
return ab.checkLocked(usrID, now)
|
|
}
|
|
|
|
// incLocked increments the number of unsuccessful attempts for attempter with
|
|
// usrID and updates it's blocking moment if needed. For internal use only.
|
|
func (ab *authRateLimiter) incLocked(usrID string, now time.Time) {
|
|
until := now.Add(failedAuthTTL)
|
|
var attNum uint = 1
|
|
|
|
a, ok := ab.failedAuths[usrID]
|
|
if ok {
|
|
until = a.until
|
|
attNum = a.num + 1
|
|
}
|
|
if attNum >= ab.maxAttempts {
|
|
until = now.Add(ab.blockDur)
|
|
}
|
|
|
|
ab.failedAuths[usrID] = failedAuth{
|
|
num: attNum,
|
|
until: until,
|
|
}
|
|
}
|
|
|
|
// inc implements the [loginRateLimiter] interface for *authRateLimiter.
|
|
func (ab *authRateLimiter) inc(usrID string) {
|
|
now := time.Now()
|
|
|
|
ab.failedAuthsLock.Lock()
|
|
defer ab.failedAuthsLock.Unlock()
|
|
|
|
ab.incLocked(usrID, now)
|
|
}
|
|
|
|
// remove implements the [loginRateLimiter] interface for *authRateLimiter.
|
|
func (ab *authRateLimiter) remove(usrID string) {
|
|
ab.failedAuthsLock.Lock()
|
|
defer ab.failedAuthsLock.Unlock()
|
|
|
|
delete(ab.failedAuths, usrID)
|
|
}
|