diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index b6bd61d5f9..36539a2558 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -1514,7 +1514,7 @@ This is an empty request | ----- | ---- | ----------- | ----------- | | sid | string | - | string.min_len: 1
string.max_len: 200
| | token | string | - | string.min_len: 1
string.max_len: 200
| -| from | string | - | string.min_len: 1
string.max_len: 200
| +| sender_number | string | - | string.min_len: 1
string.max_len: 200
| @@ -3593,6 +3593,11 @@ This is an empty request | force_mfa | bool | - | | | passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true
| | 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
string.max_len: 200
| | sid | string | - | string.min_len: 1
string.max_len: 200
| -| from | string | - | string.min_len: 1
string.max_len: 200
| +| sender_number | string | - | string.min_len: 1
string.max_len: 200
| diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md index 502ecf3d7a..bd5484afe6 100644 --- a/docs/docs/apis/proto/management.md +++ b/docs/docs/apis/proto/management.md @@ -3036,6 +3036,11 @@ This is an empty request | force_mfa | bool | - | | | passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true
| | 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 | - | | | passwordless_type | zitadel.policy.v1.PasswordlessType | - | enum.defined_only: true
| | 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 | - | | diff --git a/docs/docs/apis/proto/policy.md b/docs/docs/apis/proto/policy.md index 37961ca41b..cd482b7ff6 100644 --- a/docs/docs/apis/proto/policy.md +++ b/docs/docs/apis/proto/policy.md @@ -63,6 +63,11 @@ title: zitadel/policy.proto | passwordless_type | PasswordlessType | - | | | is_default | 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 | - | | diff --git a/internal/api/grpc/admin/login_policy_converter.go b/internal/api/grpc/admin/login_policy_converter.go index 900e88dbdb..3f6c26215a 100644 --- a/internal/api/grpc/admin/login_policy_converter.go +++ b/internal/api/grpc/admin/login_policy_converter.go @@ -10,12 +10,17 @@ import ( func updateLoginPolicyToDomain(p *admin_pb.UpdateLoginPolicyRequest) *domain.LoginPolicy { return &domain.LoginPolicy{ - AllowUsernamePassword: p.AllowUsernamePassword, - AllowRegister: p.AllowRegister, - AllowExternalIDP: p.AllowExternalIdp, - ForceMFA: p.ForceMfa, - PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType), - HidePasswordReset: p.HidePasswordReset, + AllowUsernamePassword: p.AllowUsernamePassword, + AllowRegister: p.AllowRegister, + AllowExternalIDP: p.AllowExternalIdp, + ForceMFA: p.ForceMfa, + PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType), + HidePasswordReset: p.HidePasswordReset, + PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(), + ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(), + MFAInitSkipLifetime: p.MfaInitSkipLifetime.AsDuration(), + SecondFactorCheckLifetime: p.SecondFactorCheckLifetime.AsDuration(), + MultiFactorCheckLifetime: p.MultiFactorCheckLifetime.AsDuration(), } } diff --git a/internal/api/grpc/management/policy_login_converter.go b/internal/api/grpc/management/policy_login_converter.go index 706a0f941a..1a393cbbf9 100644 --- a/internal/api/grpc/management/policy_login_converter.go +++ b/internal/api/grpc/management/policy_login_converter.go @@ -10,23 +10,33 @@ import ( func addLoginPolicyToDomain(p *mgmt_pb.AddCustomLoginPolicyRequest) *domain.LoginPolicy { return &domain.LoginPolicy{ - AllowUsernamePassword: p.AllowUsernamePassword, - AllowRegister: p.AllowRegister, - AllowExternalIDP: p.AllowExternalIdp, - ForceMFA: p.ForceMfa, - PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType), - HidePasswordReset: p.HidePasswordReset, + AllowUsernamePassword: p.AllowUsernamePassword, + AllowRegister: p.AllowRegister, + AllowExternalIDP: p.AllowExternalIdp, + ForceMFA: p.ForceMfa, + PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType), + HidePasswordReset: p.HidePasswordReset, + PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(), + ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(), + MFAInitSkipLifetime: p.MfaInitSkipLifetime.AsDuration(), + SecondFactorCheckLifetime: p.SecondFactorCheckLifetime.AsDuration(), + MultiFactorCheckLifetime: p.MultiFactorCheckLifetime.AsDuration(), } } func updateLoginPolicyToDomain(p *mgmt_pb.UpdateCustomLoginPolicyRequest) *domain.LoginPolicy { return &domain.LoginPolicy{ - AllowUsernamePassword: p.AllowUsernamePassword, - AllowRegister: p.AllowRegister, - AllowExternalIDP: p.AllowExternalIdp, - ForceMFA: p.ForceMfa, - PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType), - HidePasswordReset: p.HidePasswordReset, + AllowUsernamePassword: p.AllowUsernamePassword, + AllowRegister: p.AllowRegister, + AllowExternalIDP: p.AllowExternalIdp, + ForceMFA: p.ForceMfa, + PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType), + HidePasswordReset: p.HidePasswordReset, + PasswordCheckLifetime: p.PasswordCheckLifetime.AsDuration(), + ExternalLoginCheckLifetime: p.ExternalLoginCheckLifetime.AsDuration(), + MFAInitSkipLifetime: p.MfaInitSkipLifetime.AsDuration(), + SecondFactorCheckLifetime: p.SecondFactorCheckLifetime.AsDuration(), + MultiFactorCheckLifetime: p.MultiFactorCheckLifetime.AsDuration(), } } diff --git a/internal/api/grpc/policy/login_policy.go b/internal/api/grpc/policy/login_policy.go index f40a090dba..fb1f456604 100644 --- a/internal/api/grpc/policy/login_policy.go +++ b/internal/api/grpc/policy/login_policy.go @@ -5,18 +5,24 @@ import ( "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/pkg/grpc/object" 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" ) func ModelLoginPolicyToPb(policy *query.LoginPolicy) *policy_pb.LoginPolicy { return &policy_pb.LoginPolicy{ - IsDefault: policy.IsDefault, - AllowUsernamePassword: policy.AllowUsernamePassword, - AllowRegister: policy.AllowRegister, - AllowExternalIdp: policy.AllowExternalIDPs, - ForceMfa: policy.ForceMFA, - PasswordlessType: ModelPasswordlessTypeToPb(policy.PasswordlessType), - HidePasswordReset: policy.HidePasswordReset, + IsDefault: policy.IsDefault, + AllowUsernamePassword: policy.AllowUsernamePassword, + AllowRegister: policy.AllowRegister, + AllowExternalIdp: policy.AllowExternalIDPs, + ForceMfa: policy.ForceMFA, + PasswordlessType: ModelPasswordlessTypeToPb(policy.PasswordlessType), + 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{ Sequence: policy.Sequence, CreationDate: timestamp_pb.New(policy.CreationDate), diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 0aedc1f26f..82b1099160 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -50,12 +50,6 @@ type AuthRequestRepo struct { ApplicationProvider applicationProvider IdGenerator id.Generator - - PasswordCheckLifeTime time.Duration - ExternalLoginCheckLifeTime time.Duration - MFAInitSkippedLifeTime time.Duration - SecondFactorCheckLifeTime time.Duration - MultiFactorCheckLifeTime time.Duration } type labelPolicyProvider interface { @@ -761,7 +755,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth } 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 if selectedIDPConfigID == "" { selectedIDPConfigID = userSession.SelectedIDPConfigID @@ -858,7 +852,7 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *domain.AuthRequest, use var step domain.NextStep 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 return nil } @@ -875,7 +869,7 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *domain.AuthRequest, use return &domain.InitPasswordStep{} } - if checkVerificationTimeMaxAge(userSession.PasswordVerification, repo.PasswordCheckLifeTime, request) { + if checkVerificationTimeMaxAge(userSession.PasswordVerification, request.LoginPolicy.PasswordCheckLifetime, request) { request.PasswordVerified = true request.AuthTime = userSession.PasswordVerification return nil @@ -890,7 +884,7 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, mfaLevel := request.MFALevel() allowedProviders, required := user.MFATypesAllowed(mfaLevel, request.LoginPolicy) 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) if promptRequired && len(types) == 0 { 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 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.AuthTime = userSession.SecondFactorVerification return nil, true, nil } fallthrough 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.AuthTime = userSession.MultiFactorVerification return nil, true, nil @@ -930,11 +924,11 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, }, 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 { 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) { diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 2659f77bcd..315dbcf727 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -252,22 +252,17 @@ func (m *mockApp) AppByOIDCClientID(ctx context.Context, id string) (*query.App, func TestAuthRequestRepo_nextSteps(t *testing.T) { type fields struct { - AuthRequests *cache.AuthRequestCache - View *view.View - userSessionViewProvider userSessionViewProvider - userViewProvider userViewProvider - userEventProvider userEventProvider - orgViewProvider orgViewProvider - userGrantProvider userGrantProvider - projectProvider projectProvider - applicationProvider applicationProvider - loginPolicyProvider loginPolicyViewProvider - lockoutPolicyProvider lockoutPolicyViewProvider - PasswordCheckLifeTime time.Duration - ExternalLoginCheckLifeTime time.Duration - MFAInitSkippedLifeTime time.Duration - SecondFactorCheckLifeTime time.Duration - MultiFactorCheckLifeTime time.Duration + AuthRequests *cache.AuthRequestCache + View *view.View + userSessionViewProvider userSessionViewProvider + userViewProvider userViewProvider + userEventProvider userEventProvider + orgViewProvider orgViewProvider + userGrantProvider userGrantProvider + projectProvider projectProvider + applicationProvider applicationProvider + loginPolicyProvider loginPolicyViewProvider + lockoutPolicyProvider lockoutPolicyViewProvider } type args struct { request *domain.AuthRequest @@ -570,14 +565,18 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewUser{ PasswordlessInitRequired: true, }, - userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, - MultiFactorCheckLifeTime: 10 * time.Hour, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &query.LockoutPolicy{ ShowFailures: true, }, }, + loginPolicyProvider: &mockLoginPolicy{ + policy: &query.LoginPolicy{ + MultiFactorCheckLifetime: 10 * time.Hour, + }, + }, }, args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false}, []domain.NextStep{&domain.PasswordlessRegistrationPromptStep{}}, @@ -597,7 +596,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { 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}, []domain.NextStep{&domain.PasswordlessStep{}}, @@ -618,7 +621,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { 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}, []domain.NextStep{&domain.PasswordlessStep{PasswordSet: true}}, @@ -644,14 +651,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, - MultiFactorCheckLifeTime: 10 * time.Hour, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, }, args{&domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - PasswordlessType: domain.PasswordlessTypeAllowed, - MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN}, + PasswordlessType: domain.PasswordlessTypeAllowed, + MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN}, + MultiFactorCheckLifetime: 10 * time.Hour, }, }, false}, []domain.NextStep{&domain.VerifyEMailStep{}}, @@ -692,10 +699,19 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, - SecondFactorCheckLifeTime: 18 * time.Hour, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, + 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"}}, nil, }, @@ -715,23 +731,21 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userGrantProvider: &mockUserGrants{}, projectProvider: &mockProject{}, applicationProvider: &mockApp{app: &query.App{OIDCConfig: &query.OIDCApp{AppType: domain.OIDCApplicationTypeWeb}}}, - loginPolicyProvider: &mockLoginPolicy{ - policy: &query.LoginPolicy{}, - }, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &query.LockoutPolicy{ ShowFailures: true, }, }, - ExternalLoginCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ &domain.AuthRequest{ UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", Request: &domain.AuthRequestOIDC{}, - LoginPolicy: &domain.LoginPolicy{}, + LoginPolicy: &domain.LoginPolicy{ + ExternalLoginCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, + }, }, false}, []domain.NextStep{&domain.RedirectToCallbackStep{}}, @@ -751,7 +765,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { 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}, []domain.NextStep{&domain.PasswordStep{}}, @@ -779,15 +797,16 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - SecondFactorCheckLifeTime: 18 * time.Hour, - ExternalLoginCheckLifeTime: 10 * 24 * time.Hour, }, args{ &domain.AuthRequest{ UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", Request: &domain.AuthRequestOIDC{}, - LoginPolicy: &domain.LoginPolicy{}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactorCheckLifetime: 18 * time.Hour, + ExternalLoginCheckLifetime: 10 * 24 * time.Hour, + }, }, false}, []domain.NextStep{&domain.RedirectToCallbackStep{}}, nil, @@ -811,14 +830,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ &domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, false}, []domain.NextStep{&domain.MFAVerificationStep{ @@ -844,14 +863,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ &domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, false}, []domain.NextStep{&domain.MFAVerificationStep{ @@ -878,16 +897,16 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - ExternalLoginCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ &domain.AuthRequest{ UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", 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}, []domain.NextStep{&domain.MFAVerificationStep{ @@ -915,14 +934,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ &domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, false}, []domain.NextStep{&domain.ChangePasswordStep{}}, @@ -946,13 +965,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, false}, []domain.NextStep{&domain.VerifyEMailStep{}}, @@ -977,13 +996,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, false}, []domain.NextStep{&domain.ChangePasswordStep{}, &domain.VerifyEMailStep{}}, @@ -1011,14 +1030,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{ UserID: "UserID", Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, false}, []domain.NextStep{&domain.RedirectToCallbackStep{}}, @@ -1046,15 +1065,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{ UserID: "UserID", Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, true}, []domain.NextStep{&domain.RedirectToCallbackStep{}}, @@ -1082,15 +1101,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{ UserID: "UserID", Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, true}, []domain.NextStep{&domain.LoginSucceededStep{}, &domain.RedirectToCallbackStep{}}, @@ -1120,15 +1139,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{ UserID: "UserID", Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, true}, []domain.NextStep{&domain.GrantRequiredStep{}}, @@ -1159,15 +1178,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{ UserID: "UserID", Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, true}, []domain.NextStep{&domain.RedirectToCallbackStep{}}, @@ -1197,15 +1216,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{ UserID: "UserID", Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, true}, []domain.NextStep{&domain.ProjectRequiredStep{}}, @@ -1236,15 +1255,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - PasswordCheckLifeTime: 10 * 24 * time.Hour, - SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{ UserID: "UserID", Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + PasswordCheckLifetime: 10 * 24 * time.Hour, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, true}, []domain.NextStep{&domain.RedirectToCallbackStep{}}, @@ -1266,9 +1285,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, - SecondFactorCheckLifeTime: 18 * time.Hour, + loginPolicyProvider: &mockLoginPolicy{ + policy: &query.LoginPolicy{ + SecondFactorCheckLifetime: 18 * time.Hour, + }, + }, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, }, args{ &domain.AuthRequest{ @@ -1299,8 +1322,6 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowFailures: true, }, }, - SecondFactorCheckLifeTime: 18 * time.Hour, - PasswordCheckLifeTime: 10 * 24 * time.Hour, }, args{ &domain.AuthRequest{ @@ -1308,7 +1329,9 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SelectedIDPConfigID: "IDPConfigID", LinkingUsers: []*domain.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactorCheckLifetime: 18 * time.Hour, + PasswordCheckLifetime: 10 * 24 * time.Hour, }, }, false}, []domain.NextStep{&domain.LinkUsersStep{}}, @@ -1318,22 +1341,17 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { repo := &AuthRequestRepo{ - AuthRequests: tt.fields.AuthRequests, - View: tt.fields.View, - UserSessionViewProvider: tt.fields.userSessionViewProvider, - UserViewProvider: tt.fields.userViewProvider, - UserEventProvider: tt.fields.userEventProvider, - OrgViewProvider: tt.fields.orgViewProvider, - UserGrantProvider: tt.fields.userGrantProvider, - ProjectProvider: tt.fields.projectProvider, - ApplicationProvider: tt.fields.applicationProvider, - LoginPolicyViewProvider: tt.fields.loginPolicyProvider, - LockoutPolicyViewProvider: tt.fields.lockoutPolicyProvider, - PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime, - ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime, - MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime, - SecondFactorCheckLifeTime: tt.fields.SecondFactorCheckLifeTime, - MultiFactorCheckLifeTime: tt.fields.MultiFactorCheckLifeTime, + AuthRequests: tt.fields.AuthRequests, + View: tt.fields.View, + UserSessionViewProvider: tt.fields.userSessionViewProvider, + UserViewProvider: tt.fields.userViewProvider, + UserEventProvider: tt.fields.userEventProvider, + OrgViewProvider: tt.fields.orgViewProvider, + UserGrantProvider: tt.fields.userGrantProvider, + ProjectProvider: tt.fields.projectProvider, + ApplicationProvider: tt.fields.applicationProvider, + LoginPolicyViewProvider: tt.fields.loginPolicyProvider, + LockoutPolicyViewProvider: tt.fields.lockoutPolicyProvider, } got, err := repo.nextSteps(context.Background(), tt.args.request, tt.args.checkLoggedIn) 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) { - type fields struct { - MFAInitSkippedLifeTime time.Duration - SecondFactorCheckLifeTime time.Duration - MultiFactorCheckLifeTime time.Duration - } type args struct { userSession *user_model.UserSessionView request *domain.AuthRequest @@ -1358,7 +1371,6 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { } tests := []struct { name string - fields fields args args want domain.NextStep wantChecked bool @@ -1377,13 +1389,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { //}, { "not set up, forced by policy, no mfas configured, error", - fields{ - MFAInitSkippedLifeTime: 30 * 24 * time.Hour, - }, args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - ForceMFA: true, + ForceMFA: true, + MFAInitSkipLifetime: 30 * 24 * time.Hour, }, }, user: &user_model.UserView{ @@ -1398,12 +1408,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, { "not set up, no mfas configured, no prompt and true", - fields{ - MFAInitSkippedLifeTime: 30 * 24 * time.Hour, - }, args{ request: &domain.AuthRequest{ - LoginPolicy: &domain.LoginPolicy{}, + LoginPolicy: &domain.LoginPolicy{ + MFAInitSkipLifetime: 30 * 24 * time.Hour, + }, }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ @@ -1417,13 +1426,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, { "not set up, prompt and false", - fields{ - MFAInitSkippedLifeTime: 30 * 24 * time.Hour, - }, args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + MFAInitSkipLifetime: 30 * 24 * time.Hour, }, }, user: &user_model.UserView{ @@ -1442,14 +1449,12 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, { "not set up, forced by org, true", - fields{ - MFAInitSkippedLifeTime: 30 * 24 * time.Hour, - }, args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - ForceMFA: true, - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + ForceMFA: true, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + MFAInitSkipLifetime: 30 * 24 * time.Hour, }, }, user: &user_model.UserView{ @@ -1469,12 +1474,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, { "not set up and skipped, true", - fields{ - MFAInitSkippedLifeTime: 30 * 24 * time.Hour, - }, args{ request: &domain.AuthRequest{ - LoginPolicy: &domain.LoginPolicy{}, + LoginPolicy: &domain.LoginPolicy{ + MFAInitSkipLifetime: 30 * 24 * time.Hour, + }, }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ @@ -1489,13 +1493,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, { "checked second factor, true", - fields{ - SecondFactorCheckLifeTime: 18 * time.Hour, - }, args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, user: &user_model.UserView{ @@ -1512,13 +1514,11 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, { "not checked, check and false", - fields{ - SecondFactorCheckLifeTime: 18 * time.Hour, - }, args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactorCheckLifetime: 18 * time.Hour, }, }, user: &user_model.UserView{ @@ -1539,11 +1539,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - repo := &AuthRequestRepo{ - MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime, - SecondFactorCheckLifeTime: tt.fields.SecondFactorCheckLifeTime, - MultiFactorCheckLifeTime: tt.fields.MultiFactorCheckLifeTime, - } + repo := &AuthRequestRepo{} 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) { t.Errorf("got wrong err: %v ", err) @@ -1562,7 +1558,8 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) { MFAInitSkippedLifeTime time.Duration } type args struct { - user *user_model.UserView + user *user_model.UserView + request *domain.AuthRequest } tests := []struct { name string @@ -1574,51 +1571,58 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) { "mfa set up, true", fields{}, args{ - &user_model.UserView{ + user: &user_model.UserView{ HumanView: &user_model.HumanView{ MFAMaxSetUp: model.MFALevelSecondFactor, }, }, + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{}, + }, }, true, }, { "mfa skipped active, true", - fields{ - MFAInitSkippedLifeTime: 30 * 24 * time.Hour, - }, + fields{}, args{ - &user_model.UserView{ + user: &user_model.UserView{ HumanView: &user_model.HumanView{ MFAMaxSetUp: -1, MFAInitSkipped: time.Now().UTC().Add(-10 * time.Hour), }, }, + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{ + MFAInitSkipLifetime: 30 * 24 * time.Hour, + }, + }, }, true, }, { "mfa skipped inactive, false", - fields{ - MFAInitSkippedLifeTime: 30 * 24 * time.Hour, - }, + fields{}, args{ - &user_model.UserView{ + user: &user_model.UserView{ HumanView: &user_model.HumanView{ MFAMaxSetUp: -1, MFAInitSkipped: time.Now().UTC().Add(-40 * 24 * time.Hour), }, }, + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{ + MFAInitSkipLifetime: 30 * 24 * time.Hour, + }, + }, }, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - repo := &AuthRequestRepo{ - MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime, - } - if got := repo.mfaSkippedOrSetUp(tt.args.user); got != tt.want { + repo := &AuthRequestRepo{} + if got := repo.mfaSkippedOrSetUp(tt.args.user, tt.args.request); got != tt.want { t.Errorf("mfaSkippedOrSetUp() = %v, want %v", got, tt.want) } }) diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index d3c635d9b3..3ebb4d1b98 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -72,31 +72,26 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma es, userRepo, eventstore.AuthRequestRepo{ - PrivacyPolicyProvider: queries, - LabelPolicyProvider: queries, - Command: command, - Query: queries, - OrgViewProvider: queries, - AuthRequests: authReq, - View: view, - Eventstore: es, - UserCodeAlg: userCrypto, - UserSessionViewProvider: view, - UserViewProvider: view, - UserCommandProvider: command, - UserEventProvider: &userRepo, - IDPProviderViewProvider: view, - LockoutPolicyViewProvider: queries, - LoginPolicyViewProvider: queries, - UserGrantProvider: queryView, - ProjectProvider: queryView, - ApplicationProvider: queries, - IdGenerator: idGenerator, - PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck, - ExternalLoginCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck, - MFAInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MFAInitSkip, - SecondFactorCheckLifeTime: systemDefaults.VerificationLifetimes.SecondFactorCheck, - MultiFactorCheckLifeTime: systemDefaults.VerificationLifetimes.MultiFactorCheck, + PrivacyPolicyProvider: queries, + LabelPolicyProvider: queries, + Command: command, + Query: queries, + OrgViewProvider: queries, + AuthRequests: authReq, + View: view, + Eventstore: es, + UserCodeAlg: userCrypto, + UserSessionViewProvider: view, + UserViewProvider: view, + UserCommandProvider: command, + UserEventProvider: &userRepo, + IDPProviderViewProvider: view, + LockoutPolicyViewProvider: queries, + LoginPolicyViewProvider: queries, + UserGrantProvider: queryView, + ProjectProvider: queryView, + ApplicationProvider: queries, + IdGenerator: idGenerator, }, eventstore.TokenRepo{ View: view, diff --git a/internal/command/iam_converter.go b/internal/command/iam_converter.go index 9d03c7bc2b..e9bab1285e 100644 --- a/internal/command/iam_converter.go +++ b/internal/command/iam_converter.go @@ -35,13 +35,18 @@ func memberWriteModelToMember(writeModel *MemberWriteModel) *domain.Member { func writeModelToLoginPolicy(wm *LoginPolicyWriteModel) *domain.LoginPolicy { return &domain.LoginPolicy{ - ObjectRoot: writeModelToObjectRoot(wm.WriteModel), - AllowUsernamePassword: wm.AllowUserNamePassword, - AllowRegister: wm.AllowRegister, - AllowExternalIDP: wm.AllowExternalIDP, - HidePasswordReset: wm.HidePasswordReset, - ForceMFA: wm.ForceMFA, - PasswordlessType: wm.PasswordlessType, + ObjectRoot: writeModelToObjectRoot(wm.WriteModel), + AllowUsernamePassword: wm.AllowUserNamePassword, + AllowRegister: wm.AllowRegister, + AllowExternalIDP: wm.AllowExternalIDP, + HidePasswordReset: wm.HidePasswordReset, + ForceMFA: wm.ForceMFA, + PasswordlessType: wm.PasswordlessType, + PasswordCheckLifetime: wm.PasswordCheckLifetime, + ExternalLoginCheckLifetime: wm.ExternalLoginCheckLifetime, + MFAInitSkipLifetime: wm.MFAInitSkipLifetime, + SecondFactorCheckLifetime: wm.SecondFactorCheckLifetime, + MultiFactorCheckLifetime: wm.MultiFactorCheckLifetime, } } diff --git a/internal/command/iam_policy_login.go b/internal/command/iam_policy_login.go index 97f0f1442b..add92ef0f1 100644 --- a/internal/command/iam_policy_login.go +++ b/internal/command/iam_policy_login.go @@ -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 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) { @@ -78,7 +90,19 @@ func (c *Commands) changeDefaultLoginPolicy(ctx context.Context, iamAgg *eventst if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { 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 { return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged") } diff --git a/internal/command/iam_policy_login_model.go b/internal/command/iam_policy_login_model.go index aebb0f11a5..ac32f7fbc3 100644 --- a/internal/command/iam_policy_login_model.go +++ b/internal/command/iam_policy_login_model.go @@ -2,6 +2,7 @@ package command import ( "context" + "time" "github.com/caos/zitadel/internal/eventstore" @@ -65,6 +66,11 @@ func (wm *IAMLoginPolicyWriteModel) NewChangedEvent( forceMFA, hidePasswordReset bool, passwordlessType domain.PasswordlessType, + passwordCheckLifetime, + externalLoginCheckLifetime, + mfaInitSkipLifetime, + secondFactorCheckLifetime, + multiFactorCheckLifetime time.Duration, ) (*iam.LoginPolicyChangedEvent, bool) { changes := make([]policy.LoginPolicyChanges, 0) @@ -86,6 +92,21 @@ func (wm *IAMLoginPolicyWriteModel) NewChangedEvent( if wm.HidePasswordReset != 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 { return nil, false } diff --git a/internal/command/iam_policy_login_test.go b/internal/command/iam_policy_login_test.go index 4a5511065c..5be9c8c626 100644 --- a/internal/command/iam_policy_login_test.go +++ b/internal/command/iam_policy_login_test.go @@ -3,6 +3,7 @@ package command import ( "context" "testing" + "time" "github.com/caos/zitadel/internal/domain" caos_errs "github.com/caos/zitadel/internal/errors" @@ -49,6 +50,11 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) { false, false, 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, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), }, @@ -92,12 +103,17 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) { args: args{ ctx: context.Background(), policy: &domain.LoginPolicy{ - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDP: true, - ForceMFA: true, - HidePasswordReset: true, - PasswordlessType: domain.PasswordlessTypeAllowed, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + HidePasswordReset: true, + 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{ @@ -106,12 +122,17 @@ func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) { AggregateID: "IAM", ResourceOwner: "IAM", }, - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDP: true, - ForceMFA: true, - HidePasswordReset: true, - PasswordlessType: domain.PasswordlessTypeAllowed, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + HidePasswordReset: true, + 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, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), @@ -195,12 +221,17 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) { args: args{ ctx: context.Background(), policy: &domain.LoginPolicy{ - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDP: true, - ForceMFA: true, - HidePasswordReset: true, - PasswordlessType: domain.PasswordlessTypeAllowed, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + HidePasswordReset: true, + 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{ @@ -222,13 +253,29 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) { true, true, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), expectPush( []*repository.Event{ 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), ), }, ), @@ -237,12 +284,17 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) { args: args{ ctx: context.Background(), policy: &domain.LoginPolicy{ - AllowRegister: false, - AllowUsernamePassword: false, - AllowExternalIDP: false, - ForceMFA: false, - HidePasswordReset: false, - PasswordlessType: domain.PasswordlessTypeNotAllowed, + AllowRegister: false, + AllowUsernamePassword: false, + AllowExternalIDP: false, + ForceMFA: false, + HidePasswordReset: false, + 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{ @@ -251,12 +303,17 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) { AggregateID: "IAM", ResourceOwner: "IAM", }, - AllowRegister: false, - AllowUsernamePassword: false, - AllowExternalIDP: false, - ForceMFA: false, - HidePasswordReset: false, - PasswordlessType: domain.PasswordlessTypeNotAllowed, + AllowRegister: false, + AllowUsernamePassword: false, + AllowExternalIDP: false, + ForceMFA: false, + HidePasswordReset: false, + 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, 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, 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, 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, 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, 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, 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, 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, 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, &iam.NewAggregate().Aggregate, []policy.LoginPolicyChanges{ @@ -1293,6 +1392,11 @@ func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allow policy.ChangeAllowUserNamePassword(allowUsernamePassword), policy.ChangeHidePasswordReset(hidePasswordReset), policy.ChangePasswordlessType(passwordlessType), + policy.ChangePasswordCheckLifetime(passwordLifetime), + policy.ChangeExternalLoginCheckLifetime(externalLoginLifetime), + policy.ChangeMFAInitSkipLifetime(mfaInitSkipLifetime), + policy.ChangeSecondFactorCheckLifetime(secondFactorLifetime), + policy.ChangeMultiFactorCheckLifetime(multiFactorLifetime), }, ) return event diff --git a/internal/command/org_features.go b/internal/command/org_features.go index 9dc00b0a49..78f0252f30 100644 --- a/internal/command/org_features.go +++ b/internal/command/org_features.go @@ -241,7 +241,19 @@ func (c *Commands) setAllowedLoginPolicy(ctx context.Context, orgID string, feat if !features.LoginPolicyPasswordReset && defaultPolicy.HidePasswordReset != existingPolicy.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 { events = append(events, changedEvent) } diff --git a/internal/command/org_features_test.go b/internal/command/org_features_test.go index 1d0352998e..917fdfde52 100644 --- a/internal/command/org_features_test.go +++ b/internal/command/org_features_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/caos/zitadel/internal/repository/user" + "github.com/caos/zitadel/internal/static/mock" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "golang.org/x/text/language" @@ -16,9 +18,7 @@ import ( "github.com/caos/zitadel/internal/repository/features" "github.com/caos/zitadel/internal/repository/iam" "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/mock" ) func TestCommandSide_SetOrgFeatures(t *testing.T) { @@ -165,6 +165,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { false, false, 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, 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, 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, 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, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), eventFromEventPusher( @@ -1002,6 +1027,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { false, false, 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, 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), ), 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( org.NewPasswordComplexityPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate), @@ -1278,6 +1319,11 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { false, false, 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, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), diff --git a/internal/command/org_policy_login.go b/internal/command/org_policy_login.go index 02dd6edd71..e2be5f94cb 100644 --- a/internal/command/org_policy_login.go +++ b/internal/command/org_policy_login.go @@ -42,7 +42,12 @@ func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, pol policy.AllowExternalIDP, policy.ForceMFA, policy.HidePasswordReset, - policy.PasswordlessType)) + policy.PasswordlessType, + policy.PasswordCheckLifetime, + policy.ExternalLoginCheckLifetime, + policy.MFAInitSkipLifetime, + policy.SecondFactorCheckLifetime, + policy.MultiFactorCheckLifetime)) if err != nil { return nil, err } @@ -100,7 +105,12 @@ func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string, policy.AllowExternalIDP, policy.ForceMFA, policy.HidePasswordReset, - policy.PasswordlessType) + policy.PasswordlessType, + policy.PasswordCheckLifetime, + policy.ExternalLoginCheckLifetime, + policy.MFAInitSkipLifetime, + policy.SecondFactorCheckLifetime, + policy.MultiFactorCheckLifetime) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged") diff --git a/internal/command/org_policy_login_model.go b/internal/command/org_policy_login_model.go index 12b8734829..8cfa5df15e 100644 --- a/internal/command/org_policy_login_model.go +++ b/internal/command/org_policy_login_model.go @@ -2,6 +2,7 @@ package command import ( "context" + "time" "github.com/caos/zitadel/internal/eventstore" @@ -68,6 +69,11 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent( forceMFA, hidePasswordReset bool, passwordlessType domain.PasswordlessType, + passwordCheckLifetime, + externalLoginCheckLifetime, + mfaInitSkipLifetime, + secondFactorCheckLifetime, + multiFactorCheckLifetime time.Duration, ) (*org.LoginPolicyChangedEvent, bool) { changes := make([]policy.LoginPolicyChanges, 0) @@ -86,6 +92,21 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent( if wm.HidePasswordReset != 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 { changes = append(changes, policy.ChangePasswordlessType(passwordlessType)) } diff --git a/internal/command/org_policy_login_test.go b/internal/command/org_policy_login_test.go index e01af8d3dd..f327d72652 100644 --- a/internal/command/org_policy_login_test.go +++ b/internal/command/org_policy_login_test.go @@ -3,6 +3,7 @@ package command import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" @@ -17,6 +18,14 @@ import ( "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) { type fields struct { eventstore *eventstore.Eventstore @@ -71,6 +80,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { true, true, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), @@ -80,11 +94,16 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.LoginPolicy{ - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDP: true, - ForceMFA: true, - PasswordlessType: domain.PasswordlessTypeAllowed, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + 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{ @@ -107,6 +126,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { true, true, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), @@ -117,11 +141,16 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.LoginPolicy{ - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDP: true, - ForceMFA: true, - PasswordlessType: domain.PasswordlessTypeAllowed, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + 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{ @@ -144,6 +173,11 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { true, true, 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, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), }, @@ -169,12 +208,17 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.LoginPolicy{ - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDP: true, - ForceMFA: true, - HidePasswordReset: true, - PasswordlessType: domain.PasswordlessTypeAllowed, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + HidePasswordReset: true, + 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{ @@ -183,12 +227,17 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { AggregateID: "org1", ResourceOwner: "org1", }, - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDP: true, - ForceMFA: true, - HidePasswordReset: true, - PasswordlessType: domain.PasswordlessTypeAllowed, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + HidePasswordReset: true, + 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, 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, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), @@ -315,11 +374,16 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.LoginPolicy{ - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDP: true, - ForceMFA: true, - PasswordlessType: domain.PasswordlessTypeAllowed, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + 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{ @@ -341,6 +405,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) { true, true, 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, domain.PasswordlessTypeAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), @@ -364,12 +438,17 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.LoginPolicy{ - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDP: true, - ForceMFA: true, - HidePasswordReset: true, - PasswordlessType: domain.PasswordlessTypeAllowed, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDP: true, + ForceMFA: true, + HidePasswordReset: true, + 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{ @@ -391,6 +470,11 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) { true, true, 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, domain.PasswordlessTypeNotAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), expectPush( []*repository.Event{ 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, + ), ), }, ), @@ -421,11 +523,16 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.LoginPolicy{ - AllowRegister: false, - AllowUsernamePassword: false, - AllowExternalIDP: false, - ForceMFA: false, - PasswordlessType: domain.PasswordlessTypeNotAllowed, + AllowRegister: false, + AllowUsernamePassword: false, + AllowExternalIDP: false, + ForceMFA: false, + 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{ @@ -434,12 +541,17 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) { AggregateID: "org1", ResourceOwner: "org1", }, - AllowRegister: false, - AllowUsernamePassword: false, - AllowExternalIDP: false, - ForceMFA: false, - HidePasswordReset: false, - PasswordlessType: domain.PasswordlessTypeNotAllowed, + AllowRegister: false, + AllowUsernamePassword: false, + AllowExternalIDP: false, + ForceMFA: false, + HidePasswordReset: false, + 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, + passwordlessType domain.PasswordlessType, + passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime *time.Duration) *org.LoginPolicyChangedEvent { + changes := []policy.LoginPolicyChanges{ + policy.ChangeAllowUserNamePassword(usernamePassword), + policy.ChangeAllowRegister(register), + policy.ChangeAllowExternalIDP(externalIDP), + policy.ChangeForceMFA(mfa), + policy.ChangeHidePasswordReset(passwordReset), + 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, - []policy.LoginPolicyChanges{ - policy.ChangeAllowUserNamePassword(usernamePassword), - policy.ChangeAllowRegister(register), - policy.ChangeAllowExternalIDP(externalIDP), - policy.ChangeForceMFA(mfa), - policy.ChangeHidePasswordReset(passwordReset), - policy.ChangePasswordlessType(passwordlessType), - }, + changes, ) return event } diff --git a/internal/command/policy_login_model.go b/internal/command/policy_login_model.go index 3b3a8bfcfe..ed3a2faefb 100644 --- a/internal/command/policy_login_model.go +++ b/internal/command/policy_login_model.go @@ -1,6 +1,8 @@ package command import ( + "time" + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/repository/policy" @@ -9,13 +11,18 @@ import ( type LoginPolicyWriteModel struct { eventstore.WriteModel - AllowUserNamePassword bool - AllowRegister bool - AllowExternalIDP bool - ForceMFA bool - HidePasswordReset bool - PasswordlessType domain.PasswordlessType - State domain.PolicyState + AllowUserNamePassword bool + AllowRegister bool + AllowExternalIDP bool + ForceMFA bool + HidePasswordReset bool + PasswordlessType domain.PasswordlessType + PasswordCheckLifetime time.Duration + ExternalLoginCheckLifetime time.Duration + MFAInitSkipLifetime time.Duration + SecondFactorCheckLifetime time.Duration + MultiFactorCheckLifetime time.Duration + State domain.PolicyState } func (wm *LoginPolicyWriteModel) Reduce() error { @@ -28,6 +35,11 @@ func (wm *LoginPolicyWriteModel) Reduce() error { wm.ForceMFA = e.ForceMFA wm.PasswordlessType = e.PasswordlessType 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 case *policy.LoginPolicyChangedEvent: if e.AllowRegister != nil { @@ -48,6 +60,21 @@ func (wm *LoginPolicyWriteModel) Reduce() error { if e.PasswordlessType != nil { 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: wm.State = domain.PolicyStateRemoved } diff --git a/internal/command/user_human_password_test.go b/internal/command/user_human_password_test.go index fa95a9c3ec..41b75e935c 100644 --- a/internal/command/user_human_password_test.go +++ b/internal/command/user_human_password_test.go @@ -1157,6 +1157,11 @@ func TestCommandSide_CheckPassword(t *testing.T) { false, false, 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, 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, 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, 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, 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, domain.PasswordlessTypeNotAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), diff --git a/internal/command/user_human_test.go b/internal/command/user_human_test.go index f84a0c8a3a..2d1ac193e2 100644 --- a/internal/command/user_human_test.go +++ b/internal/command/user_human_test.go @@ -1607,6 +1607,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) { false, false, 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, 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, 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, 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, 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, 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, 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, domain.PasswordlessTypeNotAllowed, + time.Hour*1, + time.Hour*2, + time.Hour*3, + time.Hour*4, + time.Hour*5, ), ), ), diff --git a/internal/config/systemdefaults/system_defaults.go b/internal/config/systemdefaults/system_defaults.go index d6ba150f07..5e598f4901 100644 --- a/internal/config/systemdefaults/system_defaults.go +++ b/internal/config/systemdefaults/system_defaults.go @@ -23,7 +23,6 @@ type SystemDefaults struct { SMTPPasswordVerificationKey *crypto.KeyConfig SMSVerificationKey *crypto.KeyConfig Multifactors MultifactorConfig - VerificationLifetimes VerificationLifetimes DomainVerification DomainVerification Notifications Notifications KeyConfig KeyConfig @@ -49,14 +48,6 @@ type OTPConfig struct { VerificationKey *crypto.KeyConfig } -type VerificationLifetimes struct { - PasswordCheck time.Duration - ExternalLoginCheck time.Duration - MFAInitSkip time.Duration - SecondFactorCheck time.Duration - MultiFactorCheck time.Duration -} - type DomainVerification struct { VerificationKey *crypto.KeyConfig VerificationGenerator crypto.GeneratorConfig diff --git a/internal/domain/policy_login.go b/internal/domain/policy_login.go index 0ad43afb0d..db2ad78e7e 100644 --- a/internal/domain/policy_login.go +++ b/internal/domain/policy_login.go @@ -1,20 +1,29 @@ package domain -import "github.com/caos/zitadel/internal/eventstore/v1/models" +import ( + "time" + + "github.com/caos/zitadel/internal/eventstore/v1/models" +) type LoginPolicy struct { models.ObjectRoot - Default bool - AllowUsernamePassword bool - AllowRegister bool - AllowExternalIDP bool - IDPProviders []*IDPProvider - ForceMFA bool - SecondFactors []SecondFactorType - MultiFactors []MultiFactorType - PasswordlessType PasswordlessType - HidePasswordReset bool + Default bool + AllowUsernamePassword bool + AllowRegister bool + AllowExternalIDP bool + IDPProviders []*IDPProvider + ForceMFA bool + SecondFactors []SecondFactorType + MultiFactors []MultiFactorType + PasswordlessType PasswordlessType + HidePasswordReset bool + PasswordCheckLifetime time.Duration + ExternalLoginCheckLifetime time.Duration + MFAInitSkipLifetime time.Duration + SecondFactorCheckLifetime time.Duration + MultiFactorCheckLifetime time.Duration } type IDPProvider struct { diff --git a/internal/query/login_policy.go b/internal/query/login_policy.go index 75d78dc580..55ec82d36f 100644 --- a/internal/query/login_policy.go +++ b/internal/query/login_policy.go @@ -14,19 +14,24 @@ import ( ) type LoginPolicy struct { - OrgID string - CreationDate time.Time - ChangeDate time.Time - Sequence uint64 - AllowRegister bool - AllowUsernamePassword bool - AllowExternalIDPs bool - ForceMFA bool - SecondFactors []domain.SecondFactorType - MultiFactors []domain.MultiFactorType - PasswordlessType domain.PasswordlessType - IsDefault bool - HidePasswordReset bool + OrgID string + CreationDate time.Time + ChangeDate time.Time + Sequence uint64 + AllowRegister bool + AllowUsernamePassword bool + AllowExternalIDPs bool + ForceMFA bool + SecondFactors []domain.SecondFactorType + MultiFactors []domain.MultiFactorType + PasswordlessType domain.PasswordlessType + IsDefault bool + HidePasswordReset bool + PasswordCheckLifetime time.Duration + ExternalLoginCheckLifetime time.Duration + MFAInitSkipLifetime time.Duration + SecondFactorCheckLifetime time.Duration + MultiFactorCheckLifetime time.Duration } type SecondFactors struct { @@ -95,6 +100,26 @@ var ( name: projection.LoginPolicyHidePWResetCol, 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) { @@ -234,6 +259,11 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy, LoginPolicyColumnPasswordlessType.identifier(), LoginPolicyColumnIsDefault.identifier(), LoginPolicyColumnHidePasswordReset.identifier(), + LoginPolicyColumnPasswordCheckLifetime.identifier(), + LoginPolicyColumnExternalLoginCheckLifetime.identifier(), + LoginPolicyColumnMFAInitSkipLifetime.identifier(), + LoginPolicyColumnSecondFactorCheckLifetime.identifier(), + LoginPolicyColumnMultiFacotrCheckLifetime.identifier(), ).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*LoginPolicy, error) { p := new(LoginPolicy) @@ -253,6 +283,11 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy, &p.PasswordlessType, &p.IsDefault, &p.HidePasswordReset, + &p.PasswordCheckLifetime, + &p.ExternalLoginCheckLifetime, + &p.MFAInitSkipLifetime, + &p.SecondFactorCheckLifetime, + &p.MultiFactorCheckLifetime, ) if err != nil { if errs.Is(err, sql.ErrNoRows) { diff --git a/internal/query/login_policy_test.go b/internal/query/login_policy_test.go index 24c76abfb0..ade38bdc17 100644 --- a/internal/query/login_policy_test.go +++ b/internal/query/login_policy_test.go @@ -7,6 +7,7 @@ import ( "fmt" "regexp" "testing" + "time" "github.com/caos/zitadel/internal/domain" 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.passwordless_type,`+ ` 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`), nil, nil, @@ -72,7 +78,12 @@ func Test_LoginPolicyPrepares(t *testing.T) { ` zitadel.projections.login_policies.multi_factors,`+ ` zitadel.projections.login_policies.passwordless_type,`+ ` 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`), []string{ "aggregate_id", @@ -88,6 +99,11 @@ func Test_LoginPolicyPrepares(t *testing.T) { "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", }, []driver.Value{ "ro", @@ -103,23 +119,33 @@ func Test_LoginPolicyPrepares(t *testing.T) { domain.PasswordlessTypeAllowed, true, true, + time.Hour * 2, + time.Hour * 2, + time.Hour * 2, + time.Hour * 2, + time.Hour * 2, }, ), }, object: &LoginPolicy{ - OrgID: "ro", - CreationDate: testNow, - ChangeDate: testNow, - Sequence: 20211109, - AllowRegister: true, - AllowUsernamePassword: true, - AllowExternalIDPs: true, - ForceMFA: true, - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, - MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN}, - PasswordlessType: domain.PasswordlessTypeAllowed, - IsDefault: true, - HidePasswordReset: true, + OrgID: "ro", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211109, + AllowRegister: true, + AllowUsernamePassword: true, + AllowExternalIDPs: true, + ForceMFA: true, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN}, + PasswordlessType: domain.PasswordlessTypeAllowed, + IsDefault: 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.passwordless_type,`+ ` 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`), sql.ErrConnDone, ), diff --git a/internal/query/projection/login_policy.go b/internal/query/projection/login_policy.go index 19acde3a64..5f2d8fa5a7 100644 --- a/internal/query/projection/login_policy.go +++ b/internal/query/projection/login_policy.go @@ -111,6 +111,11 @@ const ( LoginPolicyPasswordlessTypeCol = "passwordless_type" LoginPolicyIsDefaultCol = "is_default" 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) { @@ -140,6 +145,11 @@ func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) ( handler.NewCol(LoginPolicyPasswordlessTypeCol, policyEvent.PasswordlessType), handler.NewCol(LoginPolicyIsDefaultCol, isDefault), 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 } @@ -177,6 +187,22 @@ func (p *LoginPolicyProjection) reduceLoginPolicyChanged(event eventstore.Event) if policyEvent.HidePasswordReset != nil { 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( &policyEvent, cols, diff --git a/internal/query/projection/login_policy_test.go b/internal/query/projection/login_policy_test.go index e9ff4633a7..928766521a 100644 --- a/internal/query/projection/login_policy_test.go +++ b/internal/query/projection/login_policy_test.go @@ -2,6 +2,7 @@ package projection import ( "testing" + "time" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" @@ -29,13 +30,18 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { repository.EventType(org.LoginPolicyAddedEventType), org.AggregateType, []byte(`{ - "allowUsernamePassword": true, - "allowRegister": true, - "allowExternalIdp": false, - "forceMFA": false, - "hidePasswordReset": true, - "passwordlessType": 1 -}`), + "allowUsernamePassword": true, + "allowRegister": true, + "allowExternalIdp": false, + "forceMFA": false, + "hidePasswordReset": true, + "passwordlessType": 1, + "passwordCheckLifetime": 10000000, + "externalLoginCheckLifetime": 10000000, + "mfaInitSkipLifetime": 10000000, + "secondFactorCheckLifetime": 10000000, + "multiFactorCheckLifetime": 10000000 + }`), ), org.LoginPolicyAddedEventMapper), }, reduce: (&LoginPolicyProjection{}).reduceLoginPolicyAdded, @@ -47,7 +53,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ 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{}{ "agg-id", anyArg{}, @@ -60,6 +66,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { domain.PasswordlessTypeAllowed, false, true, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, }, }, }, @@ -74,13 +85,18 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { repository.EventType(org.LoginPolicyChangedEventType), org.AggregateType, []byte(`{ - "allowUsernamePassword": true, - "allowRegister": true, - "allowExternalIdp": true, - "forceMFA": true, - "hidePasswordReset": true, - "passwordlessType": 1 -}`), + "allowUsernamePassword": true, + "allowRegister": true, + "allowExternalIdp": true, + "forceMFA": true, + "hidePasswordReset": true, + "passwordlessType": 1, + "passwordCheckLifetime": 10000000, + "externalLoginCheckLifetime": 10000000, + "mfaInitSkipLifetime": 10000000, + "secondFactorCheckLifetime": 10000000, + "multiFactorCheckLifetime": 10000000 + }`), ), org.LoginPolicyChangedEventMapper), }, want: wantReduce{ @@ -91,7 +107,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ 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{}{ anyArg{}, uint64(15), @@ -101,6 +117,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { true, domain.PasswordlessTypeAllowed, true, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, "agg-id", }, }, @@ -271,12 +292,17 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { repository.EventType(iam.LoginPolicyAddedEventType), iam.AggregateType, []byte(`{ - "allowUsernamePassword": true, - "allowRegister": true, - "allowExternalIdp": false, - "forceMFA": false, - "hidePasswordReset": true, - "passwordlessType": 1 + "allowUsernamePassword": true, + "allowRegister": true, + "allowExternalIdp": false, + "forceMFA": false, + "hidePasswordReset": true, + "passwordlessType": 1, + "passwordCheckLifetime": 10000000, + "externalLoginCheckLifetime": 10000000, + "mfaInitSkipLifetime": 10000000, + "secondFactorCheckLifetime": 10000000, + "multiFactorCheckLifetime": 10000000 }`), ), iam.LoginPolicyAddedEventMapper), }, @@ -288,7 +314,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ 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{}{ "agg-id", anyArg{}, @@ -301,6 +327,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { domain.PasswordlessTypeAllowed, true, true, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, }, }, }, diff --git a/internal/repository/iam/policy_login.go b/internal/repository/iam/policy_login.go index 330edf48cc..22c0def52d 100644 --- a/internal/repository/iam/policy_login.go +++ b/internal/repository/iam/policy_login.go @@ -2,6 +2,7 @@ package iam import ( "context" + "time" "github.com/caos/zitadel/internal/eventstore" @@ -28,6 +29,11 @@ func NewLoginPolicyAddedEvent( forceMFA, hidePasswordReset bool, passwordlessType domain.PasswordlessType, + passwordCheckLifetime, + externalLoginCheckLifetime, + mfaInitSkipLifetime, + secondFactorCheckLifetime, + multiFactorCheckLifetime time.Duration, ) *LoginPolicyAddedEvent { return &LoginPolicyAddedEvent{ LoginPolicyAddedEvent: *policy.NewLoginPolicyAddedEvent( @@ -40,7 +46,12 @@ func NewLoginPolicyAddedEvent( allowExternalIDP, forceMFA, hidePasswordReset, - passwordlessType), + passwordlessType, + passwordCheckLifetime, + externalLoginCheckLifetime, + mfaInitSkipLifetime, + secondFactorCheckLifetime, + multiFactorCheckLifetime), } } diff --git a/internal/repository/org/policy_login.go b/internal/repository/org/policy_login.go index 9b6fa0f9ac..dd915fd1b7 100644 --- a/internal/repository/org/policy_login.go +++ b/internal/repository/org/policy_login.go @@ -2,6 +2,7 @@ package org import ( "context" + "time" "github.com/caos/zitadel/internal/eventstore" @@ -29,6 +30,11 @@ func NewLoginPolicyAddedEvent( forceMFA, hidePasswordReset bool, passwordlessType domain.PasswordlessType, + passwordCheckLifetime, + externalLoginCheckLifetime, + mfaInitSkipLifetime, + secondFactorCheckLifetime, + multiFactorCheckLifetime time.Duration, ) *LoginPolicyAddedEvent { return &LoginPolicyAddedEvent{ LoginPolicyAddedEvent: *policy.NewLoginPolicyAddedEvent( @@ -41,7 +47,12 @@ func NewLoginPolicyAddedEvent( allowExternalIDP, forceMFA, hidePasswordReset, - passwordlessType), + passwordlessType, + passwordCheckLifetime, + externalLoginCheckLifetime, + mfaInitSkipLifetime, + secondFactorCheckLifetime, + multiFactorCheckLifetime), } } diff --git a/internal/repository/policy/login.go b/internal/repository/policy/login.go index d089540b18..4161c7ad9e 100644 --- a/internal/repository/policy/login.go +++ b/internal/repository/policy/login.go @@ -2,6 +2,7 @@ package policy import ( "encoding/json" + "time" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" @@ -19,12 +20,17 @@ const ( type LoginPolicyAddedEvent struct { eventstore.BaseEvent `json:"-"` - AllowUserNamePassword bool `json:"allowUsernamePassword,omitempty"` - AllowRegister bool `json:"allowRegister,omitempty"` - AllowExternalIDP bool `json:"allowExternalIdp,omitempty"` - ForceMFA bool `json:"forceMFA,omitempty"` - HidePasswordReset bool `json:"hidePasswordReset,omitempty"` - PasswordlessType domain.PasswordlessType `json:"passwordlessType,omitempty"` + AllowUserNamePassword bool `json:"allowUsernamePassword,omitempty"` + AllowRegister bool `json:"allowRegister,omitempty"` + AllowExternalIDP bool `json:"allowExternalIdp,omitempty"` + ForceMFA bool `json:"forceMFA,omitempty"` + HidePasswordReset bool `json:"hidePasswordReset,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{} { @@ -43,15 +49,25 @@ func NewLoginPolicyAddedEvent( forceMFA, hidePasswordReset bool, passwordlessType domain.PasswordlessType, + passwordCheckLifetime, + externalLoginCheckLifetime, + mfaInitSkipLifetime, + secondFactorCheckLifetime, + multiFactorCheckLifetime time.Duration, ) *LoginPolicyAddedEvent { return &LoginPolicyAddedEvent{ - BaseEvent: *base, - AllowExternalIDP: allowExternalIDP, - AllowRegister: allowRegister, - AllowUserNamePassword: allowUserNamePassword, - ForceMFA: forceMFA, - PasswordlessType: passwordlessType, - HidePasswordReset: hidePasswordReset, + BaseEvent: *base, + AllowExternalIDP: allowExternalIDP, + AllowRegister: allowRegister, + AllowUserNamePassword: allowUserNamePassword, + ForceMFA: forceMFA, + PasswordlessType: passwordlessType, + HidePasswordReset: hidePasswordReset, + PasswordCheckLifetime: passwordCheckLifetime, + ExternalLoginCheckLifetime: externalLoginCheckLifetime, + MFAInitSkipLifetime: mfaInitSkipLifetime, + SecondFactorCheckLifetime: secondFactorCheckLifetime, + MultiFactorCheckLifetime: multiFactorCheckLifetime, } } @@ -71,12 +87,17 @@ func LoginPolicyAddedEventMapper(event *repository.Event) (eventstore.Event, err type LoginPolicyChangedEvent struct { eventstore.BaseEvent `json:"-"` - AllowUserNamePassword *bool `json:"allowUsernamePassword,omitempty"` - AllowRegister *bool `json:"allowRegister,omitempty"` - AllowExternalIDP *bool `json:"allowExternalIdp,omitempty"` - ForceMFA *bool `json:"forceMFA,omitempty"` - HidePasswordReset *bool `json:"hidePasswordReset,omitempty"` - PasswordlessType *domain.PasswordlessType `json:"passwordlessType,omitempty"` + AllowUserNamePassword *bool `json:"allowUsernamePassword,omitempty"` + AllowRegister *bool `json:"allowRegister,omitempty"` + AllowExternalIDP *bool `json:"allowExternalIdp,omitempty"` + ForceMFA *bool `json:"forceMFA,omitempty"` + HidePasswordReset *bool `json:"hidePasswordReset,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{} { @@ -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) { e := &LoginPolicyChangedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 0e3dc4cd36..1a5a3d9024 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -3459,6 +3459,11 @@ message UpdateLoginPolicyRequest { 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 { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 0e83dc66d6..3be4a3ed61 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -4397,6 +4397,11 @@ message AddCustomLoginPolicyRequest { bool force_mfa = 4; zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}]; 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 { @@ -4410,6 +4415,11 @@ message UpdateCustomLoginPolicyRequest { bool force_mfa = 4; zitadel.policy.v1.PasswordlessType passwordless_type = 5 [(validate.rules).enum = {defined_only: true}]; 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 { diff --git a/proto/zitadel/policy.proto b/proto/zitadel/policy.proto index 227214c763..b4f9f75ff5 100644 --- a/proto/zitadel/policy.proto +++ b/proto/zitadel/policy.proto @@ -1,6 +1,7 @@ syntax = "proto3"; import "zitadel/object.proto"; +import "google/protobuf/duration.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; package zitadel.policy.v1; @@ -128,6 +129,12 @@ message LoginPolicy { 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 {