feat: Login verification lifetimes (#3190)

* feat: add login check lifetimes to login policy

* feat: org features test

* feat: read lifetimes from loginpolicy
This commit is contained in:
Fabi 2022-02-21 16:05:02 +01:00 committed by GitHub
parent 7d235e3eed
commit f05d4063bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1188 additions and 421 deletions

View File

@ -1514,7 +1514,7 @@ This is an empty request
| ----- | ---- | ----------- | ----------- | | ----- | ---- | ----------- | ----------- |
| sid | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | sid | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| token | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | token | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| from | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | sender_number | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
@ -3593,6 +3593,11 @@ This is an empty request
| force_mfa | bool | - | | | force_mfa | bool | - | |
| passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true<br /> | | passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true<br /> |
| hide_password_reset | bool | - | | | hide_password_reset | bool | - | |
| password_check_lifetime | google.protobuf.Duration | - | |
| external_login_check_lifetime | google.protobuf.Duration | - | |
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |
| second_factor_check_lifetime | google.protobuf.Duration | - | |
| multi_factor_check_lifetime | google.protobuf.Duration | - | |
@ -3710,7 +3715,7 @@ This is an empty request
| ----- | ---- | ----------- | ----------- | | ----- | ---- | ----------- | ----------- |
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| sid | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | sid | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| from | string | - | string.min_len: 1<br /> string.max_len: 200<br /> | | sender_number | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |

View File

@ -3036,6 +3036,11 @@ This is an empty request
| force_mfa | bool | - | | | force_mfa | bool | - | |
| passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true<br /> | | passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true<br /> |
| hide_password_reset | bool | - | | | hide_password_reset | bool | - | |
| password_check_lifetime | google.protobuf.Duration | - | |
| external_login_check_lifetime | google.protobuf.Duration | - | |
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |
| second_factor_check_lifetime | google.protobuf.Duration | - | |
| multi_factor_check_lifetime | google.protobuf.Duration | - | |
@ -7751,6 +7756,11 @@ This is an empty request
| force_mfa | bool | - | | | force_mfa | bool | - | |
| passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true<br /> | | passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true<br /> |
| hide_password_reset | bool | - | | | hide_password_reset | bool | - | |
| password_check_lifetime | google.protobuf.Duration | - | |
| external_login_check_lifetime | google.protobuf.Duration | - | |
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |
| second_factor_check_lifetime | google.protobuf.Duration | - | |
| multi_factor_check_lifetime | google.protobuf.Duration | - | |

View File

@ -63,6 +63,11 @@ title: zitadel/policy.proto
| passwordless_type | PasswordlessType | - | | | passwordless_type | PasswordlessType | - | |
| is_default | bool | - | | | is_default | bool | - | |
| hide_password_reset | bool | - | | | hide_password_reset | bool | - | |
| password_check_lifetime | google.protobuf.Duration | - | |
| external_login_check_lifetime | google.protobuf.Duration | - | |
| mfa_init_skip_lifetime | google.protobuf.Duration | - | |
| second_factor_check_lifetime | google.protobuf.Duration | - | |
| multi_factor_check_lifetime | google.protobuf.Duration | - | |

View File

@ -16,6 +16,11 @@ func updateLoginPolicyToDomain(p *admin_pb.UpdateLoginPolicyRequest) *domain.Log
ForceMFA: p.ForceMfa, ForceMFA: p.ForceMfa,
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType), PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
HidePasswordReset: p.HidePasswordReset, HidePasswordReset: p.HidePasswordReset,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),
MFAInitSkipLifetime: p.MfaInitSkipLifetime.AsDuration(),
SecondFactorCheckLifetime: p.SecondFactorCheckLifetime.AsDuration(),
MultiFactorCheckLifetime: p.MultiFactorCheckLifetime.AsDuration(),
} }
} }

View File

@ -16,6 +16,11 @@ func addLoginPolicyToDomain(p *mgmt_pb.AddCustomLoginPolicyRequest) *domain.Logi
ForceMFA: p.ForceMfa, ForceMFA: p.ForceMfa,
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType), PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
HidePasswordReset: p.HidePasswordReset, HidePasswordReset: p.HidePasswordReset,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),
MFAInitSkipLifetime: p.MfaInitSkipLifetime.AsDuration(),
SecondFactorCheckLifetime: p.SecondFactorCheckLifetime.AsDuration(),
MultiFactorCheckLifetime: p.MultiFactorCheckLifetime.AsDuration(),
} }
} }
@ -27,6 +32,11 @@ func updateLoginPolicyToDomain(p *mgmt_pb.UpdateCustomLoginPolicyRequest) *domai
ForceMFA: p.ForceMfa, ForceMFA: p.ForceMfa,
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType), PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
HidePasswordReset: p.HidePasswordReset, HidePasswordReset: p.HidePasswordReset,
PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(),
ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(),
MFAInitSkipLifetime: p.MfaInitSkipLifetime.AsDuration(),
SecondFactorCheckLifetime: p.SecondFactorCheckLifetime.AsDuration(),
MultiFactorCheckLifetime: p.MultiFactorCheckLifetime.AsDuration(),
} }
} }

View File

