From 959530ddadafd5a680aeca51efb5a694e92bcce5 Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Fri, 15 Jan 2021 09:32:59 +0100 Subject: [PATCH] feat: new user auth api (#1168) * fix: correct selectors for extended writemodel * fix: no previous checks in eventstore * start check previous * feat: auth user commands * feat: auth user commands * feat: auth user commands * feat: otp * feat: corrections from pr merge * feat: webauthn * feat: comment old webauthn * feat: refactor user, human, machine * feat: webauth command side * feat: command and query side in login * feat: fix user writemodel append events * fix: remove creation dates on command side * fix: remove previous sequence * previous sequence * fix: external idps * Update internal/api/grpc/management/user.go Co-authored-by: Livio Amstutz * Update internal/v2/command/user_human_email.go Co-authored-by: Livio Amstutz * fix: pr changes * fix: phone verification Co-authored-by: adlerhurst Co-authored-by: Livio Amstutz --- cmd/zitadel/main.go | 6 +- internal/api/grpc/admin/org.go | 8 +- internal/api/grpc/admin/user_converter.go | 20 +- internal/api/grpc/auth/user.go | 57 +- internal/api/grpc/auth/user_converter.go | 34 +- .../grpc/management/application_converter.go | 16 +- internal/api/grpc/management/user.go | 48 +- .../api/grpc/management/user_converter.go | 76 ++- .../eventsourcing/eventstore/user.go | 88 +-- internal/auth/repository/user.go | 21 - .../internal/repository/sql/push.go | 2 +- internal/eventstore/v2/aggregate.go | 44 +- internal/eventstore/v2/event.go | 1 - internal/eventstore/v2/event_base.go | 12 +- internal/eventstore/v2/eventstore.go | 20 +- internal/eventstore/v2/eventstore_test.go | 46 +- internal/eventstore/v2/repository/event.go | 6 +- internal/eventstore/v2/repository/sql/crdb.go | 90 ++-- .../eventstore/v2/repository/sql/crdb_test.go | 501 ++++++------------ .../v2/repository/sql/local_crdb_test.go | 4 + .../eventstore/v2/repository/sql/query.go | 5 +- .../v2/repository/sql/query_test.go | 76 +-- internal/ui/login/handler/login.go | 8 +- internal/ui/login/login.go | 6 +- .../repository/eventsourcing/eventstore.go | 312 +++++------ internal/v2/command/command.go | 37 +- internal/v2/command/org.go | 17 +- internal/v2/command/org_converter.go | 7 +- internal/v2/command/org_model.go | 9 +- internal/v2/command/org_policy_org_iam.go | 2 +- internal/v2/command/setup_step1.go | 26 +- internal/v2/command/user.go | 112 ++-- internal/v2/command/user_converter.go | 41 +- internal/v2/command/user_human.go | 226 ++++---- .../v2/command/user_human_address_model.go | 11 +- internal/v2/command/user_human_email.go | 30 ++ internal/v2/command/user_human_email_model.go | 26 +- internal/v2/command/user_human_externalidp.go | 2 +- .../command/user_human_externalidp_model.go | 19 +- internal/v2/command/user_human_model.go | 22 +- internal/v2/command/user_human_otp.go | 80 ++- internal/v2/command/user_human_otp_model.go | 22 +- internal/v2/command/user_human_password.go | 4 +- .../v2/command/user_human_password_model.go | 13 +- internal/v2/command/user_human_phone.go | 53 +- internal/v2/command/user_human_phone_model.go | 30 +- internal/v2/command/user_human_profile.go | 2 +- .../v2/command/user_human_profile_model.go | 11 +- internal/v2/command/user_human_webauthn.go | 187 ++++++- .../v2/command/user_human_webauthn_model.go | 190 ++++++- internal/v2/command/user_machine.go | 9 +- internal/v2/command/user_machine_model.go | 23 +- internal/v2/command/user_model.go | 23 +- internal/v2/domain/human.go | 17 + internal/v2/domain/human_otp.go | 38 +- internal/v2/domain/human_web_auth_n.go | 19 +- internal/v2/domain/machine.go | 10 + internal/v2/domain/mfa.go | 1 + internal/v2/domain/org.go | 4 +- internal/v2/domain/user.go | 22 +- internal/v2/query/converter.go | 12 +- internal/v2/query/iam_model.go | 1 - internal/v2/query/user_model.go | 1 - internal/v2/repository/iam/aggregate.go | 17 - .../repository/iam/event_iam_project_set.go | 2 +- internal/v2/repository/org/aggregate.go | 17 - internal/v2/repository/project/aggregate.go | 17 - internal/v2/repository/user/aggregate.go | 17 - internal/v2/repository/user/eventstore.go | 2 +- internal/v2/repository/user/human_mfa_otp.go | 4 +- .../repository/user/human_mfa_web_auth_n.go | 27 +- internal/webauthn/converter.go | 38 +- internal/webauthn/webauthn.go | 34 +- pkg/grpc/management/proto/management.proto | 32 +- 74 files changed, 1554 insertions(+), 1519 deletions(-) diff --git a/cmd/zitadel/main.go b/cmd/zitadel/main.go index 9621078b6c..b3da9d8022 100644 --- a/cmd/zitadel/main.go +++ b/cmd/zitadel/main.go @@ -119,7 +119,7 @@ func startZitadel(configPaths []string) { } startAPI(ctx, conf, authZRepo, authRepo, command, query) - startUI(ctx, conf, authRepo) + startUI(ctx, conf, authRepo, command, query) if *notificationEnabled { notification.Start(ctx, conf.Notification, conf.SystemDefaults) @@ -129,10 +129,10 @@ func startZitadel(configPaths []string) { logging.Log("MAIN-s8d2h").Info("stopping zitadel") } -func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository) { +func startUI(ctx context.Context, conf *Config, authRepo *auth_es.EsRepository, command *command.CommandSide, query *query.QuerySide) { uis := ui.Create(conf.UI) if *loginEnabled { - login, prefix := login.Start(conf.UI.Login, authRepo, conf.SystemDefaults, *localDevMode) + login, prefix := login.Start(conf.UI.Login, command, query, authRepo, conf.SystemDefaults, *localDevMode) uis.RegisterHandler(prefix, login.Handler()) } if *consoleEnabled { diff --git a/internal/api/grpc/admin/org.go b/internal/api/grpc/admin/org.go index 8d0f840fb2..73ebf7f006 100644 --- a/internal/api/grpc/admin/org.go +++ b/internal/api/grpc/admin/org.go @@ -2,6 +2,7 @@ package admin import ( "context" + "github.com/caos/zitadel/internal/errors" "github.com/golang/protobuf/ptypes/empty" @@ -31,10 +32,11 @@ func (s *Server) IsOrgUnique(ctx context.Context, request *admin.UniqueOrgReques } func (s *Server) SetUpOrg(ctx context.Context, orgSetUp *admin.OrgSetUpRequest) (_ *empty.Empty, err error) { - err = s.command.SetUpOrg(ctx, orgCreateRequestToDomain(orgSetUp.Org), userCreateRequestToDomain(orgSetUp.User)) - if err != nil { - return nil, err + human, _ := userCreateRequestToDomain(orgSetUp.User) + if human == nil { + return &empty.Empty{}, errors.ThrowPreconditionFailed(nil, "ADMIN-4nd9f", "Errors.User.NotHuman") } + err = s.command.SetUpOrg(ctx, orgCreateRequestToDomain(orgSetUp.Org), human) return &empty.Empty{}, nil } diff --git a/internal/api/grpc/admin/user_converter.go b/internal/api/grpc/admin/user_converter.go index f023da3ea4..29479a585d 100644 --- a/internal/api/grpc/admin/user_converter.go +++ b/internal/api/grpc/admin/user_converter.go @@ -9,22 +9,18 @@ import ( "golang.org/x/text/language" ) -func userCreateRequestToDomain(user *admin.CreateUserRequest) *domain.User { - var human *domain.Human - var machine *domain.Machine - +func userCreateRequestToDomain(user *admin.CreateUserRequest) (*domain.Human, *domain.Machine) { if h := user.GetHuman(); h != nil { - human = humanCreateToDomain(h) + human := humanCreateToDomain(h) + human.Username = user.UserName + return human, nil } if m := user.GetMachine(); m != nil { - machine = machineCreateToDomain(m) - } - - return &domain.User{ - UserName: user.UserName, - Human: human, - Machine: machine, + machine := machineCreateToDomain(m) + machine.Username = user.UserName + return nil, machine } + return nil, nil } func humanCreateToDomain(u *admin.CreateHumanRequest) *domain.Human { diff --git a/internal/api/grpc/auth/user.go b/internal/api/grpc/auth/user.go index 876be9e74f..d51ede3e4a 100644 --- a/internal/api/grpc/auth/user.go +++ b/internal/api/grpc/auth/user.go @@ -42,7 +42,8 @@ func (s *Server) GetMyUserPhone(ctx context.Context, _ *empty.Empty) (*auth.User } func (s *Server) RemoveMyUserPhone(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) { - err := s.repo.RemoveMyPhone(ctx) + ctxData := authz.GetCtxData(ctx) + err := s.command.RemoveHumanPhone(ctx, ctxData.UserID, ctxData.ResourceOwner) return &empty.Empty{}, err } @@ -84,12 +85,14 @@ func (s *Server) ChangeMyUserEmail(ctx context.Context, request *auth.UpdateUser } func (s *Server) VerifyMyUserEmail(ctx context.Context, request *auth.VerifyMyUserEmailRequest) (*empty.Empty, error) { - err := s.repo.VerifyMyEmail(ctx, request.Code) + ctxData := authz.GetCtxData(ctx) + err := s.command.VerifyHumanEmail(ctx, ctxData.UserID, request.Code, ctxData.OrgID) return &empty.Empty{}, err } func (s *Server) ResendMyEmailVerificationMail(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) { - err := s.repo.ResendMyEmailVerificationMail(ctx) + ctxData := authz.GetCtxData(ctx) + err := s.command.CreateHumanEmailVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner) return &empty.Empty{}, err } @@ -102,25 +105,28 @@ func (s *Server) ChangeMyUserPhone(ctx context.Context, request *auth.UpdateUser } func (s *Server) VerifyMyUserPhone(ctx context.Context, request *auth.VerifyUserPhoneRequest) (*empty.Empty, error) { - err := s.repo.VerifyMyPhone(ctx, request.Code) + ctxData := authz.GetCtxData(ctx) + err := s.command.VerifyHumanPhone(ctx, ctxData.UserID, request.Code, ctxData.ResourceOwner) return &empty.Empty{}, err } func (s *Server) ResendMyPhoneVerificationCode(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) { - err := s.repo.ResendMyPhoneVerificationCode(ctx) + ctxData := authz.GetCtxData(ctx) + err := s.command.CreateHumanPhoneVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner) return &empty.Empty{}, err } func (s *Server) UpdateMyUserAddress(ctx context.Context, request *auth.UpdateUserAddressRequest) (*auth.UserAddress, error) { - address, err := s.repo.ChangeMyAddress(ctx, updateAddressToModel(ctx, request)) + address, err := s.command.ChangeHumanAddress(ctx, updateAddressToDomain(ctx, request)) if err != nil { return nil, err } - return addressFromModel(address), nil + return addressFromDomain(address), nil } func (s *Server) ChangeMyPassword(ctx context.Context, request *auth.PasswordChange) (*empty.Empty, error) { - err := s.repo.ChangeMyPassword(ctx, request.OldPassword, request.NewPassword) + ctxData := authz.GetCtxData(ctx) + err := s.command.ChangePassword(ctx, ctxData.OrgID, ctxData.UserID, request.OldPassword, request.NewPassword, "") return &empty.Empty{}, err } @@ -133,7 +139,7 @@ func (s *Server) SearchMyExternalIDPs(ctx context.Context, request *auth.Externa } func (s *Server) RemoveMyExternalIDP(ctx context.Context, request *auth.ExternalIDPRemoveRequest) (*empty.Empty, error) { - err := s.repo.RemoveMyExternalIDP(ctx, externalIDPRemoveToModel(ctx, request)) + err := s.command.RemoveHumanExternalIDP(ctx, externalIDPRemoveToDomain(ctx, request)) return &empty.Empty{}, err } @@ -146,38 +152,44 @@ func (s *Server) GetMyPasswordComplexityPolicy(ctx context.Context, _ *empty.Emp } func (s *Server) AddMfaOTP(ctx context.Context, _ *empty.Empty) (_ *auth.MfaOtpResponse, err error) { - otp, err := s.repo.AddMyMFAOTP(ctx) + ctxData := authz.GetCtxData(ctx) + otp, err := s.command.AddHumanOTP(ctx, ctxData.UserID, ctxData.OrgID) if err != nil { return nil, err } - return otpFromModel(otp), nil + return otpFromDomain(otp), nil } func (s *Server) VerifyMfaOTP(ctx context.Context, request *auth.VerifyMfaOtp) (*empty.Empty, error) { - err := s.repo.VerifyMyMFAOTPSetup(ctx, request.Code) + ctxData := authz.GetCtxData(ctx) + err := s.command.CheckMFAOTPSetup(ctx, ctxData.UserID, request.Code, "", ctxData.ResourceOwner) return &empty.Empty{}, err } func (s *Server) RemoveMfaOTP(ctx context.Context, _ *empty.Empty) (_ *empty.Empty, err error) { - err = s.repo.RemoveMyMFAOTP(ctx) + ctxData := authz.GetCtxData(ctx) + err = s.command.RemoveHumanOTP(ctx, ctxData.UserID, ctxData.OrgID) return &empty.Empty{}, err } func (s *Server) AddMyMfaU2F(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) { - u2f, err := s.repo.AddMyMFAU2F(ctx) + ctxData := authz.GetCtxData(ctx) + u2f, err := s.command.AddHumanU2F(ctx, ctxData.UserID, ctxData.ResourceOwner, false) if err != nil { return nil, err } - return verifyWebAuthNFromModel(u2f), err + return verifyWebAuthNFromDomain(u2f), err } func (s *Server) VerifyMyMfaU2F(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) { - err := s.repo.VerifyMyMFAU2FSetup(ctx, request.TokenName, request.PublicKeyCredential) + ctxData := authz.GetCtxData(ctx) + err := s.command.VerifyHumanU2F(ctx, ctxData.UserID, ctxData.OrgID, request.TokenName, "", request.PublicKeyCredential) return &empty.Empty{}, err } func (s *Server) RemoveMyMfaU2F(ctx context.Context, id *auth.WebAuthNTokenID) (*empty.Empty, error) { - err := s.repo.RemoveMyMFAU2F(ctx, id.Id) + ctxData := authz.GetCtxData(ctx) + err := s.command.RemoveHumanU2F(ctx, ctxData.UserID, id.Id, ctxData.OrgID) return &empty.Empty{}, err } @@ -190,20 +202,23 @@ func (s *Server) GetMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth } func (s *Server) AddMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) { - u2f, err := s.repo.AddMyPasswordless(ctx) + ctxData := authz.GetCtxData(ctx) + u2f, err := s.command.AddHumanPasswordless(ctx, ctxData.UserID, ctxData.ResourceOwner, false) if err != nil { return nil, err } - return verifyWebAuthNFromModel(u2f), err + return verifyWebAuthNFromDomain(u2f), err } func (s *Server) VerifyMyPasswordless(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) { - err := s.repo.VerifyMyPasswordlessSetup(ctx, request.TokenName, request.PublicKeyCredential) + ctxData := authz.GetCtxData(ctx) + err := s.command.VerifyHumanPasswordless(ctx, ctxData.UserID, ctxData.OrgID, request.TokenName, "", request.PublicKeyCredential) return &empty.Empty{}, err } func (s *Server) RemoveMyPasswordless(ctx context.Context, id *auth.WebAuthNTokenID) (*empty.Empty, error) { - err := s.repo.RemoveMyPasswordless(ctx, id.Id) + ctxData := authz.GetCtxData(ctx) + err := s.command.RemoveHumanPasswordless(ctx, ctxData.UserID, id.Id, ctxData.ResourceOwner) return &empty.Empty{}, err } diff --git a/internal/api/grpc/auth/user_converter.go b/internal/api/grpc/auth/user_converter.go index 6f39406b26..07f6302c43 100644 --- a/internal/api/grpc/auth/user_converter.go +++ b/internal/api/grpc/auth/user_converter.go @@ -194,7 +194,7 @@ func updatePhoneToDomain(ctx context.Context, e *auth.UpdateUserPhoneRequest) *d } } -func addressFromModel(address *usr_model.Address) *auth.UserAddress { +func addressFromDomain(address *domain.Address) *auth.UserAddress { creationDate, err := ptypes.TimestampProto(address.CreationDate) logging.Log("GRPC-65FRs").OnError(err).Debug("unable to parse timestamp") @@ -234,8 +234,8 @@ func addressViewFromModel(address *usr_model.Address) *auth.UserAddressView { } } -func updateAddressToModel(ctx context.Context, address *auth.UpdateUserAddressRequest) *usr_model.Address { - return &usr_model.Address{ +func updateAddressToDomain(ctx context.Context, address *auth.UpdateUserAddressRequest) *domain.Address { + return &domain.Address{ ObjectRoot: ctxToObjectRoot(ctx), Country: address.Country, StreetAddress: address.StreetAddress, @@ -252,11 +252,11 @@ func externalIDPSearchRequestToModel(request *auth.ExternalIDPSearchRequest) *us } } -func externalIDPRemoveToModel(ctx context.Context, idp *auth.ExternalIDPRemoveRequest) *usr_model.ExternalIDP { - return &usr_model.ExternalIDP{ - ObjectRoot: ctxToObjectRoot(ctx), - IDPConfigID: idp.IdpConfigId, - UserID: idp.ExternalUserId, +func externalIDPRemoveToDomain(ctx context.Context, idp *auth.ExternalIDPRemoveRequest) *domain.ExternalIDP { + return &domain.ExternalIDP{ + ObjectRoot: ctxToObjectRoot(ctx), + IDPConfigID: idp.IdpConfigId, + ExternalUserID: idp.ExternalUserId, } } @@ -308,12 +308,12 @@ func externalIDPViewFromModel(externalIDP *usr_model.ExternalIDPView) *auth.Exte } } -func otpFromModel(otp *usr_model.OTP) *auth.MfaOtpResponse { +func otpFromDomain(otp *domain.OTP) *auth.MfaOtpResponse { return &auth.MfaOtpResponse{ UserId: otp.AggregateID, Url: otp.Url, Secret: otp.SecretString, - State: mfaStateFromModel(otp.State), + State: mfaStateFromDomain(otp.State), } } @@ -360,11 +360,11 @@ func genderToDomain(gender auth.Gender) domain.Gender { } } -func mfaStateFromModel(state usr_model.MFAState) auth.MFAState { +func mfaStateFromDomain(state domain.MFAState) auth.MFAState { switch state { - case usr_model.MFAStateReady: + case domain.MFAStateReady: return auth.MFAState_MFASTATE_READY - case usr_model.MFAStateNotReady: + case domain.MFAStateNotReady: return auth.MFAState_MFASTATE_NOT_READY default: return auth.MFAState_MFASTATE_UNSPECIFIED @@ -381,7 +381,7 @@ func mfasFromModel(mfas []*usr_model.MultiFactor) []*auth.MultiFactor { func mfaFromModel(mfa *usr_model.MultiFactor) *auth.MultiFactor { return &auth.MultiFactor{ - State: mfaStateFromModel(mfa.State), + State: auth.MFAState(mfa.State), Type: mfaTypeFromModel(mfa.Type), Attribute: mfa.Attribute, Id: mfa.ID, @@ -431,11 +431,11 @@ func userChangesToAPI(changes *usr_model.UserChanges) (_ []*auth.Change) { return result } -func verifyWebAuthNFromModel(u2f *usr_model.WebAuthNToken) *auth.WebAuthNResponse { +func verifyWebAuthNFromDomain(u2f *domain.WebAuthNToken) *auth.WebAuthNResponse { return &auth.WebAuthNResponse{ Id: u2f.WebAuthNTokenID, PublicKey: u2f.CredentialCreationData, - State: mfaStateFromModel(u2f.State), + State: mfaStateFromDomain(u2f.State), } } @@ -451,7 +451,7 @@ func webAuthNTokenFromModel(token *usr_model.WebAuthNToken) *auth.WebAuthNToken return &auth.WebAuthNToken{ Id: token.WebAuthNTokenID, Name: token.WebAuthNTokenName, - State: mfaStateFromModel(token.State), + State: auth.MFAState(token.State), } } diff --git a/internal/api/grpc/management/application_converter.go b/internal/api/grpc/management/application_converter.go index e3ebafa396..dd0e12ab5d 100644 --- a/internal/api/grpc/management/application_converter.go +++ b/internal/api/grpc/management/application_converter.go @@ -17,20 +17,16 @@ import ( ) func appFromModel(app *proj_model.Application) *management.Application { - creationDate, err := ptypes.TimestampProto(app.CreationDate) - logging.Log("GRPC-iejs3").OnError(err).Debug("unable to parse timestamp") - changeDate, err := ptypes.TimestampProto(app.ChangeDate) logging.Log("GRPC-di7rw").OnError(err).Debug("unable to parse timestamp") return &management.Application{ - Id: app.AppID, - State: appStateFromModel(app.State), - CreationDate: creationDate, - ChangeDate: changeDate, - Name: app.Name, - Sequence: app.Sequence, - AppConfig: appConfigFromModel(app), + Id: app.AppID, + State: appStateFromModel(app.State), + ChangeDate: changeDate, + Name: app.Name, + Sequence: app.Sequence, + AppConfig: appConfigFromModel(app), } } diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index bdc23ad29d..b1309145a3 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -52,43 +52,39 @@ 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, authz.GetCtxData(ctx).OrgID, userCreateToDomain(in)) + human, machine := userCreateToDomain(in) + if human != nil { + h, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, human) + if err != nil { + return nil, err + } + return userHumanFromDomain(h), nil + } + m, err := s.command.AddMachine(ctx, authz.GetCtxData(ctx).OrgID, machine) if err != nil { return nil, err } - return userFromDomain(user), nil + return userMachineFromDomain(m), nil } -func (s *Server) DeactivateUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) { - user, err := s.command.DeactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) - if err != nil { - return nil, err - } - return userFromDomain(user), nil +func (s *Server) DeactivateUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) { + err := s.command.DeactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) + return &empty.Empty{}, err } -func (s *Server) ReactivateUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) { - user, err := s.command.ReactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) - if err != nil { - return nil, err - } - return userFromDomain(user), nil +func (s *Server) ReactivateUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) { + err := s.command.ReactivateUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) + return &empty.Empty{}, err } -func (s *Server) LockUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) { - user, err := s.command.LockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) - if err != nil { - return nil, err - } - return userFromDomain(user), nil +func (s *Server) LockUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) { + err := s.command.LockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) + return &empty.Empty{}, err } -func (s *Server) UnlockUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) { - user, err := s.command.UnlockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) - if err != nil { - return nil, err - } - return userFromDomain(user), nil +func (s *Server) UnlockUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) { + err := s.command.UnlockUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) + return &empty.Empty{}, err } func (s *Server) DeleteUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) { diff --git a/internal/api/grpc/management/user_converter.go b/internal/api/grpc/management/user_converter.go index fe46abe265..558c46d9a4 100644 --- a/internal/api/grpc/management/user_converter.go +++ b/internal/api/grpc/management/user_converter.go @@ -19,48 +19,48 @@ import ( "github.com/caos/zitadel/pkg/grpc/message" ) -func userFromDomain(user *domain.User) *management.UserResponse { - creationDate, err := ptypes.TimestampProto(user.CreationDate) - logging.Log("GRPC-8duwe").OnError(err).Debug("unable to parse timestamp") - - changeDate, err := ptypes.TimestampProto(user.ChangeDate) +func userMachineFromDomain(machine *domain.Machine) *management.UserResponse { + changeDate, err := ptypes.TimestampProto(machine.ChangeDate) logging.Log("GRPC-ckoe3d").OnError(err).Debug("unable to parse timestamp") userResp := &management.UserResponse{ - Id: user.AggregateID, - State: userStateFromDomain(user.State), - CreationDate: creationDate, - ChangeDate: changeDate, - Sequence: user.Sequence, - UserName: user.UserName, + Id: machine.AggregateID, + State: userStateFromDomain(machine.GetState()), + ChangeDate: changeDate, + Sequence: machine.Sequence, + UserName: machine.GetUsername(), } - - if user.Machine != nil { - userResp.User = &management.UserResponse_Machine{Machine: machineFromDomain(user.Machine)} - } - if user.Human != nil { - userResp.User = &management.UserResponse_Human{Human: humanFromDomain(user.Human)} - } - + userResp.User = &management.UserResponse_Machine{Machine: machineFromDomain(machine)} return userResp } -func userCreateToDomain(user *management.CreateUserRequest) *domain.User { - var human *domain.Human - var machine *domain.Machine +func userHumanFromDomain(human *domain.Human) *management.UserResponse { + changeDate, err := ptypes.TimestampProto(human.ChangeDate) + logging.Log("GRPC-ckoe3d").OnError(err).Debug("unable to parse timestamp") + userResp := &management.UserResponse{ + Id: human.AggregateID, + State: userStateFromDomain(human.GetState()), + ChangeDate: changeDate, + Sequence: human.Sequence, + UserName: human.GetUsername(), + } + userResp.User = &management.UserResponse_Human{Human: humanFromDomain(human)} + return userResp +} + +func userCreateToDomain(user *management.CreateUserRequest) (*domain.Human, *domain.Machine) { if h := user.GetHuman(); h != nil { - human = humanCreateToDomain(h) + human := humanCreateToDomain(h) + human.Username = user.UserName + return human, nil } if m := user.GetMachine(); m != nil { - machine = machineCreateToDomain(m) - } - - return &domain.User{ - UserName: user.UserName, - Human: human, - Machine: machine, + machine := machineCreateToDomain(m) + machine.Username = user.UserName + return nil, machine } + return nil, nil } func passwordRequestToModel(r *management.PasswordRequest) *usr_model.Password { @@ -212,15 +212,11 @@ func userMembershipSearchKeyToModel(key management.UserMembershipSearchKey) usr_ } func profileFromDomain(profile *domain.Profile) *management.UserProfile { - creationDate, err := ptypes.TimestampProto(profile.CreationDate) - logging.Log("GRPC-dkso3").OnError(err).Debug("unable to parse timestamp") - changeDate, err := ptypes.TimestampProto(profile.ChangeDate) logging.Log("GRPC-ski8d").OnError(err).Debug("unable to parse timestamp") return &management.UserProfile{ Id: profile.AggregateID, - CreationDate: creationDate, ChangeDate: changeDate, Sequence: profile.Sequence, FirstName: profile.FirstName, @@ -270,15 +266,11 @@ func updateProfileToDomain(u *management.UpdateUserProfileRequest) *domain.Profi } func emailFromDomain(email *domain.Email) *management.UserEmail { - creationDate, err := ptypes.TimestampProto(email.CreationDate) - logging.Log("GRPC-d9ow2").OnError(err).Debug("unable to parse timestamp") - changeDate, err := ptypes.TimestampProto(email.ChangeDate) logging.Log("GRPC-s0dkw").OnError(err).Debug("unable to parse timestamp") return &management.UserEmail{ Id: email.AggregateID, - CreationDate: creationDate, ChangeDate: changeDate, Sequence: email.Sequence, Email: email.EmailAddress, @@ -312,15 +304,11 @@ func updateEmailToDomain(e *management.UpdateUserEmailRequest) *domain.Email { } func phoneFromDomain(phone *domain.Phone) *management.UserPhone { - creationDate, err := ptypes.TimestampProto(phone.CreationDate) - logging.Log("GRPC-ps9ws").OnError(err).Debug("unable to parse timestamp") - changeDate, err := ptypes.TimestampProto(phone.ChangeDate) logging.Log("GRPC-09ewq").OnError(err).Debug("unable to parse timestamp") return &management.UserPhone{ Id: phone.AggregateID, - CreationDate: creationDate, ChangeDate: changeDate, Sequence: phone.Sequence, Phone: phone.PhoneNumber, @@ -353,15 +341,11 @@ func updatePhoneToDomain(e *management.UpdateUserPhoneRequest) *domain.Phone { } func addressFromDomain(address *domain.Address) *management.UserAddress { - creationDate, err := ptypes.TimestampProto(address.CreationDate) - logging.Log("GRPC-ud8w7").OnError(err).Debug("unable to parse timestamp") - changeDate, err := ptypes.TimestampProto(address.ChangeDate) logging.Log("GRPC-si9ws").OnError(err).Debug("unable to parse timestamp") return &management.UserAddress{ Id: address.AggregateID, - CreationDate: creationDate, ChangeDate: changeDate, Sequence: address.Sequence, Country: address.Country, diff --git a/internal/auth/repository/eventsourcing/eventstore/user.go b/internal/auth/repository/eventsourcing/eventstore/user.go index 04f597261e..8c4a3e4e5d 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user.go +++ b/internal/auth/repository/eventsourcing/eventstore/user.go @@ -122,20 +122,6 @@ func (repo *UserRepo) SearchMyExternalIDPs(ctx context.Context, request *model.E return result, nil } -func (repo *UserRepo) AddMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) (*model.ExternalIDP, error) { - if err := checkIDs(ctx, externalIDP.ObjectRoot); err != nil { - return nil, err - } - return repo.UserEvents.AddExternalIDP(ctx, externalIDP) -} - -func (repo *UserRepo) RemoveMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error { - if err := checkIDs(ctx, externalIDP.ObjectRoot); err != nil { - return err - } - return repo.UserEvents.RemoveExternalIDP(ctx, externalIDP) -} - func (repo *UserRepo) MyEmail(ctx context.Context) (*model.Email, error) { user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) if err != nil { @@ -159,10 +145,6 @@ func (repo *UserRepo) ResendEmailVerificationMail(ctx context.Context, userID st return repo.UserEvents.CreateEmailVerificationCode(ctx, userID) } -func (repo *UserRepo) ResendMyEmailVerificationMail(ctx context.Context) error { - return repo.UserEvents.CreateEmailVerificationCode(ctx, authz.GetCtxData(ctx).UserID) -} - func (repo *UserRepo) MyPhone(ctx context.Context) (*model.Phone, error) { user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) if err != nil { @@ -189,10 +171,6 @@ func (repo *UserRepo) VerifyMyPhone(ctx context.Context, code string) error { return repo.UserEvents.VerifyPhone(ctx, authz.GetCtxData(ctx).UserID, code) } -func (repo *UserRepo) ResendMyPhoneVerificationCode(ctx context.Context) error { - return repo.UserEvents.CreatePhoneVerificationCode(ctx, authz.GetCtxData(ctx).UserID) -} - func (repo *UserRepo) MyAddress(ctx context.Context) (*model.Address, error) { user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) if err != nil { @@ -204,13 +182,6 @@ func (repo *UserRepo) MyAddress(ctx context.Context) (*model.Address, error) { return user.GetAddress() } -func (repo *UserRepo) ChangeMyAddress(ctx context.Context, address *model.Address) (*model.Address, error) { - if err := checkIDs(ctx, address.ObjectRoot); err != nil { - return nil, err - } - return repo.UserEvents.ChangeAddress(ctx, address) -} - func (repo *UserRepo) ChangeMyPassword(ctx context.Context, old, new string) error { policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) if errors.IsNotFound(err) { @@ -265,25 +236,10 @@ func (repo *UserRepo) AddMFAOTP(ctx context.Context, userID string) (*model.OTP, return repo.UserEvents.AddOTP(ctx, userID, accountName) } -func (repo *UserRepo) AddMyMFAOTP(ctx context.Context) (*model.OTP, error) { - accountName := "" - user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) - if err != nil { - logging.Log("EVENT-Ml0sd").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") - } else { - accountName = user.PreferredLoginName - } - return repo.UserEvents.AddOTP(ctx, authz.GetCtxData(ctx).UserID, accountName) -} - func (repo *UserRepo) VerifyMFAOTPSetup(ctx context.Context, userID, code, userAgentID string) error { return repo.UserEvents.CheckMFAOTPSetup(ctx, userID, code, userAgentID) } -func (repo *UserRepo) VerifyMyMFAOTPSetup(ctx context.Context, code string) error { - return repo.UserEvents.CheckMFAOTPSetup(ctx, authz.GetCtxData(ctx).UserID, code, "") -} - func (repo *UserRepo) RemoveMyMFAOTP(ctx context.Context) error { return repo.UserEvents.RemoveOTP(ctx, authz.GetCtxData(ctx).UserID) } @@ -315,18 +271,6 @@ func (repo *UserRepo) VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, return repo.UserEvents.VerifyU2FSetup(ctx, userID, tokenName, userAgentID, credentialData) } -func (repo *UserRepo) VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, credentialData []byte) error { - return repo.UserEvents.VerifyU2FSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, "", credentialData) -} - -func (repo *UserRepo) RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error { - return repo.UserEvents.RemoveU2FToken(ctx, userID, webAuthNTokenID) -} - -func (repo *UserRepo) RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string) error { - return repo.UserEvents.RemoveU2FToken(ctx, authz.GetCtxData(ctx).UserID, webAuthNTokenID) -} - func (repo *UserRepo) GetPasswordless(ctx context.Context, userID string) ([]*model.WebAuthNToken, error) { return repo.UserEvents.GetPasswordless(ctx, userID) } @@ -346,34 +290,26 @@ func (repo *UserRepo) GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNT return repo.UserEvents.GetPasswordless(ctx, authz.GetCtxData(ctx).UserID) } -func (repo *UserRepo) AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error) { - userID := authz.GetCtxData(ctx).UserID - accountName := "" - user, err := repo.UserByID(ctx, userID) - if err != nil { - logging.Log("EVENT-AEq21").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") - } else { - accountName = user.PreferredLoginName - } - return repo.UserEvents.AddPasswordless(ctx, authz.GetCtxData(ctx).UserID, accountName, false) -} - func (repo *UserRepo) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { return repo.UserEvents.VerifyPasswordlessSetup(ctx, userID, tokenName, userAgentID, credentialData) } -func (repo *UserRepo) VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, credentialData []byte) error { - return repo.UserEvents.VerifyPasswordlessSetup(ctx, authz.GetCtxData(ctx).UserID, tokenName, "", credentialData) -} - -func (repo *UserRepo) RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error { - return repo.UserEvents.RemovePasswordlessToken(ctx, userID, webAuthNTokenID) -} - func (repo *UserRepo) RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error { 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 2499372d06..d1eb3257b7 100644 --- a/internal/auth/repository/user.go +++ b/internal/auth/repository/user.go @@ -30,12 +30,10 @@ type UserRepository interface { AddMFAU2F(ctx context.Context, id string) (*model.WebAuthNToken, error) VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error - RemoveMFAU2F(ctx context.Context, userID, webAuthNTokenID string) error GetPasswordless(ctx context.Context, id string) ([]*model.WebAuthNToken, error) AddPasswordless(ctx context.Context, id string) (*model.WebAuthNToken, error) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error - RemovePasswordless(ctx context.Context, userID, webAuthNTokenID string) error ChangeUsername(ctx context.Context, userID, username string) error @@ -52,37 +50,18 @@ type myUserRepo interface { MyProfile(ctx context.Context) (*model.Profile, error) MyEmail(ctx context.Context) (*model.Email, error) - VerifyMyEmail(ctx context.Context, code string) error - ResendMyEmailVerificationMail(ctx context.Context) error MyPhone(ctx context.Context) (*model.Phone, error) - ChangeMyPhone(ctx context.Context, phone *model.Phone) (*model.Phone, error) - RemoveMyPhone(ctx context.Context) error - VerifyMyPhone(ctx context.Context, code string) error - ResendMyPhoneVerificationCode(ctx context.Context) error MyAddress(ctx context.Context) (*model.Address, error) - ChangeMyAddress(ctx context.Context, address *model.Address) (*model.Address, error) - - ChangeMyPassword(ctx context.Context, old, new string) error SearchMyExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error) - AddMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) (*model.ExternalIDP, error) - RemoveMyExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error MyUserMFAs(ctx context.Context) ([]*model.MultiFactor, error) - AddMyMFAOTP(ctx context.Context) (*model.OTP, error) - VerifyMyMFAOTPSetup(ctx context.Context, code string) error - RemoveMyMFAOTP(ctx context.Context) error AddMyMFAU2F(ctx context.Context) (*model.WebAuthNToken, error) - VerifyMyMFAU2FSetup(ctx context.Context, tokenName string, data []byte) error - RemoveMyMFAU2F(ctx context.Context, webAuthNTokenID string) error GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNToken, error) - AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error) - VerifyMyPasswordlessSetup(ctx context.Context, tokenName string, data []byte) error - RemoveMyPasswordless(ctx context.Context, webAuthNTokenID string) error MyUserChanges(ctx context.Context, lastSequence uint64, limit uint64, sortAscending bool) (*model.UserChanges, error) } diff --git a/internal/eventstore/internal/repository/sql/push.go b/internal/eventstore/internal/repository/sql/push.go index 3a3802ebf2..b99233914c 100644 --- a/internal/eventstore/internal/repository/sql/push.go +++ b/internal/eventstore/internal/repository/sql/push.go @@ -74,7 +74,7 @@ func insertEvents(stmt *sql.Stmt, previousSequence Sequence, events []*models.Ev event.AggregateType, event.AggregateID, previousSequence, previousSequence).Scan(&previousSequence, &event.CreationDate) if err != nil { - logging.LogWithFields("SQL-IP3js", + logging.LogWithFields("SQL-5M0sd", "aggregate", event.AggregateType, "previousSequence", previousSequence, "aggregateId", event.AggregateID, diff --git a/internal/eventstore/v2/aggregate.go b/internal/eventstore/v2/aggregate.go index f32fe76a69..5abbc7fab9 100644 --- a/internal/eventstore/v2/aggregate.go +++ b/internal/eventstore/v2/aggregate.go @@ -13,11 +13,6 @@ type Aggregater interface { ResourceOwner() string //Version represents the semantic version of the aggregate Version() Version - //PreviouseSequence should return the sequence of the latest event of this aggregate - // stored in the eventstore - // it's set to the first event of this push transaction, - // later events consume the sequence of the previously pushed event of the aggregate - PreviousSequence() uint64 } func NewAggregate( @@ -25,15 +20,13 @@ func NewAggregate( typ AggregateType, resourceOwner string, version Version, - previousSequence uint64, ) *Aggregate { return &Aggregate{ - id: id, - typ: typ, - resourceOwner: resourceOwner, - version: version, - previousSequence: previousSequence, - events: []EventPusher{}, + id: id, + typ: typ, + resourceOwner: resourceOwner, + version: version, + events: []EventPusher{}, } } @@ -43,23 +36,21 @@ func AggregateFromWriteModel( version Version, ) *Aggregate { return &Aggregate{ - id: wm.AggregateID, - typ: typ, - resourceOwner: wm.ResourceOwner, - version: version, - previousSequence: wm.ProcessedSequence, - events: []EventPusher{}, + id: wm.AggregateID, + typ: typ, + resourceOwner: wm.ResourceOwner, + version: version, + events: []EventPusher{}, } } //Aggregate is the basic implementation of Aggregater type Aggregate struct { - id string `json:"-"` - typ AggregateType `json:"-"` - events []EventPusher `json:"-"` - resourceOwner string `json:"-"` - version Version `json:"-"` - previousSequence uint64 `json:"-"` + id string `json:"-"` + typ AggregateType `json:"-"` + events []EventPusher `json:"-"` + resourceOwner string `json:"-"` + version Version `json:"-"` } //PushEvents adds all the events to the aggregate. @@ -93,8 +84,3 @@ func (a *Aggregate) ResourceOwner() string { func (a *Aggregate) Version() Version { return a.version } - -//PreviousSequence implements Aggregater -func (a *Aggregate) PreviousSequence() uint64 { - return a.previousSequence -} diff --git a/internal/eventstore/v2/event.go b/internal/eventstore/v2/event.go index 8b1709d786..bd384c5cea 100644 --- a/internal/eventstore/v2/event.go +++ b/internal/eventstore/v2/event.go @@ -33,6 +33,5 @@ type EventReader interface { ResourceOwner() string AggregateVersion() Version Sequence() uint64 - PreviousSequence() uint64 CreationDate() time.Time } diff --git a/internal/eventstore/v2/event_base.go b/internal/eventstore/v2/event_base.go index 3046952c6c..234122469c 100644 --- a/internal/eventstore/v2/event_base.go +++ b/internal/eventstore/v2/event_base.go @@ -14,11 +14,10 @@ type BaseEvent struct { aggregateType AggregateType `json:"-"` EventType EventType `json:"-"` - resourceOwner string `json:"-"` - aggregateVersion Version `json:"-"` - sequence uint64 `json:"-"` - previouseSequence uint64 `json:"-"` - creationDate time.Time `json:"-"` + resourceOwner string `json:"-"` + aggregateVersion Version `json:"-"` + sequence uint64 `json:"-"` + creationDate time.Time `json:"-"` //User is the user who created the event User string `json:"-"` @@ -56,9 +55,6 @@ func (e *BaseEvent) AggregateVersion() Version { func (e *BaseEvent) Sequence() uint64 { return e.sequence } -func (e *BaseEvent) PreviousSequence() uint64 { - return e.previouseSequence -} func (e *BaseEvent) CreationDate() time.Time { return e.creationDate } diff --git a/internal/eventstore/v2/eventstore.go b/internal/eventstore/v2/eventstore.go index adff3cc133..65f8d69678 100644 --- a/internal/eventstore/v2/eventstore.go +++ b/internal/eventstore/v2/eventstore.go @@ -66,25 +66,21 @@ func (es *Eventstore) PushAggregates(ctx context.Context, aggregates ...Aggregat func (es *Eventstore) aggregatesToEvents(aggregates []Aggregater) ([]*repository.Event, error) { events := make([]*repository.Event, 0, len(aggregates)) for _, aggregate := range aggregates { - var previousEvent *repository.Event for _, event := range aggregate.Events() { data, err := eventData(event) if err != nil { return nil, err } events = append(events, &repository.Event{ - AggregateID: aggregate.ID(), - AggregateType: repository.AggregateType(aggregate.Type()), - ResourceOwner: aggregate.ResourceOwner(), - EditorService: event.EditorService(), - EditorUser: event.EditorUser(), - Type: repository.EventType(event.Type()), - Version: repository.Version(aggregate.Version()), - PreviousEvent: previousEvent, - PreviousSequence: aggregate.PreviousSequence(), - Data: data, + AggregateID: aggregate.ID(), + AggregateType: repository.AggregateType(aggregate.Type()), + ResourceOwner: aggregate.ResourceOwner(), + EditorService: event.EditorService(), + EditorUser: event.EditorUser(), + Type: repository.EventType(event.Type()), + Version: repository.Version(aggregate.Version()), + Data: data, }) - previousEvent = events[len(events)-1] } } return events, nil diff --git a/internal/eventstore/v2/eventstore_test.go b/internal/eventstore/v2/eventstore_test.go index 16a75ddec2..1bc01bc258 100644 --- a/internal/eventstore/v2/eventstore_test.go +++ b/internal/eventstore/v2/eventstore_test.go @@ -14,9 +14,8 @@ import ( ) type testAggregate struct { - id string - events []EventPusher - previousSequence uint64 + id string + events []EventPusher } func (a *testAggregate) ID() string { @@ -39,10 +38,6 @@ func (a *testAggregate) Version() Version { return "v1" } -func (a *testAggregate) PreviousSequence() uint64 { - return a.previousSequence -} - // testEvent implements the Event interface type testEvent struct { BaseEvent @@ -425,8 +420,8 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { }, res: res{ wantErr: false, - events: linkEvents( - &repository.Event{ + events: []*repository.Event{ + { AggregateID: "1", AggregateType: "test.aggregate", Data: []byte(nil), @@ -436,7 +431,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { Type: "test.event", Version: "v1", }, - &repository.Event{ + { AggregateID: "1", AggregateType: "test.aggregate", Data: []byte(nil), @@ -446,7 +441,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { Type: "test.event", Version: "v1", }, - ), + }, }, }, { @@ -507,8 +502,8 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { res: res{ wantErr: false, events: combineEventLists( - linkEvents( - &repository.Event{ + []*repository.Event{ + { AggregateID: "1", AggregateType: "test.aggregate", Data: []byte(nil), @@ -518,7 +513,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { Type: "test.event", Version: "v1", }, - &repository.Event{ + { AggregateID: "1", AggregateType: "test.aggregate", Data: []byte(nil), @@ -528,7 +523,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { Type: "test.event", Version: "v1", }, - ), + }, []*repository.Event{ { AggregateID: "2", @@ -695,8 +690,8 @@ func TestEventstore_Push(t *testing.T) { fields: fields{ repo: &testRepo{ t: t, - events: linkEvents( - &repository.Event{ + events: []*repository.Event{ + { AggregateID: "1", AggregateType: "test.aggregate", Data: []byte(nil), @@ -706,7 +701,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.event", Version: "v1", }, - &repository.Event{ + { AggregateID: "1", AggregateType: "test.aggregate", Data: []byte(nil), @@ -716,7 +711,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.event", Version: "v1", }, - ), + }, }, eventMapper: map[EventType]func(*repository.Event) (EventReader, error){ "test.event": func(e *repository.Event) (EventReader, error) { @@ -766,8 +761,8 @@ func TestEventstore_Push(t *testing.T) { repo: &testRepo{ t: t, events: combineEventLists( - linkEvents( - &repository.Event{ + []*repository.Event{ + { AggregateID: "1", AggregateType: "test.aggregate", Data: []byte(nil), @@ -777,7 +772,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.event", Version: "v1", }, - &repository.Event{ + { AggregateID: "1", AggregateType: "test.aggregate", Data: []byte(nil), @@ -787,7 +782,7 @@ func TestEventstore_Push(t *testing.T) { Type: "test.event", Version: "v1", }, - ), + }, []*repository.Event{ { AggregateID: "2", @@ -1305,7 +1300,7 @@ func combineEventLists(lists ...[]*repository.Event) []*repository.Event { func linkEvents(events ...*repository.Event) []*repository.Event { for i := 1; i < len(events); i++ { - events[i].PreviousEvent = events[i-1] + // events[i].PreviousEvent = events[i-1] } return events } @@ -1337,9 +1332,6 @@ func compareEvents(t *testing.T, want, got *repository.Event) { if want.Version != got.Version { t.Errorf("wrong version got %q want %q", got.Version, want.Version) } - if (want.PreviousEvent == nil) != (got.PreviousEvent == nil) { - t.Errorf("linking failed got was linked: %v want was linked: %v", (got.PreviousEvent != nil), (want.PreviousEvent != nil)) - } if want.PreviousSequence != got.PreviousSequence { t.Errorf("wrong previous sequence got %d want %d", got.PreviousSequence, want.PreviousSequence) } diff --git a/internal/eventstore/v2/repository/event.go b/internal/eventstore/v2/repository/event.go index 2a174caeaf..f89271901e 100644 --- a/internal/eventstore/v2/repository/event.go +++ b/internal/eventstore/v2/repository/event.go @@ -16,17 +16,13 @@ type Event struct { // if it's 0 then it's the first event of this aggregate PreviousSequence uint64 - //PreviousEvent is needed in push to update PreviousSequence - // it implements a linked list - PreviousEvent *Event - //CreationDate is the time the event is created // it's used for human readability. // Don't use it for event ordering, // time drifts in different services could cause integrity problems CreationDate time.Time - //KeyType describes the cause of the event (e.g. user.added) + //Type describes the cause of the event (e.g. user.added) // it should always be in past-form Type EventType diff --git a/internal/eventstore/v2/repository/sql/crdb.go b/internal/eventstore/v2/repository/sql/crdb.go index 5f99908f38..c55a64d541 100644 --- a/internal/eventstore/v2/repository/sql/crdb.go +++ b/internal/eventstore/v2/repository/sql/crdb.go @@ -17,7 +17,10 @@ import ( ) const ( - crdbInsert = "WITH input_event ( " + + //as soon as stored procedures are possible in crdb + // we could move the code to migrations and coll the procedure + // traking issue: https://github.com/cockroachdb/cockroach/issues/17511 + crdbInsert = "WITH data ( " + " event_type, " + " aggregate_type, " + " aggregate_id, " + @@ -27,47 +30,35 @@ const ( " editor_user, " + " editor_service, " + " resource_owner, " + - " previous_sequence, " + // variables below are calculated - " max_event_seq " + - ") AS ( " + - " ( " + - //the following select will return no row if no previous event defined - " SELECT " + - " $1::VARCHAR, " + - " $2::VARCHAR, " + - " $3::VARCHAR, " + - " $4::VARCHAR, " + - " COALESCE($5::TIMESTAMPTZ, NOW()), " + - " $6::JSONB, " + - " $7::VARCHAR, " + - " $8::VARCHAR, " + - " resource_owner, " + - " $10::BIGINT, " + - " MAX(event_sequence) AS max_event_seq " + - " FROM eventstore.events " + - " WHERE " + - " aggregate_type = $2::VARCHAR " + - " AND aggregate_id = $3::VARCHAR " + - " GROUP BY resource_owner " + - " ) UNION (" + - // if no previous event we use the given data - " VALUES (" + - " $1::VARCHAR, " + - " $2::VARCHAR, " + - " $3::VARCHAR, " + - " $4::VARCHAR, " + - " COALESCE($5::TIMESTAMPTZ, NOW()), " + - " $6::JSONB, " + - " $7::VARCHAR, " + - " $8::VARCHAR, " + - " $9::VARCHAR, " + - " $10::BIGINT, " + - " NULL::BIGINT " + - " ) " + - " ) " + - // ensure only 1 row in input_event - " LIMIT 1 " + + " previous_sequence" + + ") AS (" + + //previous_data selects the needed data of the latest event of the aggregate + // and buffers it (crdb inmemory) + " WITH previous_data AS (" + + " SELECT MAX(event_sequence) AS seq, resource_owner " + + " FROM eventstore.events " + + //TODO: remove LIMIT 1 as soon as data cleaned up (only 1 resource_owner per aggregate) + " WHERE aggregate_type = $2 AND aggregate_id = $3 GROUP BY resource_owner LIMIT 1" + + " )" + + // defines the data to be inserted + " SELECT " + + " $1::VARCHAR AS event_type, " + + " $2::VARCHAR AS aggregate_type, " + + " $3::VARCHAR AS aggregate_id, " + + " $4::VARCHAR AS aggregate_version, " + + " NOW() AS creation_date, " + + " $5::JSONB AS event_data, " + + " $6::VARCHAR AS editor_user, " + + " $7::VARCHAR AS editor_service, " + + " CASE WHEN EXISTS (SELECT * FROM previous_data) " + + " THEN (SELECT resource_owner FROM previous_data) " + + " ELSE $8::VARCHAR " + + " end AS resource_owner, " + + " CASE WHEN EXISTS (SELECT * FROM previous_data) " + + " THEN (SELECT seq FROM previous_data) " + + " ELSE NULL " + + " end AS previous_sequence" + ") " + "INSERT INTO eventstore.events " + " ( " + @@ -94,9 +85,9 @@ const ( " editor_service, " + " resource_owner, " + " previous_sequence " + - " FROM input_event " + + " FROM data " + " ) " + - "RETURNING id, event_sequence, previous_sequence, creation_date, resource_owner " + "RETURNING id, event_sequence, previous_sequence, creation_date, resource_owner" ) type CRDB struct { @@ -119,28 +110,17 @@ func (db *CRDB) Push(ctx context.Context, events ...*repository.Event) error { return caos_errs.ThrowInternal(err, "SQL-OdXRE", "prepare failed") } + var previousSequence Sequence for _, event := range events { - previousSequence := Sequence(event.PreviousSequence) - if event.PreviousEvent != nil { - if event.PreviousEvent.AggregateType != event.AggregateType || event.PreviousEvent.AggregateID != event.AggregateID { - return caos_errs.ThrowPreconditionFailed(nil, "SQL-J55uR", "aggregate of linked events unequal") - } - previousSequence = Sequence(event.PreviousEvent.Sequence) - } err = stmt.QueryRowContext(ctx, event.Type, event.AggregateType, event.AggregateID, event.Version, - &sql.NullTime{ - Time: event.CreationDate, - Valid: !event.CreationDate.IsZero(), - }, Data(event.Data), event.EditorUser, event.EditorService, event.ResourceOwner, - previousSequence, ).Scan(&event.ID, &event.Sequence, &previousSequence, &event.CreationDate, &event.ResourceOwner) event.PreviousSequence = uint64(previousSequence) diff --git a/internal/eventstore/v2/repository/sql/crdb_test.go b/internal/eventstore/v2/repository/sql/crdb_test.go index 0da12e107a..0098fa5656 100644 --- a/internal/eventstore/v2/repository/sql/crdb_test.go +++ b/internal/eventstore/v2/repository/sql/crdb_test.go @@ -284,11 +284,11 @@ func TestCRDB_Push_OneAggregate(t *testing.T) { res res }{ { - name: "push 1 event with check previous", + name: "push 1 event", args: args{ ctx: context.Background(), events: []*repository.Event{ - generateEvent(t, "1", true, 0), + generateEvent(t, "1"), }, }, res: res{ @@ -300,83 +300,14 @@ func TestCRDB_Push_OneAggregate(t *testing.T) { }}, }, { - name: "fail push 1 event with check previous wrong sequence", + name: "push two events on agg", args: args{ ctx: context.Background(), events: []*repository.Event{ - generateEvent(t, "2", true, 5), + generateEvent(t, "6"), + generateEvent(t, "6"), }, }, - res: res{ - wantErr: true, - eventsRes: eventsRes{ - pushedEventsCount: 0, - aggID: []string{"2"}, - aggType: repository.AggregateType(t.Name()), - }, - }, - }, - { - name: "push 1 event without check previous", - args: args{ - ctx: context.Background(), - events: []*repository.Event{ - generateEvent(t, "3", false, 0), - }, - }, - res: res{ - wantErr: false, - eventsRes: eventsRes{ - pushedEventsCount: 1, - aggID: []string{"3"}, - aggType: repository.AggregateType(t.Name()), - }, - }, - }, - { - name: "push 1 event without check previous wrong sequence", - args: args{ - ctx: context.Background(), - events: []*repository.Event{ - generateEvent(t, "4", false, 5), - }, - }, - res: res{ - wantErr: false, - eventsRes: eventsRes{ - pushedEventsCount: 1, - aggID: []string{"4"}, - aggType: repository.AggregateType(t.Name()), - }, - }, - }, - { - name: "fail on push two events on agg without linking", - args: args{ - ctx: context.Background(), - events: []*repository.Event{ - generateEvent(t, "5", true, 0), - generateEvent(t, "5", true, 0), - }, - }, - res: res{ - wantErr: true, - eventsRes: eventsRes{ - pushedEventsCount: 0, - aggID: []string{"5"}, - aggType: repository.AggregateType(t.Name()), - }, - }, - }, - { - name: "push two events on agg with linking", - args: args{ - ctx: context.Background(), - events: linkEvents( - generateEvent(t, "6", true, 0), - generateEvent(t, "6", true, 0), - ), - }, res: res{ wantErr: false, eventsRes: eventsRes{ @@ -386,51 +317,12 @@ func TestCRDB_Push_OneAggregate(t *testing.T) { }, }, }, - { - name: "push two events on agg with linking without check previous", - args: args{ - ctx: context.Background(), - events: linkEvents( - generateEvent(t, "7", false, 0), - generateEvent(t, "7", false, 0), - ), - }, - res: res{ - wantErr: false, - eventsRes: eventsRes{ - pushedEventsCount: 2, - aggID: []string{"7"}, - aggType: repository.AggregateType(t.Name()), - }, - }, - }, - { - name: "push two events on agg with linking mixed check previous", - args: args{ - ctx: context.Background(), - events: linkEvents( - generateEvent(t, "8", false, 0), - generateEvent(t, "8", true, 0), - generateEvent(t, "8", false, 0), - generateEvent(t, "8", true, 0), - generateEvent(t, "8", true, 0), - ), - }, - res: res{ - wantErr: false, - eventsRes: eventsRes{ - pushedEventsCount: 5, - aggID: []string{"8"}, - aggType: repository.AggregateType(t.Name()), - }, - }, - }, { name: "failed push because context canceled", args: args{ ctx: canceledCtx(), events: []*repository.Event{ - generateEvent(t, "9", true, 0), + generateEvent(t, "9"), }, }, res: res{ @@ -485,11 +377,11 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) { res res }{ { - name: "push two aggregates both check previous", + name: "push two aggregates", args: args{ events: []*repository.Event{ - generateEvent(t, "100", true, 0), - generateEvent(t, "101", true, 0), + generateEvent(t, "100"), + generateEvent(t, "101"), }, }, res: res{ @@ -502,18 +394,14 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) { }, }, { - name: "push two aggregates both check previous multiple events", + name: "push two aggregates both multiple events", args: args{ - events: combineEventLists( - linkEvents( - generateEvent(t, "102", true, 0), - generateEvent(t, "102", true, 0), - ), - linkEvents( - generateEvent(t, "103", true, 0), - generateEvent(t, "103", true, 0), - ), - ), + events: []*repository.Event{ + generateEvent(t, "102"), + generateEvent(t, "102"), + generateEvent(t, "103"), + generateEvent(t, "103"), + }, }, res: res{ wantErr: false, @@ -525,64 +413,28 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) { }, }, { - name: "fail push linked events of different aggregates", + name: "push two aggregates mixed multiple events", args: args{ - events: linkEvents( - generateEvent(t, "104", false, 0), - generateEvent(t, "105", false, 0), - ), - }, - res: res{ - wantErr: true, - eventsRes: eventsRes{ - pushedEventsCount: 0, - aggID: []string{"104", "105"}, - aggType: []repository.AggregateType{repository.AggregateType(t.Name())}, + events: []*repository.Event{ + generateEvent(t, "106"), + generateEvent(t, "106"), + generateEvent(t, "106"), + generateEvent(t, "106"), + generateEvent(t, "107"), + generateEvent(t, "107"), + generateEvent(t, "107"), + generateEvent(t, "107"), + generateEvent(t, "108"), + generateEvent(t, "108"), + generateEvent(t, "108"), + generateEvent(t, "108"), }, }, - }, - { - name: "push two aggregates mixed check previous multiple events", - args: args{ - events: combineEventLists( - linkEvents( - generateEvent(t, "106", true, 0), - generateEvent(t, "106", false, 0), - generateEvent(t, "106", false, 0), - generateEvent(t, "106", true, 0), - ), - linkEvents( - generateEvent(t, "107", false, 0), - generateEvent(t, "107", true, 0), - generateEvent(t, "107", false, 0), - generateEvent(t, "107", true, 0), - ), - linkEvents( - generateEvent(t, "108", true, 0), - generateEvent(t, "108", false, 0), - generateEvent(t, "108", false, 0), - generateEvent(t, "108", true, 0), - ), - ), - }, - }, - { - name: "failed push same aggregate in two transactions", - args: args{ - events: combineEventLists( - linkEvents( - generateEvent(t, "109", true, 0), - ), - linkEvents( - generateEvent(t, "109", true, 0), - ), - ), - }, res: res{ - wantErr: true, + wantErr: false, eventsRes: eventsRes{ - pushedEventsCount: 0, - aggID: []string{"109"}, + pushedEventsCount: 12, + aggID: []string{"106", "107", "108"}, aggType: []repository.AggregateType{repository.AggregateType(t.Name())}, }, }, @@ -633,25 +485,19 @@ func TestCRDB_Push_Parallel(t *testing.T) { name: "clients push different aggregates", args: args{ events: [][]*repository.Event{ - linkEvents( - generateEvent(t, "200", false, 0), - generateEvent(t, "200", true, 0), - generateEvent(t, "200", false, 0), - ), - linkEvents( - generateEvent(t, "201", false, 0), - generateEvent(t, "201", true, 0), - generateEvent(t, "201", false, 0), - ), - combineEventLists( - linkEvents( - generateEvent(t, "202", false, 0), - ), - linkEvents( - generateEvent(t, "203", true, 0), - generateEvent(t, "203", false, 0), - ), - ), + { + generateEvent(t, "200"), + generateEvent(t, "200"), + generateEvent(t, "200"), + generateEvent(t, "201"), + generateEvent(t, "201"), + generateEvent(t, "201"), + }, + { + generateEvent(t, "202"), + generateEvent(t, "203"), + generateEvent(t, "203"), + }, }, }, res: res{ @@ -664,41 +510,31 @@ func TestCRDB_Push_Parallel(t *testing.T) { }, }, { - name: "clients push same aggregates no check previous", + name: "clients push same aggregates", args: args{ events: [][]*repository.Event{ - linkEvents( - generateEvent(t, "204", false, 0), - generateEvent(t, "204", false, 0), - ), - linkEvents( - generateEvent(t, "204", false, 0), - generateEvent(t, "204", false, 0), - ), - combineEventLists( - linkEvents( - generateEvent(t, "205", false, 0), - generateEvent(t, "205", false, 0), - generateEvent(t, "205", false, 0), - ), - linkEvents( - generateEvent(t, "206", false, 0), - generateEvent(t, "206", false, 0), - generateEvent(t, "206", false, 0), - ), - ), - combineEventLists( - linkEvents( - generateEvent(t, "204", false, 0), - ), - linkEvents( - generateEvent(t, "205", false, 0), - generateEvent(t, "205", false, 0), - ), - linkEvents( - generateEvent(t, "206", false, 0), - ), - ), + { + generateEvent(t, "204"), + generateEvent(t, "204"), + }, + { + generateEvent(t, "204"), + generateEvent(t, "204"), + }, + { + generateEvent(t, "205"), + generateEvent(t, "205"), + generateEvent(t, "205"), + generateEvent(t, "206"), + generateEvent(t, "206"), + generateEvent(t, "206"), + }, + { + generateEvent(t, "204"), + generateEvent(t, "205"), + generateEvent(t, "205"), + generateEvent(t, "206"), + }, }, }, res: res{ @@ -711,24 +547,24 @@ func TestCRDB_Push_Parallel(t *testing.T) { }, }, { - name: "clients push different aggregates one with check previous", + name: "clients push different aggregates", args: args{ events: [][]*repository.Event{ - linkEvents( - generateEvent(t, "207", false, 0), - generateEvent(t, "207", false, 0), - generateEvent(t, "207", false, 0), - generateEvent(t, "207", false, 0), - generateEvent(t, "207", false, 0), - generateEvent(t, "207", false, 0), - ), - linkEvents( - generateEvent(t, "208", true, 0), - generateEvent(t, "208", true, 0), - generateEvent(t, "208", true, 0), - generateEvent(t, "208", true, 0), - generateEvent(t, "208", true, 0), - ), + { + generateEvent(t, "207"), + generateEvent(t, "207"), + generateEvent(t, "207"), + generateEvent(t, "207"), + generateEvent(t, "207"), + generateEvent(t, "207"), + }, + { + generateEvent(t, "208"), + generateEvent(t, "208"), + generateEvent(t, "208"), + generateEvent(t, "208"), + generateEvent(t, "208"), + }, }, }, res: res{ @@ -741,21 +577,21 @@ func TestCRDB_Push_Parallel(t *testing.T) { }, }, { - name: "clients push different aggregates all with check previous on first event fail", + name: "clients push same aggregates", args: args{ events: [][]*repository.Event{ - linkEvents( - generateEventWithData(t, "210", true, 0, []byte(`{ "transaction": 1 }`)), - generateEventWithData(t, "210", false, 0, []byte(`{ "transaction": 1.1 }`)), - ), - linkEvents( - generateEventWithData(t, "210", true, 0, []byte(`{ "transaction": 2 }`)), - generateEventWithData(t, "210", false, 0, []byte(`{ "transaction": 2.1 }`)), - ), - linkEvents( - generateEventWithData(t, "210", true, 0, []byte(`{ "transaction": 3 }`)), - generateEventWithData(t, "210", false, 0, []byte(`{ "transaction": 30.1 }`)), - ), + { + generateEventWithData(t, "210", []byte(`{ "transaction": 1 }`)), + generateEventWithData(t, "210", []byte(`{ "transaction": 1.1 }`)), + }, + { + generateEventWithData(t, "210", []byte(`{ "transaction": 2 }`)), + generateEventWithData(t, "210", []byte(`{ "transaction": 2.1 }`)), + }, + { + generateEventWithData(t, "210", []byte(`{ "transaction": 3 }`)), + generateEventWithData(t, "210", []byte(`{ "transaction": 30.1 }`)), + }, }, }, res: res{ @@ -850,9 +686,9 @@ func TestCRDB_Filter(t *testing.T) { }, fields: fields{ existingEvents: []*repository.Event{ - generateEvent(t, "300", false, 0), - generateEvent(t, "300", false, 0), - generateEvent(t, "300", false, 0), + generateEvent(t, "300"), + generateEvent(t, "300"), + generateEvent(t, "300"), }, }, res: res{ @@ -873,10 +709,10 @@ func TestCRDB_Filter(t *testing.T) { }, fields: fields{ existingEvents: []*repository.Event{ - generateEvent(t, "303", false, 0), - generateEvent(t, "303", false, 0), - generateEvent(t, "303", false, 0), - generateEvent(t, "305", false, 0), + generateEvent(t, "303"), + generateEvent(t, "303"), + generateEvent(t, "303"), + generateEvent(t, "305"), }, }, res: res{ @@ -938,9 +774,9 @@ func TestCRDB_LatestSequence(t *testing.T) { }, fields: fields{ existingEvents: []*repository.Event{ - generateEvent(t, "400", false, 0), - generateEvent(t, "400", false, 0), - generateEvent(t, "400", false, 0), + generateEvent(t, "400"), + generateEvent(t, "400"), + generateEvent(t, "400"), }, }, res: res{ @@ -960,9 +796,9 @@ func TestCRDB_LatestSequence(t *testing.T) { }, fields: fields{ existingEvents: []*repository.Event{ - generateEvent(t, "401", false, 0), - generateEvent(t, "401", false, 0), - generateEvent(t, "401", false, 0), + generateEvent(t, "401"), + generateEvent(t, "401"), + generateEvent(t, "401"), }, }, res: res{ @@ -1016,8 +852,8 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) { name: "two events of same aggregate same resource owner", args: args{ events: []*repository.Event{ - generateEvent(t, "500", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - generateEvent(t, "500", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "500", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "500", func(e *repository.Event) { e.ResourceOwner = "caos" }), }, }, fields: fields{ @@ -1032,8 +868,8 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) { name: "two events of different aggregate same resource owner", args: args{ events: []*repository.Event{ - generateEvent(t, "501", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - generateEvent(t, "502", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "501", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "502", func(e *repository.Event) { e.ResourceOwner = "caos" }), }, }, fields: fields{ @@ -1048,8 +884,8 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) { name: "two events of different aggregate different resource owner", args: args{ events: []*repository.Event{ - generateEvent(t, "503", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - generateEvent(t, "504", false, 0, func(e *repository.Event) { e.ResourceOwner = "zitadel" }), + generateEvent(t, "503", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "504", func(e *repository.Event) { e.ResourceOwner = "zitadel" }), }, }, fields: fields{ @@ -1063,16 +899,12 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) { { name: "events of different aggregate different resource owner", args: args{ - events: combineEventLists( - linkEvents( - generateEvent(t, "505", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - generateEvent(t, "505", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - ), - linkEvents( - generateEvent(t, "506", false, 0, func(e *repository.Event) { e.ResourceOwner = "zitadel" }), - generateEvent(t, "506", false, 0, func(e *repository.Event) { e.ResourceOwner = "zitadel" }), - ), - ), + events: []*repository.Event{ + generateEvent(t, "505", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "505", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "506", func(e *repository.Event) { e.ResourceOwner = "zitadel" }), + generateEvent(t, "506", func(e *repository.Event) { e.ResourceOwner = "zitadel" }), + }, }, fields: fields{ aggregateIDs: []string{"505", "506"}, @@ -1085,16 +917,12 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) { { name: "events of different aggregate different resource owner per event", args: args{ - events: combineEventLists( - linkEvents( - generateEvent(t, "507", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - generateEvent(t, "507", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }), - ), - linkEvents( - generateEvent(t, "508", false, 0, func(e *repository.Event) { e.ResourceOwner = "zitadel" }), - generateEvent(t, "508", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }), - ), - ), + events: []*repository.Event{ + generateEvent(t, "507", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "507", func(e *repository.Event) { e.ResourceOwner = "ignored" }), + generateEvent(t, "508", func(e *repository.Event) { e.ResourceOwner = "zitadel" }), + generateEvent(t, "508", func(e *repository.Event) { e.ResourceOwner = "ignored" }), + }, }, fields: fields{ aggregateIDs: []string{"507", "508"}, @@ -1107,16 +935,12 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) { { name: "events of one aggregate different resource owner per event", args: args{ - events: combineEventLists( - linkEvents( - generateEvent(t, "509", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - generateEvent(t, "509", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }), - ), - linkEvents( - generateEvent(t, "509", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }), - generateEvent(t, "509", false, 0, func(e *repository.Event) { e.ResourceOwner = "ignored" }), - ), - ), + events: []*repository.Event{ + generateEvent(t, "509", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "509", func(e *repository.Event) { e.ResourceOwner = "ignored" }), + generateEvent(t, "509", func(e *repository.Event) { e.ResourceOwner = "ignored" }), + generateEvent(t, "509", func(e *repository.Event) { e.ResourceOwner = "ignored" }), + }, }, fields: fields{ aggregateIDs: []string{"509"}, @@ -1180,34 +1004,16 @@ func canceledCtx() context.Context { return ctx } -func combineEventLists(lists ...[]*repository.Event) []*repository.Event { - combined := make([]*repository.Event, 0) - for _, list := range lists { - combined = append(combined, list...) - } - return combined -} - -func linkEvents(events ...*repository.Event) []*repository.Event { - for i := 1; i < len(events); i++ { - events[i].PreviousEvent = events[i-1] - } - return events -} - -func generateEvent(t *testing.T, aggregateID string, checkPrevious bool, previousSeq uint64, opts ...func(*repository.Event)) *repository.Event { +func generateEvent(t *testing.T, aggregateID string, opts ...func(*repository.Event)) *repository.Event { t.Helper() e := &repository.Event{ - AggregateID: aggregateID, - AggregateType: repository.AggregateType(t.Name()), - CheckPreviousSequence: checkPrevious, - EditorService: "svc", - EditorUser: "user", - PreviousEvent: nil, - PreviousSequence: previousSeq, - ResourceOwner: "ro", - Type: "test.created", - Version: "v1", + AggregateID: aggregateID, + AggregateType: repository.AggregateType(t.Name()), + EditorService: "svc", + EditorUser: "user", + ResourceOwner: "ro", + Type: "test.created", + Version: "v1", } for _, opt := range opts { @@ -1217,19 +1023,16 @@ func generateEvent(t *testing.T, aggregateID string, checkPrevious bool, previou return e } -func generateEventWithData(t *testing.T, aggregateID string, checkPrevious bool, previousSeq uint64, data []byte) *repository.Event { +func generateEventWithData(t *testing.T, aggregateID string, data []byte) *repository.Event { t.Helper() return &repository.Event{ - AggregateID: aggregateID, - AggregateType: repository.AggregateType(t.Name()), - CheckPreviousSequence: checkPrevious, - EditorService: "svc", - EditorUser: "user", - PreviousEvent: nil, - PreviousSequence: previousSeq, - ResourceOwner: "ro", - Type: "test.created", - Version: "v1", - Data: data, + AggregateID: aggregateID, + AggregateType: repository.AggregateType(t.Name()), + EditorService: "svc", + EditorUser: "user", + ResourceOwner: "ro", + Type: "test.created", + Version: "v1", + Data: data, } } diff --git a/internal/eventstore/v2/repository/sql/local_crdb_test.go b/internal/eventstore/v2/repository/sql/local_crdb_test.go index 9f3a192d0e..0da55050ab 100644 --- a/internal/eventstore/v2/repository/sql/local_crdb_test.go +++ b/internal/eventstore/v2/repository/sql/local_crdb_test.go @@ -27,9 +27,13 @@ func TestMain(m *testing.M) { } testCRDBClient, err = sql.Open("postgres", ts.PGURL().String()) + if err != nil { logging.LogWithFields("REPOS-CF6dQ", "error", err).Fatal("unable to connect to db") } + if err = testCRDBClient.Ping(); err != nil { + logging.LogWithFields("REPOS-CF6dQ", "error", err).Fatal("unable to ping db") + } defer func() { testCRDBClient.Close() diff --git a/internal/eventstore/v2/repository/sql/query.go b/internal/eventstore/v2/repository/sql/query.go index 319fa29ee9..a3a351a9aa 100644 --- a/internal/eventstore/v2/repository/sql/query.go +++ b/internal/eventstore/v2/repository/sql/query.go @@ -112,12 +112,11 @@ func eventsScanner(scanner scan, dest interface{}) (err error) { ) if err != nil { - logging.Log("SQL-kn1Sw").WithError(err).Warn("unable to scan row") - return z_errors.ThrowInternal(err, "SQL-J0hFS", "unable to scan row") + logging.Log("SQL-3mofs").WithError(err).Warn("unable to scan row") + return z_errors.ThrowInternal(err, "SQL-M0dsf", "unable to scan row") } event.PreviousSequence = uint64(previousSequence) - event.Data = make([]byte, len(data)) copy(event.Data, data) diff --git a/internal/eventstore/v2/repository/sql/query_test.go b/internal/eventstore/v2/repository/sql/query_test.go index 8f368ab35c..d3342bd7a4 100644 --- a/internal/eventstore/v2/repository/sql/query_test.go +++ b/internal/eventstore/v2/repository/sql/query_test.go @@ -329,9 +329,9 @@ func Test_query_events_with_crdb(t *testing.T) { fields: fields{ client: testCRDBClient, existingEvents: []*repository.Event{ - generateEvent(t, "300", false, 0), - generateEvent(t, "300", false, 0), - generateEvent(t, "300", false, 0), + generateEvent(t, "300"), + generateEvent(t, "300"), + generateEvent(t, "300"), }, }, res: res{ @@ -352,10 +352,10 @@ func Test_query_events_with_crdb(t *testing.T) { fields: fields{ client: testCRDBClient, existingEvents: []*repository.Event{ - generateEvent(t, "301", false, 0), - generateEvent(t, "302", false, 0), - generateEvent(t, "302", false, 0), - generateEvent(t, "303", false, 0, func(e *repository.Event) { e.AggregateType = "not in list" }), + generateEvent(t, "301"), + generateEvent(t, "302"), + generateEvent(t, "302"), + generateEvent(t, "303", func(e *repository.Event) { e.AggregateType = "not in list" }), }, }, res: res{ @@ -377,11 +377,11 @@ func Test_query_events_with_crdb(t *testing.T) { fields: fields{ client: testCRDBClient, existingEvents: []*repository.Event{ - generateEvent(t, "303", false, 0), - generateEvent(t, "303", false, 0), - generateEvent(t, "303", false, 0), - generateEvent(t, "304", false, 0, func(e *repository.Event) { e.AggregateType = "not in list" }), - generateEvent(t, "305", false, 0), + generateEvent(t, "303"), + generateEvent(t, "303"), + generateEvent(t, "303"), + generateEvent(t, "304", func(e *repository.Event) { e.AggregateType = "not in list" }), + generateEvent(t, "305"), }, }, res: res{ @@ -402,11 +402,11 @@ func Test_query_events_with_crdb(t *testing.T) { fields: fields{ client: testCRDBClient, existingEvents: []*repository.Event{ - generateEvent(t, "306", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - generateEvent(t, "307", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - generateEvent(t, "308", false, 0, func(e *repository.Event) { e.ResourceOwner = "caos" }), - generateEvent(t, "309", false, 0, func(e *repository.Event) { e.ResourceOwner = "orgID" }), - generateEvent(t, "309", false, 0, func(e *repository.Event) { e.ResourceOwner = "orgID" }), + generateEvent(t, "306", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "307", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "308", func(e *repository.Event) { e.ResourceOwner = "caos" }), + generateEvent(t, "309", func(e *repository.Event) { e.ResourceOwner = "orgID" }), + generateEvent(t, "309", func(e *repository.Event) { e.ResourceOwner = "orgID" }), }, }, res: res{ @@ -428,11 +428,11 @@ func Test_query_events_with_crdb(t *testing.T) { fields: fields{ client: testCRDBClient, existingEvents: []*repository.Event{ - generateEvent(t, "307", false, 0, func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }), - generateEvent(t, "307", false, 0, func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }), - generateEvent(t, "308", false, 0, func(e *repository.Event) { e.EditorService = "ADMIN-API" }), - generateEvent(t, "309", false, 0, func(e *repository.Event) { e.EditorService = "AUTHAPI" }), - generateEvent(t, "309", false, 0, func(e *repository.Event) { e.EditorService = "AUTHAPI" }), + generateEvent(t, "307", func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }), + generateEvent(t, "307", func(e *repository.Event) { e.EditorService = "MANAGEMENT-API" }), + generateEvent(t, "308", func(e *repository.Event) { e.EditorService = "ADMIN-API" }), + generateEvent(t, "309", func(e *repository.Event) { e.EditorService = "AUTHAPI" }), + generateEvent(t, "309", func(e *repository.Event) { e.EditorService = "AUTHAPI" }), }, }, res: res{ @@ -455,13 +455,13 @@ func Test_query_events_with_crdb(t *testing.T) { fields: fields{ client: testCRDBClient, existingEvents: []*repository.Event{ - generateEvent(t, "310", false, 0, func(e *repository.Event) { e.EditorUser = "adlerhurst" }), - generateEvent(t, "310", false, 0, func(e *repository.Event) { e.EditorUser = "adlerhurst" }), - generateEvent(t, "310", false, 0, func(e *repository.Event) { e.EditorUser = "nobody" }), - generateEvent(t, "311", false, 0, func(e *repository.Event) { e.EditorUser = "" }), - generateEvent(t, "311", false, 0, func(e *repository.Event) { e.EditorUser = "" }), - generateEvent(t, "312", false, 0, func(e *repository.Event) { e.EditorUser = "fforootd" }), - generateEvent(t, "312", false, 0, func(e *repository.Event) { e.EditorUser = "fforootd" }), + generateEvent(t, "310", func(e *repository.Event) { e.EditorUser = "adlerhurst" }), + generateEvent(t, "310", func(e *repository.Event) { e.EditorUser = "adlerhurst" }), + generateEvent(t, "310", func(e *repository.Event) { e.EditorUser = "nobody" }), + generateEvent(t, "311", func(e *repository.Event) { e.EditorUser = "" }), + generateEvent(t, "311", func(e *repository.Event) { e.EditorUser = "" }), + generateEvent(t, "312", func(e *repository.Event) { e.EditorUser = "fforootd" }), + generateEvent(t, "312", func(e *repository.Event) { e.EditorUser = "fforootd" }), }, }, res: res{ @@ -483,15 +483,15 @@ func Test_query_events_with_crdb(t *testing.T) { fields: fields{ client: testCRDBClient, existingEvents: []*repository.Event{ - generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.created" }), - generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.updated" }), - generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.deactivated" }), - generateEvent(t, "311", false, 0, func(e *repository.Event) { e.Type = "user.locked" }), - generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.created" }), - generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.updated" }), - generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.deactivated" }), - generateEvent(t, "312", false, 0, func(e *repository.Event) { e.Type = "user.reactivated" }), - generateEvent(t, "313", false, 0, func(e *repository.Event) { e.Type = "user.locked" }), + generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.created" }), + generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.updated" }), + generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.deactivated" }), + generateEvent(t, "311", func(e *repository.Event) { e.Type = "user.locked" }), + generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.created" }), + generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.updated" }), + generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.deactivated" }), + generateEvent(t, "312", func(e *repository.Event) { e.Type = "user.reactivated" }), + generateEvent(t, "313", func(e *repository.Event) { e.Type = "user.locked" }), }, }, res: res{ diff --git a/internal/ui/login/handler/login.go b/internal/ui/login/handler/login.go index 7da2ea9678..f8cdb5ee1c 100644 --- a/internal/ui/login/handler/login.go +++ b/internal/ui/login/handler/login.go @@ -2,6 +2,8 @@ package handler import ( "context" + "github.com/caos/zitadel/internal/v2/command" + "github.com/caos/zitadel/internal/v2/query" "net" "net/http" @@ -27,6 +29,8 @@ type Login struct { router http.Handler renderer *Renderer parser *form.Parser + command *command.CommandSide + query *query.QuerySide authRepo auth_repository.Repository baseURL string zitadelURL string @@ -56,7 +60,7 @@ const ( handlerPrefix = "/login" ) -func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, systemDefaults systemdefaults.SystemDefaults, localDevMode bool) (*Login, string) { +func CreateLogin(config Config, command *command.CommandSide, query *query.QuerySide, authRepo *eventsourcing.EsRepository, systemDefaults systemdefaults.SystemDefaults, localDevMode bool) (*Login, string) { aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey) if err != nil { logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto") @@ -65,6 +69,8 @@ func CreateLogin(config Config, authRepo *eventsourcing.EsRepository, systemDefa oidcAuthCallbackURL: config.OidcAuthCallbackURL, baseURL: config.BaseURL, zitadelURL: config.ZitadelURL, + command: command, + query: query, authRepo: authRepo, IDPConfigAesCrypto: aesCrypto, } diff --git a/internal/ui/login/login.go b/internal/ui/login/login.go index 0d28b56006..4d4cbbbd84 100644 --- a/internal/ui/login/login.go +++ b/internal/ui/login/login.go @@ -4,12 +4,14 @@ import ( "github.com/caos/zitadel/internal/auth/repository/eventsourcing" "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/ui/login/handler" + "github.com/caos/zitadel/internal/v2/command" + "github.com/caos/zitadel/internal/v2/query" ) type Config struct { Handler handler.Config } -func Start(config Config, authRepo *eventsourcing.EsRepository, systemdefaults systemdefaults.SystemDefaults, localDevMode bool) (*handler.Login, string) { - return handler.CreateLogin(config.Handler, authRepo, systemdefaults, localDevMode) +func Start(config Config, command *command.CommandSide, query *query.QuerySide, authRepo *eventsourcing.EsRepository, systemdefaults systemdefaults.SystemDefaults, localDevMode bool) (*handler.Login, string) { + return handler.CreateLogin(config.Handler, command, query, authRepo, systemdefaults, localDevMode) } diff --git a/internal/user/repository/eventsourcing/eventstore.go b/internal/user/repository/eventsourcing/eventstore.go index 342c700e92..2369642565 100644 --- a/internal/user/repository/eventsourcing/eventstore.go +++ b/internal/user/repository/eventsourcing/eventstore.go @@ -1303,47 +1303,49 @@ func (es *UserEventstore) verifyMFAOTP(otp *usr_model.OTP, code string) error { } func (es *UserEventstore) AddU2F(ctx context.Context, userID string, accountName string, isLoginUI bool) (*usr_model.WebAuthNToken, error) { - user, err := es.HumanByID(ctx, userID) - if err != nil { - return nil, err - } - webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...) - if err != nil { - return nil, err - } - tokenID, err := es.idGenerator.Next() - if err != nil { - return nil, err - } - webAuthN.WebAuthNTokenID = tokenID - webAuthN.State = usr_model.MFAStateNotReady - repoUser := model.UserFromModel(user) - repoWebAuthN := model.WebAuthNFromModel(webAuthN) - - err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) - if err != nil { - return nil, err - } - return webAuthN, nil + //user, err := es.HumanByID(ctx, userID) + //if err != nil { + // return nil, err + //} + //webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...) + //if err != nil { + // return nil, err + //} + //tokenID, err := es.idGenerator.Next() + //if err != nil { + // return nil, err + //} + //webAuthN.WebAuthNTokenID = tokenID + //webAuthN.State = usr_model.MFAStateNotReady + //repoUser := model.UserFromModel(user) + //repoWebAuthN := model.WebAuthNFromModel(webAuthN) + // + //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) + //if err != nil { + // return nil, err + //} + //return webAuthN, nil + return nil, nil } func (es *UserEventstore) VerifyU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { - user, err := es.HumanByID(ctx, userID) - if err != nil { - return err - } - _, token := user.Human.GetU2FToVerify() - webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "") - if err != nil { - return err - } - repoUser := model.UserFromModel(user) - repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID) - err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) - if err != nil { - return err - } - es.userCache.cacheUser(repoUser) + //user, err := es.HumanByID(ctx, userID) + //if err != nil { + // return err + //} + //_, token := user.Human.GetU2FToVerify() + //webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "") + //if err != nil { + // return err + //} + //repoUser := model.UserFromModel(user) + //repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID) + //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) + //if err != nil { + // return err + //} + //es.userCache.cacheUser(repoUser) + //return nil return nil } @@ -1365,49 +1367,51 @@ func (es *UserEventstore) RemoveU2FToken(ctx context.Context, userID, webAuthNTo } func (es *UserEventstore) BeginU2FLogin(ctx context.Context, userID string, authRequest *req_model.AuthRequest, isLoginUI bool) (*usr_model.WebAuthNLogin, error) { - user, err := es.HumanByID(ctx, userID) - if err != nil { - return nil, err - } - if user.U2FTokens == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5Mk8s", "Errors.User.MFA.U2F.NotExisting") - } - - webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...) - if err != nil { - return nil, err - } - webAuthNLogin.AuthRequest = authRequest - repoUser := model.UserFromModel(user) - repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin) - err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin)) - if err != nil { - return nil, err - } - return webAuthNLogin, nil + //user, err := es.HumanByID(ctx, userID) + //if err != nil { + // return nil, err + //} + //if user.U2FTokens == nil { + // return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5Mk8s", "Errors.User.MFA.U2F.NotExisting") + //} + // + //webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...) + //if err != nil { + // return nil, err + //} + //webAuthNLogin.AuthRequest = authRequest + //repoUser := model.UserFromModel(user) + //repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin) + //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAU2FBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin)) + //if err != nil { + // return nil, err + //} + //return webAuthNLogin, nil + return nil, nil } func (es *UserEventstore) VerifyMFAU2F(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest, isLoginUI bool) error { - user, err := es.HumanByID(ctx, userID) - if err != nil { - return err - } - _, u2f := user.GetU2FLogin(authRequest.ID) - keyID, signCount, finishErr := es.webauthn.FinishLogin(user, u2f, credentialData, isLoginUI, user.U2FTokens...) - if finishErr != nil && keyID == nil { - return finishErr - } - - _, token := user.GetU2FByKeyID(keyID) - repoUser := model.UserFromModel(user) - repoAuthRequest := model.AuthRequestFromModel(authRequest) - - signAgg := MFAU2FSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil) - err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg) - if err != nil { - return err - } - return finishErr + //user, err := es.HumanByID(ctx, userID) + //if err != nil { + // return err + //} + //_, u2f := user.GetU2FLogin(authRequest.ID) + //keyID, signCount, finishErr := es.webauthn.FinishLogin(user, u2f, credentialData, isLoginUI, user.U2FTokens...) + //if finishErr != nil && keyID == nil { + // return finishErr + //} + // + //_, token := user.GetU2FByKeyID(keyID) + //repoUser := model.UserFromModel(user) + //repoAuthRequest := model.AuthRequestFromModel(authRequest) + // + //signAgg := MFAU2FSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil) + //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg) + //if err != nil { + // return err + //} + //return finishErr + return nil } func (es *UserEventstore) GetPasswordless(ctx context.Context, userID string) ([]*usr_model.WebAuthNToken, error) { @@ -1419,45 +1423,47 @@ func (es *UserEventstore) GetPasswordless(ctx context.Context, userID string) ([ } func (es *UserEventstore) AddPasswordless(ctx context.Context, userID, accountName string, isLoginUI bool) (*usr_model.WebAuthNToken, error) { - user, err := es.HumanByID(ctx, userID) - if err != nil { - return nil, err - } - webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...) - if err != nil { - return nil, err - } - tokenID, err := es.idGenerator.Next() - if err != nil { - return nil, err - } - webAuthN.WebAuthNTokenID = tokenID - repoUser := model.UserFromModel(user) - repoWebAuthN := model.WebAuthNFromModel(webAuthN) - err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) - if err != nil { - return nil, err - } - return webAuthN, nil + //user, err := es.HumanByID(ctx, userID) + //if err != nil { + // return nil, err + //} + //webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...) + //if err != nil { + // return nil, err + //} + //tokenID, err := es.idGenerator.Next() + //if err != nil { + // return nil, err + //} + //webAuthN.WebAuthNTokenID = tokenID + //repoUser := model.UserFromModel(user) + //repoWebAuthN := model.WebAuthNFromModel(webAuthN) + //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessAddAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) + //if err != nil { + // return nil, err + //} + //return webAuthN, nil + return nil, nil } func (es *UserEventstore) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { - user, err := es.HumanByID(ctx, userID) - if err != nil { - return err - } - _, token := user.Human.GetPasswordlessToVerify() - webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "") - if err != nil { - return err - } - repoUser := model.UserFromModel(user) - repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID) - err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) - if err != nil { - return err - } - es.userCache.cacheUser(repoUser) + //user, err := es.HumanByID(ctx, userID) + //if err != nil { + // return err + //} + //_, token := user.Human.GetPasswordlessToVerify() + //webAuthN, err := es.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "") + //if err != nil { + // return err + //} + //repoUser := model.UserFromModel(user) + //repoWebAuthN := model.WebAuthNVerifyFromModel(webAuthN, userAgentID) + //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessVerifyAggregate(es.AggregateCreator(), repoUser, repoWebAuthN)) + //if err != nil { + // return err + //} + //es.userCache.cacheUser(repoUser) + //return nil return nil } @@ -1479,47 +1485,49 @@ func (es *UserEventstore) RemovePasswordlessToken(ctx context.Context, userID, w } func (es *UserEventstore) BeginPasswordlessLogin(ctx context.Context, userID string, authRequest *req_model.AuthRequest, isLoginUI bool) (*usr_model.WebAuthNLogin, error) { - user, err := es.HumanByID(ctx, userID) - if err != nil { - return nil, err - } - if user.PasswordlessTokens == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5M9sd", "Errors.User.MFA.Passwordless.NotExisting") - } - webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...) - if err != nil { - return nil, err - } - webAuthNLogin.AuthRequest = authRequest - repoUser := model.UserFromModel(user) - repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin) - err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin)) - if err != nil { - return nil, err - } - return webAuthNLogin, nil + //user, err := es.HumanByID(ctx, userID) + //if err != nil { + // return nil, err + //} + //if user.PasswordlessTokens == nil { + // return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5M9sd", "Errors.User.MFA.Passwordless.NotExisting") + //} + //webAuthNLogin, err := es.webauthn.BeginLogin(user, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...) + //if err != nil { + // return nil, err + //} + //webAuthNLogin.AuthRequest = authRequest + //repoUser := model.UserFromModel(user) + //repoWebAuthNLogin := model.WebAuthNLoginFromModel(webAuthNLogin) + //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, MFAPasswordlessBeginLoginAggregate(es.AggregateCreator(), repoUser, repoWebAuthNLogin)) + //if err != nil { + // return nil, err + //} + //return webAuthNLogin, nil + return nil, nil } func (es *UserEventstore) VerifyPasswordless(ctx context.Context, userID string, credentialData []byte, authRequest *req_model.AuthRequest, isLoginUI bool) error { - user, err := es.HumanByID(ctx, userID) - if err != nil { - return err - } - _, passwordless := user.GetPasswordlessLogin(authRequest.ID) - keyID, signCount, finishErr := es.webauthn.FinishLogin(user, passwordless, credentialData, isLoginUI, user.PasswordlessTokens...) - if finishErr != nil && keyID == nil { - return finishErr - } - _, token := user.GetPasswordlessByKeyID(keyID) - repoUser := model.UserFromModel(user) - repoAuthRequest := model.AuthRequestFromModel(authRequest) - - signAgg := MFAPasswordlessSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil) - err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg) - if err != nil { - return err - } - return finishErr + //user, err := es.HumanByID(ctx, userID) + //if err != nil { + // return err + //} + //_, passwordless := user.GetPasswordlessLogin(authRequest.ID) + //keyID, signCount, finishErr := es.webauthn.FinishLogin(user, passwordless, credentialData, isLoginUI, user.PasswordlessTokens...) + //if finishErr != nil && keyID == nil { + // return finishErr + //} + //_, token := user.GetPasswordlessByKeyID(keyID) + //repoUser := model.UserFromModel(user) + //repoAuthRequest := model.AuthRequestFromModel(authRequest) + // + //signAgg := MFAPasswordlessSignCountAggregate(es.AggregateCreator(), repoUser, &model.WebAuthNSignCount{WebauthNTokenID: token.WebAuthNTokenID, SignCount: signCount}, repoAuthRequest, finishErr == nil) + //err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, signAgg) + //if err != nil { + // return err + //} + //return finishErr + return nil } func (es *UserEventstore) SignOut(ctx context.Context, agentID string, userIDs []string) error { diff --git a/internal/v2/command/command.go b/internal/v2/command/command.go index cae4e5c7ae..e05095ac74 100644 --- a/internal/v2/command/command.go +++ b/internal/v2/command/command.go @@ -2,6 +2,8 @@ package command import ( "context" + global_model "github.com/caos/zitadel/internal/model" + webauthn_helper "github.com/caos/zitadel/internal/webauthn" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/crypto" @@ -9,6 +11,7 @@ import ( "github.com/caos/zitadel/internal/id" "github.com/caos/zitadel/internal/telemetry/tracing" iam_repo "github.com/caos/zitadel/internal/v2/repository/iam" + usr_repo "github.com/caos/zitadel/internal/v2/repository/user" ) type CommandSide struct { @@ -18,14 +21,18 @@ type CommandSide struct { idpConfigSecretCrypto crypto.Crypto - userPasswordAlg crypto.HashAlgorithm - initializeUserCode crypto.Generator - emailVerificationCode crypto.Generator - phoneVerificationCode crypto.Generator - passwordVerificationCode crypto.Generator - machineKeyAlg crypto.EncryptionAlgorithm - machineKeySize int + userPasswordAlg crypto.HashAlgorithm + initializeUserCode crypto.Generator + emailVerificationCode crypto.Generator + phoneVerificationCode crypto.Generator + passwordVerificationCode crypto.Generator + machineKeyAlg crypto.EncryptionAlgorithm + machineKeySize int + //TODO: remove global model, or move to domain + multifactors global_model.Multifactors applicationSecretGenerator crypto.Generator + + webauthn *webauthn_helper.WebAuthN } type Config struct { @@ -40,6 +47,7 @@ func StartCommandSide(config *Config) (repo *CommandSide, err error) { iamDomain: config.SystemDefaults.Domain, } iam_repo.RegisterEventMappers(repo.eventstore) + usr_repo.RegisterEventMappers(repo.eventstore) //TODO: simplify!!!! repo.idpConfigSecretCrypto, err = crypto.NewAESCrypto(config.SystemDefaults.IDPConfigVerificationKey) @@ -58,8 +66,23 @@ func StartCommandSide(config *Config) (repo *CommandSide, err error) { repo.machineKeyAlg = userEncryptionAlgorithm repo.machineKeySize = int(config.SystemDefaults.SecretGenerators.MachineKeySize) + aesOTPCrypto, err := crypto.NewAESCrypto(config.SystemDefaults.Multifactors.OTP.VerificationKey) + if err != nil { + return nil, err + } + repo.multifactors = global_model.Multifactors{ + OTP: global_model.OTP{ + CryptoMFA: aesOTPCrypto, + Issuer: config.SystemDefaults.Multifactors.OTP.Issuer, + }, + } passwordAlg := crypto.NewBCrypt(config.SystemDefaults.SecretGenerators.PasswordSaltCost) repo.applicationSecretGenerator = crypto.NewHashGenerator(config.SystemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg) + web, err := webauthn_helper.StartServer(config.SystemDefaults.WebAuthN) + if err != nil { + return nil, err + } + repo.webauthn = web return repo, nil } diff --git a/internal/v2/command/org.go b/internal/v2/command/org.go index 998a366a1e..23afca291c 100644 --- a/internal/v2/command/org.go +++ b/internal/v2/command/org.go @@ -9,7 +9,18 @@ import ( "github.com/caos/zitadel/internal/v2/repository/user" ) -func (r *CommandSide) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.User) error { +func (r *CommandSide) getOrg(ctx context.Context, orgID string) (*domain.Org, error) { + writeModel, err := r.getOrgWriteModelByID(ctx, orgID) + if err != nil { + return nil, err + } + if writeModel.State == domain.OrgStateActive { + return nil, caos_errs.ThrowInternal(err, "COMMAND-4M9sf", "Errors.Org.NotFound") + } + return orgWriteModelToOrg(writeModel), nil +} + +func (r *CommandSide) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human) error { orgAgg, userAgg, orgMemberAgg, err := r.setUpOrg(ctx, organisation, admin) if err != nil { return err @@ -19,13 +30,13 @@ func (r *CommandSide) SetUpOrg(ctx context.Context, organisation *domain.Org, ad return err } -func (r *CommandSide) setUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.User) (*org.Aggregate, *user.Aggregate, *org.Aggregate, error) { +func (r *CommandSide) setUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human) (*org.Aggregate, *user.Aggregate, *org.Aggregate, error) { orgAgg, _, err := r.addOrg(ctx, organisation) if err != nil { return nil, nil, nil, err } - userAgg, _, err := r.addHuman(ctx, orgAgg.ID(), admin.UserName, admin.Human) + userAgg, _, err := r.addHuman(ctx, orgAgg.ID(), admin) if err != nil { return nil, nil, nil, err } diff --git a/internal/v2/command/org_converter.go b/internal/v2/command/org_converter.go index 2b10e0d925..5f655d3c37 100644 --- a/internal/v2/command/org_converter.go +++ b/internal/v2/command/org_converter.go @@ -6,9 +6,10 @@ import ( func orgWriteModelToOrg(wm *OrgWriteModel) *domain.Org { return &domain.Org{ - ObjectRoot: writeModelToObjectRoot(wm.WriteModel), - Name: wm.Name, - State: wm.State, + ObjectRoot: writeModelToObjectRoot(wm.WriteModel), + Name: wm.Name, + State: wm.State, + PrimaryDomain: wm.PrimaryDomain, } } diff --git a/internal/v2/command/org_model.go b/internal/v2/command/org_model.go index eaa93e37ec..9ba1f3c0a4 100644 --- a/internal/v2/command/org_model.go +++ b/internal/v2/command/org_model.go @@ -10,8 +10,9 @@ import ( type OrgWriteModel struct { eventstore.WriteModel - Name string - State domain.OrgState + Name string + State domain.OrgState + PrimaryDomain string } func NewOrgWriteModel(orgID string) *OrgWriteModel { @@ -30,6 +31,8 @@ func (wm *OrgWriteModel) AppendEvents(events ...eventstore.EventReader) { case *org.OrgAddedEvent, *iam.LabelPolicyChangedEvent: wm.WriteModel.AppendEvents(e) + case *org.DomainPrimarySetEvent: + wm.WriteModel.AppendEvents(e) } } } @@ -42,6 +45,8 @@ func (wm *OrgWriteModel) Reduce() error { wm.State = domain.OrgStateActive case *org.OrgChangedEvent: wm.Name = e.Name + case *org.DomainPrimarySetEvent: + wm.PrimaryDomain = e.Domain } } return nil diff --git a/internal/v2/command/org_policy_org_iam.go b/internal/v2/command/org_policy_org_iam.go index 8b6c200f9a..6b9661fd16 100644 --- a/internal/v2/command/org_policy_org_iam.go +++ b/internal/v2/command/org_policy_org_iam.go @@ -34,7 +34,7 @@ func (r *CommandSide) addOrgIAMPolicy(ctx context.Context, orgAgg *org.Aggregate return err } if addedPolicy.State == domain.PolicyStateActive { - return caos_errs.ThrowAlreadyExists(nil, "ORG-5M0ds", "Errors.Org.OrgIAMPolicy.AlreadyExists") + return caos_errs.ThrowAlreadyExists(nil, "ORG-1M8ds", "Errors.Org.OrgIAMPolicy.AlreadyExists") } orgAgg.PushEvents(org.NewOrgIAMPolicyAddedEvent(ctx, policy.UserLoginMustBeDomain)) return nil diff --git a/internal/v2/command/setup_step1.go b/internal/v2/command/setup_step1.go index fe88cb9990..768e35d066 100644 --- a/internal/v2/command/setup_step1.go +++ b/internal/v2/command/setup_step1.go @@ -105,20 +105,18 @@ func (r *CommandSide) SetupStep1(ctx context.Context, step1 *Step1) error { Name: organisation.Name, Domains: []*domain.OrgDomain{{Domain: organisation.Domain}}, }, - &domain.User{ - UserName: organisation.Owner.UserName, - Human: &domain.Human{ - Profile: &domain.Profile{ - FirstName: organisation.Owner.FirstName, - LastName: organisation.Owner.LastName, - }, - Password: &domain.Password{ - SecretString: organisation.Owner.Password, - }, - Email: &domain.Email{ - EmailAddress: organisation.Owner.Email, - IsEmailVerified: true, - }, + &domain.Human{ + Username: organisation.Owner.UserName, + Profile: &domain.Profile{ + FirstName: organisation.Owner.FirstName, + LastName: organisation.Owner.LastName, + }, + Password: &domain.Password{ + SecretString: organisation.Owner.Password, + }, + Email: &domain.Email{ + EmailAddress: organisation.Owner.Email, + IsEmailVerified: true, }, }) if err != nil { diff --git a/internal/v2/command/user.go b/internal/v2/command/user.go index 7d490adbbf..7cb9eb700e 100644 --- a/internal/v2/command/user.go +++ b/internal/v2/command/user.go @@ -9,42 +9,6 @@ import ( "github.com/caos/zitadel/internal/v2/repository/user" ) -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, 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, orgID, user.UserName, user.Machine) - if err != nil { - return nil, err - } - return &domain.User{UserName: user.UserName, Machine: machine}, nil - } - return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-8K0df", "Errors.User.TypeUndefined") -} - -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, orgID, user.UserName, user.Human, nil) - if err != nil { - return nil, err - } - return &domain.User{UserName: user.UserName, Human: human}, nil - } - 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") @@ -75,100 +39,84 @@ func (r *CommandSide) ChangeUsername(ctx context.Context, orgID, userID, userNam return r.eventstore.PushAggregate(ctx, existingUser, userAgg) } -func (r *CommandSide) DeactivateUser(ctx context.Context, userID, resourceOwner string) (*domain.User, error) { +func (r *CommandSide) DeactivateUser(ctx context.Context, userID, resourceOwner string) error { if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0gDf", "Errors.User.UserIDMissing") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0gDf", "Errors.User.UserIDMissing") } existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner) if err != nil { - return nil, err + return err } if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { - return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9ds", "Errors.User.NotFound") + return 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") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sf", "Errors.User.AlreadyInactive") } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg.PushEvents(user.NewUserDeactivatedEvent(ctx)) - err = r.eventstore.PushAggregate(ctx, existingUser, userAgg) - if err != nil { - return nil, err - } - return writeModelToUser(existingUser), nil + return r.eventstore.PushAggregate(ctx, existingUser, userAgg) } -func (r *CommandSide) ReactivateUser(ctx context.Context, userID, resourceOwner string) (*domain.User, error) { +func (r *CommandSide) ReactivateUser(ctx context.Context, userID, resourceOwner string) error { if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M9ds", "Errors.User.UserIDMissing") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M9ds", "Errors.User.UserIDMissing") } existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner) if err != nil { - return nil, err + return err } if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { - return nil, caos_errs.ThrowNotFound(nil, "COMMAND-4M0sd", "Errors.User.NotFound") + return 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") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0sf", "Errors.User.NotInactive") } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg.PushEvents(user.NewUserReactivatedEvent(ctx)) - err = r.eventstore.PushAggregate(ctx, existingUser, userAgg) - if err != nil { - return nil, err - } - return writeModelToUser(existingUser), nil + return r.eventstore.PushAggregate(ctx, existingUser, userAgg) } -func (r *CommandSide) LockUser(ctx context.Context, userID, resourceOwner string) (*domain.User, error) { +func (r *CommandSide) LockUser(ctx context.Context, userID, resourceOwner string) error { if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0sd", "Errors.User.UserIDMissing") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0sd", "Errors.User.UserIDMissing") } existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner) if err != nil { - return nil, err + return err } if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { - return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5M9fs", "Errors.User.NotFound") + return 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") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.ShouldBeActiveOrInitial") } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg.PushEvents(user.NewUserLockedEvent(ctx)) - err = r.eventstore.PushAggregate(ctx, existingUser, userAgg) - if err != nil { - return nil, err - } - return writeModelToUser(existingUser), nil + return r.eventstore.PushAggregate(ctx, existingUser, userAgg) } -func (r *CommandSide) UnlockUser(ctx context.Context, userID, resourceOwner string) (*domain.User, error) { +func (r *CommandSide) UnlockUser(ctx context.Context, userID, resourceOwner string) error { if userID == "" { - return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M0dse", "Errors.User.UserIDMissing") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M0dse", "Errors.User.UserIDMissing") } existingUser, err := r.userWriteModelByID(ctx, userID, resourceOwner) if err != nil { - return nil, err + return err } if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { - return nil, caos_errs.ThrowNotFound(nil, "COMMAND-M0dos", "Errors.User.NotFound") + return 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") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.NotLocked") } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg.PushEvents(user.NewUserUnlockedEvent(ctx)) - err = r.eventstore.PushAggregate(ctx, existingUser, userAgg) - if err != nil { - return nil, err - } - return writeModelToUser(existingUser), nil + return r.eventstore.PushAggregate(ctx, existingUser, userAgg) } func (r *CommandSide) RemoveUser(ctx context.Context, userID, resourceOwner string) error { @@ -201,3 +149,15 @@ func (r *CommandSide) userWriteModelByID(ctx context.Context, userID, resourceOw } return writeModel, nil } + +func (r *CommandSide) userReadModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *UserWriteModel, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + writeModel = NewUserWriteModel(userID, resourceOwner) + err = r.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + return writeModel, nil +} diff --git a/internal/v2/command/user_converter.go b/internal/v2/command/user_converter.go index 6a12528ab4..8a9ff21592 100644 --- a/internal/v2/command/user_converter.go +++ b/internal/v2/command/user_converter.go @@ -4,17 +4,11 @@ import ( "github.com/caos/zitadel/internal/v2/domain" ) -func writeModelToUser(wm *UserWriteModel) *domain.User { - return &domain.User{ - ObjectRoot: writeModelToObjectRoot(wm.WriteModel), - UserName: wm.UserName, - State: wm.UserState, - } -} - func writeModelToHuman(wm *HumanWriteModel) *domain.Human { return &domain.Human{ ObjectRoot: writeModelToObjectRoot(wm.WriteModel), + Username: wm.UserName, + State: wm.UserState, Profile: &domain.Profile{ FirstName: wm.FirstName, LastName: wm.LastName, @@ -82,3 +76,34 @@ func writeModelToMachine(wm *MachineWriteModel) *domain.Machine { Description: wm.Description, } } + +func readModelToU2FTokens(wm *HumanU2FTokensReadModel) []*domain.WebAuthNToken { + tokens := make([]*domain.WebAuthNToken, len(wm.WebAuthNTokens)) + for i, token := range wm.WebAuthNTokens { + tokens[i] = writeModelToWebAuthN(token) + } + return tokens +} + +func readModelToPasswordlessTokens(wm *HumanPasswordlessTokensReadModel) []*domain.WebAuthNToken { + tokens := make([]*domain.WebAuthNToken, len(wm.WebAuthNTokens)) + for i, token := range wm.WebAuthNTokens { + tokens[i] = writeModelToWebAuthN(token) + } + return tokens +} + +func writeModelToWebAuthN(wm *HumanWebAuthNWriteModel) *domain.WebAuthNToken { + return &domain.WebAuthNToken{ + ObjectRoot: writeModelToObjectRoot(wm.WriteModel), + WebAuthNTokenID: wm.WebauthNTokenID, + Challenge: wm.Challenge, + KeyID: wm.KeyID, + PublicKey: wm.PublicKey, + AttestationType: wm.AttestationType, + AAGUID: wm.AAGUID, + SignCount: wm.SignCount, + WebAuthNTokenName: wm.WebAuthNTokenName, + State: wm.State, + } +} diff --git a/internal/v2/command/user_human.go b/internal/v2/command/user_human.go index ef7b79980c..ab4a5befc7 100644 --- a/internal/v2/command/user_human.go +++ b/internal/v2/command/user_human.go @@ -2,14 +2,26 @@ package command import ( "context" + "github.com/caos/zitadel/internal/eventstore/v2" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/user" ) -func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, human *domain.Human) (*domain.Human, error) { - userAgg, addedHuman, err := r.addHuman(ctx, orgID, username, human) +func (r *CommandSide) getHuman(ctx context.Context, userID, resourceowner string) (*domain.Human, error) { + writeModel, err := r.getHumanWriteModelByID(ctx, userID, resourceowner) + if err != nil { + return nil, err + } + if writeModel.UserState == domain.UserStateUnspecified || writeModel.UserState == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-M9dsd", "Errors.User.NotFound") + } + return writeModelToHuman(writeModel), nil +} + +func (r *CommandSide) AddHuman(ctx context.Context, orgID string, human *domain.Human) (*domain.Human, error) { + userAgg, addedHuman, err := r.addHuman(ctx, orgID, human) if err != nil { return nil, err } @@ -21,10 +33,34 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, huma return writeModelToHuman(addedHuman), nil } -func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, human *domain.Human) (*user.Aggregate, *HumanWriteModel, error) { +func (r *CommandSide) addHuman(ctx context.Context, orgID string, human *domain.Human) (*user.Aggregate, *HumanWriteModel, error) { if !human.IsValid() { return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid") } + return r.createHuman(ctx, orgID, human, nil, false) +} + +func (r *CommandSide) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP) (*domain.Human, error) { + userAgg, addedHuman, err := r.registerHuman(ctx, orgID, human, externalIDP) + if err != nil { + return nil, err + } + err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg) + if err != nil { + return nil, err + } + + return writeModelToHuman(addedHuman), nil +} + +func (r *CommandSide) registerHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP) (*user.Aggregate, *HumanWriteModel, error) { + if !human.IsValid() || externalIDP == nil && (human.Password == nil || human.SecretString == "") { + return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-9dk45", "Errors.User.Invalid") + } + return r.createHuman(ctx, orgID, human, externalIDP, true) +} + +func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP, selfregister bool) (*user.Aggregate, *HumanWriteModel, error) { userID, err := r.idGenerator.Next() if err != nil { return nil, nil, err @@ -40,8 +76,8 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, huma } addedHuman := NewHumanWriteModel(human.AggregateID, orgID) - //TODO: Check Unique Username - if err := human.CheckOrgIAMPolicy(username, orgIAMPolicy); err != nil { + //TODO: Check Unique Username or unique external idp + if err := human.CheckOrgIAMPolicy(human.Username, orgIAMPolicy); err != nil { return nil, nil, err } human.SetNamesAsDisplayname() @@ -50,6 +86,73 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, huma } userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel) + var createEvent eventstore.EventPusher + if selfregister { + createEvent = createRegisterHumanEvent(ctx, human.Username, human) + } else { + createEvent = createAddHumanEvent(ctx, human.Username, human) + } + userAgg.PushEvents(createEvent) + + if externalIDP != nil { + if !externalIDP.IsValid() { + return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4Dj9s", "Errors.User.ExternalIDP.Invalid") + } + //TODO: check if idpconfig exists + userAgg.PushEvents(user.NewHumanExternalIDPAddedEvent(ctx, externalIDP.IDPConfigID, externalIDP.DisplayName)) + } + if human.IsInitialState() { + initCode, err := domain.NewInitUserCode(r.initializeUserCode) + if err != nil { + return nil, nil, err + } + userAgg.PushEvents(user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry)) + } + if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified { + userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx)) + } + if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified { + phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode) + if err != nil { + return nil, nil, err + } + user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry) + } else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified { + userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx)) + } + + return userAgg, addedHuman, nil +} + +func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email, resourceowner string) (err error) { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UserIDMissing") + } + + existingEmail, err := r.emailWriteModel(ctx, userID, resourceowner) + 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) +} + +func createAddHumanEvent(ctx context.Context, username string, human *domain.Human) *user.HumanAddedEvent { addEvent := user.NewHumanAddedEvent( ctx, username, @@ -75,60 +178,10 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, huma if human.Password != nil { addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired) } - userAgg.PushEvents(addEvent) - - if human.IsInitialState() { - initCode, err := domain.NewInitUserCode(r.initializeUserCode) - if err != nil { - return nil, nil, err - } - user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry) - } - if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified { - userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx)) - } - if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified { - phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode) - if err != nil { - return nil, nil, err - } - user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry) - } else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified { - userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx)) - } - - return userAgg, addedHuman, nil + return addEvent } -func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, human *domain.Human, externalIDP *domain.ExternalIDP) (*domain.Human, error) { - if !human.IsValid() || externalIDP == nil && (human.Password == nil || human.SecretString == "") { - return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-9dk45", "Errors.User.Invalid") - } - userID, err := r.idGenerator.Next() - if err != nil { - return nil, err - } - human.AggregateID = userID - orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, orgID) - if err != nil { - return nil, err - } - pwPolicy, err := r.GetOrgPasswordComplexityPolicy(ctx, orgID) - if err != nil { - return nil, err - } - - addedHuman := NewHumanWriteModel(human.AggregateID, orgID) - //TODO: Check Unique Username or unique external idp - if err := human.CheckOrgIAMPolicy(username, orgIAMPolicy); err != nil { - return nil, err - } - human.SetNamesAsDisplayname() - if err := human.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg, true); err != nil { - return nil, err - } - - userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel) +func createRegisterHumanEvent(ctx context.Context, username string, human *domain.Human) *user.HumanRegisteredEvent { addEvent := user.NewHumanRegisteredEvent( ctx, username, @@ -154,61 +207,14 @@ func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, if human.Password != nil { addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired) } - userAgg.PushEvents(addEvent) - //TODO: Add External IDP Event - if human.IsInitialState() { - initCode, err := domain.NewInitUserCode(r.initializeUserCode) - if err != nil { - return nil, err - } - userAgg.PushEvents(user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry)) - } + return addEvent +} - if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified { - userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx)) - } - if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified { - phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode) - if err != nil { - return nil, err - } - 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) +func (r *CommandSide) getHumanWriteModelByID(ctx context.Context, userID, resourceowner string) (*HumanWriteModel, error) { + humanWriteModel := NewHumanWriteModel(userID, resourceowner) + err := r.eventstore.FilterToQueryReducer(ctx, humanWriteModel) if err != nil { return nil, err } - - return writeModelToHuman(addedHuman), nil -} - -func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email, resourceOwner string) (err error) { - if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UserIDMissing") - } - - existingEmail, err := r.emailWriteModel(ctx, userID, resourceOwner) - 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) + return humanWriteModel, nil } diff --git a/internal/v2/command/user_human_address_model.go b/internal/v2/command/user_human_address_model.go index 19c4b8f3d6..57b3374b67 100644 --- a/internal/v2/command/user_human_address_model.go +++ b/internal/v2/command/user_human_address_model.go @@ -30,16 +30,7 @@ func NewHumanAddressWriteModel(userID, resourceOwner string) *HumanAddressWriteM } func (wm *HumanAddressWriteModel) AppendEvents(events ...eventstore.EventReader) { - for _, event := range events { - switch e := event.(type) { - case *user.HumanAddressChangedEvent: - wm.AppendEvents(e) - case *user.HumanAddedEvent, *user.HumanRegisteredEvent: - wm.AppendEvents(e) - case *user.UserRemovedEvent: - wm.AppendEvents(e) - } - } + wm.WriteModel.AppendEvents(events...) } func (wm *HumanAddressWriteModel) Reduce() error { diff --git a/internal/v2/command/user_human_email.go b/internal/v2/command/user_human_email.go index 4ba02bde28..2eefc1100b 100644 --- a/internal/v2/command/user_human_email.go +++ b/internal/v2/command/user_human_email.go @@ -2,6 +2,8 @@ package command import ( "context" + "github.com/caos/logging" + "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" @@ -45,6 +47,34 @@ func (r *CommandSide) ChangeHumanEmail(ctx context.Context, email *domain.Email) return writeModelToEmail(existingEmail), nil } +func (r *CommandSide) VerifyHumanEmail(ctx context.Context, userID, code, resourceowner string) error { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") + } + if code == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-çm0ds", "Errors.User.Code.Empty") + } + + existingCode, err := r.emailWriteModel(ctx, userID, resourceowner) + if err != nil { + return err + } + if existingCode.Code == nil || existingCode.UserState == domain.UserStateUnspecified || existingCode.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound") + } + + userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.emailVerificationCode) + if err == nil { + userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx)) + return r.eventstore.PushAggregate(ctx, existingCode, userAgg) + } + userAgg.PushEvents(user.NewHumanEmailVerificationFailedEvent(ctx)) + err = r.eventstore.PushAggregate(ctx, existingCode, userAgg) + logging.LogWithFields("COMMAND-Dg2z5", "userID", userAgg.ID()).OnError(err).Error("NewHumanEmailVerificationFailedEvent push failed") + return caos_errs.ThrowInvalidArgument(err, "COMMAND-Gdsgs", "Errors.User.Code.Invalid") +} + func (r *CommandSide) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string) error { if userID == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") diff --git a/internal/v2/command/user_human_email_model.go b/internal/v2/command/user_human_email_model.go index 2c33c3c52b..e8aa463117 100644 --- a/internal/v2/command/user_human_email_model.go +++ b/internal/v2/command/user_human_email_model.go @@ -2,10 +2,11 @@ package command import ( "context" - + "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" + "time" ) type HumanEmailWriteModel struct { @@ -14,6 +15,10 @@ type HumanEmailWriteModel struct { Email string IsEmailVerified bool + Code *crypto.CryptoValue + CodeCreationDate time.Time + CodeExpiry time.Duration + UserState domain.UserState } @@ -27,18 +32,7 @@ func NewHumanEmailWriteModel(userID, resourceOwner string) *HumanEmailWriteModel } func (wm *HumanEmailWriteModel) 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.UserRemovedEvent: - wm.AppendEvents(e) - } - } + wm.WriteModel.AppendEvents(events...) } func (wm *HumanEmailWriteModel) Reduce() error { @@ -53,8 +47,14 @@ func (wm *HumanEmailWriteModel) Reduce() error { case *user.HumanEmailChangedEvent: wm.Email = e.EmailAddress wm.IsEmailVerified = false + wm.Code = nil + case *user.HumanEmailCodeAddedEvent: + wm.Code = e.Code + wm.CodeCreationDate = e.CreationDate() + wm.CodeExpiry = e.Expiry case *user.HumanEmailVerifiedEvent: wm.IsEmailVerified = true + wm.Code = nil if wm.UserState == domain.UserStateInitial { wm.UserState = domain.UserStateActive } diff --git a/internal/v2/command/user_human_externalidp.go b/internal/v2/command/user_human_externalidp.go index e3be4e1c0d..2ca0c5ee3f 100644 --- a/internal/v2/command/user_human_externalidp.go +++ b/internal/v2/command/user_human_externalidp.go @@ -22,7 +22,7 @@ func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *d return err } if existingExternalIDP.State == domain.ExternalIDPStateUnspecified || existingExternalIDP.State == domain.ExternalIDPStateRemoved { - return caos_errs.ThrowNotFound(nil, "COMMAND-5M0ds", "Errors.User.ExternalIDP.NotFound") + return caos_errs.ThrowNotFound(nil, "COMMAND-1M9xR", "Errors.User.ExternalIDP.NotFound") } userAgg := UserAggregateFromWriteModel(&existingExternalIDP.WriteModel) if !cascade { diff --git a/internal/v2/command/user_human_externalidp_model.go b/internal/v2/command/user_human_externalidp_model.go index 43f842535e..cde992a8d8 100644 --- a/internal/v2/command/user_human_externalidp_model.go +++ b/internal/v2/command/user_human_externalidp_model.go @@ -28,24 +28,7 @@ func NewHumanExternalIDPWriteModel(userID, idpConfigID, externalUserID, resource } 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) - } - } + wm.WriteModel.AppendEvents(events...) } func (wm *HumanExternalIDPWriteModel) Reduce() error { diff --git a/internal/v2/command/user_human_model.go b/internal/v2/command/user_human_model.go index acfed20f8f..79f3d227b0 100644 --- a/internal/v2/command/user_human_model.go +++ b/internal/v2/command/user_human_model.go @@ -48,28 +48,10 @@ func NewHumanWriteModel(userID, resourceOwner string) *HumanWriteModel { } func (wm *HumanWriteModel) AppendEvents(events ...eventstore.EventReader) { - for _, event := range events { - switch e := event.(type) { - case *user.HumanAddedEvent, - *user.HumanRegisteredEvent, - *user.HumanProfileChangedEvent, - *user.HumanEmailChangedEvent, - *user.HumanEmailVerifiedEvent, - *user.HumanPhoneChangedEvent, - *user.HumanPhoneVerifiedEvent, - *user.HumanAddressChangedEvent, - *user.HumanPasswordChangedEvent, - *user.UserDeactivatedEvent, - *user.UserReactivatedEvent, - *user.UserLockedEvent, - *user.UserUnlockedEvent, - *user.UserRemovedEvent: - wm.AppendEvents(e) - } - } + wm.WriteModel.AppendEvents(events...) } -//TODO: Compute State? initial/active +//TODO: Compute OTPState? initial/active func (wm *HumanWriteModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { diff --git a/internal/v2/command/user_human_otp.go b/internal/v2/command/user_human_otp.go index 65b4ff36d2..195e402945 100644 --- a/internal/v2/command/user_human_otp.go +++ b/internal/v2/command/user_human_otp.go @@ -3,11 +3,87 @@ package command import ( "context" caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/models" "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) AddHumanOTP(ctx context.Context, userID, resourceowner string) (*domain.OTP, error) { + if userID == "" { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing") + } + human, err := r.getHuman(ctx, userID, resourceowner) + if err != nil { + return nil, err + } + org, err := r.getOrg(ctx, human.ResourceOwner) + if err != nil { + return nil, err + } + orgPolicy, err := r.getOrgIAMPolicy(ctx, org.AggregateID) + if err != nil { + return nil, err + } + otpWriteModel, err := r.otpWriteModelByID(ctx, userID, resourceowner) + if err != nil { + return nil, err + } + if otpWriteModel.State == domain.MFAStateReady { + return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady") + } + userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel) + accountName := domain.GenerateLoginName(human.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain) + if accountName == "" { + accountName = human.EmailAddress + } + key, secret, err := domain.NewOTPKey(r.multifactors.OTP.Issuer, accountName, r.multifactors.OTP.CryptoMFA) + if err != nil { + return nil, err + } + userAgg.PushEvents( + user.NewHumanOTPAddedEvent(ctx, secret), + ) + + err = r.eventstore.PushAggregate(ctx, otpWriteModel, userAgg) + if err != nil { + return nil, err + } + return &domain.OTP{ + ObjectRoot: models.ObjectRoot{ + AggregateID: human.AggregateID, + }, + SecretString: key.Secret(), + Url: key.URL(), + }, nil +} + +func (r *CommandSide) CheckMFAOTPSetup(ctx context.Context, userID, code, userAgentID, resourceowner string) error { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing") + } + + existingOTP, err := r.otpWriteModelByID(ctx, userID, resourceowner) + if err != nil { + return err + } + if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotExisting") + } + if existingOTP.State == domain.MFAStateReady { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-qx4ls", "Errors.Users.MFA.OTP.AlreadyReady") + } + if err := domain.VerifyMFAOTP(code, existingOTP.Secret, r.multifactors.OTP.CryptoMFA); err != nil { + return err + } + userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel) + userAgg.PushEvents( + user.NewHumanOTPVerifiedEvent(ctx, userAgentID), + ) + + return r.eventstore.PushAggregate(ctx, existingOTP, userAgg) +} + func (r *CommandSide) RemoveHumanOTP(ctx context.Context, userID, resourceOwner string) error { if userID == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing") @@ -17,8 +93,8 @@ func (r *CommandSide) RemoveHumanOTP(ctx context.Context, userID, resourceOwner 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") + if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-Hd9sd", "Errors.User.MFA.OTP.NotExisting") } userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel) userAgg.PushEvents( diff --git a/internal/v2/command/user_human_otp_model.go b/internal/v2/command/user_human_otp_model.go index 07b13d284f..7a3f239a6f 100644 --- a/internal/v2/command/user_human_otp_model.go +++ b/internal/v2/command/user_human_otp_model.go @@ -10,9 +10,8 @@ import ( type HumanOTPWriteModel struct { eventstore.WriteModel + State domain.MFAState Secret *crypto.CryptoValue - - State domain.OTPState } func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel { @@ -25,16 +24,7 @@ func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel { } 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) - } - } + wm.WriteModel.AppendEvents(events...) } func (wm *HumanOTPWriteModel) Reduce() error { @@ -42,11 +32,13 @@ func (wm *HumanOTPWriteModel) Reduce() error { switch e := event.(type) { case *user.HumanOTPAddedEvent: wm.Secret = e.Secret - wm.State = domain.OTPStateActive + wm.State = domain.MFAStateNotReady + case *user.HumanOTPVerifiedEvent: + wm.State = domain.MFAStateReady case *user.HumanOTPRemovedEvent: - wm.State = domain.OTPStateRemoved + wm.State = domain.MFAStateRemoved case *user.UserRemovedEvent: - wm.State = domain.OTPStateRemoved + wm.State = domain.MFAStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/v2/command/user_human_password.go b/internal/v2/command/user_human_password.go index f2eca34f75..1d4a8ee222 100644 --- a/internal/v2/command/user_human_password.go +++ b/internal/v2/command/user_human_password.go @@ -24,11 +24,11 @@ func (r *CommandSide) SetOneTimePassword(ctx context.Context, orgID, userID, pas return r.changePassword(ctx, orgID, userID, "", password, existingPassword) } -func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID, resourceOwner string) (err error) { +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, resourceOwner) + existingPassword, err := r.passwordWriteModel(ctx, userID, orgID) if err != nil { return err } diff --git a/internal/v2/command/user_human_password_model.go b/internal/v2/command/user_human_password_model.go index f3ca35be50..8820e9905e 100644 --- a/internal/v2/command/user_human_password_model.go +++ b/internal/v2/command/user_human_password_model.go @@ -26,18 +26,7 @@ func NewHumanPasswordWriteModel(userID, resourceOwner string) *HumanPasswordWrit } 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) - } - } + wm.WriteModel.AppendEvents(events...) } func (wm *HumanPasswordWriteModel) Reduce() error { diff --git a/internal/v2/command/user_human_phone.go b/internal/v2/command/user_human_phone.go index 320c6174d3..303e45cd2d 100644 --- a/internal/v2/command/user_human_phone.go +++ b/internal/v2/command/user_human_phone.go @@ -2,7 +2,8 @@ package command import ( "context" - + "github.com/caos/logging" + "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" @@ -14,12 +15,12 @@ func (r *CommandSide) ChangeHumanPhone(ctx context.Context, phone *domain.Phone) return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.Phone.Invalid") } - existingPhone, err := r.phoneWriteModel(ctx, phone.AggregateID, phone.ResourceOwner) + existingPhone, err := r.phoneWriteModelByID(ctx, phone.AggregateID, phone.ResourceOwner) 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") + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-aM9cs", "Errors.User.Phone.NotFound") } changedEvent, hasChanged := existingPhone.NewChangedEvent(ctx, phone.PhoneNumber) if !hasChanged { @@ -46,12 +47,48 @@ func (r *CommandSide) ChangeHumanPhone(ctx context.Context, phone *domain.Phone) return writeModelToPhone(existingPhone), nil } -func (r *CommandSide) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceOwner string) error { +func (r *CommandSide) VerifyHumanPhone(ctx context.Context, userID, code, resourceowner string) error { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Km9ds", "Errors.User.UserIDMissing") + } + if code == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-wMe9f", "Errors.User.Code.Empty") + } + + existingCode, err := r.phoneWriteModelByID(ctx, userID, resourceowner) + if err != nil { + return err + } + if existingCode.Code == nil || existingCode.State == domain.PhoneStateUnspecified || existingCode.State == domain.PhoneStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-Rsj8c", "Errors.User.Code.NotFound") + } + + userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.emailVerificationCode) + if err == nil { + userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx)) + return r.eventstore.PushAggregate(ctx, existingCode, userAgg) + } + userAgg.PushEvents(user.NewHumanPhoneVerificationFailedEvent(ctx)) + err = r.eventstore.PushAggregate(ctx, existingCode, userAgg) + + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.emailVerificationCode) + if err == nil { + userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx)) + return r.eventstore.PushAggregate(ctx, existingCode, userAgg) + } + userAgg.PushEvents(user.NewHumanEmailVerificationFailedEvent(ctx)) + err = r.eventstore.PushAggregate(ctx, existingCode, userAgg) + logging.LogWithFields("COMMAND-5M9ds", "userID", userAgg.ID()).OnError(err).Error("NewHumanEmailVerificationFailedEvent push failed") + return caos_errs.ThrowInvalidArgument(err, "COMMAND-sM0cs", "Errors.User.Code.Invalid") +} + +func (r *CommandSide) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceowner string) error { if userID == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") } - existingPhone, err := r.phoneWriteModel(ctx, userID, resourceOwner) + existingPhone, err := r.phoneWriteModelByID(ctx, userID, resourceowner) if err != nil { return err } @@ -75,12 +112,12 @@ func (r *CommandSide) RemoveHumanPhone(ctx context.Context, userID, resourceOwne return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.UserIDMissing") } - existingPhone, err := r.phoneWriteModel(ctx, userID, resourceOwner) + existingPhone, err := r.phoneWriteModelByID(ctx, userID, resourceOwner) 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") + return caos_errs.ThrowNotFound(nil, "COMMAND-p6rsc", "Errors.User.Phone.NotFound") } userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel) userAgg.PushEvents( @@ -89,7 +126,7 @@ func (r *CommandSide) RemoveHumanPhone(ctx context.Context, userID, resourceOwne return r.eventstore.PushAggregate(ctx, existingPhone, userAgg) } -func (r *CommandSide) phoneWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPhoneWriteModel, err error) { +func (r *CommandSide) phoneWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPhoneWriteModel, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/v2/command/user_human_phone_model.go b/internal/v2/command/user_human_phone_model.go index d5ac27a227..381ff6ff18 100644 --- a/internal/v2/command/user_human_phone_model.go +++ b/internal/v2/command/user_human_phone_model.go @@ -2,10 +2,11 @@ package command import ( "context" - + "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" + "time" ) type HumanPhoneWriteModel struct { @@ -14,6 +15,10 @@ type HumanPhoneWriteModel struct { Phone string IsPhoneVerified bool + Code *crypto.CryptoValue + CodeCreationDate time.Time + CodeExpiry time.Duration + State domain.PhoneState } @@ -27,20 +32,7 @@ func NewHumanPhoneWriteModel(userID, resourceOwner string) *HumanPhoneWriteModel } 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) - } - } + wm.WriteModel.AppendEvents(events...) } func (wm *HumanPhoneWriteModel) Reduce() error { @@ -49,8 +41,8 @@ func (wm *HumanPhoneWriteModel) Reduce() error { case *user.HumanAddedEvent: if e.PhoneNumber != "" { wm.Phone = e.PhoneNumber - wm.State = domain.PhoneStateActive } + wm.State = domain.PhoneStateActive case *user.HumanRegisteredEvent: if e.PhoneNumber != "" { wm.Phone = e.PhoneNumber @@ -60,8 +52,14 @@ func (wm *HumanPhoneWriteModel) Reduce() error { wm.Phone = e.PhoneNumber wm.IsPhoneVerified = false wm.State = domain.PhoneStateActive + wm.Code = nil case *user.HumanPhoneVerifiedEvent: wm.IsPhoneVerified = true + wm.Code = nil + case *user.HumanPhoneCodeAddedEvent: + wm.Code = e.Code + wm.CodeCreationDate = e.CreationDate() + wm.CodeExpiry = e.Expiry case *user.HumanPhoneRemovedEvent: wm.State = domain.PhoneStateRemoved case *user.UserRemovedEvent: diff --git a/internal/v2/command/user_human_profile.go b/internal/v2/command/user_human_profile.go index 282dd87b13..9388407bc8 100644 --- a/internal/v2/command/user_human_profile.go +++ b/internal/v2/command/user_human_profile.go @@ -19,7 +19,7 @@ func (r *CommandSide) ChangeHumanProfile(ctx context.Context, profile *domain.Pr if existingProfile.UserState == domain.UserStateUnspecified || existingProfile.UserState == domain.UserStateDeleted { 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)) + changedEvent, hasChanged := existingProfile.NewChangedEvent(ctx, profile.FirstName, profile.LastName, profile.NickName, profile.DisplayName, profile.PreferredLanguage, profile.Gender) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.User.Profile.NotChanged") } diff --git a/internal/v2/command/user_human_profile_model.go b/internal/v2/command/user_human_profile_model.go index 0acfcd74ea..8d15988833 100644 --- a/internal/v2/command/user_human_profile_model.go +++ b/internal/v2/command/user_human_profile_model.go @@ -33,16 +33,7 @@ func NewHumanProfileWriteModel(userID, resourceOwner string) *HumanProfileWriteM } func (wm *HumanProfileWriteModel) AppendEvents(events ...eventstore.EventReader) { - for _, event := range events { - switch e := event.(type) { - case *user.HumanProfileChangedEvent: - wm.AppendEvents(e) - case *user.HumanAddedEvent, *user.HumanRegisteredEvent: - wm.AppendEvents(e) - case *user.UserRemovedEvent: - wm.AppendEvents(e) - } - } + wm.WriteModel.AppendEvents(events...) } func (wm *HumanProfileWriteModel) Reduce() error { diff --git a/internal/v2/command/user_human_webauthn.go b/internal/v2/command/user_human_webauthn.go index bd28e167db..f5ff84709c 100644 --- a/internal/v2/command/user_human_webauthn.go +++ b/internal/v2/command/user_human_webauthn.go @@ -6,16 +6,193 @@ import ( "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" + usr_repo "github.com/caos/zitadel/internal/v2/repository/user" ) +func (r *CommandSide) getHumanU2FTokens(ctx context.Context, userID, resourceowner string) ([]*domain.WebAuthNToken, error) { + tokenReadModel := NewHumanU2FTokensReadModel(userID, resourceowner) + err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel) + if err != nil { + return nil, err + } + if tokenReadModel.UserState == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-4M0ds", "Errors.User.NotFound") + } + return readModelToU2FTokens(tokenReadModel), nil +} + +func (r *CommandSide) getHumanPasswordlessTokens(ctx context.Context, userID, resourceowner string) ([]*domain.WebAuthNToken, error) { + tokenReadModel := NewHumanPasswordlessTokensReadModel(userID, resourceowner) + err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel) + if err != nil { + return nil, err + } + if tokenReadModel.UserState == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Mv9sd", "Errors.User.NotFound") + } + return readModelToPasswordlessTokens(tokenReadModel), nil +} + +func (r *CommandSide) AddHumanU2F(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) { + u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner) + if err != nil { + return nil, err + } + addWebAuthN, userAgg, webAuthN, err := r.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, u2fTokens) + if err != nil { + return nil, err + } + userAgg.PushEvents(usr_repo.NewHumanU2FAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge)) + + err = r.eventstore.PushAggregate(ctx, addWebAuthN, userAgg) + if err != nil { + return nil, err + } + createdWebAuthN := writeModelToWebAuthN(addWebAuthN) + createdWebAuthN.CredentialCreationData = webAuthN.CredentialCreationData + createdWebAuthN.AllowedCredentialIDs = webAuthN.AllowedCredentialIDs + createdWebAuthN.UserVerification = webAuthN.UserVerification + return createdWebAuthN, nil +} + +func (r *CommandSide) AddHumanPasswordless(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) { + passwordlessTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner) + if err != nil { + return nil, err + } + addWebAuthN, userAgg, webAuthN, err := r.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, passwordlessTokens) + if err != nil { + return nil, err + } + userAgg.PushEvents(usr_repo.NewHumanU2FAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge)) + + err = r.eventstore.PushAggregate(ctx, addWebAuthN, userAgg) + if err != nil { + return nil, err + } + createdWebAuthN := writeModelToWebAuthN(addWebAuthN) + createdWebAuthN.CredentialCreationData = webAuthN.CredentialCreationData + createdWebAuthN.AllowedCredentialIDs = webAuthN.AllowedCredentialIDs + createdWebAuthN.UserVerification = webAuthN.UserVerification + return createdWebAuthN, nil +} + +func (r *CommandSide) addHumanWebAuthN(ctx context.Context, userID, resourceowner string, isLoginUI bool, tokens []*domain.WebAuthNToken) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNToken, error) { + if userID == "" || resourceowner == "" { + return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing") + } + user, err := r.getHuman(ctx, userID, resourceowner) + if err != nil { + return nil, nil, nil, err + } + org, err := r.getOrg(ctx, user.ResourceOwner) + if err != nil { + return nil, nil, nil, err + } + orgPolicy, err := r.getOrgIAMPolicy(ctx, org.AggregateID) + if err != nil { + return nil, nil, nil, err + } + accountName := domain.GenerateLoginName(user.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain) + if accountName == "" { + accountName = user.EmailAddress + } + webAuthN, err := r.webauthn.BeginRegistration(user, accountName, domain.AuthenticatorAttachmentUnspecified, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...) + if err != nil { + return nil, nil, nil, err + } + tokenID, err := r.idGenerator.Next() + if err != nil { + return nil, nil, nil, err + } + addWebAuthN, err := r.webauthNWriteModelByID(ctx, userID, tokenID, resourceowner) + if err != nil { + return nil, nil, nil, err + } + + userAgg := UserAggregateFromWriteModel(&addWebAuthN.WriteModel) + return addWebAuthN, userAgg, webAuthN, nil +} + +func (r *CommandSide) VerifyHumanU2F(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error { + u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner) + if err != nil { + return err + } + verifyWebAuthN, userAgg, webAuthN, err := r.verifyHumanWebAuthN(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, u2fTokens) + if err != nil { + return err + } + userAgg.PushEvents( + usr_repo.NewHumanU2FVerifiedEvent( + ctx, + verifyWebAuthN.WebauthNTokenID, + webAuthN.WebAuthNTokenName, + webAuthN.AttestationType, + webAuthN.KeyID, + webAuthN.PublicKey, + webAuthN.AAGUID, + webAuthN.SignCount, + ), + ) + + return r.eventstore.PushAggregate(ctx, verifyWebAuthN, userAgg) +} + +func (r *CommandSide) VerifyHumanPasswordless(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error { + u2fTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner) + if err != nil { + return err + } + verifyWebAuthN, userAgg, webAuthN, err := r.verifyHumanWebAuthN(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, u2fTokens) + if err != nil { + return err + } + userAgg.PushEvents( + usr_repo.NewHumanU2FVerifiedEvent( + ctx, + verifyWebAuthN.WebauthNTokenID, + webAuthN.WebAuthNTokenName, + webAuthN.AttestationType, + webAuthN.KeyID, + webAuthN.PublicKey, + webAuthN.AAGUID, + webAuthN.SignCount, + ), + ) + return r.eventstore.PushAggregate(ctx, verifyWebAuthN, userAgg) +} + +func (r *CommandSide) verifyHumanWebAuthN(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte, tokens []*domain.WebAuthNToken) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNToken, error) { + if userID == "" || resourceowner == "" { + return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing") + } + user, err := r.getHuman(ctx, userID, resourceowner) + if err != nil { + return nil, nil, nil, err + } + _, token := domain.GetTokenToVerify(tokens) + webAuthN, err := r.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "") + if err != nil { + return nil, nil, nil, err + } + + verifyWebAuthN, err := r.webauthNWriteModelByID(ctx, userID, token.WebAuthNTokenID, resourceowner) + if err != nil { + return nil, nil, nil, err + } + + userAgg := UserAggregateFromWriteModel(&verifyWebAuthN.WriteModel) + return verifyWebAuthN, userAgg, webAuthN, nil +} + func (r *CommandSide) RemoveHumanU2F(ctx context.Context, userID, webAuthNID, resourceOwner string) error { - event := user.NewHumanU2FRemovedEvent(ctx, webAuthNID) + event := usr_repo.NewHumanU2FRemovedEvent(ctx, webAuthNID) return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event) } func (r *CommandSide) RemoveHumanPasswordless(ctx context.Context, userID, webAuthNID, resourceOwner string) error { - event := user.NewHumanPasswordlessRemovedEvent(ctx, webAuthNID) + event := usr_repo.NewHumanPasswordlessRemovedEvent(ctx, webAuthNID) return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event) } @@ -28,8 +205,8 @@ func (r *CommandSide) removeHumanWebAuthN(ctx context.Context, userID, webAuthNI 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") + if existingWebAuthN.State == domain.MFAStateUnspecified || existingWebAuthN.State == domain.MFAStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-2M9ds", "Errors.User.ExternalIDP.NotFound") } userAgg := UserAggregateFromWriteModel(&existingWebAuthN.WriteModel) userAgg.PushEvents(event) diff --git a/internal/v2/command/user_human_webauthn_model.go b/internal/v2/command/user_human_webauthn_model.go index 26a2adc247..6c01cbf133 100644 --- a/internal/v2/command/user_human_webauthn_model.go +++ b/internal/v2/command/user_human_webauthn_model.go @@ -10,8 +10,16 @@ type HumanWebAuthNWriteModel struct { eventstore.WriteModel WebauthNTokenID string + Challenge string - State domain.WebAuthNState + KeyID []byte + PublicKey []byte + AttestationType string + AAGUID []byte + SignCount uint32 + WebAuthNTokenName string + + State domain.MFAState } func NewHumanWebAuthNWriteModel(userID, wbAuthNTokenID, resourceOwner string) *HumanWebAuthNWriteModel { @@ -29,14 +37,14 @@ func (wm *HumanWebAuthNWriteModel) AppendEvents(events ...eventstore.EventReader switch e := event.(type) { case *user.HumanWebAuthNAddedEvent: if wm.WebauthNTokenID == e.WebAuthNTokenID { - wm.AppendEvents(e) + wm.WriteModel.AppendEvents(e) } case *user.HumanWebAuthNRemovedEvent: if wm.WebauthNTokenID == e.WebAuthNTokenID { - wm.AppendEvents(e) + wm.WriteModel.AppendEvents(e) } case *user.UserRemovedEvent: - wm.AppendEvents(e) + wm.WriteModel.AppendEvents(e) } } } @@ -45,19 +53,185 @@ 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 + wm.appendAddedEvent(e) + case *user.HumanWebAuthNVerifiedEvent: + wm.appendVerifiedEvent(e) case *user.HumanWebAuthNRemovedEvent: - wm.State = domain.WebAuthNStateRemoved + wm.State = domain.MFAStateRemoved case *user.UserRemovedEvent: - wm.State = domain.WebAuthNStateRemoved + wm.State = domain.MFAStateRemoved } } return wm.WriteModel.Reduce() } +func (wm *HumanWebAuthNWriteModel) appendAddedEvent(e *user.HumanWebAuthNAddedEvent) { + wm.WebauthNTokenID = e.WebAuthNTokenID + wm.Challenge = e.Challenge + wm.State = domain.MFAStateNotReady +} + +func (wm *HumanWebAuthNWriteModel) appendVerifiedEvent(e *user.HumanWebAuthNVerifiedEvent) { + wm.KeyID = e.KeyID + wm.PublicKey = e.PublicKey + wm.AttestationType = e.AttestationType + wm.AAGUID = e.AAGUID + wm.SignCount = e.SignCount + wm.WebAuthNTokenName = e.WebAuthNTokenName + wm.State = domain.MFAStateReady +} + func (wm *HumanWebAuthNWriteModel) Query() *eventstore.SearchQueryBuilder { return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). AggregateIDs(wm.AggregateID). ResourceOwner(wm.ResourceOwner) } + +type HumanU2FTokensReadModel struct { + eventstore.WriteModel + + WebAuthNTokens []*HumanWebAuthNWriteModel + UserState domain.UserState +} + +func NewHumanU2FTokensReadModel(userID, resourceOwner string) *HumanU2FTokensReadModel { + return &HumanU2FTokensReadModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + ResourceOwner: resourceOwner, + }, + } +} + +func (wm *HumanU2FTokensReadModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.HumanWebAuthNAddedEvent: + wm.WriteModel.AppendEvents(e) + case *user.HumanWebAuthNVerifiedEvent: + wm.WriteModel.AppendEvents(e) + case *user.HumanWebAuthNRemovedEvent: + wm.WriteModel.AppendEvents(e) + case *user.UserRemovedEvent: + wm.WriteModel.AppendEvents(e) + } + } +} + +func (wm *HumanU2FTokensReadModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanWebAuthNAddedEvent: + token := &HumanWebAuthNWriteModel{} + token.appendAddedEvent(e) + wm.WebAuthNTokens = append(wm.WebAuthNTokens, token) + case *user.HumanWebAuthNVerifiedEvent: + idx, token := wm.WebAuthNTokenByID(e.WebAuthNTokenID) + if idx < 0 { + continue + } + token.appendVerifiedEvent(e) + case *user.HumanWebAuthNRemovedEvent: + idx, _ := wm.WebAuthNTokenByID(e.WebAuthNTokenID) + if idx < 0 { + continue + } + copy(wm.WebAuthNTokens[idx:], wm.WebAuthNTokens[idx+1:]) + wm.WebAuthNTokens[len(wm.WebAuthNTokens)-1] = nil + wm.WebAuthNTokens = wm.WebAuthNTokens[:len(wm.WebAuthNTokens)-1] + case *user.UserRemovedEvent: + wm.UserState = domain.UserStateDeleted + } + } + return wm.WriteModel.Reduce() +} + +func (rm *HumanU2FTokensReadModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(rm.AggregateID). + ResourceOwner(rm.ResourceOwner). + EventTypes( + user.HumanU2FTokenAddedType, + user.HumanU2FTokenVerifiedType, + user.HumanU2FTokenRemovedType, + user.UserV1MFAOTPRemovedType) + +} + +func (wm *HumanU2FTokensReadModel) WebAuthNTokenByID(id string) (idx int, token *HumanWebAuthNWriteModel) { + for idx, token = range wm.WebAuthNTokens { + if token.WebauthNTokenID == id { + return idx, token + } + } + return -1, nil +} + +type HumanPasswordlessTokensReadModel struct { + eventstore.WriteModel + + WebAuthNTokens []*HumanWebAuthNWriteModel + UserState domain.UserState +} + +func NewHumanPasswordlessTokensReadModel(userID, resourceOwner string) *HumanPasswordlessTokensReadModel { + return &HumanPasswordlessTokensReadModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + ResourceOwner: resourceOwner, + }, + } +} + +func (wm *HumanPasswordlessTokensReadModel) AppendEvents(events ...eventstore.EventReader) { + wm.WriteModel.AppendEvents(events...) +} + +func (wm *HumanPasswordlessTokensReadModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanWebAuthNAddedEvent: + token := &HumanWebAuthNWriteModel{} + token.appendAddedEvent(e) + wm.WebAuthNTokens = append(wm.WebAuthNTokens, token) + case *user.HumanWebAuthNVerifiedEvent: + idx, token := wm.WebAuthNTokenByID(e.WebAuthNTokenID) + if idx < 0 { + continue + } + token.appendVerifiedEvent(e) + case *user.HumanWebAuthNRemovedEvent: + idx, _ := wm.WebAuthNTokenByID(e.WebAuthNTokenID) + if idx < 0 { + continue + } + copy(wm.WebAuthNTokens[idx:], wm.WebAuthNTokens[idx+1:]) + wm.WebAuthNTokens[len(wm.WebAuthNTokens)-1] = nil + wm.WebAuthNTokens = wm.WebAuthNTokens[:len(wm.WebAuthNTokens)-1] + case *user.UserRemovedEvent: + wm.UserState = domain.UserStateDeleted + } + } + return wm.WriteModel.Reduce() +} + +func (rm *HumanPasswordlessTokensReadModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(rm.AggregateID). + ResourceOwner(rm.ResourceOwner). + EventTypes( + user.HumanPasswordlessTokenAddedType, + user.HumanPasswordlessTokenVerifiedType, + user.HumanPasswordlessTokenRemovedType, + user.UserV1MFAOTPRemovedType) + +} + +func (wm *HumanPasswordlessTokensReadModel) WebAuthNTokenByID(id string) (idx int, token *HumanWebAuthNWriteModel) { + for idx, token = range wm.WebAuthNTokens { + if token.WebauthNTokenID == id { + return idx, token + } + } + return -1, nil +} diff --git a/internal/v2/command/user_machine.go b/internal/v2/command/user_machine.go index d206b39536..ea35a9f9a1 100644 --- a/internal/v2/command/user_machine.go +++ b/internal/v2/command/user_machine.go @@ -8,9 +8,9 @@ import ( "github.com/caos/zitadel/internal/v2/repository/user" ) -func (r *CommandSide) AddMachine(ctx context.Context, orgID, username string, machine *domain.Machine) (*domain.Machine, error) { +func (r *CommandSide) AddMachine(ctx context.Context, orgID string, machine *domain.Machine) (*domain.Machine, error) { if !machine.IsValid() { - return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0ds", "Errors.User.Invalid") + return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid") } userID, err := r.idGenerator.Next() if err != nil { @@ -31,11 +31,12 @@ func (r *CommandSide) AddMachine(ctx context.Context, orgID, username string, ma userAgg.PushEvents( user.NewMachineAddedEvent( ctx, - username, + machine.Username, machine.Name, machine.Description, ), ) + err = r.eventstore.PushAggregate(ctx, addedMachine, userAgg) return writeModelToMachine(addedMachine), nil } @@ -64,7 +65,7 @@ func (r *CommandSide) ChangeMachine(ctx context.Context, machine *domain.Machine func (r *CommandSide) machineWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *MachineWriteModel, err error) { if userID == "" { - return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0ds", "Errors.User.UserIDMissing") + return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-0Plof", "Errors.User.UserIDMissing") } ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/v2/command/user_machine_model.go b/internal/v2/command/user_machine_model.go index 4e4ca3e3e9..31868aa816 100644 --- a/internal/v2/command/user_machine_model.go +++ b/internal/v2/command/user_machine_model.go @@ -28,29 +28,10 @@ func NewMachineWriteModel(userID, resourceOwner string) *MachineWriteModel { } func (wm *MachineWriteModel) AppendEvents(events ...eventstore.EventReader) { - for _, event := range events { - 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: - wm.AppendEvents(e) - case *user.UserReactivatedEvent: - wm.AppendEvents(e) - case *user.UserLockedEvent: - wm.AppendEvents(e) - case *user.UserUnlockedEvent: - wm.AppendEvents(e) - case *user.UserRemovedEvent: - wm.AppendEvents(e) - } - } + wm.WriteModel.AppendEvents(events...) } -//TODO: Compute State? initial/active +//TODO: Compute OTPState? initial/active func (wm *MachineWriteModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { diff --git a/internal/v2/command/user_model.go b/internal/v2/command/user_model.go index b8b2f4d80a..1fab282942 100644 --- a/internal/v2/command/user_model.go +++ b/internal/v2/command/user_model.go @@ -26,29 +26,10 @@ func NewUserWriteModel(userID, resourceOwner string) *UserWriteModel { } func (wm *UserWriteModel) AppendEvents(events ...eventstore.EventReader) { - for _, event := range events { - switch e := event.(type) { - 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: - wm.AppendEvents(e) - case *user.UserLockedEvent: - wm.AppendEvents(e) - case *user.UserUnlockedEvent: - wm.AppendEvents(e) - case *user.UserRemovedEvent: - wm.AppendEvents(e) - } - } + wm.WriteModel.AppendEvents(events...) } -//TODO: Compute State? initial/active +//TODO: Compute OTPState? initial/active func (wm *UserWriteModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { diff --git a/internal/v2/domain/human.go b/internal/v2/domain/human.go index 2b11d1f0a9..b476e54d35 100644 --- a/internal/v2/domain/human.go +++ b/internal/v2/domain/human.go @@ -11,6 +11,8 @@ import ( type Human struct { es_models.ObjectRoot + Username string + State UserState *Password *Profile *Email @@ -24,6 +26,14 @@ type Human struct { PasswordlessLogins []*WebAuthNLogin } +func (h Human) GetUsername() string { + return h.Username +} + +func (h Human) GetState() UserState { + return h.State +} + type InitUserCode struct { es_models.ObjectRoot @@ -91,3 +101,10 @@ func NewInitUserCode(generator crypto.Generator) (*InitUserCode, error) { Expiry: generator.Expiry(), }, nil } + +func GenerateLoginName(username, domain string, appendDomain bool) string { + if !appendDomain { + return username + } + return username + "@" + domain +} diff --git a/internal/v2/domain/human_otp.go b/internal/v2/domain/human_otp.go index bc633071a6..06840e8266 100644 --- a/internal/v2/domain/human_otp.go +++ b/internal/v2/domain/human_otp.go @@ -2,7 +2,10 @@ package domain import ( "github.com/caos/zitadel/internal/crypto" + caos_errs "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/models" + "github.com/pquerna/otp" + "github.com/pquerna/otp/totp" ) type OTP struct { @@ -14,16 +17,27 @@ type OTP struct { State MFAState } -type OTPState int32 - -const ( - OTPStateUnspecified OTPState = iota - OTPStateActive - OTPStateRemoved - - otpStateCount -) - -func (s OTPState) Valid() bool { - return s >= 0 && s < otpStateCount +func NewOTPKey(issuer, accountName string, cryptoAlg crypto.EncryptionAlgorithm) (*otp.Key, *crypto.CryptoValue, error) { + key, err := totp.Generate(totp.GenerateOpts{Issuer: issuer, AccountName: accountName}) + if err != nil { + return nil, nil, err + } + encryptedSecret, err := crypto.Encrypt([]byte(key.Secret()), cryptoAlg) + if err != nil { + return nil, nil, err + } + return key, encryptedSecret, nil +} + +func VerifyMFAOTP(code string, secret *crypto.CryptoValue, cryptoAlg crypto.EncryptionAlgorithm) error { + decrypt, err := crypto.DecryptString(secret, cryptoAlg) + if err != nil { + return err + } + + valid := totp.Validate(code, decrypt) + if !valid { + return caos_errs.ThrowInvalidArgument(nil, "EVENT-8isk2", "Errors.User.MFA.OTP.InvalidCode") + } + return nil } diff --git a/internal/v2/domain/human_web_auth_n.go b/internal/v2/domain/human_web_auth_n.go index 2e93f32531..5c6004aa12 100644 --- a/internal/v2/domain/human_web_auth_n.go +++ b/internal/v2/domain/human_web_auth_n.go @@ -39,16 +39,19 @@ const ( UserVerificationRequirementDiscouraged ) -type WebAuthNState int32 +type AuthenticatorAttachment int32 const ( - WebAuthNStateUnspecified WebAuthNState = iota - WebAuthNStateActive - WebAuthNStateRemoved - - webAuthNStateCount + AuthenticatorAttachmentUnspecified AuthenticatorAttachment = iota + AuthenticatorAttachmentPlattform + AuthenticatorAttachmentCrossPlattform ) -func (s WebAuthNState) Valid() bool { - return s >= 0 && s < webAuthNStateCount +func GetTokenToVerify(tokens []*WebAuthNToken) (int, *WebAuthNToken) { + for i, u2f := range tokens { + if u2f.State == MFAStateNotReady { + return i, u2f + } + } + return -1, nil } diff --git a/internal/v2/domain/machine.go b/internal/v2/domain/machine.go index 8913792498..dad9e0ef36 100644 --- a/internal/v2/domain/machine.go +++ b/internal/v2/domain/machine.go @@ -5,10 +5,20 @@ import "github.com/caos/zitadel/internal/eventstore/models" type Machine struct { models.ObjectRoot + Username string + State UserState Name string Description string } +func (m Machine) GetUsername() string { + return m.Username +} + +func (m Machine) GetState() UserState { + return m.State +} + func (sa *Machine) IsValid() bool { return sa.Name != "" } diff --git a/internal/v2/domain/mfa.go b/internal/v2/domain/mfa.go index 39156f7fff..3785e46f64 100644 --- a/internal/v2/domain/mfa.go +++ b/internal/v2/domain/mfa.go @@ -6,6 +6,7 @@ const ( MFAStateUnspecified MFAState = iota MFAStateNotReady MFAStateReady + MFAStateRemoved stateCount ) diff --git a/internal/v2/domain/org.go b/internal/v2/domain/org.go index d9e1af38d1..5f6bc11db8 100644 --- a/internal/v2/domain/org.go +++ b/internal/v2/domain/org.go @@ -12,6 +12,7 @@ type Org struct { State OrgState Name string + PrimaryDomain string Domains []*OrgDomain Members []*Member OrgIamPolicy *OrgIAMPolicy @@ -38,6 +39,7 @@ func (o *Org) nameForDomain(iamDomain string) string { type OrgState int32 const ( - OrgStateActive OrgState = iota + OrgStateUnspecified OrgState = iota + OrgStateActive OrgStateInactive ) diff --git a/internal/v2/domain/user.go b/internal/v2/domain/user.go index ef23506cce..3e9e013f1b 100644 --- a/internal/v2/domain/user.go +++ b/internal/v2/domain/user.go @@ -1,14 +1,8 @@ package domain -import es_models "github.com/caos/zitadel/internal/eventstore/models" - -type User struct { - es_models.ObjectRoot - State UserState - UserName string - - *Human - *Machine +type User interface { + GetUsername() string + GetState() UserState } type UserState int32 @@ -28,13 +22,3 @@ const ( func (f UserState) Valid() bool { return f >= 0 && f < userStateCount } - -func (u *User) IsValid() bool { - if u.Human == nil && u.Machine == nil || u.UserName == "" { - return false - } - if u.Human != nil { - return u.Human.IsValid() - } - return u.Machine.IsValid() -} diff --git a/internal/v2/query/converter.go b/internal/v2/query/converter.go index 72e0635d1e..48a56a7de1 100644 --- a/internal/v2/query/converter.go +++ b/internal/v2/query/converter.go @@ -77,7 +77,7 @@ func readModelToLabelPolicy(readModel *IAMLabelPolicyReadModel) *model.LabelPoli PrimaryColor: readModel.PrimaryColor, SecondaryColor: readModel.SecondaryColor, Default: true, - //TODO: State: int32, + //TODO: OTPState: int32, } } @@ -89,7 +89,7 @@ func readModelToLoginPolicy(readModel *IAMLoginPolicyReadModel) *model.LoginPoli AllowUsernamePassword: readModel.AllowUserNamePassword, Default: true, //TODO: IDPProviders: []*model.IDPProvider, - //TODO: State: int32, + //TODO: OTPState: int32, } } func readModelToOrgIAMPolicy(readModel *IAMOrgIAMPolicyReadModel) *model.OrgIAMPolicy { @@ -97,7 +97,7 @@ func readModelToOrgIAMPolicy(readModel *IAMOrgIAMPolicyReadModel) *model.OrgIAMP ObjectRoot: readModelToObjectRoot(readModel.OrgIAMPolicyReadModel.ReadModel), UserLoginMustBeDomain: readModel.UserLoginMustBeDomain, Default: true, - //TODO: State: int32, + //TODO: OTPState: int32, } } func readModelToPasswordAgePolicy(readModel *IAMPasswordAgePolicyReadModel) *model.PasswordAgePolicy { @@ -105,7 +105,7 @@ func readModelToPasswordAgePolicy(readModel *IAMPasswordAgePolicyReadModel) *mod ObjectRoot: readModelToObjectRoot(readModel.PasswordAgePolicyReadModel.ReadModel), ExpireWarnDays: uint64(readModel.ExpireWarnDays), MaxAgeDays: uint64(readModel.MaxAgeDays), - //TODO: State: int32, + //TODO: OTPState: int32, } } func readModelToPasswordComplexityPolicy(readModel *IAMPasswordComplexityPolicyReadModel) *model.PasswordComplexityPolicy { @@ -116,7 +116,7 @@ func readModelToPasswordComplexityPolicy(readModel *IAMPasswordComplexityPolicyR HasSymbol: readModel.HasSymbol, HasUppercase: readModel.HasUpperCase, MinLength: uint64(readModel.MinLength), - //TODO: State: int32, + //TODO: OTPState: int32, } } func readModelToPasswordLockoutPolicy(readModel *IAMPasswordLockoutPolicyReadModel) *model.PasswordLockoutPolicy { @@ -124,7 +124,7 @@ func readModelToPasswordLockoutPolicy(readModel *IAMPasswordLockoutPolicyReadMod ObjectRoot: readModelToObjectRoot(readModel.PasswordLockoutPolicyReadModel.ReadModel), MaxAttempts: uint64(readModel.MaxAttempts), ShowLockOutFailures: readModel.ShowLockOutFailures, - //TODO: State: int32, + //TODO: OTPState: int32, } } diff --git a/internal/v2/query/iam_model.go b/internal/v2/query/iam_model.go index a8c02f9048..3b4ba50098 100644 --- a/internal/v2/query/iam_model.go +++ b/internal/v2/query/iam_model.go @@ -138,7 +138,6 @@ func IAMAggregateFromReadModel(rm *ReadModel) *iam.Aggregate { iam.AggregateType, rm.ResourceOwner, iam.AggregateVersion, - rm.ProcessedSequence, ), } } diff --git a/internal/v2/query/user_model.go b/internal/v2/query/user_model.go index 08aa4c551c..1059fe6603 100644 --- a/internal/v2/query/user_model.go +++ b/internal/v2/query/user_model.go @@ -59,7 +59,6 @@ func UserAggregateFromReadModel(rm *UserReadModel) *user.Aggregate { user.AggregateType, rm.ResourceOwner, user.AggregateVersion, - rm.ProcessedSequence, ), } } diff --git a/internal/v2/repository/iam/aggregate.go b/internal/v2/repository/iam/aggregate.go index d0f963d74b..a59e53ea9b 100644 --- a/internal/v2/repository/iam/aggregate.go +++ b/internal/v2/repository/iam/aggregate.go @@ -19,23 +19,6 @@ type Aggregate struct { eventstore.Aggregate } -func NewAggregate( - id, - resourceOwner string, - previousSequence uint64, -) *Aggregate { - - return &Aggregate{ - Aggregate: *eventstore.NewAggregate( - id, - AggregateType, - resourceOwner, - AggregateVersion, - previousSequence, - ), - } -} - func (a *Aggregate) PushStepStarted(ctx context.Context, step domain.Step) *Aggregate { a.Aggregate = *a.PushEvents(NewSetupStepStartedEvent(ctx, step)) return a diff --git a/internal/v2/repository/iam/event_iam_project_set.go b/internal/v2/repository/iam/event_iam_project_set.go index a0e353abdc..fd21b0f176 100644 --- a/internal/v2/repository/iam/event_iam_project_set.go +++ b/internal/v2/repository/iam/event_iam_project_set.go @@ -27,7 +27,7 @@ func NewIAMProjectSetEvent(ctx context.Context, projectID string) *ProjectSetEve return &ProjectSetEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, - SetupDoneEventType, + ProjectSetEventType, ), ProjectID: projectID, } diff --git a/internal/v2/repository/org/aggregate.go b/internal/v2/repository/org/aggregate.go index 02aeaf9cee..886e9e0bea 100644 --- a/internal/v2/repository/org/aggregate.go +++ b/internal/v2/repository/org/aggregate.go @@ -16,20 +16,3 @@ const ( type Aggregate struct { eventstore.Aggregate } - -func NewAggregate( - id, - resourceOwner string, - previousSequence uint64, -) *Aggregate { - - return &Aggregate{ - Aggregate: *eventstore.NewAggregate( - id, - AggregateType, - resourceOwner, - AggregateVersion, - previousSequence, - ), - } -} diff --git a/internal/v2/repository/project/aggregate.go b/internal/v2/repository/project/aggregate.go index 6847dd92ed..58a99f6ef2 100644 --- a/internal/v2/repository/project/aggregate.go +++ b/internal/v2/repository/project/aggregate.go @@ -12,20 +12,3 @@ const ( type Aggregate struct { eventstore.Aggregate } - -func NewAggregate( - id, - resourceOwner string, - previousSequence uint64, -) *Aggregate { - - return &Aggregate{ - Aggregate: *eventstore.NewAggregate( - id, - AggregateType, - resourceOwner, - AggregateVersion, - previousSequence, - ), - } -} diff --git a/internal/v2/repository/user/aggregate.go b/internal/v2/repository/user/aggregate.go index 3147bec8ff..bf7cf9cf32 100644 --- a/internal/v2/repository/user/aggregate.go +++ b/internal/v2/repository/user/aggregate.go @@ -12,20 +12,3 @@ const ( type Aggregate struct { eventstore.Aggregate } - -func NewAggregate( - id, - resourceOwner string, - previousSequence uint64, -) *Aggregate { - - return &Aggregate{ - Aggregate: *eventstore.NewAggregate( - id, - AggregateType, - resourceOwner, - AggregateVersion, - previousSequence, - ), - } -} diff --git a/internal/v2/repository/user/eventstore.go b/internal/v2/repository/user/eventstore.go index 69083a4d84..a53044ebb9 100644 --- a/internal/v2/repository/user/eventstore.go +++ b/internal/v2/repository/user/eventstore.go @@ -37,7 +37,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(UserV1MFAOTPCheckSucceededType, HumanOTPCheckSucceededEventMapper). RegisterFilterEventMapper(UserV1MFAOTPCheckFailedType, HumanOTPCheckFailedEventMapper). RegisterFilterEventMapper(UserLockedType, UserLockedEventMapper). - RegisterFilterEventMapper(UserUnlockedType, UserLockedEventMapper). + RegisterFilterEventMapper(UserUnlockedType, UserUnlockedEventMapper). RegisterFilterEventMapper(UserDeactivatedType, UserDeactivatedEventMapper). RegisterFilterEventMapper(UserReactivatedType, UserReactivatedEventMapper). RegisterFilterEventMapper(UserRemovedType, UserRemovedEventMapper). diff --git a/internal/v2/repository/user/human_mfa_otp.go b/internal/v2/repository/user/human_mfa_otp.go index 21dced54d1..347a2057f6 100644 --- a/internal/v2/repository/user/human_mfa_otp.go +++ b/internal/v2/repository/user/human_mfa_otp.go @@ -52,18 +52,20 @@ func HumanOTPAddedEventMapper(event *repository.Event) (eventstore.EventReader, type HumanOTPVerifiedEvent struct { eventstore.BaseEvent `json:"-"` + UserAgentID string `json:"userAgentID,omitempty"` } func (e *HumanOTPVerifiedEvent) Data() interface{} { return nil } -func NewHumanOTPVerifiedEvent(ctx context.Context) *HumanOTPVerifiedEvent { +func NewHumanOTPVerifiedEvent(ctx context.Context, userAgentID string) *HumanOTPVerifiedEvent { return &HumanOTPVerifiedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanMFAOTPVerifiedType, ), + UserAgentID: userAgentID, } } diff --git a/internal/v2/repository/user/human_mfa_web_auth_n.go b/internal/v2/repository/user/human_mfa_web_auth_n.go index 0d7dff661c..2a1a401acf 100644 --- a/internal/v2/repository/user/human_mfa_web_auth_n.go +++ b/internal/v2/repository/user/human_mfa_web_auth_n.go @@ -32,9 +32,8 @@ const ( type HumanWebAuthNAddedEvent struct { eventstore.BaseEvent `json:"-"` - WebAuthNTokenID string `json:"webAuthNTokenId"` - Challenge string `json:"challenge"` - State domain.MFAState `json:"-"` + WebAuthNTokenID string `json:"webAuthNTokenId"` + Challenge string `json:"challenge"` } func (e *HumanWebAuthNAddedEvent) Data() interface{} { @@ -74,7 +73,6 @@ func NewHumanPasswordlessAddedEvent( func WebAuthNAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { webAuthNAdded := &HumanWebAuthNAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - State: domain.MFAStateNotReady, } err := json.Unmarshal(event.Data, webAuthNAdded) if err != nil { @@ -86,14 +84,13 @@ func WebAuthNAddedEventMapper(event *repository.Event) (eventstore.EventReader, type HumanWebAuthNVerifiedEvent struct { eventstore.BaseEvent `json:"-"` - WebAuthNTokenID string `json:"webAuthNTokenId"` - KeyID []byte `json:"keyId"` - PublicKey []byte `json:"publicKey"` - AttestationType string `json:"attestationType"` - AAGUID []byte `json:"aaguid"` - SignCount uint32 `json:"signCount"` - WebAuthNTokenName string `json:"webAuthNTokenName"` - State domain.MFAState `json:"-"` + WebAuthNTokenID string `json:"webAuthNTokenId"` + KeyID []byte `json:"keyId"` + PublicKey []byte `json:"publicKey"` + AttestationType string `json:"attestationType"` + AAGUID []byte `json:"aaguid"` + SignCount uint32 `json:"signCount"` + WebAuthNTokenName string `json:"webAuthNTokenName"` } func (e *HumanWebAuthNVerifiedEvent) Data() interface{} { @@ -153,7 +150,6 @@ func NewHumanPasswordlessVerifiedEvent( func HumanWebAuthNVerifiedEventMapper(event *repository.Event) (eventstore.EventReader, error) { webauthNVerified := &HumanWebAuthNVerifiedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - State: domain.MFAStateReady, } err := json.Unmarshal(event.Data, webauthNVerified) if err != nil { @@ -165,9 +161,8 @@ func HumanWebAuthNVerifiedEventMapper(event *repository.Event) (eventstore.Event type HumanWebAuthNSignCountChangedEvent struct { eventstore.BaseEvent `json:"-"` - WebAuthNTokenID string `json:"webAuthNTokenId"` - SignCount uint32 `json:"signCount"` - State domain.MFAState `json:"-"` + WebAuthNTokenID string `json:"webAuthNTokenId"` + SignCount uint32 `json:"signCount"` } func (e *HumanWebAuthNSignCountChangedEvent) Data() interface{} { diff --git a/internal/webauthn/converter.go b/internal/webauthn/converter.go index efe8b8bb26..bac6d98374 100644 --- a/internal/webauthn/converter.go +++ b/internal/webauthn/converter.go @@ -1,15 +1,15 @@ package webauthn import ( - "github.com/caos/zitadel/internal/user/model" + "github.com/caos/zitadel/internal/v2/domain" "github.com/duo-labs/webauthn/protocol" "github.com/duo-labs/webauthn/webauthn" ) -func WebAuthNsToCredentials(webAuthNs []*model.WebAuthNToken) []webauthn.Credential { +func WebAuthNsToCredentials(webAuthNs []*domain.WebAuthNToken) []webauthn.Credential { creds := make([]webauthn.Credential, 0) for _, webAuthN := range webAuthNs { - if webAuthN.State == model.MFAStateReady { + if webAuthN.State == domain.MFAStateReady { creds = append(creds, webauthn.Credential{ ID: webAuthN.KeyID, PublicKey: webAuthN.PublicKey, @@ -24,55 +24,55 @@ func WebAuthNsToCredentials(webAuthNs []*model.WebAuthNToken) []webauthn.Credent return creds } -func WebAuthNToSessionData(webAuthN *model.WebAuthNToken) webauthn.SessionData { +func WebAuthNToSessionData(webAuthN *domain.WebAuthNToken) webauthn.SessionData { return webauthn.SessionData{ Challenge: webAuthN.Challenge, UserID: []byte(webAuthN.AggregateID), AllowedCredentialIDs: webAuthN.AllowedCredentialIDs, - UserVerification: UserVerificationFromModel(webAuthN.UserVerification), + UserVerification: UserVerificationFromDomain(webAuthN.UserVerification), } } -func WebAuthNLoginToSessionData(webAuthN *model.WebAuthNLogin) webauthn.SessionData { +func WebAuthNLoginToSessionData(webAuthN *domain.WebAuthNLogin) webauthn.SessionData { return webauthn.SessionData{ Challenge: webAuthN.Challenge, UserID: []byte(webAuthN.AggregateID), AllowedCredentialIDs: webAuthN.AllowedCredentialIDs, - UserVerification: UserVerificationFromModel(webAuthN.UserVerification), + UserVerification: UserVerificationFromDomain(webAuthN.UserVerification), } } -func UserVerificationToModel(verification protocol.UserVerificationRequirement) model.UserVerificationRequirement { +func UserVerificationToDomain(verification protocol.UserVerificationRequirement) domain.UserVerificationRequirement { switch verification { case protocol.VerificationRequired: - return model.UserVerificationRequirementRequired + return domain.UserVerificationRequirementRequired case protocol.VerificationPreferred: - return model.UserVerificationRequirementPreferred + return domain.UserVerificationRequirementPreferred case protocol.VerificationDiscouraged: - return model.UserVerificationRequirementDiscouraged + return domain.UserVerificationRequirementDiscouraged default: - return model.UserVerificationRequirementUnspecified + return domain.UserVerificationRequirementUnspecified } } -func UserVerificationFromModel(verification model.UserVerificationRequirement) protocol.UserVerificationRequirement { +func UserVerificationFromDomain(verification domain.UserVerificationRequirement) protocol.UserVerificationRequirement { switch verification { - case model.UserVerificationRequirementRequired: + case domain.UserVerificationRequirementRequired: return protocol.VerificationRequired - case model.UserVerificationRequirementPreferred: + case domain.UserVerificationRequirementPreferred: return protocol.VerificationPreferred - case model.UserVerificationRequirementDiscouraged: + case domain.UserVerificationRequirementDiscouraged: return protocol.VerificationDiscouraged default: return protocol.VerificationDiscouraged } } -func AuthenticatorAttachmentFromModel(authType model.AuthenticatorAttachment) protocol.AuthenticatorAttachment { +func AuthenticatorAttachmentFromDomain(authType domain.AuthenticatorAttachment) protocol.AuthenticatorAttachment { switch authType { - case model.AuthenticatorAttachmentPlattform: + case domain.AuthenticatorAttachmentPlattform: return protocol.Platform - case model.AuthenticatorAttachmentCrossPlattform: + case domain.AuthenticatorAttachmentCrossPlattform: return protocol.CrossPlatform default: return "" diff --git a/internal/webauthn/webauthn.go b/internal/webauthn/webauthn.go index b003b3d5f6..2be843742b 100644 --- a/internal/webauthn/webauthn.go +++ b/internal/webauthn/webauthn.go @@ -3,13 +3,13 @@ package webauthn import ( "bytes" "encoding/json" + "github.com/caos/zitadel/internal/v2/domain" "github.com/duo-labs/webauthn/protocol" "github.com/duo-labs/webauthn/webauthn" "github.com/caos/zitadel/internal/config/systemdefaults" caos_errs "github.com/caos/zitadel/internal/errors" - usr_model "github.com/caos/zitadel/internal/user/model" ) type WebAuthN struct { @@ -41,7 +41,7 @@ func StartServer(sd systemdefaults.WebAuthN) (*WebAuthN, error) { } type webUser struct { - *usr_model.User + *domain.Human accountName string credentials []webauthn.Credential } @@ -54,7 +54,7 @@ func (u *webUser) WebAuthnName() string { if u.accountName != "" { return u.accountName } - return u.UserName + return u.GetUsername() } func (u *webUser) WebAuthnDisplayName() string { @@ -69,7 +69,7 @@ func (u *webUser) WebAuthnCredentials() []webauthn.Credential { return u.credentials } -func (w *WebAuthN) BeginRegistration(user *usr_model.User, accountName string, authType usr_model.AuthenticatorAttachment, userVerification usr_model.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*usr_model.WebAuthNToken) (*usr_model.WebAuthNToken, error) { +func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, authType domain.AuthenticatorAttachment, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNToken, error) { creds := WebAuthNsToCredentials(webAuthNs) existing := make([]protocol.CredentialDescriptor, len(creds)) for i, cred := range creds { @@ -80,13 +80,13 @@ func (w *WebAuthN) BeginRegistration(user *usr_model.User, accountName string, a } credentialOptions, sessionData, err := w.web(isLoginUI).BeginRegistration( &webUser{ - User: user, + Human: user, accountName: accountName, credentials: creds, }, webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{ - UserVerification: UserVerificationFromModel(userVerification), - AuthenticatorAttachment: AuthenticatorAttachmentFromModel(authType), + UserVerification: UserVerificationFromDomain(userVerification), + AuthenticatorAttachment: AuthenticatorAttachmentFromDomain(authType), }), webauthn.WithConveyancePreference(protocol.PreferNoAttestation), webauthn.WithExclusions(existing), @@ -98,15 +98,15 @@ func (w *WebAuthN) BeginRegistration(user *usr_model.User, accountName string, a if err != nil { return nil, caos_errs.ThrowInternal(err, "WEBAU-D7cus", "Errors.User.WebAuthN.MarshalError") } - return &usr_model.WebAuthNToken{ + return &domain.WebAuthNToken{ Challenge: sessionData.Challenge, CredentialCreationData: cred, AllowedCredentialIDs: sessionData.AllowedCredentialIDs, - UserVerification: UserVerificationToModel(sessionData.UserVerification), + UserVerification: UserVerificationToDomain(sessionData.UserVerification), }, nil } -func (w *WebAuthN) FinishRegistration(user *usr_model.User, webAuthN *usr_model.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*usr_model.WebAuthNToken, error) { +func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*domain.WebAuthNToken, error) { if webAuthN == nil { return nil, caos_errs.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound") } @@ -117,7 +117,7 @@ func (w *WebAuthN) FinishRegistration(user *usr_model.User, webAuthN *usr_model. sessionData := WebAuthNToSessionData(webAuthN) credential, err := w.web(isLoginUI).CreateCredential( &webUser{ - User: user, + Human: user, }, sessionData, credentialData) if err != nil { @@ -133,11 +133,11 @@ func (w *WebAuthN) FinishRegistration(user *usr_model.User, webAuthN *usr_model. return webAuthN, nil } -func (w *WebAuthN) BeginLogin(user *usr_model.User, userVerification usr_model.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*usr_model.WebAuthNToken) (*usr_model.WebAuthNLogin, error) { +func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) { assertion, sessionData, err := w.web(isLoginUI).BeginLogin(&webUser{ - User: user, + Human: user, credentials: WebAuthNsToCredentials(webAuthNs), - }, webauthn.WithUserVerification(UserVerificationFromModel(userVerification))) + }, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification))) if err != nil { return nil, caos_errs.ThrowInternal(err, "WEBAU-4G8sw", "Errors.User.WebAuthN.BeginLoginFailed") } @@ -145,7 +145,7 @@ func (w *WebAuthN) BeginLogin(user *usr_model.User, userVerification usr_model.U if err != nil { return nil, caos_errs.ThrowInternal(err, "WEBAU-2M0s9", "Errors.User.WebAuthN.MarshalError") } - return &usr_model.WebAuthNLogin{ + return &domain.WebAuthNLogin{ Challenge: sessionData.Challenge, CredentialAssertionData: cred, AllowedCredentialIDs: sessionData.AllowedCredentialIDs, @@ -153,13 +153,13 @@ func (w *WebAuthN) BeginLogin(user *usr_model.User, userVerification usr_model.U }, nil } -func (w *WebAuthN) FinishLogin(user *usr_model.User, webAuthN *usr_model.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*usr_model.WebAuthNToken) ([]byte, uint32, error) { +func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) ([]byte, uint32, error) { assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData)) if err != nil { return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed") } webUser := &webUser{ - User: user, + Human: user, credentials: WebAuthNsToCredentials(webAuthNs), } credential, err := w.web(isLoginUI).ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData) diff --git a/pkg/grpc/management/proto/management.proto b/pkg/grpc/management/proto/management.proto index 21f06ab9d7..2ee3cdef30 100644 --- a/pkg/grpc/management/proto/management.proto +++ b/pkg/grpc/management/proto/management.proto @@ -114,7 +114,7 @@ service ManagementService { }; } - rpc DeactivateUser(UserID) returns (UserResponse) { + rpc DeactivateUser(UserID) returns (google.protobuf.Empty) { option (google.api.http) = { put: "/users/{id}/_deactivate" body: "*" @@ -125,7 +125,7 @@ service ManagementService { }; } - rpc ReactivateUser(UserID) returns (UserResponse) { + rpc ReactivateUser(UserID) returns (google.protobuf.Empty) { option (google.api.http) = { put: "/users/{id}/_reactivate" body: "*" @@ -136,7 +136,7 @@ service ManagementService { }; } - rpc LockUser(UserID) returns (UserResponse) { + rpc LockUser(UserID) returns (google.protobuf.Empty) { option (google.api.http) = { put: "/users/{id}/_lock" body: "*" @@ -147,7 +147,7 @@ service ManagementService { }; } - rpc UnlockUser(UserID) returns (UserResponse) { + rpc UnlockUser(UserID) returns (google.protobuf.Empty) { option (google.api.http) = { put: "/users/{id}/_unlock" body: "*" @@ -1741,16 +1741,15 @@ message CreateMachineRequest { message UserResponse { string id = 1; UserState state = 2; - google.protobuf.Timestamp creation_date = 3; - google.protobuf.Timestamp change_date = 4; - uint64 sequence = 5; - string user_name = 6; + google.protobuf.Timestamp change_date = 3; + uint64 sequence = 4; + string user_name = 5; oneof user { option (validate.required) = true; - HumanResponse human = 7; - MachineResponse machine = 8; + HumanResponse human = 6; + MachineResponse machine = 7; } } @@ -1954,8 +1953,7 @@ message UserProfile { string preferred_language = 6; Gender gender = 7; uint64 sequence = 8; - google.protobuf.Timestamp creation_date = 9; - google.protobuf.Timestamp change_date = 10; + google.protobuf.Timestamp change_date = 9; } message UserProfileView { @@ -1992,8 +1990,7 @@ message UserEmail { string email = 2; bool is_email_verified = 3; uint64 sequence = 4; - google.protobuf.Timestamp creation_date = 5; - google.protobuf.Timestamp change_date = 6; + google.protobuf.Timestamp change_date = 5; } message UserEmailView { @@ -2016,8 +2013,7 @@ message UserPhone { string phone = 2; bool is_phone_verified = 3; uint64 sequence = 5; - google.protobuf.Timestamp creation_date = 6; - google.protobuf.Timestamp change_date = 7; + google.protobuf.Timestamp change_date = 6; } message UserPhoneView { @@ -2043,8 +2039,7 @@ message UserAddress { string region = 5; string street_address = 6; uint64 sequence = 7; - google.protobuf.Timestamp creation_date = 8; - google.protobuf.Timestamp change_date = 9; + google.protobuf.Timestamp change_date = 8; } message UserAddressView { @@ -2528,7 +2523,6 @@ enum AppState { message Application { string id = 1; AppState state = 2; - google.protobuf.Timestamp creation_date = 3; google.protobuf.Timestamp change_date = 4; string name = 5; oneof app_config {