mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-23 10:17:41 +00:00
Merge commit from fork
* fix: respect lockout policy on password change (with old password) * add tarpitting * cleanup
This commit is contained in:
@@ -740,6 +740,15 @@ SystemDefaults:
|
|||||||
# If this is empty, the issuer is the requested domain
|
# If this is empty, the issuer is the requested domain
|
||||||
# This is helpful in scenarios with multiple ZITADEL environments or virtual instances
|
# This is helpful in scenarios with multiple ZITADEL environments or virtual instances
|
||||||
Issuer: "ZITADEL" # ZITADEL_SYSTEMDEFAULTS_MULTIFACTORS_OTP_ISSUER
|
Issuer: "ZITADEL" # ZITADEL_SYSTEMDEFAULTS_MULTIFACTORS_OTP_ISSUER
|
||||||
|
Tarpit:
|
||||||
|
# The amount of failed attempts, the tarpit should start.
|
||||||
|
MinFailedAttempts: 5 # ZITADEL_SYSTEMDEFAULTS_TARPIT_MINFAILEDATTEMPTS
|
||||||
|
# The seconds that will be added per step.
|
||||||
|
StepDuration: 1s # ZITADEL_SYSTEMDEFAULTS_TARPIT_STEPDURATION
|
||||||
|
# The failed attempts that are needed to increase the tarpit by one step.
|
||||||
|
StepSize: 5 # ZITADEL_SYSTEMDEFAULTS_TARPIT_STEPSIZE
|
||||||
|
# The maximum duration the tarpit can reach.
|
||||||
|
MaxDuration: 10s # ZITADEL_SYSTEMDEFAULTS_TARPIT_MAXDURATION
|
||||||
DomainVerification:
|
DomainVerification:
|
||||||
VerificationGenerator:
|
VerificationGenerator:
|
||||||
Length: 32 # ZITADEL_SYSTEMDEFAULTS_DOMAINVERIFICATION_VERIFICATIONGENERATOR_LENGTH
|
Length: 32 # ZITADEL_SYSTEMDEFAULTS_DOMAINVERIFICATION_VERIFICATIONGENERATOR_LENGTH
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ type Commands struct {
|
|||||||
defaultRefreshTokenLifetime time.Duration
|
defaultRefreshTokenLifetime time.Duration
|
||||||
defaultRefreshTokenIdleLifetime time.Duration
|
defaultRefreshTokenIdleLifetime time.Duration
|
||||||
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
|
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
|
||||||
|
tarpit func(failedAttempts uint64)
|
||||||
|
|
||||||
multifactors domain.MultifactorConfigs
|
multifactors domain.MultifactorConfigs
|
||||||
webauthnConfig *webauthn_helper.Config
|
webauthnConfig *webauthn_helper.Config
|
||||||
@@ -214,6 +215,7 @@ func StartCommands(
|
|||||||
repo.newHashedSecret = newHashedSecretWithDefault(secretHasher, defaultSecretGenerators.ClientSecret)
|
repo.newHashedSecret = newHashedSecretWithDefault(secretHasher, defaultSecretGenerators.ClientSecret)
|
||||||
}
|
}
|
||||||
repo.phoneCodeVerifier = repo.phoneCodeVerifierFromConfig
|
repo.phoneCodeVerifier = repo.phoneCodeVerifierFromConfig
|
||||||
|
repo.tarpit = defaults.Tarpit.Tarpit()
|
||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ type SessionCommands struct {
|
|||||||
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
|
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
|
||||||
now func() time.Time
|
now func() time.Time
|
||||||
maxIdPIntentLifetime time.Duration
|
maxIdPIntentLifetime time.Duration
|
||||||
|
tarpit func(failedAttempts uint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWriteModel) *SessionCommands {
|
func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWriteModel) *SessionCommands {
|
||||||
@@ -60,6 +61,7 @@ func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWri
|
|||||||
getCodeVerifier: c.phoneCodeVerifierFromConfig,
|
getCodeVerifier: c.phoneCodeVerifierFromConfig,
|
||||||
now: time.Now,
|
now: time.Now,
|
||||||
maxIdPIntentLifetime: c.maxIdPIntentLifetime,
|
maxIdPIntentLifetime: c.maxIdPIntentLifetime,
|
||||||
|
tarpit: c.tarpit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ func CheckUser(id string, resourceOwner string, preferredLanguage *language.Tag)
|
|||||||
// CheckPassword defines a password check to be executed for a session update
|
// CheckPassword defines a password check to be executed for a session update
|
||||||
func CheckPassword(password string) SessionCommand {
|
func CheckPassword(password string) SessionCommand {
|
||||||
return func(ctx context.Context, cmd *SessionCommands) ([]eventstore.Command, error) {
|
return func(ctx context.Context, cmd *SessionCommands) ([]eventstore.Command, error) {
|
||||||
commands, err := checkPassword(ctx, cmd.sessionWriteModel.UserID, password, cmd.eventstore, cmd.hasher, nil)
|
commands, err := checkPassword(ctx, cmd.sessionWriteModel.UserID, password, cmd.eventstore, cmd.hasher, nil, cmd.tarpit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return commands, err
|
return commands, err
|
||||||
}
|
}
|
||||||
@@ -135,6 +137,7 @@ func CheckTOTP(code string) SessionCommand {
|
|||||||
cmd.eventstore.FilterToQueryReducer,
|
cmd.eventstore.FilterToQueryReducer,
|
||||||
cmd.totpAlg,
|
cmd.totpAlg,
|
||||||
nil,
|
nil,
|
||||||
|
cmd.tarpit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return commands, err
|
return commands, err
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ func CheckOTPSMS(code string) SessionCommand {
|
|||||||
cmd.getCodeVerifier,
|
cmd.getCodeVerifier,
|
||||||
succeededEvent,
|
succeededEvent,
|
||||||
failedEvent,
|
failedEvent,
|
||||||
|
cmd.tarpit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return commands, err
|
return commands, err
|
||||||
@@ -183,6 +184,7 @@ func CheckOTPEmail(code string) SessionCommand {
|
|||||||
nil, // email currently always uses local code checks
|
nil, // email currently always uses local code checks
|
||||||
succeededEvent,
|
succeededEvent,
|
||||||
failedEvent,
|
failedEvent,
|
||||||
|
cmd.tarpit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return commands, err
|
return commands, err
|
||||||
|
|||||||
@@ -1037,6 +1037,9 @@ func TestCheckOTPSMS(t *testing.T) {
|
|||||||
now: func() time.Time {
|
now: func() time.Time {
|
||||||
return testNow
|
return testNow
|
||||||
},
|
},
|
||||||
|
tarpit: func(failedAttempts uint64) {
|
||||||
|
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
gotCmds, err := cmd(context.Background(), cmds)
|
gotCmds, err := cmd(context.Background(), cmds)
|
||||||
@@ -1053,6 +1056,7 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
userID string
|
userID string
|
||||||
otpCodeChallenge *OTPCode
|
otpCodeChallenge *OTPCode
|
||||||
otpAlg crypto.EncryptionAlgorithm
|
otpAlg crypto.EncryptionAlgorithm
|
||||||
|
tarpit Tarpit
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
code string
|
code string
|
||||||
@@ -1073,6 +1077,7 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
userID: "",
|
userID: "",
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
code: "code",
|
code: "code",
|
||||||
@@ -1086,6 +1091,7 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
userID: "userID",
|
userID: "userID",
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{},
|
args: args{},
|
||||||
res: res{
|
res: res{
|
||||||
@@ -1099,6 +1105,7 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
userID: "userID",
|
userID: "userID",
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
code: "code",
|
code: "code",
|
||||||
@@ -1117,6 +1124,7 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
),
|
),
|
||||||
userID: "userID",
|
userID: "userID",
|
||||||
otpCodeChallenge: nil,
|
otpCodeChallenge: nil,
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
code: "code",
|
code: "code",
|
||||||
@@ -1153,6 +1161,7 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
CreationDate: testNow.Add(-10 * time.Minute),
|
CreationDate: testNow.Add(-10 * time.Minute),
|
||||||
},
|
},
|
||||||
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
code: "code",
|
code: "code",
|
||||||
@@ -1192,6 +1201,7 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
CreationDate: testNow.Add(-10 * time.Minute),
|
CreationDate: testNow.Add(-10 * time.Minute),
|
||||||
},
|
},
|
||||||
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
code: "code",
|
code: "code",
|
||||||
@@ -1225,6 +1235,7 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
CreationDate: testNow,
|
CreationDate: testNow,
|
||||||
},
|
},
|
||||||
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
code: "code",
|
code: "code",
|
||||||
@@ -1261,6 +1272,7 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
CreationDate: testNow,
|
CreationDate: testNow,
|
||||||
},
|
},
|
||||||
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
code: "code",
|
code: "code",
|
||||||
@@ -1289,12 +1301,39 @@ func TestCheckOTPEmail(t *testing.T) {
|
|||||||
now: func() time.Time {
|
now: func() time.Time {
|
||||||
return testNow
|
return testNow
|
||||||
},
|
},
|
||||||
|
tarpit: tt.fields.tarpit.tarpit,
|
||||||
}
|
}
|
||||||
|
|
||||||
gotCmds, err := cmd(context.Background(), cmds)
|
gotCmds, err := cmd(context.Background(), cmds)
|
||||||
assert.ErrorIs(t, err, tt.res.err)
|
assert.ErrorIs(t, err, tt.res.err)
|
||||||
assert.Equal(t, tt.res.errorCommands, gotCmds)
|
assert.Equal(t, tt.res.errorCommands, gotCmds)
|
||||||
assert.Equal(t, tt.res.commands, cmds.eventCommands)
|
assert.Equal(t, tt.res.commands, cmds.eventCommands)
|
||||||
|
tt.fields.tarpit.metExpectedCalls(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expectTarpit(requiredFailedAttempts uint64) Tarpit {
|
||||||
|
return &mockTarpit{
|
||||||
|
requiredFailedAttempts: requiredFailedAttempts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockTarpit struct {
|
||||||
|
requiredFailedAttempts uint64
|
||||||
|
failedAttempts uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockTarpit) tarpit(failedAttempts uint64) {
|
||||||
|
m.failedAttempts = failedAttempts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockTarpit) metExpectedCalls(t *testing.T) bool {
|
||||||
|
t.Helper()
|
||||||
|
return assert.Equalf(t, m.requiredFailedAttempts, m.failedAttempts, "tarpit was called with %d failed attempts, but %d were expected", m.failedAttempts, m.requiredFailedAttempts)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tarpit interface {
|
||||||
|
tarpit(failedAttempts uint64)
|
||||||
|
metExpectedCalls(t *testing.T) bool
|
||||||
|
}
|
||||||
|
|||||||
@@ -1091,6 +1091,7 @@ func TestCheckTOTP(t *testing.T) {
|
|||||||
type fields struct {
|
type fields struct {
|
||||||
sessionWriteModel *SessionWriteModel
|
sessionWriteModel *SessionWriteModel
|
||||||
eventstore func(*testing.T) *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
|
tarpit Tarpit
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -1109,6 +1110,7 @@ func TestCheckTOTP(t *testing.T) {
|
|||||||
aggregate: sessAgg,
|
aggregate: sessAgg,
|
||||||
},
|
},
|
||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing"),
|
wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing"),
|
||||||
},
|
},
|
||||||
@@ -1124,6 +1126,7 @@ func TestCheckTOTP(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilterError(io.ErrClosedPipe),
|
expectFilterError(io.ErrClosedPipe),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
wantErr: io.ErrClosedPipe,
|
wantErr: io.ErrClosedPipe,
|
||||||
},
|
},
|
||||||
@@ -1143,6 +1146,7 @@ func TestCheckTOTP(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady"),
|
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady"),
|
||||||
},
|
},
|
||||||
@@ -1169,6 +1173,7 @@ func TestCheckTOTP(t *testing.T) {
|
|||||||
eventFromEventPusher(org.NewLockoutPolicyAddedEvent(ctx, orgAgg, 0, 0, false)),
|
eventFromEventPusher(org.NewLockoutPolicyAddedEvent(ctx, orgAgg, 0, 0, false)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
wantErrorCommands: []eventstore.Command{
|
wantErrorCommands: []eventstore.Command{
|
||||||
user.NewHumanOTPCheckFailedEvent(ctx, userAgg, nil),
|
user.NewHumanOTPCheckFailedEvent(ctx, userAgg, nil),
|
||||||
@@ -1198,6 +1203,7 @@ func TestCheckTOTP(t *testing.T) {
|
|||||||
eventFromEventPusher(org.NewLockoutPolicyAddedEvent(ctx, orgAgg, 1, 1, false)),
|
eventFromEventPusher(org.NewLockoutPolicyAddedEvent(ctx, orgAgg, 1, 1, false)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
wantErrorCommands: []eventstore.Command{
|
wantErrorCommands: []eventstore.Command{
|
||||||
user.NewHumanOTPCheckFailedEvent(ctx, userAgg, nil),
|
user.NewHumanOTPCheckFailedEvent(ctx, userAgg, nil),
|
||||||
@@ -1225,6 +1231,7 @@ func TestCheckTOTP(t *testing.T) {
|
|||||||
),
|
),
|
||||||
expectFilter(), // recheck
|
expectFilter(), // recheck
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
wantEventCommands: []eventstore.Command{
|
wantEventCommands: []eventstore.Command{
|
||||||
user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, nil),
|
user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, nil),
|
||||||
@@ -1253,6 +1260,7 @@ func TestCheckTOTP(t *testing.T) {
|
|||||||
user.NewUserLockedEvent(ctx, userAgg),
|
user.NewUserLockedEvent(ctx, userAgg),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3fg", "Errors.User.Locked"),
|
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3fg", "Errors.User.Locked"),
|
||||||
},
|
},
|
||||||
@@ -1264,11 +1272,13 @@ func TestCheckTOTP(t *testing.T) {
|
|||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
totpAlg: cryptoAlg,
|
totpAlg: cryptoAlg,
|
||||||
now: func() time.Time { return testNow },
|
now: func() time.Time { return testNow },
|
||||||
|
tarpit: tt.fields.tarpit.tarpit,
|
||||||
}
|
}
|
||||||
gotCmds, err := CheckTOTP(tt.code)(ctx, cmd)
|
gotCmds, err := CheckTOTP(tt.code)(ctx, cmd)
|
||||||
require.ErrorIs(t, err, tt.wantErr)
|
require.ErrorIs(t, err, tt.wantErr)
|
||||||
assert.Equal(t, tt.wantErrorCommands, gotCmds)
|
assert.Equal(t, tt.wantErrorCommands, gotCmds)
|
||||||
assert.Equal(t, tt.wantEventCommands, cmd.eventCommands)
|
assert.Equal(t, tt.wantEventCommands, cmd.eventCommands)
|
||||||
|
tt.fields.tarpit.metExpectedCalls(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resource
|
|||||||
c.eventstore.FilterToQueryReducer,
|
c.eventstore.FilterToQueryReducer,
|
||||||
c.multifactors.OTP.CryptoMFA,
|
c.multifactors.OTP.CryptoMFA,
|
||||||
authRequestDomainToAuthRequestInfo(authRequest),
|
authRequestDomainToAuthRequestInfo(authRequest),
|
||||||
|
c.tarpit,
|
||||||
)
|
)
|
||||||
|
|
||||||
_, pushErr := c.eventstore.Push(ctx, commands...)
|
_, pushErr := c.eventstore.Push(ctx, commands...)
|
||||||
@@ -183,6 +184,7 @@ func checkTOTP(
|
|||||||
queryReducer func(ctx context.Context, r eventstore.QueryReducer) error,
|
queryReducer func(ctx context.Context, r eventstore.QueryReducer) error,
|
||||||
alg crypto.EncryptionAlgorithm,
|
alg crypto.EncryptionAlgorithm,
|
||||||
optionalAuthRequestInfo *user.AuthRequestInfo,
|
optionalAuthRequestInfo *user.AuthRequestInfo,
|
||||||
|
tarpit func(failedAttempts uint64),
|
||||||
) ([]eventstore.Command, error) {
|
) ([]eventstore.Command, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
||||||
@@ -222,6 +224,7 @@ func checkTOTP(
|
|||||||
if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount+1 >= lockoutPolicy.MaxOTPAttempts {
|
if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount+1 >= lockoutPolicy.MaxOTPAttempts {
|
||||||
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
||||||
}
|
}
|
||||||
|
tarpit(existingOTP.CheckFailedCount + 1)
|
||||||
return commands, verifyErr
|
return commands, verifyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,6 +377,7 @@ func (c *Commands) HumanCheckOTPSMS(ctx context.Context, userID, code, resourceO
|
|||||||
c.phoneCodeVerifier,
|
c.phoneCodeVerifier,
|
||||||
succeededEvent,
|
succeededEvent,
|
||||||
failedEvent,
|
failedEvent,
|
||||||
|
c.tarpit,
|
||||||
)
|
)
|
||||||
if len(commands) > 0 {
|
if len(commands) > 0 {
|
||||||
_, pushErr := c.eventstore.Push(ctx, commands...)
|
_, pushErr := c.eventstore.Push(ctx, commands...)
|
||||||
@@ -508,6 +512,7 @@ func (c *Commands) HumanCheckOTPEmail(ctx context.Context, userID, code, resourc
|
|||||||
nil, // email currently always uses local code checks
|
nil, // email currently always uses local code checks
|
||||||
succeededEvent,
|
succeededEvent,
|
||||||
failedEvent,
|
failedEvent,
|
||||||
|
c.tarpit,
|
||||||
)
|
)
|
||||||
if len(commands) > 0 {
|
if len(commands) > 0 {
|
||||||
_, pushErr := c.eventstore.Push(ctx, commands...)
|
_, pushErr := c.eventstore.Push(ctx, commands...)
|
||||||
@@ -576,6 +581,7 @@ func checkOTP(
|
|||||||
alg crypto.EncryptionAlgorithm,
|
alg crypto.EncryptionAlgorithm,
|
||||||
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error),
|
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error),
|
||||||
checkSucceededEvent, checkFailedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
|
checkSucceededEvent, checkFailedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
|
||||||
|
tarpit func(failedAttempts uint64),
|
||||||
) ([]eventstore.Command, error) {
|
) ([]eventstore.Command, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-S453v", "Errors.User.UserIDMissing")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-S453v", "Errors.User.UserIDMissing")
|
||||||
@@ -627,6 +633,7 @@ func checkOTP(
|
|||||||
if lockoutPolicy != nil && lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount()+1 >= lockoutPolicy.MaxOTPAttempts {
|
if lockoutPolicy != nil && lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount()+1 >= lockoutPolicy.MaxOTPAttempts {
|
||||||
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
||||||
}
|
}
|
||||||
|
tarpit(existingOTP.CheckFailedCount() + 1)
|
||||||
return commands, verifyErr
|
return commands, verifyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1940,6 +1940,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
eventstore func(*testing.T) *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
userEncryption crypto.EncryptionAlgorithm
|
userEncryption crypto.EncryptionAlgorithm
|
||||||
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
|
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
|
||||||
|
tarpit Tarpit
|
||||||
}
|
}
|
||||||
type (
|
type (
|
||||||
args struct {
|
args struct {
|
||||||
@@ -1964,6 +1965,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
name: "userid missing, invalid argument error",
|
name: "userid missing, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -1979,6 +1981,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
name: "code missing, invalid argument error",
|
name: "code missing, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -1996,6 +1999,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -2019,6 +2023,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -2088,6 +2093,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -2169,6 +2175,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -2239,6 +2246,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -2301,6 +2309,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -2380,6 +2389,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
sender.EXPECT().VerifyCode("verificationID", "code").Return(nil)
|
sender.EXPECT().VerifyCode("verificationID", "code").Return(nil)
|
||||||
return sender, nil
|
return sender, nil
|
||||||
},
|
},
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -2409,9 +2419,11 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
userEncryption: tt.fields.userEncryption,
|
userEncryption: tt.fields.userEncryption,
|
||||||
phoneCodeVerifier: tt.fields.phoneCodeVerifier,
|
phoneCodeVerifier: tt.fields.phoneCodeVerifier,
|
||||||
|
tarpit: tt.fields.tarpit.tarpit,
|
||||||
}
|
}
|
||||||
err := r.HumanCheckOTPSMS(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.authRequest)
|
err := r.HumanCheckOTPSMS(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.authRequest)
|
||||||
assert.ErrorIs(t, err, tt.res.err)
|
assert.ErrorIs(t, err, tt.res.err)
|
||||||
|
tt.fields.tarpit.metExpectedCalls(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3115,6 +3127,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(*testing.T) *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
userEncryption crypto.EncryptionAlgorithm
|
userEncryption crypto.EncryptionAlgorithm
|
||||||
|
tarpit Tarpit
|
||||||
}
|
}
|
||||||
type (
|
type (
|
||||||
args struct {
|
args struct {
|
||||||
@@ -3139,6 +3152,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
name: "userid missing, invalid argument error",
|
name: "userid missing, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -3154,6 +3168,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
name: "code missing, invalid argument error",
|
name: "code missing, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -3171,6 +3186,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -3194,6 +3210,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -3262,6 +3279,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -3342,6 +3360,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -3411,6 +3430,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -3472,6 +3492,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -3498,9 +3519,11 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
userEncryption: tt.fields.userEncryption,
|
userEncryption: tt.fields.userEncryption,
|
||||||
|
tarpit: tt.fields.tarpit.tarpit,
|
||||||
}
|
}
|
||||||
err := r.HumanCheckOTPEmail(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.authRequest)
|
err := r.HumanCheckOTPEmail(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.authRequest)
|
||||||
assert.ErrorIs(t, err, tt.res.err)
|
assert.ErrorIs(t, err, tt.res.err)
|
||||||
|
tt.fields.tarpit.metExpectedCalls(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPasswor
|
|||||||
"",
|
"",
|
||||||
userAgentID,
|
userAgentID,
|
||||||
changeRequired,
|
changeRequired,
|
||||||
c.checkCurrentPassword(newPassword, "", oldPassword, wm.EncodedHash),
|
c.checkCurrentPassword(newPassword, "", oldPassword, wm, c.tarpit),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,23 +140,49 @@ func (c *Commands) setPasswordWithVerifyCode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HumanPasswordCheckWriteModel interface {
|
||||||
|
GetUserState() domain.UserState
|
||||||
|
GetPasswordCheckFailedCount() uint64
|
||||||
|
GetEncodedHash() string
|
||||||
|
GetResourceOwner() string
|
||||||
|
GetWriteModel() *eventstore.WriteModel
|
||||||
|
eventstore.QueryReducer
|
||||||
|
}
|
||||||
|
|
||||||
// checkCurrentPassword returns a password check as [setPasswordVerification] implementation
|
// checkCurrentPassword returns a password check as [setPasswordVerification] implementation
|
||||||
func (c *Commands) checkCurrentPassword(
|
func (c *Commands) checkCurrentPassword(
|
||||||
newPassword, newEncodedPassword, currentPassword, currentEncodePassword string,
|
newPassword, newEncodedPassword, currentPassword string,
|
||||||
|
wm HumanPasswordCheckWriteModel,
|
||||||
|
tarpit func(failedAttempts uint64),
|
||||||
) setPasswordVerification {
|
) setPasswordVerification {
|
||||||
|
return func(ctx context.Context) (_ string, err error) {
|
||||||
|
verify := func(hash, password string) (string, error) {
|
||||||
// in case the new password is already encoded, we only need to verify the current
|
// in case the new password is already encoded, we only need to verify the current
|
||||||
if newEncodedPassword != "" {
|
if newEncodedPassword != "" {
|
||||||
return func(ctx context.Context) (_ string, err error) {
|
|
||||||
_, spanPasswap := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
_, spanPasswap := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
||||||
_, err = c.userPasswordHasher.Verify(currentEncodePassword, currentPassword)
|
_, err = c.userPasswordHasher.Verify(hash, password)
|
||||||
spanPasswap.EndWithError(err)
|
spanPasswap.EndWithError(err)
|
||||||
return "", convertPasswapErr(err)
|
return "", convertPasswapErr(err)
|
||||||
}
|
}
|
||||||
|
// otherwise, let's directly verify and return the new generated hash, so we can reuse it in the event
|
||||||
|
return c.verifyAndUpdatePassword(ctx, hash, password, newPassword)
|
||||||
}
|
}
|
||||||
|
commands, updated, err := verifyPasswordWithLockoutPolicy(ctx, wm, currentPassword, c.eventstore, verify, nil, tarpit)
|
||||||
// otherwise let's directly verify and return the new generate hash, so we can reuse it in the event
|
// The verification was successful, and we might have an updated hash.
|
||||||
return func(ctx context.Context) (string, error) {
|
if err == nil {
|
||||||
return c.verifyAndUpdatePassword(ctx, currentEncodePassword, currentPassword, newPassword)
|
return updated, nil
|
||||||
|
}
|
||||||
|
// If we get here, the verification failed, either due to a precondition (e.g. user not found or locked)
|
||||||
|
// or due to a wrong password.
|
||||||
|
// If the former, we just return the error.
|
||||||
|
if len(commands) == 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// If the latter, there's at least a failed password check event to push or additionally a lock event.
|
||||||
|
// We push these events, but return the original error (which might contain details about the failed attempts).
|
||||||
|
_, pushErr := c.eventstore.Push(ctx, commands...)
|
||||||
|
logging.OnError(pushErr).Error("error create password check failed event")
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +370,11 @@ func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, passwo
|
|||||||
if !loginPolicy.AllowUsernamePassword {
|
if !loginPolicy.AllowUsernamePassword {
|
||||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-Dft32", "Errors.Org.LoginPolicy.UsernamePasswordNotAllowed")
|
return zerrors.ThrowPreconditionFailed(err, "COMMAND-Dft32", "Errors.Org.LoginPolicy.UsernamePasswordNotAllowed")
|
||||||
}
|
}
|
||||||
commands, err := checkPassword(ctx, userID, password, c.eventstore, c.userPasswordHasher, authRequestDomainToAuthRequestInfo(authRequest))
|
var tarpit func(failedAttempts uint64)
|
||||||
|
if !loginPolicy.IgnoreUnknownUsernames {
|
||||||
|
tarpit = c.tarpit
|
||||||
|
}
|
||||||
|
commands, err := checkPassword(ctx, userID, password, c.eventstore, c.userPasswordHasher, authRequestDomainToAuthRequestInfo(authRequest), tarpit)
|
||||||
if len(commands) == 0 {
|
if len(commands) == 0 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -353,7 +383,7 @@ func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, passwo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPassword(ctx context.Context, userID, password string, es *eventstore.Eventstore, hasher *crypto.Hasher, optionalAuthRequestInfo *user.AuthRequestInfo) ([]eventstore.Command, error) {
|
func checkPassword(ctx context.Context, userID, password string, es *eventstore.Eventstore, hasher *crypto.Hasher, optionalAuthRequestInfo *user.AuthRequestInfo, tarpit func(failedAttempts uint64)) ([]eventstore.Command, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sfw3f", "Errors.User.UserIDMissing")
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sfw3f", "Errors.User.UserIDMissing")
|
||||||
}
|
}
|
||||||
@@ -362,36 +392,49 @@ func checkPassword(ctx context.Context, userID, password string, es *eventstore.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !wm.UserState.Exists() {
|
commands, _, err := verifyPasswordWithLockoutPolicy(ctx, wm, password, es, hasher.Verify, optionalAuthRequestInfo, tarpit)
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.NotFound")
|
return commands, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyPasswordWithLockoutPolicy(
|
||||||
|
ctx context.Context,
|
||||||
|
wm HumanPasswordCheckWriteModel,
|
||||||
|
password string,
|
||||||
|
es *eventstore.Eventstore,
|
||||||
|
verify func(hash string, password string) (newHash string, err error),
|
||||||
|
optionalAuthRequestInfo *user.AuthRequestInfo,
|
||||||
|
tarpit func(failedAttempts uint64),
|
||||||
|
) ([]eventstore.Command, string, error) {
|
||||||
|
if !wm.GetUserState().Exists() {
|
||||||
|
return nil, "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.NotFound")
|
||||||
}
|
}
|
||||||
if wm.UserState == domain.UserStateLocked {
|
if wm.GetUserState() == domain.UserStateLocked {
|
||||||
wrongPasswordError := &commandErrors.WrongPasswordError{
|
wrongPasswordError := &commandErrors.WrongPasswordError{
|
||||||
FailedAttempts: int32(wm.PasswordCheckFailedCount),
|
FailedAttempts: int32(wm.GetPasswordCheckFailedCount()),
|
||||||
}
|
}
|
||||||
return nil, zerrors.ThrowPreconditionFailed(wrongPasswordError, "COMMAND-JLK35", "Errors.User.Locked")
|
return nil, "", zerrors.ThrowPreconditionFailed(wrongPasswordError, "COMMAND-JLK35", "Errors.User.Locked")
|
||||||
}
|
}
|
||||||
if wm.EncodedHash == "" {
|
if wm.GetEncodedHash() == "" {
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-3nJ4t", "Errors.User.Password.NotSet")
|
return nil, "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-3nJ4t", "Errors.User.Password.NotSet")
|
||||||
}
|
}
|
||||||
|
|
||||||
userAgg := UserAggregateFromWriteModel(&wm.WriteModel)
|
userAgg := UserAggregateFromWriteModel(wm.GetWriteModel())
|
||||||
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
||||||
updated, err := hasher.Verify(wm.EncodedHash, password)
|
updated, err := verify(wm.GetEncodedHash(), password)
|
||||||
spanPasswordComparison.EndWithError(err)
|
spanPasswordComparison.EndWithError(err)
|
||||||
err = convertLoginPasswapErr(wm.PasswordCheckFailedCount+1, err)
|
err = convertLoginPasswapErr(wm.GetPasswordCheckFailedCount()+1, err)
|
||||||
commands := make([]eventstore.Command, 0, 2)
|
commands := make([]eventstore.Command, 0, 2)
|
||||||
|
|
||||||
// recheck for additional events (failed password checks or locks)
|
// recheck for additional events (failed password checks or locks)
|
||||||
recheckErr := es.FilterToQueryReducer(ctx, wm)
|
recheckErr := es.FilterToQueryReducer(ctx, wm)
|
||||||
if recheckErr != nil {
|
if recheckErr != nil {
|
||||||
return nil, recheckErr
|
return nil, "", recheckErr
|
||||||
}
|
}
|
||||||
if wm.UserState == domain.UserStateLocked {
|
if wm.GetUserState() == domain.UserStateLocked {
|
||||||
wrongPasswordError := &commandErrors.WrongPasswordError{
|
wrongPasswordError := &commandErrors.WrongPasswordError{
|
||||||
FailedAttempts: int32(wm.PasswordCheckFailedCount),
|
FailedAttempts: int32(wm.GetPasswordCheckFailedCount()),
|
||||||
}
|
}
|
||||||
return nil, zerrors.ThrowPreconditionFailed(wrongPasswordError, "COMMAND-SFA3t", "Errors.User.Locked")
|
return nil, "", zerrors.ThrowPreconditionFailed(wrongPasswordError, "COMMAND-SFA3t", "Errors.User.Locked")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -399,17 +442,23 @@ func checkPassword(ctx context.Context, userID, password string, es *eventstore.
|
|||||||
if updated != "" {
|
if updated != "" {
|
||||||
commands = append(commands, user.NewHumanPasswordHashUpdatedEvent(ctx, userAgg, updated))
|
commands = append(commands, user.NewHumanPasswordHashUpdatedEvent(ctx, userAgg, updated))
|
||||||
}
|
}
|
||||||
return commands, nil
|
return commands, updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
commands = append(commands, user.NewHumanPasswordCheckFailedEvent(ctx, userAgg, optionalAuthRequestInfo))
|
commands = append(commands, user.NewHumanPasswordCheckFailedEvent(ctx, userAgg, optionalAuthRequestInfo))
|
||||||
|
|
||||||
lockoutPolicy, lockoutErr := getLockoutPolicy(ctx, wm.ResourceOwner, es.FilterToQueryReducer)
|
lockoutPolicy, lockoutErr := getLockoutPolicy(ctx, wm.GetResourceOwner(), es.FilterToQueryReducer)
|
||||||
logging.OnError(lockoutErr).Error("unable to get lockout policy")
|
logging.OnError(lockoutErr).Error("unable to get lockout policy")
|
||||||
if lockoutPolicy != nil && lockoutPolicy.MaxPasswordAttempts > 0 && wm.PasswordCheckFailedCount+1 >= lockoutPolicy.MaxPasswordAttempts {
|
if lockoutPolicy != nil && lockoutPolicy.MaxPasswordAttempts > 0 && wm.GetPasswordCheckFailedCount()+1 >= lockoutPolicy.MaxPasswordAttempts {
|
||||||
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
||||||
}
|
}
|
||||||
return commands, err
|
// in case the login policy ignores unknown usernames,
|
||||||
|
// we do not slow down the response time with a tarpit
|
||||||
|
// since this would leak the user existence
|
||||||
|
if tarpit != nil {
|
||||||
|
tarpit(wm.GetPasswordCheckFailedCount() + 1)
|
||||||
|
}
|
||||||
|
return commands, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) passwordWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPasswordWriteModel, err error) {
|
func (c *Commands) passwordWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPasswordWriteModel, err error) {
|
||||||
|
|||||||
@@ -25,6 +25,26 @@ type HumanPasswordWriteModel struct {
|
|||||||
UserState domain.UserState
|
UserState domain.UserState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wm *HumanPasswordWriteModel) GetUserState() domain.UserState {
|
||||||
|
return wm.UserState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanPasswordWriteModel) GetPasswordCheckFailedCount() uint64 {
|
||||||
|
return wm.PasswordCheckFailedCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanPasswordWriteModel) GetEncodedHash() string {
|
||||||
|
return wm.EncodedHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanPasswordWriteModel) GetResourceOwner() string {
|
||||||
|
return wm.ResourceOwner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanPasswordWriteModel) GetWriteModel() *eventstore.WriteModel {
|
||||||
|
return &wm.WriteModel
|
||||||
|
}
|
||||||
|
|
||||||
func NewHumanPasswordWriteModel(userID, resourceOwner string) *HumanPasswordWriteModel {
|
func NewHumanPasswordWriteModel(userID, resourceOwner string) *HumanPasswordWriteModel {
|
||||||
return &HumanPasswordWriteModel{
|
return &HumanPasswordWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
|
|||||||
@@ -760,6 +760,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
|||||||
func TestCommandSide_ChangePassword(t *testing.T) {
|
func TestCommandSide_ChangePassword(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
userPasswordHasher *crypto.Hasher
|
userPasswordHasher *crypto.Hasher
|
||||||
|
tarpit Tarpit
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -783,7 +784,9 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "userid missing, invalid argument error",
|
name: "userid missing, invalid argument error",
|
||||||
fields: fields{},
|
fields: fields{
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
oldPassword: "password",
|
oldPassword: "password",
|
||||||
@@ -797,7 +800,9 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "old password missing, invalid argument error",
|
name: "old password missing, invalid argument error",
|
||||||
fields: fields{},
|
fields: fields{
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
userID: "user1",
|
userID: "user1",
|
||||||
@@ -811,7 +816,9 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "new password missing, invalid argument error",
|
name: "new password missing, invalid argument error",
|
||||||
fields: fields{},
|
fields: fields{
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
userID: "user1",
|
userID: "user1",
|
||||||
@@ -825,7 +832,9 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "user not existing, precondition error",
|
name: "user not existing, precondition error",
|
||||||
fields: fields{},
|
fields: fields{
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
userID: "user1",
|
userID: "user1",
|
||||||
@@ -844,6 +853,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
name: "existing password empty, precondition error",
|
name: "existing password empty, precondition error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -878,6 +888,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
name: "password not matching complexity policy, invalid argument error",
|
name: "password not matching complexity policy, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -914,6 +925,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
false,
|
false,
|
||||||
"")),
|
"")),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck of user locking relevant events
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
org.NewPasswordComplexityPolicyAddedEvent(
|
org.NewPasswordComplexityPolicyAddedEvent(
|
||||||
@@ -936,6 +948,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
name: "password not matching, invalid argument error",
|
name: "password not matching, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -972,6 +985,89 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
false,
|
false,
|
||||||
"")),
|
"")),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck of user locking relevant events
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
|
&org.NewAggregate("org1").Aggregate,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
user.NewHumanPasswordCheckFailedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "password not matching, lockout policy active, invalid argument error",
|
||||||
|
fields: fields{
|
||||||
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
userID: "user1",
|
||||||
|
oldPassword: "password-old",
|
||||||
|
newPassword: "password1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
expect: []expect{
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanAddedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
"username",
|
||||||
|
"firstname",
|
||||||
|
"lastname",
|
||||||
|
"nickname",
|
||||||
|
"displayname",
|
||||||
|
language.German,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email@test.ch",
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanEmailVerifiedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanPasswordChangedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
"$plain$x$password",
|
||||||
|
false,
|
||||||
|
"")),
|
||||||
|
),
|
||||||
|
expectFilter(), // recheck of user locking relevant events
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
|
&org.NewAggregate("org1").Aggregate,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
user.NewHumanPasswordCheckFailedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
user.NewUserLockedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
@@ -981,6 +1077,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
name: "change password, ok",
|
name: "change password, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1017,6 +1114,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
false,
|
false,
|
||||||
"")),
|
"")),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck of user locking relevant events
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
||||||
@@ -1048,6 +1146,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
name: "change password with userAgentID, ok",
|
name: "change password with userAgentID, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1085,6 +1184,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
false,
|
false,
|
||||||
"")),
|
"")),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck of user locking relevant events
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
||||||
@@ -1116,6 +1216,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
name: "change password with changeRequired, ok",
|
name: "change password with changeRequired, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1154,6 +1255,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
false,
|
false,
|
||||||
"")),
|
"")),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck of user locking relevant events
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
||||||
@@ -1185,8 +1287,9 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: eventstoreExpect(t, tt.expect...),
|
eventstore: expectEventstore(tt.expect...)(t),
|
||||||
userPasswordHasher: tt.fields.userPasswordHasher,
|
userPasswordHasher: tt.fields.userPasswordHasher,
|
||||||
|
tarpit: tt.fields.tarpit.tarpit,
|
||||||
}
|
}
|
||||||
got, err := r.ChangePassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.oldPassword, tt.args.newPassword, tt.args.userAgentID, tt.args.changeRequired)
|
got, err := r.ChangePassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.oldPassword, tt.args.newPassword, tt.args.userAgentID, tt.args.changeRequired)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
@@ -1198,6 +1301,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
|||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
assertObjectDetails(t, tt.res.want, got)
|
assertObjectDetails(t, tt.res.want, got)
|
||||||
}
|
}
|
||||||
|
tt.fields.tarpit.metExpectedCalls(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1597,6 +1701,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(*testing.T) *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
userPasswordHasher *crypto.Hasher
|
userPasswordHasher *crypto.Hasher
|
||||||
|
tarpit Tarpit
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -1618,6 +1723,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
name: "userid missing, invalid argument error",
|
name: "userid missing, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1632,6 +1738,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
name: "password missing, invalid argument error",
|
name: "password missing, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1649,6 +1756,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
expectFilter(),
|
expectFilter(),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1689,6 +1797,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1730,6 +1839,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1791,6 +1902,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1848,6 +1961,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -1933,6 +2047,97 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
userID: "user1",
|
||||||
|
password: "password1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
authReq: &domain.AuthRequest{
|
||||||
|
ID: "request1",
|
||||||
|
AgentID: "agent1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "password not matching, ignore unknow usernames (no tarpit), precondition error",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewLoginPolicyAddedEvent(context.Background(),
|
||||||
|
&org.NewAggregate("org1").Aggregate,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
domain.PasswordlessTypeNotAllowed,
|
||||||
|
"",
|
||||||
|
time.Hour*1,
|
||||||
|
time.Hour*2,
|
||||||
|
time.Hour*3,
|
||||||
|
time.Hour*4,
|
||||||
|
time.Hour*5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanAddedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
"username",
|
||||||
|
"firstname",
|
||||||
|
"lastname",
|
||||||
|
"nickname",
|
||||||
|
"displayname",
|
||||||
|
language.German,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email@test.ch",
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanEmailVerifiedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanPasswordChangedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
"$plain$x$password",
|
||||||
|
false,
|
||||||
|
"")),
|
||||||
|
),
|
||||||
|
expectFilter(),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
|
&org.NewAggregate("org1").Aggregate,
|
||||||
|
0, 0, false,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
user.NewHumanPasswordCheckFailedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&user.AuthRequestInfo{
|
||||||
|
ID: "request1",
|
||||||
|
UserAgentID: "agent1",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2026,6 +2231,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2108,6 +2314,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2193,6 +2400,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2270,6 +2478,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2366,6 +2575,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2385,6 +2595,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
userPasswordHasher: tt.fields.userPasswordHasher,
|
userPasswordHasher: tt.fields.userPasswordHasher,
|
||||||
|
tarpit: tt.fields.tarpit.tarpit,
|
||||||
}
|
}
|
||||||
err := r.HumanCheckPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.authReq)
|
err := r.HumanCheckPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.authReq)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
@@ -2393,6 +2604,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
if tt.res.err != nil && !tt.res.err(err) {
|
if tt.res.err != nil && !tt.res.err(err) {
|
||||||
t.Errorf("got wrong err: %v ", err)
|
t.Errorf("got wrong err: %v ", err)
|
||||||
}
|
}
|
||||||
|
tt.fields.tarpit.metExpectedCalls(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Com
|
|||||||
}
|
}
|
||||||
// ...or old password
|
// ...or old password
|
||||||
if password.OldPassword != "" {
|
if password.OldPassword != "" {
|
||||||
verification = c.checkCurrentPassword(password.Password, password.EncodedPasswordHash, password.OldPassword, wm.PasswordEncodedHash)
|
verification = c.checkCurrentPassword(password.Password, password.EncodedPasswordHash, password.OldPassword, wm, c.tarpit)
|
||||||
}
|
}
|
||||||
cmd, err := c.setPasswordCommand(
|
cmd, err := c.setPasswordCommand(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -2207,6 +2207,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
checkPermission domain.PermissionCheck
|
checkPermission domain.PermissionCheck
|
||||||
defaultSecretGenerators *SecretGenerators
|
defaultSecretGenerators *SecretGenerators
|
||||||
defaultEmailCodeURLTemplate func(ctx context.Context) string
|
defaultEmailCodeURLTemplate func(ctx context.Context) string
|
||||||
|
tarpit Tarpit
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -2243,6 +2244,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2268,6 +2270,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2289,6 +2292,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2334,6 +2338,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2381,6 +2386,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2408,6 +2414,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2435,6 +2442,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2483,6 +2491,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2517,6 +2526,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2572,6 +2582,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2602,6 +2613,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2631,6 +2643,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2668,6 +2681,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2703,6 +2717,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2753,6 +2768,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2835,6 +2851,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
|
||||||
defaultSecretGenerators: defaultGenerators,
|
defaultSecretGenerators: defaultGenerators,
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2910,6 +2927,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
|
||||||
defaultSecretGenerators: defaultGenerators,
|
defaultSecretGenerators: defaultGenerators,
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2940,6 +2958,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -2977,6 +2996,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3012,6 +3032,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3091,6 +3112,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
|
||||||
defaultSecretGenerators: defaultGenerators,
|
defaultSecretGenerators: defaultGenerators,
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3118,6 +3140,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3151,6 +3174,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3183,6 +3207,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck of user locking relevant events
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
||||||
@@ -3198,6 +3223,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3222,6 +3248,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
eventstore: expectEventstore(),
|
eventstore: expectEventstore(),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3256,6 +3283,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3310,6 +3338,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3343,6 +3372,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck of user locking relevant events
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
||||||
@@ -3366,6 +3396,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3400,9 +3431,27 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck of user locking relevant events
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
|
&userAgg.Aggregate,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
user.NewHumanPasswordCheckFailedEvent(context.Background(),
|
||||||
|
&userAgg.Aggregate,
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(1),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3474,6 +3523,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3528,6 +3578,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3592,6 +3643,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3650,6 +3702,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -3672,7 +3725,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "change human password and password encoded, password code, encoded used",
|
name: "change human password encoded, old password, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
@@ -3724,15 +3777,15 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
),
|
),
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
userPasswordHasher: mockPasswordHasher("x"),
|
userPasswordHasher: mockPasswordHasher("x"),
|
||||||
|
tarpit: expectTarpit(0),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
human: &ChangeHuman{
|
human: &ChangeHuman{
|
||||||
Password: &Password{
|
Password: &Password{
|
||||||
Password: "passwordnotused",
|
OldPassword: "password",
|
||||||
EncodedPasswordHash: "$plain$x$password2",
|
EncodedPasswordHash: "$plain$x$password2",
|
||||||
PasswordCode: "code",
|
|
||||||
ChangeRequired: true,
|
ChangeRequired: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3758,6 +3811,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
|
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
|
||||||
userEncryption: tt.args.codeAlg,
|
userEncryption: tt.args.codeAlg,
|
||||||
defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
|
defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
|
||||||
|
tarpit: tt.fields.tarpit.tarpit,
|
||||||
}
|
}
|
||||||
err := r.ChangeUserHuman(tt.args.ctx, tt.args.human, tt.args.codeAlg)
|
err := r.ChangeUserHuman(tt.args.ctx, tt.args.human, tt.args.codeAlg)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
@@ -3773,6 +3827,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
|||||||
assert.Equal(t, tt.res.wantEmailCode, tt.args.human.EmailCode)
|
assert.Equal(t, tt.res.wantEmailCode, tt.args.human.EmailCode)
|
||||||
assert.Equal(t, tt.res.wantPhoneCode, tt.args.human.PhoneCode)
|
assert.Equal(t, tt.res.wantPhoneCode, tt.args.human.PhoneCode)
|
||||||
}
|
}
|
||||||
|
tt.fields.tarpit.metExpectedCalls(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,26 @@ type UserV2WriteModel struct {
|
|||||||
Metadata map[string][]byte
|
Metadata map[string][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wm *UserV2WriteModel) GetUserState() domain.UserState {
|
||||||
|
return wm.UserState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *UserV2WriteModel) GetPasswordCheckFailedCount() uint64 {
|
||||||
|
return wm.PasswordCheckFailedCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *UserV2WriteModel) GetEncodedHash() string {
|
||||||
|
return wm.PasswordEncodedHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *UserV2WriteModel) GetResourceOwner() string {
|
||||||
|
return wm.ResourceOwner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *UserV2WriteModel) GetWriteModel() *eventstore.WriteModel {
|
||||||
|
return &wm.WriteModel
|
||||||
|
}
|
||||||
|
|
||||||
func NewUserExistsWriteModel(userID, resourceOwner string) *UserV2WriteModel {
|
func NewUserExistsWriteModel(userID, resourceOwner string) *UserV2WriteModel {
|
||||||
return newUserV2WriteModel(userID, resourceOwner, WithHuman(), WithMachine())
|
return newUserV2WriteModel(userID, resourceOwner, WithHuman(), WithMachine())
|
||||||
}
|
}
|
||||||
@@ -292,6 +312,7 @@ func (wm *UserV2WriteModel) Reduce() error {
|
|||||||
case *user.HumanPasswordChangedEvent:
|
case *user.HumanPasswordChangedEvent:
|
||||||
wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
||||||
wm.PasswordChangeRequired = e.ChangeRequired
|
wm.PasswordChangeRequired = e.ChangeRequired
|
||||||
|
wm.PasswordCheckFailedCount = 0
|
||||||
wm.EmptyPasswordCode()
|
wm.EmptyPasswordCode()
|
||||||
case *user.HumanPasswordCodeAddedEvent:
|
case *user.HumanPasswordCodeAddedEvent:
|
||||||
wm.SetPasswordCode(e)
|
wm.SetPasswordCode(e)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type SystemDefaults struct {
|
|||||||
PasswordHasher crypto.HashConfig
|
PasswordHasher crypto.HashConfig
|
||||||
SecretHasher crypto.HashConfig
|
SecretHasher crypto.HashConfig
|
||||||
Multifactors MultifactorConfig
|
Multifactors MultifactorConfig
|
||||||
|
Tarpit TarpitConfig
|
||||||
DomainVerification DomainVerification
|
DomainVerification DomainVerification
|
||||||
Notifications Notifications
|
Notifications Notifications
|
||||||
KeyConfig KeyConfig
|
KeyConfig KeyConfig
|
||||||
|
|||||||
34
internal/config/systemdefaults/tarpit.go
Normal file
34
internal/config/systemdefaults/tarpit.go
Normal 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
|
||||||
|
}
|
||||||
83
internal/config/systemdefaults/tarpit_test.go
Normal file
83
internal/config/systemdefaults/tarpit_test.go
Normal file
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user