@ -5,6 +5,7 @@ import (
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/pkg/grpc/object" "github.com/caos/zitadel/pkg/grpc/object"
policy_pb "github.com/caos/zitadel/pkg/grpc/policy" policy_pb "github.com/caos/zitadel/pkg/grpc/policy"
"google.golang.org/protobuf/types/known/durationpb"
timestamp_pb "google.golang.org/protobuf/types/known/timestamppb" timestamp_pb "google.golang.org/protobuf/types/known/timestamppb"
) )
@ -17,6 +18,11 @@ func ModelLoginPolicyToPb(policy *query.LoginPolicy) *policy_pb.LoginPolicy {
ForceMfa: policy.ForceMFA, ForceMfa: policy.ForceMFA,
PasswordlessType: ModelPasswordlessTypeToPb(policy.PasswordlessType), PasswordlessType: ModelPasswordlessTypeToPb(policy.PasswordlessType),
HidePasswordReset: policy.HidePasswordReset, HidePasswordReset: policy.HidePasswordReset,
PasswordCheckLifetime: durationpb.New(policy.PasswordCheckLifetime),
ExternalLoginCheckLifetime: durationpb.New(policy.ExternalLoginCheckLifetime),
MfaInitSkipLifetime: durationpb.New(policy.MFAInitSkipLifetime),
SecondFactorCheckLifetime: durationpb.New(policy.SecondFactorCheckLifetime),
MultiFactorCheckLifetime: durationpb.New(policy.MultiFactorCheckLifetime),
Details: &object.ObjectDetails{ Details: &object.ObjectDetails{
Sequence: policy.Sequence, Sequence: policy.Sequence,
CreationDate: timestamp_pb.New(policy.CreationDate), CreationDate: timestamp_pb.New(policy.CreationDate),

View File

@ -50,12 +50,6 @@ type AuthRequestRepo struct {
ApplicationProvider applicationProvider ApplicationProvider applicationProvider
IdGenerator id.Generator IdGenerator id.Generator
PasswordCheckLifeTime time.Duration
ExternalLoginCheckLifeTime time.Duration
MFAInitSkippedLifeTime time.Duration
SecondFactorCheckLifeTime time.Duration
MultiFactorCheckLifeTime time.Duration
} }
type labelPolicyProvider interface { type labelPolicyProvider interface {
@ -761,7 +755,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
} }
isInternalLogin := request.SelectedIDPConfigID == "" && userSession.SelectedIDPConfigID == "" isInternalLogin := request.SelectedIDPConfigID == "" && userSession.SelectedIDPConfigID == ""
if !isInternalLogin && len(request.LinkingUsers) == 0 && !checkVerificationTimeMaxAge(userSession.ExternalLoginVerification, repo.ExternalLoginCheckLifeTime, request) { if !isInternalLogin && len(request.LinkingUsers) == 0 && !checkVerificationTimeMaxAge(userSession.ExternalLoginVerification, request.LoginPolicy.ExternalLoginCheckLifetime, request) {
selectedIDPConfigID := request.SelectedIDPConfigID selectedIDPConfigID := request.SelectedIDPConfigID
if selectedIDPConfigID == "" { if selectedIDPConfigID == "" {
selectedIDPConfigID = userSession.SelectedIDPConfigID selectedIDPConfigID = userSession.SelectedIDPConfigID
@ -858,7 +852,7 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *domain.AuthRequest, use
var step domain.NextStep var step domain.NextStep
if request.LoginPolicy.PasswordlessType != domain.PasswordlessTypeNotAllowed && user.IsPasswordlessReady() { if request.LoginPolicy.PasswordlessType != domain.PasswordlessTypeNotAllowed && user.IsPasswordlessReady() {
if checkVerificationTimeMaxAge(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime, request) { if checkVerificationTimeMaxAge(userSession.PasswordlessVerification, request.LoginPolicy.MultiFactorCheckLifetime, request) {
request.AuthTime = userSession.PasswordlessVerification request.AuthTime = userSession.PasswordlessVerification
return nil return nil
} }
@ -875,7 +869,7 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *domain.AuthRequest, use
return &domain.InitPasswordStep{} return &domain.InitPasswordStep{}
} }
if checkVerificationTimeMaxAge(userSession.PasswordVerification, repo.PasswordCheckLifeTime, request) { if checkVerificationTimeMaxAge(userSession.PasswordVerification, request.LoginPolicy.PasswordCheckLifetime, request) {
request.PasswordVerified = true request.PasswordVerified = true
request.AuthTime = userSession.PasswordVerification request.AuthTime = userSession.PasswordVerification
return nil return nil
@ -890,7 +884,7 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView,
mfaLevel := request.MFALevel() mfaLevel := request.MFALevel()
allowedProviders, required := user.MFATypesAllowed(mfaLevel, request.LoginPolicy) allowedProviders, required := user.MFATypesAllowed(mfaLevel, request.LoginPolicy)
promptRequired := (model.MFALevelToDomain(user.MFAMaxSetUp) < mfaLevel) || (len(allowedProviders) == 0 && required) promptRequired := (model.MFALevelToDomain(user.MFAMaxSetUp) < mfaLevel) || (len(allowedProviders) == 0 && required)
if promptRequired || !repo.mfaSkippedOrSetUp(user) { if promptRequired || !repo.mfaSkippedOrSetUp(user, request) {
types := user.MFATypesSetupPossible(mfaLevel, request.LoginPolicy) types := user.MFATypesSetupPossible(mfaLevel, request.LoginPolicy)
if promptRequired && len(types) == 0 { if promptRequired && len(types) == 0 {
return nil, false, errors.ThrowPreconditionFailed(nil, "LOGIN-5Hm8s", "Errors.Login.LoginPolicy.MFA.ForceAndNotConfigured") return nil, false, errors.ThrowPreconditionFailed(nil, "LOGIN-5Hm8s", "Errors.Login.LoginPolicy.MFA.ForceAndNotConfigured")
@ -912,14 +906,14 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView,
} }
fallthrough fallthrough
case domain.MFALevelSecondFactor: case domain.MFALevelSecondFactor:
if checkVerificationTimeMaxAge(userSession.SecondFactorVerification, repo.SecondFactorCheckLifeTime, request) { if checkVerificationTimeMaxAge(userSession.SecondFactorVerification, request.LoginPolicy.SecondFactorCheckLifetime, request) {
request.MFAsVerified = append(request.MFAsVerified, model.MFATypeToDomain(userSession.SecondFactorVerificationType)) request.MFAsVerified = append(request.MFAsVerified, model.MFATypeToDomain(userSession.SecondFactorVerificationType))
request.AuthTime = userSession.SecondFactorVerification request.AuthTime = userSession.SecondFactorVerification
return nil, true, nil return nil, true, nil
} }
fallthrough fallthrough
case domain.MFALevelMultiFactor: case domain.MFALevelMultiFactor:
if checkVerificationTimeMaxAge(userSession.MultiFactorVerification, repo.MultiFactorCheckLifeTime, request) { if checkVerificationTimeMaxAge(userSession.MultiFactorVerification, request.LoginPolicy.MultiFactorCheckLifetime, request) {
request.MFAsVerified = append(request.MFAsVerified, model.MFATypeToDomain(userSession.MultiFactorVerificationType)) request.MFAsVerified = append(request.MFAsVerified, model.MFATypeToDomain(userSession.MultiFactorVerificationType))
request.AuthTime = userSession.MultiFactorVerification request.AuthTime = userSession.MultiFactorVerification
return nil, true, nil return nil, true, nil
@ -930,11 +924,11 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView,
}, false, nil }, false, nil
} }
func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool { func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView, request *domain.AuthRequest) bool {
if user.MFAMaxSetUp > model.MFALevelNotSetUp { if user.MFAMaxSetUp > model.MFALevelNotSetUp {
return true return true
} }
return checkVerificationTime(user.MFAInitSkipped, repo.MFAInitSkippedLifeTime) return checkVerificationTime(user.MFAInitSkipped, request.LoginPolicy.MFAInitSkipLifetime)
} }
func (repo *AuthRequestRepo) getPrivacyPolicy(ctx context.Context, orgID string) (*domain.PrivacyPolicy, error) { func (repo *AuthRequestRepo) getPrivacyPolicy(ctx context.Context, orgID string) (*domain.PrivacyPolicy, error) {

View File

@ -263,11 +263,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
applicationProvider applicationProvider applicationProvider applicationProvider
loginPolicyProvider loginPolicyViewProvider loginPolicyProvider loginPolicyViewProvider
lockoutPolicyProvider lockoutPolicyViewProvider lockoutPolicyProvider lockoutPolicyViewProvider
PasswordCheckLifeTime time.Duration
ExternalLoginCheckLifeTime time.Duration
MFAInitSkippedLifeTime time.Duration
SecondFactorCheckLifeTime time.Duration
MultiFactorCheckLifeTime time.Duration
} }
type args struct { type args struct {
request *domain.AuthRequest request *domain.AuthRequest
@ -572,12 +567,16 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
MultiFactorCheckLifeTime: 10 * time.Hour,
lockoutPolicyProvider: &mockLockoutPolicy{ lockoutPolicyProvider: &mockLockoutPolicy{
policy: &query.LockoutPolicy{ policy: &query.LockoutPolicy{
ShowFailures: true, ShowFailures: true,
}, },
}, },
loginPolicyProvider: &mockLoginPolicy{
policy: &query.LoginPolicy{
MultiFactorCheckLifetime: 10 * time.Hour,
},
},
}, },
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false}, args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
[]domain.NextStep{&domain.PasswordlessRegistrationPromptStep{}}, []domain.NextStep{&domain.PasswordlessRegistrationPromptStep{}},
@ -597,7 +596,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
MultiFactorCheckLifeTime: 10 * time.Hour, loginPolicyProvider: &mockLoginPolicy{
policy: &query.LoginPolicy{
MultiFactorCheckLifetime: 10 * time.Hour,
},
},
}, },
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false}, args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
[]domain.NextStep{&domain.PasswordlessStep{}}, []domain.NextStep{&domain.PasswordlessStep{}},
@ -618,7 +621,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
MultiFactorCheckLifeTime: 10 * time.Hour, loginPolicyProvider: &mockLoginPolicy{
policy: &query.LoginPolicy{
MultiFactorCheckLifetime: 10 * time.Hour,
},
},
}, },
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false}, args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
[]domain.NextStep{&domain.PasswordlessStep{PasswordSet: true}}, []domain.NextStep{&domain.PasswordlessStep{PasswordSet: true}},
@ -645,13 +652,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
}, },
}, },
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
MultiFactorCheckLifeTime: 10 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN}, MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN},
MultiFactorCheckLifetime: 10 * time.Hour,
}, },
}, false}, }, false},
[]domain.NextStep{&domain.VerifyEMailStep{}}, []domain.NextStep{&domain.VerifyEMailStep{}},
@ -693,9 +700,18 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
}, },
}, },
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
SecondFactorCheckLifeTime: 18 * time.Hour, loginPolicyProvider: &mockLoginPolicy{
policy: &query.LoginPolicy{
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false}, },
},
args{&domain.AuthRequest{
UserID: "UserID",
SelectedIDPConfigID: "IDPConfigID",
LoginPolicy: &domain.LoginPolicy{
SecondFactorCheckLifetime: 18 * time.Hour,
}}, false},
[]domain.NextStep{&domain.ExternalLoginStep{SelectedIDPConfigID: "IDPConfigID"}}, []domain.NextStep{&domain.ExternalLoginStep{SelectedIDPConfigID: "IDPConfigID"}},
nil, nil,
}, },
@ -715,23 +731,21 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userGrantProvider: &mockUserGrants{}, userGrantProvider: &mockUserGrants{},
projectProvider: &mockProject{}, projectProvider: &mockProject{},
applicationProvider: &mockApp{app: &query.App{OIDCConfig: &query.OIDCApp{AppType: domain.OIDCApplicationTypeWeb}}}, applicationProvider: &mockApp{app: &query.App{OIDCConfig: &query.OIDCApp{AppType: domain.OIDCApplicationTypeWeb}}},
loginPolicyProvider: &mockLoginPolicy{
policy: &query.LoginPolicy{},
},
lockoutPolicyProvider: &mockLockoutPolicy{ lockoutPolicyProvider: &mockLockoutPolicy{
policy: &query.LockoutPolicy{ policy: &query.LockoutPolicy{
ShowFailures: true, ShowFailures: true,
}, },
}, },
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{ args{
&domain.AuthRequest{ &domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
SelectedIDPConfigID: "IDPConfigID", SelectedIDPConfigID: "IDPConfigID",
Request: &domain.AuthRequestOIDC{}, Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{}, LoginPolicy: &domain.LoginPolicy{
ExternalLoginCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
},
}, },
false}, false},
[]domain.NextStep{&domain.RedirectToCallbackStep{}}, []domain.NextStep{&domain.RedirectToCallbackStep{}},
@ -751,7 +765,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour, loginPolicyProvider: &mockLoginPolicy{
policy: &query.LoginPolicy{
PasswordCheckLifetime: 10 * 24 * time.Hour,
},
},
}, },
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false}, args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
[]domain.NextStep{&domain.PasswordStep{}}, []domain.NextStep{&domain.PasswordStep{}},
@ -779,15 +797,16 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
SecondFactorCheckLifeTime: 18 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
}, },
args{ args{
&domain.AuthRequest{ &domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
SelectedIDPConfigID: "IDPConfigID", SelectedIDPConfigID: "IDPConfigID",
Request: &domain.AuthRequestOIDC{}, Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{}, LoginPolicy: &domain.LoginPolicy{
SecondFactorCheckLifetime: 18 * time.Hour,
ExternalLoginCheckLifetime: 10 * 24 * time.Hour,
},
}, false}, }, false},
[]domain.NextStep{&domain.RedirectToCallbackStep{}}, []domain.NextStep{&domain.RedirectToCallbackStep{}},
nil, nil,
@ -811,14 +830,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{ args{
&domain.AuthRequest{ &domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, false}, }, false},
[]domain.NextStep{&domain.MFAVerificationStep{ []domain.NextStep{&domain.MFAVerificationStep{
@ -844,14 +863,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{ args{
&domain.AuthRequest{ &domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, false}, }, false},
[]domain.NextStep{&domain.MFAVerificationStep{ []domain.NextStep{&domain.MFAVerificationStep{
@ -878,9 +897,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{ args{
&domain.AuthRequest{ &domain.AuthRequest{
@ -888,6 +904,9 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
SelectedIDPConfigID: "IDPConfigID", SelectedIDPConfigID: "IDPConfigID",
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
ExternalLoginCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, false}, }, false},
[]domain.NextStep{&domain.MFAVerificationStep{ []domain.NextStep{&domain.MFAVerificationStep{
@ -915,14 +934,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{ args{
&domain.AuthRequest{ &domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, false}, }, false},
[]domain.NextStep{&domain.ChangePasswordStep{}}, []domain.NextStep{&domain.ChangePasswordStep{}},
@ -946,13 +965,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, false}, }, false},
[]domain.NextStep{&domain.VerifyEMailStep{}}, []domain.NextStep{&domain.VerifyEMailStep{}},
@ -977,13 +996,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, false}, }, false},
[]domain.NextStep{&domain.ChangePasswordStep{}, &domain.VerifyEMailStep{}}, []domain.NextStep{&domain.ChangePasswordStep{}, &domain.VerifyEMailStep{}},
@ -1011,14 +1030,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
Request: &domain.AuthRequestOIDC{}, Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, false}, }, false},
[]domain.NextStep{&domain.RedirectToCallbackStep{}}, []domain.NextStep{&domain.RedirectToCallbackStep{}},
@ -1046,8 +1065,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
@ -1055,6 +1072,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
Request: &domain.AuthRequestOIDC{}, Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, true}, }, true},
[]domain.NextStep{&domain.RedirectToCallbackStep{}}, []domain.NextStep{&domain.RedirectToCallbackStep{}},
@ -1082,8 +1101,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
@ -1091,6 +1108,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
Request: &domain.AuthRequestOIDC{}, Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, true}, }, true},
[]domain.NextStep{&domain.LoginSucceededStep{}, &domain.RedirectToCallbackStep{}}, []domain.NextStep{&domain.LoginSucceededStep{}, &domain.RedirectToCallbackStep{}},
@ -1120,8 +1139,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
@ -1129,6 +1146,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
Request: &domain.AuthRequestOIDC{}, Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, true}, }, true},
[]domain.NextStep{&domain.GrantRequiredStep{}}, []domain.NextStep{&domain.GrantRequiredStep{}},
@ -1159,8 +1178,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
@ -1168,6 +1185,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
Request: &domain.AuthRequestOIDC{}, Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, true}, }, true},
[]domain.NextStep{&domain.RedirectToCallbackStep{}}, []domain.NextStep{&domain.RedirectToCallbackStep{}},
@ -1197,8 +1216,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
@ -1206,6 +1223,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
Request: &domain.AuthRequestOIDC{}, Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, true}, }, true},
[]domain.NextStep{&domain.ProjectRequiredStep{}}, []domain.NextStep{&domain.ProjectRequiredStep{}},
@ -1236,8 +1255,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{&domain.AuthRequest{ args{&domain.AuthRequest{
UserID: "UserID", UserID: "UserID",
@ -1245,6 +1262,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
Request: &domain.AuthRequestOIDC{}, Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
PasswordCheckLifetime: 10 * 24 * time.Hour,
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, true}, }, true},
[]domain.NextStep{&domain.RedirectToCallbackStep{}}, []domain.NextStep{&domain.RedirectToCallbackStep{}},
@ -1266,9 +1285,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
loginPolicyProvider: &mockLoginPolicy{
policy: &query.LoginPolicy{
SecondFactorCheckLifetime: 18 * time.Hour,
},
},
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
SecondFactorCheckLifeTime: 18 * time.Hour,
}, },
args{ args{
&domain.AuthRequest{ &domain.AuthRequest{
@ -1299,8 +1322,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ShowFailures: true, ShowFailures: true,
}, },
}, },
SecondFactorCheckLifeTime: 18 * time.Hour,
PasswordCheckLifeTime: 10 * 24 * time.Hour,
}, },
args{ args{
&domain.AuthRequest{ &domain.AuthRequest{
@ -1309,6 +1330,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
LinkingUsers: []*domain.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}}, LinkingUsers: []*domain.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}},
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
SecondFactorCheckLifetime: 18 * time.Hour,
PasswordCheckLifetime: 10 * 24 * time.Hour,
}, },
}, false}, }, false},
[]domain.NextStep{&domain.LinkUsersStep{}}, []domain.NextStep{&domain.LinkUsersStep{}},
@ -1329,11 +1352,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
ApplicationProvider: tt.fields.applicationProvider, ApplicationProvider: tt.fields.applicationProvider,
LoginPolicyViewProvider: tt.fields.loginPolicyProvider, LoginPolicyViewProvider: tt.fields.loginPolicyProvider,
LockoutPolicyViewProvider: tt.fields.lockoutPolicyProvider, LockoutPolicyViewProvider: tt.fields.lockoutPolicyProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime,
MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime,
SecondFactorCheckLifeTime: tt.fields.SecondFactorCheckLifeTime,
MultiFactorCheckLifeTime: tt.fields.MultiFactorCheckLifeTime,
} }
got, err := repo.nextSteps(context.Background(), tt.args.request, tt.args.checkLoggedIn) got, err := repo.nextSteps(context.Background(), tt.args.request, tt.args.checkLoggedIn)
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) { if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
@ -1346,11 +1364,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
} }
func TestAuthRequestRepo_mfaChecked(t *testing.T) { func TestAuthRequestRepo_mfaChecked(t *testing.T) {
type fields struct {
MFAInitSkippedLifeTime time.Duration
SecondFactorCheckLifeTime time.Duration
MultiFactorCheckLifeTime time.Duration
}
type args struct { type args struct {
userSession *user_model.UserSessionView userSession *user_model.UserSessionView
request *domain.AuthRequest request *domain.AuthRequest
@ -1358,7 +1371,6 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
} }
tests := []struct { tests := []struct {
name string name string
fields fields
args args args args
want domain.NextStep want domain.NextStep
wantChecked bool wantChecked bool
@ -1377,13 +1389,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
//}, //},
{ {
"not set up, forced by policy, no mfas configured, error", "not set up, forced by policy, no mfas configured, error",
fields{
MFAInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{ args{
request: &domain.AuthRequest{ request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
ForceMFA: true, ForceMFA: true,
MFAInitSkipLifetime: 30 * 24 * time.Hour,
}, },
}, },
user: &user_model.UserView{ user: &user_model.UserView{
@ -1398,12 +1408,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
}, },
{ {
"not set up, no mfas configured, no prompt and true", "not set up, no mfas configured, no prompt and true",
fields{
MFAInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{ args{
request: &domain.AuthRequest{ request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{}, LoginPolicy: &domain.LoginPolicy{
MFAInitSkipLifetime: 30 * 24 * time.Hour,
},
}, },
user: &user_model.UserView{ user: &user_model.UserView{
HumanView: &user_model.HumanView{ HumanView: &user_model.HumanView{
@ -1417,13 +1426,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
}, },
{ {
"not set up, prompt and false", "not set up, prompt and false",
fields{
MFAInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{ args{
request: &domain.AuthRequest{ request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
MFAInitSkipLifetime: 30 * 24 * time.Hour,
}, },
}, },
user: &user_model.UserView{ user: &user_model.UserView{
@ -1442,14 +1449,12 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
}, },
{ {
"not set up, forced by org, true", "not set up, forced by org, true",
fields{
MFAInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{ args{
request: &domain.AuthRequest{ request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
ForceMFA: true, ForceMFA: true,
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
MFAInitSkipLifetime: 30 * 24 * time.Hour,
}, },
}, },
user: &user_model.UserView{ user: &user_model.UserView{
@ -1469,12 +1474,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
}, },
{ {
"not set up and skipped, true", "not set up and skipped, true",
fields{
MFAInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{ args{
request: &domain.AuthRequest{ request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{}, LoginPolicy: &domain.LoginPolicy{
MFAInitSkipLifetime: 30 * 24 * time.Hour,
},
}, },
user: &user_model.UserView{ user: &user_model.UserView{
HumanView: &user_model.HumanView{ HumanView: &user_model.HumanView{
@ -1489,13 +1493,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
}, },
{ {
"checked second factor, true", "checked second factor, true",
fields{
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{ args{
request: &domain.AuthRequest{ request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, },
user: &user_model.UserView{ user: &user_model.UserView{
@ -1512,13 +1514,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
}, },
{ {
"not checked, check and false", "not checked, check and false",
fields{
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{ args{
request: &domain.AuthRequest{ request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{ LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
SecondFactorCheckLifetime: 18 * time.Hour,
}, },
}, },
user: &user_model.UserView{ user: &user_model.UserView{
@ -1539,11 +1539,7 @@ func TestAuthRequestRepo_mfaChecked(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) {
repo := &AuthRequestRepo{ repo := &AuthRequestRepo{}
MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime,
SecondFactorCheckLifeTime: tt.fields.SecondFactorCheckLifeTime,
MultiFactorCheckLifeTime: tt.fields.MultiFactorCheckLifeTime,
}
got, ok, err := repo.mfaChecked(tt.args.userSession, tt.args.request, tt.args.user) got, ok, err := repo.mfaChecked(tt.args.userSession, tt.args.request, tt.args.user)
if (tt.errFunc != nil && !tt.errFunc(err)) || (err != nil && tt.errFunc == nil) { if (tt.errFunc != nil && !tt.errFunc(err)) || (err != nil && tt.errFunc == nil) {
t.Errorf("got wrong err: %v ", err) t.Errorf("got wrong err: %v ", err)
@ -1563,6 +1559,7 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
} }
type args struct { type args struct {
user *user_model.UserView user *user_model.UserView
request *domain.AuthRequest
} }
tests := []struct { tests := []struct {
name string name string
@ -1574,51 +1571,58 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
"mfa set up, true", "mfa set up, true",
fields{}, fields{},
args{ args{
&user_model.UserView{ user: &user_model.UserView{
HumanView: &user_model.HumanView{ HumanView: &user_model.HumanView{
MFAMaxSetUp: model.MFALevelSecondFactor, MFAMaxSetUp: model.MFALevelSecondFactor,
}, },
}, },
request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{},
},
}, },
true, true,
}, },
{ {
"mfa skipped active, true", "mfa skipped active, true",
fields{ fields{},
MFAInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{ args{
&user_model.UserView{ user: &user_model.UserView{
HumanView: &user_model.HumanView{ HumanView: &user_model.HumanView{
MFAMaxSetUp: -1, MFAMaxSetUp: -1,
MFAInitSkipped: time.Now().UTC().Add(-10 * time.Hour), MFAInitSkipped: time.Now().UTC().Add(-10 * time.Hour),
}, },
}, },
request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{
MFAInitSkipLifetime: 30 * 24 * time.Hour,
},
},
}, },
true, true,
}, },
{ {
"mfa skipped inactive, false", "mfa skipped inactive, false",
fields{ fields{},
MFAInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{ args{
&user_model.UserView{ user: &user_model.UserView{
HumanView: &user_model.HumanView{ HumanView: &user_model.HumanView{
MFAMaxSetUp: -1, MFAMaxSetUp: -1,
MFAInitSkipped: time.Now().UTC().Add(-40 * 24 * time.Hour), MFAInitSkipped: time.Now().UTC().Add(-40 * 24 * time.Hour),
}, },
}, },
request: &domain.AuthRequest{
LoginPolicy: &domain.LoginPolicy{
MFAInitSkipLifetime: 30 * 24 * time.Hour,
},
},
}, },
false, false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
repo := &AuthRequestRepo{ repo := &AuthRequestRepo{}
MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime, if got := repo.mfaSkippedOrSetUp(tt.args.user, tt.args.request); got != tt.want {
}
if got := repo.mfaSkippedOrSetUp(tt.args.user); got != tt.want {
t.Errorf("mfaSkippedOrSetUp() = %v, want %v", got, tt.want) t.Errorf("mfaSkippedOrSetUp() = %v, want %v", got, tt.want)
} }
}) })

View File

@ -92,11 +92,6 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma
ProjectProvider: queryView, ProjectProvider: queryView,
ApplicationProvider: queries, ApplicationProvider: queries,
IdGenerator: idGenerator, IdGenerator: idGenerator,
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck,
ExternalLoginCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck,
MFAInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MFAInitSkip,
SecondFactorCheckLifeTime: systemDefaults.VerificationLifetimes.SecondFactorCheck,
MultiFactorCheckLifeTime: systemDefaults.VerificationLifetimes.MultiFactorCheck,
}, },
eventstore.TokenRepo{ eventstore.TokenRepo{
View: view, View: view,

View File

@ -42,6 +42,11 @@ func writeModelToLoginPolicy(wm *LoginPolicyWriteModel) *domain.LoginPolicy {
HidePasswordReset: wm.HidePasswordReset, HidePasswordReset: wm.HidePasswordReset,
ForceMFA: wm.ForceMFA, ForceMFA: wm.ForceMFA,
PasswordlessType: wm.PasswordlessType, PasswordlessType: wm.PasswordlessType,
PasswordCheckLifetime: wm.PasswordCheckLifetime,
ExternalLoginCheckLifetime: wm.ExternalLoginCheckLifetime,
MFAInitSkipLifetime: wm.MFAInitSkipLifetime,
SecondFactorCheckLifetime: wm.SecondFactorCheckLifetime,
MultiFactorCheckLifetime: wm.MultiFactorCheckLifetime,
} }
} }

View File

@ -49,7 +49,19 @@ func (c *Commands) addDefaultLoginPolicy(ctx context.Context, iamAgg *eventstore
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LoginPolicy.AlreadyExists") return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LoginPolicy.AlreadyExists")
} }
return iam_repo.NewLoginPolicyAddedEvent(ctx, iamAgg, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.HidePasswordReset, policy.PasswordlessType), nil return iam_repo.NewLoginPolicyAddedEvent(ctx,
iamAgg,
policy.AllowUsernamePassword,
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.PasswordlessType,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime), nil
} }
func (c *Commands) ChangeDefaultLoginPolicy(ctx context.Context, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) { func (c *Commands) ChangeDefaultLoginPolicy(ctx context.Context, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) {
@ -78,7 +90,19 @@ func (c *Commands) changeDefaultLoginPolicy(ctx context.Context, iamAgg *eventst
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-M0sif", "Errors.IAM.LoginPolicy.NotFound") return nil, caos_errs.ThrowNotFound(nil, "IAM-M0sif", "Errors.IAM.LoginPolicy.NotFound")
} }
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.HidePasswordReset, policy.PasswordlessType) changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx,
iamAgg,
policy.AllowUsernamePassword,
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.PasswordlessType,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime)
if !hasChanged { if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged") return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged")
} }

View File

@ -2,6 +2,7 @@ package command
import ( import (
"context" "context"
"time"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
@ -65,6 +66,11 @@ func (wm *IAMLoginPolicyWriteModel) NewChangedEvent(
forceMFA, forceMFA,
hidePasswordReset bool, hidePasswordReset bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
secondFactorCheckLifetime,
multiFactorCheckLifetime time.Duration,
) (*iam.LoginPolicyChangedEvent, bool) { ) (*iam.LoginPolicyChangedEvent, bool) {
changes := make([]policy.LoginPolicyChanges, 0) changes := make([]policy.LoginPolicyChanges, 0)
@ -86,6 +92,21 @@ func (wm *IAMLoginPolicyWriteModel) NewChangedEvent(
if wm.HidePasswordReset != hidePasswordReset { if wm.HidePasswordReset != hidePasswordReset {
changes = append(changes, policy.ChangeHidePasswordReset(hidePasswordReset)) changes = append(changes, policy.ChangeHidePasswordReset(hidePasswordReset))
} }
if wm.PasswordCheckLifetime != passwordCheckLifetime {
changes = append(changes, policy.ChangePasswordCheckLifetime(passwordCheckLifetime))
}
if wm.ExternalLoginCheckLifetime != externalLoginCheckLifetime {
changes = append(changes, policy.ChangeExternalLoginCheckLifetime(externalLoginCheckLifetime))
}
if wm.MFAInitSkipLifetime != mfaInitSkipLifetime {
changes = append(changes, policy.ChangeMFAInitSkipLifetime(mfaInitSkipLifetime))
}
if wm.SecondFactorCheckLifetime != secondFactorCheckLifetime {
changes = append(changes, policy.ChangeSecondFactorCheckLifetime(secondFactorCheckLifetime))
}
if wm.MultiFactorCheckLifetime != multiFactorCheckLifetime {
changes = append(changes, policy.ChangeMultiFactorCheckLifetime(multiFactorCheckLifetime))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false return nil, false
} }

View File

@ -3,6 +3,7 @@ package command
import ( import (
"context" "context"
"testing" "testing"
"time"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
@ -49,6 +50,11 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*1,
time.Hour*1,
time.Hour*1,
time.Hour*1,
), ),
), ),
), ),
@ -83,6 +89,11 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
}, },
@ -98,6 +109,11 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
ForceMFA: true, ForceMFA: true,
HidePasswordReset: true, HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
res: res{ res: res{
@ -112,6 +128,11 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
ForceMFA: true, ForceMFA: true,
HidePasswordReset: true, HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
}, },
@ -187,6 +208,11 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -201,6 +227,11 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
ForceMFA: true, ForceMFA: true,
HidePasswordReset: true, HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
res: res{ res: res{
@ -222,13 +253,29 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
expectPush( expectPush(
[]*repository.Event{ []*repository.Event{
eventFromEventPusher( eventFromEventPusher(
newDefaultLoginPolicyChangedEvent(context.Background(), false, false, false, false, false, domain.PasswordlessTypeNotAllowed), newDefaultLoginPolicyChangedEvent(context.Background(),
false,
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
time.Hour*10,
time.Hour*20,
time.Hour*30,
time.Hour*40,
time.Hour*50),
), ),
}, },
), ),
@ -243,6 +290,11 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
ForceMFA: false, ForceMFA: false,
HidePasswordReset: false, HidePasswordReset: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed, PasswordlessType: domain.PasswordlessTypeNotAllowed,
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
SecondFactorCheckLifetime: time.Hour * 40,
MultiFactorCheckLifetime: time.Hour * 50,
}, },
}, },
res: res{ res: res{
@ -257,6 +309,11 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
ForceMFA: false, ForceMFA: false,
HidePasswordReset: false, HidePasswordReset: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed, PasswordlessType: domain.PasswordlessTypeNotAllowed,
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
SecondFactorCheckLifetime: time.Hour * 40,
MultiFactorCheckLifetime: time.Hour * 50,
}, },
}, },
}, },
@ -346,6 +403,11 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -377,6 +439,11 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -427,6 +494,11 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -557,6 +629,11 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -588,6 +665,11 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -632,6 +714,11 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -681,6 +768,11 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -738,6 +830,11 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1283,7 +1380,9 @@ func TestCommandSide_RemoveMultiFactorDefaultLoginPolicy(t *testing.T) {
} }
} }
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, hidePasswordReset bool, passwordlessType domain.PasswordlessType) *iam.LoginPolicyChangedEvent { func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, hidePasswordReset bool,
passwordlessType domain.PasswordlessType,
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime time.Duration) *iam.LoginPolicyChangedEvent {
event, _ := iam.NewLoginPolicyChangedEvent(ctx, event, _ := iam.NewLoginPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate, &iam.NewAggregate().Aggregate,
[]policy.LoginPolicyChanges{ []policy.LoginPolicyChanges{
@ -1293,6 +1392,11 @@ func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allow
policy.ChangeAllowUserNamePassword(allowUsernamePassword), policy.ChangeAllowUserNamePassword(allowUsernamePassword),
policy.ChangeHidePasswordReset(hidePasswordReset), policy.ChangeHidePasswordReset(hidePasswordReset),
policy.ChangePasswordlessType(passwordlessType), policy.ChangePasswordlessType(passwordlessType),
policy.ChangePasswordCheckLifetime(passwordLifetime),
policy.ChangeExternalLoginCheckLifetime(externalLoginLifetime),
policy.ChangeMFAInitSkipLifetime(mfaInitSkipLifetime),
policy.ChangeSecondFactorCheckLifetime(secondFactorLifetime),
policy.ChangeMultiFactorCheckLifetime(multiFactorLifetime),
}, },
) )
return event return event

View File

@ -241,7 +241,19 @@ func (c *Commands) setAllowedLoginPolicy(ctx context.Context, orgID string, feat
if !features.LoginPolicyPasswordReset && defaultPolicy.HidePasswordReset != existingPolicy.HidePasswordReset { if !features.LoginPolicyPasswordReset && defaultPolicy.HidePasswordReset != existingPolicy.HidePasswordReset {
policy.HidePasswordReset = defaultPolicy.HidePasswordReset policy.HidePasswordReset = defaultPolicy.HidePasswordReset
} }
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, OrgAggregateFromWriteModel(&existingPolicy.WriteModel), policy.AllowUserNamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.HidePasswordReset, policy.PasswordlessType) changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx,
OrgAggregateFromWriteModel(&existingPolicy.WriteModel),
policy.AllowUserNamePassword,
policy.AllowRegister,
policy.AllowExternalIDP,
policy.ForceMFA,
policy.HidePasswordReset,
policy.PasswordlessType,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime)
if hasChanged { if hasChanged {
events = append(events, changedEvent) events = append(events, changedEvent)
} }

View File

@ -5,6 +5,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/caos/zitadel/internal/repository/user"
"github.com/caos/zitadel/internal/static/mock"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/text/language" "golang.org/x/text/language"
@ -16,9 +18,7 @@ import (
"github.com/caos/zitadel/internal/repository/features" "github.com/caos/zitadel/internal/repository/features"
"github.com/caos/zitadel/internal/repository/iam" "github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/org" "github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
"github.com/caos/zitadel/internal/static" "github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/static/mock"
) )
func TestCommandSide_SetOrgFeatures(t *testing.T) { func TestCommandSide_SetOrgFeatures(t *testing.T) {
@ -165,6 +165,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -343,6 +348,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -547,6 +557,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -761,6 +776,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -990,6 +1010,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
eventFromEventPusher( eventFromEventPusher(
@ -1002,6 +1027,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*10,
time.Hour*20,
time.Hour*30,
time.Hour*40,
time.Hour*50,
), ),
), ),
), ),
@ -1017,6 +1047,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1195,7 +1230,13 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
org.NewLoginPolicyMultiFactorAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.MultiFactorTypeU2FWithPIN), org.NewLoginPolicyMultiFactorAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.MultiFactorTypeU2FWithPIN),
), ),
eventFromEventPusher( eventFromEventPusher(
newLoginPolicyChangedEvent(context.Background(), "org1", true, true, true, true, true, domain.PasswordlessTypeAllowed), newLoginPolicyChangedEvent(context.Background(), "org1",
true, true, true, true, true, domain.PasswordlessTypeAllowed,
nil,
nil,
nil,
nil,
nil),
), ),
eventFromEventPusher( eventFromEventPusher(
org.NewPasswordComplexityPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate), org.NewPasswordComplexityPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate),
@ -1278,6 +1319,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1537,6 +1583,11 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),

