mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-06 14:33:10 -06:00
* Refactor ApplicationServiceWorkerState to be more robust * Add launch.json to VS Code * Implement login with JWT, registering with email, failed login rate limiting and reset password with m.login.email.identity auth type * Log errors when JWT parsing failed * Development build script * Fix linter errors * Use golangci-lint as a linter in VS Code * Fix tests with RtFailedLogin * Pass config load tests - parse JWT public key only if enabled * Reduce CI steps Do not support 386 arch and go 1.16, 1.17 * Fix linter errors * Change RtFailedLogin logic - nil pointer can be provided * Respect access token in query * Fix typos * Use only one mutex in RtFailedLogin * Remove eventsRemaining across appservice component * Push dendrite to production registry as well * Rafactor TestRtFailedLogin
118 lines
2 KiB
Go
118 lines
2 KiB
Go
package ratelimit
|
|
|
|
import (
|
|
"container/list"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type rateLimit struct {
|
|
cfg *RtFailedLoginConfig
|
|
times *list.List
|
|
}
|
|
|
|
type RtFailedLogin struct {
|
|
cfg *RtFailedLoginConfig
|
|
mtx sync.RWMutex
|
|
rts map[string]*rateLimit
|
|
}
|
|
|
|
type RtFailedLoginConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Limit int `yaml:"burst"`
|
|
Interval time.Duration `yaml:"interval"`
|
|
}
|
|
|
|
// New creates a new rate limiter for the limit and interval.
|
|
func NewRtFailedLogin(cfg *RtFailedLoginConfig) *RtFailedLogin {
|
|
if !cfg.Enabled {
|
|
return nil
|
|
}
|
|
rt := &RtFailedLogin{
|
|
cfg: cfg,
|
|
mtx: sync.RWMutex{},
|
|
rts: make(map[string]*rateLimit),
|
|
}
|
|
go rt.clean()
|
|
return rt
|
|
}
|
|
|
|
// CanAct is expected to be called before Act
|
|
func (r *RtFailedLogin) CanAct(key string) (ok bool, remaining time.Duration) {
|
|
r.mtx.RLock()
|
|
rt, ok := r.rts[key]
|
|
if !ok {
|
|
r.mtx.RUnlock()
|
|
return true, 0
|
|
}
|
|
ok, remaining = rt.canAct()
|
|
r.mtx.RUnlock()
|
|
return
|
|
}
|
|
|
|
// Act can be called after CanAct returns true.
|
|
func (r *RtFailedLogin) Act(key string) {
|
|
r.mtx.Lock()
|
|
rt, ok := r.rts[key]
|
|
if !ok {
|
|
rt = &rateLimit{
|
|
cfg: r.cfg,
|
|
times: list.New(),
|
|
}
|
|
r.rts[key] = rt
|
|
}
|
|
rt.act()
|
|
r.mtx.Unlock()
|
|
}
|
|
|
|
func (r *RtFailedLogin) clean() {
|
|
for {
|
|
r.mtx.Lock()
|
|
for k, v := range r.rts {
|
|
if v.empty() {
|
|
delete(r.rts, k)
|
|
}
|
|
}
|
|
r.mtx.Unlock()
|
|
time.Sleep(time.Hour)
|
|
}
|
|
}
|
|
|
|
func (r *rateLimit) empty() bool {
|
|
back := r.times.Back()
|
|
if back == nil {
|
|
return true
|
|
}
|
|
v := back.Value
|
|
b := v.(time.Time)
|
|
now := time.Now()
|
|
return now.Sub(b) > r.cfg.Interval
|
|
}
|
|
|
|
func (r *rateLimit) canAct() (ok bool, remaining time.Duration) {
|
|
now := time.Now()
|
|
l := r.times.Len()
|
|
if l < r.cfg.Limit {
|
|
return true, 0
|
|
}
|
|
frnt := r.times.Front()
|
|
t := frnt.Value.(time.Time)
|
|
diff := now.Sub(t)
|
|
if diff < r.cfg.Interval {
|
|
return false, r.cfg.Interval - diff
|
|
}
|
|
return true, 0
|
|
}
|
|
|
|
func (r *rateLimit) act() {
|
|
now := time.Now()
|
|
l := r.times.Len()
|
|
if l < r.cfg.Limit {
|
|
r.times.PushBack(now)
|
|
return
|
|
}
|
|
frnt := r.times.Front()
|
|
frnt.Value = now
|
|
r.times.MoveToBack(frnt)
|
|
}
|