From 26c8113930d9d22bf29c13ed057d27568f6a51eb Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Thu, 7 Jan 2021 16:06:45 +0100 Subject: [PATCH] feat: New event user (#1156) * feat: change user command side * feat: change user command side * feat: use states on write model * feat: command and query side in auth api * feat: auth commands * feat: check external idp id * feat: user state check * fix: error messages * fix: is active state --- cmd/zitadel/main.go | 2 +- internal/api/grpc/auth/server.go | 12 +- internal/api/grpc/auth/user.go | 17 +- internal/api/grpc/auth/user_converter.go | 62 +++---- .../api/grpc/auth/user_human_converter.go | 23 +-- internal/api/grpc/management/user.go | 34 ++-- .../api/grpc/management/user_converter.go | 24 +-- .../eventsourcing/eventstore/user.go | 26 --- internal/auth/repository/user.go | 4 - .../eventsourcing/eventstore/user.go | 164 ------------------ internal/management/repository/user.go | 26 --- internal/static/i18n/de.yaml | 2 + internal/static/i18n/en.yaml | 2 + internal/v2/command/iam_idp_config.go | 8 +- internal/v2/command/iam_idp_oidc_config.go | 2 +- internal/v2/command/iam_member.go | 4 +- internal/v2/command/iam_policy_label.go | 8 +- internal/v2/command/iam_policy_login.go | 26 +-- internal/v2/command/iam_policy_org_iam.go | 8 +- .../v2/command/iam_policy_password_age.go | 8 +- .../command/iam_policy_password_complexity.go | 8 +- .../v2/command/iam_policy_password_lockout.go | 8 +- .../v2/command/identity_provider_model.go | 6 +- internal/v2/command/member_model.go | 12 +- internal/v2/command/org_policy_org_iam.go | 10 +- .../command/org_policy_password_complexity.go | 2 +- internal/v2/command/policy_label_model.go | 8 +- .../v2/command/policy_login_factors_model.go | 16 +- internal/v2/command/policy_login_model.go | 6 +- internal/v2/command/policy_org_iam_model.go | 5 +- .../v2/command/policy_password_age_model.go | 7 +- .../policy_password_complexity_model.go | 7 +- .../command/policy_password_lockout_model.go | 7 +- internal/v2/command/user.go | 76 ++++++-- internal/v2/command/user_converter.go | 7 + internal/v2/command/user_human.go | 50 +++++- internal/v2/command/user_human_address.go | 6 +- .../v2/command/user_human_address_model.go | 8 +- internal/v2/command/user_human_email.go | 45 ++++- internal/v2/command/user_human_email_model.go | 7 +- internal/v2/command/user_human_externalidp.go | 52 ++++++ .../command/user_human_externalidp_model.go | 72 ++++++++ internal/v2/command/user_human_model.go | 2 + internal/v2/command/user_human_otp.go | 41 +++++ internal/v2/command/user_human_otp_model.go | 57 ++++++ internal/v2/command/user_human_password.go | 107 ++++++++++++ .../v2/command/user_human_password_model.go | 70 ++++++++ internal/v2/command/user_human_phone.go | 101 +++++++++++ internal/v2/command/user_human_phone_model.go | 88 ++++++++++ internal/v2/command/user_human_profile.go | 6 +- internal/v2/command/user_human_webauthn.go | 50 ++++++ .../v2/command/user_human_webauthn_model.go | 61 +++++++ internal/v2/command/user_machine.go | 4 +- ...e_write_model.go => user_machine_model.go} | 4 + internal/v2/command/user_model.go | 21 ++- internal/v2/domain/factors.go | 14 ++ internal/v2/domain/human.go | 3 +- internal/v2/domain/human_address.go | 14 ++ internal/v2/domain/human_email.go | 11 ++ internal/v2/domain/human_external_idp.go | 24 ++- internal/v2/domain/human_otp.go | 14 ++ internal/v2/domain/human_password.go | 14 +- internal/v2/domain/human_phone.go | 14 ++ internal/v2/domain/human_web_auth_n.go | 14 ++ internal/v2/domain/iam_member.go | 14 ++ internal/v2/domain/policy.go | 15 ++ internal/v2/domain/provider.go | 14 ++ .../v2/repository/user/human_external_idp.go | 9 +- internal/v2/repository/user/human_mfa_otp.go | 5 - internal/v2/repository/user/human_password.go | 3 + internal/v2/repository/user/human_phone.go | 3 +- 71 files changed, 1242 insertions(+), 442 deletions(-) create mode 100644 internal/v2/command/user_human_externalidp.go create mode 100644 internal/v2/command/user_human_externalidp_model.go create mode 100644 internal/v2/command/user_human_otp.go create mode 100644 internal/v2/command/user_human_otp_model.go create mode 100644 internal/v2/command/user_human_password.go create mode 100644 internal/v2/command/user_human_password_model.go create mode 100644 internal/v2/command/user_human_phone.go create mode 100644 internal/v2/command/user_human_phone_model.go create mode 100644 internal/v2/command/user_human_webauthn.go create mode 100644 internal/v2/command/user_human_webauthn_model.go rename internal/v2/command/{user_machine_write_model.go => user_machine_model.go} (95%) create mode 100644 internal/v2/domain/policy.go diff --git a/cmd/zitadel/main.go b/cmd/zitadel/main.go index d20e89a6c9..9621078b6c 100644 --- a/cmd/zitadel/main.go +++ b/cmd/zitadel/main.go @@ -162,7 +162,7 @@ func startAPI(ctx context.Context, conf *Config, authZRepo *authz_repo.EsReposit apis.RegisterServer(ctx, management.CreateServer(command, query, managementRepo, conf.SystemDefaults)) } if *authEnabled { - apis.RegisterServer(ctx, auth.CreateServer(authRepo)) + apis.RegisterServer(ctx, auth.CreateServer(command, query, authRepo)) } if *oidcEnabled { op := oidc.NewProvider(ctx, conf.API.OIDC, authRepo, *localDevMode) diff --git a/internal/api/grpc/auth/server.go b/internal/api/grpc/auth/server.go index bb224cf9d5..30a53159b0 100644 --- a/internal/api/grpc/auth/server.go +++ b/internal/api/grpc/auth/server.go @@ -1,6 +1,8 @@ package auth import ( + "github.com/caos/zitadel/internal/v2/command" + "github.com/caos/zitadel/internal/v2/query" "google.golang.org/grpc" "github.com/caos/zitadel/internal/api/authz" @@ -17,16 +19,20 @@ const ( ) type Server struct { - repo repository.Repository + command *command.CommandSide + query *query.QuerySide + repo repository.Repository } type Config struct { Repository eventsourcing.Config } -func CreateServer(authRepo repository.Repository) *Server { +func CreateServer(command *command.CommandSide, query *query.QuerySide, authRepo repository.Repository) *Server { return &Server{ - repo: authRepo, + command: command, + query: query, + repo: authRepo, } } diff --git a/internal/api/grpc/auth/user.go b/internal/api/grpc/auth/user.go index f00ef7f872..5bfc3a5e8e 100644 --- a/internal/api/grpc/auth/user.go +++ b/internal/api/grpc/auth/user.go @@ -2,7 +2,7 @@ package auth import ( "context" - + "github.com/caos/zitadel/internal/api/authz" "github.com/golang/protobuf/ptypes/empty" "github.com/caos/zitadel/pkg/grpc/auth" @@ -62,23 +62,24 @@ func (s *Server) GetMyMfas(ctx context.Context, _ *empty.Empty) (*auth.MultiFact } func (s *Server) UpdateMyUserProfile(ctx context.Context, request *auth.UpdateUserProfileRequest) (*auth.UserProfile, error) { - profile, err := s.repo.ChangeMyProfile(ctx, updateProfileToModel(ctx, request)) + profile, err := s.command.ChangeHumanProfile(ctx, updateProfileToDomain(ctx, request)) if err != nil { return nil, err } - return profileFromModel(profile), nil + return profileFromDomain(profile), nil } func (s *Server) ChangeMyUserName(ctx context.Context, request *auth.ChangeUserNameRequest) (*empty.Empty, error) { - return &empty.Empty{}, s.repo.ChangeMyUsername(ctx, request.UserName) + ctxData := authz.GetCtxData(ctx) + return &empty.Empty{}, s.command.ChangeUsername(ctx, ctxData.OrgID, ctxData.UserID, request.UserName) } func (s *Server) ChangeMyUserEmail(ctx context.Context, request *auth.UpdateUserEmailRequest) (*auth.UserEmail, error) { - email, err := s.repo.ChangeMyEmail(ctx, updateEmailToModel(ctx, request)) + email, err := s.command.ChangeHumanEmail(ctx, updateEmailToDomain(ctx, request)) if err != nil { return nil, err } - return emailFromModel(email), nil + return emailFromDomain(email), nil } func (s *Server) VerifyMyUserEmail(ctx context.Context, request *auth.VerifyMyUserEmailRequest) (*empty.Empty, error) { @@ -92,11 +93,11 @@ func (s *Server) ResendMyEmailVerificationMail(ctx context.Context, _ *empty.Emp } func (s *Server) ChangeMyUserPhone(ctx context.Context, request *auth.UpdateUserPhoneRequest) (*auth.UserPhone, error) { - phone, err := s.repo.ChangeMyPhone(ctx, updatePhoneToModel(ctx, request)) + phone, err := s.command.ChangeHumanPhone(ctx, updatePhoneToDomain(ctx, request)) if err != nil { return nil, err } - return phoneFromModel(phone), nil + return phoneFromDomain(phone), nil } func (s *Server) VerifyMyUserPhone(ctx context.Context, request *auth.VerifyUserPhoneRequest) (*empty.Empty, error) { diff --git a/internal/api/grpc/auth/user_converter.go b/internal/api/grpc/auth/user_converter.go index 3251320201..9b0c10638e 100644 --- a/internal/api/grpc/auth/user_converter.go +++ b/internal/api/grpc/auth/user_converter.go @@ -3,6 +3,7 @@ package auth import ( "context" "encoding/json" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/logging" "github.com/golang/protobuf/ptypes" @@ -52,7 +53,7 @@ func userViewFromModel(user *usr_model.UserView) *auth.UserView { return userView } -func profileFromModel(profile *usr_model.Profile) *auth.UserProfile { +func profileFromDomain(profile *domain.Profile) *auth.UserProfile { creationDate, err := ptypes.TimestampProto(profile.CreationDate) logging.Log("GRPC-56t5s").OnError(err).Debug("unable to parse timestamp") @@ -69,7 +70,7 @@ func profileFromModel(profile *usr_model.Profile) *auth.UserProfile { DisplayName: profile.DisplayName, NickName: profile.NickName, PreferredLanguage: profile.PreferredLanguage.String(), - Gender: genderFromModel(profile.Gender), + Gender: genderFromDomain(profile.Gender), } } @@ -81,36 +82,37 @@ func profileViewFromModel(profile *usr_model.Profile) *auth.UserProfileView { logging.Log("GRPC-9sujE").OnError(err).Debug("unable to parse timestamp") return &auth.UserProfileView{ - Id: profile.AggregateID, - CreationDate: creationDate, - ChangeDate: changeDate, - Sequence: profile.Sequence, - FirstName: profile.FirstName, - LastName: profile.LastName, - DisplayName: profile.DisplayName, - NickName: profile.NickName, - PreferredLanguage: profile.PreferredLanguage.String(), - Gender: genderFromModel(profile.Gender), + Id: profile.AggregateID, + CreationDate: creationDate, + ChangeDate: changeDate, + Sequence: profile.Sequence, + FirstName: profile.FirstName, + LastName: profile.LastName, + DisplayName: profile.DisplayName, + NickName: profile.NickName, + PreferredLanguage: profile.PreferredLanguage.String(), + //TODO: Use converter + Gender: auth.Gender(profile.Gender), LoginNames: profile.LoginNames, PreferredLoginName: profile.PreferredLoginName, } } -func updateProfileToModel(ctx context.Context, u *auth.UpdateUserProfileRequest) *usr_model.Profile { +func updateProfileToDomain(ctx context.Context, u *auth.UpdateUserProfileRequest) *domain.Profile { preferredLanguage, err := language.Parse(u.PreferredLanguage) logging.Log("GRPC-lk73L").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("language malformed") - return &usr_model.Profile{ + return &domain.Profile{ ObjectRoot: models.ObjectRoot{AggregateID: authz.GetCtxData(ctx).UserID}, FirstName: u.FirstName, LastName: u.LastName, NickName: u.NickName, PreferredLanguage: preferredLanguage, - Gender: genderToModel(u.Gender), + Gender: genderToDomain(u.Gender), } } -func emailFromModel(email *usr_model.Email) *auth.UserEmail { +func emailFromDomain(email *domain.Email) *auth.UserEmail { creationDate, err := ptypes.TimestampProto(email.CreationDate) logging.Log("GRPC-sdoi3").OnError(err).Debug("unable to parse timestamp") @@ -144,14 +146,14 @@ func emailViewFromModel(email *usr_model.Email) *auth.UserEmailView { } } -func updateEmailToModel(ctx context.Context, e *auth.UpdateUserEmailRequest) *usr_model.Email { - return &usr_model.Email{ +func updateEmailToDomain(ctx context.Context, e *auth.UpdateUserEmailRequest) *domain.Email { + return &domain.Email{ ObjectRoot: models.ObjectRoot{AggregateID: authz.GetCtxData(ctx).UserID}, EmailAddress: e.Email, } } -func phoneFromModel(phone *usr_model.Phone) *auth.UserPhone { +func phoneFromDomain(phone *domain.Phone) *auth.UserPhone { creationDate, err := ptypes.TimestampProto(phone.CreationDate) logging.Log("GRPC-kjn5J").OnError(err).Debug("unable to parse timestamp") @@ -185,8 +187,8 @@ func phoneViewFromModel(phone *usr_model.Phone) *auth.UserPhoneView { } } -func updatePhoneToModel(ctx context.Context, e *auth.UpdateUserPhoneRequest) *usr_model.Phone { - return &usr_model.Phone{ +func updatePhoneToDomain(ctx context.Context, e *auth.UpdateUserPhoneRequest) *domain.Phone { + return &domain.Phone{ ObjectRoot: models.ObjectRoot{AggregateID: authz.GetCtxData(ctx).UserID}, PhoneNumber: e.Phone, } @@ -332,29 +334,29 @@ func userStateFromModel(state usr_model.UserState) auth.UserState { } } -func genderFromModel(gender usr_model.Gender) auth.Gender { +func genderFromDomain(gender domain.Gender) auth.Gender { switch gender { - case usr_model.GenderFemale: + case domain.GenderFemale: return auth.Gender_GENDER_FEMALE - case usr_model.GenderMale: + case domain.GenderMale: return auth.Gender_GENDER_MALE - case usr_model.GenderDiverse: + case domain.GenderDiverse: return auth.Gender_GENDER_DIVERSE default: return auth.Gender_GENDER_UNSPECIFIED } } -func genderToModel(gender auth.Gender) usr_model.Gender { +func genderToDomain(gender auth.Gender) domain.Gender { switch gender { case auth.Gender_GENDER_FEMALE: - return usr_model.GenderFemale + return domain.GenderFemale case auth.Gender_GENDER_MALE: - return usr_model.GenderMale + return domain.GenderMale case auth.Gender_GENDER_DIVERSE: - return usr_model.GenderDiverse + return domain.GenderDiverse default: - return usr_model.GenderUnspecified + return domain.GenderUnspecified } } diff --git a/internal/api/grpc/auth/user_human_converter.go b/internal/api/grpc/auth/user_human_converter.go index 62e3bc36cc..e3e077001d 100644 --- a/internal/api/grpc/auth/user_human_converter.go +++ b/internal/api/grpc/auth/user_human_converter.go @@ -17,16 +17,17 @@ func humanViewFromModel(user *usr_model.HumanView) *auth.HumanView { DisplayName: user.DisplayName, NickName: user.NickName, PreferredLanguage: user.PreferredLanguage, - Gender: genderFromModel(user.Gender), - Email: user.Email, - IsEmailVerified: user.IsEmailVerified, - Phone: user.Phone, - IsPhoneVerified: user.IsPhoneVerified, - Country: user.Country, - Locality: user.Locality, - PostalCode: user.PostalCode, - Region: user.Region, - StreetAddress: user.StreetAddress, - PasswordChanged: passwordChanged, + //TODO: add converter + Gender: auth.Gender(user.Gender), + Email: user.Email, + IsEmailVerified: user.IsEmailVerified, + Phone: user.Phone, + IsPhoneVerified: user.IsPhoneVerified, + Country: user.Country, + Locality: user.Locality, + PostalCode: user.PostalCode, + Region: user.Region, + StreetAddress: user.StreetAddress, + PasswordChanged: passwordChanged, } } diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index af65d566bb..fbc544ad68 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -52,7 +52,7 @@ func (s *Server) IsUserUnique(ctx context.Context, request *management.UniqueUse } func (s *Server) CreateUser(ctx context.Context, in *management.CreateUserRequest) (*management.UserResponse, error) { - user, err := s.command.AddUser(ctx, userCreateToDomain(in)) + user, err := s.command.AddUser(ctx, authz.GetCtxData(ctx).OrgID, userCreateToDomain(in)) if err != nil { return nil, err } @@ -113,7 +113,7 @@ func (s *Server) GetUserProfile(ctx context.Context, in *management.UserID) (*ma } func (s *Server) ChangeUserUserName(ctx context.Context, request *management.UpdateUserUserNameRequest) (*empty.Empty, error) { - return &empty.Empty{}, s.user.ChangeUsername(ctx, request.Id, request.UserName) + return &empty.Empty{}, s.command.ChangeUsername(ctx, authz.GetCtxData(ctx).OrgID, request.Id, request.UserName) } func (s *Server) UpdateUserProfile(ctx context.Context, request *management.UpdateUserProfileRequest) (*management.UserProfile, error) { @@ -141,7 +141,7 @@ func (s *Server) ChangeUserEmail(ctx context.Context, request *management.Update } func (s *Server) ResendEmailVerificationMail(ctx context.Context, in *management.UserID) (*empty.Empty, error) { - err := s.user.CreateEmailVerificationCode(ctx, in.Id) + err := s.command.CreateHumanEmailVerificationCode(ctx, in.Id) return &empty.Empty{}, err } @@ -154,20 +154,20 @@ func (s *Server) GetUserPhone(ctx context.Context, in *management.UserID) (*mana } func (s *Server) ChangeUserPhone(ctx context.Context, request *management.UpdateUserPhoneRequest) (*management.UserPhone, error) { - phone, err := s.user.ChangePhone(ctx, updatePhoneToModel(request)) + phone, err := s.command.ChangeHumanPhone(ctx, updatePhoneToDomain(request)) if err != nil { return nil, err } - return phoneFromModel(phone), nil + return phoneFromDomain(phone), nil } func (s *Server) RemoveUserPhone(ctx context.Context, userID *management.UserID) (*empty.Empty, error) { - err := s.user.RemovePhone(ctx, userID.Id) + err := s.command.RemoveHumanPhone(ctx, userID.Id) return &empty.Empty{}, err } func (s *Server) ResendPhoneVerificationCode(ctx context.Context, in *management.UserID) (*empty.Empty, error) { - err := s.user.CreatePhoneVerificationCode(ctx, in.Id) + err := s.command.CreateHumanPhoneVerificationCode(ctx, in.Id) return &empty.Empty{}, err } @@ -188,18 +188,16 @@ func (s *Server) UpdateUserAddress(ctx context.Context, request *management.Upda } func (s *Server) SendSetPasswordNotification(ctx context.Context, request *management.SetPasswordNotificationRequest) (*empty.Empty, error) { - err := s.user.RequestSetPassword(ctx, request.Id, notifyTypeToModel(request.Type)) + err := s.command.RequestSetPassword(ctx, request.Id, notifyTypeToDomain(request.Type)) return &empty.Empty{}, err } func (s *Server) SetInitialPassword(ctx context.Context, request *management.PasswordRequest) (*empty.Empty, error) { - _, err := s.user.SetOneTimePassword(ctx, passwordRequestToModel(request)) - return &empty.Empty{}, err + return &empty.Empty{}, s.command.SetOneTimePassword(ctx, authz.GetCtxData(ctx).OrgID, request.Id, request.Password) } func (s *Server) ResendInitialMail(ctx context.Context, request *management.InitialMailRequest) (*empty.Empty, error) { - err := s.user.ResendInitialMail(ctx, request.Id, request.Email) - return &empty.Empty{}, err + return &empty.Empty{}, s.command.ResendInitialMail(ctx, request.Id, request.Email) } func (s *Server) SearchUserExternalIDPs(ctx context.Context, request *management.ExternalIDPSearchRequest) (*management.ExternalIDPSearchResponse, error) { @@ -211,8 +209,7 @@ func (s *Server) SearchUserExternalIDPs(ctx context.Context, request *management } func (s *Server) RemoveExternalIDP(ctx context.Context, request *management.ExternalIDPRemoveRequest) (*empty.Empty, error) { - err := s.user.RemoveExternalIDP(ctx, externalIDPRemoveToModel(request)) - return &empty.Empty{}, err + return &empty.Empty{}, s.command.RemoveHumanExternalIDP(ctx, externalIDPRemoveToDomain(request)) } func (s *Server) GetUserMfas(ctx context.Context, userID *management.UserID) (*management.UserMultiFactors, error) { @@ -224,13 +221,11 @@ func (s *Server) GetUserMfas(ctx context.Context, userID *management.UserID) (*m } func (s *Server) RemoveMfaOTP(ctx context.Context, userID *management.UserID) (*empty.Empty, error) { - err := s.user.RemoveOTP(ctx, userID.Id) - return &empty.Empty{}, err + return &empty.Empty{}, s.command.RemoveHumanOTP(ctx, userID.Id) } func (s *Server) RemoveMfaU2F(ctx context.Context, webAuthNTokenID *management.WebAuthNTokenID) (*empty.Empty, error) { - err := s.user.RemoveU2F(ctx, webAuthNTokenID.UserId, webAuthNTokenID.Id) - return &empty.Empty{}, err + return &empty.Empty{}, s.command.RemoveHumanU2F(ctx, webAuthNTokenID.UserId, webAuthNTokenID.Id) } func (s *Server) GetPasswordless(ctx context.Context, userID *management.UserID) (_ *management.WebAuthNTokens, err error) { @@ -242,8 +237,7 @@ func (s *Server) GetPasswordless(ctx context.Context, userID *management.UserID) } func (s *Server) RemovePasswordless(ctx context.Context, id *management.WebAuthNTokenID) (*empty.Empty, error) { - err := s.user.RemovePasswordless(ctx, id.UserId, id.Id) - return &empty.Empty{}, err + return &empty.Empty{}, s.command.RemoveHumanPasswordless(ctx, id.UserId, id.Id) } func (s *Server) SearchUserMemberships(ctx context.Context, in *management.UserMembershipSearchRequest) (*management.UserMembershipSearchResponse, error) { diff --git a/internal/api/grpc/management/user_converter.go b/internal/api/grpc/management/user_converter.go index 38bd686de8..fbb81b7b70 100644 --- a/internal/api/grpc/management/user_converter.go +++ b/internal/api/grpc/management/user_converter.go @@ -76,11 +76,11 @@ func externalIDPSearchRequestToModel(request *management.ExternalIDPSearchReques } } -func externalIDPRemoveToModel(idp *management.ExternalIDPRemoveRequest) *usr_model.ExternalIDP { - return &usr_model.ExternalIDP{ - ObjectRoot: models.ObjectRoot{AggregateID: idp.UserId}, - IDPConfigID: idp.IdpConfigId, - UserID: idp.ExternalUserId, +func externalIDPRemoveToDomain(idp *management.ExternalIDPRemoveRequest) *domain.ExternalIDP { + return &domain.ExternalIDP{ + ObjectRoot: models.ObjectRoot{AggregateID: idp.UserId}, + IDPConfigID: idp.IdpConfigId, + ExternalUserID: idp.ExternalUserId, } } @@ -306,7 +306,7 @@ func updateEmailToDomain(e *management.UpdateUserEmailRequest) *domain.Email { } } -func phoneFromModel(phone *usr_model.Phone) *management.UserPhone { +func phoneFromDomain(phone *domain.Phone) *management.UserPhone { creationDate, err := ptypes.TimestampProto(phone.CreationDate) logging.Log("GRPC-ps9ws").OnError(err).Debug("unable to parse timestamp") @@ -339,8 +339,8 @@ func phoneViewFromModel(phone *usr_model.Phone) *management.UserPhoneView { IsPhoneVerified: phone.IsPhoneVerified, } } -func updatePhoneToModel(e *management.UpdateUserPhoneRequest) *usr_model.Phone { - return &usr_model.Phone{ +func updatePhoneToDomain(e *management.UpdateUserPhoneRequest) *domain.Phone { + return &domain.Phone{ ObjectRoot: models.ObjectRoot{AggregateID: e.Id}, PhoneNumber: e.Phone, IsPhoneVerified: e.IsPhoneVerified, @@ -510,14 +510,14 @@ func mfaFromModel(mfa *usr_model.MultiFactor) *management.UserMultiFactor { } } -func notifyTypeToModel(state management.NotificationType) usr_model.NotificationType { +func notifyTypeToDomain(state management.NotificationType) domain.NotificationType { switch state { case management.NotificationType_NOTIFICATIONTYPE_EMAIL: - return usr_model.NotificationTypeEmail + return domain.NotificationTypeEmail case management.NotificationType_NOTIFICATIONTYPE_SMS: - return usr_model.NotificationTypeSms + return domain.NotificationTypeSms default: - return usr_model.NotificationTypeEmail + return domain.NotificationTypeEmail } } diff --git a/internal/auth/repository/eventsourcing/eventstore/user.go b/internal/auth/repository/eventsourcing/eventstore/user.go index 392ff2a793..04f597261e 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user.go +++ b/internal/auth/repository/eventsourcing/eventstore/user.go @@ -100,13 +100,6 @@ func (repo *UserRepo) MyProfile(ctx context.Context) (*model.Profile, error) { return user.GetProfile() } -func (repo *UserRepo) ChangeMyProfile(ctx context.Context, profile *model.Profile) (*model.Profile, error) { - if err := checkIDs(ctx, profile.ObjectRoot); err != nil { - return nil, err - } - return repo.UserEvents.ChangeProfile(ctx, profile) -} - func (repo *UserRepo) SearchMyExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error) { request.EnsureLimit(repo.SearchLimit) sequence, seqErr := repo.View.GetLatestExternalIDPSequence("") @@ -154,13 +147,6 @@ func (repo *UserRepo) MyEmail(ctx context.Context) (*model.Email, error) { return user.GetEmail() } -func (repo *UserRepo) ChangeMyEmail(ctx context.Context, email *model.Email) (*model.Email, error) { - if err := checkIDs(ctx, email.ObjectRoot); err != nil { - return nil, err - } - return repo.UserEvents.ChangeEmail(ctx, email) -} - func (repo *UserRepo) VerifyEmail(ctx context.Context, userID, code string) error { return repo.UserEvents.VerifyEmail(ctx, userID, code) } @@ -388,18 +374,6 @@ func (repo *UserRepo) RemoveMyPasswordless(ctx context.Context, webAuthNTokenID return repo.UserEvents.RemovePasswordlessToken(ctx, authz.GetCtxData(ctx).UserID, webAuthNTokenID) } -func (repo *UserRepo) ChangeMyUsername(ctx context.Context, username string) error { - ctxData := authz.GetCtxData(ctx) - orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(ctxData.OrgID) - if errors.IsNotFound(err) { - orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return err - } - orgPolicyView := iam_es_model.OrgIAMViewToModel(orgPolicy) - return repo.UserEvents.ChangeUsername(ctx, ctxData.UserID, username, orgPolicyView) -} func (repo *UserRepo) ResendInitVerificationMail(ctx context.Context, userID string) error { _, err := repo.UserEvents.CreateInitializeUserCodeByID(ctx, userID) return err diff --git a/internal/auth/repository/user.go b/internal/auth/repository/user.go index 8ae79d92d2..2499372d06 100644 --- a/internal/auth/repository/user.go +++ b/internal/auth/repository/user.go @@ -50,10 +50,8 @@ type myUserRepo interface { MyUser(ctx context.Context) (*model.UserView, error) MyProfile(ctx context.Context) (*model.Profile, error) - ChangeMyProfile(ctx context.Context, profile *model.Profile) (*model.Profile, error) MyEmail(ctx context.Context) (*model.Email, error) - ChangeMyEmail(ctx context.Context, email *model.Email) (*model.Email, error) VerifyMyEmail(ctx context.Context, code string) error ResendMyEmailVerificationMail(ctx context.Context) error @@ -86,7 +84,5 @@ type myUserRepo interface { VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, data []byte) error RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error - ChangeMyUsername(ctx context.Context, username string) error - MyUserChanges(ctx context.Context, lastSequence uint64, limit uint64, sortAscending bool) (*model.UserChanges, error) } diff --git a/internal/management/repository/eventsourcing/eventstore/user.go b/internal/management/repository/eventsourcing/eventstore/user.go index 1c16407b4f..aa860d1bdd 100644 --- a/internal/management/repository/eventsourcing/eventstore/user.go +++ b/internal/management/repository/eventsourcing/eventstore/user.go @@ -9,8 +9,6 @@ import ( "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors" es_int "github.com/caos/zitadel/internal/eventstore" - es_models "github.com/caos/zitadel/internal/eventstore/models" - es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" "github.com/caos/zitadel/internal/management/repository/eventsourcing/view" global_model "github.com/caos/zitadel/internal/model" @@ -60,100 +58,6 @@ func (repo *UserRepo) UserByID(ctx context.Context, id string) (*usr_model.UserV return model.UserToModel(&userCopy), nil } -func (repo *UserRepo) CreateUser(ctx context.Context, user *usr_model.User) (*usr_model.User, error) { - pwPolicy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if err != nil && caos_errs.IsNotFound(err) { - pwPolicy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return nil, err - } - pwPolicyView := iam_es_model.PasswordComplexityViewToModel(pwPolicy) - orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if err != nil && errors.IsNotFound(err) { - orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return nil, err - } - orgPolicyView := iam_es_model.OrgIAMViewToModel(orgPolicy) - return repo.UserEvents.CreateUser(ctx, user, pwPolicyView, orgPolicyView) -} - -func (repo *UserRepo) RegisterUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*usr_model.User, error) { - policyResourceOwner := authz.GetCtxData(ctx).OrgID - if resourceOwner != "" { - policyResourceOwner = resourceOwner - } - pwPolicy, err := repo.View.PasswordComplexityPolicyByAggregateID(policyResourceOwner) - if err != nil && caos_errs.IsNotFound(err) { - pwPolicy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return nil, err - } - pwPolicyView := iam_es_model.PasswordComplexityViewToModel(pwPolicy) - orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if err != nil && errors.IsNotFound(err) { - orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return nil, err - } - orgPolicyView := iam_es_model.OrgIAMViewToModel(orgPolicy) - return repo.UserEvents.RegisterUser(ctx, user, pwPolicyView, orgPolicyView, resourceOwner) -} - -func (repo *UserRepo) DeactivateUser(ctx context.Context, id string) (*usr_model.User, error) { - return repo.UserEvents.DeactivateUser(ctx, id) -} - -func (repo *UserRepo) ReactivateUser(ctx context.Context, id string) (*usr_model.User, error) { - return repo.UserEvents.ReactivateUser(ctx, id) -} - -func (repo *UserRepo) LockUser(ctx context.Context, id string) (*usr_model.User, error) { - return repo.UserEvents.LockUser(ctx, id) -} - -func (repo *UserRepo) UnlockUser(ctx context.Context, id string) (*usr_model.User, error) { - return repo.UserEvents.UnlockUser(ctx, id) -} - -func (repo *UserRepo) RemoveUser(ctx context.Context, id string) error { - aggregates := make([]*es_models.Aggregate, 0) - orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if err != nil && errors.IsNotFound(err) { - orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return err - } - orgPolicyView := iam_es_model.OrgIAMViewToModel(orgPolicy) - user, agg, err := repo.UserEvents.PrepareRemoveUser(ctx, id, orgPolicyView) - if err != nil { - return err - } - aggregates = append(aggregates, agg...) - - // remove user_grants - usergrants, err := repo.View.UserGrantsByUserID(id) - if err != nil { - return err - } - for _, grant := range usergrants { - _, aggs, err := repo.UserGrantEvents.PrepareRemoveUserGrant(ctx, grant.ID, true) - if err != nil { - return err - } - for _, agg := range aggs { - aggregates = append(aggregates, agg) - } - } - - return es_sdk.PushAggregates(ctx, repo.Eventstore.PushAggregates, user.AppendEvents, aggregates...) -} - func (repo *UserRepo) SearchUsers(ctx context.Context, request *usr_model.UserSearchRequest) (*usr_model.UserSearchResponse, error) { request.EnsureLimit(repo.SearchLimit) sequence, sequenceErr := repo.View.GetLatestUserSequence("") @@ -225,42 +129,10 @@ func (repo *UserRepo) UserMFAs(ctx context.Context, userID string) ([]*usr_model return mfas, nil } -func (repo *UserRepo) RemoveOTP(ctx context.Context, userID string) error { - return repo.UserEvents.RemoveOTP(ctx, userID) -} - -func (repo *UserRepo) RemoveU2F(ctx context.Context, userID, webAuthNTokenID string) error { - return repo.UserEvents.RemoveU2FToken(ctx, userID, webAuthNTokenID) -} - func (repo *UserRepo) GetPasswordless(ctx context.Context, userID string) ([]*usr_model.WebAuthNToken, error) { return repo.UserEvents.GetPasswordless(ctx, userID) } -func (repo *UserRepo) RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error { - return repo.UserEvents.RemovePasswordlessToken(ctx, userID, webAuthNTokenID) -} - -func (repo *UserRepo) SetOneTimePassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) { - policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if err != nil && caos_errs.IsNotFound(err) { - policy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return nil, err - } - pwPolicyView := iam_es_model.PasswordComplexityViewToModel(policy) - return repo.UserEvents.SetOneTimePassword(ctx, pwPolicyView, password) -} - -func (repo *UserRepo) RequestSetPassword(ctx context.Context, id string, notifyType usr_model.NotificationType) error { - return repo.UserEvents.RequestSetPassword(ctx, id, notifyType) -} - -func (repo *UserRepo) ResendInitialMail(ctx context.Context, userID, email string) error { - return repo.UserEvents.ResendInitialMail(ctx, userID, email) -} - func (repo *UserRepo) ProfileByID(ctx context.Context, userID string) (*usr_model.Profile, error) { user, err := repo.UserByID(ctx, userID) if err != nil { @@ -293,14 +165,6 @@ func (repo *UserRepo) SearchExternalIDPs(ctx context.Context, request *usr_model return result, nil } -func (repo *UserRepo) RemoveExternalIDP(ctx context.Context, externalIDP *usr_model.ExternalIDP) error { - return repo.UserEvents.RemoveExternalIDP(ctx, externalIDP) -} - -func (repo *UserRepo) ChangeMachine(ctx context.Context, machine *usr_model.Machine) (*usr_model.Machine, error) { - return repo.UserEvents.ChangeMachine(ctx, machine) -} - func (repo *UserRepo) GetMachineKey(ctx context.Context, userID, keyID string) (*usr_model.MachineKeyView, error) { key, err := repo.View.MachineKeyByIDs(userID, keyID) if err != nil { @@ -338,10 +202,6 @@ func (repo *UserRepo) RemoveMachineKey(ctx context.Context, userID, keyID string return repo.UserEvents.RemoveMachineKey(ctx, userID, keyID) } -func (repo *UserRepo) ChangeProfile(ctx context.Context, profile *usr_model.Profile) (*usr_model.Profile, error) { - return repo.UserEvents.ChangeProfile(ctx, profile) -} - func (repo *UserRepo) ChangeUsername(ctx context.Context, userID, userName string) error { orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) if err != nil && errors.IsNotFound(err) { @@ -365,14 +225,6 @@ func (repo *UserRepo) EmailByID(ctx context.Context, userID string) (*usr_model. return user.GetEmail() } -func (repo *UserRepo) ChangeEmail(ctx context.Context, email *usr_model.Email) (*usr_model.Email, error) { - return repo.UserEvents.ChangeEmail(ctx, email) -} - -func (repo *UserRepo) CreateEmailVerificationCode(ctx context.Context, userID string) error { - return repo.UserEvents.CreateEmailVerificationCode(ctx, userID) -} - func (repo *UserRepo) PhoneByID(ctx context.Context, userID string) (*usr_model.Phone, error) { user, err := repo.UserByID(ctx, userID) if err != nil { @@ -384,18 +236,6 @@ func (repo *UserRepo) PhoneByID(ctx context.Context, userID string) (*usr_model. return user.GetPhone() } -func (repo *UserRepo) ChangePhone(ctx context.Context, email *usr_model.Phone) (*usr_model.Phone, error) { - return repo.UserEvents.ChangePhone(ctx, email) -} - -func (repo *UserRepo) RemovePhone(ctx context.Context, userID string) error { - return repo.UserEvents.RemovePhone(ctx, userID) -} - -func (repo *UserRepo) CreatePhoneVerificationCode(ctx context.Context, userID string) error { - return repo.UserEvents.CreatePhoneVerificationCode(ctx, userID) -} - func (repo *UserRepo) AddressByID(ctx context.Context, userID string) (*usr_model.Address, error) { user, err := repo.UserByID(ctx, userID) if err != nil { @@ -407,10 +247,6 @@ func (repo *UserRepo) AddressByID(ctx context.Context, userID string) (*usr_mode return user.GetAddress() } -func (repo *UserRepo) ChangeAddress(ctx context.Context, address *usr_model.Address) (*usr_model.Address, error) { - return repo.UserEvents.ChangeAddress(ctx, address) -} - func (repo *UserRepo) SearchUserMemberships(ctx context.Context, request *usr_model.UserMembershipSearchRequest) (*usr_model.UserMembershipSearchResponse, error) { request.EnsureLimit(repo.SearchLimit) sequence, sequenceErr := repo.View.GetLatestUserMembershipSequence("") diff --git a/internal/management/repository/user.go b/internal/management/repository/user.go index da8d81b359..b1d19e6332 100644 --- a/internal/management/repository/user.go +++ b/internal/management/repository/user.go @@ -8,13 +8,6 @@ import ( type UserRepository interface { UserByID(ctx context.Context, id string) (*model.UserView, error) - CreateUser(ctx context.Context, user *model.User) (*model.User, error) - RegisterUser(ctx context.Context, user *model.User, resourceOwner string) (*model.User, error) - DeactivateUser(ctx context.Context, id string) (*model.User, error) - ReactivateUser(ctx context.Context, id string) (*model.User, error) - LockUser(ctx context.Context, id string) (*model.User, error) - UnlockUser(ctx context.Context, id string) (*model.User, error) - RemoveUser(ctx context.Context, id string) error SearchUsers(ctx context.Context, request *model.UserSearchRequest) (*model.UserSearchResponse, error) GetUserByLoginNameGlobal(ctx context.Context, email string) (*model.UserView, error) @@ -22,43 +15,24 @@ type UserRepository interface { UserChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool) (*model.UserChanges, error) - ChangeUsername(ctx context.Context, id, username string) error - - SetOneTimePassword(ctx context.Context, password *model.Password) (*model.Password, error) - RequestSetPassword(ctx context.Context, id string, notifyType model.NotificationType) error - ProfileByID(ctx context.Context, userID string) (*model.Profile, error) - ChangeProfile(ctx context.Context, profile *model.Profile) (*model.Profile, error) UserMFAs(ctx context.Context, userID string) ([]*model.MultiFactor, error) - RemoveOTP(ctx context.Context, userID string) error - RemoveU2F(ctx context.Context, userID, webAuthNTokenID string) error GetPasswordless(ctx context.Context, userID string) ([]*model.WebAuthNToken, error) - RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error SearchExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error) - RemoveExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error SearchMachineKeys(ctx context.Context, request *model.MachineKeySearchRequest) (*model.MachineKeySearchResponse, error) GetMachineKey(ctx context.Context, userID, keyID string) (*model.MachineKeyView, error) - ChangeMachine(ctx context.Context, machine *model.Machine) (*model.Machine, error) AddMachineKey(ctx context.Context, key *model.MachineKey) (*model.MachineKey, error) RemoveMachineKey(ctx context.Context, userID, keyID string) error EmailByID(ctx context.Context, userID string) (*model.Email, error) - ChangeEmail(ctx context.Context, email *model.Email) (*model.Email, error) - CreateEmailVerificationCode(ctx context.Context, userID string) error PhoneByID(ctx context.Context, userID string) (*model.Phone, error) - ChangePhone(ctx context.Context, email *model.Phone) (*model.Phone, error) - RemovePhone(ctx context.Context, userID string) error - CreatePhoneVerificationCode(ctx context.Context, userID string) error AddressByID(ctx context.Context, userID string) (*model.Address, error) - ChangeAddress(ctx context.Context, address *model.Address) (*model.Address, error) SearchUserMemberships(ctx context.Context, request *model.UserMembershipSearchRequest) (*model.UserMembershipSearchResponse, error) - - ResendInitialMail(ctx context.Context, userID, email string) error } diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 8b67dd2b6a..43b4272fb5 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -2,6 +2,7 @@ Errors: Internal: Es ist ein interner Fehler aufgetreten NoChangesFound: Keine Änderungen gefunden OriginNotAllowed: Dieser "Origin" ist nicht freigeschaltet + IDMissing: ID fehlt User: NotFound: Benutzer konnte nicht gefunden werden NotFoundOnOrg: Benutzer konnte in der gewünschten Organisation nicht gefunden werden @@ -19,6 +20,7 @@ Errors: NotLocked: Benutzer ist nicht gesperrt NoChanges: Keine Änderungen gefunden InitCodeNotFound: Kein Initialisierungs Code gefunden + UsernameNotChanged: Benutzername wurde nicht verändert Profile: NotFound: Profil nicht gefunden NotChanged: Profile nicht verändert diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 29947a8af9..17b2a26dc8 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -2,6 +2,7 @@ Errors: Internal: An internal error occured NoChangesFound: No changes OriginNotAllowed: This "Origin" is not allowed + IDMissing: ID missing User: NotFound: User could not be found NotFoundOnOrg: User could not be found on chosen organisation @@ -19,6 +20,7 @@ Errors: NotLocked: User is not locked NoChanges: No changes found InitCodeNotFound: Initialization Code not found + UsernameNotChanged: Username not changed Profile: NotFound: Profile not found NotChanged: Profile not changed diff --git a/internal/v2/command/iam_idp_config.go b/internal/v2/command/iam_idp_config.go index 68a89f0040..2a1f256c7c 100644 --- a/internal/v2/command/iam_idp_config.go +++ b/internal/v2/command/iam_idp_config.go @@ -63,12 +63,12 @@ func (r *CommandSide) ChangeDefaultIDPConfig(ctx context.Context, config *domain return nil, err } if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-4M9so", "Errors.IAM.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.IAM.IDPConfig.NotExisting") } changedEvent, hasChanged := existingIDP.NewChangedEvent(ctx, config.IDPConfigID, config.Name, config.StylingType) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") } iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel) iamAgg.PushEvents(changedEvent) @@ -86,7 +86,7 @@ func (r *CommandSide) DeactivateDefaultIDPConfig(ctx context.Context, idpID stri return nil, err } if existingIDP.State != domain.IDPConfigStateActive { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-4M9so", "Errors.IAM.IDPConfig.NotActive") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9so", "Errors.IAM.IDPConfig.NotActive") } iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel) iamAgg.PushEvents(iam_repo.NewIDPConfigDeactivatedEvent(ctx, idpID)) @@ -104,7 +104,7 @@ func (r *CommandSide) ReactivateDefaultIDPConfig(ctx context.Context, idpID stri return nil, err } if existingIDP.State != domain.IDPConfigStateInactive { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-5Mo0d", "Errors.IAM.IDPConfig.NotInactive") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-5Mo0d", "Errors.IAM.IDPConfig.NotInactive") } iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel) iamAgg.PushEvents(iam_repo.NewIDPConfigReactivatedEvent(ctx, idpID)) diff --git a/internal/v2/command/iam_idp_oidc_config.go b/internal/v2/command/iam_idp_oidc_config.go index fa4aa3d8be..d757916429 100644 --- a/internal/v2/command/iam_idp_oidc_config.go +++ b/internal/v2/command/iam_idp_oidc_config.go @@ -30,7 +30,7 @@ func (r *CommandSide) ChangeDefaultIDPOIDCConfig(ctx context.Context, config *do return nil, err } if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") } iamAgg := IAMAggregateFromWriteModel(&existingConfig.WriteModel) diff --git a/internal/v2/command/iam_member.go b/internal/v2/command/iam_member.go index 2b5db224a6..71dee33da3 100644 --- a/internal/v2/command/iam_member.go +++ b/internal/v2/command/iam_member.go @@ -23,7 +23,7 @@ func (r *CommandSide) AddIAMMember(ctx context.Context, member *domain.IAMMember if err != nil { return nil, err } - if addedMember.IsActive { + if addedMember.State == domain.MemberStateActive { return nil, errors.ThrowAlreadyExists(nil, "IAM-PtXi1", "Errors.IAM.Member.AlreadyExists") } @@ -95,7 +95,7 @@ func (r *CommandSide) iamMemberWriteModelByID(ctx context.Context, iamID, userID return nil, err } - if !writeModel.IsActive { + if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved { return nil, errors.ThrowNotFound(nil, "IAM-D8JxR", "Errors.NotFound") } diff --git a/internal/v2/command/iam_policy_label.go b/internal/v2/command/iam_policy_label.go index 2dd35c3b4f..abf18209b0 100644 --- a/internal/v2/command/iam_policy_label.go +++ b/internal/v2/command/iam_policy_label.go @@ -30,7 +30,7 @@ func (r *CommandSide) addDefaultLabelPolicy(ctx context.Context, iamAgg *iam_rep if err != nil { return err } - if addedPolicy.IsActive { + if addedPolicy.State == domain.PolicyStateActive { return caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LabelPolicy.AlreadyExists") } @@ -46,13 +46,13 @@ func (r *CommandSide) ChangeDefaultLabelPolicy(ctx context.Context, policy *doma return nil, err } - if !existingPolicy.IsActive { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-0K9dq", "Errors.IAM.LabelPolicy.NotFound") + if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { + return nil, caos_errs.ThrowNotFound(nil, "IAM-0K9dq", "Errors.IAM.LabelPolicy.NotFound") } changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.PrimaryColor, policy.SecondaryColor) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") } iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel) diff --git a/internal/v2/command/iam_policy_login.go b/internal/v2/command/iam_policy_login.go index 0d9e3fbbe8..3497f450ec 100644 --- a/internal/v2/command/iam_policy_login.go +++ b/internal/v2/command/iam_policy_login.go @@ -42,7 +42,7 @@ func (r *CommandSide) addDefaultLoginPolicy(ctx context.Context, iamAgg *iam_rep if err != nil { return err } - if addedPolicy.IsActive { + if addedPolicy.State == domain.PolicyStateActive { return caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LoginPolicy.AlreadyExists") } @@ -73,12 +73,12 @@ func (r *CommandSide) changeDefaultLoginPolicy(ctx context.Context, iamAgg *iam_ if err != nil { return err } - if !existingPolicy.IsActive { - return caos_errs.ThrowAlreadyExists(nil, "IAM-M0sif", "Errors.IAM.LoginPolicy.NotFound") + if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { + return caos_errs.ThrowNotFound(nil, "IAM-M0sif", "Errors.IAM.LoginPolicy.NotFound") } changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIdp, policy.ForceMFA, domain.PasswordlessType(policy.PasswordlessType)) if !hasChanged { - return caos_errs.ThrowAlreadyExists(nil, "IAM-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged") + return caos_errs.ThrowPreconditionFailed(nil, "IAM-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged") } iamAgg.PushEvents(changedEvent) @@ -92,7 +92,7 @@ func (r *CommandSide) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, id if err != nil { return nil, err } - if idpModel.IsActive { + if idpModel.State == domain.IdentityProviderStateActive { return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LoginPolicy.IDP.AlreadyExists") } @@ -113,8 +113,8 @@ func (r *CommandSide) RemoveIDPProviderFromDefaultLoginPolicy(ctx context.Contex if err != nil { return err } - if !idpModel.IsActive { - return caos_errs.ThrowAlreadyExists(nil, "IAM-39fjs", "Errors.IAM.LoginPolicy.IDP.NotExisting") + if idpModel.State == domain.IdentityProviderStateUnspecified || idpModel.State == domain.IdentityProviderStateRemoved { + return caos_errs.ThrowNotFound(nil, "IAM-39fjs", "Errors.IAM.LoginPolicy.IDP.NotExisting") } iamAgg := IAMAggregateFromWriteModel(&idpModel.IdentityProviderWriteModel.WriteModel) iamAgg.PushEvents(iam_repo.NewIdentityProviderRemovedEvent(ctx, idpProvider.IDPConfigID)) @@ -143,7 +143,7 @@ func (r *CommandSide) addSecondFactorToDefaultLoginPolicy(ctx context.Context, i return err } - if secondFactorModel.IsActive { + if secondFactorModel.State == domain.FactorStateActive { return caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LoginPolicy.MFA.AlreadyExists") } @@ -158,8 +158,8 @@ func (r *CommandSide) RemoveSecondFactorFromDefaultLoginPolicy(ctx context.Conte if err != nil { return err } - if !secondFactorModel.IsActive { - return caos_errs.ThrowAlreadyExists(nil, "IAM-3M9od", "Errors.IAM.LoginPolicy.MFA.NotExisting") + if secondFactorModel.State == domain.FactorStateUnspecified || secondFactorModel.State == domain.FactorStateRemoved { + return caos_errs.ThrowNotFound(nil, "IAM-3M9od", "Errors.IAM.LoginPolicy.MFA.NotExisting") } iamAgg := IAMAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel) iamAgg.PushEvents(iam_repo.NewLoginPolicySecondFactorRemovedEvent(ctx, domain.SecondFactorType(secondFactor))) @@ -187,7 +187,7 @@ func (r *CommandSide) addMultiFactorToDefaultLoginPolicy(ctx context.Context, ia if err != nil { return err } - if multiFactorModel.IsActive { + if multiFactorModel.State == domain.FactorStateActive { return caos_errs.ThrowAlreadyExists(nil, "IAM-3M9od", "Errors.IAM.LoginPolicy.MFA.AlreadyExists") } @@ -202,8 +202,8 @@ func (r *CommandSide) RemoveMultiFactorFromDefaultLoginPolicy(ctx context.Contex if err != nil { return err } - if multiFactorModel.IsActive { - return caos_errs.ThrowAlreadyExists(nil, "IAM-3M9df", "Errors.IAM.LoginPolicy.MFA.NotExisting") + if multiFactorModel.State == domain.FactorStateUnspecified || multiFactorModel.State == domain.FactorStateRemoved { + return caos_errs.ThrowNotFound(nil, "IAM-3M9df", "Errors.IAM.LoginPolicy.MFA.NotExisting") } iamAgg := IAMAggregateFromWriteModel(&multiFactorModel.MultiFactoryWriteModel.WriteModel) iamAgg.PushEvents(iam_repo.NewLoginPolicyMultiFactorRemovedEvent(ctx, domain.MultiFactorType(multiFactor))) diff --git a/internal/v2/command/iam_policy_org_iam.go b/internal/v2/command/iam_policy_org_iam.go index 35012322c3..0410a3ec82 100644 --- a/internal/v2/command/iam_policy_org_iam.go +++ b/internal/v2/command/iam_policy_org_iam.go @@ -41,7 +41,7 @@ func (r *CommandSide) addDefaultOrgIAMPolicy(ctx context.Context, iamAgg *iam_re if err != nil { return err } - if addedPolicy.IsActive { + if addedPolicy.State == domain.PolicyStateActive { return caos_errs.ThrowAlreadyExists(nil, "IAM-Lk0dS", "Errors.IAM.OrgIAMPolicy.AlreadyExists") } iamAgg.PushEvents(iam_repo.NewOrgIAMPolicyAddedEvent(ctx, policy.UserLoginMustBeDomain)) @@ -55,13 +55,13 @@ func (r *CommandSide) ChangeDefaultOrgIAMPolicy(ctx context.Context, policy *dom if err != nil { return nil, err } - if !existingPolicy.IsActive { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-0Pl0d", "Errors.IAM.OrgIAMPolicy.NotFound") + if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { + return nil, caos_errs.ThrowNotFound(nil, "IAM-0Pl0d", "Errors.IAM.OrgIAMPolicy.NotFound") } changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.UserLoginMustBeDomain) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") } iamAgg := IAMAggregateFromWriteModel(&existingPolicy.PolicyOrgIAMWriteModel.WriteModel) diff --git a/internal/v2/command/iam_policy_password_age.go b/internal/v2/command/iam_policy_password_age.go index cb2df774ff..f55757e63e 100644 --- a/internal/v2/command/iam_policy_password_age.go +++ b/internal/v2/command/iam_policy_password_age.go @@ -30,7 +30,7 @@ func (r *CommandSide) addDefaultPasswordAgePolicy(ctx context.Context, iamAgg *i if err != nil { return err } - if addedPolicy.IsActive { + if addedPolicy.State == domain.PolicyStateActive { return caos_errs.ThrowAlreadyExists(nil, "IAM-Lk0dS", "Errors.IAM.PasswordAgePolicy.AlreadyExists") } @@ -45,13 +45,13 @@ func (r *CommandSide) ChangeDefaultPasswordAgePolicy(ctx context.Context, policy if err != nil { return nil, err } - if !existingPolicy.IsActive { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-0oPew", "Errors.IAM.PasswordAgePolicy.NotFound") + if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { + return nil, caos_errs.ThrowNotFound(nil, "IAM-0oPew", "Errors.IAM.PasswordAgePolicy.NotFound") } changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.ExpireWarnDays, policy.MaxAgeDays) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") } iamAgg := IAMAggregateFromWriteModel(&existingPolicy.PasswordAgePolicyWriteModel.WriteModel) diff --git a/internal/v2/command/iam_policy_password_complexity.go b/internal/v2/command/iam_policy_password_complexity.go index e72dac2667..3cec1a171b 100644 --- a/internal/v2/command/iam_policy_password_complexity.go +++ b/internal/v2/command/iam_policy_password_complexity.go @@ -45,7 +45,7 @@ func (r *CommandSide) addDefaultPasswordComplexityPolicy(ctx context.Context, ia if err != nil { return err } - if addedPolicy.IsActive { + if addedPolicy.State == domain.PolicyStateActive { return caos_errs.ThrowAlreadyExists(nil, "IAM-Lk0dS", "Errors.IAM.PasswordComplexityPolicy.AlreadyExists") } @@ -64,13 +64,13 @@ func (r *CommandSide) ChangeDefaultPasswordComplexityPolicy(ctx context.Context, if err != nil { return nil, err } - if !existingPolicy.IsActive { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-0oPew", "Errors.IAM.PasswordAgePolicy.NotFound") + if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { + return nil, caos_errs.ThrowNotFound(nil, "IAM-0oPew", "Errors.IAM.PasswordAgePolicy.NotFound") } changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.MinLength, policy.HasLowercase, policy.HasUppercase, policy.HasNumber, policy.HasSymbol) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged") } iamAgg := IAMAggregateFromWriteModel(&existingPolicy.PasswordComplexityPolicyWriteModel.WriteModel) iamAgg.PushEvents(changedEvent) diff --git a/internal/v2/command/iam_policy_password_lockout.go b/internal/v2/command/iam_policy_password_lockout.go index d19e87d551..bbe65ac442 100644 --- a/internal/v2/command/iam_policy_password_lockout.go +++ b/internal/v2/command/iam_policy_password_lockout.go @@ -30,7 +30,7 @@ func (r *CommandSide) addDefaultPasswordLockoutPolicy(ctx context.Context, iamAg if err != nil { return err } - if addedPolicy.IsActive { + if addedPolicy.State == domain.PolicyStateActive { return caos_errs.ThrowAlreadyExists(nil, "IAM-0olDf", "Errors.IAM.PasswordLockoutPolicy.AlreadyExists") } @@ -45,13 +45,13 @@ func (r *CommandSide) ChangeDefaultPasswordLockoutPolicy(ctx context.Context, po if err != nil { return nil, err } - if !existingPolicy.IsActive { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-0oPew", "Errors.IAM.PasswordLockoutPolicy.NotFound") + if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { + return nil, caos_errs.ThrowNotFound(nil, "IAM-0oPew", "Errors.IAM.PasswordLockoutPolicy.NotFound") } changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.MaxAttempts, policy.ShowLockOutFailures) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-4M9vs", "Errors.IAM.PasswordLockoutPolicy.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.PasswordLockoutPolicy.NotChanged") } iamAgg := IAMAggregateFromWriteModel(&existingPolicy.PasswordLockoutPolicyWriteModel.WriteModel) diff --git a/internal/v2/command/identity_provider_model.go b/internal/v2/command/identity_provider_model.go index 9942b69267..c6ad36f287 100644 --- a/internal/v2/command/identity_provider_model.go +++ b/internal/v2/command/identity_provider_model.go @@ -11,7 +11,7 @@ type IdentityProviderWriteModel struct { IDPConfigID string IDPProviderType domain.IdentityProviderType - IsActive bool + State domain.IdentityProviderState } func (wm *IdentityProviderWriteModel) Reduce() error { @@ -20,9 +20,9 @@ func (wm *IdentityProviderWriteModel) Reduce() error { case *policy.IdentityProviderAddedEvent: wm.IDPConfigID = e.IDPConfigID wm.IDPProviderType = e.IDPProviderType - wm.IsActive = true + wm.State = domain.IdentityProviderStateActive case *policy.IdentityProviderRemovedEvent: - wm.IsActive = false + wm.State = domain.IdentityProviderStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/member_model.go b/internal/v2/command/member_model.go index be94ee2ade..e4f86eaa72 100644 --- a/internal/v2/command/member_model.go +++ b/internal/v2/command/member_model.go @@ -2,15 +2,17 @@ package command import ( "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/member" ) type MemberWriteModel struct { eventstore.WriteModel - UserID string - Roles []string - IsActive bool + UserID string + Roles []string + + State domain.MemberState } func NewMemberWriteModel(userID string) *MemberWriteModel { @@ -25,12 +27,12 @@ func (wm *MemberWriteModel) Reduce() error { case *member.MemberAddedEvent: wm.UserID = e.UserID wm.Roles = e.Roles - wm.IsActive = true + wm.State = domain.MemberStateActive case *member.MemberChangedEvent: wm.Roles = e.Roles case *member.MemberRemovedEvent: wm.Roles = nil - wm.IsActive = false + wm.State = domain.MemberStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/org_policy_org_iam.go b/internal/v2/command/org_policy_org_iam.go index 32293730d5..caecfbbf4a 100644 --- a/internal/v2/command/org_policy_org_iam.go +++ b/internal/v2/command/org_policy_org_iam.go @@ -14,7 +14,7 @@ func (r *CommandSide) GetOrgIAMPolicy(ctx context.Context, orgID string) (*domai if err != nil { return nil, err } - if policy.IsActive { + if policy.State == domain.PolicyStateActive { return orgWriteModelToOrgIAMPolicy(policy), nil } return r.GetDefaultOrgIAMPolicy(ctx) @@ -26,7 +26,7 @@ func (r *CommandSide) AddOrgIAMPolicy(ctx context.Context, policy *domain.OrgIAM if err != nil { return nil, err } - if addedPolicy.IsActive { + if addedPolicy.State == domain.PolicyStateActive { return nil, caos_errs.ThrowAlreadyExists(nil, "ORG-5M0ds", "Errors.Org.OrgIAMPolicy.AlreadyExists") } orgAgg := ORGAggregateFromWriteModel(&addedPolicy.PolicyOrgIAMWriteModel.WriteModel) @@ -45,13 +45,13 @@ func (r *CommandSide) ChangeOrgIAMPolicy(ctx context.Context, policy *domain.Org if err != nil { return nil, err } - if !existingPolicy.IsActive { - return nil, caos_errs.ThrowAlreadyExists(nil, "ORG-2N9sd", "Errors.Org.OrgIAMPolicy.NotFound") + if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { + return nil, caos_errs.ThrowNotFound(nil, "ORG-2N9sd", "Errors.Org.OrgIAMPolicy.NotFound") } changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.UserLoginMustBeDomain) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "ORG-3M9ds", "Errors.Org.LabelPolicy.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-3M9ds", "Errors.Org.LabelPolicy.NotChanged") } orgAgg := ORGAggregateFromWriteModel(&existingPolicy.PolicyOrgIAMWriteModel.WriteModel) diff --git a/internal/v2/command/org_policy_password_complexity.go b/internal/v2/command/org_policy_password_complexity.go index 309ef6cdd0..88139a8cb8 100644 --- a/internal/v2/command/org_policy_password_complexity.go +++ b/internal/v2/command/org_policy_password_complexity.go @@ -11,7 +11,7 @@ func (r *CommandSide) GetOrgPasswordComplexityPolicy(ctx context.Context, orgID if err != nil { return nil, err } - if policy.IsActive { + if policy.State == domain.PolicyStateActive { return orgWriteModelToPasswordComplexityPolicy(policy), nil } return r.GetDefaultPasswordComplexityPolicy(ctx) diff --git a/internal/v2/command/policy_label_model.go b/internal/v2/command/policy_label_model.go index aa82f4625a..30462617b2 100644 --- a/internal/v2/command/policy_label_model.go +++ b/internal/v2/command/policy_label_model.go @@ -2,6 +2,7 @@ package command import ( "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/policy" ) @@ -10,7 +11,8 @@ type LabelPolicyWriteModel struct { PrimaryColor string SecondaryColor string - IsActive bool + + State domain.PolicyState } func (wm *LabelPolicyWriteModel) Reduce() error { @@ -19,7 +21,7 @@ func (wm *LabelPolicyWriteModel) Reduce() error { case *policy.LabelPolicyAddedEvent: wm.PrimaryColor = e.PrimaryColor wm.SecondaryColor = e.SecondaryColor - wm.IsActive = true + wm.State = domain.PolicyStateActive case *policy.LabelPolicyChangedEvent: if e.PrimaryColor != nil { wm.PrimaryColor = *e.PrimaryColor @@ -28,7 +30,7 @@ func (wm *LabelPolicyWriteModel) Reduce() error { wm.SecondaryColor = *e.SecondaryColor } case *policy.LabelPolicyRemovedEvent: - wm.IsActive = false + wm.State = domain.PolicyStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/policy_login_factors_model.go b/internal/v2/command/policy_login_factors_model.go index 4404fb9224..e8004aa4d7 100644 --- a/internal/v2/command/policy_login_factors_model.go +++ b/internal/v2/command/policy_login_factors_model.go @@ -8,8 +8,8 @@ import ( type SecondFactorWriteModel struct { eventstore.WriteModel - MFAType domain.SecondFactorType - IsActive bool + MFAType domain.SecondFactorType + State domain.FactorState } func (wm *SecondFactorWriteModel) Reduce() error { @@ -17,10 +17,10 @@ func (wm *SecondFactorWriteModel) Reduce() error { switch e := event.(type) { case *policy.SecondFactorAddedEvent: wm.MFAType = e.MFAType - wm.IsActive = true + wm.State = domain.FactorStateActive case *policy.SecondFactorRemovedEvent: wm.MFAType = e.MFAType - wm.IsActive = false + wm.State = domain.FactorStateRemoved } } return wm.WriteModel.Reduce() @@ -28,8 +28,8 @@ func (wm *SecondFactorWriteModel) Reduce() error { type MultiFactoryWriteModel struct { eventstore.WriteModel - MFAType domain.MultiFactorType - IsActive bool + MFAType domain.MultiFactorType + State domain.FactorState } func (wm *MultiFactoryWriteModel) Reduce() error { @@ -37,10 +37,10 @@ func (wm *MultiFactoryWriteModel) Reduce() error { switch e := event.(type) { case *policy.MultiFactorAddedEvent: wm.MFAType = e.MFAType - wm.IsActive = true + wm.State = domain.FactorStateActive case *policy.MultiFactorRemovedEvent: wm.MFAType = e.MFAType - wm.IsActive = false + wm.State = domain.FactorStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/policy_login_model.go b/internal/v2/command/policy_login_model.go index c37a1d12b5..ee42524ac9 100644 --- a/internal/v2/command/policy_login_model.go +++ b/internal/v2/command/policy_login_model.go @@ -14,7 +14,7 @@ type LoginPolicyWriteModel struct { AllowExternalIDP bool ForceMFA bool PasswordlessType domain.PasswordlessType - IsActive bool + State domain.PolicyState } func (wm *LoginPolicyWriteModel) Reduce() error { @@ -26,7 +26,7 @@ func (wm *LoginPolicyWriteModel) Reduce() error { wm.AllowExternalIDP = e.AllowExternalIDP wm.ForceMFA = e.ForceMFA wm.PasswordlessType = e.PasswordlessType - wm.IsActive = true + wm.State = domain.PolicyStateActive case *policy.LoginPolicyChangedEvent: if e.AllowRegister != nil { wm.AllowRegister = *e.AllowRegister @@ -44,7 +44,7 @@ func (wm *LoginPolicyWriteModel) Reduce() error { wm.PasswordlessType = *e.PasswordlessType } case *policy.LoginPolicyRemovedEvent: - wm.IsActive = false + wm.State = domain.PolicyStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/policy_org_iam_model.go b/internal/v2/command/policy_org_iam_model.go index 06c19057ec..5b95c619f9 100644 --- a/internal/v2/command/policy_org_iam_model.go +++ b/internal/v2/command/policy_org_iam_model.go @@ -2,6 +2,7 @@ package command import ( "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/policy" ) @@ -9,7 +10,7 @@ type PolicyOrgIAMWriteModel struct { eventstore.WriteModel UserLoginMustBeDomain bool - IsActive bool + State domain.PolicyState } func (wm *PolicyOrgIAMWriteModel) Reduce() error { @@ -17,7 +18,7 @@ func (wm *PolicyOrgIAMWriteModel) Reduce() error { switch e := event.(type) { case *policy.OrgIAMPolicyAddedEvent: wm.UserLoginMustBeDomain = e.UserLoginMustBeDomain - wm.IsActive = true + wm.State = domain.PolicyStateActive case *policy.OrgIAMPolicyChangedEvent: if e.UserLoginMustBeDomain != nil { wm.UserLoginMustBeDomain = *e.UserLoginMustBeDomain diff --git a/internal/v2/command/policy_password_age_model.go b/internal/v2/command/policy_password_age_model.go index d3c6ba31c1..d9dc244f7a 100644 --- a/internal/v2/command/policy_password_age_model.go +++ b/internal/v2/command/policy_password_age_model.go @@ -2,6 +2,7 @@ package command import ( "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/policy" ) @@ -10,7 +11,7 @@ type PasswordAgePolicyWriteModel struct { ExpireWarnDays uint64 MaxAgeDays uint64 - IsActive bool + State domain.PolicyState } func (wm *PasswordAgePolicyWriteModel) Reduce() error { @@ -19,7 +20,7 @@ func (wm *PasswordAgePolicyWriteModel) Reduce() error { case *policy.PasswordAgePolicyAddedEvent: wm.ExpireWarnDays = e.ExpireWarnDays wm.MaxAgeDays = e.MaxAgeDays - wm.IsActive = true + wm.State = domain.PolicyStateActive case *policy.PasswordAgePolicyChangedEvent: if e.ExpireWarnDays != nil { wm.ExpireWarnDays = *e.ExpireWarnDays @@ -28,7 +29,7 @@ func (wm *PasswordAgePolicyWriteModel) Reduce() error { wm.ExpireWarnDays = *e.ExpireWarnDays } case *policy.PasswordAgePolicyRemovedEvent: - wm.IsActive = false + wm.State = domain.PolicyStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/policy_password_complexity_model.go b/internal/v2/command/policy_password_complexity_model.go index 5fcdb80d48..fd4e24d36a 100644 --- a/internal/v2/command/policy_password_complexity_model.go +++ b/internal/v2/command/policy_password_complexity_model.go @@ -2,6 +2,7 @@ package command import ( "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/policy" ) @@ -13,7 +14,7 @@ type PasswordComplexityPolicyWriteModel struct { HasUpperCase bool HasNumber bool HasSymbol bool - IsActive bool + State domain.PolicyState } func (wm *PasswordComplexityPolicyWriteModel) Reduce() error { @@ -25,7 +26,7 @@ func (wm *PasswordComplexityPolicyWriteModel) Reduce() error { wm.HasUpperCase = e.HasUpperCase wm.HasNumber = e.HasNumber wm.HasSymbol = e.HasSymbol - wm.IsActive = true + wm.State = domain.PolicyStateActive case *policy.PasswordComplexityPolicyChangedEvent: if e.MinLength != nil { wm.MinLength = *e.MinLength @@ -43,7 +44,7 @@ func (wm *PasswordComplexityPolicyWriteModel) Reduce() error { wm.HasSymbol = *e.HasSymbol } case *policy.PasswordComplexityPolicyRemovedEvent: - wm.IsActive = false + wm.State = domain.PolicyStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/policy_password_lockout_model.go b/internal/v2/command/policy_password_lockout_model.go index d653942023..4049d05f7b 100644 --- a/internal/v2/command/policy_password_lockout_model.go +++ b/internal/v2/command/policy_password_lockout_model.go @@ -2,6 +2,7 @@ package command import ( "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/policy" ) @@ -10,7 +11,7 @@ type PasswordLockoutPolicyWriteModel struct { MaxAttempts uint64 ShowLockOutFailures bool - IsActive bool + State domain.PolicyState } func (wm *PasswordLockoutPolicyWriteModel) Reduce() error { @@ -19,7 +20,7 @@ func (wm *PasswordLockoutPolicyWriteModel) Reduce() error { case *policy.PasswordLockoutPolicyAddedEvent: wm.MaxAttempts = e.MaxAttempts wm.ShowLockOutFailures = e.ShowLockOutFailures - wm.IsActive = true + wm.State = domain.PolicyStateActive case *policy.PasswordLockoutPolicyChangedEvent: if e.MaxAttempts != nil { wm.MaxAttempts = *e.MaxAttempts @@ -28,7 +29,7 @@ func (wm *PasswordLockoutPolicyWriteModel) Reduce() error { wm.ShowLockOutFailures = *e.ShowLockOutFailures } case *policy.PasswordLockoutPolicyRemovedEvent: - wm.IsActive = false + wm.State = domain.PolicyStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/user.go b/internal/v2/command/user.go index c6e9ed37b1..000e2dc36e 100644 --- a/internal/v2/command/user.go +++ b/internal/v2/command/user.go @@ -8,19 +8,19 @@ import ( "github.com/caos/zitadel/internal/v2/repository/user" ) -func (r *CommandSide) AddUser(ctx context.Context, user *domain.User) (*domain.User, error) { +func (r *CommandSide) AddUser(ctx context.Context, orgID string, user *domain.User) (*domain.User, error) { if !user.IsValid() { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2N9fs", "Errors.User.Invalid") } if user.Human != nil { - human, err := r.AddHuman(ctx, user.ResourceOwner, user.UserName, user.Human) + human, err := r.AddHuman(ctx, orgID, user.UserName, user.Human) if err != nil { return nil, err } return &domain.User{UserName: user.UserName, Human: human}, nil } else if user.Machine != nil { - machine, err := r.AddMachine(ctx, user.ResourceOwner, user.UserName, user.Machine) + machine, err := r.AddMachine(ctx, orgID, user.UserName, user.Machine) if err != nil { return nil, err } @@ -29,13 +29,13 @@ func (r *CommandSide) AddUser(ctx context.Context, user *domain.User) (*domain.U return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-8K0df", "Errors.User.TypeUndefined") } -func (r *CommandSide) RegisterUser(ctx context.Context, user *domain.User) (*domain.User, error) { +func (r *CommandSide) RegisterUser(ctx context.Context, orgID string, user *domain.User) (*domain.User, error) { if !user.IsValid() { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2N9fs", "Errors.User.Invalid") } if user.Human != nil { - human, err := r.RegisterHuman(ctx, user.ResourceOwner, user.UserName, user.Human, nil) + human, err := r.RegisterHuman(ctx, orgID, user.UserName, user.Human, nil) if err != nil { return nil, err } @@ -44,13 +44,46 @@ func (r *CommandSide) RegisterUser(ctx context.Context, user *domain.User) (*dom return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-8K0df", "Errors.User.TypeUndefined") } +func (r *CommandSide) ChangeUsername(ctx context.Context, orgID, userID, userName string) error { + if orgID == "" || userID == "" || userName == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2N9fs", "Errors.IDMissing") + } + existingUser, err := r.userWriteModelByID(ctx, userID) + if err != nil { + return err + } + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-5N9ds", "Errors.User.NotFound") + } + if existingUser.UserName == userName { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UsernameNotChanged") + } + + orgIAMPolicy, err := r.GetOrgIAMPolicy(ctx, orgID) + if err != nil { + return err + } + if err := CheckOrgIAMPolicyForUserName(userName, orgIAMPolicy); err != nil { + return err + } + userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) + userAgg.PushEvents(user.NewUsernameChangedEvent(ctx, userName)) + //TODO: Check Uniqueness + //TODO: release old username, set new unique username + + return r.eventstore.PushAggregate(ctx, existingUser, userAgg) +} + func (r *CommandSide) DeactivateUser(ctx context.Context, userID string) (*domain.User, error) { + if userID == "" { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0gDf", "Errors.User.UserIDMissing") + } existingUser, err := r.userWriteModelByID(ctx, userID) if err != nil { return nil, err } - if existingUser.UserState != domain.UserStateUnspecified || existingUser.UserState != domain.UserStateDeleted { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-3M9ds", "Errors.User.NotFound") + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9ds", "Errors.User.NotFound") } if existingUser.UserState == domain.UserStateInactive { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sf", "Errors.User.AlreadyInactive") @@ -66,12 +99,15 @@ func (r *CommandSide) DeactivateUser(ctx context.Context, userID string) (*domai } func (r *CommandSide) ReactivateUser(ctx context.Context, userID string) (*domain.User, error) { + if userID == "" { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M9ds", "Errors.User.UserIDMissing") + } existingUser, err := r.userWriteModelByID(ctx, userID) if err != nil { return nil, err } - if existingUser.UserState != domain.UserStateUnspecified || existingUser.UserState != domain.UserStateDeleted { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-4M0sd", "Errors.User.NotFound") + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-4M0sd", "Errors.User.NotFound") } if existingUser.UserState != domain.UserStateInactive { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0sf", "Errors.User.NotInactive") @@ -87,12 +123,15 @@ func (r *CommandSide) ReactivateUser(ctx context.Context, userID string) (*domai } func (r *CommandSide) LockUser(ctx context.Context, userID string) (*domain.User, error) { + if userID == "" { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0sd", "Errors.User.UserIDMissing") + } existingUser, err := r.userWriteModelByID(ctx, userID) if err != nil { return nil, err } - if existingUser.UserState != domain.UserStateUnspecified || existingUser.UserState != domain.UserStateDeleted { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-5M9fs", "Errors.User.NotFound") + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5M9fs", "Errors.User.NotFound") } if existingUser.UserState != domain.UserStateActive && existingUser.UserState != domain.UserStateInitial { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.ShouldBeActiveOrInitial") @@ -108,12 +147,15 @@ func (r *CommandSide) LockUser(ctx context.Context, userID string) (*domain.User } func (r *CommandSide) UnlockUser(ctx context.Context, userID string) (*domain.User, error) { + if userID == "" { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M0dse", "Errors.User.UserIDMissing") + } existingUser, err := r.userWriteModelByID(ctx, userID) if err != nil { return nil, err } - if existingUser.UserState != domain.UserStateUnspecified || existingUser.UserState != domain.UserStateDeleted { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-M0dos", "Errors.User.NotFound") + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-M0dos", "Errors.User.NotFound") } if existingUser.UserState != domain.UserStateLocked { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.NotLocked") @@ -129,16 +171,20 @@ func (r *CommandSide) UnlockUser(ctx context.Context, userID string) (*domain.Us } func (r *CommandSide) RemoveUser(ctx context.Context, userID string) error { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing") + } existingUser, err := r.userWriteModelByID(ctx, userID) if err != nil { return err } - if existingUser.UserState != domain.UserStateDeleted { - return caos_errs.ThrowAlreadyExists(nil, "COMMAND-5M0od", "Errors.User.NotFound") + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-5M0od", "Errors.User.NotFound") } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg.PushEvents(user.NewUserRemovedEvent(ctx)) //TODO: release unqie username + //TODO: remove user grants return r.eventstore.PushAggregate(ctx, existingUser, userAgg) } diff --git a/internal/v2/command/user_converter.go b/internal/v2/command/user_converter.go index 0e3038360e..6a12528ab4 100644 --- a/internal/v2/command/user_converter.go +++ b/internal/v2/command/user_converter.go @@ -57,6 +57,13 @@ func writeModelToEmail(wm *HumanEmailWriteModel) *domain.Email { } } +func writeModelToPhone(wm *HumanPhoneWriteModel) *domain.Phone { + return &domain.Phone{ + ObjectRoot: writeModelToObjectRoot(wm.WriteModel), + PhoneNumber: wm.Phone, + IsPhoneVerified: wm.IsPhoneVerified, + } +} func writeModelToAddress(wm *HumanAddressWriteModel) *domain.Address { return &domain.Address{ ObjectRoot: writeModelToObjectRoot(wm.WriteModel), diff --git a/internal/v2/command/user_human.go b/internal/v2/command/user_human.go index f8c1a75a3e..022c36ec69 100644 --- a/internal/v2/command/user_human.go +++ b/internal/v2/command/user_human.go @@ -27,9 +27,13 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, huma addedHuman := NewHumanWriteModel(human.AggregateID) //TODO: Check Unique Username - human.CheckOrgIAMPolicy(username, orgIAMPolicy) + if err := human.CheckOrgIAMPolicy(username, orgIAMPolicy); err != nil { + return nil, err + } human.SetNamesAsDisplayname() - human.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg, true) + if err := human.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg, true); err != nil { + return nil, err + } userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel) addEvent := user.NewHumanAddedEvent( @@ -107,9 +111,13 @@ func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, addedHuman := NewHumanWriteModel(human.AggregateID) //TODO: Check Unique Username or unique external idp - human.CheckOrgIAMPolicy(username, orgIAMPolicy) + if err := human.CheckOrgIAMPolicy(username, orgIAMPolicy); err != nil { + return nil, err + } human.SetNamesAsDisplayname() - human.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg, true) + if err := human.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg, true); err != nil { + return nil, err + } userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel) addEvent := user.NewHumanRegisteredEvent( @@ -144,7 +152,7 @@ func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, if err != nil { return nil, err } - user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry) + userAgg.PushEvents(user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry)) } if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified { @@ -155,7 +163,9 @@ func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, if err != nil { return nil, err } - user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry) + userAgg.PushEvents(user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry)) + } else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified { + userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx)) } err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg) @@ -165,3 +175,31 @@ func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, return writeModelToHuman(addedHuman), nil } + +func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email string) (err error) { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UserIDMissing") + } + + existingEmail, err := r.emailWriteModel(ctx, userID) + if err != nil { + return err + } + if existingEmail.UserState == domain.UserStateUnspecified || existingEmail.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-2M9df", "Errors.User.NotFound") + } + if existingEmail.UserState != domain.UserStateInitial { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.AlreadyInitialised") + } + userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel) + if email != "" && existingEmail.Email != email { + changedEvent, _ := existingEmail.NewChangedEvent(ctx, email) + userAgg.PushEvents(changedEvent) + } + initCode, err := domain.NewInitUserCode(r.initializeUserCode) + if err != nil { + return err + } + userAgg.PushEvents(user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry)) + return r.eventstore.PushAggregate(ctx, existingEmail, userAgg) +} diff --git a/internal/v2/command/user_human_address.go b/internal/v2/command/user_human_address.go index 0bc42db923..d0da894481 100644 --- a/internal/v2/command/user_human_address.go +++ b/internal/v2/command/user_human_address.go @@ -12,12 +12,12 @@ func (r *CommandSide) ChangeHumanAddress(ctx context.Context, address *domain.Ad if err != nil { return nil, err } - if existingAddress.UserState == domain.UserStateUnspecified || existingAddress.UserState == domain.UserStateDeleted { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-0pLdo", "Errors.User.Address.NotFound") + if existingAddress.State == domain.AddressStateUnspecified || existingAddress.State == domain.AddressStateRemoved { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-0pLdo", "Errors.User.Address.NotFound") } changedEvent, hasChanged := existingAddress.NewChangedEvent(ctx, address.Country, address.Locality, address.PostalCode, address.Region, address.StreetAddress) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-3M0cs", "Errors.User.Address.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0cs", "Errors.User.Address.NotChanged") } userAgg := UserAggregateFromWriteModel(&existingAddress.WriteModel) userAgg.PushEvents(changedEvent) diff --git a/internal/v2/command/user_human_address_model.go b/internal/v2/command/user_human_address_model.go index 66d3a71585..7515af587f 100644 --- a/internal/v2/command/user_human_address_model.go +++ b/internal/v2/command/user_human_address_model.go @@ -16,7 +16,7 @@ type HumanAddressWriteModel struct { Region string StreetAddress string - UserState domain.UserState + State domain.AddressState } func NewHumanAddressWriteModel(userID string) *HumanAddressWriteModel { @@ -49,14 +49,14 @@ func (wm *HumanAddressWriteModel) Reduce() error { wm.PostalCode = e.PostalCode wm.Region = e.Region wm.StreetAddress = e.StreetAddress - wm.UserState = domain.UserStateActive + wm.State = domain.AddressStateActive case *user.HumanRegisteredEvent: wm.Country = e.Country wm.Locality = e.Locality wm.PostalCode = e.PostalCode wm.Region = e.Region wm.StreetAddress = e.StreetAddress - wm.UserState = domain.UserStateActive + wm.State = domain.AddressStateActive case *user.HumanAddressChangedEvent: if e.Country != nil { wm.Country = *e.Country @@ -74,7 +74,7 @@ func (wm *HumanAddressWriteModel) Reduce() error { wm.StreetAddress = *e.StreetAddress } case *user.UserRemovedEvent: - wm.UserState = domain.UserStateDeleted + wm.State = domain.AddressStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/user_human_email.go b/internal/v2/command/user_human_email.go index dea43a5a15..e47b97568b 100644 --- a/internal/v2/command/user_human_email.go +++ b/internal/v2/command/user_human_email.go @@ -5,10 +5,11 @@ import ( caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" ) func (r *CommandSide) ChangeHumanEmail(ctx context.Context, email *domain.Email) (*domain.Email, error) { - if !email.IsValid() { + if !email.IsValid() || email.AggregateID == "" { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M9sf", "Errors.Email.Invalid") } @@ -17,15 +18,25 @@ func (r *CommandSide) ChangeHumanEmail(ctx context.Context, email *domain.Email) return nil, err } if existingEmail.UserState == domain.UserStateUnspecified || existingEmail.UserState == domain.UserStateDeleted { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-0Pe4r", "Errors.User.Email.NotFound") + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-0Pe4r", "Errors.User.Email.NotFound") } changedEvent, hasChanged := existingEmail.NewChangedEvent(ctx, email.EmailAddress) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-2M9fs", "Errors.User.Email.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Email.NotChanged") } userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel) userAgg.PushEvents(changedEvent) + if email.IsEmailVerified { + userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx)) + } else { + emailCode, err := domain.NewEmailCode(r.emailVerificationCode) + if err != nil { + return nil, err + } + userAgg.PushEvents(user.NewHumanEmailCodeAddedEvent(ctx, emailCode.Code, emailCode.Expiry)) + } + err = r.eventstore.PushAggregate(ctx, existingEmail, userAgg) if err != nil { return nil, err @@ -34,6 +45,34 @@ func (r *CommandSide) ChangeHumanEmail(ctx context.Context, email *domain.Email) return writeModelToEmail(existingEmail), nil } +func (r *CommandSide) CreateHumanEmailVerificationCode(ctx context.Context, userID string) error { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") + } + + existingEmail, err := r.emailWriteModel(ctx, userID) + if err != nil { + return err + } + if existingEmail.UserState == domain.UserStateUnspecified || existingEmail.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-0Pe4r", "Errors.User.Email.NotFound") + } + if existingEmail.UserState == domain.UserStateInitial { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-E3fbw", "Errors.User.NotInitialised") + } + if existingEmail.IsEmailVerified { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9ds", "Errors.User.Email.AlreadyVerified") + } + userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel) + emailCode, err := domain.NewEmailCode(r.emailVerificationCode) + if err != nil { + return err + } + userAgg.PushEvents(user.NewHumanEmailCodeAddedEvent(ctx, emailCode.Code, emailCode.Expiry)) + + return r.eventstore.PushAggregate(ctx, existingEmail, userAgg) +} + func (r *CommandSide) emailWriteModel(ctx context.Context, userID string) (writeModel *HumanEmailWriteModel, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/v2/command/user_human_email_model.go b/internal/v2/command/user_human_email_model.go index 69fb6d99d5..917b9805cd 100644 --- a/internal/v2/command/user_human_email_model.go +++ b/internal/v2/command/user_human_email_model.go @@ -44,15 +44,18 @@ func (wm *HumanEmailWriteModel) Reduce() error { switch e := event.(type) { case *user.HumanAddedEvent: wm.Email = e.EmailAddress - wm.UserState = domain.UserStateActive + wm.UserState = domain.UserStateInitial case *user.HumanRegisteredEvent: wm.Email = e.EmailAddress - wm.UserState = domain.UserStateActive + wm.UserState = domain.UserStateInitial case *user.HumanEmailChangedEvent: wm.Email = e.EmailAddress wm.IsEmailVerified = false case *user.HumanEmailVerifiedEvent: wm.IsEmailVerified = true + if wm.UserState == domain.UserStateInitial { + wm.UserState = domain.UserStateActive + } case *user.UserRemovedEvent: wm.UserState = domain.UserStateDeleted } diff --git a/internal/v2/command/user_human_externalidp.go b/internal/v2/command/user_human_externalidp.go new file mode 100644 index 0000000000..663659e804 --- /dev/null +++ b/internal/v2/command/user_human_externalidp.go @@ -0,0 +1,52 @@ +package command + +import ( + "context" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +func (r *CommandSide) RemoveHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP) error { + return r.removeHumanExternalIDP(ctx, externalIDP, false) +} + +func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP, cascade bool) error { + if externalIDP.IsValid() { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9ds", "Errors.IDMissing") + } + + existingExternalIDP, err := r.externalIDPWriteModelByID(ctx, externalIDP.AggregateID, externalIDP.IDPConfigID, externalIDP.ExternalUserID) + if err != nil { + return err + } + if existingExternalIDP.State == domain.ExternalIDPStateUnspecified || existingExternalIDP.State == domain.ExternalIDPStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.ExternalIDP.NotFound") + } + userAgg := UserAggregateFromWriteModel(&existingExternalIDP.WriteModel) + if !cascade { + userAgg.PushEvents( + user.NewHumanExternalIDPRemovedEvent(ctx, externalIDP.IDPConfigID, externalIDP.ExternalUserID), + ) + } else { + userAgg.PushEvents( + user.NewHumanExternalIDPCascadeRemovedEvent(ctx, externalIDP.IDPConfigID, externalIDP.ExternalUserID), + ) + } + + //TODO: Release unique externalidp + return r.eventstore.PushAggregate(ctx, existingExternalIDP, userAgg) +} + +func (r *CommandSide) externalIDPWriteModelByID(ctx context.Context, userID, idpConfigID, externalUserID string) (writeModel *HumanExternalIDPWriteModel, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + writeModel = NewHumanExternalIDPWriteModel(userID, idpConfigID, externalUserID) + err = r.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + return writeModel, nil +} diff --git a/internal/v2/command/user_human_externalidp_model.go b/internal/v2/command/user_human_externalidp_model.go new file mode 100644 index 0000000000..e5a116be95 --- /dev/null +++ b/internal/v2/command/user_human_externalidp_model.go @@ -0,0 +1,72 @@ +package command + +import ( + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +type HumanExternalIDPWriteModel struct { + eventstore.WriteModel + + IDPConfigID string + ExternalUserID string + DisplayName string + + State domain.ExternalIDPState +} + +func NewHumanExternalIDPWriteModel(userID, idpConfigID, externalUserID string) *HumanExternalIDPWriteModel { + return &HumanExternalIDPWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + }, + IDPConfigID: idpConfigID, + ExternalUserID: externalUserID, + } +} + +func (wm *HumanExternalIDPWriteModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.HumanExternalIDPAddedEvent: + if wm.IDPConfigID == e.IDPConfigID && wm.ExternalUserID == e.UserID { + wm.AppendEvents(e) + } + case *user.HumanExternalIDPRemovedEvent: + if wm.IDPConfigID == e.IDPConfigID && wm.ExternalUserID == e.UserID { + wm.AppendEvents(e) + } + case *user.HumanExternalIDPCascadeRemovedEvent: + if wm.IDPConfigID == e.IDPConfigID && wm.ExternalUserID == e.UserID { + wm.AppendEvents(e) + } + case *user.UserRemovedEvent: + wm.AppendEvents(e) + } + } +} + +func (wm *HumanExternalIDPWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanExternalIDPAddedEvent: + wm.IDPConfigID = e.IDPConfigID + wm.DisplayName = e.DisplayName + wm.ExternalUserID = e.UserID + wm.State = domain.ExternalIDPStateActive + case *user.HumanExternalIDPRemovedEvent: + wm.State = domain.ExternalIDPStateRemoved + case *user.HumanExternalIDPCascadeRemovedEvent: + wm.State = domain.ExternalIDPStateRemoved + case *user.UserRemovedEvent: + wm.State = domain.ExternalIDPStateRemoved + } + } + return wm.WriteModel.Reduce() +} + +func (wm *HumanExternalIDPWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID) +} diff --git a/internal/v2/command/user_human_model.go b/internal/v2/command/user_human_model.go index a921eefe81..053232b638 100644 --- a/internal/v2/command/user_human_model.go +++ b/internal/v2/command/user_human_model.go @@ -76,6 +76,8 @@ func (wm *HumanWriteModel) Reduce() error { wm.reduceHumanAddedEvent(e) case *user.HumanRegisteredEvent: wm.reduceHumanRegisteredEvent(e) + case *user.UsernameChangedEvent: + wm.UserName = e.UserName case *user.HumanProfileChangedEvent: wm.reduceHumanProfileChangedEvent(e) case *user.HumanEmailChangedEvent: diff --git a/internal/v2/command/user_human_otp.go b/internal/v2/command/user_human_otp.go new file mode 100644 index 0000000000..484611f2a7 --- /dev/null +++ b/internal/v2/command/user_human_otp.go @@ -0,0 +1,41 @@ +package command + +import ( + "context" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +func (r *CommandSide) RemoveHumanOTP(ctx context.Context, userID string) error { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing") + } + + existingOTP, err := r.otpWriteModelByID(ctx, userID) + if err != nil { + return err + } + if existingOTP.State == domain.OTPStateUnspecified || existingOTP.State == domain.OTPStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.OTP.NotFound") + } + userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel) + userAgg.PushEvents( + user.NewHumanOTPRemovedEvent(ctx), + ) + + return r.eventstore.PushAggregate(ctx, existingOTP, userAgg) +} + +func (r *CommandSide) otpWriteModelByID(ctx context.Context, userID string) (writeModel *HumanOTPWriteModel, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + writeModel = NewHumanOTPWriteModel(userID) + err = r.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + return writeModel, nil +} diff --git a/internal/v2/command/user_human_otp_model.go b/internal/v2/command/user_human_otp_model.go new file mode 100644 index 0000000000..c010218acf --- /dev/null +++ b/internal/v2/command/user_human_otp_model.go @@ -0,0 +1,57 @@ +package command + +import ( + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +type HumanOTPWriteModel struct { + eventstore.WriteModel + + Secret *crypto.CryptoValue + + State domain.OTPState +} + +func NewHumanOTPWriteModel(userID string) *HumanOTPWriteModel { + return &HumanOTPWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + }, + } +} + +func (wm *HumanOTPWriteModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.HumanOTPAddedEvent: + wm.AppendEvents(e) + case *user.HumanOTPRemovedEvent: + wm.AppendEvents(e) + case *user.UserRemovedEvent: + wm.AppendEvents(e) + } + } +} + +func (wm *HumanOTPWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanOTPAddedEvent: + wm.Secret = e.Secret + wm.State = domain.OTPStateActive + case *user.HumanOTPRemovedEvent: + wm.State = domain.OTPStateRemoved + case *user.UserRemovedEvent: + wm.State = domain.OTPStateRemoved + } + } + return wm.WriteModel.Reduce() +} + +func (wm *HumanOTPWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID) +} diff --git a/internal/v2/command/user_human_password.go b/internal/v2/command/user_human_password.go new file mode 100644 index 0000000000..d98177db97 --- /dev/null +++ b/internal/v2/command/user_human_password.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + "github.com/caos/zitadel/internal/crypto" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +func (r *CommandSide) SetOneTimePassword(ctx context.Context, orgID, userID, passwordString string) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + existingPassword, err := r.passwordWriteModel(ctx, userID) + if err != nil { + return err + } + password := &domain.Password{ + SecretString: passwordString, + ChangeRequired: true, + } + return r.changePassword(ctx, orgID, userID, "", password, existingPassword) +} + +func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID string) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + existingPassword, err := r.passwordWriteModel(ctx, userID) + if err != nil { + return err + } + if existingPassword.Secret != nil { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Fds3s", "Errors.User.Password.Empty") + } + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash") + err = crypto.CompareHash(existingPassword.Secret, []byte(oldPassword), r.userPasswordAlg) + spanPasswordComparison.EndWithError(err) + + if err != nil { + return caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M0fs", "Errors.User.Password.Invalid") + } + password := &domain.Password{ + SecretString: newPassword, + ChangeRequired: true, + } + return r.changePassword(ctx, orgID, userID, userAgentID, password, existingPassword) +} + +func (r *CommandSide) changePassword(ctx context.Context, orgID, userID, userAgentID string, password *domain.Password, existingPassword *HumanPasswordWriteModel) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-B8hY3", "Errors.User.UserIDMissing") + } + if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-G8dh3", "Errors.User.Email.NotFound") + } + if existingPassword.UserState == domain.UserStateInitial { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M9dse", "Errors.User.NotInitialised") + } + pwPolicy, err := r.GetOrgPasswordComplexityPolicy(ctx, orgID) + if err != nil { + return err + } + if err := password.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg); err != nil { + return err + } + userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel) + userAgg.PushEvents(user.NewHumanPasswordChangedEvent(ctx, password.SecretCrypto, password.ChangeRequired, userAgentID)) + return r.eventstore.PushAggregate(ctx, existingPassword, userAgg) +} + +func (r *CommandSide) RequestSetPassword(ctx context.Context, userID string, notifyType domain.NotificationType) (err error) { + existingHuman, err := r.userWriteModelByID(ctx, userID) + if err != nil { + return err + } + if existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-Hj9ds", "Errors.User.NotFound") + } + if existingHuman.UserState == domain.UserStateInitial { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.NotInitialised") + } + userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel) + passwordCode, err := domain.NewPasswordCode(r.passwordVerificationCode) + if err != nil { + return err + } + userAgg.PushEvents(user.NewHumanPasswordCodeAddedEvent(ctx, passwordCode.Code, passwordCode.Expiry, notifyType)) + return r.eventstore.PushAggregate(ctx, existingHuman, userAgg) +} + +func (r *CommandSide) passwordWriteModel(ctx context.Context, userID string) (writeModel *HumanPasswordWriteModel, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + writeModel = NewHumanPasswordWriteModel(userID) + err = r.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + return writeModel, nil +} diff --git a/internal/v2/command/user_human_password_model.go b/internal/v2/command/user_human_password_model.go new file mode 100644 index 0000000000..a61a0c2c0e --- /dev/null +++ b/internal/v2/command/user_human_password_model.go @@ -0,0 +1,70 @@ +package command + +import ( + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +type HumanPasswordWriteModel struct { + eventstore.WriteModel + + Secret *crypto.CryptoValue + SecretChangeRequired bool + + UserState domain.UserState +} + +func NewHumanPasswordWriteModel(userID string) *HumanPasswordWriteModel { + return &HumanPasswordWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + }, + } +} + +func (wm *HumanPasswordWriteModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.HumanPasswordChangedEvent: + wm.AppendEvents(e) + case *user.HumanAddedEvent, *user.HumanRegisteredEvent: + wm.AppendEvents(e) + case *user.HumanEmailVerifiedEvent: + wm.AppendEvents(e) + case *user.UserRemovedEvent: + wm.AppendEvents(e) + } + } +} + +func (wm *HumanPasswordWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanAddedEvent: + wm.Secret = e.Secret + wm.SecretChangeRequired = e.ChangeRequired + wm.UserState = domain.UserStateInitial + case *user.HumanRegisteredEvent: + wm.Secret = e.Secret + wm.SecretChangeRequired = e.ChangeRequired + wm.UserState = domain.UserStateActive + case *user.HumanPasswordChangedEvent: + wm.Secret = e.Secret + wm.SecretChangeRequired = e.ChangeRequired + case *user.HumanEmailVerifiedEvent: + if wm.UserState == domain.UserStateInitial { + wm.UserState = domain.UserStateActive + } + case *user.UserRemovedEvent: + wm.UserState = domain.UserStateDeleted + } + } + return wm.WriteModel.Reduce() +} + +func (wm *HumanPasswordWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID) +} diff --git a/internal/v2/command/user_human_phone.go b/internal/v2/command/user_human_phone.go new file mode 100644 index 0000000000..e4355ed40c --- /dev/null +++ b/internal/v2/command/user_human_phone.go @@ -0,0 +1,101 @@ +package command + +import ( + "context" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +func (r *CommandSide) ChangeHumanPhone(ctx context.Context, phone *domain.Phone) (*domain.Phone, error) { + if !phone.IsValid() { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.Phone.Invalid") + } + + existingPhone, err := r.phoneWriteModel(ctx, phone.AggregateID) + if err != nil { + return nil, err + } + if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.Phone.NotFound") + } + changedEvent, hasChanged := existingPhone.NewChangedEvent(ctx, phone.PhoneNumber) + if !hasChanged { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-wF94r", "Errors.User.Phone.NotChanged") + } + userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel) + userAgg.PushEvents(changedEvent) + + if phone.IsPhoneVerified { + userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx)) + } else { + phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode) + if err != nil { + return nil, err + } + userAgg.PushEvents(user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry)) + } + + err = r.eventstore.PushAggregate(ctx, existingPhone, userAgg) + if err != nil { + return nil, err + } + + return writeModelToPhone(existingPhone), nil +} + +func (r *CommandSide) CreateHumanPhoneVerificationCode(ctx context.Context, userID string) error { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") + } + + existingPhone, err := r.phoneWriteModel(ctx, userID) + if err != nil { + return err + } + if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-2M9fs", "Errors.User.Phone.NotFound") + } + if existingPhone.IsPhoneVerified { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified") + } + userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel) + phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode) + if err != nil { + return err + } + userAgg.PushEvents(user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry)) + return r.eventstore.PushAggregate(ctx, existingPhone, userAgg) +} + +func (r *CommandSide) RemoveHumanPhone(ctx context.Context, userID string) error { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.UserIDMissing") + } + + existingPhone, err := r.phoneWriteModel(ctx, userID) + if err != nil { + return err + } + if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.Phone.NotFound") + } + userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel) + userAgg.PushEvents( + user.NewHumanPhoneRemovedEvent(ctx), + ) + return r.eventstore.PushAggregate(ctx, existingPhone, userAgg) +} + +func (r *CommandSide) phoneWriteModel(ctx context.Context, userID string) (writeModel *HumanPhoneWriteModel, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + writeModel = NewHumanPhoneWriteModel(userID) + err = r.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + return writeModel, nil +} diff --git a/internal/v2/command/user_human_phone_model.go b/internal/v2/command/user_human_phone_model.go new file mode 100644 index 0000000000..34ad3fe6af --- /dev/null +++ b/internal/v2/command/user_human_phone_model.go @@ -0,0 +1,88 @@ +package command + +import ( + "context" + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +type HumanPhoneWriteModel struct { + eventstore.WriteModel + + Phone string + IsPhoneVerified bool + + State domain.PhoneState +} + +func NewHumanPhoneWriteModel(userID string) *HumanPhoneWriteModel { + return &HumanPhoneWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + }, + } +} + +func (wm *HumanPhoneWriteModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.HumanAddedEvent, *user.HumanRegisteredEvent: + wm.AppendEvents(e) + case *user.HumanPhoneChangedEvent: + wm.AppendEvents(e) + case *user.HumanPhoneVerifiedEvent: + wm.AppendEvents(e) + case *user.HumanPhoneRemovedEvent: + wm.AppendEvents(e) + case *user.UserRemovedEvent: + wm.AppendEvents(e) + } + } +} + +func (wm *HumanPhoneWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanAddedEvent: + if e.PhoneNumber != "" { + wm.Phone = e.PhoneNumber + wm.State = domain.PhoneStateActive + } + case *user.HumanRegisteredEvent: + if e.PhoneNumber != "" { + wm.Phone = e.PhoneNumber + wm.State = domain.PhoneStateActive + } + case *user.HumanPhoneChangedEvent: + wm.Phone = e.PhoneNumber + wm.IsPhoneVerified = false + wm.State = domain.PhoneStateActive + case *user.HumanPhoneVerifiedEvent: + wm.IsPhoneVerified = true + case *user.HumanPhoneRemovedEvent: + wm.State = domain.PhoneStateRemoved + case *user.UserRemovedEvent: + wm.State = domain.PhoneStateRemoved + } + } + return wm.WriteModel.Reduce() +} + +func (wm *HumanPhoneWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID) +} + +func (wm *HumanPhoneWriteModel) NewChangedEvent( + ctx context.Context, + phone string, +) (*user.HumanPhoneChangedEvent, bool) { + hasChanged := false + changedEvent := user.NewHumanPhoneChangedEvent(ctx) + if wm.Phone != phone { + hasChanged = true + changedEvent.PhoneNumber = phone + } + return changedEvent, hasChanged +} diff --git a/internal/v2/command/user_human_profile.go b/internal/v2/command/user_human_profile.go index 16b261b855..445aeb512e 100644 --- a/internal/v2/command/user_human_profile.go +++ b/internal/v2/command/user_human_profile.go @@ -8,7 +8,7 @@ import ( ) func (r *CommandSide) ChangeHumanProfile(ctx context.Context, profile *domain.Profile) (*domain.Profile, error) { - if !profile.IsValid() { + if !profile.IsValid() && profile.AggregateID != "" { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8io0d", "Errors.User.Profile.Invalid") } @@ -17,11 +17,11 @@ func (r *CommandSide) ChangeHumanProfile(ctx context.Context, profile *domain.Pr return nil, err } if existingProfile.UserState == domain.UserStateUnspecified || existingProfile.UserState == domain.UserStateDeleted { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-3M9sd", "Errors.User.Profile.NotFound") + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.User.Profile.NotFound") } changedEvent, hasChanged := existingProfile.NewChangedEvent(ctx, profile.FirstName, profile.LastName, profile.NickName, profile.DisplayName, profile.PreferredLanguage, domain.Gender(profile.Gender)) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-2M0fs", "Errors.User.Profile.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.User.Profile.NotChanged") } userAgg := UserAggregateFromWriteModel(&existingProfile.WriteModel) userAgg.PushEvents(changedEvent) diff --git a/internal/v2/command/user_human_webauthn.go b/internal/v2/command/user_human_webauthn.go new file mode 100644 index 0000000000..ade816778b --- /dev/null +++ b/internal/v2/command/user_human_webauthn.go @@ -0,0 +1,50 @@ +package command + +import ( + "context" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +func (r *CommandSide) RemoveHumanU2F(ctx context.Context, userID, webAuthNID string) error { + event := user.NewHumanU2FRemovedEvent(ctx, webAuthNID) + return r.removeHumanWebAuthN(ctx, userID, webAuthNID, event) +} + +func (r *CommandSide) RemoveHumanPasswordless(ctx context.Context, userID, webAuthNID string) error { + event := user.NewHumanPasswordlessRemovedEvent(ctx, webAuthNID) + return r.removeHumanWebAuthN(ctx, userID, webAuthNID, event) +} + +func (r *CommandSide) removeHumanWebAuthN(ctx context.Context, userID, webAuthNID string, event eventstore.EventPusher) error { + if userID == "" || webAuthNID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M9de", "Errors.IDMissing") + } + + existingWebAuthN, err := r.webauthNWriteModelByID(ctx, userID, webAuthNID) + if err != nil { + return err + } + if existingWebAuthN.State == domain.WebAuthNStateUnspecified || existingWebAuthN.State == domain.WebAuthNStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.ExternalIDP.NotFound") + } + userAgg := UserAggregateFromWriteModel(&existingWebAuthN.WriteModel) + userAgg.PushEvents(event) + + return r.eventstore.PushAggregate(ctx, existingWebAuthN, userAgg) +} + +func (r *CommandSide) webauthNWriteModelByID(ctx context.Context, userID, webAuthNID string) (writeModel *HumanWebAuthNWriteModel, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + writeModel = NewHumanWebAuthNWriteModel(userID, webAuthNID) + err = r.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + return writeModel, nil +} diff --git a/internal/v2/command/user_human_webauthn_model.go b/internal/v2/command/user_human_webauthn_model.go new file mode 100644 index 0000000000..968cb7795b --- /dev/null +++ b/internal/v2/command/user_human_webauthn_model.go @@ -0,0 +1,61 @@ +package command + +import ( + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +type HumanWebAuthNWriteModel struct { + eventstore.WriteModel + + WebauthNTokenID string + + State domain.WebAuthNState +} + +func NewHumanWebAuthNWriteModel(userID, wbAuthNTokenID string) *HumanWebAuthNWriteModel { + return &HumanWebAuthNWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + }, + WebauthNTokenID: wbAuthNTokenID, + } +} + +func (wm *HumanWebAuthNWriteModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.HumanWebAuthNAddedEvent: + if wm.WebauthNTokenID == e.WebAuthNTokenID { + wm.AppendEvents(e) + } + case *user.HumanWebAuthNRemovedEvent: + if wm.WebauthNTokenID == e.WebAuthNTokenID { + wm.AppendEvents(e) + } + case *user.UserRemovedEvent: + wm.AppendEvents(e) + } + } +} + +func (wm *HumanWebAuthNWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanWebAuthNAddedEvent: + wm.WebauthNTokenID = e.WebAuthNTokenID + wm.State = domain.WebAuthNStateActive + case *user.HumanWebAuthNRemovedEvent: + wm.State = domain.WebAuthNStateRemoved + case *user.UserRemovedEvent: + wm.State = domain.WebAuthNStateRemoved + } + } + return wm.WriteModel.Reduce() +} + +func (wm *HumanWebAuthNWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID) +} diff --git a/internal/v2/command/user_machine.go b/internal/v2/command/user_machine.go index ba10508e11..69d911ac77 100644 --- a/internal/v2/command/user_machine.go +++ b/internal/v2/command/user_machine.go @@ -45,12 +45,12 @@ func (r *CommandSide) ChangeMachine(ctx context.Context, machine *domain.Machine return nil, err } if existingUser.UserState == domain.UserStateDeleted || existingUser.UserState == domain.UserStateUnspecified { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-5M0od", "Errors.User.NotFound") + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5M0od", "Errors.User.NotFound") } changedEvent, hasChanged := existingUser.NewChangedEvent(ctx, machine.Name, machine.Description) if !hasChanged { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-2M9fs", "Errors.User.Email.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Email.NotChanged") } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg.PushEvents(changedEvent) diff --git a/internal/v2/command/user_machine_write_model.go b/internal/v2/command/user_machine_model.go similarity index 95% rename from internal/v2/command/user_machine_write_model.go rename to internal/v2/command/user_machine_model.go index ea6783d741..fa947d90c7 100644 --- a/internal/v2/command/user_machine_write_model.go +++ b/internal/v2/command/user_machine_model.go @@ -30,6 +30,8 @@ func (wm *MachineWriteModel) AppendEvents(events ...eventstore.EventReader) { switch e := event.(type) { case *user.MachineAddedEvent: wm.AppendEvents(e) + case *user.UsernameChangedEvent: + wm.AppendEvents(e) case *user.MachineChangedEvent: wm.AppendEvents(e) case *user.UserDeactivatedEvent: @@ -55,6 +57,8 @@ func (wm *MachineWriteModel) Reduce() error { wm.Name = e.Name wm.Description = e.Description wm.UserState = domain.UserStateActive + case *user.UsernameChangedEvent: + wm.UserName = e.UserName case *user.MachineChangedEvent: if e.Name != nil { wm.Name = *e.Name diff --git a/internal/v2/command/user_model.go b/internal/v2/command/user_model.go index 5a31d4aa56..2326644700 100644 --- a/internal/v2/command/user_model.go +++ b/internal/v2/command/user_model.go @@ -1,9 +1,11 @@ package command import ( + caos_errors "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/user" + "strings" ) type UserWriteModel struct { @@ -24,14 +26,12 @@ func NewUserWriteModel(userID string) *UserWriteModel { func (wm *UserWriteModel) AppendEvents(events ...eventstore.EventReader) { for _, event := range events { switch e := event.(type) { - case *user.HumanEmailChangedEvent: - wm.AppendEvents(e) - case *user.HumanEmailVerifiedEvent: - wm.AppendEvents(e) case *user.HumanAddedEvent, *user.HumanRegisteredEvent: wm.AppendEvents(e) case *user.MachineAddedEvent: wm.AppendEvents(e) + case *user.UsernameChangedEvent: + wm.AppendEvents(e) case *user.UserDeactivatedEvent: wm.AppendEvents(e) case *user.UserReactivatedEvent: @@ -56,10 +56,11 @@ func (wm *UserWriteModel) Reduce() error { case *user.HumanRegisteredEvent: wm.UserName = e.UserName wm.UserState = domain.UserStateInitial - case *user.MachineAddedEvent: wm.UserName = e.UserName wm.UserState = domain.UserStateActive + case *user.UsernameChangedEvent: + wm.UserName = e.UserName case *user.UserLockedEvent: if wm.UserState != domain.UserStateDeleted { wm.UserState = domain.UserStateLocked @@ -93,3 +94,13 @@ func UserAggregateFromWriteModel(wm *eventstore.WriteModel) *user.Aggregate { Aggregate: *eventstore.AggregateFromWriteModel(wm, user.AggregateType, user.AggregateVersion), } } + +func CheckOrgIAMPolicyForUserName(userName string, policy *domain.OrgIAMPolicy) error { + if policy == nil { + return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-3Mb9s", "Errors.Users.OrgIamPolicyNil") + } + if policy.UserLoginMustBeDomain && strings.Contains(userName, "@") { + return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-4M9vs", "Errors.User.EmailAsUsernameNotAllowed") + } + return nil +} diff --git a/internal/v2/domain/factors.go b/internal/v2/domain/factors.go index 862a59e46b..7965ae7e37 100644 --- a/internal/v2/domain/factors.go +++ b/internal/v2/domain/factors.go @@ -14,3 +14,17 @@ const ( MultiFactorTypeUnspecified MultiFactorType = iota MultiFactorTypeU2FWithPIN ) + +type FactorState int32 + +const ( + FactorStateUnspecified FactorState = iota + FactorStateActive + FactorStateRemoved + + factorStateCount +) + +func (f FactorState) Valid() bool { + return f >= 0 && f < factorStateCount +} diff --git a/internal/v2/domain/human.go b/internal/v2/domain/human.go index 61b91a61d1..2b11d1f0a9 100644 --- a/internal/v2/domain/human.go +++ b/internal/v2/domain/human.go @@ -71,7 +71,8 @@ func (u *Human) SetNamesAsDisplayname() { func (u *Human) HashPasswordIfExisting(policy *PasswordComplexityPolicy, passwordAlg crypto.HashAlgorithm, onetime bool) error { if u.Password != nil { - return u.Password.HashPasswordIfExisting(policy, passwordAlg, onetime) + u.Password.ChangeRequired = onetime + return u.Password.HashPasswordIfExisting(policy, passwordAlg) } return nil } diff --git a/internal/v2/domain/human_address.go b/internal/v2/domain/human_address.go index b216b0bb63..1b89a26e2e 100644 --- a/internal/v2/domain/human_address.go +++ b/internal/v2/domain/human_address.go @@ -11,3 +11,17 @@ type Address struct { Region string StreetAddress string } + +type AddressState int32 + +const ( + AddressStateUnspecified AddressState = iota + AddressStateActive + AddressStateRemoved + + addressStateCount +) + +func (s AddressState) Valid() bool { + return s >= 0 && s < addressStateCount +} diff --git a/internal/v2/domain/human_email.go b/internal/v2/domain/human_email.go index 344fb4069b..c27e2e37c7 100644 --- a/internal/v2/domain/human_email.go +++ b/internal/v2/domain/human_email.go @@ -23,3 +23,14 @@ type EmailCode struct { func (e *Email) IsValid() bool { return e.EmailAddress != "" } + +func NewEmailCode(emailGenerator crypto.Generator) (*EmailCode, error) { + emailCodeCrypto, _, err := crypto.NewCode(emailGenerator) + if err != nil { + return nil, err + } + return &EmailCode{ + Code: emailCodeCrypto, + Expiry: emailGenerator.Expiry(), + }, nil +} diff --git a/internal/v2/domain/human_external_idp.go b/internal/v2/domain/human_external_idp.go index 0c7c1f1f52..ad9433959e 100644 --- a/internal/v2/domain/human_external_idp.go +++ b/internal/v2/domain/human_external_idp.go @@ -5,7 +5,25 @@ import es_models "github.com/caos/zitadel/internal/eventstore/models" type ExternalIDP struct { es_models.ObjectRoot - IDPConfigID string - UserID string - DisplayName string + IDPConfigID string + ExternalUserID string + DisplayName string +} + +func (idp *ExternalIDP) IsValid() bool { + return idp.AggregateID != "" && idp.IDPConfigID != "" && idp.ExternalUserID != "" +} + +type ExternalIDPState int32 + +const ( + ExternalIDPStateUnspecified ExternalIDPState = iota + ExternalIDPStateActive + ExternalIDPStateRemoved + + externalIDPStateCount +) + +func (s ExternalIDPState) Valid() bool { + return s >= 0 && s < externalIDPStateCount } diff --git a/internal/v2/domain/human_otp.go b/internal/v2/domain/human_otp.go index 28051da1fd..bc633071a6 100644 --- a/internal/v2/domain/human_otp.go +++ b/internal/v2/domain/human_otp.go @@ -13,3 +13,17 @@ type OTP struct { Url string State MFAState } + +type OTPState int32 + +const ( + OTPStateUnspecified OTPState = iota + OTPStateActive + OTPStateRemoved + + otpStateCount +) + +func (s OTPState) Valid() bool { + return s >= 0 && s < otpStateCount +} diff --git a/internal/v2/domain/human_password.go b/internal/v2/domain/human_password.go index 24f3b257f6..136a5299f4 100644 --- a/internal/v2/domain/human_password.go +++ b/internal/v2/domain/human_password.go @@ -23,7 +23,7 @@ type PasswordCode struct { NotificationType NotificationType } -func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, passwordAlg crypto.HashAlgorithm, onetime bool) error { +func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, passwordAlg crypto.HashAlgorithm) error { if p.SecretString == "" { return nil } @@ -38,6 +38,16 @@ func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, pass return err } p.SecretCrypto = secret - p.ChangeRequired = onetime return nil } + +func NewPasswordCode(passwordGenerator crypto.Generator) (*PasswordCode, error) { + passwordCodeCrypto, _, err := crypto.NewCode(passwordGenerator) + if err != nil { + return nil, err + } + return &PasswordCode{ + Code: passwordCodeCrypto, + Expiry: passwordGenerator.Expiry(), + }, nil +} diff --git a/internal/v2/domain/human_phone.go b/internal/v2/domain/human_phone.go index 3ba7b027a0..123472d73b 100644 --- a/internal/v2/domain/human_phone.go +++ b/internal/v2/domain/human_phone.go @@ -50,3 +50,17 @@ func NewPhoneCode(phoneGenerator crypto.Generator) (*PhoneCode, error) { Expiry: phoneGenerator.Expiry(), }, nil } + +type PhoneState int32 + +const ( + PhoneStateUnspecified PhoneState = iota + PhoneStateActive + PhoneStateRemoved + + phoneStateCount +) + +func (s PhoneState) Valid() bool { + return s >= 0 && s < phoneStateCount +} diff --git a/internal/v2/domain/human_web_auth_n.go b/internal/v2/domain/human_web_auth_n.go index 69c43972ae..2e93f32531 100644 --- a/internal/v2/domain/human_web_auth_n.go +++ b/internal/v2/domain/human_web_auth_n.go @@ -38,3 +38,17 @@ const ( UserVerificationRequirementPreferred UserVerificationRequirementDiscouraged ) + +type WebAuthNState int32 + +const ( + WebAuthNStateUnspecified WebAuthNState = iota + WebAuthNStateActive + WebAuthNStateRemoved + + webAuthNStateCount +) + +func (s WebAuthNState) Valid() bool { + return s >= 0 && s < webAuthNStateCount +} diff --git a/internal/v2/domain/iam_member.go b/internal/v2/domain/iam_member.go index a4f8941197..2a0f9115b1 100644 --- a/internal/v2/domain/iam_member.go +++ b/internal/v2/domain/iam_member.go @@ -14,3 +14,17 @@ type IAMMember struct { func (i *IAMMember) IsValid() bool { return i.AggregateID != "" && i.UserID != "" && len(i.Roles) != 0 } + +type MemberState int32 + +const ( + MemberStateUnspecified MemberState = iota + MemberStateActive + MemberStateRemoved + + memberStateCount +) + +func (f MemberState) Valid() bool { + return f >= 0 && f < memberStateCount +} diff --git a/internal/v2/domain/policy.go b/internal/v2/domain/policy.go new file mode 100644 index 0000000000..c5bb817687 --- /dev/null +++ b/internal/v2/domain/policy.go @@ -0,0 +1,15 @@ +package domain + +type PolicyState int32 + +const ( + PolicyStateUnspecified PolicyState = iota + PolicyStateActive + PolicyStateRemoved + + policyStateCount +) + +func (f PolicyState) Valid() bool { + return f >= 0 && f < policyStateCount +} diff --git a/internal/v2/domain/provider.go b/internal/v2/domain/provider.go index 5cf6c64f07..b21a5e2a61 100644 --- a/internal/v2/domain/provider.go +++ b/internal/v2/domain/provider.go @@ -12,3 +12,17 @@ const ( func (f IdentityProviderType) Valid() bool { return f >= 0 && f < identityProviderCount } + +type IdentityProviderState int32 + +const ( + IdentityProviderStateUnspecified IdentityProviderState = iota + IdentityProviderStateActive + IdentityProviderStateRemoved + + idpProviderState +) + +func (s IdentityProviderState) Valid() bool { + return s >= 0 && s < idpProviderState +} diff --git a/internal/v2/repository/user/human_external_idp.go b/internal/v2/repository/user/human_external_idp.go index 72311ea094..73fc14d178 100644 --- a/internal/v2/repository/user/human_external_idp.go +++ b/internal/v2/repository/user/human_external_idp.go @@ -61,6 +61,7 @@ type HumanExternalIDPAddedEvent struct { eventstore.BaseEvent `json:"-"` IDPConfigID string `json:"idpConfigId,omitempty"` + UserID string `json:"userId,omitempty"` DisplayName string `json:"displayName,omitempty"` } @@ -96,19 +97,21 @@ type HumanExternalIDPRemovedEvent struct { eventstore.BaseEvent `json:"-"` IDPConfigID string `json:"idpConfigId"` + UserID string `json:"userId,omitempty"` } func (e *HumanExternalIDPRemovedEvent) Data() interface{} { return e } -func NewHumanExternalIDPRemovedEvent(ctx context.Context, idpConfigID string) *HumanExternalIDPRemovedEvent { +func NewHumanExternalIDPRemovedEvent(ctx context.Context, idpConfigID, externalUserID string) *HumanExternalIDPRemovedEvent { return &HumanExternalIDPRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanExternalIDPRemovedType, ), IDPConfigID: idpConfigID, + UserID: externalUserID, } } @@ -129,19 +132,21 @@ type HumanExternalIDPCascadeRemovedEvent struct { eventstore.BaseEvent `json:"-"` IDPConfigID string `json:"idpConfigId"` + UserID string `json:"userId,omitempty"` } func (e *HumanExternalIDPCascadeRemovedEvent) Data() interface{} { return e } -func NewHumanExternalIDPCascadeRemovedEvent(ctx context.Context, idpConfigID string) *HumanExternalIDPCascadeRemovedEvent { +func NewHumanExternalIDPCascadeRemovedEvent(ctx context.Context, idpConfigID, externalUserID string) *HumanExternalIDPCascadeRemovedEvent { return &HumanExternalIDPCascadeRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanExternalIDPCascadeRemovedType, ), IDPConfigID: idpConfigID, + UserID: externalUserID, } } diff --git a/internal/v2/repository/user/human_mfa_otp.go b/internal/v2/repository/user/human_mfa_otp.go index 3ba9529f3b..21dced54d1 100644 --- a/internal/v2/repository/user/human_mfa_otp.go +++ b/internal/v2/repository/user/human_mfa_otp.go @@ -7,7 +7,6 @@ import ( "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/eventstore/v2/repository" - "github.com/caos/zitadel/internal/v2/domain" ) const ( @@ -23,7 +22,6 @@ type HumanOTPAddedEvent struct { eventstore.BaseEvent `json:"-"` Secret *crypto.CryptoValue `json:"otpSecret,omitempty"` - State domain.MFAState `json:"-"` } func (e *HumanOTPAddedEvent) Data() interface{} { @@ -44,7 +42,6 @@ func NewHumanOTPAddedEvent(ctx context.Context, func HumanOTPAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { otpAdded := &HumanOTPAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - State: domain.MFAStateNotReady, } err := json.Unmarshal(event.Data, otpAdded) if err != nil { @@ -55,7 +52,6 @@ func HumanOTPAddedEventMapper(event *repository.Event) (eventstore.EventReader, type HumanOTPVerifiedEvent struct { eventstore.BaseEvent `json:"-"` - State domain.MFAState `json:"-"` } func (e *HumanOTPVerifiedEvent) Data() interface{} { @@ -74,7 +70,6 @@ func NewHumanOTPVerifiedEvent(ctx context.Context) *HumanOTPVerifiedEvent { func HumanOTPVerifiedEventMapper(event *repository.Event) (eventstore.EventReader, error) { return &HumanOTPVerifiedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - State: domain.MFAStateReady, }, nil } diff --git a/internal/v2/repository/user/human_password.go b/internal/v2/repository/user/human_password.go index e10b025526..1ed703aa48 100644 --- a/internal/v2/repository/user/human_password.go +++ b/internal/v2/repository/user/human_password.go @@ -25,6 +25,7 @@ type HumanPasswordChangedEvent struct { Secret *crypto.CryptoValue `json:"secret,omitempty"` ChangeRequired bool `json:"changeRequired"` + UserAgentID string `json:"userAgentID,omitempty"` } func (e *HumanPasswordChangedEvent) Data() interface{} { @@ -35,6 +36,7 @@ func NewHumanPasswordChangedEvent( ctx context.Context, secret *crypto.CryptoValue, changeRequired bool, + userAgentID string, ) *HumanPasswordChangedEvent { return &HumanPasswordChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -43,6 +45,7 @@ func NewHumanPasswordChangedEvent( ), Secret: secret, ChangeRequired: changeRequired, + UserAgentID: userAgentID, } } diff --git a/internal/v2/repository/user/human_phone.go b/internal/v2/repository/user/human_phone.go index a5bd2a9bbf..580fa73c6f 100644 --- a/internal/v2/repository/user/human_phone.go +++ b/internal/v2/repository/user/human_phone.go @@ -30,13 +30,12 @@ func (e *HumanPhoneChangedEvent) Data() interface{} { return e } -func NewHumanPhoneChangedEvent(ctx context.Context, phone string) *HumanPhoneChangedEvent { +func NewHumanPhoneChangedEvent(ctx context.Context) *HumanPhoneChangedEvent { return &HumanPhoneChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanPhoneChangedType, ), - PhoneNumber: phone, } }