View File

@ -42,7 +42,12 @@ func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, pol
policy.AllowExternalIDP, policy.AllowExternalIDP,
policy.ForceMFA, policy.ForceMFA,
policy.HidePasswordReset, policy.HidePasswordReset,
policy.PasswordlessType)) policy.PasswordlessType,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,7 +105,12 @@ func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string,
policy.AllowExternalIDP, policy.AllowExternalIDP,
policy.ForceMFA, policy.ForceMFA,
policy.HidePasswordReset, policy.HidePasswordReset,
policy.PasswordlessType) policy.PasswordlessType,
policy.PasswordCheckLifetime,
policy.ExternalLoginCheckLifetime,
policy.MFAInitSkipLifetime,
policy.SecondFactorCheckLifetime,
policy.MultiFactorCheckLifetime)
if !hasChanged { if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged") return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged")

View File

@ -2,6 +2,7 @@ package command
import ( import (
"context" "context"
"time"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
@ -68,6 +69,11 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
forceMFA, forceMFA,
hidePasswordReset bool, hidePasswordReset bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
secondFactorCheckLifetime,
multiFactorCheckLifetime time.Duration,
) (*org.LoginPolicyChangedEvent, bool) { ) (*org.LoginPolicyChangedEvent, bool) {
changes := make([]policy.LoginPolicyChanges, 0) changes := make([]policy.LoginPolicyChanges, 0)
@ -86,6 +92,21 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
if wm.HidePasswordReset != hidePasswordReset { if wm.HidePasswordReset != hidePasswordReset {
changes = append(changes, policy.ChangeHidePasswordReset(hidePasswordReset)) changes = append(changes, policy.ChangeHidePasswordReset(hidePasswordReset))
} }
if wm.PasswordCheckLifetime != passwordCheckLifetime {
changes = append(changes, policy.ChangePasswordCheckLifetime(passwordCheckLifetime))
}
if wm.ExternalLoginCheckLifetime != externalLoginCheckLifetime {
changes = append(changes, policy.ChangeExternalLoginCheckLifetime(externalLoginCheckLifetime))
}
if wm.MFAInitSkipLifetime != mfaInitSkipLifetime {
changes = append(changes, policy.ChangeMFAInitSkipLifetime(mfaInitSkipLifetime))
}
if wm.SecondFactorCheckLifetime != secondFactorCheckLifetime {
changes = append(changes, policy.ChangeSecondFactorCheckLifetime(secondFactorCheckLifetime))
}
if wm.MultiFactorCheckLifetime != multiFactorCheckLifetime {
changes = append(changes, policy.ChangeMultiFactorCheckLifetime(multiFactorCheckLifetime))
}
if passwordlessType.Valid() && wm.PasswordlessType != passwordlessType { if passwordlessType.Valid() && wm.PasswordlessType != passwordlessType {
changes = append(changes, policy.ChangePasswordlessType(passwordlessType)) changes = append(changes, policy.ChangePasswordlessType(passwordlessType))
} }

View File

@ -3,6 +3,7 @@ package command
import ( import (
"context" "context"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -17,6 +18,14 @@ import (
"github.com/caos/zitadel/internal/repository/user" "github.com/caos/zitadel/internal/repository/user"
) )
var (
duration10 = time.Hour * 10
duration20 = time.Hour * 20
duration30 = time.Hour * 30
duration40 = time.Hour * 40
duration50 = time.Hour * 50
)
func TestCommandSide_AddLoginPolicy(t *testing.T) { func TestCommandSide_AddLoginPolicy(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
@ -71,6 +80,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -85,6 +99,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
AllowExternalIDP: true, AllowExternalIDP: true,
ForceMFA: true, ForceMFA: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
res: res{ res: res{
@ -107,6 +126,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -122,6 +146,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
AllowExternalIDP: true, AllowExternalIDP: true,
ForceMFA: true, ForceMFA: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
res: res{ res: res{
@ -144,6 +173,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -158,6 +192,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
}, },
@ -175,6 +214,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true, ForceMFA: true,
HidePasswordReset: true, HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
res: res{ res: res{
@ -189,6 +233,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
ForceMFA: true, ForceMFA: true,
HidePasswordReset: true, HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
}, },
@ -292,6 +341,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -305,6 +359,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -320,6 +379,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
AllowExternalIDP: true, AllowExternalIDP: true,
ForceMFA: true, ForceMFA: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
res: res{ res: res{
@ -341,6 +405,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -354,6 +423,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -370,6 +444,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
ForceMFA: true, ForceMFA: true,
HidePasswordReset: true, HidePasswordReset: true,
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
PasswordCheckLifetime: time.Hour * 1,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 3,
SecondFactorCheckLifetime: time.Hour * 4,
MultiFactorCheckLifetime: time.Hour * 5,
}, },
}, },
res: res{ res: res{
@ -391,6 +470,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -404,13 +488,31 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
expectPush( expectPush(
[]*repository.Event{ []*repository.Event{
eventFromEventPusher( eventFromEventPusher(
newLoginPolicyChangedEvent(context.Background(), "org1", false, false, false, false, false, domain.PasswordlessTypeNotAllowed), newLoginPolicyChangedEvent(context.Background(),
"org1",
false,
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
&duration10,
&duration20,
&duration30,
&duration40,
&duration50,
),
), ),
}, },
), ),
@ -426,6 +528,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
AllowExternalIDP: false, AllowExternalIDP: false,
ForceMFA: false, ForceMFA: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed, PasswordlessType: domain.PasswordlessTypeNotAllowed,
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
SecondFactorCheckLifetime: time.Hour * 40,
MultiFactorCheckLifetime: time.Hour * 50,
}, },
}, },
res: res{ res: res{
@ -440,6 +547,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
ForceMFA: false, ForceMFA: false,
HidePasswordReset: false, HidePasswordReset: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed, PasswordlessType: domain.PasswordlessTypeNotAllowed,
PasswordCheckLifetime: time.Hour * 10,
ExternalLoginCheckLifetime: time.Hour * 20,
MFAInitSkipLifetime: time.Hour * 30,
SecondFactorCheckLifetime: time.Hour * 40,
MultiFactorCheckLifetime: time.Hour * 50,
}, },
}, },
}, },
@ -527,6 +639,11 @@ func TestCommandSide_RemoveLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -660,6 +777,11 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -694,6 +816,11 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -748,6 +875,11 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -905,6 +1037,11 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -939,6 +1076,11 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -985,6 +1127,11 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1038,6 +1185,11 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1099,6 +1251,11 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
true, true,
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1710,17 +1867,35 @@ func TestCommandSide_RemoveMultiFactorLoginPolicy(t *testing.T) {
} }
} }
func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa, passwordReset bool, passwordlessType domain.PasswordlessType) *org.LoginPolicyChangedEvent { func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa, passwordReset bool,
event, _ := org.NewLoginPolicyChangedEvent(ctx, passwordlessType domain.PasswordlessType,
&org.NewAggregate(orgID, orgID).Aggregate, passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime *time.Duration) *org.LoginPolicyChangedEvent {
[]policy.LoginPolicyChanges{ changes := []policy.LoginPolicyChanges{
policy.ChangeAllowUserNamePassword(usernamePassword), policy.ChangeAllowUserNamePassword(usernamePassword),
policy.ChangeAllowRegister(register), policy.ChangeAllowRegister(register),
policy.ChangeAllowExternalIDP(externalIDP), policy.ChangeAllowExternalIDP(externalIDP),
policy.ChangeForceMFA(mfa), policy.ChangeForceMFA(mfa),
policy.ChangeHidePasswordReset(passwordReset), policy.ChangeHidePasswordReset(passwordReset),
policy.ChangePasswordlessType(passwordlessType), policy.ChangePasswordlessType(passwordlessType),
}, }
if passwordLifetime != nil {
changes = append(changes, policy.ChangePasswordCheckLifetime(*passwordLifetime))
}
if externalLoginLifetime != nil {
changes = append(changes, policy.ChangeExternalLoginCheckLifetime(*externalLoginLifetime))
}
if mfaInitSkipLifetime != nil {
changes = append(changes, policy.ChangeMFAInitSkipLifetime(*mfaInitSkipLifetime))
}
if secondFactorLifetime != nil {
changes = append(changes, policy.ChangeSecondFactorCheckLifetime(*secondFactorLifetime))
}
if multiFactorLifetime != nil {
changes = append(changes, policy.ChangeMultiFactorCheckLifetime(*multiFactorLifetime))
}
event, _ := org.NewLoginPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
changes,
) )
return event return event
} }

