Merge commit from fork

* fix: respect lockout policy on password change (with old password)

* add tarpitting

* cleanup
This commit is contained in:
Livio Spring
2025-10-29 10:07:35 +01:00
committed by GitHub
parent 72a5c33e6a
commit b8db8cdf9c
17 changed files with 619 additions and 49 deletions
@@ -2,7 +2,7 @@ package systemdefaults
import (
"time"
"github.com/zitadel/zitadel/internal/crypto"
)
@@ -11,6 +11,7 @@ type SystemDefaults struct {
PasswordHasher crypto.HashConfig
SecretHasher crypto.HashConfig
Multifactors MultifactorConfig
Tarpit TarpitConfig
DomainVerification DomainVerification
Notifications Notifications
KeyConfig KeyConfig
+34
View File
@@ -0,0 +1,34 @@
package systemdefaults
import "time"
type TarpitConfig struct {
// After how many failed attempts, the tarpit should start.
MinFailedAttempts uint64
// The seconds that will be added per step.
StepDuration time.Duration
// The failed attempts that are needed to increase the tarpit by one step.
StepSize uint64
// The maximum duration the tarpit can reach.
MaxDuration time.Duration
}
func (t *TarpitConfig) Tarpit() func(failedCount uint64) {
return func(failedCount uint64) {
time.Sleep(t.duration(failedCount))
}
}
func (t *TarpitConfig) duration(failedCount uint64) time.Duration {
if failedCount < t.MinFailedAttempts {
return 0
}
// calculate the step we are at
// every StepSize failed attempts increase the step by one
step := (failedCount - t.MinFailedAttempts) / t.StepSize
duration := time.Duration(step) * t.StepDuration
if duration < t.MaxDuration {
return duration
}
return t.MaxDuration
}
@@ -0,0 +1,83 @@
package systemdefaults
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTarpitConfig_duration(t *testing.T) {
type fields struct {
MinFailedAttempts uint64
StepDuration time.Duration
StepSize uint64
MaxDuration time.Duration
}
type args struct {
failedCount uint64
}
tests := []struct {
name string
fields fields
args args
want time.Duration
}{
{
"no tarpit",
fields{
MinFailedAttempts: 2,
StepDuration: time.Second,
StepSize: 1,
MaxDuration: 5 * time.Second,
},
args{failedCount: 1},
0,
},
{
"first step",
fields{
MinFailedAttempts: 2,
StepDuration: time.Second,
StepSize: 1,
MaxDuration: 5 * time.Second,
},
args{failedCount: 3},
time.Second,
},
{
"second step",
fields{
MinFailedAttempts: 2,
StepDuration: time.Second,
StepSize: 1,
MaxDuration: 5 * time.Second,
},
args{failedCount: 4},
2 * time.Second,
},
{
"exceeding max duration",
fields{
MinFailedAttempts: 2,
StepDuration: time.Second,
StepSize: 1,
MaxDuration: 5 * time.Second,
},
args{failedCount: 20},
5 * time.Second,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &TarpitConfig{
MinFailedAttempts: tt.fields.MinFailedAttempts,
StepDuration: tt.fields.StepDuration,
StepSize: tt.fields.StepSize,
MaxDuration: tt.fields.MaxDuration,
}
got := c.duration(tt.args.failedCount)
assert.Equal(t, tt.want, got)
})
}
}