View File

@ -1,6 +1,8 @@
package command package command
import ( import (
"time"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/policy" "github.com/caos/zitadel/internal/repository/policy"
@ -15,6 +17,11 @@ type LoginPolicyWriteModel struct {
ForceMFA bool ForceMFA bool
HidePasswordReset bool HidePasswordReset bool
PasswordlessType domain.PasswordlessType PasswordlessType domain.PasswordlessType
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
SecondFactorCheckLifetime time.Duration
MultiFactorCheckLifetime time.Duration
State domain.PolicyState State domain.PolicyState
} }
@ -28,6 +35,11 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
wm.ForceMFA = e.ForceMFA wm.ForceMFA = e.ForceMFA
wm.PasswordlessType = e.PasswordlessType wm.PasswordlessType = e.PasswordlessType
wm.HidePasswordReset = e.HidePasswordReset wm.HidePasswordReset = e.HidePasswordReset
wm.PasswordCheckLifetime = e.PasswordCheckLifetime
wm.ExternalLoginCheckLifetime = e.ExternalLoginCheckLifetime
wm.MFAInitSkipLifetime = e.MFAInitSkipLifetime
wm.SecondFactorCheckLifetime = e.SecondFactorCheckLifetime
wm.MultiFactorCheckLifetime = e.MultiFactorCheckLifetime
wm.State = domain.PolicyStateActive wm.State = domain.PolicyStateActive
case *policy.LoginPolicyChangedEvent: case *policy.LoginPolicyChangedEvent:
if e.AllowRegister != nil { if e.AllowRegister != nil {
@ -48,6 +60,21 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
if e.PasswordlessType != nil { if e.PasswordlessType != nil {
wm.PasswordlessType = *e.PasswordlessType wm.PasswordlessType = *e.PasswordlessType
} }
if e.PasswordCheckLifetime != nil {
wm.PasswordCheckLifetime = *e.PasswordCheckLifetime
}
if e.ExternalLoginCheckLifetime != nil {
wm.ExternalLoginCheckLifetime = *e.ExternalLoginCheckLifetime
}
if e.MFAInitSkipLifetime != nil {
wm.MFAInitSkipLifetime = *e.MFAInitSkipLifetime
}
if e.SecondFactorCheckLifetime != nil {
wm.SecondFactorCheckLifetime = *e.SecondFactorCheckLifetime
}
if e.MultiFactorCheckLifetime != nil {
wm.MultiFactorCheckLifetime = *e.MultiFactorCheckLifetime
}
case *policy.LoginPolicyRemovedEvent: case *policy.LoginPolicyRemovedEvent:
wm.State = domain.PolicyStateRemoved wm.State = domain.PolicyStateRemoved
} }

View File

@ -1157,6 +1157,11 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1187,6 +1192,11 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1218,6 +1228,11 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1265,6 +1280,11 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1346,6 +1366,11 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1434,6 +1459,11 @@ func TestCommandSide_CheckPassword(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),

View File

@ -1607,6 +1607,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1664,6 +1669,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1721,6 +1731,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1795,6 +1810,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -1927,6 +1947,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -2027,6 +2052,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -2121,6 +2151,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),
@ -2237,6 +2272,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
false, false,
false, false,
domain.PasswordlessTypeNotAllowed, domain.PasswordlessTypeNotAllowed,
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
), ),
), ),
), ),

View File

@ -23,7 +23,6 @@ type SystemDefaults struct {
SMTPPasswordVerificationKey *crypto.KeyConfig SMTPPasswordVerificationKey *crypto.KeyConfig
SMSVerificationKey *crypto.KeyConfig SMSVerificationKey *crypto.KeyConfig
Multifactors MultifactorConfig Multifactors MultifactorConfig
VerificationLifetimes VerificationLifetimes
DomainVerification DomainVerification DomainVerification DomainVerification
Notifications Notifications Notifications Notifications
KeyConfig KeyConfig KeyConfig KeyConfig
@ -49,14 +48,6 @@ type OTPConfig struct {
VerificationKey *crypto.KeyConfig VerificationKey *crypto.KeyConfig
} }
type VerificationLifetimes struct {
PasswordCheck time.Duration
ExternalLoginCheck time.Duration
MFAInitSkip time.Duration
SecondFactorCheck time.Duration
MultiFactorCheck time.Duration
}
type DomainVerification struct { type DomainVerification struct {
VerificationKey *crypto.KeyConfig VerificationKey *crypto.KeyConfig
VerificationGenerator crypto.GeneratorConfig VerificationGenerator crypto.GeneratorConfig

View File

@ -1,6 +1,10 @@
package domain package domain
import "github.com/caos/zitadel/internal/eventstore/v1/models" import (
"time"
"github.com/caos/zitadel/internal/eventstore/v1/models"
)
type LoginPolicy struct { type LoginPolicy struct {
models.ObjectRoot models.ObjectRoot
@ -15,6 +19,11 @@ type LoginPolicy struct {
MultiFactors []MultiFactorType MultiFactors []MultiFactorType
PasswordlessType PasswordlessType PasswordlessType PasswordlessType
HidePasswordReset bool HidePasswordReset bool
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
SecondFactorCheckLifetime time.Duration
MultiFactorCheckLifetime time.Duration
} }
type IDPProvider struct { type IDPProvider struct {

View File

@ -27,6 +27,11 @@ type LoginPolicy struct {
PasswordlessType domain.PasswordlessType PasswordlessType domain.PasswordlessType
IsDefault bool IsDefault bool
HidePasswordReset bool HidePasswordReset bool
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
SecondFactorCheckLifetime time.Duration
MultiFactorCheckLifetime time.Duration
} }
type SecondFactors struct { type SecondFactors struct {
@ -95,6 +100,26 @@ var (
name: projection.LoginPolicyHidePWResetCol, name: projection.LoginPolicyHidePWResetCol,
table: loginPolicyTable, table: loginPolicyTable,
} }
LoginPolicyColumnPasswordCheckLifetime = Column{
name: projection.PasswordCheckLifetimeCol,
table: loginPolicyTable,
}
LoginPolicyColumnExternalLoginCheckLifetime = Column{
name: projection.ExternalLoginCheckLifetimeCol,
table: loginPolicyTable,
}
LoginPolicyColumnMFAInitSkipLifetime = Column{
name: projection.MFAInitSkipLifetimeCol,
table: loginPolicyTable,
}
LoginPolicyColumnSecondFactorCheckLifetime = Column{
name: projection.SecondFactorCheckLifetimeCol,
table: loginPolicyTable,
}
LoginPolicyColumnMultiFacotrCheckLifetime = Column{
name: projection.MultiFactorCheckLifetimeCol,
table: loginPolicyTable,
}
) )
func (q *Queries) LoginPolicyByID(ctx context.Context, orgID string) (*LoginPolicy, error) { func (q *Queries) LoginPolicyByID(ctx context.Context, orgID string) (*LoginPolicy, error) {
@ -234,6 +259,11 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
LoginPolicyColumnPasswordlessType.identifier(), LoginPolicyColumnPasswordlessType.identifier(),
LoginPolicyColumnIsDefault.identifier(), LoginPolicyColumnIsDefault.identifier(),
LoginPolicyColumnHidePasswordReset.identifier(), LoginPolicyColumnHidePasswordReset.identifier(),
LoginPolicyColumnPasswordCheckLifetime.identifier(),
LoginPolicyColumnExternalLoginCheckLifetime.identifier(),
LoginPolicyColumnMFAInitSkipLifetime.identifier(),
LoginPolicyColumnSecondFactorCheckLifetime.identifier(),
LoginPolicyColumnMultiFacotrCheckLifetime.identifier(),
).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar), ).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*LoginPolicy, error) { func(row *sql.Row) (*LoginPolicy, error) {
p := new(LoginPolicy) p := new(LoginPolicy)
@ -253,6 +283,11 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
&p.PasswordlessType, &p.PasswordlessType,
&p.IsDefault, &p.IsDefault,
&p.HidePasswordReset, &p.HidePasswordReset,
&p.PasswordCheckLifetime,
&p.ExternalLoginCheckLifetime,
&p.MFAInitSkipLifetime,
&p.SecondFactorCheckLifetime,
&p.MultiFactorCheckLifetime,
) )
if err != nil { if err != nil {
if errs.Is(err, sql.ErrNoRows) { if errs.Is(err, sql.ErrNoRows) {

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"testing" "testing"
"time"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
errs "github.com/caos/zitadel/internal/errors" errs "github.com/caos/zitadel/internal/errors"
@ -41,7 +42,12 @@ func Test_LoginPolicyPrepares(t *testing.T) {
` zitadel.projections.login_policies.multi_factors,`+ ` zitadel.projections.login_policies.multi_factors,`+
` zitadel.projections.login_policies.passwordless_type,`+ ` zitadel.projections.login_policies.passwordless_type,`+
` zitadel.projections.login_policies.is_default,`+ ` zitadel.projections.login_policies.is_default,`+
` zitadel.projections.login_policies.hide_password_reset`+ ` zitadel.projections.login_policies.hide_password_reset,`+
` zitadel.projections.login_policies.password_check_lifetime,`+
` zitadel.projections.login_policies.external_login_check_lifetime,`+
` zitadel.projections.login_policies.mfa_init_skip_lifetime,`+
` zitadel.projections.login_policies.second_factor_check_lifetime,`+
` zitadel.projections.login_policies.multi_factor_check_lifetime`+
` FROM zitadel.projections.login_policies`), ` FROM zitadel.projections.login_policies`),
nil, nil,
nil, nil,
@ -72,7 +78,12 @@ func Test_LoginPolicyPrepares(t *testing.T) {
` zitadel.projections.login_policies.multi_factors,`+ ` zitadel.projections.login_policies.multi_factors,`+
` zitadel.projections.login_policies.passwordless_type,`+ ` zitadel.projections.login_policies.passwordless_type,`+
` zitadel.projections.login_policies.is_default,`+ ` zitadel.projections.login_policies.is_default,`+
` zitadel.projections.login_policies.hide_password_reset`+ ` zitadel.projections.login_policies.hide_password_reset,`+
` zitadel.projections.login_policies.password_check_lifetime,`+
` zitadel.projections.login_policies.external_login_check_lifetime,`+
` zitadel.projections.login_policies.mfa_init_skip_lifetime,`+
` zitadel.projections.login_policies.second_factor_check_lifetime,`+
` zitadel.projections.login_policies.multi_factor_check_lifetime`+
` FROM zitadel.projections.login_policies`), ` FROM zitadel.projections.login_policies`),
[]string{ []string{
"aggregate_id", "aggregate_id",
@ -88,6 +99,11 @@ func Test_LoginPolicyPrepares(t *testing.T) {
"passwordless_type", "passwordless_type",
"is_default", "is_default",
"hide_password_reset", "hide_password_reset",
"password_check_lifetime",
"external_login_check_lifetime",
"mfa_init_skip_lifetime",
"second_factor_check_lifetime",
"multi_factor_check_lifetime",
}, },
[]driver.Value{ []driver.Value{
"ro", "ro",
@ -103,6 +119,11 @@ func Test_LoginPolicyPrepares(t *testing.T) {
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
true, true,
true, true,
time.Hour * 2,
time.Hour * 2,
time.Hour * 2,
time.Hour * 2,
time.Hour * 2,
}, },
), ),
}, },
@ -120,6 +141,11 @@ func Test_LoginPolicyPrepares(t *testing.T) {
PasswordlessType: domain.PasswordlessTypeAllowed, PasswordlessType: domain.PasswordlessTypeAllowed,
IsDefault: true, IsDefault: true,
HidePasswordReset: true, HidePasswordReset: true,
PasswordCheckLifetime: time.Hour * 2,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 2,
SecondFactorCheckLifetime: time.Hour * 2,
MultiFactorCheckLifetime: time.Hour * 2,
}, },
}, },
{ {
@ -139,7 +165,12 @@ func Test_LoginPolicyPrepares(t *testing.T) {
` zitadel.projections.login_policies.multi_factors,`+ ` zitadel.projections.login_policies.multi_factors,`+
` zitadel.projections.login_policies.passwordless_type,`+ ` zitadel.projections.login_policies.passwordless_type,`+
` zitadel.projections.login_policies.is_default,`+ ` zitadel.projections.login_policies.is_default,`+
` zitadel.projections.login_policies.hide_password_reset`+ ` zitadel.projections.login_policies.hide_password_reset,`+
` zitadel.projections.login_policies.password_check_lifetime,`+
` zitadel.projections.login_policies.external_login_check_lifetime,`+
` zitadel.projections.login_policies.mfa_init_skip_lifetime,`+
` zitadel.projections.login_policies.second_factor_check_lifetime,`+
` zitadel.projections.login_policies.multi_factor_check_lifetime`+
` FROM zitadel.projections.login_policies`), ` FROM zitadel.projections.login_policies`),
sql.ErrConnDone, sql.ErrConnDone,
), ),

View File

@ -111,6 +111,11 @@ const (
LoginPolicyPasswordlessTypeCol = "passwordless_type" LoginPolicyPasswordlessTypeCol = "passwordless_type"
LoginPolicyIsDefaultCol = "is_default" LoginPolicyIsDefaultCol = "is_default"
LoginPolicyHidePWResetCol = "hide_password_reset" LoginPolicyHidePWResetCol = "hide_password_reset"
PasswordCheckLifetimeCol = "password_check_lifetime"
ExternalLoginCheckLifetimeCol = "external_login_check_lifetime"
MFAInitSkipLifetimeCol = "mfa_init_skip_lifetime"
SecondFactorCheckLifetimeCol = "second_factor_check_lifetime"
MultiFactorCheckLifetimeCol = "multi_factor_check_lifetime"
) )
func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (*handler.Statement, error) { func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (*handler.Statement, error) {
@ -140,6 +145,11 @@ func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (
handler.NewCol(LoginPolicyPasswordlessTypeCol, policyEvent.PasswordlessType), handler.NewCol(LoginPolicyPasswordlessTypeCol, policyEvent.PasswordlessType),
handler.NewCol(LoginPolicyIsDefaultCol, isDefault), handler.NewCol(LoginPolicyIsDefaultCol, isDefault),
handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset), handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset),
handler.NewCol(PasswordCheckLifetimeCol, policyEvent.PasswordCheckLifetime),
handler.NewCol(ExternalLoginCheckLifetimeCol, policyEvent.ExternalLoginCheckLifetime),
handler.NewCol(MFAInitSkipLifetimeCol, policyEvent.MFAInitSkipLifetime),
handler.NewCol(SecondFactorCheckLifetimeCol, policyEvent.SecondFactorCheckLifetime),
handler.NewCol(MultiFactorCheckLifetimeCol, policyEvent.MultiFactorCheckLifetime),
}), nil }), nil
} }
@ -177,6 +187,22 @@ func (p *LoginPolicyProjection) reduceLoginPolicyChanged(event eventstore.Event)
if policyEvent.HidePasswordReset != nil { if policyEvent.HidePasswordReset != nil {
cols = append(cols, handler.NewCol(LoginPolicyHidePWResetCol, *policyEvent.HidePasswordReset)) cols = append(cols, handler.NewCol(LoginPolicyHidePWResetCol, *policyEvent.HidePasswordReset))
} }
if policyEvent.PasswordCheckLifetime != nil {
cols = append(cols, handler.NewCol(PasswordCheckLifetimeCol, *policyEvent.PasswordCheckLifetime))
}
if policyEvent.ExternalLoginCheckLifetime != nil {
cols = append(cols, handler.NewCol(ExternalLoginCheckLifetimeCol, *policyEvent.ExternalLoginCheckLifetime))
}
if policyEvent.MFAInitSkipLifetime != nil {
cols = append(cols, handler.NewCol(MFAInitSkipLifetimeCol, *policyEvent.MFAInitSkipLifetime))
}
if policyEvent.SecondFactorCheckLifetime != nil {
cols = append(cols, handler.NewCol(SecondFactorCheckLifetimeCol, *policyEvent.SecondFactorCheckLifetime))
}
if policyEvent.MultiFactorCheckLifetime != nil {
cols = append(cols, handler.NewCol(MultiFactorCheckLifetimeCol, *policyEvent.MultiFactorCheckLifetime))
}
return crdb.NewUpdateStatement( return crdb.NewUpdateStatement(
&policyEvent, &policyEvent,
cols, cols,

View File

@ -2,6 +2,7 @@ package projection
import ( import (
"testing" "testing"
"time"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
@ -34,7 +35,12 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": false, "allowExternalIdp": false,
"forceMFA": false, "forceMFA": false,
"hidePasswordReset": true, "hidePasswordReset": true,
"passwordlessType": 1 "passwordlessType": 1,
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
"secondFactorCheckLifetime": 10000000,
"multiFactorCheckLifetime": 10000000
}`), }`),
), org.LoginPolicyAddedEventMapper), ), org.LoginPolicyAddedEventMapper),
}, },
@ -47,7 +53,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -60,6 +66,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
false, false,
true, true,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
}, },
}, },
}, },
@ -79,7 +90,12 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": true, "allowExternalIdp": true,
"forceMFA": true, "forceMFA": true,
"hidePasswordReset": true, "hidePasswordReset": true,
"passwordlessType": 1 "passwordlessType": 1,
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
"secondFactorCheckLifetime": 10000000,
"multiFactorCheckLifetime": 10000000
}`), }`),
), org.LoginPolicyChangedEventMapper), ), org.LoginPolicyChangedEventMapper),
}, },
@ -91,7 +107,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9)", expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) WHERE (aggregate_id = $14)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -101,6 +117,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true, true,
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
true, true,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
"agg-id", "agg-id",
}, },
}, },
@ -276,7 +297,12 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
"allowExternalIdp": false, "allowExternalIdp": false,
"forceMFA": false, "forceMFA": false,
"hidePasswordReset": true, "hidePasswordReset": true,
"passwordlessType": 1 "passwordlessType": 1,
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
"secondFactorCheckLifetime": 10000000,
"multiFactorCheckLifetime": 10000000
}`), }`),
), iam.LoginPolicyAddedEventMapper), ), iam.LoginPolicyAddedEventMapper),
}, },
@ -288,7 +314,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
@ -301,6 +327,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed, domain.PasswordlessTypeAllowed,
true, true,
true, true,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
}, },
}, },
}, },

View File

@ -2,6 +2,7 @@ package iam
import ( import (
"context" "context"
"time"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
@ -28,6 +29,11 @@ func NewLoginPolicyAddedEvent(
forceMFA, forceMFA,
hidePasswordReset bool, hidePasswordReset bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
secondFactorCheckLifetime,
multiFactorCheckLifetime time.Duration,
) *LoginPolicyAddedEvent { ) *LoginPolicyAddedEvent {
return &LoginPolicyAddedEvent{ return &LoginPolicyAddedEvent{
LoginPolicyAddedEvent: *policy.NewLoginPolicyAddedEvent( LoginPolicyAddedEvent: *policy.NewLoginPolicyAddedEvent(
@ -40,7 +46,12 @@ func NewLoginPolicyAddedEvent(
allowExternalIDP, allowExternalIDP,
forceMFA, forceMFA,
hidePasswordReset, hidePasswordReset,
passwordlessType), passwordlessType,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
secondFactorCheckLifetime,
multiFactorCheckLifetime),
} }
} }

View File

@ -2,6 +2,7 @@ package org
import ( import (
"context" "context"
"time"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
@ -29,6 +30,11 @@ func NewLoginPolicyAddedEvent(
forceMFA, forceMFA,
hidePasswordReset bool, hidePasswordReset bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
secondFactorCheckLifetime,
multiFactorCheckLifetime time.Duration,
) *LoginPolicyAddedEvent { ) *LoginPolicyAddedEvent {
return &LoginPolicyAddedEvent{ return &LoginPolicyAddedEvent{
LoginPolicyAddedEvent: *policy.NewLoginPolicyAddedEvent( LoginPolicyAddedEvent: *policy.NewLoginPolicyAddedEvent(
@ -41,7 +47,12 @@ func NewLoginPolicyAddedEvent(
allowExternalIDP, allowExternalIDP,
forceMFA, forceMFA,
hidePasswordReset, hidePasswordReset,
passwordlessType), passwordlessType,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
secondFactorCheckLifetime,
multiFactorCheckLifetime),
} }
} }

View File

@ -2,6 +2,7 @@ package policy
import ( import (
"encoding/json" "encoding/json"
"time"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
@ -25,6 +26,11 @@ type LoginPolicyAddedEvent struct {
ForceMFA bool `json:"forceMFA,omitempty"` ForceMFA bool `json:"forceMFA,omitempty"`
HidePasswordReset bool `json:"hidePasswordReset,omitempty"` HidePasswordReset bool `json:"hidePasswordReset,omitempty"`
PasswordlessType domain.PasswordlessType `json:"passwordlessType,omitempty"` PasswordlessType domain.PasswordlessType `json:"passwordlessType,omitempty"`
PasswordCheckLifetime time.Duration `json:"passwordCheckLifetime,omitempty"`
ExternalLoginCheckLifetime time.Duration `json:"externalLoginCheckLifetime,omitempty"`
MFAInitSkipLifetime time.Duration `json:"mfaInitSkipLifetime,omitempty"`
SecondFactorCheckLifetime time.Duration `json:"secondFactorCheckLifetime,omitempty"`
MultiFactorCheckLifetime time.Duration `json:"multiFactorCheckLifetime,omitempty"`
} }
func (e *LoginPolicyAddedEvent) Data() interface{} { func (e *LoginPolicyAddedEvent) Data() interface{} {
@ -43,6 +49,11 @@ func NewLoginPolicyAddedEvent(
forceMFA, forceMFA,
hidePasswordReset bool, hidePasswordReset bool,
passwordlessType domain.PasswordlessType, passwordlessType domain.PasswordlessType,
passwordCheckLifetime,
externalLoginCheckLifetime,
mfaInitSkipLifetime,
secondFactorCheckLifetime,
multiFactorCheckLifetime time.Duration,
) *LoginPolicyAddedEvent { ) *LoginPolicyAddedEvent {
return &LoginPolicyAddedEvent{ return &LoginPolicyAddedEvent{
BaseEvent: *base, BaseEvent: *base,
@ -52,6 +63,11 @@ func NewLoginPolicyAddedEvent(
ForceMFA: forceMFA, ForceMFA: forceMFA,
PasswordlessType: passwordlessType, PasswordlessType: passwordlessType,
HidePasswordReset: hidePasswordReset, HidePasswordReset: hidePasswordReset,
PasswordCheckLifetime: passwordCheckLifetime,
ExternalLoginCheckLifetime: externalLoginCheckLifetime,
MFAInitSkipLifetime: mfaInitSkipLifetime,
SecondFactorCheckLifetime: secondFactorCheckLifetime,
MultiFactorCheckLifetime: multiFactorCheckLifetime,
} }
} }
@ -77,6 +93,11 @@ type LoginPolicyChangedEvent struct {
ForceMFA *bool `json:"forceMFA,omitempty"` ForceMFA *bool `json:"forceMFA,omitempty"`
HidePasswordReset *bool `json:"hidePasswordReset,omitempty"` HidePasswordReset *bool `json:"hidePasswordReset,omitempty"`
PasswordlessType *domain.PasswordlessType `json:"passwordlessType,omitempty"` PasswordlessType *domain.PasswordlessType `json:"passwordlessType,omitempty"`
PasswordCheckLifetime *time.Duration `json:"passwordCheckLifetime,omitempty"`
ExternalLoginCheckLifetime *time.Duration `json:"externalLoginCheckLifetime,omitempty"`
MFAInitSkipLifetime *time.Duration `json:"mfaInitSkipLifetime,omitempty"`
SecondFactorCheckLifetime *time.Duration `json:"secondFactorCheckLifetime,omitempty"`
MultiFactorCheckLifetime *time.Duration `json:"multiFactorCheckLifetime,omitempty"`
} }
func (e *LoginPolicyChangedEvent) Data() interface{} { func (e *LoginPolicyChangedEvent) Data() interface{} {
@ -141,6 +162,31 @@ func ChangeHidePasswordReset(hidePasswordReset bool) func(*LoginPolicyChangedEve
} }
} }
func ChangePasswordCheckLifetime(passwordCheckLifetime time.Duration) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.PasswordCheckLifetime = &passwordCheckLifetime
}
}
func ChangeExternalLoginCheckLifetime(externalLoginCheckLifetime time.Duration) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.ExternalLoginCheckLifetime = &externalLoginCheckLifetime
}
}
func ChangeMFAInitSkipLifetime(mfaInitSkipLifetime time.Duration) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.MFAInitSkipLifetime = &mfaInitSkipLifetime
}
}
func ChangeSecondFactorCheckLifetime(secondFactorCheckLifetime time.Duration) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.SecondFactorCheckLifetime = &secondFactorCheckLifetime
}
}
func ChangeMultiFactorCheckLifetime(multiFactorCheckLifetime time.Duration) func(*LoginPolicyChangedEvent) {
return func(e *LoginPolicyChangedEvent) {
e.MultiFactorCheckLifetime = &multiFactorCheckLifetime
}
}
func LoginPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) { func LoginPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &LoginPolicyChangedEvent{ e := &LoginPolicyChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event), BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -3459,6 +3459,11 @@ message UpdateLoginPolicyRequest {
description: "defines if password reset link should be shown in the login screen" description: "defines if password reset link should be shown in the login screen"
} }
]; ];
google.protobuf.Duration password_check_lifetime = 7;
google.protobuf.Duration external_login_check_lifetime = 8;
google.protobuf.Duration mfa_init_skip_lifetime = 9;
google.protobuf.Duration second_factor_check_lifetime = 10;
google.protobuf.Duration multi_factor_check_lifetime = 11;
} }
message UpdateLoginPolicyResponse { message UpdateLoginPolicyResponse {

View File

@ -4397,6 +4397,11 @@ message AddCustomLoginPolicyRequest {
bool force_mfa = 4; bool force_mfa = 4;
zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}]; zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}];
bool hide_password_reset = 6; bool hide_password_reset = 6;
google.protobuf.Duration password_check_lifetime = 7;
google.protobuf.Duration external_login_check_lifetime = 8;
google.protobuf.Duration mfa_init_skip_lifetime = 9;
google.protobuf.Duration second_factor_check_lifetime = 10;
google.protobuf.Duration multi_factor_check_lifetime = 11;
} }
message AddCustomLoginPolicyResponse { message AddCustomLoginPolicyResponse {
@ -4410,6 +4415,11 @@ message UpdateCustomLoginPolicyRequest {
bool force_mfa = 4; bool force_mfa = 4;
zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}]; zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}];
bool hide_password_reset = 6; bool hide_password_reset = 6;
google.protobuf.Duration password_check_lifetime = 7;
google.protobuf.Duration external_login_check_lifetime = 8;
google.protobuf.Duration mfa_init_skip_lifetime = 9;
google.protobuf.Duration second_factor_check_lifetime = 10;
google.protobuf.Duration multi_factor_check_lifetime = 11;
} }
message UpdateCustomLoginPolicyResponse { message UpdateCustomLoginPolicyResponse {

View File

@ -1,6 +1,7 @@
syntax = "proto3"; syntax = "proto3";
import "zitadel/object.proto"; import "zitadel/object.proto";
import "google/protobuf/duration.proto";
import "protoc-gen-openapiv2/options/annotations.proto"; import "protoc-gen-openapiv2/options/annotations.proto";
package zitadel.policy.v1; package zitadel.policy.v1;
@ -128,6 +129,12 @@ message LoginPolicy {
description: "defines if password reset link should be shown in the login screen" description: "defines if password reset link should be shown in the login screen"
} }
]; ];
google.protobuf.Duration password_check_lifetime = 9;
google.protobuf.Duration external_login_check_lifetime = 10;
google.protobuf.Duration mfa_init_skip_lifetime = 11;
google.protobuf.Duration second_factor_check_lifetime = 12;
google.protobuf.Duration multi_factor_check_lifetime = 13;
} }
enum SecondFactorType { enum SecondFactorType {