From 320679467b9978d093e7101b9eeea7946306f8d4 Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Mon, 8 Feb 2021 11:30:30 +0100 Subject: [PATCH] feat: User login commands (#1228) * feat: change login to command side * feat: change login to command side * fix: fix push on user * feat: user command side * feat: sign out * feat: command side login * feat: command side login * feat: fix register user * feat: fix register user * feat: fix web auth n events * feat: add machine keys * feat: send codes * feat: move authrequest to domain * feat: move authrequest to domain * feat: webauthn working * feat: external users * feat: external users login * feat: notify users * fix: tests * feat: cascade remove user grants on project remove * fix: webauthn * fix: pr requests * fix: register human with member * fix: fix bugs * fix: fix bugs --- cmd/zitadel/main.go | 6 +- .../eventsourcing/eventstore/user.go | 70 ------ .../repository/eventsourcing/repository.go | 7 - .../api/grpc/admin/login_policy_converter.go | 4 +- internal/api/grpc/auth/user.go | 16 +- .../grpc/management/login_policy_converter.go | 4 +- internal/api/grpc/management/project.go | 7 +- internal/api/grpc/management/user.go | 6 +- .../grpc/management/user_grant_converter.go | 8 + internal/api/grpc/management/user_machine.go | 7 +- .../grpc/management/user_machine_converter.go | 36 +-- internal/api/oidc/auth_request.go | 8 +- internal/api/oidc/auth_request_converter.go | 76 +++--- internal/api/oidc/op.go | 12 +- internal/auth/repository/auth_request.go | 30 ++- .../eventsourcing/eventstore/auth_request.go | 218 ++++++++--------- .../eventsourcing/eventstore/token.go | 32 --- .../eventsourcing/eventstore/user.go | 221 ++--------------- .../repository/eventsourcing/repository.go | 5 +- internal/auth/repository/token.go | 2 - internal/auth/repository/user.go | 31 +-- internal/auth_request/model/next_step.go | 46 ++++ .../auth_request/repository/cache/cache.go | 18 +- .../repository/mock/repository.mock.go | 2 +- .../auth_request/repository/repository.go | 11 +- internal/eventstore/v2/eventstore_test.go | 2 +- internal/eventstore/v2/repository/sql/crdb.go | 2 +- internal/eventstore/v2/search_query.go | 12 +- internal/iam/model/idp_provider_view.go | 61 +++++ internal/iam/model/login_policy_view.go | 56 +++++ .../eventsourcing/eventstore/user.go | 21 -- .../eventsourcing/eventstore/user_grant.go | 16 ++ internal/management/repository/user.go | 2 - internal/management/repository/user_grant.go | 2 + internal/notification/notification.go | 5 +- .../eventsourcing/handler/handler.go | 4 +- .../eventsourcing/handler/notification.go | 49 +++- .../repository/eventsourcing/repository.go | 5 +- .../eventsourcing/spooler/spooler.go | 5 +- internal/static/i18n/de.yaml | 4 + internal/static/i18n/en.yaml | 4 + internal/ui/login/handler/auth_request.go | 6 +- internal/ui/login/handler/callback_handler.go | 5 +- .../login/handler/change_password_handler.go | 8 +- .../login/handler/external_login_handler.go | 72 +++--- .../handler/external_register_handler.go | 59 ++--- .../ui/login/handler/init_password_handler.go | 25 +- .../ui/login/handler/init_user_handler.go | 18 +- .../ui/login/handler/link_users_handler.go | 9 +- internal/ui/login/handler/login_handler.go | 4 +- .../ui/login/handler/mail_verify_handler.go | 17 +- .../ui/login/handler/mfa_init_done_handler.go | 5 +- internal/ui/login/handler/mfa_init_u2f.go | 12 +- .../login/handler/mfa_init_verify_handler.go | 20 +- .../ui/login/handler/mfa_prompt_handler.go | 20 +- .../ui/login/handler/mfa_verify_handler.go | 19 +- .../login/handler/mfa_verify_u2f_handler.go | 19 +- internal/ui/login/handler/password_handler.go | 6 +- .../login/handler/password_reset_handler.go | 12 +- .../handler/passwordless_login_handler.go | 11 +- internal/ui/login/handler/register_handler.go | 48 ++-- .../login/handler/register_option_handler.go | 4 +- .../ui/login/handler/register_org_handler.go | 4 +- internal/ui/login/handler/renderer.go | 72 +++--- .../ui/login/handler/select_user_handler.go | 4 +- .../login/handler/username_change_handler.go | 9 +- .../static/templates/password_reset_done.html | 2 + internal/user/model/user_view.go | 29 +-- .../user/repository/view/model/notify_user.go | 3 + internal/v2/command/iam_converter.go | 2 +- .../v2/command/iam_idp_oidc_config_model.go | 2 +- internal/v2/command/iam_policy_login.go | 4 +- internal/v2/command/org_policy_login.go | 4 +- internal/v2/command/project.go | 17 +- internal/v2/command/setup_step1.go | 2 +- internal/v2/command/user.go | 71 +++++- internal/v2/command/user_converter.go | 26 ++ internal/v2/command/user_grant.go | 3 +- internal/v2/command/user_grant_model.go | 9 +- internal/v2/command/user_human.go | 93 +++++--- internal/v2/command/user_human_email.go | 17 +- internal/v2/command/user_human_email_model.go | 9 +- internal/v2/command/user_human_externalidp.go | 47 ++++ internal/v2/command/user_human_init.go | 104 ++++++++ internal/v2/command/user_human_init_model.go | 89 +++++++ internal/v2/command/user_human_otp.go | 33 ++- internal/v2/command/user_human_otp_model.go | 9 +- internal/v2/command/user_human_password.go | 89 ++++++- .../v2/command/user_human_password_model.go | 19 +- internal/v2/command/user_human_phone.go | 15 +- internal/v2/command/user_human_webauthn.go | 188 ++++++++++++++- .../v2/command/user_human_webauthn_model.go | 188 ++++++++++++--- internal/v2/command/user_machine.go | 2 +- internal/v2/command/user_machine_key.go | 83 +++++++ internal/v2/command/user_machine_key_model.go | 74 ++++++ internal/v2/command/user_model.go | 2 + internal/v2/domain/auth_request.go | 150 ++++++++++++ internal/v2/domain/browser_info.go | 22 ++ internal/v2/domain/human.go | 8 +- internal/v2/domain/human_web_auth_n.go | 14 +- internal/v2/domain/idp_config.go | 9 + internal/v2/domain/machine_key.go | 43 ++++ internal/v2/domain/next_step.go | 149 ++++++++++++ internal/v2/domain/oidc_code_challenge.go | 17 ++ internal/v2/domain/policy_login.go | 15 +- internal/v2/domain/request.go | 54 +++++ internal/v2/domain/roles.go | 1 + internal/v2/domain/token.go | 18 ++ internal/v2/query/query.go | 2 + internal/v2/query/user.go | 11 + internal/v2/query/user_model.go | 5 + .../v2/repository/idpconfig/oidc_config.go | 4 +- .../v2/repository/user/auth_request_info.go | 16 ++ internal/v2/repository/user/eventstore.go | 28 +-- internal/v2/repository/user/human.go | 5 +- .../v2/repository/user/human_external_idp.go | 24 +- internal/v2/repository/user/human_mfa_otp.go | 30 ++- .../repository/user/human_mfa_passwordless.go | 224 ++++++++++++++++++ internal/v2/repository/user/human_mfa_u2f.go | 224 ++++++++++++++++++ .../repository/user/human_mfa_web_auth_n.go | 193 ++------------- internal/v2/repository/user/human_password.go | 32 ++- .../v2/repository/usergrant/user_grant.go | 4 +- internal/webauthn/webauthn.go | 3 +- 123 files changed, 2949 insertions(+), 1212 deletions(-) delete mode 100644 internal/admin/repository/eventsourcing/eventstore/user.go create mode 100644 internal/v2/command/user_human_init.go create mode 100644 internal/v2/command/user_human_init_model.go create mode 100644 internal/v2/command/user_machine_key.go create mode 100644 internal/v2/command/user_machine_key_model.go create mode 100644 internal/v2/domain/auth_request.go create mode 100644 internal/v2/domain/browser_info.go create mode 100644 internal/v2/domain/next_step.go create mode 100644 internal/v2/domain/oidc_code_challenge.go create mode 100644 internal/v2/domain/request.go create mode 100644 internal/v2/domain/token.go create mode 100644 internal/v2/query/user.go create mode 100644 internal/v2/repository/user/auth_request_info.go create mode 100644 internal/v2/repository/user/human_mfa_passwordless.go create mode 100644 internal/v2/repository/user/human_mfa_u2f.go diff --git a/cmd/zitadel/main.go b/cmd/zitadel/main.go index 96b5e22653..09e591c739 100644 --- a/cmd/zitadel/main.go +++ b/cmd/zitadel/main.go @@ -115,7 +115,7 @@ func startZitadel(configPaths []string) { logging.Log("MAIN-s9KOw").OnError(err).Fatal("error starting authz repo") var authRepo *auth_es.EsRepository if *authEnabled || *oidcEnabled || *loginEnabled { - authRepo, err = auth_es.Start(conf.Auth, conf.InternalAuthZ, conf.SystemDefaults, authZRepo) + authRepo, err = auth_es.Start(conf.Auth, conf.InternalAuthZ, conf.SystemDefaults, command, authZRepo) logging.Log("MAIN-9oRw6").OnError(err).Fatal("error starting auth repo") } @@ -123,7 +123,7 @@ func startZitadel(configPaths []string) { startUI(ctx, conf, authRepo, command, query) if *notificationEnabled { - notification.Start(ctx, conf.Notification, conf.SystemDefaults) + notification.Start(ctx, conf.Notification, conf.SystemDefaults, command) } <-ctx.Done() @@ -166,7 +166,7 @@ func startAPI(ctx context.Context, conf *Config, authZRepo *authz_repo.EsReposit apis.RegisterServer(ctx, auth.CreateServer(command, query, authRepo)) } if *oidcEnabled { - op := oidc.NewProvider(ctx, conf.API.OIDC, authRepo, *localDevMode) + op := oidc.NewProvider(ctx, conf.API.OIDC, command, query, authRepo, *localDevMode) apis.RegisterHandler("/oauth/v2", op.HttpHandler()) } apis.Start(ctx) diff --git a/internal/admin/repository/eventsourcing/eventstore/user.go b/internal/admin/repository/eventsourcing/eventstore/user.go deleted file mode 100644 index b465ef091b..0000000000 --- a/internal/admin/repository/eventsourcing/eventstore/user.go +++ /dev/null @@ -1,70 +0,0 @@ -package eventstore - -import ( - "context" - admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view" - "github.com/caos/zitadel/internal/config/systemdefaults" - caos_errs "github.com/caos/zitadel/internal/errors" - iam_view "github.com/caos/zitadel/internal/iam/repository/view/model" - - "github.com/caos/zitadel/internal/api/authz" - org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing" - usr_model "github.com/caos/zitadel/internal/user/model" - usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" -) - -type UserRepo struct { - UserEvents *usr_event.UserEventstore - OrgEvents *org_event.OrgEventstore - View *admin_view.View - SystemDefaults systemdefaults.SystemDefaults -} - -func (repo *UserRepo) UserByID(ctx context.Context, id string) (project *usr_model.User, err error) { - return repo.UserEvents.UserByID(ctx, id) -} - -func (repo *UserRepo) CreateUser(ctx context.Context, user *usr_model.User) (*usr_model.User, error) { - pwPolicy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if caos_errs.IsNotFound(err) { - pwPolicy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return nil, err - } - pwPolicyView := iam_view.PasswordComplexityViewToModel(pwPolicy) - orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if err != nil && caos_errs.IsNotFound(err) { - orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID) - if err != nil { - return nil, err - } - } - orgPolicyView := iam_view.OrgIAMViewToModel(orgPolicy) - return repo.UserEvents.CreateUser(ctx, user, pwPolicyView, orgPolicyView) -} - -func (repo *UserRepo) RegisterUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*usr_model.User, error) { - policyResourceOwner := authz.GetCtxData(ctx).OrgID - if resourceOwner != "" { - policyResourceOwner = resourceOwner - } - pwPolicy, err := repo.View.PasswordComplexityPolicyByAggregateID(policyResourceOwner) - if caos_errs.IsNotFound(err) { - pwPolicy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return nil, err - } - pwPolicyView := iam_view.PasswordComplexityViewToModel(pwPolicy) - - orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(policyResourceOwner) - if caos_errs.IsNotFound(err) { - orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return nil, err - } - orgPolicyView := iam_view.OrgIAMViewToModel(orgPolicy) - return repo.UserEvents.RegisterUser(ctx, user, pwPolicyView, orgPolicyView, resourceOwner) -} diff --git a/internal/admin/repository/eventsourcing/repository.go b/internal/admin/repository/eventsourcing/repository.go index f75c1cf20c..631908c92c 100644 --- a/internal/admin/repository/eventsourcing/repository.go +++ b/internal/admin/repository/eventsourcing/repository.go @@ -28,7 +28,6 @@ type EsRepository struct { eventstore.OrgRepo eventstore.IAMRepository eventstore.AdministratorRepo - eventstore.UserRepo } func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, roles []string) (*EsRepository, error) { @@ -86,12 +85,6 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, r AdministratorRepo: eventstore.AdministratorRepo{ View: view, }, - UserRepo: eventstore.UserRepo{ - UserEvents: user, - OrgEvents: org, - View: view, - SystemDefaults: systemDefaults, - }, }, nil } diff --git a/internal/api/grpc/admin/login_policy_converter.go b/internal/api/grpc/admin/login_policy_converter.go index 1ae851b4b4..b0cdd5136e 100644 --- a/internal/api/grpc/admin/login_policy_converter.go +++ b/internal/api/grpc/admin/login_policy_converter.go @@ -12,7 +12,7 @@ import ( func loginPolicyToDomain(policy *admin.DefaultLoginPolicyRequest) *domain.LoginPolicy { return &domain.LoginPolicy{ AllowUsernamePassword: policy.AllowUsernamePassword, - AllowExternalIdp: policy.AllowExternalIdp, + AllowExternalIDP: policy.AllowExternalIdp, AllowRegister: policy.AllowRegister, ForceMFA: policy.ForceMfa, PasswordlessType: passwordlessTypeToDomain(policy.PasswordlessType), @@ -22,7 +22,7 @@ func loginPolicyToDomain(policy *admin.DefaultLoginPolicyRequest) *domain.LoginP func loginPolicyFromDomain(policy *domain.LoginPolicy) *admin.DefaultLoginPolicy { return &admin.DefaultLoginPolicy{ AllowUsernamePassword: policy.AllowUsernamePassword, - AllowExternalIdp: policy.AllowExternalIdp, + AllowExternalIdp: policy.AllowExternalIDP, AllowRegister: policy.AllowRegister, ForceMfa: policy.ForceMFA, PasswordlessType: passwordlessTypeFromDomain(policy.PasswordlessType), diff --git a/internal/api/grpc/auth/user.go b/internal/api/grpc/auth/user.go index d51ede3e4a..f51a06d5af 100644 --- a/internal/api/grpc/auth/user.go +++ b/internal/api/grpc/auth/user.go @@ -162,19 +162,19 @@ func (s *Server) AddMfaOTP(ctx context.Context, _ *empty.Empty) (_ *auth.MfaOtpR func (s *Server) VerifyMfaOTP(ctx context.Context, request *auth.VerifyMfaOtp) (*empty.Empty, error) { ctxData := authz.GetCtxData(ctx) - err := s.command.CheckMFAOTPSetup(ctx, ctxData.UserID, request.Code, "", ctxData.ResourceOwner) + err := s.command.HumanCheckMFAOTPSetup(ctx, ctxData.UserID, request.Code, "", ctxData.ResourceOwner) return &empty.Empty{}, err } func (s *Server) RemoveMfaOTP(ctx context.Context, _ *empty.Empty) (_ *empty.Empty, err error) { ctxData := authz.GetCtxData(ctx) - err = s.command.RemoveHumanOTP(ctx, ctxData.UserID, ctxData.OrgID) + err = s.command.HumanRemoveOTP(ctx, ctxData.UserID, ctxData.OrgID) return &empty.Empty{}, err } func (s *Server) AddMyMfaU2F(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) { ctxData := authz.GetCtxData(ctx) - u2f, err := s.command.AddHumanU2F(ctx, ctxData.UserID, ctxData.ResourceOwner, false) + u2f, err := s.command.HumanAddU2FSetup(ctx, ctxData.UserID, ctxData.ResourceOwner, false) if err != nil { return nil, err } @@ -183,13 +183,13 @@ func (s *Server) AddMyMfaU2F(ctx context.Context, _ *empty.Empty) (_ *auth.WebAu func (s *Server) VerifyMyMfaU2F(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) { ctxData := authz.GetCtxData(ctx) - err := s.command.VerifyHumanU2F(ctx, ctxData.UserID, ctxData.OrgID, request.TokenName, "", request.PublicKeyCredential) + err := s.command.HumanVerifyU2FSetup(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) { ctxData := authz.GetCtxData(ctx) - err := s.command.RemoveHumanU2F(ctx, ctxData.UserID, id.Id, ctxData.OrgID) + err := s.command.HumanRemoveU2F(ctx, ctxData.UserID, id.Id, ctxData.OrgID) return &empty.Empty{}, err } @@ -203,7 +203,7 @@ func (s *Server) GetMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth func (s *Server) AddMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth.WebAuthNResponse, err error) { ctxData := authz.GetCtxData(ctx) - u2f, err := s.command.AddHumanPasswordless(ctx, ctxData.UserID, ctxData.ResourceOwner, false) + u2f, err := s.command.HumanAddPasswordlessSetup(ctx, ctxData.UserID, ctxData.ResourceOwner, false) if err != nil { return nil, err } @@ -212,13 +212,13 @@ func (s *Server) AddMyPasswordless(ctx context.Context, _ *empty.Empty) (_ *auth func (s *Server) VerifyMyPasswordless(ctx context.Context, request *auth.VerifyWebAuthN) (*empty.Empty, error) { ctxData := authz.GetCtxData(ctx) - err := s.command.VerifyHumanPasswordless(ctx, ctxData.UserID, ctxData.OrgID, request.TokenName, "", request.PublicKeyCredential) + err := s.command.HumanHumanPasswordlessSetup(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) { ctxData := authz.GetCtxData(ctx) - err := s.command.RemoveHumanPasswordless(ctx, ctxData.UserID, id.Id, ctxData.ResourceOwner) + err := s.command.HumanRemovePasswordless(ctx, ctxData.UserID, id.Id, ctxData.ResourceOwner) return &empty.Empty{}, err } diff --git a/internal/api/grpc/management/login_policy_converter.go b/internal/api/grpc/management/login_policy_converter.go index b1aa9816f5..824274fd69 100644 --- a/internal/api/grpc/management/login_policy_converter.go +++ b/internal/api/grpc/management/login_policy_converter.go @@ -20,7 +20,7 @@ func loginPolicyRequestToDomain(ctx context.Context, policy *management.LoginPol AggregateID: authz.GetCtxData(ctx).OrgID, }, AllowUsernamePassword: policy.AllowUsernamePassword, - AllowExternalIdp: policy.AllowExternalIdp, + AllowExternalIDP: policy.AllowExternalIdp, AllowRegister: policy.AllowRegister, ForceMFA: policy.ForceMfa, PasswordlessType: passwordlessTypeToDomain(policy.PasswordlessType), @@ -30,7 +30,7 @@ func loginPolicyRequestToDomain(ctx context.Context, policy *management.LoginPol func loginPolicyFromDomain(policy *domain.LoginPolicy) *management.LoginPolicy { return &management.LoginPolicy{ AllowUsernamePassword: policy.AllowUsernamePassword, - AllowExternalIdp: policy.AllowExternalIdp, + AllowExternalIdp: policy.AllowExternalIDP, AllowRegister: policy.AllowRegister, ChangeDate: timestamppb.New(policy.ChangeDate), ForceMfa: policy.ForceMFA, diff --git a/internal/api/grpc/management/project.go b/internal/api/grpc/management/project.go index 1b114be418..afa3ab1ef8 100644 --- a/internal/api/grpc/management/project.go +++ b/internal/api/grpc/management/project.go @@ -2,7 +2,6 @@ package management import ( "context" - "github.com/golang/protobuf/ptypes/empty" "github.com/caos/zitadel/internal/api/authz" @@ -36,7 +35,11 @@ func (s *Server) ReactivateProject(ctx context.Context, in *management.ProjectID } func (s *Server) RemoveProject(ctx context.Context, in *management.ProjectID) (*empty.Empty, error) { - err := s.command.RemoveProject(ctx, in.Id, authz.GetCtxData(ctx).OrgID) + grants, err := s.usergrant.UserGrantsByProjectID(ctx, in.Id) + if err != nil { + return &empty.Empty{}, err + } + err = s.command.RemoveProject(ctx, in.Id, authz.GetCtxData(ctx).OrgID, userGrantsToIDs(grants)...) return &empty.Empty{}, err } diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index b1309145a3..e10419df85 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -217,11 +217,11 @@ func (s *Server) GetUserMfas(ctx context.Context, userID *management.UserID) (*m } func (s *Server) RemoveMfaOTP(ctx context.Context, userID *management.UserID) (*empty.Empty, error) { - return &empty.Empty{}, s.command.RemoveHumanOTP(ctx, userID.Id, authz.GetCtxData(ctx).OrgID) + return &empty.Empty{}, s.command.HumanRemoveOTP(ctx, userID.Id, authz.GetCtxData(ctx).OrgID) } func (s *Server) RemoveMfaU2F(ctx context.Context, webAuthNTokenID *management.WebAuthNTokenID) (*empty.Empty, error) { - return &empty.Empty{}, s.command.RemoveHumanU2F(ctx, webAuthNTokenID.UserId, webAuthNTokenID.Id, authz.GetCtxData(ctx).OrgID) + return &empty.Empty{}, s.command.HumanRemoveU2F(ctx, webAuthNTokenID.UserId, webAuthNTokenID.Id, authz.GetCtxData(ctx).OrgID) } func (s *Server) GetPasswordless(ctx context.Context, userID *management.UserID) (_ *management.WebAuthNTokens, err error) { @@ -233,7 +233,7 @@ func (s *Server) GetPasswordless(ctx context.Context, userID *management.UserID) } func (s *Server) RemovePasswordless(ctx context.Context, id *management.WebAuthNTokenID) (*empty.Empty, error) { - return &empty.Empty{}, s.command.RemoveHumanPasswordless(ctx, id.UserId, id.Id, authz.GetCtxData(ctx).OrgID) + return &empty.Empty{}, s.command.HumanRemovePasswordless(ctx, id.UserId, id.Id, authz.GetCtxData(ctx).OrgID) } func (s *Server) SearchUserMemberships(ctx context.Context, in *management.UserMembershipSearchRequest) (*management.UserMembershipSearchResponse, error) { diff --git a/internal/api/grpc/management/user_grant_converter.go b/internal/api/grpc/management/user_grant_converter.go index 6273d97243..7feddcd31a 100644 --- a/internal/api/grpc/management/user_grant_converter.go +++ b/internal/api/grpc/management/user_grant_converter.go @@ -177,3 +177,11 @@ func usergrantStateFromDomain(state domain.UserGrantState) management.UserGrantS return management.UserGrantState_USERGRANTSTATE_UNSPECIFIED } } + +func userGrantsToIDs(userGrants []*grant_model.UserGrantView) []string { + converted := make([]string, len(userGrants)) + for i, grant := range userGrants { + converted[i] = grant.ID + } + return converted +} diff --git a/internal/api/grpc/management/user_machine.go b/internal/api/grpc/management/user_machine.go index adb6e5c6eb..6368e5fdf1 100644 --- a/internal/api/grpc/management/user_machine.go +++ b/internal/api/grpc/management/user_machine.go @@ -2,21 +2,22 @@ package management import ( "context" + "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/pkg/grpc/management" "github.com/golang/protobuf/ptypes/empty" ) func (s *Server) AddMachineKey(ctx context.Context, req *management.AddMachineKeyRequest) (*management.AddMachineKeyResponse, error) { - key, err := s.user.AddMachineKey(ctx, addMachineKeyToModel(req)) + key, err := s.command.AddUserMachineKey(ctx, addMachineKeyToDomain(req), authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } - return addMachineKeyFromModel(key), nil + return addMachineKeyFromDomain(key), nil } func (s *Server) DeleteMachineKey(ctx context.Context, req *management.MachineKeyIDRequest) (*empty.Empty, error) { - err := s.user.RemoveMachineKey(ctx, req.UserId, req.KeyId) + err := s.command.RemoveUserMachineKey(ctx, req.UserId, req.KeyId, authz.GetCtxData(ctx).OrgID) return &empty.Empty{}, err } diff --git a/internal/api/grpc/management/user_machine_converter.go b/internal/api/grpc/management/user_machine_converter.go index afa978a5ed..fd1d651d68 100644 --- a/internal/api/grpc/management/user_machine_converter.go +++ b/internal/api/grpc/management/user_machine_converter.go @@ -2,6 +2,7 @@ package management import ( "encoding/json" + "google.golang.org/protobuf/types/known/timestamppb" "time" "github.com/caos/zitadel/internal/api/authz" @@ -75,7 +76,7 @@ func machineKeyViewFromModel(key *usr_model.MachineKeyView) *management.MachineK } } -func addMachineKeyToModel(key *management.AddMachineKeyRequest) *usr_model.MachineKey { +func addMachineKeyToDomain(key *management.AddMachineKeyRequest) *domain.MachineKey { expirationDate := time.Time{} if key.ExpirationDate != nil { var err error @@ -83,20 +84,14 @@ func addMachineKeyToModel(key *management.AddMachineKeyRequest) *usr_model.Machi logging.Log("MANAG-iNshR").OnError(err).Debug("unable to parse expiration date") } - return &usr_model.MachineKey{ + return &domain.MachineKey{ ExpirationDate: expirationDate, - Type: machineKeyTypeToModel(key.Type), + Type: machineKeyTypeToDomain(key.Type), ObjectRoot: models.ObjectRoot{AggregateID: key.UserId}, } } -func addMachineKeyFromModel(key *usr_model.MachineKey) *management.AddMachineKeyResponse { - creationDate, err := ptypes.TimestampProto(key.CreationDate) - logging.Log("MANAG-dlb8m").OnError(err).Debug("unable to parse cretaion date") - - expirationDate, err := ptypes.TimestampProto(key.ExpirationDate) - logging.Log("MANAG-dlb8m").OnError(err).Debug("unable to parse cretaion date") - +func addMachineKeyFromDomain(key *domain.MachineKey) *management.AddMachineKeyResponse { detail, err := json.Marshal(struct { Type string `json:"type"` KeyID string `json:"keyId"` @@ -112,20 +107,29 @@ func addMachineKeyFromModel(key *usr_model.MachineKey) *management.AddMachineKey return &management.AddMachineKeyResponse{ Id: key.KeyID, - CreationDate: creationDate, - ExpirationDate: expirationDate, + CreationDate: timestamppb.New(key.CreationDate), + ExpirationDate: timestamppb.New(key.ExpirationDate), Sequence: key.Sequence, KeyDetails: detail, - Type: machineKeyTypeFromModel(key.Type), + Type: machineKeyTypeFromDomain(key.Type), } } -func machineKeyTypeToModel(typ management.MachineKeyType) usr_model.MachineKeyType { +func machineKeyTypeToDomain(typ management.MachineKeyType) domain.MachineKeyType { switch typ { case management.MachineKeyType_MACHINEKEY_JSON: - return usr_model.MachineKeyTypeJSON + return domain.MachineKeyTypeJSON default: - return usr_model.MachineKeyTypeNONE + return domain.MachineKeyTypeNONE + } +} + +func machineKeyTypeFromDomain(typ domain.MachineKeyType) management.MachineKeyType { + switch typ { + case domain.MachineKeyTypeJSON: + return management.MachineKeyType_MACHINEKEY_JSON + default: + return management.MachineKeyType_MACHINEKEY_UNSPECIFIED } } diff --git a/internal/api/oidc/auth_request.go b/internal/api/oidc/auth_request.go index e1c57c64b5..923bdbc640 100644 --- a/internal/api/oidc/auth_request.go +++ b/internal/api/oidc/auth_request.go @@ -89,7 +89,7 @@ func (o *OPStorage) CreateToken(ctx context.Context, req op.TokenRequest) (_ str userAgentID = authReq.AgentID applicationID = authReq.ApplicationID } - resp, err := o.repo.CreateToken(ctx, userAgentID, applicationID, req.GetSubject(), req.GetAudience(), req.GetScopes(), o.defaultAccessTokenLifetime) //PLANNED: lifetime from client + resp, err := o.command.CreateUserToken(ctx, authReq.UserOrgID, userAgentID, applicationID, req.GetSubject(), req.GetAudience(), req.GetScopes(), o.defaultAccessTokenLifetime) //PLANNED: lifetime from client if err != nil { return "", time.Time{}, err } @@ -113,7 +113,11 @@ func (o *OPStorage) TerminateSession(ctx context.Context, userID, clientID strin if !ok { return errors.ThrowPreconditionFailed(nil, "OIDC-fso7F", "no user agent id") } - return o.repo.SignOut(ctx, userAgentID) + userIDs, err := o.repo.UserSessionUserIDsByAgentID(ctx, userAgentID) + if err != nil { + return err + } + return o.command.HumansSignOut(ctx, userAgentID, userIDs) } func (o *OPStorage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, errCh chan<- error, timer <-chan time.Time) { diff --git a/internal/api/oidc/auth_request_converter.go b/internal/api/oidc/auth_request_converter.go index 5dd4dc15b6..f891bf0be4 100644 --- a/internal/api/oidc/auth_request_converter.go +++ b/internal/api/oidc/auth_request_converter.go @@ -2,6 +2,7 @@ package oidc import ( "context" + "github.com/caos/zitadel/internal/v2/domain" "net" "time" @@ -10,7 +11,6 @@ import ( "golang.org/x/text/language" http_utils "github.com/caos/zitadel/internal/api/http" - "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/errors" ) @@ -22,7 +22,7 @@ const ( ) type AuthRequest struct { - *model.AuthRequest + *domain.AuthRequest } func (a *AuthRequest) GetID() string { @@ -92,26 +92,26 @@ func (a *AuthRequest) GetSubject() string { func (a *AuthRequest) Done() bool { for _, step := range a.PossibleSteps { - if step.Type() == model.NextStepRedirectToCallback { + if step.Type() == domain.NextStepRedirectToCallback { return true } } return false } -func (a *AuthRequest) oidc() *model.AuthRequestOIDC { - return a.Request.(*model.AuthRequestOIDC) +func (a *AuthRequest) oidc() *domain.AuthRequestOIDC { + return a.Request.(*domain.AuthRequestOIDC) } -func AuthRequestFromBusiness(authReq *model.AuthRequest) (_ op.AuthRequest, err error) { - if _, ok := authReq.Request.(*model.AuthRequestOIDC); !ok { +func AuthRequestFromBusiness(authReq *domain.AuthRequest) (_ op.AuthRequest, err error) { + if _, ok := authReq.Request.(*domain.AuthRequestOIDC); !ok { return nil, errors.ThrowInvalidArgument(nil, "OIDC-Haz7A", "auth request is not of type oidc") } return &AuthRequest{authReq}, nil } -func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest, userAgentID, userID string) *model.AuthRequest { - return &model.AuthRequest{ +func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest, userAgentID, userID string) *domain.AuthRequest { + return &domain.AuthRequest{ AgentID: userAgentID, BrowserInfo: ParseBrowserInfoFromContext(ctx), ApplicationID: authReq.ClientID, @@ -123,7 +123,7 @@ func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest, LoginHint: authReq.LoginHint, MaxAuthAge: authReq.MaxAge, UserID: userID, - Request: &model.AuthRequestOIDC{ + Request: &domain.AuthRequestOIDC{ Scopes: authReq.Scopes, ResponseType: ResponseTypeToBusiness(authReq.ResponseType), Nonce: authReq.Nonce, @@ -132,10 +132,10 @@ func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest, } } -func ParseBrowserInfoFromContext(ctx context.Context) *model.BrowserInfo { +func ParseBrowserInfoFromContext(ctx context.Context) *domain.BrowserInfo { userAgent, acceptLang := HttpHeadersFromContext(ctx) ip := IpFromContext(ctx) - return &model.BrowserInfo{RemoteIP: ip, UserAgent: userAgent, AcceptLanguage: acceptLang} + return &domain.BrowserInfo{RemoteIP: ip, UserAgent: userAgent, AcceptLanguage: acceptLang} } func HttpHeadersFromContext(ctx context.Context) (userAgent, acceptLang string) { @@ -160,22 +160,22 @@ func IpFromContext(ctx context.Context) net.IP { return net.ParseIP(ipString) } -func PromptToBusiness(prompt oidc.Prompt) model.Prompt { +func PromptToBusiness(prompt oidc.Prompt) domain.Prompt { switch prompt { case oidc.PromptNone: - return model.PromptNone + return domain.PromptNone case oidc.PromptLogin: - return model.PromptLogin + return domain.PromptLogin case oidc.PromptConsent: - return model.PromptConsent + return domain.PromptConsent case oidc.PromptSelectAccount: - return model.PromptSelectAccount + return domain.PromptSelectAccount default: - return model.PromptUnspecified + return domain.PromptUnspecified } } -func ACRValuesToBusiness(values []string) []model.LevelOfAssurance { +func ACRValuesToBusiness(values []string) []domain.LevelOfAssurance { return nil } @@ -190,52 +190,52 @@ func UILocalesToBusiness(tags []language.Tag) []string { return locales } -func ResponseTypeToBusiness(responseType oidc.ResponseType) model.OIDCResponseType { +func ResponseTypeToBusiness(responseType oidc.ResponseType) domain.OIDCResponseType { switch responseType { case oidc.ResponseTypeCode: - return model.OIDCResponseTypeCode + return domain.OIDCResponseTypeCode case oidc.ResponseTypeIDTokenOnly: - return model.OIDCResponseTypeIdToken + return domain.OIDCResponseTypeIDToken case oidc.ResponseTypeIDToken: - return model.OIDCResponseTypeIdTokenToken + return domain.OIDCResponseTypeIDTokenToken default: - return model.OIDCResponseTypeCode + return domain.OIDCResponseTypeCode } } -func ResponseTypeToOIDC(responseType model.OIDCResponseType) oidc.ResponseType { +func ResponseTypeToOIDC(responseType domain.OIDCResponseType) oidc.ResponseType { switch responseType { - case model.OIDCResponseTypeCode: + case domain.OIDCResponseTypeCode: return oidc.ResponseTypeCode - case model.OIDCResponseTypeIdTokenToken: + case domain.OIDCResponseTypeIDTokenToken: return oidc.ResponseTypeIDToken - case model.OIDCResponseTypeIdToken: + case domain.OIDCResponseTypeIDToken: return oidc.ResponseTypeIDTokenOnly default: return oidc.ResponseTypeCode } } -func CodeChallengeToBusiness(challenge string, method oidc.CodeChallengeMethod) *model.OIDCCodeChallenge { +func CodeChallengeToBusiness(challenge string, method oidc.CodeChallengeMethod) *domain.OIDCCodeChallenge { if challenge == "" { return nil } - challengeMethod := model.CodeChallengeMethodPlain + challengeMethod := domain.CodeChallengeMethodPlain if method == oidc.CodeChallengeMethodS256 { - challengeMethod = model.CodeChallengeMethodS256 + challengeMethod = domain.CodeChallengeMethodS256 } - return &model.OIDCCodeChallenge{ + return &domain.OIDCCodeChallenge{ Challenge: challenge, Method: challengeMethod, } } -func CodeChallengeToOIDC(challenge *model.OIDCCodeChallenge) *oidc.CodeChallenge { +func CodeChallengeToOIDC(challenge *domain.OIDCCodeChallenge) *oidc.CodeChallenge { if challenge == nil { return nil } challengeMethod := oidc.CodeChallengeMethodPlain - if challenge.Method == model.CodeChallengeMethodS256 { + if challenge.Method == domain.CodeChallengeMethodS256 { challengeMethod = oidc.CodeChallengeMethodS256 } return &oidc.CodeChallenge{ @@ -244,12 +244,12 @@ func CodeChallengeToOIDC(challenge *model.OIDCCodeChallenge) *oidc.CodeChallenge } } -func AMRFromMFAType(mfaType model.MFAType) string { +func AMRFromMFAType(mfaType domain.MFAType) string { switch mfaType { - case model.MFATypeOTP: + case domain.MFATypeOTP: return amrOTP - case model.MFATypeU2F, - model.MFATypeU2FUserVerification: + case domain.MFATypeU2F, + domain.MFATypeU2FUserVerification: return amrUserPresence default: return "" diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index f5a11012bf..de1ffa4f04 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -3,6 +3,8 @@ package oidc import ( "context" "github.com/caos/zitadel/internal/telemetry/metrics" + "github.com/caos/zitadel/internal/v2/command" + "github.com/caos/zitadel/internal/v2/query" "time" "github.com/caos/logging" @@ -46,13 +48,15 @@ type Endpoint struct { type OPStorage struct { repo repository.Repository + command *command.CommandSide + query *query.QuerySide defaultLoginURL string defaultAccessTokenLifetime time.Duration defaultIdTokenLifetime time.Duration signingKeyAlgorithm string } -func NewProvider(ctx context.Context, config OPHandlerConfig, repo repository.Repository, localDevMode bool) op.OpenIDProvider { +func NewProvider(ctx context.Context, config OPHandlerConfig, command *command.CommandSide, query *query.QuerySide, repo repository.Repository, localDevMode bool) op.OpenIDProvider { cookieHandler, err := middleware.NewUserAgentHandler(config.UserAgentCookieConfig, id.SonyFlakeGenerator, localDevMode) logging.Log("OIDC-sd4fd").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("cannot user agent handler") config.OPConfig.CodeMethodS256 = true @@ -60,7 +64,7 @@ func NewProvider(ctx context.Context, config OPHandlerConfig, repo repository.Re provider, err := op.NewOpenIDProvider( ctx, config.OPConfig, - newStorage(config.StorageConfig, repo), + newStorage(config.StorageConfig, command, query, repo), op.WithHttpInterceptors( middleware.MetricsHandler(metricTypes), middleware.TelemetryHandler(), @@ -79,9 +83,11 @@ func NewProvider(ctx context.Context, config OPHandlerConfig, repo repository.Re return provider } -func newStorage(config StorageConfig, repo repository.Repository) *OPStorage { +func newStorage(config StorageConfig, command *command.CommandSide, query *query.QuerySide, repo repository.Repository) *OPStorage { return &OPStorage{ repo: repo, + command: command, + query: query, defaultLoginURL: config.DefaultLoginURL, signingKeyAlgorithm: config.SigningKeyAlgorithm, defaultAccessTokenLifetime: config.DefaultAccessTokenLifetime.Duration, diff --git a/internal/auth/repository/auth_request.go b/internal/auth/repository/auth_request.go index 8425c68a06..1899c9f14a 100644 --- a/internal/auth/repository/auth_request.go +++ b/internal/auth/repository/auth_request.go @@ -2,32 +2,30 @@ package repository import ( "context" - "github.com/caos/zitadel/internal/auth_request/model" - org_model "github.com/caos/zitadel/internal/org/model" - user_model "github.com/caos/zitadel/internal/user/model" + "github.com/caos/zitadel/internal/v2/domain" ) type AuthRequestRepository interface { - CreateAuthRequest(ctx context.Context, request *model.AuthRequest) (*model.AuthRequest, error) - AuthRequestByID(ctx context.Context, id, userAgentID string) (*model.AuthRequest, error) - AuthRequestByIDCheckLoggedIn(ctx context.Context, id, userAgentID string) (*model.AuthRequest, error) - AuthRequestByCode(ctx context.Context, code string) (*model.AuthRequest, error) + CreateAuthRequest(ctx context.Context, request *domain.AuthRequest) (*domain.AuthRequest, error) + AuthRequestByID(ctx context.Context, id, userAgentID string) (*domain.AuthRequest, error) + AuthRequestByIDCheckLoggedIn(ctx context.Context, id, userAgentID string) (*domain.AuthRequest, error) + AuthRequestByCode(ctx context.Context, code string) (*domain.AuthRequest, error) SaveAuthCode(ctx context.Context, id, code, userAgentID string) error DeleteAuthRequest(ctx context.Context, id string) error CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error - CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *model.ExternalUser, info *model.BrowserInfo) error + CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *domain.ExternalUser, info *domain.BrowserInfo) error SelectUser(ctx context.Context, id, userID, userAgentID string) error SelectExternalIDP(ctx context.Context, authReqID, idpConfigID, userAgentID string) error - VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) error + VerifyPassword(ctx context.Context, id, userID, resourceOwner, password, userAgentID string, info *domain.BrowserInfo) error - VerifyMFAOTP(ctx context.Context, agentID, authRequestID, code, userAgentID string, info *model.BrowserInfo) error - BeginMFAU2FLogin(ctx context.Context, userID, authRequestID, userAgentID string) (*user_model.WebAuthNLogin, error) - VerifyMFAU2F(ctx context.Context, userID, authRequestID, userAgentID string, credentialData []byte, info *model.BrowserInfo) error - BeginPasswordlessLogin(ctx context.Context, userID, authRequestID, userAgentID string) (*user_model.WebAuthNLogin, error) - VerifyPasswordless(ctx context.Context, userID, authRequestID, userAgentID string, credentialData []byte, info *model.BrowserInfo) error + VerifyMFAOTP(ctx context.Context, authRequestID, userID, resourceOwner, code, userAgentID string, info *domain.BrowserInfo) error + BeginMFAU2FLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (*domain.WebAuthNLogin, error) + VerifyMFAU2F(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error + BeginPasswordlessLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (*domain.WebAuthNLogin, error) + VerifyPasswordless(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error - LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *model.BrowserInfo) error - AutoRegisterExternalUser(ctx context.Context, user *user_model.User, externalIDP *user_model.ExternalIDP, member *org_model.OrgMember, authReqID, userAgentID, resourceOwner string, info *model.BrowserInfo) error + LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *domain.BrowserInfo) error + AutoRegisterExternalUser(ctx context.Context, user *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, info *domain.BrowserInfo) error ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 92ef6aada6..3a3aba5092 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -2,6 +2,8 @@ package eventstore import ( "context" + "github.com/caos/zitadel/internal/v2/command" + "github.com/caos/zitadel/internal/v2/domain" "time" "github.com/caos/logging" @@ -9,10 +11,10 @@ import ( "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" "github.com/caos/zitadel/internal/auth_request/model" + auth_req_model "github.com/caos/zitadel/internal/auth_request/model" cache "github.com/caos/zitadel/internal/auth_request/repository" "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/models" - "github.com/caos/zitadel/internal/eventstore/sdk" iam_model "github.com/caos/zitadel/internal/iam/model" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model" @@ -30,6 +32,7 @@ import ( ) type AuthRequestRepo struct { + Command *command.CommandSide UserEvents *user_event.UserEventstore OrgEvents *org_event.OrgEventstore AuthRequests cache.AuthRequestCache @@ -37,6 +40,7 @@ type AuthRequestRepo struct { UserSessionViewProvider userSessionViewProvider UserViewProvider userViewProvider + UserCommandProvider userCommandProvider UserEventProvider userEventProvider OrgViewProvider orgViewProvider LoginPolicyViewProvider loginPolicyViewProvider @@ -72,7 +76,10 @@ type idpProviderViewProvider interface { type userEventProvider interface { UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) - BulkAddExternalIDPs(ctx context.Context, userID string, externalIDPs []*user_model.ExternalIDP) error +} + +type userCommandProvider interface { + BulkAddedHumanExternalIDP(ctx context.Context, userID, resourceOwner string, externalIDPs []*domain.ExternalIDP) error } type orgViewProvider interface { @@ -92,7 +99,7 @@ func (repo *AuthRequestRepo) Health(ctx context.Context) error { return repo.AuthRequests.Health(ctx) } -func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *model.AuthRequest) (_ *model.AuthRequest, err error) { +func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *domain.AuthRequest) (_ *domain.AuthRequest, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() reqID, err := repo.IdGenerator.Next() @@ -124,13 +131,13 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod return request, nil } -func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id, userAgentID string) (_ *model.AuthRequest, err error) { +func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id, userAgentID string) (_ *domain.AuthRequest, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() return repo.getAuthRequestNextSteps(ctx, id, userAgentID, false) } -func (repo *AuthRequestRepo) AuthRequestByIDCheckLoggedIn(ctx context.Context, id, userAgentID string) (_ *model.AuthRequest, err error) { +func (repo *AuthRequestRepo) AuthRequestByIDCheckLoggedIn(ctx context.Context, id, userAgentID string) (_ *domain.AuthRequest, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() return repo.getAuthRequestNextSteps(ctx, id, userAgentID, true) @@ -147,7 +154,7 @@ func (repo *AuthRequestRepo) SaveAuthCode(ctx context.Context, id, code, userAge return repo.AuthRequests.UpdateAuthRequest(ctx, request) } -func (repo *AuthRequestRepo) AuthRequestByCode(ctx context.Context, code string) (_ *model.AuthRequest, err error) { +func (repo *AuthRequestRepo) AuthRequestByCode(ctx context.Context, code string) (_ *domain.AuthRequest, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() request, err := repo.AuthRequests.GetAuthRequestByCode(ctx, code) @@ -200,7 +207,7 @@ func (repo *AuthRequestRepo) SelectExternalIDP(ctx context.Context, authReqID, i return repo.AuthRequests.UpdateAuthRequest(ctx, request) } -func (repo *AuthRequestRepo) CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, externalUser *model.ExternalUser, info *model.BrowserInfo) (err error) { +func (repo *AuthRequestRepo) CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, externalUser *domain.ExternalUser, info *domain.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) @@ -218,14 +225,14 @@ func (repo *AuthRequestRepo) CheckExternalUserLogin(ctx context.Context, authReq return err } - err = repo.UserEvents.ExternalLoginChecked(ctx, request.UserID, request.WithCurrentInfo(info)) + err = repo.Command.HumanExternalLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info)) if err != nil { return err } return repo.AuthRequests.UpdateAuthRequest(ctx, request) } -func (repo *AuthRequestRepo) setLinkingUser(ctx context.Context, request *model.AuthRequest, externalUser *model.ExternalUser) error { +func (repo *AuthRequestRepo) setLinkingUser(ctx context.Context, request *domain.AuthRequest, externalUser *domain.ExternalUser) error { request.LinkingUsers = append(request.LinkingUsers, externalUser) return repo.AuthRequests.UpdateAuthRequest(ctx, request) } @@ -248,27 +255,27 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge return repo.AuthRequests.UpdateAuthRequest(ctx, request) } -func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) (err error) { +func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, resourceOwner, password, userAgentID string, info *domain.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() request, err := repo.getAuthRequestEnsureUser(ctx, id, userAgentID, userID) if err != nil { return err } - return repo.UserEvents.CheckPassword(ctx, userID, password, request.WithCurrentInfo(info)) + return repo.Command.HumanCheckPassword(ctx, resourceOwner, userID, password, request.WithCurrentInfo(info)) } -func (repo *AuthRequestRepo) VerifyMFAOTP(ctx context.Context, authRequestID, userID, code, userAgentID string, info *model.BrowserInfo) (err error) { +func (repo *AuthRequestRepo) VerifyMFAOTP(ctx context.Context, authRequestID, userID, resourceOwner, code, userAgentID string, info *domain.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID) if err != nil { return err } - return repo.UserEvents.CheckMFAOTP(ctx, userID, code, request.WithCurrentInfo(info)) + return repo.Command.HumanCheckMFAOTP(ctx, userID, code, resourceOwner, request.WithCurrentInfo(info)) } -func (repo *AuthRequestRepo) BeginMFAU2FLogin(ctx context.Context, userID, authRequestID, userAgentID string) (login *user_model.WebAuthNLogin, err error) { +func (repo *AuthRequestRepo) BeginMFAU2FLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (login *domain.WebAuthNLogin, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -276,51 +283,51 @@ func (repo *AuthRequestRepo) BeginMFAU2FLogin(ctx context.Context, userID, authR if err != nil { return nil, err } - return repo.UserEvents.BeginU2FLogin(ctx, userID, request, true) + return repo.Command.HumanBeginU2FLogin(ctx, userID, resourceOwner, request, true) } -func (repo *AuthRequestRepo) VerifyMFAU2F(ctx context.Context, userID, authRequestID, userAgentID string, credentialData []byte, info *model.BrowserInfo) (err error) { +func (repo *AuthRequestRepo) VerifyMFAU2F(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID) if err != nil { return err } - return repo.UserEvents.VerifyMFAU2F(ctx, userID, credentialData, request, true) + return repo.Command.HumanFinishU2FLogin(ctx, userID, resourceOwner, credentialData, request, true) } -func (repo *AuthRequestRepo) BeginPasswordlessLogin(ctx context.Context, userID, authRequestID, userAgentID string) (login *user_model.WebAuthNLogin, err error) { +func (repo *AuthRequestRepo) BeginPasswordlessLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (login *domain.WebAuthNLogin, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID) if err != nil { return nil, err } - return repo.UserEvents.BeginPasswordlessLogin(ctx, userID, request, true) + return repo.Command.HumanBeginPasswordlessLogin(ctx, userID, resourceOwner, request, true) } -func (repo *AuthRequestRepo) VerifyPasswordless(ctx context.Context, userID, authRequestID, userAgentID string, credentialData []byte, info *model.BrowserInfo) (err error) { +func (repo *AuthRequestRepo) VerifyPasswordless(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID) if err != nil { return err } - return repo.UserEvents.VerifyPasswordless(ctx, userID, credentialData, request, true) + return repo.Command.HumanFinishPasswordlessLogin(ctx, userID, resourceOwner, credentialData, request, true) } -func (repo *AuthRequestRepo) LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *model.BrowserInfo) (err error) { +func (repo *AuthRequestRepo) LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *domain.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) if err != nil { return err } - err = linkExternalIDPs(ctx, repo.UserEventProvider, request) + err = linkExternalIDPs(ctx, repo.UserCommandProvider, request) if err != nil { return err } - err = repo.UserEvents.ExternalLoginChecked(ctx, request.UserID, request.WithCurrentInfo(info)) + err = repo.Command.HumanExternalLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info)) if err != nil { return err } @@ -338,62 +345,29 @@ func (repo *AuthRequestRepo) ResetLinkingUsers(ctx context.Context, authReqID, u return repo.AuthRequests.UpdateAuthRequest(ctx, request) } -func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *user_model.User, externalIDP *user_model.ExternalIDP, orgMember *org_model.OrgMember, authReqID, userAgentID, resourceOwner string, info *model.BrowserInfo) (err error) { +func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, info *domain.BrowserInfo) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) if err != nil { return err } - policyResourceOwner := authz.GetCtxData(ctx).OrgID - if resourceOwner != "" { - policyResourceOwner = resourceOwner - } - pwPolicy, err := repo.View.PasswordComplexityPolicyByAggregateID(policyResourceOwner) - if errors.IsNotFound(err) { - pwPolicy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.IAMID) - } + human, err := repo.Command.RegisterHuman(ctx, resourceOwner, registerUser, externalIDP, orgMemberRoles) if err != nil { return err } - pwPolicyView := iam_es_model.PasswordComplexityViewToModel(pwPolicy) - orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(policyResourceOwner) - if errors.IsNotFound(err) { - orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.IAMID) - } - if err != nil { - return err - } - orgPolicyView := iam_es_model.OrgIAMViewToModel(orgPolicy) - user, aggregates, err := repo.UserEvents.PrepareRegisterUser(ctx, registerUser, externalIDP, pwPolicyView, orgPolicyView, resourceOwner) - if err != nil { - return err - } - if orgMember != nil { - orgMember.UserID = user.AggregateID - _, memberAggregate, err := repo.OrgEvents.PrepareAddOrgMember(ctx, orgMember, policyResourceOwner) - if err != nil { - return err - } - aggregates = append(aggregates, memberAggregate) - } - - err = sdk.PushAggregates(ctx, repo.UserEvents.PushAggregates, user.AppendEvents, aggregates...) - if err != nil { - return err - } - request.UserID = user.AggregateID - request.UserOrgID = user.ResourceOwner + request.UserID = human.AggregateID + request.UserOrgID = human.ResourceOwner request.SelectedIDPConfigID = externalIDP.IDPConfigID request.LinkingUsers = nil - err = repo.UserEvents.ExternalLoginChecked(ctx, request.UserID, request.WithCurrentInfo(info)) + err = repo.Command.HumanExternalLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info)) if err != nil { return err } return repo.AuthRequests.UpdateAuthRequest(ctx, request) } -func (repo *AuthRequestRepo) getAuthRequestNextSteps(ctx context.Context, id, userAgentID string, checkLoggedIn bool) (*model.AuthRequest, error) { +func (repo *AuthRequestRepo) getAuthRequestNextSteps(ctx context.Context, id, userAgentID string, checkLoggedIn bool) (*domain.AuthRequest, error) { request, err := repo.getAuthRequest(ctx, id, userAgentID) if err != nil { return nil, err @@ -406,7 +380,7 @@ func (repo *AuthRequestRepo) getAuthRequestNextSteps(ctx context.Context, id, us return request, nil } -func (repo *AuthRequestRepo) getAuthRequestEnsureUser(ctx context.Context, authRequestID, userAgentID, userID string) (*model.AuthRequest, error) { +func (repo *AuthRequestRepo) getAuthRequestEnsureUser(ctx context.Context, authRequestID, userAgentID, userID string) (*domain.AuthRequest, error) { request, err := repo.getAuthRequest(ctx, authRequestID, userAgentID) if err != nil { return nil, err @@ -417,7 +391,7 @@ func (repo *AuthRequestRepo) getAuthRequestEnsureUser(ctx context.Context, authR return request, nil } -func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id, userAgentID string) (*model.AuthRequest, error) { +func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id, userAgentID string) (*domain.AuthRequest, error) { request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id) if err != nil { return nil, err @@ -432,22 +406,24 @@ func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id, userAgentID return request, nil } -func (repo *AuthRequestRepo) getLoginPolicyAndIDPProviders(ctx context.Context, orgID string) (*iam_model.LoginPolicyView, []*iam_model.IDPProviderView, error) { +func (repo *AuthRequestRepo) getLoginPolicyAndIDPProviders(ctx context.Context, orgID string) (*domain.LoginPolicy, []*domain.IDPProvider, error) { policy, err := repo.getLoginPolicy(ctx, orgID) if err != nil { return nil, nil, err } if !policy.AllowExternalIDP { - return policy, nil, nil + return policy.ToLoginPolicyDomain(), nil, nil } idpProviders, err := getLoginPolicyIDPProviders(repo.IDPProviderViewProvider, repo.IAMID, orgID, policy.Default) if err != nil { return nil, nil, err } - return policy, idpProviders, nil + + providers := iam_model.IdpProviderViewsToDomain(idpProviders) + return policy.ToLoginPolicyDomain(), providers, nil } -func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *model.AuthRequest) error { +func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *domain.AuthRequest) error { orgID := request.RequestedOrgID if orgID == "" { orgID = request.UserOrgID @@ -467,7 +443,7 @@ func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *model return nil } -func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model.AuthRequest, loginName string) (err error) { +func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain.AuthRequest, loginName string) (err error) { user := new(user_view_model.UserView) if request.RequestedOrgID != "" { user, err = repo.View.UserByLoginNameAndResourceOwner(loginName, request.RequestedOrgID) @@ -488,7 +464,7 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model. return nil } -func (repo AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Context, request *model.AuthRequest, user *user_view_model.UserView) error { +func (repo AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Context, request *domain.AuthRequest, user *user_view_model.UserView) error { loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, user.ResourceOwner) if err != nil { return err @@ -507,7 +483,7 @@ func (repo AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Contex return nil } -func (repo *AuthRequestRepo) checkSelectedExternalIDP(request *model.AuthRequest, idpConfigID string) error { +func (repo *AuthRequestRepo) checkSelectedExternalIDP(request *domain.AuthRequest, idpConfigID string) error { for _, externalIDP := range request.AllowedExternalIDPs { if externalIDP.IDPConfigID == idpConfigID { request.SelectedIDPConfigID = idpConfigID @@ -517,7 +493,7 @@ func (repo *AuthRequestRepo) checkSelectedExternalIDP(request *model.AuthRequest return errors.ThrowNotFound(nil, "LOGIN-Nsm8r", "Errors.User.ExternalIDP.NotAllowed") } -func (repo *AuthRequestRepo) checkExternalUserLogin(request *model.AuthRequest, idpConfigID, externalUserID string) (err error) { +func (repo *AuthRequestRepo) checkExternalUserLogin(request *domain.AuthRequest, idpConfigID, externalUserID string) (err error) { externalIDP := new(user_view_model.ExternalIDPView) if request.RequestedOrgID != "" { externalIDP, err = repo.View.ExternalIDPByExternalUserIDAndIDPConfigIDAndResourceOwner(externalUserID, idpConfigID, request.RequestedOrgID) @@ -531,27 +507,27 @@ func (repo *AuthRequestRepo) checkExternalUserLogin(request *model.AuthRequest, return nil } -func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthRequest, checkLoggedIn bool) ([]model.NextStep, error) { +func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.AuthRequest, checkLoggedIn bool) ([]domain.NextStep, error) { if request == nil { return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "Errors.Internal") } - steps := make([]model.NextStep, 0) - if !checkLoggedIn && request.Prompt == model.PromptNone { - return append(steps, &model.RedirectToCallbackStep{}), nil + steps := make([]domain.NextStep, 0) + if !checkLoggedIn && request.Prompt == domain.PromptNone { + return append(steps, &domain.RedirectToCallbackStep{}), nil } if request.UserID == "" { if request.LinkingUsers != nil && len(request.LinkingUsers) > 0 { - steps = append(steps, new(model.ExternalNotFoundOptionStep)) + steps = append(steps, new(domain.ExternalNotFoundOptionStep)) return steps, nil } - steps = append(steps, new(model.LoginStep)) - if request.Prompt == model.PromptSelectAccount || request.Prompt == model.PromptUnspecified { + steps = append(steps, new(domain.LoginStep)) + if request.Prompt == domain.PromptSelectAccount || request.Prompt == domain.PromptUnspecified { users, err := repo.usersForUserSelection(request) if err != nil { return nil, err } - if len(users) > 0 || request.Prompt == model.PromptSelectAccount { - steps = append(steps, &model.SelectUserStep{Users: users}) + if len(users) > 0 || request.Prompt == domain.PromptSelectAccount { + steps = append(steps, &domain.SelectUserStep{Users: users}) } } return steps, nil @@ -572,7 +548,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR if selectedIDPConfigID == "" { selectedIDPConfigID = userSession.SelectedIDPConfigID } - return append(steps, &model.ExternalLoginStep{SelectedIDPConfigID: selectedIDPConfigID}), nil + return append(steps, &domain.ExternalLoginStep{SelectedIDPConfigID: selectedIDPConfigID}), nil } if isInternalLogin || (!isInternalLogin && len(request.LinkingUsers) > 0) { step := repo.firstFactorChecked(request, user, userSession) @@ -590,13 +566,13 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR } if user.PasswordChangeRequired { - steps = append(steps, &model.ChangePasswordStep{}) + steps = append(steps, &domain.ChangePasswordStep{}) } if !user.IsEmailVerified { - steps = append(steps, &model.VerifyEMailStep{}) + steps = append(steps, &domain.VerifyEMailStep{}) } if user.UsernameChangeRequired { - steps = append(steps, &model.ChangeUsernameStep{}) + steps = append(steps, &domain.ChangeUsernameStep{}) } if user.PasswordChangeRequired || !user.IsEmailVerified || user.UsernameChangeRequired { @@ -604,7 +580,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR } if request.LinkingUsers != nil && len(request.LinkingUsers) != 0 { - return append(steps, &model.LinkUsersStep{}), nil + return append(steps, &domain.LinkUsersStep{}), nil } //PLANNED: consent step @@ -614,46 +590,46 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR return nil, err } if missing { - return append(steps, &model.GrantRequiredStep{}), nil + return append(steps, &domain.GrantRequiredStep{}), nil } - return append(steps, &model.RedirectToCallbackStep{}), nil + return append(steps, &domain.RedirectToCallbackStep{}), nil } -func (repo *AuthRequestRepo) usersForUserSelection(request *model.AuthRequest) ([]model.UserSelection, error) { +func (repo *AuthRequestRepo) usersForUserSelection(request *domain.AuthRequest) ([]domain.UserSelection, error) { userSessions, err := userSessionsByUserAgentID(repo.UserSessionViewProvider, request.AgentID) if err != nil { return nil, err } - users := make([]model.UserSelection, len(userSessions)) + users := make([]domain.UserSelection, len(userSessions)) for i, session := range userSessions { - users[i] = model.UserSelection{ + users[i] = domain.UserSelection{ UserID: session.UserID, DisplayName: session.DisplayName, LoginName: session.LoginName, - UserSessionState: session.State, + UserSessionState: auth_req_model.UserSessionStateToDomain(session.State), SelectionPossible: request.RequestedOrgID == "" || request.RequestedOrgID == session.ResourceOwner, } } return users, nil } -func (repo *AuthRequestRepo) firstFactorChecked(request *model.AuthRequest, user *user_model.UserView, userSession *user_model.UserSessionView) model.NextStep { +func (repo *AuthRequestRepo) firstFactorChecked(request *domain.AuthRequest, user *user_model.UserView, userSession *user_model.UserSessionView) domain.NextStep { if user.InitRequired { - return &model.InitUserStep{PasswordSet: user.PasswordSet} + return &domain.InitUserStep{PasswordSet: user.PasswordSet} } - var step model.NextStep - if request.LoginPolicy.PasswordlessType != iam_model.PasswordlessTypeNotAllowed && user.IsPasswordlessReady() { + var step domain.NextStep + if request.LoginPolicy.PasswordlessType != domain.PasswordlessTypeNotAllowed && user.IsPasswordlessReady() { if checkVerificationTime(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime) { request.AuthTime = userSession.PasswordlessVerification return nil } - step = &model.PasswordlessStep{} + step = &domain.PasswordlessStep{} } if !user.PasswordSet { - return &model.InitPasswordStep{} + return &domain.InitPasswordStep{} } if checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) { @@ -664,13 +640,13 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *model.AuthRequest, user if step != nil { return step } - return &model.PasswordStep{} + return &domain.PasswordStep{} } -func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *model.AuthRequest, user *user_model.UserView) (model.NextStep, bool, error) { +func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *domain.AuthRequest, user *user_model.UserView) (domain.NextStep, bool, error) { mfaLevel := request.MFALevel() allowedProviders, required := user.MFATypesAllowed(mfaLevel, request.LoginPolicy) - promptRequired := (user.MFAMaxSetUp < mfaLevel) || (len(allowedProviders) == 0 && required) + promptRequired := (auth_req_model.MFALevelToDomain(user.MFAMaxSetUp) < mfaLevel) || (len(allowedProviders) == 0 && required) if promptRequired || !repo.mfaSkippedOrSetUp(user) { types := user.MFATypesSetupPossible(mfaLevel, request.LoginPolicy) if promptRequired && len(types) == 0 { @@ -679,7 +655,7 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, if len(types) == 0 { return nil, true, nil } - return &model.MFAPromptStep{ + return &domain.MFAPromptStep{ Required: promptRequired, MFAProviders: types, }, false, nil @@ -687,26 +663,26 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, switch mfaLevel { default: fallthrough - case model.MFALevelNotSetUp: + case domain.MFALevelNotSetUp: if len(allowedProviders) == 0 { return nil, true, nil } fallthrough - case model.MFALevelSecondFactor: + case domain.MFALevelSecondFactor: if checkVerificationTime(userSession.SecondFactorVerification, repo.SecondFactorCheckLifeTime) { - request.MFAsVerified = append(request.MFAsVerified, userSession.SecondFactorVerificationType) + request.MFAsVerified = append(request.MFAsVerified, auth_req_model.MFATypeToDomain(userSession.SecondFactorVerificationType)) request.AuthTime = userSession.SecondFactorVerification return nil, true, nil } fallthrough - case model.MFALevelMultiFactor: + case domain.MFALevelMultiFactor: if checkVerificationTime(userSession.MultiFactorVerification, repo.MultiFactorCheckLifeTime) { - request.MFAsVerified = append(request.MFAsVerified, userSession.MultiFactorVerificationType) + request.MFAsVerified = append(request.MFAsVerified, auth_req_model.MFATypeToDomain(userSession.MultiFactorVerificationType)) request.AuthTime = userSession.MultiFactorVerification return nil, true, nil } } - return &model.MFAVerificationStep{ + return &domain.MFAVerificationStep{ MFAProviders: allowedProviders, }, false, nil } @@ -733,7 +709,7 @@ func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) ( return iam_es_model.LoginPolicyViewToModel(policy), err } -func setOrgID(orgViewProvider orgViewProvider, request *model.AuthRequest) error { +func setOrgID(orgViewProvider orgViewProvider, request *domain.AuthRequest) error { primaryDomain := request.GetScopeOrgPrimaryDomain() if primaryDomain == "" { return nil @@ -881,14 +857,14 @@ func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider return user_view_model.UserToModel(&userCopy), nil } -func linkExternalIDPs(ctx context.Context, userEventProvider userEventProvider, request *model.AuthRequest) error { - externalIDPs := make([]*user_model.ExternalIDP, len(request.LinkingUsers)) +func linkExternalIDPs(ctx context.Context, userCommandProvider userCommandProvider, request *domain.AuthRequest) error { + externalIDPs := make([]*domain.ExternalIDP, len(request.LinkingUsers)) for i, linkingUser := range request.LinkingUsers { - externalIDP := &user_model.ExternalIDP{ - ObjectRoot: es_models.ObjectRoot{AggregateID: request.UserID}, - IDPConfigID: linkingUser.IDPConfigID, - UserID: linkingUser.ExternalUserID, - DisplayName: linkingUser.DisplayName, + externalIDP := &domain.ExternalIDP{ + ObjectRoot: es_models.ObjectRoot{AggregateID: request.UserID}, + IDPConfigID: linkingUser.IDPConfigID, + ExternalUserID: linkingUser.ExternalUserID, + DisplayName: linkingUser.DisplayName, } externalIDPs[i] = externalIDP } @@ -896,10 +872,10 @@ func linkExternalIDPs(ctx context.Context, userEventProvider userEventProvider, UserID: "LOGIN", OrgID: request.UserOrgID, } - return userEventProvider.BulkAddExternalIDPs(authz.SetCtxData(ctx, data), request.UserID, externalIDPs) + return userCommandProvider.BulkAddedHumanExternalIDP(authz.SetCtxData(ctx, data), request.UserID, request.UserOrgID, externalIDPs) } -func linkingIDPConfigExistingInAllowedIDPs(linkingUsers []*model.ExternalUser, idpProviders []*iam_model.IDPProviderView) bool { +func linkingIDPConfigExistingInAllowedIDPs(linkingUsers []*domain.ExternalUser, idpProviders []*domain.IDPProvider) bool { for _, linkingUser := range linkingUsers { exists := false for _, idp := range idpProviders { @@ -914,10 +890,10 @@ func linkingIDPConfigExistingInAllowedIDPs(linkingUsers []*model.ExternalUser, i } return true } -func userGrantRequired(ctx context.Context, request *model.AuthRequest, user *user_model.UserView, userGrantProvider userGrantProvider) (_ bool, err error) { +func userGrantRequired(ctx context.Context, request *domain.AuthRequest, user *user_model.UserView, userGrantProvider userGrantProvider) (_ bool, err error) { var app *project_view_model.ApplicationView switch request.Request.Type() { - case model.AuthRequestTypeOIDC: + case domain.AuthRequestTypeOIDC: app, err = userGrantProvider.ApplicationByClientID(ctx, request.ApplicationID) if err != nil { return false, err diff --git a/internal/auth/repository/eventsourcing/eventstore/token.go b/internal/auth/repository/eventsourcing/eventstore/token.go index ec3e81aa7b..d71523c98a 100644 --- a/internal/auth/repository/eventsourcing/eventstore/token.go +++ b/internal/auth/repository/eventsourcing/eventstore/token.go @@ -2,12 +2,8 @@ package eventstore import ( "context" - "strings" - "github.com/caos/logging" - auth_req_model "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" @@ -22,34 +18,6 @@ type TokenRepo struct { View *view.View } -func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, clientID, userID string, audience, scopes []string, lifetime time.Duration) (*usr_model.Token, error) { - preferredLanguage := "" - user, _ := repo.View.UserByID(userID) - if user != nil { - preferredLanguage = user.PreferredLanguage - } - - for _, scope := range scopes { - if strings.HasPrefix(scope, auth_req_model.ProjectIDScope) && strings.HasSuffix(scope, auth_req_model.AudSuffix) { - audience = append(audience, strings.TrimSuffix(strings.TrimPrefix(scope, auth_req_model.ProjectIDScope), auth_req_model.AudSuffix)) - } - } - - now := time.Now().UTC() - token := &usr_model.Token{ - ObjectRoot: models.ObjectRoot{ - AggregateID: userID, - }, - UserAgentID: agentID, - ApplicationID: clientID, - Audience: audience, - Scopes: scopes, - Expiration: now.Add(lifetime), - PreferredLanguage: preferredLanguage, - } - return repo.UserEvents.TokenAdded(ctx, token) -} - func (repo *TokenRepo) IsTokenValid(ctx context.Context, userID, tokenID string) (bool, error) { token, err := repo.TokenByID(ctx, userID, tokenID) if err == nil { diff --git a/internal/auth/repository/eventsourcing/eventstore/user.go b/internal/auth/repository/eventsourcing/eventstore/user.go index 8c4a3e4e5d..b831f81865 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user.go +++ b/internal/auth/repository/eventsourcing/eventstore/user.go @@ -36,14 +36,6 @@ func (repo *UserRepo) Health(ctx context.Context) error { return repo.UserEvents.Health(ctx) } -func (repo *UserRepo) Register(ctx context.Context, user *model.User, orgMember *org_model.OrgMember, resourceOwner string) (*model.User, error) { - return repo.registerUser(ctx, user, nil, orgMember, resourceOwner) -} - -func (repo *UserRepo) RegisterExternalUser(ctx context.Context, user *model.User, externalIDP *model.ExternalIDP, orgMember *org_model.OrgMember, resourceOwner string) (*model.User, error) { - return repo.registerUser(ctx, user, externalIDP, orgMember, resourceOwner) -} - func (repo *UserRepo) registerUser(ctx context.Context, registerUser *model.User, externalIDP *model.ExternalIDP, orgMember *org_model.OrgMember, resourceOwner string) (*model.User, error) { policyResourceOwner := authz.GetCtxData(ctx).OrgID if resourceOwner != "" { @@ -133,18 +125,6 @@ func (repo *UserRepo) MyEmail(ctx context.Context) (*model.Email, error) { return user.GetEmail() } -func (repo *UserRepo) VerifyEmail(ctx context.Context, userID, code string) error { - return repo.UserEvents.VerifyEmail(ctx, userID, code) -} - -func (repo *UserRepo) VerifyMyEmail(ctx context.Context, code string) error { - return repo.UserEvents.VerifyEmail(ctx, authz.GetCtxData(ctx).UserID, code) -} - -func (repo *UserRepo) ResendEmailVerificationMail(ctx context.Context, userID string) error { - return repo.UserEvents.CreateEmailVerificationCode(ctx, userID) -} - func (repo *UserRepo) MyPhone(ctx context.Context) (*model.Phone, error) { user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) if err != nil { @@ -156,21 +136,6 @@ func (repo *UserRepo) MyPhone(ctx context.Context) (*model.Phone, error) { return user.GetPhone() } -func (repo *UserRepo) ChangeMyPhone(ctx context.Context, phone *model.Phone) (*model.Phone, error) { - if err := checkIDs(ctx, phone.ObjectRoot); err != nil { - return nil, err - } - return repo.UserEvents.ChangePhone(ctx, phone) -} - -func (repo *UserRepo) RemoveMyPhone(ctx context.Context) error { - return repo.UserEvents.RemovePhone(ctx, authz.GetCtxData(ctx).UserID) -} - -func (repo *UserRepo) VerifyMyPhone(ctx context.Context, code string) error { - return repo.UserEvents.VerifyPhone(ctx, authz.GetCtxData(ctx).UserID, code) -} - func (repo *UserRepo) MyAddress(ctx context.Context) (*model.Address, error) { user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) if err != nil { @@ -182,34 +147,6 @@ func (repo *UserRepo) MyAddress(ctx context.Context) (*model.Address, error) { return user.GetAddress() } -func (repo *UserRepo) ChangeMyPassword(ctx context.Context, old, new string) error { - policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if errors.IsNotFound(err) { - policy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return err - } - pwPolicyView := iam_es_model.PasswordComplexityViewToModel(policy) - _, err = repo.UserEvents.ChangePassword(ctx, pwPolicyView, authz.GetCtxData(ctx).UserID, old, new, "") - return err -} - -func (repo *UserRepo) ChangePassword(ctx context.Context, userID, old, new, userAgentID string) (err error) { - ctx, span := tracing.NewSpan(ctx) - defer func() { span.EndWithError(err) }() - policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if errors.IsNotFound(err) { - policy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return err - } - pwPolicyView := iam_es_model.PasswordComplexityViewToModel(policy) - _, err = repo.UserEvents.ChangePassword(ctx, pwPolicyView, userID, old, new, userAgentID) - return err -} - func (repo *UserRepo) MyUserMFAs(ctx context.Context) ([]*model.MultiFactor, error) { user, err := repo.UserByID(ctx, authz.GetCtxData(ctx).UserID) if err != nil { @@ -225,142 +162,24 @@ func (repo *UserRepo) MyUserMFAs(ctx context.Context) ([]*model.MultiFactor, err return mfas, nil } -func (repo *UserRepo) AddMFAOTP(ctx context.Context, userID string) (*model.OTP, error) { - accountName := "" - user, err := repo.UserByID(ctx, userID) - if err != nil { - logging.Log("EVENT-Fk93s").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") - } else { - accountName = user.PreferredLoginName - } - return repo.UserEvents.AddOTP(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) RemoveMyMFAOTP(ctx context.Context) error { - return repo.UserEvents.RemoveOTP(ctx, authz.GetCtxData(ctx).UserID) -} - -func (repo *UserRepo) AddMFAU2F(ctx context.Context, userID string) (*model.WebAuthNToken, error) { - accountName := "" - user, err := repo.UserByID(ctx, userID) - if err != nil { - logging.Log("EVENT-DAqe1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") - } else { - accountName = user.PreferredLoginName - } - return repo.UserEvents.AddU2F(ctx, userID, accountName, true) -} - -func (repo *UserRepo) AddMyMFAU2F(ctx context.Context) (*model.WebAuthNToken, error) { - userID := authz.GetCtxData(ctx).UserID - accountName := "" - user, err := repo.UserByID(ctx, userID) - if err != nil { - logging.Log("EVENT-Ghwl1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") - } else { - accountName = user.PreferredLoginName - } - return repo.UserEvents.AddU2F(ctx, userID, accountName, false) -} - -func (repo *UserRepo) VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { - return repo.UserEvents.VerifyU2FSetup(ctx, userID, tokenName, userAgentID, credentialData) -} - func (repo *UserRepo) GetPasswordless(ctx context.Context, userID string) ([]*model.WebAuthNToken, error) { return repo.UserEvents.GetPasswordless(ctx, userID) } -func (repo *UserRepo) AddPasswordless(ctx context.Context, userID string) (*model.WebAuthNToken, error) { - accountName := "" - user, err := repo.UserByID(ctx, userID) - if err != nil { - logging.Log("EVENT-Vj2k1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") - } else { - accountName = user.PreferredLoginName - } - return repo.UserEvents.AddPasswordless(ctx, userID, accountName, true) -} - func (repo *UserRepo) GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNToken, error) { return repo.UserEvents.GetPasswordless(ctx, authz.GetCtxData(ctx).UserID) } -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) 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 -} - -func (repo *UserRepo) VerifyInitCode(ctx context.Context, userID, code, password string) error { - policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if errors.IsNotFound(err) { - policy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return err - } - pwPolicyView := iam_es_model.PasswordComplexityViewToModel(policy) - return repo.UserEvents.VerifyInitCode(ctx, pwPolicyView, userID, code, password) -} - -func (repo *UserRepo) SkipMFAInit(ctx context.Context, userID string) error { - return repo.UserEvents.SkipMFAInit(ctx, userID) -} - -func (repo *UserRepo) RequestPasswordReset(ctx context.Context, loginname string) error { - user, err := repo.View.UserByLoginName(loginname) - if err != nil { - return err - } - return repo.UserEvents.RequestSetPassword(ctx, user.ID, model.NotificationTypeEmail) -} - -func (repo *UserRepo) SetPassword(ctx context.Context, userID, code, password, userAgentID string) error { - policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if errors.IsNotFound(err) { - policy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return err - } - pwPolicyView := iam_es_model.PasswordComplexityViewToModel(policy) - return repo.UserEvents.SetPassword(ctx, pwPolicyView, userID, code, password, userAgentID) -} - -func (repo *UserRepo) SignOut(ctx context.Context, agentID string) error { +func (repo *UserRepo) UserSessionUserIDsByAgentID(ctx context.Context, agentID string) ([]string, error) { userSessions, err := repo.View.UserSessionsByAgentID(agentID) if err != nil { - return err + return nil, err } userIDs := make([]string, len(userSessions)) for i, session := range userSessions { userIDs[i] = session.UserID } - return repo.UserEvents.SignOut(ctx, agentID, userIDs) + return userIDs, nil } func (repo *UserRepo) UserByID(ctx context.Context, id string) (*model.UserView, error) { @@ -385,6 +204,27 @@ func (repo *UserRepo) UserByID(ctx context.Context, id string) (*model.UserView, return usr_view_model.UserToModel(&userCopy), nil } +func (repo *UserRepo) UserByLoginName(ctx context.Context, loginname string) (*model.UserView, error) { + user, err := repo.View.UserByLoginName(loginname) + if err != nil { + return nil, err + } + events, err := repo.UserEvents.UserEventsByID(ctx, user.ID, user.Sequence) + if err != nil { + logging.Log("EVENT-PSoc3").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("error retrieving new events") + return usr_view_model.UserToModel(user), nil + } + userCopy := *user + for _, event := range events { + if err := userCopy.AppendEvent(event); err != nil { + return usr_view_model.UserToModel(user), nil + } + } + if userCopy.State == int32(model.UserStateDeleted) { + return nil, errors.ThrowNotFound(nil, "EVENT-vZ8us", "Errors.User.NotFound") + } + return usr_view_model.UserToModel(&userCopy), nil +} func (repo *UserRepo) MyUserChanges(ctx context.Context, lastSequence uint64, limit uint64, sortAscending bool) (*model.UserChanges, error) { changes, err := repo.UserEvents.UserChanges(ctx, authz.GetCtxData(ctx).UserID, lastSequence, limit, sortAscending) if err != nil { @@ -405,19 +245,6 @@ func (repo *UserRepo) MyUserChanges(ctx context.Context, lastSequence uint64, li return changes, nil } -func (repo *UserRepo) ChangeUsername(ctx context.Context, userID, username string) error { - policyResourceOwner := authz.GetCtxData(ctx).OrgID - orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(policyResourceOwner) - 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, userID, username, orgPolicyView) -} - func checkIDs(ctx context.Context, obj es_models.ObjectRoot) error { if obj.AggregateID != authz.GetCtxData(ctx).UserID { return errors.ThrowPermissionDenied(nil, "EVENT-kFi9w", "object does not belong to user") diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 3d196ee067..e554e06602 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -20,6 +20,7 @@ import ( es_org "github.com/caos/zitadel/internal/org/repository/eventsourcing" es_proj "github.com/caos/zitadel/internal/project/repository/eventsourcing" es_user "github.com/caos/zitadel/internal/user/repository/eventsourcing" + "github.com/caos/zitadel/internal/v2/command" "github.com/caos/zitadel/internal/v2/query" ) @@ -46,7 +47,7 @@ type EsRepository struct { eventstore.IAMRepository } -func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, authZRepo *authz_repo.EsRepository) (*EsRepository, error) { +func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, command *command.CommandSide, authZRepo *authz_repo.EsRepository) (*EsRepository, error) { es, err := es_int.Start(conf.Eventstore) if err != nil { return nil, err @@ -131,12 +132,14 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au SystemDefaults: systemDefaults, }, eventstore.AuthRequestRepo{ + Command: command, UserEvents: user, OrgEvents: org, AuthRequests: authReq, View: view, UserSessionViewProvider: view, UserViewProvider: view, + UserCommandProvider: command, UserEventProvider: user, OrgViewProvider: view, IDPProviderViewProvider: view, diff --git a/internal/auth/repository/token.go b/internal/auth/repository/token.go index c5d5130593..1451e06aea 100644 --- a/internal/auth/repository/token.go +++ b/internal/auth/repository/token.go @@ -3,11 +3,9 @@ package repository import ( "context" usr_model "github.com/caos/zitadel/internal/user/model" - "time" ) type TokenRepository interface { - CreateToken(ctx context.Context, agentID, clientID, userID string, audience, scopes []string, lifetime time.Duration) (*usr_model.Token, error) IsTokenValid(ctx context.Context, userID, tokenID string) (bool, error) TokenByID(ctx context.Context, userID, tokenID string) (*usr_model.TokenView, error) } diff --git a/internal/auth/repository/user.go b/internal/auth/repository/user.go index d1eb3257b7..46a93c13f1 100644 --- a/internal/auth/repository/user.go +++ b/internal/auth/repository/user.go @@ -3,43 +3,18 @@ package repository import ( "context" - org_model "github.com/caos/zitadel/internal/org/model" - "github.com/caos/zitadel/internal/user/model" ) type UserRepository interface { - Register(ctx context.Context, user *model.User, member *org_model.OrgMember, resourceOwner string) (*model.User, error) - RegisterExternalUser(ctx context.Context, user *model.User, externalIDP *model.ExternalIDP, member *org_model.OrgMember, resourceOwner string) (*model.User, error) - myUserRepo - SkipMFAInit(ctx context.Context, userID string) error - - RequestPasswordReset(ctx context.Context, username string) error - SetPassword(ctx context.Context, userID, code, password, userAgentID string) error - ChangePassword(ctx context.Context, userID, old, new, userAgentID string) error - - VerifyEmail(ctx context.Context, userID, code string) error - ResendEmailVerificationMail(ctx context.Context, userID string) error - - VerifyInitCode(ctx context.Context, userID, code, password string) error - ResendInitVerificationMail(ctx context.Context, userID string) error - - AddMFAOTP(ctx context.Context, userID string) (*model.OTP, error) - VerifyMFAOTPSetup(ctx context.Context, userID, code, userAgentID string) error - - AddMFAU2F(ctx context.Context, id string) (*model.WebAuthNToken, error) - VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) 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 - ChangeUsername(ctx context.Context, userID, username string) error - - SignOut(ctx context.Context, agentID string) error + UserSessionUserIDsByAgentID(ctx context.Context, agentID string) ([]string, error) UserByID(ctx context.Context, userID string) (*model.UserView, error) + UserByLoginName(ctx context.Context, loginName string) (*model.UserView, error) MachineKeyByID(ctx context.Context, keyID string) (*model.MachineKeyView, error) } @@ -59,8 +34,6 @@ type myUserRepo interface { MyUserMFAs(ctx context.Context) ([]*model.MultiFactor, error) - AddMyMFAU2F(ctx context.Context) (*model.WebAuthNToken, error) - GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNToken, error) MyUserChanges(ctx context.Context, lastSequence uint64, limit uint64, sortAscending bool) (*model.UserChanges, error) diff --git a/internal/auth_request/model/next_step.go b/internal/auth_request/model/next_step.go index b3a3a07733..3d4a5e01ec 100644 --- a/internal/auth_request/model/next_step.go +++ b/internal/auth_request/model/next_step.go @@ -1,5 +1,9 @@ package model +import ( + "github.com/caos/zitadel/internal/v2/domain" +) + type NextStep interface { Type() NextStepType } @@ -164,3 +168,45 @@ const ( MFALevelMultiFactor MFALevelMultiFactorCertified ) + +func MFATypeToDomain(mfaType MFAType) domain.MFAType { + switch mfaType { + case MFATypeOTP: + return domain.MFATypeOTP + case MFATypeU2F: + return domain.MFATypeU2F + case MFATypeU2FUserVerification: + return domain.MFATypeU2FUserVerification + default: + return domain.MFATypeOTP + } + +} + +func MFALevelToDomain(mfaLevel MFALevel) domain.MFALevel { + switch mfaLevel { + case MFALevelNotSetUp: + return domain.MFALevelNotSetUp + case MFALevelSecondFactor: + return domain.MFALevelSecondFactor + case MFALevelMultiFactor: + return domain.MFALevelMultiFactor + case MFALevelMultiFactorCertified: + return domain.MFALevelMultiFactorCertified + default: + return domain.MFALevelNotSetUp + } + +} + +func UserSessionStateToDomain(state UserSessionState) domain.UserSessionState { + switch state { + case UserSessionStateActive: + return domain.UserSessionStateActive + case UserSessionStateTerminated: + return domain.UserSessionStateTerminated + default: + return domain.UserSessionStateActive + } + +} diff --git a/internal/auth_request/repository/cache/cache.go b/internal/auth_request/repository/cache/cache.go index 1a85c6eb5d..7bfacaaf5d 100644 --- a/internal/auth_request/repository/cache/cache.go +++ b/internal/auth_request/repository/cache/cache.go @@ -6,8 +6,8 @@ import ( "encoding/json" "errors" "fmt" + "github.com/caos/zitadel/internal/v2/domain" - "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/config/types" caos_errs "github.com/caos/zitadel/internal/errors" ) @@ -34,19 +34,19 @@ func (c *AuthRequestCache) Health(ctx context.Context) error { return c.client.PingContext(ctx) } -func (c *AuthRequestCache) GetAuthRequestByID(_ context.Context, id string) (*model.AuthRequest, error) { +func (c *AuthRequestCache) GetAuthRequestByID(_ context.Context, id string) (*domain.AuthRequest, error) { return c.getAuthRequest("id", id) } -func (c *AuthRequestCache) GetAuthRequestByCode(_ context.Context, code string) (*model.AuthRequest, error) { +func (c *AuthRequestCache) GetAuthRequestByCode(_ context.Context, code string) (*domain.AuthRequest, error) { return c.getAuthRequest("code", code) } -func (c *AuthRequestCache) SaveAuthRequest(_ context.Context, request *model.AuthRequest) error { +func (c *AuthRequestCache) SaveAuthRequest(_ context.Context, request *domain.AuthRequest) error { return c.saveAuthRequest(request, "INSERT INTO auth.auth_requests (id, request, request_type) VALUES($1, $2, $3)", request.Request.Type()) } -func (c *AuthRequestCache) UpdateAuthRequest(_ context.Context, request *model.AuthRequest) error { +func (c *AuthRequestCache) UpdateAuthRequest(_ context.Context, request *domain.AuthRequest) error { return c.saveAuthRequest(request, "UPDATE auth.auth_requests SET request = $2, code = $3 WHERE id = $1", request.Code) } @@ -58,9 +58,9 @@ func (c *AuthRequestCache) DeleteAuthRequest(_ context.Context, id string) error return nil } -func (c *AuthRequestCache) getAuthRequest(key, value string) (*model.AuthRequest, error) { +func (c *AuthRequestCache) getAuthRequest(key, value string) (*domain.AuthRequest, error) { var b []byte - var requestType model.AuthRequestType + var requestType domain.AuthRequestType query := fmt.Sprintf("SELECT request, request_type FROM auth.auth_requests WHERE %s = $1", key) err := c.client.QueryRow(query, value).Scan(&b, &requestType) if err != nil { @@ -69,7 +69,7 @@ func (c *AuthRequestCache) getAuthRequest(key, value string) (*model.AuthRequest } return nil, caos_errs.ThrowInternal(err, "CACHE-as3kj", "Errors.Internal") } - request, err := model.NewAuthRequestFromType(requestType) + request, err := domain.NewAuthRequestFromType(requestType) if err == nil { err = json.Unmarshal(b, request) } @@ -79,7 +79,7 @@ func (c *AuthRequestCache) getAuthRequest(key, value string) (*model.AuthRequest return request, nil } -func (c *AuthRequestCache) saveAuthRequest(request *model.AuthRequest, query string, param interface{}) error { +func (c *AuthRequestCache) saveAuthRequest(request *domain.AuthRequest, query string, param interface{}) error { b, err := json.Marshal(request) if err != nil { return caos_errs.ThrowInternal(err, "CACHE-os0GH", "Errors.Internal") diff --git a/internal/auth_request/repository/mock/repository.mock.go b/internal/auth_request/repository/mock/repository.mock.go index 9d23f6e35f..95433e4610 100644 --- a/internal/auth_request/repository/mock/repository.mock.go +++ b/internal/auth_request/repository/mock/repository.mock.go @@ -107,7 +107,7 @@ func (mr *MockAuthRequestCacheMockRecorder) SaveAuthRequest(arg0, arg1 interface } // UpdateAuthRequest mocks base method -func (m *MockAuthRequestCache) UpdateAuthRequest(arg0 context.Context, arg1 *model.AuthRequest) error { +func (m *MockAuthRequestCache) UpdateAuthRequest(arg0 context.Context, arg1 *domain.AuthRequest) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateAuthRequest", arg0, arg1) ret0, _ := ret[0].(error) diff --git a/internal/auth_request/repository/repository.go b/internal/auth_request/repository/repository.go index f72d2c8fda..ab9ca86f47 100644 --- a/internal/auth_request/repository/repository.go +++ b/internal/auth_request/repository/repository.go @@ -2,16 +2,15 @@ package repository import ( "context" - - "github.com/caos/zitadel/internal/auth_request/model" + "github.com/caos/zitadel/internal/v2/domain" ) type AuthRequestCache interface { Health(ctx context.Context) error - GetAuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error) - GetAuthRequestByCode(ctx context.Context, code string) (*model.AuthRequest, error) - SaveAuthRequest(ctx context.Context, request *model.AuthRequest) error - UpdateAuthRequest(ctx context.Context, request *model.AuthRequest) error + GetAuthRequestByID(ctx context.Context, id string) (*domain.AuthRequest, error) + GetAuthRequestByCode(ctx context.Context, code string) (*domain.AuthRequest, error) + SaveAuthRequest(ctx context.Context, request *domain.AuthRequest) error + UpdateAuthRequest(ctx context.Context, request *domain.AuthRequest) error DeleteAuthRequest(ctx context.Context, id string) error } diff --git a/internal/eventstore/v2/eventstore_test.go b/internal/eventstore/v2/eventstore_test.go index a841f99e77..81a2de1c2a 100644 --- a/internal/eventstore/v2/eventstore_test.go +++ b/internal/eventstore/v2/eventstore_test.go @@ -63,7 +63,7 @@ func (e *testEvent) Data() interface{} { return e.data() } -func (e *testEvent) UniqueConstraint() []EventUniqueConstraint { +func (e *testEvent) UniqueConstraint() []*EventUniqueConstraint { return nil } diff --git a/internal/eventstore/v2/repository/sql/crdb.go b/internal/eventstore/v2/repository/sql/crdb.go index 7b65f616a9..e9ad16a231 100644 --- a/internal/eventstore/v2/repository/sql/crdb.go +++ b/internal/eventstore/v2/repository/sql/crdb.go @@ -188,7 +188,7 @@ func (db *CRDB) handleUniqueConstraints(ctx context.Context, tx *sql.Tx, uniqueC logging.LogWithFields("SQL-M0vsf", "unique_type", uniqueConstraint.UniqueType, "unique_field", uniqueConstraint.UniqueField).WithError(err).Info("delete unique constraint failed") - return caos_errs.ThrowInternal(err, "SQL-2M9fs", "unable to remove unique constraint ") + return caos_errs.ThrowInternal(err, "SQL-6n88i", "unable to remove unique constraint ") } } } diff --git a/internal/eventstore/v2/search_query.go b/internal/eventstore/v2/search_query.go index 64961e8e7f..634b2ccccc 100644 --- a/internal/eventstore/v2/search_query.go +++ b/internal/eventstore/v2/search_query.go @@ -139,14 +139,22 @@ func (factory *SearchQueryBuilder) eventTypeFilter() *repository.Filter { if len(factory.eventTypes) == 1 { return repository.NewFilter(repository.FieldEventType, factory.eventTypes[0], repository.OperationEquals) } - return repository.NewFilter(repository.FieldEventType, factory.eventTypes, repository.OperationIn) + eventTypes := make([]repository.EventType, len(factory.eventTypes)) + for i, eventType := range factory.eventTypes { + eventTypes[i] = repository.EventType(eventType) + } + return repository.NewFilter(repository.FieldEventType, eventTypes, repository.OperationIn) } func (factory *SearchQueryBuilder) aggregateTypeFilter() *repository.Filter { if len(factory.aggregateTypes) == 1 { return repository.NewFilter(repository.FieldAggregateType, factory.aggregateTypes[0], repository.OperationEquals) } - return repository.NewFilter(repository.FieldAggregateType, factory.aggregateTypes, repository.OperationIn) + aggregateTypes := make([]repository.AggregateType, len(factory.aggregateTypes)) + for i, aggregateType := range factory.aggregateTypes { + aggregateTypes[i] = repository.AggregateType(aggregateType) + } + return repository.NewFilter(repository.FieldAggregateType, aggregateTypes, repository.OperationIn) } func (factory *SearchQueryBuilder) eventSequenceFilter() *repository.Filter { diff --git a/internal/iam/model/idp_provider_view.go b/internal/iam/model/idp_provider_view.go index 5a8f981c13..0ac112147e 100644 --- a/internal/iam/model/idp_provider_view.go +++ b/internal/iam/model/idp_provider_view.go @@ -2,6 +2,7 @@ package model import ( "github.com/caos/zitadel/internal/model" + "github.com/caos/zitadel/internal/v2/domain" "time" ) @@ -60,3 +61,63 @@ func (r *IDPProviderSearchRequest) EnsureLimit(limit uint64) { func (r *IDPProviderSearchRequest) AppendAggregateIDQuery(aggregateID string) { r.Queries = append(r.Queries, &IDPProviderSearchQuery{Key: IDPProviderSearchKeyAggregateID, Method: model.SearchMethodEquals, Value: aggregateID}) } + +func IdpProviderViewsToDomain(idpProviders []*IDPProviderView) []*domain.IDPProvider { + providers := make([]*domain.IDPProvider, len(idpProviders)) + for i, provider := range idpProviders { + p := &domain.IDPProvider{ + IDPConfigID: provider.IDPConfigID, + Type: idpProviderTypeToDomain(provider.IDPProviderType), + Name: provider.Name, + IDPConfigType: idpConfigTypeToDomain(provider.IDPConfigType), + StylingType: idpStylingTypeToDomain(provider.StylingType), + IDPState: idpStateToDomain(provider.IDPState), + } + providers[i] = p + } + return providers +} + +func idpProviderTypeToDomain(idpType IDPProviderType) domain.IdentityProviderType { + switch idpType { + case IDPProviderTypeSystem: + return domain.IdentityProviderTypeSystem + case IDPProviderTypeOrg: + return domain.IdentityProviderTypeOrg + default: + return domain.IdentityProviderTypeSystem + } +} + +func idpConfigTypeToDomain(idpType IdpConfigType) domain.IDPConfigType { + switch idpType { + case IDPConfigTypeOIDC: + return domain.IDPConfigTypeOIDC + case IDPConfigTypeSAML: + return domain.IDPConfigTypeSAML + default: + return domain.IDPConfigTypeOIDC + } +} + +func idpStylingTypeToDomain(stylingType IDPStylingType) domain.IDPConfigStylingType { + switch stylingType { + case IDPStylingTypeGoogle: + return domain.IDPConfigStylingTypeGoogle + default: + return domain.IDPConfigStylingTypeUnspecified + } +} + +func idpStateToDomain(state IDPConfigState) domain.IDPConfigState { + switch state { + case IDPConfigStateActive: + return domain.IDPConfigStateActive + case IDPConfigStateInactive: + return domain.IDPConfigStateInactive + case IDPConfigStateRemoved: + return domain.IDPConfigStateRemoved + default: + return domain.IDPConfigStateActive + } +} diff --git a/internal/iam/model/login_policy_view.go b/internal/iam/model/login_policy_view.go index 4bc6d51850..b28a3f62e2 100644 --- a/internal/iam/model/login_policy_view.go +++ b/internal/iam/model/login_policy_view.go @@ -1,7 +1,9 @@ package model import ( + "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/model" + "github.com/caos/zitadel/internal/v2/domain" "time" ) @@ -64,3 +66,57 @@ func (p *LoginPolicyView) HasMultiFactors() bool { } return true } + +func (p *LoginPolicyView) ToLoginPolicyDomain() *domain.LoginPolicy { + return &domain.LoginPolicy{ + ObjectRoot: models.ObjectRoot{ + AggregateID: p.AggregateID, + CreationDate: p.CreationDate, + ChangeDate: p.ChangeDate, + Sequence: p.Sequence, + }, + Default: p.Default, + AllowUsernamePassword: p.AllowUsernamePassword, + AllowRegister: p.AllowRegister, + AllowExternalIDP: p.AllowExternalIDP, + ForceMFA: p.ForceMFA, + PasswordlessType: passwordLessTypeToDomain(p.PasswordlessType), + SecondFactors: secondFactorsToDomain(p.SecondFactors), + MultiFactors: multiFactorsToDomain(p.MultiFactors), + } +} + +func passwordLessTypeToDomain(passwordless PasswordlessType) domain.PasswordlessType { + switch passwordless { + case PasswordlessTypeNotAllowed: + return domain.PasswordlessTypeNotAllowed + case PasswordlessTypeAllowed: + return domain.PasswordlessTypeAllowed + default: + return domain.PasswordlessTypeNotAllowed + } +} + +func secondFactorsToDomain(types []SecondFactorType) []domain.SecondFactorType { + secondfactors := make([]domain.SecondFactorType, len(types)) + for i, secondfactorType := range types { + switch secondfactorType { + case SecondFactorTypeU2F: + secondfactors[i] = domain.SecondFactorTypeU2F + case SecondFactorTypeOTP: + secondfactors[i] = domain.SecondFactorTypeOTP + } + } + return secondfactors +} + +func multiFactorsToDomain(types []MultiFactorType) []domain.MultiFactorType { + multifactors := make([]domain.MultiFactorType, len(types)) + for i, multifactorType := range types { + switch multifactorType { + case MultiFactorTypeU2FWithPIN: + multifactors[i] = domain.MultiFactorTypeU2FWithPIN + } + } + return multifactors +} diff --git a/internal/management/repository/eventsourcing/eventstore/user.go b/internal/management/repository/eventsourcing/eventstore/user.go index aa860d1bdd..b586d87504 100644 --- a/internal/management/repository/eventsourcing/eventstore/user.go +++ b/internal/management/repository/eventsourcing/eventstore/user.go @@ -9,7 +9,6 @@ import ( "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors" es_int "github.com/caos/zitadel/internal/eventstore" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" "github.com/caos/zitadel/internal/management/repository/eventsourcing/view" global_model "github.com/caos/zitadel/internal/model" org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing" @@ -194,26 +193,6 @@ func (repo *UserRepo) SearchMachineKeys(ctx context.Context, request *usr_model. return result, nil } -func (repo *UserRepo) AddMachineKey(ctx context.Context, key *usr_model.MachineKey) (*usr_model.MachineKey, error) { - return repo.UserEvents.AddMachineKey(ctx, key) -} - -func (repo *UserRepo) RemoveMachineKey(ctx context.Context, userID, keyID string) error { - return repo.UserEvents.RemoveMachineKey(ctx, userID, keyID) -} - -func (repo *UserRepo) ChangeUsername(ctx context.Context, userID, userName string) error { - orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if err != nil && errors.IsNotFound(err) { - orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID) - } - if err != nil { - return err - } - orgPolicyView := iam_es_model.OrgIAMViewToModel(orgPolicy) - return repo.UserEvents.ChangeUsername(ctx, userID, userName, orgPolicyView) -} - func (repo *UserRepo) EmailByID(ctx context.Context, userID string) (*usr_model.Email, error) { user, err := repo.UserByID(ctx, userID) if err != nil { diff --git a/internal/management/repository/eventsourcing/eventstore/user_grant.go b/internal/management/repository/eventsourcing/eventstore/user_grant.go index bf49bebe3c..e79e325e6a 100644 --- a/internal/management/repository/eventsourcing/eventstore/user_grant.go +++ b/internal/management/repository/eventsourcing/eventstore/user_grant.go @@ -27,6 +27,22 @@ func (repo *UserGrantRepo) UserGrantByID(ctx context.Context, grantID string) (* return model.UserGrantToModel(grant), nil } +func (repo *UserGrantRepo) UserGrantsByProjectID(ctx context.Context, projectID string) ([]*grant_model.UserGrantView, error) { + grants, err := repo.View.UserGrantsByProjectID(projectID) + if err != nil { + return nil, err + } + return model.UserGrantsToModel(grants), nil +} + +func (repo *UserGrantRepo) UserGrantsByUserID(ctx context.Context, userID string) ([]*grant_model.UserGrantView, error) { + grants, err := repo.View.UserGrantsByUserID(userID) + if err != nil { + return nil, err + } + return model.UserGrantsToModel(grants), nil +} + func (repo *UserGrantRepo) SearchUserGrants(ctx context.Context, request *grant_model.UserGrantSearchRequest) (*grant_model.UserGrantSearchResponse, error) { request.EnsureLimit(repo.SearchLimit) sequence, sequenceErr := repo.View.GetLatestUserGrantSequence("") diff --git a/internal/management/repository/user.go b/internal/management/repository/user.go index b1d19e6332..b545ecb6d1 100644 --- a/internal/management/repository/user.go +++ b/internal/management/repository/user.go @@ -25,8 +25,6 @@ type UserRepository interface { SearchMachineKeys(ctx context.Context, request *model.MachineKeySearchRequest) (*model.MachineKeySearchResponse, error) GetMachineKey(ctx context.Context, userID, keyID string) (*model.MachineKeyView, error) - AddMachineKey(ctx context.Context, key *model.MachineKey) (*model.MachineKey, error) - RemoveMachineKey(ctx context.Context, userID, keyID string) error EmailByID(ctx context.Context, userID string) (*model.Email, error) diff --git a/internal/management/repository/user_grant.go b/internal/management/repository/user_grant.go index 18b7b179d3..00714568a1 100644 --- a/internal/management/repository/user_grant.go +++ b/internal/management/repository/user_grant.go @@ -8,4 +8,6 @@ import ( type UserGrantRepository interface { UserGrantByID(ctx context.Context, grantID string) (*model.UserGrantView, error) SearchUserGrants(ctx context.Context, request *model.UserGrantSearchRequest) (*model.UserGrantSearchResponse, error) + UserGrantsByProjectID(ctx context.Context, projectID string) ([]*model.UserGrantView, error) + UserGrantsByUserID(ctx context.Context, userID string) ([]*model.UserGrantView, error) } diff --git a/internal/notification/notification.go b/internal/notification/notification.go index 631df76eeb..3fe5a8c947 100644 --- a/internal/notification/notification.go +++ b/internal/notification/notification.go @@ -5,6 +5,7 @@ import ( "github.com/caos/logging" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/notification/repository/eventsourcing" + "github.com/caos/zitadel/internal/v2/command" "github.com/rakyll/statik/fs" _ "github.com/caos/zitadel/internal/notification/statik" @@ -14,10 +15,10 @@ type Config struct { Repository eventsourcing.Config } -func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults) { +func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults, command *command.CommandSide) { statikFS, err := fs.NewWithNamespace("notification") logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start listener") - _, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults) + _, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command) logging.Log("MAIN-9uBxp").OnError(err).Panic("unable to start app") } diff --git a/internal/notification/repository/eventsourcing/handler/handler.go b/internal/notification/repository/eventsourcing/handler/handler.go index eb8035ed88..ef91b94256 100644 --- a/internal/notification/repository/eventsourcing/handler/handler.go +++ b/internal/notification/repository/eventsourcing/handler/handler.go @@ -1,6 +1,7 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/command" "net/http" "time" @@ -42,7 +43,7 @@ type EventstoreRepos struct { IAMEvents *iam_es.IAMEventstore } -func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es eventstore.Eventstore, repos EventstoreRepos, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) []query.Handler { +func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es eventstore.Eventstore, command *command.CommandSide, repos EventstoreRepos, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) []query.Handler { aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey) if err != nil { logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto") @@ -56,6 +57,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es ), newNotification( handler{view, bulkLimit, configs.cycleDuration("Notification"), errorCount, es}, + command, repos.UserEvents, systemDefaults, aesCrypto, diff --git a/internal/notification/repository/eventsourcing/handler/notification.go b/internal/notification/repository/eventsourcing/handler/notification.go index 0d37914e3a..ac22f914cf 100644 --- a/internal/notification/repository/eventsourcing/handler/notification.go +++ b/internal/notification/repository/eventsourcing/handler/notification.go @@ -3,6 +3,8 @@ package handler import ( "context" "encoding/json" + "github.com/caos/zitadel/internal/user/repository/view/model" + "github.com/caos/zitadel/internal/v2/command" "net/http" "time" @@ -32,6 +34,7 @@ const ( type Notification struct { handler + command *command.CommandSide userEvents *usr_event.UserEventstore systemDefaults sd.SystemDefaults AesCrypto crypto.EncryptionAlgorithm @@ -42,6 +45,7 @@ type Notification struct { func newNotification( handler handler, + command *command.CommandSide, userEvents *usr_event.UserEventstore, defaults sd.SystemDefaults, aesCrypto crypto.EncryptionAlgorithm, @@ -50,6 +54,7 @@ func newNotification( ) *Notification { h := &Notification{ handler: handler, + command: command, userEvents: userEvents, systemDefaults: defaults, i18n: translator, @@ -135,7 +140,7 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) { return err } - user, err := n.view.NotifyUserByID(event.AggregateID) + user, err := n.getUserByID(event.AggregateID) if err != nil { return err } @@ -143,7 +148,7 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) { if err != nil { return err } - return n.userEvents.InitCodeSent(getSetNotifyContextData(event.ResourceOwner), event.AggregateID) + return n.command.HumanInitCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID) } func (n *Notification) handlePasswordCode(event *models.Event) (err error) { @@ -163,7 +168,7 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) { return err } - user, err := n.view.NotifyUserByID(event.AggregateID) + user, err := n.getUserByID(event.AggregateID) if err != nil { return err } @@ -171,7 +176,7 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) { if err != nil { return err } - return n.userEvents.PasswordCodeSent(getSetNotifyContextData(event.ResourceOwner), event.AggregateID) + return n.command.PasswordCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID) } func (n *Notification) handleEmailVerificationCode(event *models.Event) (err error) { @@ -191,7 +196,7 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err return err } - user, err := n.view.NotifyUserByID(event.AggregateID) + user, err := n.getUserByID(event.AggregateID) if err != nil { return err } @@ -199,7 +204,7 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err if err != nil { return err } - return n.userEvents.EmailVerificationCodeSent(getSetNotifyContextData(event.ResourceOwner), event.AggregateID) + return n.command.HumanEmailVerificationCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID) } func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err error) { @@ -213,7 +218,7 @@ func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err err if err != nil || alreadyHandled { return nil } - user, err := n.view.NotifyUserByID(event.AggregateID) + user, err := n.getUserByID(event.AggregateID) if err != nil { return err } @@ -221,7 +226,7 @@ func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err err if err != nil { return err } - return n.userEvents.PhoneVerificationCodeSent(getSetNotifyContextData(event.ResourceOwner), event.AggregateID) + return n.command.HumanPhoneVerificationCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID) } func (n *Notification) handleDomainClaimed(event *models.Event) (err error) { @@ -234,7 +239,7 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) { logging.Log("HANDLE-Gghq2").WithError(err).Error("could not unmarshal event data") return caos_errs.ThrowInternal(err, "HANDLE-7hgj3", "could not unmarshal event") } - user, err := n.view.NotifyUserByID(event.AggregateID) + user, err := n.getUserByID(event.AggregateID) if err != nil { return err } @@ -242,7 +247,7 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) { if err != nil { return err } - return n.userEvents.DomainClaimedSent(getSetNotifyContextData(event.ResourceOwner), event.AggregateID) + return n.command.UserDomainClaimedSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID) } func (n *Notification) checkIfCodeAlreadyHandledOrExpired(event *models.Event, expiry time.Duration, eventTypes ...models.EventType) (bool, error) { @@ -306,3 +311,27 @@ func (n *Notification) getLabelPolicy(ctx context.Context) (*iam_model.LabelPoli } return iam_es_model.LabelPolicyViewToModel(policy), err } + +func (n *Notification) getUserByID(userID string) (*model.NotifyUser, error) { + user, usrErr := n.view.NotifyUserByID(userID) + if usrErr != nil && !caos_errs.IsNotFound(usrErr) { + return nil, usrErr + } + if user == nil { + user = &model.NotifyUser{} + } + events, err := n.getUserEvents(userID, user.Sequence) + if err != nil { + return user, usrErr + } + userCopy := *user + for _, event := range events { + if err := userCopy.AppendEvent(event); err != nil { + return user, nil + } + } + if userCopy.State == int32(model.UserStateDeleted) { + return nil, caos_errs.ThrowNotFound(nil, "EVENT-3n8fs", "Errors.User.NotFound") + } + return &userCopy, nil +} diff --git a/internal/notification/repository/eventsourcing/repository.go b/internal/notification/repository/eventsourcing/repository.go index cfdae2787f..548ea68297 100644 --- a/internal/notification/repository/eventsourcing/repository.go +++ b/internal/notification/repository/eventsourcing/repository.go @@ -2,6 +2,7 @@ package eventsourcing import ( es_iam "github.com/caos/zitadel/internal/iam/repository/eventsourcing" + "github.com/caos/zitadel/internal/v2/command" "net/http" sd "github.com/caos/zitadel/internal/config/systemdefaults" @@ -29,7 +30,7 @@ type EsRepository struct { spooler *es_spol.Spooler } -func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults) (*EsRepository, error) { +func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.CommandSide) (*EsRepository, error) { es, err := es_int.Start(conf.Eventstore) if err != nil { return nil, err @@ -65,7 +66,7 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults) ( return nil, err } eventstoreRepos := handler.EventstoreRepos{UserEvents: user, OrgEvents: org, IAMEvents: iam} - spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, eventstoreRepos, systemDefaults, translator, dir) + spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, command, eventstoreRepos, systemDefaults, translator, dir) return &EsRepository{ spool, diff --git a/internal/notification/repository/eventsourcing/spooler/spooler.go b/internal/notification/repository/eventsourcing/spooler/spooler.go index 3fe218f9dd..86255a6d46 100644 --- a/internal/notification/repository/eventsourcing/spooler/spooler.go +++ b/internal/notification/repository/eventsourcing/spooler/spooler.go @@ -2,6 +2,7 @@ package spooler import ( "database/sql" + "github.com/caos/zitadel/internal/v2/command" "net/http" sd "github.com/caos/zitadel/internal/config/systemdefaults" @@ -19,12 +20,12 @@ type SpoolerConfig struct { Handlers handler.Configs } -func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, sql *sql.DB, eventstoreRepos handler.EventstoreRepos, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) *spooler.Spooler { +func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, sql *sql.DB, command *command.CommandSide, eventstoreRepos handler.EventstoreRepos, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) *spooler.Spooler { spoolerConfig := spooler.Config{ Eventstore: es, Locker: &locker{dbClient: sql}, ConcurrentWorkers: c.ConcurrentWorkers, - ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, eventstoreRepos, systemDefaults, i18n, dir), + ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, eventstoreRepos, systemDefaults, i18n, dir), } spool := spoolerConfig.New() spool.Start() diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 6d72a857a6..07b935bca4 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -38,6 +38,9 @@ Errors: Address: NotFound: Addresse nicht gefunden NotChanged: Addresse wurde nicht geändert + Machine: + Key: + NotFound: Maschinen Key nicht gefunden NotHuman: Der Benutzer muss eine Person sein NotMachine: Der Benutzer muss technisch sein NotAllowedToLink: Der Benutzer darf nicht mit einem externen Login Provider verlinkt werden @@ -53,6 +56,7 @@ Errors: NotFound: Password nicht gefunden Empty: Passwort ist leer Invalid: Passwort ungültig + NotSet: Benutzer hat kein Passwort gesetzt PasswordComplexityPolicy: NotFound: Passwort Policy konnte nicht gefunden werden MinLength: Passwort ist zu kurz diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 9ba5001c48..eb99429d13 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -38,6 +38,9 @@ Errors: Address: NotFound: Address not found NotChanged: Address not changed + Machine: + Key: + NotFound: Machine key not found NotHuman: The User must be personal NotMachine: The User must be technical NotAllowedToLink: User is not allowed to link with external login provider @@ -53,6 +56,7 @@ Errors: NotFound: Passoword not found Empty: Password is empty Invalid: Passwort is invalid + NotSet: User has not set a password PasswordComplexityPolicy: NotFound: Password policy not found MinLength: Password is to short diff --git a/internal/ui/login/handler/auth_request.go b/internal/ui/login/handler/auth_request.go index f3c3d4b8d1..24e44eb606 100644 --- a/internal/ui/login/handler/auth_request.go +++ b/internal/ui/login/handler/auth_request.go @@ -1,17 +1,17 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" ) const ( queryAuthRequestID = "authRequestID" ) -func (l *Login) getAuthRequest(r *http.Request) (*model.AuthRequest, error) { +func (l *Login) getAuthRequest(r *http.Request) (*domain.AuthRequest, error) { authRequestID := r.FormValue(queryAuthRequestID) if authRequestID == "" { return nil, nil @@ -20,7 +20,7 @@ func (l *Login) getAuthRequest(r *http.Request) (*model.AuthRequest, error) { return l.authRepo.AuthRequestByID(r.Context(), authRequestID, userAgentID) } -func (l *Login) getAuthRequestAndParseData(r *http.Request, data interface{}) (*model.AuthRequest, error) { +func (l *Login) getAuthRequestAndParseData(r *http.Request, data interface{}) (*domain.AuthRequest, error) { authReq, err := l.getAuthRequest(r) if err != nil { return nil, err diff --git a/internal/ui/login/handler/callback_handler.go b/internal/ui/login/handler/callback_handler.go index a0209c68f6..1cdbab83e0 100644 --- a/internal/ui/login/handler/callback_handler.go +++ b/internal/ui/login/handler/callback_handler.go @@ -1,12 +1,11 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" - - "github.com/caos/zitadel/internal/auth_request/model" ) -func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { +func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { callback := l.oidcAuthCallbackURL + authReq.ID http.Redirect(w, r, callback, http.StatusFound) } diff --git a/internal/ui/login/handler/change_password_handler.go b/internal/ui/login/handler/change_password_handler.go index d85f537e08..88167dbe1a 100644 --- a/internal/ui/login/handler/change_password_handler.go +++ b/internal/ui/login/handler/change_password_handler.go @@ -1,10 +1,10 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" ) const ( @@ -26,7 +26,7 @@ func (l *Login) handleChangePassword(w http.ResponseWriter, r *http.Request) { return } userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - err = l.authRepo.ChangePassword(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.OldPassword, data.NewPassword, userAgentID) + err = l.command.ChangePassword(setContext(r.Context(), authReq.UserOrgID), authReq.UserOrgID, authReq.UserID, data.OldPassword, data.NewPassword, userAgentID) if err != nil { l.renderChangePassword(w, r, authReq, err) return @@ -34,7 +34,7 @@ func (l *Login) handleChangePassword(w http.ResponseWriter, r *http.Request) { l.renderChangePasswordDone(w, r, authReq) } -func (l *Login) renderChangePassword(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderChangePassword(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -63,7 +63,7 @@ func (l *Login) renderChangePassword(w http.ResponseWriter, r *http.Request, aut l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplChangePassword], data, nil) } -func (l *Login) renderChangePasswordDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { +func (l *Login) renderChangePasswordDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { var errType, errMessage string data := l.getUserData(r, authReq, "Password Change Done", errType, errMessage) l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplChangePasswordDone], data, nil) diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go index 1d7455e91f..c640319818 100644 --- a/internal/ui/login/handler/external_login_handler.go +++ b/internal/ui/login/handler/external_login_handler.go @@ -4,14 +4,11 @@ import ( "github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/rp" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/errors" caos_errors "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/models" iam_model "github.com/caos/zitadel/internal/iam/model" - org_model "github.com/caos/zitadel/internal/org/model" - usr_model "github.com/caos/zitadel/internal/user/model" + "github.com/caos/zitadel/internal/v2/domain" "net/http" "strings" "time" @@ -41,7 +38,7 @@ type externalNotFoundOptionData struct { baseData } -func (l *Login) handleExternalLoginStep(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, selectedIDPConfigID string) { +func (l *Login) handleExternalLoginStep(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, selectedIDPConfigID string) { for _, idp := range authReq.AllowedExternalIDPs { if idp.IDPConfigID == selectedIDPConfigID { l.handleIDP(w, r, authReq, selectedIDPConfigID) @@ -65,7 +62,7 @@ func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) { l.handleIDP(w, r, authReq, data.IDPConfigID) } -func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, selectedIDPConfigID string) { +func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, selectedIDPConfigID string) { idpConfig, err := l.getIDPConfigByID(r, selectedIDPConfigID) if err != nil { l.renderError(w, r, authReq, err) @@ -84,7 +81,7 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *model l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalLoginCallback) } -func (l *Login) handleOIDCAuthorize(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) { +func (l *Login) handleOIDCAuthorize(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) { provider := l.getRPConfig(w, r, authReq, idpConfig, callbackEndpoint) http.Redirect(w, r, rp.AuthURL(authReq.ID, provider, rp.WithPrompt(oidc.PromptSelectAccount)), http.StatusFound) } @@ -116,7 +113,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens) } -func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) rp.RelayingParty { +func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) rp.RelayingParty { oidcClientSecret, err := crypto.DecryptString(idpConfig.OIDCClientSecret, l.IDPConfigAesCrypto) if err != nil { l.renderError(w, r, authReq, err) @@ -130,9 +127,9 @@ func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *mod return provider } -func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) { +func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) { externalUser := l.mapTokenToLoginUser(tokens, idpConfig) - err := l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, userAgentID, externalUser, model.BrowserInfoFromRequest(r)) + err := l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, userAgentID, externalUser, domain.BrowserInfoFromRequest(r)) if err != nil { if errors.IsNotFound(err) { err = nil @@ -143,7 +140,7 @@ func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.R l.renderNextStep(w, r, authReq) } -func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -176,7 +173,7 @@ func (l *Login) handleExternalNotFoundOptionCheck(w http.ResponseWriter, r *http l.handleAutoRegister(w, r, authReq) } -func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { +func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { iam, err := l.authRepo.GetIAM(r.Context()) if err != nil { l.renderExternalNotFoundOption(w, r, authReq, err) @@ -184,13 +181,10 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR } resourceOwner := iam.GlobalOrgID - member := &org_model.OrgMember{ - ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID}, - Roles: []string{orgProjectCreatorRole}, - } + memberRoles := []string{domain.RoleOrgProjectCreator} if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != iam.GlobalOrgID { - member = nil + memberRoles = nil resourceOwner = authReq.RequestedOrgID } @@ -208,7 +202,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) user, externalIDP := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig) - err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, member, authReq.ID, userAgentID, resourceOwner, model.BrowserInfoFromRequest(r)) + err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, memberRoles, authReq.ID, userAgentID, resourceOwner, domain.BrowserInfoFromRequest(r)) if err != nil { l.renderExternalNotFoundOption(w, r, authReq, err) return @@ -216,7 +210,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR l.renderNextStep(w, r, authReq) } -func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) *model.ExternalUser { +func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) *domain.ExternalUser { displayName := tokens.IDTokenClaims.GetPreferredUsername() if displayName == "" && tokens.IDTokenClaims.GetEmail() != "" { displayName = tokens.IDTokenClaims.GetEmail() @@ -228,7 +222,7 @@ func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.ID } } - externalUser := &model.ExternalUser{ + externalUser := &domain.ExternalUser{ IDPConfigID: idpConfig.IDPConfigID, ExternalUserID: tokens.IDTokenClaims.GetSubject(), PreferredUsername: tokens.IDTokenClaims.GetPreferredUsername(), @@ -246,7 +240,7 @@ func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.ID } return externalUser } -func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *iam_model.OrgIAMPolicyView, linkingUser *model.ExternalUser, idpConfig *iam_model.IDPConfigView) (*usr_model.User, *usr_model.ExternalIDP) { +func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *iam_model.OrgIAMPolicyView, linkingUser *domain.ExternalUser, idpConfig *iam_model.IDPConfigView) (*domain.Human, *domain.ExternalIDP) { username := linkingUser.PreferredUsername switch idpConfig.OIDCUsernameMapping { case iam_model.OIDCMappingFieldEmail: @@ -262,23 +256,21 @@ func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *iam_model.OrgIAMPolicyV } } - user := &usr_model.User{ - UserName: username, - Human: &usr_model.Human{ - Profile: &usr_model.Profile{ - FirstName: linkingUser.FirstName, - LastName: linkingUser.LastName, - PreferredLanguage: linkingUser.PreferredLanguage, - NickName: linkingUser.NickName, - }, - Email: &usr_model.Email{ - EmailAddress: linkingUser.Email, - IsEmailVerified: linkingUser.IsEmailVerified, - }, + human := &domain.Human{ + Username: username, + Profile: &domain.Profile{ + FirstName: linkingUser.FirstName, + LastName: linkingUser.LastName, + PreferredLanguage: linkingUser.PreferredLanguage, + NickName: linkingUser.NickName, + }, + Email: &domain.Email{ + EmailAddress: linkingUser.Email, + IsEmailVerified: linkingUser.IsEmailVerified, }, } if linkingUser.Phone != "" { - user.Phone = &usr_model.Phone{ + human.Phone = &domain.Phone{ PhoneNumber: linkingUser.Phone, IsPhoneVerified: linkingUser.IsPhoneVerified, } @@ -292,10 +284,10 @@ func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *iam_model.OrgIAMPolicyV } } - externalIDP := &usr_model.ExternalIDP{ - IDPConfigID: idpConfig.IDPConfigID, - UserID: linkingUser.ExternalUserID, - DisplayName: displayName, + externalIDP := &domain.ExternalIDP{ + IDPConfigID: idpConfig.IDPConfigID, + ExternalUserID: linkingUser.ExternalUserID, + DisplayName: displayName, } - return user, externalIDP + return human, externalIDP } diff --git a/internal/ui/login/handler/external_register_handler.go b/internal/ui/login/handler/external_register_handler.go index 3d2834bb47..b1907df24a 100644 --- a/internal/ui/login/handler/external_register_handler.go +++ b/internal/ui/login/handler/external_register_handler.go @@ -1,20 +1,15 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" "strings" "github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/rp" - "golang.org/x/text/language" - http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" caos_errors "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/models" iam_model "github.com/caos/zitadel/internal/iam/model" - org_model "github.com/caos/zitadel/internal/org/model" - usr_model "github.com/caos/zitadel/internal/user/model" ) func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) { @@ -73,20 +68,17 @@ func (l *Login) handleExternalRegisterCallback(w http.ResponseWriter, r *http.Re l.handleExternalUserRegister(w, r, authReq, idpConfig, userAgentID, tokens) } -func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) { +func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) { iam, err := l.authRepo.GetIAM(r.Context()) if err != nil { l.renderRegisterOption(w, r, authReq, err) return } resourceOwner := iam.GlobalOrgID - member := &org_model.OrgMember{ - ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID}, - Roles: []string{orgProjectCreatorRole}, - } + memberRoles := []string{domain.RoleOrgProjectCreator} if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != iam.GlobalOrgID { - member = nil + memberRoles = nil resourceOwner = authReq.RequestedOrgID } orgIamPolicy, err := l.getOrgIamPolicy(r, resourceOwner) @@ -94,8 +86,8 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques l.renderRegisterOption(w, r, authReq, err) return } - user, externalIDP := l.mapTokenToLoginUserAndExternalIDP(orgIamPolicy, tokens, idpConfig) - _, err = l.authRepo.RegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, member, resourceOwner) + user, externalIDP := l.mapTokenToLoginHumanAndExternalIDP(orgIamPolicy, tokens, idpConfig) + _, err = l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, externalIDP, memberRoles) if err != nil { l.renderRegisterOption(w, r, authReq, err) return @@ -103,7 +95,7 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques l.renderNextStep(w, r, authReq) } -func (l *Login) mapTokenToLoginUserAndExternalIDP(orgIamPolicy *iam_model.OrgIAMPolicyView, tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) (*usr_model.User, *usr_model.ExternalIDP) { +func (l *Login) mapTokenToLoginHumanAndExternalIDP(orgIamPolicy *iam_model.OrgIAMPolicyView, tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) (*domain.Human, *domain.ExternalIDP) { username := tokens.IDTokenClaims.GetPreferredUsername() switch idpConfig.OIDCUsernameMapping { case iam_model.OIDCMappingFieldEmail: @@ -119,23 +111,22 @@ func (l *Login) mapTokenToLoginUserAndExternalIDP(orgIamPolicy *iam_model.OrgIAM } } - user := &usr_model.User{ - UserName: username, - Human: &usr_model.Human{ - Profile: &usr_model.Profile{ - FirstName: tokens.IDTokenClaims.GetGivenName(), - LastName: tokens.IDTokenClaims.GetFamilyName(), - PreferredLanguage: language.Tag(tokens.IDTokenClaims.GetLocale()), - NickName: tokens.IDTokenClaims.GetNickname(), - }, - Email: &usr_model.Email{ - EmailAddress: tokens.IDTokenClaims.GetEmail(), - IsEmailVerified: tokens.IDTokenClaims.IsEmailVerified(), - }, + human := &domain.Human{ + Username: username, + Profile: &domain.Profile{ + FirstName: tokens.IDTokenClaims.GetGivenName(), + LastName: tokens.IDTokenClaims.GetFamilyName(), + PreferredLanguage: tokens.IDTokenClaims.GetLocale(), + NickName: tokens.IDTokenClaims.GetNickname(), + }, + Email: &domain.Email{ + EmailAddress: tokens.IDTokenClaims.GetEmail(), + IsEmailVerified: tokens.IDTokenClaims.IsEmailVerified(), }, } + if tokens.IDTokenClaims.GetPhoneNumber() != "" { - user.Phone = &usr_model.Phone{ + human.Phone = &domain.Phone{ PhoneNumber: tokens.IDTokenClaims.GetPhoneNumber(), IsPhoneVerified: tokens.IDTokenClaims.IsPhoneNumberVerified(), } @@ -149,10 +140,10 @@ func (l *Login) mapTokenToLoginUserAndExternalIDP(orgIamPolicy *iam_model.OrgIAM } } - externalIDP := &usr_model.ExternalIDP{ - IDPConfigID: idpConfig.IDPConfigID, - UserID: tokens.IDTokenClaims.GetSubject(), - DisplayName: displayName, + externalIDP := &domain.ExternalIDP{ + IDPConfigID: idpConfig.IDPConfigID, + ExternalUserID: tokens.IDTokenClaims.GetSubject(), + DisplayName: displayName, } - return user, externalIDP + return human, externalIDP } diff --git a/internal/ui/login/handler/init_password_handler.go b/internal/ui/login/handler/init_password_handler.go index 21d9cfe080..7348babf15 100644 --- a/internal/ui/login/handler/init_password_handler.go +++ b/internal/ui/login/handler/init_password_handler.go @@ -1,10 +1,10 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/errors" ) @@ -58,18 +58,18 @@ func (l *Login) handleInitPasswordCheck(w http.ResponseWriter, r *http.Request) l.checkPWCode(w, r, authReq, data, nil) } -func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *initPasswordFormData, err error) { +func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *initPasswordFormData, err error) { if data.Password != data.PasswordConfirm { err := errors.ThrowInvalidArgument(nil, "VIEW-KaGue", "Errors.User.Password.ConfirmationWrong") l.renderInitPassword(w, r, authReq, data.UserID, data.Code, err) return } - userOrg := login + userOrg := "" if authReq != nil { userOrg = authReq.UserOrgID } userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - err = l.authRepo.SetPassword(setContext(r.Context(), userOrg), data.UserID, data.Code, data.Password, userAgentID) + err = l.command.SetPassword(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password, userAgentID) if err != nil { l.renderInitPassword(w, r, authReq, data.UserID, "", err) return @@ -77,16 +77,25 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *mod l.renderInitPasswordDone(w, r, authReq) } -func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { +func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { + if authReq == nil { + l.renderError(w, r, nil, errors.ThrowInternal(nil, "LOGIN-8sn7s", "Errors.AuthRequest.NotFound")) + return + } userOrg := login if authReq != nil { userOrg = authReq.UserOrgID } - err := l.authRepo.RequestPasswordReset(setContext(r.Context(), userOrg), authReq.LoginName) + user, err := l.authRepo.UserByLoginName(setContext(r.Context(), userOrg), authReq.LoginName) + if err != nil { + l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) + return + } + err = l.command.RequestSetPassword(setContext(r.Context(), userOrg), user.ID, user.ResourceOwner, domain.NotificationTypeEmail) l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) } -func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID, code string, err error) { +func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, code string, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -121,7 +130,7 @@ func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authR l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitPassword], data, nil) } -func (l *Login) renderInitPasswordDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { +func (l *Login) renderInitPasswordDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { data := l.getUserData(r, authReq, "Password Init Done", "", "") l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitPasswordDone], data, nil) } diff --git a/internal/ui/login/handler/init_user_handler.go b/internal/ui/login/handler/init_user_handler.go index 862666e089..94e9485c41 100644 --- a/internal/ui/login/handler/init_user_handler.go +++ b/internal/ui/login/handler/init_user_handler.go @@ -1,10 +1,10 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" "strconv" - "github.com/caos/zitadel/internal/auth_request/model" caos_errs "github.com/caos/zitadel/internal/errors" ) @@ -62,17 +62,17 @@ func (l *Login) handleInitUserCheck(w http.ResponseWriter, r *http.Request) { l.checkUserInitCode(w, r, authReq, data, nil) } -func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *initUserFormData, err error) { +func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *initUserFormData, err error) { if data.Password != data.PasswordConfirm { err := caos_errs.ThrowInvalidArgument(nil, "VIEW-fsdfd", "Errors.User.Password.ConfirmationWrong") l.renderInitUser(w, r, authReq, data.UserID, data.Code, data.PasswordSet, err) return } - userOrgID := login + userOrgID := "" if authReq != nil { userOrgID = authReq.UserOrgID } - err = l.authRepo.VerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, data.Code, data.Password) + err = l.command.HumanVerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, userOrgID, data.Code, data.Password) if err != nil { l.renderInitUser(w, r, authReq, data.UserID, "", data.PasswordSet, err) return @@ -80,16 +80,16 @@ func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authRe l.renderInitUserDone(w, r, authReq) } -func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID string, showPassword bool) { - userOrgID := login +func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID string, showPassword bool) { + userOrgID := "" if authReq != nil { userOrgID = authReq.UserOrgID } - err := l.authRepo.ResendInitVerificationMail(setContext(r.Context(), userOrgID), userID) + err := l.command.ResendInitialMail(setContext(r.Context(), userOrgID), userID, "", userOrgID) l.renderInitUser(w, r, authReq, userID, "", showPassword, err) } -func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID, code string, passwordSet bool, err error) { +func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, code string, passwordSet bool, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -124,7 +124,7 @@ func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq * l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitUser], data, nil) } -func (l *Login) renderInitUserDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { +func (l *Login) renderInitUserDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { data := l.getUserData(r, authReq, "User Init Done", "", "") l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitUserDone], data, nil) } diff --git a/internal/ui/login/handler/link_users_handler.go b/internal/ui/login/handler/link_users_handler.go index 62346cf4ad..c1ca76a934 100644 --- a/internal/ui/login/handler/link_users_handler.go +++ b/internal/ui/login/handler/link_users_handler.go @@ -2,22 +2,21 @@ package handler import ( http_mw "github.com/caos/zitadel/internal/api/http/middleware" + "github.com/caos/zitadel/internal/v2/domain" "net/http" - - "github.com/caos/zitadel/internal/auth_request/model" ) const ( tmplLinkUsersDone = "linkusersdone" ) -func (l *Login) linkUsers(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) linkUsers(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - err = l.authRepo.LinkExternalUsers(setContext(r.Context(), authReq.UserOrgID), authReq.ID, userAgentID, model.BrowserInfoFromRequest(r)) + err = l.authRepo.LinkExternalUsers(setContext(r.Context(), authReq.UserOrgID), authReq.ID, userAgentID, domain.BrowserInfoFromRequest(r)) l.renderLinkUsersDone(w, r, authReq, err) } -func (l *Login) renderLinkUsersDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderLinkUsersDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage string data := l.getUserData(r, authReq, "Linking Users Done", errType, errMessage) l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplLinkUsersDone], data, nil) diff --git a/internal/ui/login/handler/login_handler.go b/internal/ui/login/handler/login_handler.go index 7c68ce0ddb..bb7d3c2a66 100644 --- a/internal/ui/login/handler/login_handler.go +++ b/internal/ui/login/handler/login_handler.go @@ -1,10 +1,10 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" ) const ( @@ -62,7 +62,7 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) { l.renderNextStep(w, r, authReq) } -func (l *Login) renderLogin(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderLogin(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) diff --git a/internal/ui/login/handler/mail_verify_handler.go b/internal/ui/login/handler/mail_verify_handler.go index 3e95714add..58f81f96c4 100644 --- a/internal/ui/login/handler/mail_verify_handler.go +++ b/internal/ui/login/handler/mail_verify_handler.go @@ -1,9 +1,8 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" - - "github.com/caos/zitadel/internal/auth_request/model" ) const ( @@ -47,21 +46,21 @@ func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Reque l.checkMailCode(w, r, authReq, data.UserID, data.Code) return } - userOrg := login + userOrg := "" if authReq != nil { userOrg = authReq.UserOrgID } - err = l.authRepo.ResendEmailVerificationMail(setContext(r.Context(), userOrg), data.UserID) + err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg) l.renderMailVerification(w, r, authReq, data.UserID, err) } -func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID, code string) { - userOrg := login +func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, code string) { + userOrg := "" if authReq != nil { userID = authReq.UserID userOrg = authReq.UserOrgID } - err := l.authRepo.VerifyEmail(setContext(r.Context(), userOrg), userID, code) + err := l.command.VerifyHumanEmail(setContext(r.Context(), userOrg), userID, code, userOrg) if err != nil { l.renderMailVerification(w, r, authReq, userID, err) return @@ -69,7 +68,7 @@ func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *m l.renderMailVerified(w, r, authReq) } -func (l *Login) renderMailVerification(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID string, err error) { +func (l *Login) renderMailVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID string, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -85,7 +84,7 @@ func (l *Login) renderMailVerification(w http.ResponseWriter, r *http.Request, a l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMailVerification], data, nil) } -func (l *Login) renderMailVerified(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { +func (l *Login) renderMailVerified(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { data := mailVerificationData{ baseData: l.getBaseData(r, authReq, "Mail Verified", "", ""), profileData: l.getProfileData(authReq), diff --git a/internal/ui/login/handler/mfa_init_done_handler.go b/internal/ui/login/handler/mfa_init_done_handler.go index 741a010677..01509935ac 100644 --- a/internal/ui/login/handler/mfa_init_done_handler.go +++ b/internal/ui/login/handler/mfa_init_done_handler.go @@ -1,9 +1,8 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" - - "github.com/caos/zitadel/internal/auth_request/model" ) const ( @@ -13,7 +12,7 @@ const ( type mfaInitDoneData struct { } -func (l *Login) renderMFAInitDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaDoneData) { +func (l *Login) renderMFAInitDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *mfaDoneData) { var errType, errMessage string data.baseData = l.getBaseData(r, authReq, "MFA Init Done", errType, errMessage) data.profileData = l.getProfileData(authReq) diff --git a/internal/ui/login/handler/mfa_init_u2f.go b/internal/ui/login/handler/mfa_init_u2f.go index 1e5d64f08f..370d062413 100644 --- a/internal/ui/login/handler/mfa_init_u2f.go +++ b/internal/ui/login/handler/mfa_init_u2f.go @@ -2,11 +2,11 @@ package handler import ( "encoding/base64" + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/auth_request/model" - user_model "github.com/caos/zitadel/internal/user/model" ) const ( @@ -18,11 +18,11 @@ type u2fInitData struct { MFAType model.MFAType } -func (l *Login) renderRegisterU2F(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderRegisterU2F(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage, credentialData string - var u2f *user_model.WebAuthNToken + var u2f *domain.WebAuthNToken if err == nil { - u2f, err = l.authRepo.AddMFAU2F(setContext(r.Context(), authReq.UserOrgID), authReq.UserID) + u2f, err = l.command.HumanAddU2FSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, true) } if err != nil { errMessage = l.getErrorMessage(r, err) @@ -54,12 +54,12 @@ func (l *Login) handleRegisterU2F(w http.ResponseWriter, r *http.Request) { } userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - if err = l.authRepo.VerifyMFAU2FSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Name, userAgentID, credData); err != nil { + if err = l.command.HumanVerifyU2FSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, data.Name, userAgentID, credData); err != nil { l.renderRegisterU2F(w, r, authReq, err) return } done := &mfaDoneData{ - MFAType: model.MFATypeU2F, + MFAType: domain.MFATypeU2F, } l.renderMFAInitDone(w, r, authReq, done) } diff --git a/internal/ui/login/handler/mfa_init_verify_handler.go b/internal/ui/login/handler/mfa_init_verify_handler.go index 92ff320541..888b4b828f 100644 --- a/internal/ui/login/handler/mfa_init_verify_handler.go +++ b/internal/ui/login/handler/mfa_init_verify_handler.go @@ -2,13 +2,13 @@ package handler import ( "bytes" + "github.com/caos/zitadel/internal/v2/domain" "net/http" svg "github.com/ajstarks/svgo" "github.com/boombuler/barcode/qr" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/qrcode" ) @@ -17,10 +17,10 @@ const ( ) type mfaInitVerifyData struct { - MFAType model.MFAType `schema:"mfaType"` - Code string `schema:"code"` - URL string `schema:"url"` - Secret string `schema:"secret"` + MFAType domain.MFAType `schema:"mfaType"` + Code string `schema:"code"` + URL string `schema:"url"` + Secret string `schema:"secret"` } func (l *Login) handleMFAInitVerify(w http.ResponseWriter, r *http.Request) { @@ -32,7 +32,7 @@ func (l *Login) handleMFAInitVerify(w http.ResponseWriter, r *http.Request) { } var verifyData *mfaVerifyData switch data.MFAType { - case model.MFATypeOTP: + case domain.MFATypeOTP: verifyData = l.handleOTPVerify(w, r, authReq, data) } @@ -47,9 +47,9 @@ func (l *Login) handleMFAInitVerify(w http.ResponseWriter, r *http.Request) { l.renderMFAInitDone(w, r, authReq, done) } -func (l *Login) handleOTPVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaInitVerifyData) *mfaVerifyData { +func (l *Login) handleOTPVerify(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *mfaInitVerifyData) *mfaVerifyData { userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - err := l.authRepo.VerifyMFAOTPSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Code, userAgentID) + err := l.command.HumanCheckMFAOTPSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Code, userAgentID, authReq.UserOrgID) if err == nil { return nil } @@ -64,14 +64,14 @@ func (l *Login) handleOTPVerify(w http.ResponseWriter, r *http.Request, authReq return mfadata } -func (l *Login) renderMFAInitVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData, err error) { +func (l *Login) renderMFAInitVerify(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *mfaVerifyData, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) } data.baseData = l.getBaseData(r, authReq, "MFA Init Verify", errType, errMessage) data.profileData = l.getProfileData(authReq) - if data.MFAType == model.MFATypeOTP { + if data.MFAType == domain.MFATypeOTP { code, err := generateQrCode(data.otpData.Url) if err == nil { data.otpData.QrCode = code diff --git a/internal/ui/login/handler/mfa_prompt_handler.go b/internal/ui/login/handler/mfa_prompt_handler.go index 32a32a00db..e42508c907 100644 --- a/internal/ui/login/handler/mfa_prompt_handler.go +++ b/internal/ui/login/handler/mfa_prompt_handler.go @@ -1,9 +1,9 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" - "github.com/caos/zitadel/internal/auth_request/model" caos_errs "github.com/caos/zitadel/internal/errors" ) @@ -12,8 +12,8 @@ const ( ) type mfaPromptData struct { - MFAProvider model.MFAType `schema:"provider"` - Skip bool `schema:"skip"` + MFAProvider domain.MFAType `schema:"provider"` + Skip bool `schema:"skip"` } func (l *Login) handleMFAPrompt(w http.ResponseWriter, r *http.Request) { @@ -29,7 +29,7 @@ func (l *Login) handleMFAPrompt(w http.ResponseWriter, r *http.Request) { l.handleMFACreation(w, r, authReq, mfaVerifyData) return } - err = l.authRepo.SkipMFAInit(setContext(r.Context(), authReq.UserOrgID), authReq.UserID) + err = l.command.HumanSkipMFAInit(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID) if err != nil { l.renderError(w, r, authReq, err) return @@ -48,7 +48,7 @@ func (l *Login) handleMFAPromptSelection(w http.ResponseWriter, r *http.Request) l.renderNextStep(w, r, authReq) } -func (l *Login) renderMFAPrompt(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, mfaPromptData *model.MFAPromptStep, err error) { +func (l *Login) renderMFAPrompt(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, mfaPromptData *domain.MFAPromptStep, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -76,20 +76,20 @@ func (l *Login) renderMFAPrompt(w http.ResponseWriter, r *http.Request, authReq l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMFAPrompt], data, nil) } -func (l *Login) handleMFACreation(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData) { +func (l *Login) handleMFACreation(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *mfaVerifyData) { switch data.MFAType { - case model.MFATypeOTP: + case domain.MFATypeOTP: l.handleOTPCreation(w, r, authReq, data) return - case model.MFATypeU2F: + case domain.MFATypeU2F: l.renderRegisterU2F(w, r, authReq, nil) return } l.renderError(w, r, authReq, caos_errs.ThrowPreconditionFailed(nil, "APP-Or3HO", "Errors.User.MFA.NoProviders")) } -func (l *Login) handleOTPCreation(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData) { - otp, err := l.authRepo.AddMFAOTP(setContext(r.Context(), authReq.UserOrgID), authReq.UserID) +func (l *Login) handleOTPCreation(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *mfaVerifyData) { + otp, err := l.command.AddHumanOTP(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID) if err != nil { l.renderError(w, r, authReq, err) return diff --git a/internal/ui/login/handler/mfa_verify_handler.go b/internal/ui/login/handler/mfa_verify_handler.go index 3d284e01aa..fee297b963 100644 --- a/internal/ui/login/handler/mfa_verify_handler.go +++ b/internal/ui/login/handler/mfa_verify_handler.go @@ -1,6 +1,7 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" @@ -25,7 +26,7 @@ func (l *Login) handleMFAVerify(w http.ResponseWriter, r *http.Request) { } if data.MFAType == model.MFATypeOTP { userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - err = l.authRepo.VerifyMFAOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Code, userAgentID, model.BrowserInfoFromRequest(r)) + err = l.authRepo.VerifyMFAOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, authReq.UserOrgID, data.Code, userAgentID, domain.BrowserInfoFromRequest(r)) } if err != nil { l.renderError(w, r, authReq, err) @@ -34,7 +35,7 @@ func (l *Login) handleMFAVerify(w http.ResponseWriter, r *http.Request) { l.renderNextStep(w, r, authReq) } -func (l *Login) renderMFAVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, verificationStep *model.MFAVerificationStep, err error) { +func (l *Login) renderMFAVerify(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, verificationStep *domain.MFAVerificationStep, err error) { if verificationStep == nil { l.renderError(w, r, authReq, err) return @@ -43,7 +44,7 @@ func (l *Login) renderMFAVerify(w http.ResponseWriter, r *http.Request, authReq l.renderMFAVerifySelected(w, r, authReq, verificationStep, provider, err) } -func (l *Login) renderMFAVerifySelected(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, verificationStep *model.MFAVerificationStep, selectedProvider model.MFAType, err error) { +func (l *Login) renderMFAVerifySelected(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, verificationStep *domain.MFAVerificationStep, selectedProvider domain.MFAType, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -54,12 +55,12 @@ func (l *Login) renderMFAVerifySelected(w http.ResponseWriter, r *http.Request, return } switch selectedProvider { - case model.MFATypeU2F: - l.renderU2FVerification(w, r, authReq, removeSelectedProviderFromList(verificationStep.MFAProviders, model.MFATypeU2F), nil) + case domain.MFATypeU2F: + l.renderU2FVerification(w, r, authReq, removeSelectedProviderFromList(verificationStep.MFAProviders, domain.MFATypeU2F), nil) return - case model.MFATypeOTP: - data.MFAProviders = removeSelectedProviderFromList(verificationStep.MFAProviders, model.MFATypeOTP) - data.SelectedMFAProvider = model.MFATypeOTP + case domain.MFATypeOTP: + data.MFAProviders = removeSelectedProviderFromList(verificationStep.MFAProviders, domain.MFATypeOTP) + data.SelectedMFAProvider = domain.MFATypeOTP default: l.renderError(w, r, authReq, err) return @@ -67,7 +68,7 @@ func (l *Login) renderMFAVerifySelected(w http.ResponseWriter, r *http.Request, l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMFAVerify], data, nil) } -func removeSelectedProviderFromList(providers []model.MFAType, selected model.MFAType) []model.MFAType { +func removeSelectedProviderFromList(providers []domain.MFAType, selected domain.MFAType) []domain.MFAType { for i := len(providers) - 1; i >= 0; i-- { if providers[i] == selected { copy(providers[i:], providers[i+1:]) diff --git a/internal/ui/login/handler/mfa_verify_u2f_handler.go b/internal/ui/login/handler/mfa_verify_u2f_handler.go index 1ac79846a5..d8261b2814 100644 --- a/internal/ui/login/handler/mfa_verify_u2f_handler.go +++ b/internal/ui/login/handler/mfa_verify_u2f_handler.go @@ -2,11 +2,10 @@ package handler import ( "encoding/base64" + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" - user_model "github.com/caos/zitadel/internal/user/model" ) const ( @@ -15,21 +14,21 @@ const ( type mfaU2FData struct { webAuthNData - MFAProviders []model.MFAType - SelectedProvider model.MFAType + MFAProviders []domain.MFAType + SelectedProvider domain.MFAType } type mfaU2FFormData struct { webAuthNFormData - SelectedProvider model.MFAType `schema:"provider"` + SelectedProvider domain.MFAType `schema:"provider"` } -func (l *Login) renderU2FVerification(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, providers []model.MFAType, err error) { +func (l *Login) renderU2FVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, providers []domain.MFAType, err error) { var errType, errMessage, credentialData string - var webAuthNLogin *user_model.WebAuthNLogin + var webAuthNLogin *domain.WebAuthNLogin if err == nil { userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - webAuthNLogin, err = l.authRepo.BeginMFAU2FLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.ID, userAgentID) + webAuthNLogin, err = l.authRepo.BeginMFAU2FLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID) } if err != nil { errMessage = l.getErrorMessage(r, err) @@ -55,7 +54,7 @@ func (l *Login) handleU2FVerification(w http.ResponseWriter, r *http.Request) { l.renderError(w, r, authReq, err) return } - step, ok := authReq.PossibleSteps[0].(*model.MFAVerificationStep) + step, ok := authReq.PossibleSteps[0].(*domain.MFAVerificationStep) if !ok { l.renderError(w, r, authReq, err) return @@ -70,7 +69,7 @@ func (l *Login) handleU2FVerification(w http.ResponseWriter, r *http.Request) { return } userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - err = l.authRepo.VerifyMFAU2F(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.ID, userAgentID, credData, model.BrowserInfoFromRequest(r)) + err = l.authRepo.VerifyMFAU2F(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID, credData, domain.BrowserInfoFromRequest(r)) if err != nil { l.renderU2FVerification(w, r, authReq, step.MFAProviders, err) return diff --git a/internal/ui/login/handler/password_handler.go b/internal/ui/login/handler/password_handler.go index 971f50d18d..5cfd0a6a38 100644 --- a/internal/ui/login/handler/password_handler.go +++ b/internal/ui/login/handler/password_handler.go @@ -1,10 +1,10 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" ) const ( @@ -15,7 +15,7 @@ type passwordFormData struct { Password string `schema:"password"` } -func (l *Login) renderPassword(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderPassword(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -32,7 +32,7 @@ func (l *Login) handlePasswordCheck(w http.ResponseWriter, r *http.Request) { return } userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - err = l.authRepo.VerifyPassword(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Password, userAgentID, model.BrowserInfoFromRequest(r)) + err = l.authRepo.VerifyPassword(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, authReq.UserOrgID, data.Password, userAgentID, domain.BrowserInfoFromRequest(r)) if err != nil { l.renderPassword(w, r, authReq, err) return diff --git a/internal/ui/login/handler/password_reset_handler.go b/internal/ui/login/handler/password_reset_handler.go index 524c1094e5..359598491c 100644 --- a/internal/ui/login/handler/password_reset_handler.go +++ b/internal/ui/login/handler/password_reset_handler.go @@ -1,9 +1,8 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" - - "github.com/caos/zitadel/internal/auth_request/model" ) const ( @@ -16,11 +15,16 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) { l.renderError(w, r, authReq, err) return } - err = l.authRepo.RequestPasswordReset(setContext(r.Context(), authReq.UserOrgID), authReq.LoginName) + user, err := l.authRepo.UserByLoginName(setContext(r.Context(), authReq.UserOrgID), authReq.LoginName) + if err != nil { + l.renderPasswordResetDone(w, r, authReq, err) + return + } + err = l.command.RequestSetPassword(setContext(r.Context(), authReq.UserOrgID), user.ID, authReq.UserOrgID, domain.NotificationTypeEmail) l.renderPasswordResetDone(w, r, authReq, err) } -func (l *Login) renderPasswordResetDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderPasswordResetDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) diff --git a/internal/ui/login/handler/passwordless_login_handler.go b/internal/ui/login/handler/passwordless_login_handler.go index 7523e3d1f6..597ea7c325 100644 --- a/internal/ui/login/handler/passwordless_login_handler.go +++ b/internal/ui/login/handler/passwordless_login_handler.go @@ -2,11 +2,10 @@ package handler import ( "encoding/base64" + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" - user_model "github.com/caos/zitadel/internal/user/model" ) const ( @@ -23,12 +22,12 @@ type passwordlessFormData struct { PasswordLogin bool `schema:"passwordlogin"` } -func (l *Login) renderPasswordlessVerification(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderPasswordlessVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage, credentialData string - var webAuthNLogin *user_model.WebAuthNLogin + var webAuthNLogin *domain.WebAuthNLogin if err == nil { userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - webAuthNLogin, err = l.authRepo.BeginPasswordlessLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.ID, userAgentID) + webAuthNLogin, err = l.authRepo.BeginPasswordlessLogin(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID) } if err != nil { errMessage = l.getErrorMessage(r, err) @@ -67,7 +66,7 @@ func (l *Login) handlePasswordlessVerification(w http.ResponseWriter, r *http.Re return } userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - err = l.authRepo.VerifyPasswordless(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.ID, userAgentID, credData, model.BrowserInfoFromRequest(r)) + err = l.authRepo.VerifyPasswordless(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID, credData, domain.BrowserInfoFromRequest(r)) if err != nil { l.renderPasswordlessVerification(w, r, authReq, err) return diff --git a/internal/ui/login/handler/register_handler.go b/internal/ui/login/handler/register_handler.go index ffb9d91731..890956a5a0 100644 --- a/internal/ui/login/handler/register_handler.go +++ b/internal/ui/login/handler/register_handler.go @@ -1,20 +1,16 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" "golang.org/x/text/language" - "github.com/caos/zitadel/internal/auth_request/model" caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/models" - org_model "github.com/caos/zitadel/internal/org/model" - usr_model "github.com/caos/zitadel/internal/user/model" ) const ( - tmplRegister = "register" - orgProjectCreatorRole = "ORG_PROJECT_CREATOR" + tmplRegister = "register" ) type registerFormData struct { @@ -68,15 +64,13 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) { } resourceOwner := iam.GlobalOrgID - member := &org_model.OrgMember{ - ObjectRoot: models.ObjectRoot{AggregateID: iam.GlobalOrgID}, - Roles: []string{orgProjectCreatorRole}, - } + memberRoles := []string{domain.RoleOrgProjectCreator} + if authRequest.RequestedOrgID != "" && authRequest.RequestedOrgID != iam.GlobalOrgID { - member = nil + memberRoles = nil resourceOwner = authRequest.RequestedOrgID } - user, err := l.authRepo.Register(setContext(r.Context(), resourceOwner), data.toUserModel(), member, resourceOwner) + user, err := l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, data.toHumanDomain(), nil, memberRoles) if err != nil { l.renderRegister(w, r, authRequest, data, err) return @@ -89,7 +83,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) { l.renderNextStep(w, r, authRequest) } -func (l *Login) renderRegister(w http.ResponseWriter, r *http.Request, authRequest *model.AuthRequest, formData *registerFormData, err error) { +func (l *Login) renderRegister(w http.ResponseWriter, r *http.Request, authRequest *domain.AuthRequest, formData *registerFormData, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -142,21 +136,19 @@ func (l *Login) renderRegister(w http.ResponseWriter, r *http.Request, authReque l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplRegister], data, funcs) } -func (d registerFormData) toUserModel() *usr_model.User { - return &usr_model.User{ - Human: &usr_model.Human{ - Profile: &usr_model.Profile{ - FirstName: d.Firstname, - LastName: d.Lastname, - PreferredLanguage: language.Make(d.Language), - Gender: usr_model.Gender(d.Gender), - }, - Password: &usr_model.Password{ - SecretString: d.Password, - }, - Email: &usr_model.Email{ - EmailAddress: d.Email, - }, +func (d registerFormData) toHumanDomain() *domain.Human { + return &domain.Human{ + Profile: &domain.Profile{ + FirstName: d.Firstname, + LastName: d.Lastname, + PreferredLanguage: language.Make(d.Language), + Gender: domain.Gender(d.Gender), + }, + Password: &domain.Password{ + SecretString: d.Password, + }, + Email: &domain.Email{ + EmailAddress: d.Email, }, } } diff --git a/internal/ui/login/handler/register_option_handler.go b/internal/ui/login/handler/register_option_handler.go index 254b258b4f..c3ad5e8399 100644 --- a/internal/ui/login/handler/register_option_handler.go +++ b/internal/ui/login/handler/register_option_handler.go @@ -1,7 +1,7 @@ package handler import ( - "github.com/caos/zitadel/internal/auth_request/model" + "github.com/caos/zitadel/internal/v2/domain" "net/http" ) @@ -27,7 +27,7 @@ func (l *Login) handleRegisterOption(w http.ResponseWriter, r *http.Request) { l.renderRegisterOption(w, r, authRequest, nil) } -func (l *Login) renderRegisterOption(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderRegisterOption(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) diff --git a/internal/ui/login/handler/register_org_handler.go b/internal/ui/login/handler/register_org_handler.go index ea2f7a739d..aebdaba3bb 100644 --- a/internal/ui/login/handler/register_org_handler.go +++ b/internal/ui/login/handler/register_org_handler.go @@ -1,10 +1,10 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" auth_model "github.com/caos/zitadel/internal/auth/model" - "github.com/caos/zitadel/internal/auth_request/model" caos_errs "github.com/caos/zitadel/internal/errors" org_model "github.com/caos/zitadel/internal/org/model" usr_model "github.com/caos/zitadel/internal/user/model" @@ -78,7 +78,7 @@ func (l *Login) handleRegisterOrgCheck(w http.ResponseWriter, r *http.Request) { l.renderNextStep(w, r, authRequest) } -func (l *Login) renderRegisterOrg(w http.ResponseWriter, r *http.Request, authRequest *model.AuthRequest, formData *registerOrgFormData, err error) { +func (l *Login) renderRegisterOrg(w http.ResponseWriter, r *http.Request, authRequest *domain.AuthRequest, formData *registerOrgFormData, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) diff --git a/internal/ui/login/handler/renderer.go b/internal/ui/login/handler/renderer.go index 6dbf8d2dce..486d858a1d 100644 --- a/internal/ui/login/handler/renderer.go +++ b/internal/ui/login/handler/renderer.go @@ -3,6 +3,7 @@ package handler import ( "errors" "fmt" + "github.com/caos/zitadel/internal/v2/domain" "html/template" "net/http" "path" @@ -15,7 +16,6 @@ import ( "github.com/caos/zitadel/internal/auth_request/model" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/i18n" - iam_model "github.com/caos/zitadel/internal/iam/model" "github.com/caos/zitadel/internal/renderer" ) @@ -153,7 +153,7 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str "hasExternalLogin": func() bool { return false }, - "idpProviderClass": func(stylingType iam_model.IDPStylingType) string { + "idpProviderClass": func(stylingType domain.IDPConfigStylingType) string { return stylingType.GetCSSClass() }, } @@ -167,7 +167,7 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, cookieName str return r } -func (l *Login) renderNextStep(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { +func (l *Login) renderNextStep(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) authReq, err := l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID) if err != nil { @@ -181,7 +181,7 @@ func (l *Login) renderNextStep(w http.ResponseWriter, r *http.Request, authReq * l.chooseNextStep(w, r, authReq, 0, nil) } -func (l *Login) renderError(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { if authReq == nil || len(authReq.PossibleSteps) == 0 { l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(err, "APP-OVOiT", "no possible steps")) return @@ -189,54 +189,54 @@ func (l *Login) renderError(w http.ResponseWriter, r *http.Request, authReq *mod l.chooseNextStep(w, r, authReq, 0, err) } -func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, stepNumber int, err error) { +func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, stepNumber int, err error) { switch step := authReq.PossibleSteps[stepNumber].(type) { - case *model.LoginStep: + case *domain.LoginStep: if len(authReq.PossibleSteps) > 1 { l.chooseNextStep(w, r, authReq, 1, err) return } l.renderLogin(w, r, authReq, err) - case *model.SelectUserStep: + case *domain.SelectUserStep: l.renderUserSelection(w, r, authReq, step) - case *model.InitPasswordStep: + case *domain.InitPasswordStep: l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) - case *model.PasswordStep: + case *domain.PasswordStep: l.renderPassword(w, r, authReq, nil) - case *model.PasswordlessStep: + case *domain.PasswordlessStep: l.renderPasswordlessVerification(w, r, authReq, nil) - case *model.MFAVerificationStep: + case *domain.MFAVerificationStep: l.renderMFAVerify(w, r, authReq, step, err) - case *model.RedirectToCallbackStep: + case *domain.RedirectToCallbackStep: if len(authReq.PossibleSteps) > 1 { l.chooseNextStep(w, r, authReq, 1, err) return } l.redirectToCallback(w, r, authReq) - case *model.ChangePasswordStep: + case *domain.ChangePasswordStep: l.renderChangePassword(w, r, authReq, err) - case *model.VerifyEMailStep: + case *domain.VerifyEMailStep: l.renderMailVerification(w, r, authReq, "", err) - case *model.MFAPromptStep: + case *domain.MFAPromptStep: l.renderMFAPrompt(w, r, authReq, step, err) - case *model.InitUserStep: + case *domain.InitUserStep: l.renderInitUser(w, r, authReq, "", "", step.PasswordSet, nil) - case *model.ChangeUsernameStep: + case *domain.ChangeUsernameStep: l.renderChangeUsername(w, r, authReq, nil) - case *model.LinkUsersStep: + case *domain.LinkUsersStep: l.linkUsers(w, r, authReq, err) - case *model.ExternalNotFoundOptionStep: + case *domain.ExternalNotFoundOptionStep: l.renderExternalNotFoundOption(w, r, authReq, err) - case *model.ExternalLoginStep: + case *domain.ExternalLoginStep: l.handleExternalLoginStep(w, r, authReq, step.SelectedIDPConfigID) - case *model.GrantRequiredStep: + case *domain.GrantRequiredStep: l.renderInternalError(w, r, authReq, caos_errs.ThrowPreconditionFailed(nil, "APP-asb43", "Errors.User.GrantRequired")) default: l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible")) } } -func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var msg string if err != nil { msg = err.Error() @@ -245,7 +245,7 @@ func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, auth l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplError], data, nil) } -func (l *Login) getUserData(r *http.Request, authReq *model.AuthRequest, title string, errType, errMessage string) userData { +func (l *Login) getUserData(r *http.Request, authReq *domain.AuthRequest, title string, errType, errMessage string) userData { userData := userData{ baseData: l.getBaseData(r, authReq, title, errType, errMessage), profileData: l.getProfileData(authReq), @@ -256,7 +256,7 @@ func (l *Login) getUserData(r *http.Request, authReq *model.AuthRequest, title s return userData } -func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title string, errType, errMessage string) baseData { +func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, title string, errType, errMessage string) baseData { baseData := baseData{ errorData: errorData{ ErrType: errType, @@ -279,7 +279,7 @@ func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title s return baseData } -func (l *Login) getProfileData(authReq *model.AuthRequest) profileData { +func (l *Login) getProfileData(authReq *domain.AuthRequest) profileData { var loginName, displayName string if authReq != nil { loginName = authReq.LoginName @@ -309,7 +309,7 @@ func (l *Login) getThemeMode(r *http.Request) string { return "" //TODO: impl } -func (l *Login) getOrgID(authReq *model.AuthRequest) string { +func (l *Login) getOrgID(authReq *domain.AuthRequest) string { if authReq == nil { return "" } @@ -319,14 +319,14 @@ func (l *Login) getOrgID(authReq *model.AuthRequest) string { return authReq.UserOrgID } -func (l *Login) getOrgName(authReq *model.AuthRequest) string { +func (l *Login) getOrgName(authReq *domain.AuthRequest) string { if authReq == nil { return "" } return authReq.RequestedOrgName } -func getRequestID(authReq *model.AuthRequest, r *http.Request) string { +func getRequestID(authReq *domain.AuthRequest, r *http.Request) string { if authReq != nil { return authReq.ID } @@ -357,8 +357,8 @@ type baseData struct { AuthReqID string CSRF template.HTML Nonce string - LoginPolicy *iam_model.LoginPolicyView - IDPProviders []*iam_model.IDPProviderView + LoginPolicy *domain.LoginPolicy + IDPProviders []*domain.IDPProvider } type errorData struct { @@ -370,8 +370,8 @@ type userData struct { baseData profileData PasswordChecked string - MFAProviders []model.MFAType - SelectedMFAProvider model.MFAType + MFAProviders []domain.MFAType + SelectedMFAProvider domain.MFAType Linking bool } @@ -393,28 +393,28 @@ type passwordData struct { type userSelectionData struct { baseData - Users []model.UserSelection + Users []domain.UserSelection Linking bool } type mfaData struct { baseData profileData - MFAProviders []model.MFAType + MFAProviders []domain.MFAType MFARequired bool } type mfaVerifyData struct { baseData profileData - MFAType model.MFAType + MFAType domain.MFAType otpData } type mfaDoneData struct { baseData profileData - MFAType model.MFAType + MFAType domain.MFAType } type otpData struct { diff --git a/internal/ui/login/handler/select_user_handler.go b/internal/ui/login/handler/select_user_handler.go index 088c963439..c30319d53d 100644 --- a/internal/ui/login/handler/select_user_handler.go +++ b/internal/ui/login/handler/select_user_handler.go @@ -1,10 +1,10 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" http_mw "github.com/caos/zitadel/internal/api/http/middleware" - "github.com/caos/zitadel/internal/auth_request/model" ) const ( @@ -15,7 +15,7 @@ type userSelectionFormData struct { UserID string `schema:"userID"` } -func (l *Login) renderUserSelection(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, selectionData *model.SelectUserStep) { +func (l *Login) renderUserSelection(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, selectionData *domain.SelectUserStep) { data := userSelectionData{ baseData: l.getBaseData(r, authReq, "Select User", "", ""), Users: selectionData.Users, diff --git a/internal/ui/login/handler/username_change_handler.go b/internal/ui/login/handler/username_change_handler.go index 690deb3db2..de7bbf60b3 100644 --- a/internal/ui/login/handler/username_change_handler.go +++ b/internal/ui/login/handler/username_change_handler.go @@ -1,9 +1,8 @@ package handler import ( + "github.com/caos/zitadel/internal/v2/domain" "net/http" - - "github.com/caos/zitadel/internal/auth_request/model" ) const ( @@ -15,7 +14,7 @@ type changeUsernameData struct { Username string `schema:"username"` } -func (l *Login) renderChangeUsername(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { +func (l *Login) renderChangeUsername(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -31,7 +30,7 @@ func (l *Login) handleChangeUsername(w http.ResponseWriter, r *http.Request) { l.renderError(w, r, authReq, err) return } - err = l.authRepo.ChangeUsername(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Username) + err = l.command.ChangeUsername(setContext(r.Context(), authReq.UserOrgID), authReq.UserOrgID, authReq.UserID, data.Username) if err != nil { l.renderChangeUsername(w, r, authReq, err) return @@ -39,7 +38,7 @@ func (l *Login) handleChangeUsername(w http.ResponseWriter, r *http.Request) { l.renderChangeUsernameDone(w, r, authReq) } -func (l *Login) renderChangeUsernameDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) { +func (l *Login) renderChangeUsernameDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { var errType, errMessage string data := l.getUserData(r, authReq, "Username Change Done", errType, errMessage) l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplChangeUsernameDone], data, nil) diff --git a/internal/ui/login/static/templates/password_reset_done.html b/internal/ui/login/static/templates/password_reset_done.html index 55a3cc9b57..c8ce9f6105 100644 --- a/internal/ui/login/static/templates/password_reset_done.html +++ b/internal/ui/login/static/templates/password_reset_done.html @@ -12,6 +12,8 @@ + + {{template "error-message" .}}
diff --git a/internal/user/model/user_view.go b/internal/user/model/user_view.go index 72ed0af935..afc7fa3a86 100644 --- a/internal/user/model/user_view.go +++ b/internal/user/model/user_view.go @@ -1,6 +1,7 @@ package model import ( + "github.com/caos/zitadel/internal/v2/domain" "time" "golang.org/x/text/language" @@ -117,21 +118,21 @@ func (r *UserSearchRequest) AppendMyOrgQuery(orgID string) { r.Queries = append(r.Queries, &UserSearchQuery{Key: UserSearchKeyResourceOwner, Method: model.SearchMethodEquals, Value: orgID}) } -func (u *UserView) MFATypesSetupPossible(level req_model.MFALevel, policy *iam_model.LoginPolicyView) []req_model.MFAType { - types := make([]req_model.MFAType, 0) +func (u *UserView) MFATypesSetupPossible(level domain.MFALevel, policy *domain.LoginPolicy) []domain.MFAType { + types := make([]domain.MFAType, 0) switch level { default: fallthrough - case req_model.MFALevelSecondFactor: + case domain.MFALevelSecondFactor: if policy.HasSecondFactors() { for _, mfaType := range policy.SecondFactors { switch mfaType { - case iam_model.SecondFactorTypeOTP: + case domain.SecondFactorTypeOTP: if u.OTPState != MFAStateReady { - types = append(types, req_model.MFATypeOTP) + types = append(types, domain.MFATypeOTP) } - case iam_model.SecondFactorTypeU2F: - types = append(types, req_model.MFATypeU2F) + case domain.SecondFactorTypeU2F: + types = append(types, domain.MFATypeU2F) } } } @@ -140,24 +141,24 @@ func (u *UserView) MFATypesSetupPossible(level req_model.MFALevel, policy *iam_m return types } -func (u *UserView) MFATypesAllowed(level req_model.MFALevel, policy *iam_model.LoginPolicyView) ([]req_model.MFAType, bool) { - types := make([]req_model.MFAType, 0) +func (u *UserView) MFATypesAllowed(level domain.MFALevel, policy *domain.LoginPolicy) ([]domain.MFAType, bool) { + types := make([]domain.MFAType, 0) required := true switch level { default: required = policy.ForceMFA fallthrough - case req_model.MFALevelSecondFactor: + case domain.MFALevelSecondFactor: if policy.HasSecondFactors() { for _, mfaType := range policy.SecondFactors { switch mfaType { - case iam_model.SecondFactorTypeOTP: + case domain.SecondFactorTypeOTP: if u.OTPState == MFAStateReady { - types = append(types, req_model.MFATypeOTP) + types = append(types, domain.MFATypeOTP) } - case iam_model.SecondFactorTypeU2F: + case domain.SecondFactorTypeU2F: if u.IsU2FReady() { - types = append(types, req_model.MFATypeU2F) + types = append(types, domain.MFATypeU2F) } } } diff --git a/internal/user/repository/view/model/notify_user.go b/internal/user/repository/view/model/notify_user.go index b5a5e687f6..00de13bf22 100644 --- a/internal/user/repository/view/model/notify_user.go +++ b/internal/user/repository/view/model/notify_user.go @@ -39,6 +39,7 @@ type NotifyUser struct { VerifiedPhone string `json:"-" gorm:"column:verified_phone"` PasswordSet bool `json:"-" gorm:"column:password_set"` Sequence uint64 `json:"-" gorm:"column:sequence"` + State int32 `json:"-"` } func NotifyUserFromModel(user *model.NotifyUser) *NotifyUser { @@ -144,6 +145,8 @@ func (u *NotifyUser) AppendEvent(event *models.Event) (err error) { case es_model.UserPasswordChanged, es_model.HumanPasswordChanged: err = u.setPasswordData(event) + case es_model.UserRemoved: + u.State = int32(UserStateDeleted) } return err } diff --git a/internal/v2/command/iam_converter.go b/internal/v2/command/iam_converter.go index 8b8686de0e..239e6b4c36 100644 --- a/internal/v2/command/iam_converter.go +++ b/internal/v2/command/iam_converter.go @@ -38,7 +38,7 @@ func writeModelToLoginPolicy(wm *LoginPolicyWriteModel) *domain.LoginPolicy { ObjectRoot: writeModelToObjectRoot(wm.WriteModel), AllowUsernamePassword: wm.AllowUserNamePassword, AllowRegister: wm.AllowRegister, - AllowExternalIdp: wm.AllowExternalIDP, + AllowExternalIDP: wm.AllowExternalIDP, ForceMFA: wm.ForceMFA, PasswordlessType: wm.PasswordlessType, } diff --git a/internal/v2/command/iam_idp_oidc_config_model.go b/internal/v2/command/iam_idp_oidc_config_model.go index 7bff022c53..dddd19328d 100644 --- a/internal/v2/command/iam_idp_oidc_config_model.go +++ b/internal/v2/command/iam_idp_oidc_config_model.go @@ -108,7 +108,7 @@ func (wm *IAMIDPOIDCConfigWriteModel) NewChangedEvent( if userNameMapping.Valid() && wm.UserNameMapping != userNameMapping { changes = append(changes, idpconfig.ChangeUserNameMapping(userNameMapping)) } - if reflect.DeepEqual(wm.Scopes, scopes) { + if !reflect.DeepEqual(wm.Scopes, scopes) { changes = append(changes, idpconfig.ChangeScopes(scopes)) } if len(changes) == 0 { diff --git a/internal/v2/command/iam_policy_login.go b/internal/v2/command/iam_policy_login.go index 83a28447ee..4efe343394 100644 --- a/internal/v2/command/iam_policy_login.go +++ b/internal/v2/command/iam_policy_login.go @@ -44,7 +44,7 @@ func (r *CommandSide) addDefaultLoginPolicy(ctx context.Context, iamAgg *iam_rep return caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LoginPolicy.AlreadyExists") } - iamAgg.PushEvents(iam_repo.NewLoginPolicyAddedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIdp, policy.ForceMFA, policy.PasswordlessType)) + iamAgg.PushEvents(iam_repo.NewLoginPolicyAddedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType)) return nil } @@ -72,7 +72,7 @@ func (r *CommandSide) changeDefaultLoginPolicy(ctx context.Context, iamAgg *iam_ if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { return caos_errs.ThrowNotFound(nil, "IAM-M0sif", "Errors.IAM.LoginPolicy.NotFound") } - changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIdp, policy.ForceMFA, policy.PasswordlessType) + changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType) if !hasChanged { return caos_errs.ThrowPreconditionFailed(nil, "IAM-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged") } diff --git a/internal/v2/command/org_policy_login.go b/internal/v2/command/org_policy_login.go index 79593b5e8d..2bb8e47696 100644 --- a/internal/v2/command/org_policy_login.go +++ b/internal/v2/command/org_policy_login.go @@ -19,7 +19,7 @@ func (r *CommandSide) AddLoginPolicy(ctx context.Context, policy *domain.LoginPo } orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel) - orgAgg.PushEvents(org.NewLoginPolicyAddedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIdp, policy.ForceMFA, policy.PasswordlessType)) + orgAgg.PushEvents(org.NewLoginPolicyAddedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType)) err = r.eventstore.PushAggregate(ctx, addedPolicy, orgAgg) if err != nil { @@ -38,7 +38,7 @@ func (r *CommandSide) ChangeLoginPolicy(ctx context.Context, policy *domain.Logi if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { return nil, caos_errs.ThrowNotFound(nil, "Org-M0sif", "Errors.Org.LoginPolicy.NotFound") } - changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIdp, policy.ForceMFA, domain.PasswordlessType(policy.PasswordlessType)) + changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, domain.PasswordlessType(policy.PasswordlessType)) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged") } diff --git a/internal/v2/command/project.go b/internal/v2/command/project.go index a21398b166..cf072b0ae3 100644 --- a/internal/v2/command/project.go +++ b/internal/v2/command/project.go @@ -2,6 +2,7 @@ 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" @@ -144,7 +145,7 @@ func (r *CommandSide) ReactivateProject(ctx context.Context, projectID string, r return r.eventstore.PushAggregate(ctx, existingProject, projectAgg) } -func (r *CommandSide) RemoveProject(ctx context.Context, projectID, resourceOwner string) error { +func (r *CommandSide) RemoveProject(ctx context.Context, projectID, resourceOwner string, cascadingGrantIDs ...string) error { if projectID == "" || resourceOwner == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-66hM9", "Errors.Project.ProjectIDMissing") } @@ -157,11 +158,21 @@ func (r *CommandSide) RemoveProject(ctx context.Context, projectID, resourceOwne return caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound") } + aggregates := make([]eventstore.Aggregater, 0) projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel) projectAgg.PushEvents(project.NewProjectRemovedEvent(ctx, existingProject.Name, existingProject.ResourceOwner)) - //TODO: Remove User Grants by ProjectID + aggregates = append(aggregates, projectAgg) - return r.eventstore.PushAggregate(ctx, existingProject, projectAgg) + for _, grantID := range cascadingGrantIDs { + grantAgg, _, err := r.removeUserGrant(ctx, grantID, "", true) + if err != nil { + return err + } + aggregates = append(aggregates, grantAgg) + } + + _, err = r.eventstore.PushAggregates(ctx, aggregates...) + return err } func (r *CommandSide) getProjectWriteModelByID(ctx context.Context, projectID, resourceOwner string) (*ProjectWriteModel, error) { diff --git a/internal/v2/command/setup_step1.go b/internal/v2/command/setup_step1.go index a0e2633d75..bd0b1eb942 100644 --- a/internal/v2/command/setup_step1.go +++ b/internal/v2/command/setup_step1.go @@ -91,7 +91,7 @@ func (r *CommandSide) SetupStep1(ctx context.Context, step1 *Step1) error { &domain.LoginPolicy{ AllowUsernamePassword: step1.DefaultLoginPolicy.AllowUsernamePassword, AllowRegister: step1.DefaultLoginPolicy.AllowRegister, - AllowExternalIdp: step1.DefaultLoginPolicy.AllowExternalIdp, + AllowExternalIDP: step1.DefaultLoginPolicy.AllowExternalIdp, }) if err != nil { return err diff --git a/internal/v2/command/user.go b/internal/v2/command/user.go index 53576c39e1..f02e0fb604 100644 --- a/internal/v2/command/user.go +++ b/internal/v2/command/user.go @@ -2,6 +2,10 @@ package command import ( "context" + auth_req_model "github.com/caos/zitadel/internal/auth_request/model" + "github.com/caos/zitadel/internal/eventstore/models" + "strings" + "time" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/telemetry/tracing" @@ -21,7 +25,7 @@ func (r *CommandSide) ChangeUsername(ctx context.Context, orgID, userID, userNam return caos_errs.ThrowNotFound(nil, "COMMAND-5N9ds", "Errors.User.NotFound") } if existingUser.UserName == userName { - return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UsernameNotChanged") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6m9gs", "Errors.User.UsernameNotChanged") } orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, orgID) @@ -89,7 +93,7 @@ func (r *CommandSide) LockUser(ctx context.Context, userID, resourceOwner string return caos_errs.ThrowNotFound(nil, "COMMAND-5M9fs", "Errors.User.NotFound") } if existingUser.UserState != domain.UserStateActive && existingUser.UserState != domain.UserStateInitial { - return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.ShouldBeActiveOrInitial") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3NN8v", "Errors.User.ShouldBeActiveOrInitial") } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg.PushEvents(user.NewUserLockedEvent(ctx)) @@ -139,6 +143,69 @@ func (r *CommandSide) RemoveUser(ctx context.Context, userID, resourceOwner stri return r.eventstore.PushAggregate(ctx, existingUser, userAgg) } +func (r *CommandSide) CreateUserToken(ctx context.Context, orgID, agentID, clientID, userID string, audience, scopes []string, lifetime time.Duration) (*domain.Token, error) { + if orgID == "" || userID == "" { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-55n8M", "Errors.IDMissing") + } + existingUser, err := r.userWriteModelByID(ctx, userID, orgID) + if err != nil { + return nil, err + } + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-1d6Gg", "Errors.User.NotFound") + } + + for _, scope := range scopes { + if strings.HasPrefix(scope, auth_req_model.ProjectIDScope) && strings.HasSuffix(scope, auth_req_model.AudSuffix) { + audience = append(audience, strings.TrimSuffix(strings.TrimPrefix(scope, auth_req_model.ProjectIDScope), auth_req_model.AudSuffix)) + } + } + + preferredLanguage := "" + existingHuman, err := r.getHumanWriteModelByID(ctx, userID, orgID) + if existingHuman != nil { + preferredLanguage = existingHuman.PreferredLanguage.String() + } + now := time.Now().UTC() + tokenID, err := r.idGenerator.Next() + if err != nil { + return nil, err + } + + userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) + userAgg.PushEvents(user.NewUserTokenAddedEvent(ctx, tokenID, clientID, agentID, preferredLanguage, audience, scopes, now.Add(lifetime))) + + err = r.eventstore.PushAggregate(ctx, existingUser, userAgg) + if err != nil { + return nil, err + } + return &domain.Token{ + ObjectRoot: models.ObjectRoot{ + AggregateID: userID, + }, + TokenID: tokenID, + UserAgentID: agentID, + ApplicationID: clientID, + Audience: audience, + Scopes: scopes, + Expiration: now.Add(lifetime), + PreferredLanguage: preferredLanguage, + }, nil +} + +func (r *CommandSide) UserDomainClaimedSent(ctx context.Context, orgID, userID string) (err error) { + existingUser, err := r.userWriteModelByID(ctx, userID, orgID) + if err != nil { + return err + } + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-5m9gK", "Errors.User.NotFound") + } + userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) + userAgg.PushEvents(user.NewDomainClaimedSentEvent(ctx)) + return r.eventstore.PushAggregate(ctx, existingUser, userAgg) +} + func (r *CommandSide) checkUserExists(ctx context.Context, userID, resourceOwner string) error { userWriteModel, err := r.userWriteModelByID(ctx, userID, resourceOwner) if err != nil { diff --git a/internal/v2/command/user_converter.go b/internal/v2/command/user_converter.go index 8a9ff21592..f08c47e140 100644 --- a/internal/v2/command/user_converter.go +++ b/internal/v2/command/user_converter.go @@ -2,6 +2,7 @@ package command import ( "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" ) func writeModelToHuman(wm *HumanWriteModel) *domain.Human { @@ -77,6 +78,15 @@ func writeModelToMachine(wm *MachineWriteModel) *domain.Machine { } } +func keyWriteModelToMachineKey(wm *MachineKeyWriteModel) *domain.MachineKey { + return &domain.MachineKey{ + ObjectRoot: writeModelToObjectRoot(wm.WriteModel), + KeyID: wm.KeyID, + Type: wm.KeyType, + ExpirationDate: wm.ExpirationDate, + } +} + func readModelToU2FTokens(wm *HumanU2FTokensReadModel) []*domain.WebAuthNToken { tokens := make([]*domain.WebAuthNToken, len(wm.WebAuthNTokens)) for i, token := range wm.WebAuthNTokens { @@ -107,3 +117,19 @@ func writeModelToWebAuthN(wm *HumanWebAuthNWriteModel) *domain.WebAuthNToken { State: wm.State, } } + +func authRequestDomainToAuthRequestInfo(authRequest *domain.AuthRequest) *user.AuthRequestInfo { + info := &user.AuthRequestInfo{ + ID: authRequest.ID, + UserAgentID: authRequest.AgentID, + SelectedIDPConfigID: authRequest.SelectedIDPConfigID, + } + if authRequest.BrowserInfo != nil { + info.BrowserInfo = &user.BrowserInfo{ + UserAgent: authRequest.BrowserInfo.UserAgent, + AcceptLanguage: authRequest.BrowserInfo.AcceptLanguage, + RemoteIP: authRequest.BrowserInfo.RemoteIP, + } + } + return info +} diff --git a/internal/v2/command/user_grant.go b/internal/v2/command/user_grant.go index aec6bdf2f1..9c460c52c7 100644 --- a/internal/v2/command/user_grant.go +++ b/internal/v2/command/user_grant.go @@ -189,7 +189,7 @@ func (r *CommandSide) BulkRemoveUserGrant(ctx context.Context, grantIDs []string } func (r *CommandSide) removeUserGrant(ctx context.Context, grantID, resourceOwner string, cascade bool) (_ *usergrant.Aggregate, _ *UserGrantWriteModel, err error) { - if grantID == "" || resourceOwner == "" { + if grantID == "" { return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-J9sc5", "Errors.UserGrant.IDMissing") } @@ -205,7 +205,6 @@ func (r *CommandSide) removeUserGrant(ctx context.Context, grantID, resourceOwne return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-1My0t", "Errors.UserGrant.NotFound") } - //TODO: Remove Uniqueness removeUserGrant := NewUserGrantWriteModel(grantID, resourceOwner) userGrantAgg := UserGrantAggregateFromWriteModel(&removeUserGrant.WriteModel) if !cascade { diff --git a/internal/v2/command/user_grant_model.go b/internal/v2/command/user_grant_model.go index 65cc9363f3..0641210f5a 100644 --- a/internal/v2/command/user_grant_model.go +++ b/internal/v2/command/user_grant_model.go @@ -62,9 +62,12 @@ func (wm *UserGrantWriteModel) Reduce() error { } func (wm *UserGrantWriteModel) Query() *eventstore.SearchQueryBuilder { - return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, usergrant.AggregateType). - AggregateIDs(wm.AggregateID). - ResourceOwner(wm.ResourceOwner) + query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, usergrant.AggregateType). + AggregateIDs(wm.AggregateID) + if wm.ResourceOwner != "" { + query.ResourceOwner(wm.ResourceOwner) + } + return query } func UserGrantAggregateFromWriteModel(wm *eventstore.WriteModel) *usergrant.Aggregate { diff --git a/internal/v2/command/user_human.go b/internal/v2/command/user_human.go index 32f28b833f..a0f2112635 100644 --- a/internal/v2/command/user_human.go +++ b/internal/v2/command/user_human.go @@ -2,6 +2,7 @@ package command import ( "context" + "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/v2" caos_errs "github.com/caos/zitadel/internal/errors" @@ -27,9 +28,6 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID string, human *domain. } err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg) if err != nil { - if caos_errs.IsErrorAlreadyExists(err) { - return nil, caos_errs.ThrowAlreadyExists(err, "COMMAND-4kSff", "Errors.User.AlreadyExists") - } return nil, err } @@ -43,19 +41,36 @@ func (r *CommandSide) addHuman(ctx context.Context, orgID string, human *domain. 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) { +func (r *CommandSide) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string) (*domain.Human, error) { + aggregates := make([]eventstore.Aggregater, 2) + 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 { - if caos_errs.IsErrorAlreadyExists(err) { - return nil, caos_errs.ThrowAlreadyExists(err, "COMMAND-4kSff", "Errors.User.AlreadyExists") + aggregates[0] = userAgg + + orgMemberWriteModel := NewOrgMemberWriteModel(orgID, addedHuman.AggregateID) + orgAgg := OrgAggregateFromWriteModel(&orgMemberWriteModel.WriteModel) + if orgMemberRoles != nil { + orgMember := &domain.Member{ + ObjectRoot: models.ObjectRoot{ + AggregateID: orgID, + }, + UserID: userAgg.ID(), + Roles: orgMemberRoles, } - return nil, err + r.addOrgMember(ctx, orgAgg, orgMemberWriteModel, orgMember) } + aggregates[1] = orgAgg + + eventReader, err := r.eventstore.PushAggregates(ctx, aggregates...) + if err != nil { + return nil, err + } + addedHuman.AppendEvents(eventReader...) + addedHuman.Reduce() return writeModelToHuman(addedHuman), nil } @@ -82,7 +97,7 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma } addedHuman := NewHumanWriteModel(human.AggregateID, orgID) - if err := human.CheckOrgIAMPolicy(human.Username, orgIAMPolicy); err != nil { + if err := human.CheckOrgIAMPolicy(orgIAMPolicy); err != nil { return nil, nil, err } human.SetNamesAsDisplayname() @@ -100,11 +115,10 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma userAgg.PushEvents(createEvent) if externalIDP != nil { - if !externalIDP.IsValid() { - return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4Dj9s", "Errors.User.ExternalIDP.Invalid") + err = r.addHumanExternalIDP(ctx, userAgg, externalIDP) + if err != nil { + return nil, nil, err } - //TODO: check if idpconfig exists - userAgg.PushEvents(user.NewHumanExternalIDPAddedEvent(ctx, externalIDP.IDPConfigID, externalIDP.DisplayName)) } if human.IsInitialState() { initCode, err := domain.NewInitUserCode(r.initializeUserCode) @@ -121,7 +135,7 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma if err != nil { return nil, nil, err } - user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry) + userAgg.PushEvents(user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry)) } else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified { userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx)) } @@ -129,32 +143,21 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma return userAgg, addedHuman, nil } -func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email, resourceowner string) (err error) { +func (r *CommandSide) HumanSkipMFAInit(ctx context.Context, userID, resourceowner string) (err error) { if userID == "" { - return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UserIDMissing") + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2xpX9", "Errors.User.UserIDMissing") } - existingEmail, err := r.emailWriteModel(ctx, userID, resourceowner) + existingHuman, err := r.getHumanWriteModelByID(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 existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-m9cV8", "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) + userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel) + userAgg.PushEvents(user.NewHumanMFAInitSkippedEvent(ctx)) + return r.eventstore.PushAggregate(ctx, existingHuman, userAgg) } func createAddHumanEvent(ctx context.Context, orgID string, human *domain.Human, userLoginMustBeDomain bool) *user.HumanAddedEvent { @@ -219,6 +222,28 @@ func createRegisterHumanEvent(ctx context.Context, orgID string, human *domain.H return addEvent } +func (r *CommandSide) HumansSignOut(ctx context.Context, agentID string, userIDs []string) error { + if agentID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing") + } + aggregates := make([]eventstore.Aggregater, len(userIDs)) + for i, userID := range userIDs { + existingUser, err := r.getHumanWriteModelByID(ctx, userID, "") + if err != nil { + return err + } + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + continue + } + userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) + userAgg.PushEvents(user.NewHumanSignedOutEvent(ctx, agentID)) + aggregates[i] = userAgg + } + + _, err := r.eventstore.PushAggregates(ctx, aggregates...) + return err +} + func (r *CommandSide) getHumanWriteModelByID(ctx context.Context, userID, resourceowner string) (*HumanWriteModel, error) { humanWriteModel := NewHumanWriteModel(userID, resourceowner) err := r.eventstore.FilterToQueryReducer(ctx, humanWriteModel) diff --git a/internal/v2/command/user_human_email.go b/internal/v2/command/user_human_email.go index 2eefc1100b..913804c57f 100644 --- a/internal/v2/command/user_human_email.go +++ b/internal/v2/command/user_human_email.go @@ -24,7 +24,7 @@ func (r *CommandSide) ChangeHumanEmail(ctx context.Context, email *domain.Email) } changedEvent, hasChanged := existingEmail.NewChangedEvent(ctx, email.EmailAddress) if !hasChanged { - return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Email.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2b7fM", "Errors.User.Email.NotChanged") } userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel) userAgg.PushEvents(changedEvent) @@ -60,7 +60,7 @@ func (r *CommandSide) VerifyHumanEmail(ctx context.Context, userID, code, resour 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") + return caos_errs.ThrowNotFound(nil, "COMMAND-3n8ud", "Errors.User.Code.NotFound") } userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) @@ -103,6 +103,19 @@ func (r *CommandSide) CreateHumanEmailVerificationCode(ctx context.Context, user return r.eventstore.PushAggregate(ctx, existingEmail, userAgg) } +func (r *CommandSide) HumanEmailVerificationCodeSent(ctx context.Context, orgID, userID string) (err error) { + existingEmail, err := r.emailWriteModel(ctx, userID, orgID) + if err != nil { + return err + } + if existingEmail.UserState == domain.UserStateUnspecified || existingEmail.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-6n8uH", "Errors.User.Email.NotFound") + } + userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel) + userAgg.PushEvents(user.NewHumanEmailCodeSentEvent(ctx)) + return r.eventstore.PushAggregate(ctx, existingEmail, userAgg) +} + func (r *CommandSide) emailWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanEmailWriteModel, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/v2/command/user_human_email_model.go b/internal/v2/command/user_human_email_model.go index e8aa463117..a7ce888b48 100644 --- a/internal/v2/command/user_human_email_model.go +++ b/internal/v2/command/user_human_email_model.go @@ -66,9 +66,12 @@ func (wm *HumanEmailWriteModel) Reduce() error { } func (wm *HumanEmailWriteModel) Query() *eventstore.SearchQueryBuilder { - return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). - AggregateIDs(wm.AggregateID). - ResourceOwner(wm.ResourceOwner) + query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID) + if wm.ResourceOwner != "" { + query.ResourceOwner(wm.ResourceOwner) + } + return query } func (wm *HumanEmailWriteModel) NewChangedEvent( diff --git a/internal/v2/command/user_human_externalidp.go b/internal/v2/command/user_human_externalidp.go index cba2b273c3..95e8c5c75b 100644 --- a/internal/v2/command/user_human_externalidp.go +++ b/internal/v2/command/user_human_externalidp.go @@ -3,11 +3,40 @@ package command import ( "context" caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/user" ) +func (r *CommandSide) BulkAddedHumanExternalIDP(ctx context.Context, userID, resourceOwner string, externalIDPs []*domain.ExternalIDP) error { + if len(externalIDPs) == 0 { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded") + } + aggregates := make([]eventstore.Aggregater, len(externalIDPs)) + + for i, externalIDP := range externalIDPs { + externalIDPWriteModel := NewHumanExternalIDPWriteModel(userID, externalIDP.IDPConfigID, externalIDP.ExternalUserID, resourceOwner) + userAgg := UserAggregateFromWriteModel(&externalIDPWriteModel.WriteModel) + err := r.addHumanExternalIDP(ctx, userAgg, externalIDP) + if err != nil { + return err + } + aggregates[i] = userAgg + } + _, err := r.eventstore.PushAggregates(ctx, aggregates...) + return err +} + +func (r *CommandSide) addHumanExternalIDP(ctx context.Context, userAgg *user.Aggregate, externalIDP *domain.ExternalIDP) error { + if !externalIDP.IsValid() { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6m9Kd", "Errors.User.ExternalIDP.Invalid") + } + //TODO: check if idpconfig exists + userAgg.PushEvents(user.NewHumanExternalIDPAddedEvent(ctx, externalIDP.IDPConfigID, externalIDP.DisplayName, externalIDP.ExternalUserID)) + return nil +} + func (r *CommandSide) RemoveHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP) error { return r.removeHumanExternalIDP(ctx, externalIDP, false) } @@ -37,6 +66,24 @@ func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *d return r.eventstore.PushAggregate(ctx, existingExternalIDP, userAgg) } +func (r *CommandSide) HumanExternalLoginChecked(ctx context.Context, orgID, userID string, authRequest *domain.AuthRequest) (err error) { + if userID == "" { + return caos_errs.ThrowNotFound(nil, "COMMAND-5n8sM", "Errors.IDMissing") + } + + existingHuman, err := r.getHumanWriteModelByID(ctx, userID, orgID) + if err != nil { + return err + } + if existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-dn88J", "Errors.User.NotFound") + } + + userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel) + userAgg.PushEvents(user.NewHumanExternalIDPCheckSucceededEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest))) + return r.eventstore.PushAggregate(ctx, existingHuman, userAgg) +} + func (r *CommandSide) externalIDPWriteModelByID(ctx context.Context, userID, idpConfigID, externalUserID, resourceOwner string) (writeModel *HumanExternalIDPWriteModel, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/v2/command/user_human_init.go b/internal/v2/command/user_human_init.go new file mode 100644 index 0000000000..eac761c442 --- /dev/null +++ b/internal/v2/command/user_human_init.go @@ -0,0 +1,104 @@ +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/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +//ResendInitialMail resend inital mail and changes email if provided +func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email, resourceOwner string) (err error) { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing") + } + + existingCode, err := r.getHumanInitWriteModelByID(ctx, userID, resourceOwner) + if err != nil { + return err + } + if existingCode.UserState == domain.UserStateUnspecified || existingCode.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-2M9df", "Errors.User.NotFound") + } + if existingCode.UserState != domain.UserStateInitial { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.AlreadyInitialised") + } + userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) + if email != "" && existingCode.Email != email { + changedEvent, _ := existingCode.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, existingCode, userAgg) +} + +func (r *CommandSide) HumanVerifyInitCode(ctx context.Context, userID, resourceOwner, code, passwordString string) error { + if userID == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-mkM9f", "Errors.User.UserIDMissing") + } + if code == "" { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-44G8s", "Errors.User.Code.Empty") + } + + existingCode, err := r.getHumanInitWriteModelByID(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-mmn5f", "Errors.User.Code.NotFound") + } + + userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.initializeUserCode) + if err != nil { + userAgg.PushEvents(user.NewHumanInitializedCheckFailedEvent(ctx)) + err = r.eventstore.PushAggregate(ctx, existingCode, userAgg) + logging.LogWithFields("COMMAND-Dg2z5", "userID", userAgg.ID()).OnError(err).Error("NewHumanInitializedCheckFailedEvent push failed") + return caos_errs.ThrowInvalidArgument(err, "COMMAND-11v6G", "Errors.User.Code.Invalid") + } + + userAgg.PushEvents(user.NewHumanInitializedCheckSucceededEvent(ctx)) + if !existingCode.IsEmailVerified { + userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx)) + } + if passwordString != "" { + passwordWriteModel := NewHumanPasswordWriteModel(userID, existingCode.ResourceOwner) + password := &domain.Password{ + SecretString: passwordString, + ChangeRequired: false, + } + err = r.changePassword(ctx, existingCode.ResourceOwner, userID, "", password, userAgg, passwordWriteModel) + if err != nil { + return err + } + } + return r.eventstore.PushAggregate(ctx, existingCode, userAgg) +} + +func (r *CommandSide) HumanInitCodeSent(ctx context.Context, orgID, userID string) (err error) { + existingInitCode, err := r.getHumanInitWriteModelByID(ctx, userID, orgID) + if err != nil { + return err + } + if existingInitCode.UserState == domain.UserStateUnspecified || existingInitCode.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-556zg", "Errors.User.Code.NotFound") + } + userAgg := UserAggregateFromWriteModel(&existingInitCode.WriteModel) + userAgg.PushEvents(user.NewHumanInitialCodeSentEvent(ctx)) + return r.eventstore.PushAggregate(ctx, existingInitCode, userAgg) +} + +func (r *CommandSide) getHumanInitWriteModelByID(ctx context.Context, userID, resourceowner string) (*HumanInitCodeWriteModel, error) { + initWriteModel := NewHumanInitCodeWriteModel(userID, resourceowner) + err := r.eventstore.FilterToQueryReducer(ctx, initWriteModel) + if err != nil { + return nil, err + } + return initWriteModel, nil +} diff --git a/internal/v2/command/user_human_init_model.go b/internal/v2/command/user_human_init_model.go new file mode 100644 index 0000000000..20edecf4cc --- /dev/null +++ b/internal/v2/command/user_human_init_model.go @@ -0,0 +1,89 @@ +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 HumanInitCodeWriteModel struct { + eventstore.WriteModel + + Email string + IsEmailVerified bool + + Code *crypto.CryptoValue + CodeCreationDate time.Time + CodeExpiry time.Duration + + UserState domain.UserState +} + +func NewHumanInitCodeWriteModel(userID, resourceOwner string) *HumanInitCodeWriteModel { + return &HumanInitCodeWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + ResourceOwner: resourceOwner, + }, + } +} + +func (wm *HumanInitCodeWriteModel) AppendEvents(events ...eventstore.EventReader) { + wm.WriteModel.AppendEvents(events...) +} + +func (wm *HumanInitCodeWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanAddedEvent: + wm.Email = e.EmailAddress + wm.UserState = domain.UserStateInitial + case *user.HumanRegisteredEvent: + wm.Email = e.EmailAddress + wm.UserState = domain.UserStateInitial + case *user.HumanEmailChangedEvent: + wm.Email = e.EmailAddress + wm.IsEmailVerified = false + case *user.HumanEmailVerifiedEvent: + wm.IsEmailVerified = true + wm.Code = nil + if wm.UserState == domain.UserStateInitial { + wm.UserState = domain.UserStateActive + } + case *user.HumanInitialCodeAddedEvent: + wm.Code = e.Code + wm.CodeCreationDate = e.CreationDate() + wm.CodeExpiry = e.Expiry + case *user.HumanInitializedCheckSucceededEvent: + wm.Code = nil + case *user.UserRemovedEvent: + wm.UserState = domain.UserStateDeleted + } + } + return wm.WriteModel.Reduce() +} + +func (wm *HumanInitCodeWriteModel) Query() *eventstore.SearchQueryBuilder { + query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID) + if wm.ResourceOwner != "" { + query.ResourceOwner(wm.ResourceOwner) + } + return query +} + +func (wm *HumanInitCodeWriteModel) NewChangedEvent( + ctx context.Context, + email string, +) (*user.HumanEmailChangedEvent, bool) { + hasChanged := false + changedEvent := user.NewHumanEmailChangedEvent(ctx) + if wm.Email != email { + hasChanged = true + changedEvent.EmailAddress = email + } + return changedEvent, hasChanged +} diff --git a/internal/v2/command/user_human_otp.go b/internal/v2/command/user_human_otp.go index 195e402945..b0a0151c31 100644 --- a/internal/v2/command/user_human_otp.go +++ b/internal/v2/command/user_human_otp.go @@ -2,6 +2,7 @@ package command import ( "context" + "github.com/caos/logging" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/telemetry/tracing" @@ -15,14 +16,17 @@ func (r *CommandSide) AddHumanOTP(ctx context.Context, userID, resourceowner str } human, err := r.getHuman(ctx, userID, resourceowner) if err != nil { + logging.Log("COMMAND-DAqe1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get human for loginname") return nil, err } org, err := r.getOrg(ctx, human.ResourceOwner) if err != nil { + logging.Log("COMMAND-Cm0ds").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org for loginname") return nil, err } orgPolicy, err := r.getOrgIAMPolicy(ctx, org.AggregateID) if err != nil { + logging.Log("COMMAND-y5zv9").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname") return nil, err } otpWriteModel, err := r.otpWriteModelByID(ctx, userID, resourceowner) @@ -58,7 +62,7 @@ func (r *CommandSide) AddHumanOTP(ctx context.Context, userID, resourceowner str }, nil } -func (r *CommandSide) CheckMFAOTPSetup(ctx context.Context, userID, code, userAgentID, resourceowner string) error { +func (r *CommandSide) HumanCheckMFAOTPSetup(ctx context.Context, userID, code, userAgentID, resourceowner string) error { if userID == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing") } @@ -84,7 +88,32 @@ func (r *CommandSide) CheckMFAOTPSetup(ctx context.Context, userID, code, userAg return r.eventstore.PushAggregate(ctx, existingOTP, userAgg) } -func (r *CommandSide) RemoveHumanOTP(ctx context.Context, userID, resourceOwner string) error { +func (r *CommandSide) HumanCheckMFAOTP(ctx context.Context, userID, code, resourceowner string, authRequest *domain.AuthRequest) 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.MFAStateReady { + return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady") + } + userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel) + err = domain.VerifyMFAOTP(code, existingOTP.Secret, r.multifactors.OTP.CryptoMFA) + if err == nil { + userAgg.PushEvents( + user.NewHumanOTPCheckSucceededEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest)), + ) + return r.eventstore.PushAggregate(ctx, existingOTP, userAgg) + } + userAgg.PushEvents(user.NewHumanOTPCheckFailedEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest))) + pushErr := r.eventstore.PushAggregate(ctx, existingOTP, userAgg) + logging.Log("COMMAND-9fj7s").OnError(pushErr).Error("error create password check failed event") + return err +} + +func (r *CommandSide) HumanRemoveOTP(ctx context.Context, userID, resourceOwner string) error { if userID == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing") } diff --git a/internal/v2/command/user_human_otp_model.go b/internal/v2/command/user_human_otp_model.go index 7a3f239a6f..2e37e4699a 100644 --- a/internal/v2/command/user_human_otp_model.go +++ b/internal/v2/command/user_human_otp_model.go @@ -45,7 +45,10 @@ func (wm *HumanOTPWriteModel) Reduce() error { } func (wm *HumanOTPWriteModel) Query() *eventstore.SearchQueryBuilder { - return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). - AggregateIDs(wm.AggregateID). - ResourceOwner(wm.ResourceOwner) + query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID) + if wm.ResourceOwner != "" { + query.ResourceOwner(wm.ResourceOwner) + } + return query } diff --git a/internal/v2/command/user_human_password.go b/internal/v2/command/user_human_password.go index 6f1e4f0e62..0a0d0a316c 100644 --- a/internal/v2/command/user_human_password.go +++ b/internal/v2/command/user_human_password.go @@ -2,6 +2,7 @@ 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" @@ -21,7 +22,34 @@ func (r *CommandSide) SetOneTimePassword(ctx context.Context, orgID, userID, pas SecretString: passwordString, ChangeRequired: true, } - return r.changePassword(ctx, orgID, userID, "", password, existingPassword) + userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel) + return r.changePassword(ctx, orgID, userID, "", password, userAgg, existingPassword) +} + +func (r *CommandSide) SetPassword(ctx context.Context, orgID, userID, code, passwordString, userAgentID string) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + existingCode, err := r.passwordWriteModel(ctx, userID, orgID) + 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") + } + + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, r.emailVerificationCode) + if err != nil { + return err + } + + password := &domain.Password{ + SecretString: passwordString, + ChangeRequired: false, + } + userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) + return r.changePassword(ctx, existingCode.ResourceOwner, userID, userAgentID, password, userAgg, existingCode) } func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID string) (err error) { @@ -32,7 +60,7 @@ func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPass if err != nil { return err } - if existingPassword.Secret != nil { + if existingPassword.Secret == nil { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Fds3s", "Errors.User.Password.Empty") } ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash") @@ -44,12 +72,14 @@ func (r *CommandSide) ChangePassword(ctx context.Context, orgID, userID, oldPass } password := &domain.Password{ SecretString: newPassword, - ChangeRequired: true, + ChangeRequired: false, } - return r.changePassword(ctx, orgID, userID, userAgentID, password, existingPassword) + + userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel) + return r.changePassword(ctx, orgID, userID, userAgentID, password, userAgg, existingPassword) } -func (r *CommandSide) changePassword(ctx context.Context, orgID, userID, userAgentID string, password *domain.Password, existingPassword *HumanPasswordWriteModel) (err error) { +func (r *CommandSide) changePassword(ctx context.Context, orgID, userID, userAgentID string, password *domain.Password, userAgg *user.Aggregate, existingPassword *HumanPasswordWriteModel) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -69,7 +99,6 @@ func (r *CommandSide) changePassword(ctx context.Context, orgID, userID, userAge if err := password.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg); err != nil { return err } - userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel) userAgg.PushEvents(user.NewHumanPasswordChangedEvent(ctx, password.SecretCrypto, password.ChangeRequired, userAgentID)) return r.eventstore.PushAggregate(ctx, existingPassword, userAgg) } @@ -94,6 +123,54 @@ func (r *CommandSide) RequestSetPassword(ctx context.Context, userID, resourceOw return r.eventstore.PushAggregate(ctx, existingHuman, userAgg) } +func (r *CommandSide) PasswordCodeSent(ctx context.Context, orgID, userID string) (err error) { + existingPassword, err := r.passwordWriteModel(ctx, userID, orgID) + if err != nil { + return err + } + if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-3n77z", "Errors.User.NotFound") + } + userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel) + userAgg.PushEvents(user.NewHumanPasswordCodeSentEvent(ctx)) + return r.eventstore.PushAggregate(ctx, existingPassword, userAgg) +} + +func (r *CommandSide) HumanCheckPassword(ctx context.Context, orgID, userID, password string, authRequest *domain.AuthRequest) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + if password == "" { + return caos_errs.ThrowNotFound(nil, "COMMAND-3n8fs", "Errors.User.Password.Empty") + } + + existingPassword, err := r.passwordWriteModel(ctx, userID, orgID) + if err != nil { + return err + } + if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted { + return caos_errs.ThrowNotFound(nil, "COMMAND-3n77z", "Errors.User.NotFound") + } + + if existingPassword.Secret == nil { + return caos_errs.ThrowNotFound(nil, "COMMAND-3n77z", "Errors.User.Password.NotSet") + } + + userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel) + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash") + err = crypto.CompareHash(existingPassword.Secret, []byte(password), r.userPasswordAlg) + spanPasswordComparison.EndWithError(err) + if err == nil { + userAgg.PushEvents(user.NewHumanPasswordCheckSucceededEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest))) + return r.eventstore.PushAggregate(ctx, existingPassword, userAgg) + } + + userAgg.PushEvents(user.NewHumanPasswordCheckFailedEvent(ctx, authRequestDomainToAuthRequestInfo(authRequest))) + err = r.eventstore.PushAggregate(ctx, existingPassword, userAgg) + logging.Log("COMMAND-9fj7s").OnError(err).Error("error create password check failed event") + return caos_errs.ThrowInvalidArgument(nil, "COMMAND-452ad", "Errors.User.Password.Invalid") +} + func (r *CommandSide) passwordWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPasswordWriteModel, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/v2/command/user_human_password_model.go b/internal/v2/command/user_human_password_model.go index 8820e9905e..c03f60f4bf 100644 --- a/internal/v2/command/user_human_password_model.go +++ b/internal/v2/command/user_human_password_model.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/user" + "time" ) type HumanPasswordWriteModel struct { @@ -13,6 +14,10 @@ type HumanPasswordWriteModel struct { Secret *crypto.CryptoValue SecretChangeRequired bool + Code *crypto.CryptoValue + CodeCreationDate time.Time + CodeExpiry time.Duration + UserState domain.UserState } @@ -43,6 +48,11 @@ func (wm *HumanPasswordWriteModel) Reduce() error { case *user.HumanPasswordChangedEvent: wm.Secret = e.Secret wm.SecretChangeRequired = e.ChangeRequired + wm.Code = nil + case *user.HumanPasswordCodeAddedEvent: + wm.Code = e.Code + wm.CodeCreationDate = e.CreationDate() + wm.CodeExpiry = e.Expiry case *user.HumanEmailVerifiedEvent: if wm.UserState == domain.UserStateInitial { wm.UserState = domain.UserStateActive @@ -55,7 +65,10 @@ func (wm *HumanPasswordWriteModel) Reduce() error { } func (wm *HumanPasswordWriteModel) Query() *eventstore.SearchQueryBuilder { - return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). - AggregateIDs(wm.AggregateID). - ResourceOwner(wm.ResourceOwner) + query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID) + if wm.ResourceOwner != "" { + query.ResourceOwner(wm.ResourceOwner) + } + return query } diff --git a/internal/v2/command/user_human_phone.go b/internal/v2/command/user_human_phone.go index 303e45cd2d..1f00e99e35 100644 --- a/internal/v2/command/user_human_phone.go +++ b/internal/v2/command/user_human_phone.go @@ -93,7 +93,7 @@ func (r *CommandSide) CreateHumanPhoneVerificationCode(ctx context.Context, user return err } if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved { - return caos_errs.ThrowNotFound(nil, "COMMAND-2M9fs", "Errors.User.Phone.NotFound") + return caos_errs.ThrowNotFound(nil, "COMMAND-2b7Hf", "Errors.User.Phone.NotFound") } if existingPhone.IsPhoneVerified { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified") @@ -107,6 +107,19 @@ func (r *CommandSide) CreateHumanPhoneVerificationCode(ctx context.Context, user return r.eventstore.PushAggregate(ctx, existingPhone, userAgg) } +func (r *CommandSide) HumanPhoneVerificationCodeSent(ctx context.Context, orgID, userID string) (err error) { + existingPhone, err := r.phoneWriteModelByID(ctx, userID, orgID) + if err != nil { + return err + } + if existingPhone.State == domain.PhoneStateUnspecified || existingPhone.State == domain.PhoneStateRemoved { + return caos_errs.ThrowNotFound(nil, "COMMAND-66n8J", "Errors.User.Phone.NotFound") + } + userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel) + userAgg.PushEvents(user.NewHumanPhoneCodeSentEvent(ctx)) + return r.eventstore.PushAggregate(ctx, existingPhone, userAgg) +} + func (r *CommandSide) RemoveHumanPhone(ctx context.Context, userID, resourceOwner string) error { if userID == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.UserIDMissing") diff --git a/internal/v2/command/user_human_webauthn.go b/internal/v2/command/user_human_webauthn.go index f5ff84709c..ebdf81bb32 100644 --- a/internal/v2/command/user_human_webauthn.go +++ b/internal/v2/command/user_human_webauthn.go @@ -33,7 +33,35 @@ func (r *CommandSide) getHumanPasswordlessTokens(ctx context.Context, userID, re return readModelToPasswordlessTokens(tokenReadModel), nil } -func (r *CommandSide) AddHumanU2F(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) { +func (r *CommandSide) getHumanU2FLogin(ctx context.Context, userID, authReqID, resourceowner string) (*domain.WebAuthNLogin, error) { + tokenReadModel := NewHumanU2FLoginReadModel(userID, authReqID, resourceowner) + err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel) + if err != nil { + return nil, err + } + if tokenReadModel.State == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5m88U", "Errors.User.NotFound") + } + return &domain.WebAuthNLogin{ + Challenge: tokenReadModel.Challenge, + }, nil +} + +func (r *CommandSide) getHumanPasswordlessLogin(ctx context.Context, userID, authReqID, resourceowner string) (*domain.WebAuthNLogin, error) { + tokenReadModel := NewHumanPasswordlessLoginReadModel(userID, authReqID, resourceowner) + err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel) + if err != nil { + return nil, err + } + if tokenReadModel.State == domain.UserStateDeleted { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-fm84R", "Errors.User.NotFound") + } + return &domain.WebAuthNLogin{ + Challenge: tokenReadModel.Challenge, + }, nil +} + +func (r *CommandSide) HumanAddU2FSetup(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) { u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner) if err != nil { return nil, err @@ -55,7 +83,7 @@ func (r *CommandSide) AddHumanU2F(ctx context.Context, userID, resourceowner str return createdWebAuthN, nil } -func (r *CommandSide) AddHumanPasswordless(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) { +func (r *CommandSide) HumanAddPasswordlessSetup(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) { passwordlessTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner) if err != nil { return nil, err @@ -64,7 +92,7 @@ func (r *CommandSide) AddHumanPasswordless(ctx context.Context, userID, resource if err != nil { return nil, err } - userAgg.PushEvents(usr_repo.NewHumanU2FAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge)) + userAgg.PushEvents(usr_repo.NewHumanPasswordlessAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge)) err = r.eventstore.PushAggregate(ctx, addWebAuthN, userAgg) if err != nil { @@ -114,7 +142,7 @@ func (r *CommandSide) addHumanWebAuthN(ctx context.Context, userID, resourceowne return addWebAuthN, userAgg, webAuthN, nil } -func (r *CommandSide) VerifyHumanU2F(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error { +func (r *CommandSide) HumanVerifyU2FSetup(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error { u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner) if err != nil { return err @@ -139,7 +167,7 @@ func (r *CommandSide) VerifyHumanU2F(ctx context.Context, userID, resourceowner, return r.eventstore.PushAggregate(ctx, verifyWebAuthN, userAgg) } -func (r *CommandSide) VerifyHumanPasswordless(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error { +func (r *CommandSide) HumanHumanPasswordlessSetup(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error { u2fTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner) if err != nil { return err @@ -149,7 +177,7 @@ func (r *CommandSide) VerifyHumanPasswordless(ctx context.Context, userID, resou return err } userAgg.PushEvents( - usr_repo.NewHumanU2FVerifiedEvent( + usr_repo.NewHumanPasswordlessVerifiedEvent( ctx, verifyWebAuthN.WebauthNTokenID, webAuthN.WebAuthNTokenName, @@ -186,12 +214,156 @@ func (r *CommandSide) verifyHumanWebAuthN(ctx context.Context, userID, resourceo return verifyWebAuthN, userAgg, webAuthN, nil } -func (r *CommandSide) RemoveHumanU2F(ctx context.Context, userID, webAuthNID, resourceOwner string) error { +func (r *CommandSide) HumanBeginU2FLogin(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest, isLoginUI bool) (*domain.WebAuthNLogin, error) { + u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceOwner) + if err != nil { + return nil, err + } + + writeModel, userAgg, webAuthNLogin, err := r.beginWebAuthNLogin(ctx, userID, resourceOwner, u2fTokens, isLoginUI) + if err != nil { + return nil, err + } + userAgg.PushEvents( + usr_repo.NewHumanU2FBeginLoginEvent( + ctx, + webAuthNLogin.Challenge, + authRequestDomainToAuthRequestInfo(authRequest), + ), + ) + + err = r.eventstore.PushAggregate(ctx, writeModel, userAgg) + return webAuthNLogin, err +} + +func (r *CommandSide) HumanBeginPasswordlessLogin(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest, isLoginUI bool) (*domain.WebAuthNLogin, error) { + u2fTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceOwner) + if err != nil { + return nil, err + } + + writeModel, userAgg, webAuthNLogin, err := r.beginWebAuthNLogin(ctx, userID, resourceOwner, u2fTokens, isLoginUI) + if err != nil { + return nil, err + } + userAgg.PushEvents( + usr_repo.NewHumanPasswordlessBeginLoginEvent( + ctx, + webAuthNLogin.Challenge, + authRequestDomainToAuthRequestInfo(authRequest), + ), + ) + + err = r.eventstore.PushAggregate(ctx, writeModel, userAgg) + return webAuthNLogin, err +} + +func (r *CommandSide) beginWebAuthNLogin(ctx context.Context, userID, resourceOwner string, tokens []*domain.WebAuthNToken, isLoginUI bool) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNLogin, error) { + if userID == "" { + return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-hh8K9", "Errors.IDMissing") + } + + human, err := r.getHuman(ctx, userID, resourceOwner) + if err != nil { + return nil, nil, nil, err + } + webAuthNLogin, err := r.webauthn.BeginLogin(human, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...) + if err != nil { + return nil, nil, nil, err + } + writeModel, err := r.webauthNWriteModelByID(ctx, userID, "", resourceOwner) + if err != nil { + return nil, nil, nil, err + } + + userAgg := UserAggregateFromWriteModel(&writeModel.WriteModel) + return writeModel, userAgg, webAuthNLogin, nil +} + +func (r *CommandSide) HumanFinishU2FLogin(ctx context.Context, userID, resourceOwner string, credentialData []byte, authRequest *domain.AuthRequest, isLoginUI bool) error { + webAuthNLogin, err := r.getHumanU2FLogin(ctx, userID, authRequest.ID, resourceOwner) + if err != nil { + return err + } + u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceOwner) + if err != nil { + return err + } + + writeModel, userAgg, token, signCount, err := r.finishWebAuthNLogin(ctx, userID, resourceOwner, credentialData, webAuthNLogin, u2fTokens, isLoginUI) + if err != nil { + return err + } + userAgg.PushEvents( + usr_repo.NewHumanU2FSignCountChangedEvent( + ctx, + token.WebAuthNTokenID, + signCount, + ), + ) + + return r.eventstore.PushAggregate(ctx, writeModel, userAgg) +} + +func (r *CommandSide) HumanFinishPasswordlessLogin(ctx context.Context, userID, resourceOwner string, credentialData []byte, authRequest *domain.AuthRequest, isLoginUI bool) error { + webAuthNLogin, err := r.getHumanPasswordlessLogin(ctx, userID, authRequest.ID, resourceOwner) + if err != nil { + return err + } + + passwordlessTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceOwner) + if err != nil { + return err + } + + writeModel, userAgg, token, signCount, err := r.finishWebAuthNLogin(ctx, userID, resourceOwner, credentialData, webAuthNLogin, passwordlessTokens, isLoginUI) + if err != nil { + return err + } + userAgg.PushEvents( + usr_repo.NewHumanPasswordlessSignCountChangedEvent( + ctx, + token.WebAuthNTokenID, + signCount, + ), + ) + + return r.eventstore.PushAggregate(ctx, writeModel, userAgg) +} + +func (r *CommandSide) finishWebAuthNLogin(ctx context.Context, userID, resourceOwner string, credentialData []byte, webAuthN *domain.WebAuthNLogin, tokens []*domain.WebAuthNToken, isLoginUI bool) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNToken, uint32, error) { + if userID == "" { + return nil, nil, nil, 0, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-hh8K9", "Errors.IDMissing") + } + + human, err := r.getHuman(ctx, userID, resourceOwner) + if err != nil { + return nil, nil, nil, 0, err + } + keyID, signCount, err := r.webauthn.FinishLogin(human, webAuthN, credentialData, isLoginUI, tokens...) + if err != nil && keyID == nil { + return nil, nil, nil, 0, err + } + + _, token := domain.GetTokenByKeyID(tokens, keyID) + if token == nil { + return nil, nil, nil, 0, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3b7zs", "Errors.User.WebAuthN.NotFound") + } + writeModel, err := r.webauthNWriteModelByID(ctx, userID, "", resourceOwner) + if err != nil { + return nil, nil, nil, 0, err + } + + userAgg := UserAggregateFromWriteModel(&writeModel.WriteModel) + return writeModel, userAgg, token, signCount, nil +} + +func (r *CommandSide) HumanRemoveU2F(ctx context.Context, userID, webAuthNID, resourceOwner string) error { 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 { +func (r *CommandSide) HumanRemovePasswordless(ctx context.Context, userID, webAuthNID, resourceOwner string) error { event := usr_repo.NewHumanPasswordlessRemovedEvent(ctx, webAuthNID) return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event) } diff --git a/internal/v2/command/user_human_webauthn_model.go b/internal/v2/command/user_human_webauthn_model.go index 6c01cbf133..85f17b0d5a 100644 --- a/internal/v2/command/user_human_webauthn_model.go +++ b/internal/v2/command/user_human_webauthn_model.go @@ -39,6 +39,14 @@ func (wm *HumanWebAuthNWriteModel) AppendEvents(events ...eventstore.EventReader if wm.WebauthNTokenID == e.WebAuthNTokenID { wm.WriteModel.AppendEvents(e) } + case *user.HumanWebAuthNVerifiedEvent: + if wm.WebauthNTokenID == e.WebAuthNTokenID { + wm.WriteModel.AppendEvents(e) + } + case *user.HumanWebAuthNSignCountChangedEvent: + if wm.WebauthNTokenID == e.WebAuthNTokenID { + wm.WriteModel.AppendEvents(e) + } case *user.HumanWebAuthNRemovedEvent: if wm.WebauthNTokenID == e.WebAuthNTokenID { wm.WriteModel.AppendEvents(e) @@ -56,6 +64,8 @@ func (wm *HumanWebAuthNWriteModel) Reduce() error { wm.appendAddedEvent(e) case *user.HumanWebAuthNVerifiedEvent: wm.appendVerifiedEvent(e) + case *user.HumanWebAuthNSignCountChangedEvent: + wm.SignCount = e.SignCount case *user.HumanWebAuthNRemovedEvent: wm.State = domain.MFAStateRemoved case *user.UserRemovedEvent: @@ -104,34 +114,35 @@ func NewHumanU2FTokensReadModel(userID, resourceOwner string) *HumanU2FTokensRea } 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) - } - } + wm.WriteModel.AppendEvents(events...) } func (wm *HumanU2FTokensReadModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { - case *user.HumanWebAuthNAddedEvent: + case *user.HumanU2FAddedEvent: token := &HumanWebAuthNWriteModel{} - token.appendAddedEvent(e) - wm.WebAuthNTokens = append(wm.WebAuthNTokens, token) - case *user.HumanWebAuthNVerifiedEvent: + token.appendAddedEvent(&e.HumanWebAuthNAddedEvent) + token.WriteModel = eventstore.WriteModel{ + AggregateID: e.AggregateID(), + } + replaced := false + for i, existingTokens := range wm.WebAuthNTokens { + if existingTokens.State == domain.MFAStateNotReady { + wm.WebAuthNTokens[i] = token + replaced = true + } + } + if !replaced { + wm.WebAuthNTokens = append(wm.WebAuthNTokens, token) + } + case *user.HumanU2FVerifiedEvent: idx, token := wm.WebAuthNTokenByID(e.WebAuthNTokenID) if idx < 0 { continue } - token.appendVerifiedEvent(e) - case *user.HumanWebAuthNRemovedEvent: + token.appendVerifiedEvent(&e.HumanWebAuthNVerifiedEvent) + case *user.HumanU2FRemovedEvent: idx, _ := wm.WebAuthNTokenByID(e.WebAuthNTokenID) if idx < 0 { continue @@ -153,8 +164,7 @@ func (rm *HumanU2FTokensReadModel) Query() *eventstore.SearchQueryBuilder { EventTypes( user.HumanU2FTokenAddedType, user.HumanU2FTokenVerifiedType, - user.HumanU2FTokenRemovedType, - user.UserV1MFAOTPRemovedType) + user.HumanU2FTokenRemovedType) } @@ -190,17 +200,29 @@ func (wm *HumanPasswordlessTokensReadModel) AppendEvents(events ...eventstore.Ev func (wm *HumanPasswordlessTokensReadModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { - case *user.HumanWebAuthNAddedEvent: + case *user.HumanPasswordlessAddedEvent: token := &HumanWebAuthNWriteModel{} - token.appendAddedEvent(e) - wm.WebAuthNTokens = append(wm.WebAuthNTokens, token) - case *user.HumanWebAuthNVerifiedEvent: + token.appendAddedEvent(&e.HumanWebAuthNAddedEvent) + token.WriteModel = eventstore.WriteModel{ + AggregateID: e.AggregateID(), + } + replaced := false + for i, existingTokens := range wm.WebAuthNTokens { + if existingTokens.State == domain.MFAStateNotReady { + wm.WebAuthNTokens[i] = token + replaced = true + } + } + if !replaced { + wm.WebAuthNTokens = append(wm.WebAuthNTokens, token) + } + case *user.HumanPasswordlessVerifiedEvent: idx, token := wm.WebAuthNTokenByID(e.WebAuthNTokenID) if idx < 0 { continue } - token.appendVerifiedEvent(e) - case *user.HumanWebAuthNRemovedEvent: + token.appendVerifiedEvent(&e.HumanWebAuthNVerifiedEvent) + case *user.HumanPasswordlessRemovedEvent: idx, _ := wm.WebAuthNTokenByID(e.WebAuthNTokenID) if idx < 0 { continue @@ -222,8 +244,7 @@ func (rm *HumanPasswordlessTokensReadModel) Query() *eventstore.SearchQueryBuild EventTypes( user.HumanPasswordlessTokenAddedType, user.HumanPasswordlessTokenVerifiedType, - user.HumanPasswordlessTokenRemovedType, - user.UserV1MFAOTPRemovedType) + user.HumanPasswordlessTokenRemovedType) } @@ -235,3 +256,114 @@ func (wm *HumanPasswordlessTokensReadModel) WebAuthNTokenByID(id string) (idx in } return -1, nil } + +type HumanU2FLoginReadModel struct { + eventstore.WriteModel + + AuthReqID string + Challenge string + State domain.UserState +} + +func NewHumanU2FLoginReadModel(userID, authReqID, resourceOwner string) *HumanU2FLoginReadModel { + return &HumanU2FLoginReadModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + ResourceOwner: resourceOwner, + }, + AuthReqID: authReqID, + } +} + +func (wm *HumanU2FLoginReadModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.HumanU2FBeginLoginEvent: + if e.AuthRequestInfo.ID != wm.AuthReqID { + continue + } + wm.WriteModel.AppendEvents(e) + case *user.UserRemovedEvent: + wm.WriteModel.AppendEvents(e) + } + } +} + +func (wm *HumanU2FLoginReadModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanU2FBeginLoginEvent: + wm.Challenge = e.Challenge + wm.State = domain.UserStateActive + case *user.UserRemovedEvent: + wm.State = domain.UserStateDeleted + } + } + return wm.WriteModel.Reduce() +} + +func (rm *HumanU2FLoginReadModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(rm.AggregateID). + ResourceOwner(rm.ResourceOwner). + EventTypes( + user.HumanU2FTokenBeginLoginType, + user.UserRemovedType, + ) +} + +type HumanPasswordlessLoginReadModel struct { + eventstore.WriteModel + + AuthReqID string + Challenge string + State domain.UserState +} + +func NewHumanPasswordlessLoginReadModel(userID, authReqID, resourceOwner string) *HumanPasswordlessLoginReadModel { + return &HumanPasswordlessLoginReadModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + ResourceOwner: resourceOwner, + }, + AuthReqID: authReqID, + } +} + +func (wm *HumanPasswordlessLoginReadModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.HumanPasswordlessBeginLoginEvent: + if e.AuthRequestInfo.ID != wm.AuthReqID { + continue + } + wm.WriteModel.AppendEvents(e) + case *user.UserRemovedEvent: + wm.WriteModel.AppendEvents(e) + } + } +} + +func (wm *HumanPasswordlessLoginReadModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.HumanPasswordlessBeginLoginEvent: + wm.Challenge = e.Challenge + wm.State = domain.UserStateActive + case *user.UserRemovedEvent: + wm.State = domain.UserStateDeleted + } + } + return wm.WriteModel.Reduce() +} + +func (rm *HumanPasswordlessLoginReadModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(rm.AggregateID). + ResourceOwner(rm.ResourceOwner). + EventTypes( + user.HumanPasswordlessTokenBeginLoginType, + user.UserRemovedType, + ) + +} diff --git a/internal/v2/command/user_machine.go b/internal/v2/command/user_machine.go index c46cdfe503..b7620481e6 100644 --- a/internal/v2/command/user_machine.go +++ b/internal/v2/command/user_machine.go @@ -51,7 +51,7 @@ func (r *CommandSide) ChangeMachine(ctx context.Context, machine *domain.Machine changedEvent, hasChanged := existingUser.NewChangedEvent(ctx, machine.Name, machine.Description) if !hasChanged { - return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Email.NotChanged") + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2n8vs", "Errors.User.Email.NotChanged") } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg.PushEvents(changedEvent) diff --git a/internal/v2/command/user_machine_key.go b/internal/v2/command/user_machine_key.go new file mode 100644 index 0000000000..04d37dd192 --- /dev/null +++ b/internal/v2/command/user_machine_key.go @@ -0,0 +1,83 @@ +package command + +import ( + "context" + "github.com/caos/logging" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" + "time" +) + +const ( + yearLayout = "2006-01-02" + defaultExpirationDate = "9999-01-01" +) + +func (r *CommandSide) AddUserMachineKey(ctx context.Context, machineKey *domain.MachineKey, resourceOwner string) (*domain.MachineKey, error) { + err := r.checkUserExists(ctx, machineKey.AggregateID, resourceOwner) + if err != nil { + return nil, err + } + keyID, err := r.idGenerator.Next() + if err != nil { + return nil, err + } + keyWriteModel := NewMachineKeyWriteModel(machineKey.AggregateID, keyID, resourceOwner) + userAgg := UserAggregateFromWriteModel(&keyWriteModel.WriteModel) + err = r.eventstore.FilterToQueryReducer(ctx, keyWriteModel) + if err != nil { + return nil, err + } + + if machineKey.ExpirationDate.IsZero() { + machineKey.ExpirationDate, err = time.Parse(yearLayout, defaultExpirationDate) + if err != nil { + logging.Log("COMMAND9-v8jMi").WithError(err).Warn("unable to set default date") + return nil, errors.ThrowInternal(err, "COMMAND-38jfus", "Errors.Internal") + } + } + if machineKey.ExpirationDate.Before(time.Now()) { + return nil, errors.ThrowInvalidArgument(nil, "COMMAND-38vns", "Errors.MachineKey.ExpireBeforeNow") + } + + machineKey.GenerateNewMachineKeyPair(r.machineKeySize) + + userAgg.PushEvents(user.NewMachineKeyAddedEvent(ctx, keyID, machineKey.Type, machineKey.ExpirationDate, machineKey.PublicKey)) + err = r.eventstore.PushAggregate(ctx, keyWriteModel, userAgg) + if err != nil { + return nil, err + } + key := keyWriteModelToMachineKey(keyWriteModel) + key.PrivateKey = machineKey.PrivateKey + return key, nil +} + +func (r *CommandSide) RemoveUserMachineKey(ctx context.Context, userID, keyID, resourceOwner string) error { + keyWriteModel, err := r.machineKeyWriteModelByID(ctx, userID, keyID, resourceOwner) + if err != nil { + return err + } + if keyWriteModel.State == domain.MachineKeyStateUnspecified || keyWriteModel.State == domain.MachineKeyStateRemoved { + return errors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.User.Machine.Key.NotFound") + } + userAgg := UserAggregateFromWriteModel(&keyWriteModel.WriteModel) + userAgg.PushEvents(user.NewMachineKeyRemovedEvent(ctx, keyID)) + return r.eventstore.PushAggregate(ctx, keyWriteModel, userAgg) +} + +func (r *CommandSide) machineKeyWriteModelByID(ctx context.Context, userID, keyID, resourceOwner string) (writeModel *MachineKeyWriteModel, err error) { + if userID == "" { + return nil, errors.ThrowInvalidArgument(nil, "COMMAND-4n8vs", "Errors.User.UserIDMissing") + } + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + writeModel = NewMachineKeyWriteModel(userID, keyID, resourceOwner) + err = r.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + return writeModel, nil +} diff --git a/internal/v2/command/user_machine_key_model.go b/internal/v2/command/user_machine_key_model.go new file mode 100644 index 0000000000..5fc20fbe65 --- /dev/null +++ b/internal/v2/command/user_machine_key_model.go @@ -0,0 +1,74 @@ +package command + +import ( + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/user" + "time" +) + +type MachineKeyWriteModel struct { + eventstore.WriteModel + + KeyID string + KeyType domain.MachineKeyType + ExpirationDate time.Time + + State domain.MachineKeyState +} + +func NewMachineKeyWriteModel(userID, keyID, resourceOwner string) *MachineKeyWriteModel { + return &MachineKeyWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: userID, + ResourceOwner: resourceOwner, + }, + KeyID: keyID, + } +} + +func (wm *MachineKeyWriteModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.MachineKeyAddedEvent: + if wm.KeyID != e.KeyID { + continue + } + wm.WriteModel.AppendEvents(e) + case *user.MachineKeyRemovedEvent: + if wm.KeyID != e.KeyID { + continue + } + wm.WriteModel.AppendEvents(e) + case *user.UserRemovedEvent: + wm.WriteModel.AppendEvents(e) + } + } +} + +func (wm *MachineKeyWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *user.MachineKeyAddedEvent: + wm.KeyID = e.KeyID + wm.KeyType = e.KeyType + wm.ExpirationDate = e.ExpirationDate + wm.State = domain.MachineKeyStateActive + case *user.MachineKeyRemovedEvent: + wm.State = domain.MachineKeyStateRemoved + case *user.UserRemovedEvent: + wm.State = domain.MachineKeyStateRemoved + } + } + return wm.WriteModel.Reduce() +} + +func (wm *MachineKeyWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(wm.AggregateID). + ResourceOwner(wm.ResourceOwner). + EventTypes( + user.MachineKeyAddedEventType, + user.MachineKeyRemovedEventType, + user.UserRemovedType) +} diff --git a/internal/v2/command/user_model.go b/internal/v2/command/user_model.go index 23c9fe87c2..b058396edc 100644 --- a/internal/v2/command/user_model.go +++ b/internal/v2/command/user_model.go @@ -39,6 +39,8 @@ func (wm *UserWriteModel) Reduce() error { case *user.HumanRegisteredEvent: wm.UserName = e.UserName wm.UserState = domain.UserStateInitial + case *user.HumanInitializedCheckSucceededEvent: + wm.UserState = domain.UserStateActive case *user.MachineAddedEvent: wm.UserName = e.UserName wm.UserState = domain.UserStateActive diff --git a/internal/v2/domain/auth_request.go b/internal/v2/domain/auth_request.go new file mode 100644 index 0000000000..13187dc22f --- /dev/null +++ b/internal/v2/domain/auth_request.go @@ -0,0 +1,150 @@ +package domain + +import ( + "github.com/caos/zitadel/internal/errors" + "golang.org/x/text/language" + "strings" + "time" +) + +type AuthRequest struct { + ID string + AgentID string + CreationDate time.Time + ChangeDate time.Time + BrowserInfo *BrowserInfo + ApplicationID string + CallbackURI string + TransferState string + Prompt Prompt + PossibleLOAs []LevelOfAssurance + UiLocales []string + LoginHint string + MaxAuthAge uint32 + Request Request + + levelOfAssurance LevelOfAssurance + UserID string + LoginName string + DisplayName string + UserOrgID string + RequestedOrgID string + RequestedOrgName string + SelectedIDPConfigID string + LinkingUsers []*ExternalUser + PossibleSteps []NextStep + PasswordVerified bool + MFAsVerified []MFAType + Audience []string + AuthTime time.Time + Code string + LoginPolicy *LoginPolicy + AllowedExternalIDPs []*IDPProvider +} + +type ExternalUser struct { + IDPConfigID string + ExternalUserID string + DisplayName string + PreferredUsername string + FirstName string + LastName string + NickName string + Email string + IsEmailVerified bool + PreferredLanguage language.Tag + Phone string + IsPhoneVerified bool +} + +type Prompt int32 + +const ( + PromptUnspecified Prompt = iota + PromptNone + PromptLogin + PromptConsent + PromptSelectAccount +) + +type LevelOfAssurance int + +const ( + LevelOfAssuranceNone LevelOfAssurance = iota +) + +type MFAType int + +const ( + MFATypeOTP MFAType = iota + MFATypeU2F + MFATypeU2FUserVerification +) + +type MFALevel int + +const ( + MFALevelNotSetUp MFALevel = iota + MFALevelSecondFactor + MFALevelMultiFactor + MFALevelMultiFactorCertified +) + +func NewAuthRequestFromType(requestType AuthRequestType) (*AuthRequest, error) { + request, ok := authRequestTypeMapping[requestType] + if !ok { + return nil, errors.ThrowInvalidArgument(nil, "DOMAIN-ds2kl", "invalid request type") + } + return &AuthRequest{Request: request}, nil +} + +func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest { + a.BrowserInfo = info + return a +} + +func (a *AuthRequest) SetUserInfo(userID, loginName, displayName, userOrgID string) { + a.UserID = userID + a.LoginName = loginName + a.DisplayName = displayName + a.UserOrgID = userOrgID +} + +func (a *AuthRequest) MFALevel() MFALevel { + return -1 + //PLANNED: check a.PossibleLOAs (and Prompt Login?) +} + +func (a *AuthRequest) AppendAudIfNotExisting(aud string) { + for _, a := range a.Audience { + if a == aud { + return + } + } + a.Audience = append(a.Audience, aud) +} + +func (a *AuthRequest) GetScopeProjectIDsForAud() []string { + projectIDs := make([]string, 0) + switch request := a.Request.(type) { + case *AuthRequestOIDC: + for _, scope := range request.Scopes { + if strings.HasPrefix(scope, ProjectIDScope) && strings.HasSuffix(scope, AudSuffix) { + projectIDs = append(projectIDs, strings.TrimSuffix(strings.TrimPrefix(scope, ProjectIDScope), AudSuffix)) + } + } + } + return projectIDs +} + +func (a *AuthRequest) GetScopeOrgPrimaryDomain() string { + switch request := a.Request.(type) { + case *AuthRequestOIDC: + for _, scope := range request.Scopes { + if strings.HasPrefix(scope, OrgDomainPrimaryScope) { + return strings.TrimPrefix(scope, OrgDomainPrimaryScope) + } + } + } + return "" +} diff --git a/internal/v2/domain/browser_info.go b/internal/v2/domain/browser_info.go new file mode 100644 index 0000000000..d6e8bd5d50 --- /dev/null +++ b/internal/v2/domain/browser_info.go @@ -0,0 +1,22 @@ +package domain + +import ( + "net" + net_http "net/http" + + http_util "github.com/caos/zitadel/internal/api/http" +) + +type BrowserInfo struct { + UserAgent string + AcceptLanguage string + RemoteIP net.IP +} + +func BrowserInfoFromRequest(r *net_http.Request) *BrowserInfo { + return &BrowserInfo{ + UserAgent: r.Header.Get(http_util.UserAgentHeader), + AcceptLanguage: r.Header.Get(http_util.AcceptLanguage), + RemoteIP: http_util.RemoteIPFromRequest(r), + } +} diff --git a/internal/v2/domain/human.go b/internal/v2/domain/human.go index b476e54d35..f2568b287c 100644 --- a/internal/v2/domain/human.go +++ b/internal/v2/domain/human.go @@ -60,15 +60,15 @@ func (u *Human) IsValid() bool { return u.Profile != nil && u.FirstName != "" && u.LastName != "" && u.Email != nil && u.Email.IsValid() && u.Phone == nil || (u.Phone != nil && u.Phone.PhoneNumber != "" && u.Phone.IsValid()) } -func (u *Human) CheckOrgIAMPolicy(userName string, policy *OrgIAMPolicy) error { +func (u *Human) CheckOrgIAMPolicy(policy *OrgIAMPolicy) error { if policy == nil { return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-zSH7j", "Errors.Users.OrgIamPolicyNil") } - if policy.UserLoginMustBeDomain && strings.Contains(userName, "@") { + if policy.UserLoginMustBeDomain && strings.Contains(u.Username, "@") { return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-se4sJ", "Errors.User.EmailAsUsernameNotAllowed") } - if !policy.UserLoginMustBeDomain && u.Profile != nil && userName == "" && u.Email != nil { - userName = u.EmailAddress + if !policy.UserLoginMustBeDomain && u.Profile != nil && u.Username == "" && u.Email != nil { + u.Username = u.EmailAddress } return nil } diff --git a/internal/v2/domain/human_web_auth_n.go b/internal/v2/domain/human_web_auth_n.go index 5c6004aa12..225b87ac4e 100644 --- a/internal/v2/domain/human_web_auth_n.go +++ b/internal/v2/domain/human_web_auth_n.go @@ -1,6 +1,9 @@ package domain -import es_models "github.com/caos/zitadel/internal/eventstore/models" +import ( + "bytes" + es_models "github.com/caos/zitadel/internal/eventstore/models" +) type WebAuthNToken struct { es_models.ObjectRoot @@ -55,3 +58,12 @@ func GetTokenToVerify(tokens []*WebAuthNToken) (int, *WebAuthNToken) { } return -1, nil } + +func GetTokenByKeyID(tokens []*WebAuthNToken, keyID []byte) (int, *WebAuthNToken) { + for i, token := range tokens { + if bytes.Compare(token.KeyID, keyID) == 0 { + return i, token + } + } + return -1, nil +} diff --git a/internal/v2/domain/idp_config.go b/internal/v2/domain/idp_config.go index 7a45182f81..092f8efeae 100644 --- a/internal/v2/domain/idp_config.go +++ b/internal/v2/domain/idp_config.go @@ -89,3 +89,12 @@ const ( func (f IDPConfigStylingType) Valid() bool { return f >= 0 && f < idpConfigStylingTypeCount } + +func (st IDPConfigStylingType) GetCSSClass() string { + switch st { + case IDPConfigStylingTypeGoogle: + return "google" + default: + return "" + } +} diff --git a/internal/v2/domain/machine_key.go b/internal/v2/domain/machine_key.go index 239558e18d..e6624ac00a 100644 --- a/internal/v2/domain/machine_key.go +++ b/internal/v2/domain/machine_key.go @@ -1,5 +1,21 @@ package domain +import ( + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/eventstore/models" + "time" +) + +type MachineKey struct { + models.ObjectRoot + + KeyID string + Type MachineKeyType + ExpirationDate time.Time + PrivateKey []byte + PublicKey []byte +} + type MachineKeyType int32 const ( @@ -9,6 +25,33 @@ const ( keyCount ) +type MachineKeyState int32 + +const ( + MachineKeyStateUnspecified MachineKeyState = iota + MachineKeyStateActive + MachineKeyStateRemoved + + machineKeyStateCount +) + +func (f MachineKeyState) Valid() bool { + return f >= 0 && f < machineKeyStateCount +} + func (f MachineKeyType) Valid() bool { return f >= 0 && f < keyCount } + +func (key *MachineKey) GenerateNewMachineKeyPair(keySize int) error { + privateKey, publicKey, err := crypto.GenerateKeyPair(keySize) + if err != nil { + return err + } + key.PublicKey, err = crypto.PublicKeyToBytes(publicKey) + if err != nil { + return err + } + key.PrivateKey = crypto.PrivateKeyToBytes(privateKey) + return nil +} diff --git a/internal/v2/domain/next_step.go b/internal/v2/domain/next_step.go new file mode 100644 index 0000000000..8f261487b8 --- /dev/null +++ b/internal/v2/domain/next_step.go @@ -0,0 +1,149 @@ +package domain + +type NextStep interface { + Type() NextStepType +} + +type NextStepType int32 + +const ( + NextStepUnspecified NextStepType = iota + NextStepLogin + NextStepUserSelection + NextStepInitUser + NextStepPassword + NextStepChangePassword + NextStepInitPassword + NextStepVerifyEmail + NextStepMFAPrompt + NextStepMFAVerify + NextStepRedirectToCallback + NextStepChangeUsername + NextStepLinkUsers + NextStepExternalNotFoundOption + NextStepExternalLogin + NextStepGrantRequired + NextStepPasswordless +) + +type LoginStep struct{} + +func (s *LoginStep) Type() NextStepType { + return NextStepLogin +} + +type SelectUserStep struct { + Users []UserSelection +} + +func (s *SelectUserStep) Type() NextStepType { + return NextStepUserSelection +} + +type UserSelection struct { + UserID string + DisplayName string + LoginName string + UserSessionState UserSessionState + SelectionPossible bool +} + +type UserSessionState int32 + +const ( + UserSessionStateActive UserSessionState = iota + UserSessionStateTerminated +) + +type InitUserStep struct { + PasswordSet bool +} + +func (s *InitUserStep) Type() NextStepType { + return NextStepInitUser +} + +type ExternalNotFoundOptionStep struct{} + +func (s *ExternalNotFoundOptionStep) Type() NextStepType { + return NextStepExternalNotFoundOption +} + +type PasswordStep struct{} + +func (s *PasswordStep) Type() NextStepType { + return NextStepPassword +} + +type ExternalLoginStep struct { + SelectedIDPConfigID string +} + +func (s *ExternalLoginStep) Type() NextStepType { + return NextStepExternalLogin +} + +type PasswordlessStep struct{} + +func (s *PasswordlessStep) Type() NextStepType { + return NextStepPasswordless +} + +type ChangePasswordStep struct{} + +func (s *ChangePasswordStep) Type() NextStepType { + return NextStepChangePassword +} + +type InitPasswordStep struct{} + +func (s *InitPasswordStep) Type() NextStepType { + return NextStepInitPassword +} + +type ChangeUsernameStep struct{} + +func (s *ChangeUsernameStep) Type() NextStepType { + return NextStepChangeUsername +} + +type VerifyEMailStep struct{} + +func (s *VerifyEMailStep) Type() NextStepType { + return NextStepVerifyEmail +} + +type MFAPromptStep struct { + Required bool + MFAProviders []MFAType +} + +func (s *MFAPromptStep) Type() NextStepType { + return NextStepMFAPrompt +} + +type MFAVerificationStep struct { + MFAProviders []MFAType +} + +func (s *MFAVerificationStep) Type() NextStepType { + return NextStepMFAVerify +} + +type LinkUsersStep struct{} + +func (s *LinkUsersStep) Type() NextStepType { + return NextStepLinkUsers +} + +type GrantRequiredStep struct{} + +func (s *GrantRequiredStep) Type() NextStepType { + return NextStepGrantRequired +} + +type RedirectToCallbackStep struct{} + +func (s *RedirectToCallbackStep) Type() NextStepType { + return NextStepRedirectToCallback +} diff --git a/internal/v2/domain/oidc_code_challenge.go b/internal/v2/domain/oidc_code_challenge.go new file mode 100644 index 0000000000..aed62b9277 --- /dev/null +++ b/internal/v2/domain/oidc_code_challenge.go @@ -0,0 +1,17 @@ +package domain + +type OIDCCodeChallenge struct { + Challenge string + Method OIDCCodeChallengeMethod +} + +func (c *OIDCCodeChallenge) IsValid() bool { + return c.Challenge != "" +} + +type OIDCCodeChallengeMethod int32 + +const ( + CodeChallengeMethodPlain OIDCCodeChallengeMethod = iota + CodeChallengeMethodS256 +) diff --git a/internal/v2/domain/policy_login.go b/internal/v2/domain/policy_login.go index ee6eeab0db..14f8b65a13 100644 --- a/internal/v2/domain/policy_login.go +++ b/internal/v2/domain/policy_login.go @@ -8,7 +8,7 @@ type LoginPolicy struct { Default bool AllowUsernamePassword bool AllowRegister bool - AllowExternalIdp bool + AllowExternalIDP bool IDPProviders []*IDPProvider ForceMFA bool SecondFactors []SecondFactorType @@ -20,6 +20,11 @@ type IDPProvider struct { models.ObjectRoot Type IdentityProviderType IDPConfigID string + + Name string + StylingType IDPConfigStylingType + IDPConfigType IDPConfigType + IDPState IDPConfigState } type PasswordlessType int32 @@ -34,3 +39,11 @@ const ( func (f PasswordlessType) Valid() bool { return f >= 0 && f < passwordlessCount } + +func (p *LoginPolicy) HasSecondFactors() bool { + return len(p.SecondFactors) > 0 +} + +func (p *LoginPolicy) HasMultiFactors() bool { + return len(p.MultiFactors) > 0 +} diff --git a/internal/v2/domain/request.go b/internal/v2/domain/request.go new file mode 100644 index 0000000000..b31d85fe57 --- /dev/null +++ b/internal/v2/domain/request.go @@ -0,0 +1,54 @@ +package domain + +const ( + OrgDomainPrimaryScope = "urn:zitadel:iam:org:domain:primary:" + OrgDomainPrimaryClaim = "urn:zitadel:iam:org:domain:primary" + ProjectIDScope = "urn:zitadel:iam:org:project:id:" + AudSuffix = ":aud" +) + +//TODO: Change AuthRequest to interface and let oidcauthreqesut implement it +type Request interface { + Type() AuthRequestType + IsValid() bool +} + +type AuthRequestType int32 + +var ( + authRequestTypeMapping = map[AuthRequestType]Request{ + AuthRequestTypeOIDC: &AuthRequestOIDC{}, + } +) + +const ( + AuthRequestTypeOIDC AuthRequestType = iota + AuthRequestTypeSAML +) + +type AuthRequestOIDC struct { + Scopes []string + ResponseType OIDCResponseType + Nonce string + CodeChallenge *OIDCCodeChallenge +} + +func (a *AuthRequestOIDC) Type() AuthRequestType { + return AuthRequestTypeOIDC +} + +func (a *AuthRequestOIDC) IsValid() bool { + return len(a.Scopes) > 0 && + a.CodeChallenge == nil || a.CodeChallenge != nil && a.CodeChallenge.IsValid() +} + +type AuthRequestSAML struct { +} + +func (a *AuthRequestSAML) Type() AuthRequestType { + return AuthRequestTypeSAML +} + +func (a *AuthRequestSAML) IsValid() bool { + return true +} diff --git a/internal/v2/domain/roles.go b/internal/v2/domain/roles.go index 1c1fa189dc..ee87906520 100644 --- a/internal/v2/domain/roles.go +++ b/internal/v2/domain/roles.go @@ -2,6 +2,7 @@ package domain const ( RoleOrgOwner = "ORG_OWNER" + RoleOrgProjectCreator = "ORG_PROJECT_CREATOR" RoleIAMOwner = "IAM_OWNER" RoleProjectOwner = "PROJECT_OWNER" RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL" diff --git a/internal/v2/domain/token.go b/internal/v2/domain/token.go new file mode 100644 index 0000000000..f5219d368e --- /dev/null +++ b/internal/v2/domain/token.go @@ -0,0 +1,18 @@ +package domain + +import ( + es_models "github.com/caos/zitadel/internal/eventstore/models" + "time" +) + +type Token struct { + es_models.ObjectRoot + + TokenID string + ApplicationID string + UserAgentID string + Audience []string + Expiration time.Time + Scopes []string + PreferredLanguage string +} diff --git a/internal/v2/query/query.go b/internal/v2/query/query.go index 464491dc7e..599b0da5ca 100644 --- a/internal/v2/query/query.go +++ b/internal/v2/query/query.go @@ -2,6 +2,7 @@ package query import ( "context" + usr_repo "github.com/caos/zitadel/internal/v2/repository/user" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/crypto" @@ -31,6 +32,7 @@ func StartQuerySide(config *Config) (repo *QuerySide, err error) { idGenerator: id.SonyFlakeGenerator, } iam_repo.RegisterEventMappers(repo.eventstore) + usr_repo.RegisterEventMappers(repo.eventstore) repo.secretCrypto, err = crypto.NewAESCrypto(config.SystemDefaults.IDPConfigVerificationKey) if err != nil { diff --git a/internal/v2/query/user.go b/internal/v2/query/user.go new file mode 100644 index 0000000000..815a04f0de --- /dev/null +++ b/internal/v2/query/user.go @@ -0,0 +1,11 @@ +package query + +import ( + "context" + "github.com/caos/zitadel/internal/eventstore/v2" +) + +func (q *QuerySide) UserEvents(ctx context.Context, orgID, userID string, sequence uint64) ([]eventstore.EventReader, error) { + query := NewUserEventSearchQuery(userID, orgID, sequence) + return q.eventstore.FilterEvents(ctx, query) +} diff --git a/internal/v2/query/user_model.go b/internal/v2/query/user_model.go index 1059fe6603..9115459bac 100644 --- a/internal/v2/query/user_model.go +++ b/internal/v2/query/user_model.go @@ -62,3 +62,8 @@ func UserAggregateFromReadModel(rm *UserReadModel) *user.Aggregate { ), } } + +func NewUserEventSearchQuery(userID, orgID string, sequence uint64) *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType). + AggregateIDs(userID).ResourceOwner(orgID).SequenceGreater(sequence) +} diff --git a/internal/v2/repository/idpconfig/oidc_config.go b/internal/v2/repository/idpconfig/oidc_config.go index a0830708d8..e7c21c6d7c 100644 --- a/internal/v2/repository/idpconfig/oidc_config.go +++ b/internal/v2/repository/idpconfig/oidc_config.go @@ -22,7 +22,7 @@ type OIDCConfigAddedEvent struct { ClientID string `json:"clientId,omitempty"` ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` Issuer string `json:"issuer,omitempty"` - Scopes []string `json:"scpoes,omitempty"` + Scopes []string `json:"scopes,omitempty"` IDPDisplayNameMapping domain.OIDCMappingField `json:"idpDisplayNameMapping,omitempty"` UserNameMapping domain.OIDCMappingField `json:"usernameMapping,omitempty"` @@ -80,7 +80,7 @@ type OIDCConfigChangedEvent struct { ClientID *string `json:"clientId,omitempty"` ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` Issuer *string `json:"issuer,omitempty"` - Scopes []string `json:"scpoes,omitempty"` + Scopes []string `json:"scopes,omitempty"` IDPDisplayNameMapping *domain.OIDCMappingField `json:"idpDisplayNameMapping,omitempty"` UserNameMapping *domain.OIDCMappingField `json:"usernameMapping,omitempty"` diff --git a/internal/v2/repository/user/auth_request_info.go b/internal/v2/repository/user/auth_request_info.go new file mode 100644 index 0000000000..7a2d118a8b --- /dev/null +++ b/internal/v2/repository/user/auth_request_info.go @@ -0,0 +1,16 @@ +package user + +import "net" + +type AuthRequestInfo struct { + ID string `json:"id,omitempty"` + UserAgentID string `json:"userAgentID,omitempty"` + SelectedIDPConfigID string `json:"selectedIDPConfigID,omitempty"` + *BrowserInfo +} + +type BrowserInfo struct { + UserAgent string `json:"userAgent,omitempty"` + AcceptLanguage string `json:"acceptLanguage,omitempty"` + RemoteIP net.IP `json:"remoteIP,omitempty"` +} diff --git a/internal/v2/repository/user/eventstore.go b/internal/v2/repository/user/eventstore.go index a53044ebb9..a3d50f3865 100644 --- a/internal/v2/repository/user/eventstore.go +++ b/internal/v2/repository/user/eventstore.go @@ -80,20 +80,20 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(HumanMFAOTPRemovedType, HumanOTPRemovedEventMapper). RegisterFilterEventMapper(HumanMFAOTPCheckSucceededType, HumanOTPCheckSucceededEventMapper). RegisterFilterEventMapper(HumanMFAOTPCheckFailedType, HumanOTPCheckFailedEventMapper). - RegisterFilterEventMapper(HumanU2FTokenAddedType, WebAuthNAddedEventMapper). - RegisterFilterEventMapper(HumanU2FTokenVerifiedType, HumanWebAuthNVerifiedEventMapper). - RegisterFilterEventMapper(HumanU2FTokenSignCountChangedType, HumanWebAuthNSignCountChangedEventMapper). - RegisterFilterEventMapper(HumanU2FTokenRemovedType, HumanWebAuthNRemovedEventMapper). - RegisterFilterEventMapper(HumanU2FTokenBeginLoginType, HumanWebAuthNBeginLoginEventMapper). - RegisterFilterEventMapper(HumanU2FTokenCheckSucceededType, HumanWebAuthNCheckSucceededEventMapper). - RegisterFilterEventMapper(HumanU2FTokenCheckFailedType, HumanWebAuthNCheckFailedEventMapper). - RegisterFilterEventMapper(HumanPasswordlessTokenAddedType, WebAuthNAddedEventMapper). - RegisterFilterEventMapper(HumanPasswordlessTokenVerifiedType, HumanWebAuthNVerifiedEventMapper). - RegisterFilterEventMapper(HumanPasswordlessTokenSignCountChangedType, HumanWebAuthNSignCountChangedEventMapper). - RegisterFilterEventMapper(HumanPasswordlessTokenRemovedType, HumanWebAuthNRemovedEventMapper). - RegisterFilterEventMapper(HumanPasswordlessTokenBeginLoginType, HumanWebAuthNBeginLoginEventMapper). - RegisterFilterEventMapper(HumanPasswordlessTokenCheckSucceededType, HumanWebAuthNCheckSucceededEventMapper). - RegisterFilterEventMapper(HumanPasswordlessTokenCheckFailedType, HumanWebAuthNCheckFailedEventMapper). + RegisterFilterEventMapper(HumanU2FTokenAddedType, HumanU2FAddedEventMapper). + RegisterFilterEventMapper(HumanU2FTokenVerifiedType, HumanU2FVerifiedEventMapper). + RegisterFilterEventMapper(HumanU2FTokenSignCountChangedType, HumanU2FSignCountChangedEventMapper). + RegisterFilterEventMapper(HumanU2FTokenRemovedType, HumanU2FRemovedEventMapper). + RegisterFilterEventMapper(HumanU2FTokenBeginLoginType, HumanU2FBeginLoginEventMapper). + RegisterFilterEventMapper(HumanU2FTokenCheckSucceededType, HumanU2FCheckSucceededEventMapper). + RegisterFilterEventMapper(HumanU2FTokenCheckFailedType, HumanU2FCheckFailedEventMapper). + RegisterFilterEventMapper(HumanPasswordlessTokenAddedType, HumanPasswordlessAddedEventMapper). + RegisterFilterEventMapper(HumanPasswordlessTokenVerifiedType, HumanPasswordlessVerifiedEventMapper). + RegisterFilterEventMapper(HumanPasswordlessTokenSignCountChangedType, HumanPasswordlessSignCountChangedEventMapper). + RegisterFilterEventMapper(HumanPasswordlessTokenRemovedType, HumanPasswordlessRemovedEventMapper). + RegisterFilterEventMapper(HumanPasswordlessTokenBeginLoginType, HumanPasswordlessBeginLoginEventMapper). + RegisterFilterEventMapper(HumanPasswordlessTokenCheckSucceededType, HumanPasswordlessCheckSucceededEventMapper). + RegisterFilterEventMapper(HumanPasswordlessTokenCheckFailedType, HumanPasswordlessCheckFailedEventMapper). RegisterFilterEventMapper(MachineAddedEventType, MachineAddedEventMapper). RegisterFilterEventMapper(MachineChangedEventType, MachineChangedEventMapper). RegisterFilterEventMapper(MachineKeyAddedEventType, MachineKeyAddedEventMapper). diff --git a/internal/v2/repository/user/human.go b/internal/v2/repository/user/human.go index fd2f56c28b..d7b98ef8b7 100644 --- a/internal/v2/repository/user/human.go +++ b/internal/v2/repository/user/human.go @@ -359,6 +359,8 @@ func HumanInitializedCheckFailedEventMapper(event *repository.Event) (eventstore type HumanSignedOutEvent struct { eventstore.BaseEvent `json:"-"` + + UserAgentID string `json:"userAgentID"` } func (e *HumanSignedOutEvent) Data() interface{} { @@ -369,12 +371,13 @@ func (e *HumanSignedOutEvent) UniqueConstraints() []*eventstore.EventUniqueConst return nil } -func NewHumanSignedOutEvent(ctx context.Context) *HumanSignedOutEvent { +func NewHumanSignedOutEvent(ctx context.Context, userAgentID string) *HumanSignedOutEvent { return &HumanSignedOutEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanSignedOutType, ), + UserAgentID: userAgentID, } } diff --git a/internal/v2/repository/user/human_external_idp.go b/internal/v2/repository/user/human_external_idp.go index e7bba60c80..8c8bf8cc54 100644 --- a/internal/v2/repository/user/human_external_idp.go +++ b/internal/v2/repository/user/human_external_idp.go @@ -13,10 +13,6 @@ const ( externalIDPEventPrefix = humanEventPrefix + "externalidp." externalLoginEventPrefix = humanEventPrefix + "externallogin." - //TODO: Handle unique Aggregate - HumanExternalIDPReservedType = externalIDPEventPrefix + "reserved" - HumanExternalIDPReleasedType = externalIDPEventPrefix + "released" - HumanExternalIDPAddedType = externalIDPEventPrefix + "added" HumanExternalIDPRemovedType = externalIDPEventPrefix + "removed" HumanExternalIDPCascadeRemovedType = externalIDPEventPrefix + "cascade.removed" @@ -53,7 +49,7 @@ func (e *HumanExternalIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniq return []*eventstore.EventUniqueConstraint{NewAddExternalIDPUniqueConstraint(e.IDPConfigID, e.UserID)} } -func NewHumanExternalIDPAddedEvent(ctx context.Context, idpConfigID, displayName string) *HumanExternalIDPAddedEvent { +func NewHumanExternalIDPAddedEvent(ctx context.Context, idpConfigID, displayName, externalUserID string) *HumanExternalIDPAddedEvent { return &HumanExternalIDPAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, @@ -61,6 +57,7 @@ func NewHumanExternalIDPAddedEvent(ctx context.Context, idpConfigID, displayName ), IDPConfigID: idpConfigID, DisplayName: displayName, + UserID: externalUserID, } } @@ -157,27 +154,36 @@ func HumanExternalIDPCascadeRemovedEventMapper(event *repository.Event) (eventst type HumanExternalIDPCheckSucceededEvent struct { eventstore.BaseEvent `json:"-"` + *AuthRequestInfo } func (e *HumanExternalIDPCheckSucceededEvent) Data() interface{} { - return nil + return e } func (e *HumanExternalIDPCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { return nil } -func NewHumanExternalIDPCheckSucceededEvent(ctx context.Context) *HumanExternalIDPCheckSucceededEvent { +func NewHumanExternalIDPCheckSucceededEvent(ctx context.Context, info *AuthRequestInfo) *HumanExternalIDPCheckSucceededEvent { return &HumanExternalIDPCheckSucceededEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanExternalLoginCheckSucceededType, ), + AuthRequestInfo: info, } } func HumanExternalIDPCheckSucceededEventMapper(event *repository.Event) (eventstore.EventReader, error) { - return &HumanExternalIDPCheckSucceededEvent{ + e := &HumanExternalIDPCheckSucceededEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - }, nil + } + + err := json.Unmarshal(event.Data, e) + if err != nil { + return nil, errors.ThrowInternal(err, "USER-2M0sd", "unable to unmarshal user external idp check succeeded") + } + + return e, nil } diff --git a/internal/v2/repository/user/human_mfa_otp.go b/internal/v2/repository/user/human_mfa_otp.go index c05accf71f..14d73bdce1 100644 --- a/internal/v2/repository/user/human_mfa_otp.go +++ b/internal/v2/repository/user/human_mfa_otp.go @@ -112,54 +112,68 @@ func HumanOTPRemovedEventMapper(event *repository.Event) (eventstore.EventReader type HumanOTPCheckSucceededEvent struct { eventstore.BaseEvent `json:"-"` + *AuthRequestInfo } func (e *HumanOTPCheckSucceededEvent) Data() interface{} { - return nil + return e } func (e *HumanOTPCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { return nil } -func NewHumanOTPCheckSucceededEvent(ctx context.Context) *HumanOTPCheckSucceededEvent { +func NewHumanOTPCheckSucceededEvent(ctx context.Context, info *AuthRequestInfo) *HumanOTPCheckSucceededEvent { return &HumanOTPCheckSucceededEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanMFAOTPCheckSucceededType, ), + AuthRequestInfo: info, } } func HumanOTPCheckSucceededEventMapper(event *repository.Event) (eventstore.EventReader, error) { - return &HumanOTPCheckSucceededEvent{ + otpAdded := &HumanOTPCheckSucceededEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - }, nil + } + err := json.Unmarshal(event.Data, otpAdded) + if err != nil { + return nil, errors.ThrowInternal(err, "USER-Ns9df", "unable to unmarshal human otp check succeeded") + } + return otpAdded, nil } type HumanOTPCheckFailedEvent struct { eventstore.BaseEvent `json:"-"` + *AuthRequestInfo } func (e *HumanOTPCheckFailedEvent) Data() interface{} { - return nil + return e } func (e *HumanOTPCheckFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { return nil } -func NewHumanOTPCheckFailedEvent(ctx context.Context) *HumanOTPCheckFailedEvent { +func NewHumanOTPCheckFailedEvent(ctx context.Context, info *AuthRequestInfo) *HumanOTPCheckFailedEvent { return &HumanOTPCheckFailedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanMFAOTPCheckFailedType, ), + AuthRequestInfo: info, } } func HumanOTPCheckFailedEventMapper(event *repository.Event) (eventstore.EventReader, error) { - return &HumanOTPCheckFailedEvent{ + otpAdded := &HumanOTPCheckFailedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - }, nil + } + err := json.Unmarshal(event.Data, otpAdded) + if err != nil { + return nil, errors.ThrowInternal(err, "USER-Ns9df", "unable to unmarshal human otp check failed") + } + return otpAdded, nil } diff --git a/internal/v2/repository/user/human_mfa_passwordless.go b/internal/v2/repository/user/human_mfa_passwordless.go new file mode 100644 index 0000000000..950cffb89f --- /dev/null +++ b/internal/v2/repository/user/human_mfa_passwordless.go @@ -0,0 +1,224 @@ +package user + +import ( + "context" + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/eventstore/v2/repository" +) + +const ( + passwordlessEventPrefix = humanEventPrefix + "passwordless.token." + HumanPasswordlessTokenAddedType = passwordlessEventPrefix + "added" + HumanPasswordlessTokenVerifiedType = passwordlessEventPrefix + "verified" + HumanPasswordlessTokenSignCountChangedType = passwordlessEventPrefix + "signcount.changed" + HumanPasswordlessTokenRemovedType = passwordlessEventPrefix + "removed" + HumanPasswordlessTokenBeginLoginType = passwordlessEventPrefix + "begin.login" + HumanPasswordlessTokenCheckSucceededType = passwordlessEventPrefix + "check.succeeded" + HumanPasswordlessTokenCheckFailedType = passwordlessEventPrefix + "check.failed" +) + +type HumanPasswordlessAddedEvent struct { + HumanWebAuthNAddedEvent +} + +func NewHumanPasswordlessAddedEvent( + ctx context.Context, + webAuthNTokenID, + challenge string, +) *HumanPasswordlessAddedEvent { + return &HumanPasswordlessAddedEvent{ + HumanWebAuthNAddedEvent: *NewHumanWebAuthNAddedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanPasswordlessTokenAddedType, + ), + webAuthNTokenID, + challenge, + ), + } +} + +func HumanPasswordlessAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanPasswordlessAddedEvent{HumanWebAuthNAddedEvent: *e.(*HumanWebAuthNAddedEvent)}, nil +} + +type HumanPasswordlessVerifiedEvent struct { + HumanWebAuthNVerifiedEvent +} + +func NewHumanPasswordlessVerifiedEvent( + ctx context.Context, + webAuthNTokenID, + webAuthNTokenName, + attestationType string, + keyID, + publicKey, + aaguid []byte, + signCount uint32, +) *HumanPasswordlessVerifiedEvent { + return &HumanPasswordlessVerifiedEvent{ + HumanWebAuthNVerifiedEvent: *NewHumanWebAuthNVerifiedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanPasswordlessTokenVerifiedType, + ), + webAuthNTokenID, + webAuthNTokenName, + attestationType, + keyID, + publicKey, + aaguid, + signCount, + ), + } +} + +func HumanPasswordlessVerifiedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNVerifiedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanPasswordlessVerifiedEvent{HumanWebAuthNVerifiedEvent: *e.(*HumanWebAuthNVerifiedEvent)}, nil +} + +type HumanPasswordlessSignCountChangedEvent struct { + HumanWebAuthNSignCountChangedEvent +} + +func NewHumanPasswordlessSignCountChangedEvent( + ctx context.Context, + webAuthNTokenID string, + signCount uint32, +) *HumanPasswordlessSignCountChangedEvent { + return &HumanPasswordlessSignCountChangedEvent{ + HumanWebAuthNSignCountChangedEvent: *NewHumanWebAuthNSignCountChangedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanPasswordlessTokenSignCountChangedType, + ), + webAuthNTokenID, + signCount, + ), + } +} + +func HumanPasswordlessSignCountChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNSignCountChangedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanPasswordlessSignCountChangedEvent{HumanWebAuthNSignCountChangedEvent: *e.(*HumanWebAuthNSignCountChangedEvent)}, nil +} + +type HumanPasswordlessRemovedEvent struct { + HumanWebAuthNRemovedEvent +} + +func NewHumanPasswordlessRemovedEvent( + ctx context.Context, + webAuthNTokenID string, +) *HumanPasswordlessRemovedEvent { + return &HumanPasswordlessRemovedEvent{ + HumanWebAuthNRemovedEvent: *NewHumanWebAuthNRemovedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanPasswordlessTokenRemovedType, + ), + webAuthNTokenID, + ), + } +} + +func HumanPasswordlessRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNRemovedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanPasswordlessRemovedEvent{HumanWebAuthNRemovedEvent: *e.(*HumanWebAuthNRemovedEvent)}, nil +} + +type HumanPasswordlessBeginLoginEvent struct { + HumanWebAuthNBeginLoginEvent +} + +func NewHumanPasswordlessBeginLoginEvent( + ctx context.Context, + challenge string, + info *AuthRequestInfo, +) *HumanPasswordlessBeginLoginEvent { + return &HumanPasswordlessBeginLoginEvent{ + HumanWebAuthNBeginLoginEvent: *NewHumanWebAuthNBeginLoginEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanPasswordlessTokenVerifiedType, + ), + challenge, + info, + ), + } +} + +func HumanPasswordlessBeginLoginEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNBeginLoginEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanPasswordlessBeginLoginEvent{HumanWebAuthNBeginLoginEvent: *e.(*HumanWebAuthNBeginLoginEvent)}, nil +} + +type HumanPasswordlessCheckSucceededEvent struct { + HumanWebAuthNCheckSucceededEvent +} + +func NewHumanPasswordlessCheckSucceededEvent(ctx context.Context) *HumanPasswordlessCheckSucceededEvent { + return &HumanPasswordlessCheckSucceededEvent{ + HumanWebAuthNCheckSucceededEvent: *NewHumanWebAuthNCheckSucceededEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanPasswordlessTokenCheckSucceededType, + ), + ), + } +} + +func HumanPasswordlessCheckSucceededEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNCheckSucceededEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanPasswordlessCheckSucceededEvent{HumanWebAuthNCheckSucceededEvent: *e.(*HumanWebAuthNCheckSucceededEvent)}, nil +} + +type HumanPasswordlessCheckFailedEvent struct { + HumanWebAuthNCheckFailedEvent +} + +func NewHumanPasswordlessCheckFailedEvent(ctx context.Context) *HumanPasswordlessCheckFailedEvent { + return &HumanPasswordlessCheckFailedEvent{ + HumanWebAuthNCheckFailedEvent: *NewHumanWebAuthNCheckFailedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanPasswordlessTokenCheckFailedType, + ), + ), + } +} + +func HumanPasswordlessCheckFailedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNCheckFailedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanPasswordlessCheckFailedEvent{HumanWebAuthNCheckFailedEvent: *e.(*HumanWebAuthNCheckFailedEvent)}, nil +} diff --git a/internal/v2/repository/user/human_mfa_u2f.go b/internal/v2/repository/user/human_mfa_u2f.go new file mode 100644 index 0000000000..effde7e1c6 --- /dev/null +++ b/internal/v2/repository/user/human_mfa_u2f.go @@ -0,0 +1,224 @@ +package user + +import ( + "context" + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/eventstore/v2/repository" +) + +const ( + u2fEventPrefix = mfaEventPrefix + "u2f.token." + HumanU2FTokenAddedType = u2fEventPrefix + "added" + HumanU2FTokenVerifiedType = u2fEventPrefix + "verified" + HumanU2FTokenSignCountChangedType = u2fEventPrefix + "signcount.changed" + HumanU2FTokenRemovedType = u2fEventPrefix + "removed" + HumanU2FTokenBeginLoginType = u2fEventPrefix + "begin.login" + HumanU2FTokenCheckSucceededType = u2fEventPrefix + "check.succeeded" + HumanU2FTokenCheckFailedType = u2fEventPrefix + "check.failed" +) + +type HumanU2FAddedEvent struct { + HumanWebAuthNAddedEvent +} + +func NewHumanU2FAddedEvent( + ctx context.Context, + webAuthNTokenID, + challenge string, +) *HumanU2FAddedEvent { + return &HumanU2FAddedEvent{ + HumanWebAuthNAddedEvent: *NewHumanWebAuthNAddedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanU2FTokenAddedType, + ), + webAuthNTokenID, + challenge, + ), + } +} + +func HumanU2FAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanU2FAddedEvent{HumanWebAuthNAddedEvent: *e.(*HumanWebAuthNAddedEvent)}, nil +} + +type HumanU2FVerifiedEvent struct { + HumanWebAuthNVerifiedEvent +} + +func NewHumanU2FVerifiedEvent( + ctx context.Context, + webAuthNTokenID, + webAuthNTokenName, + attestationType string, + keyID, + publicKey, + aaguid []byte, + signCount uint32, +) *HumanU2FVerifiedEvent { + return &HumanU2FVerifiedEvent{ + HumanWebAuthNVerifiedEvent: *NewHumanWebAuthNVerifiedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanU2FTokenVerifiedType, + ), + webAuthNTokenID, + webAuthNTokenName, + attestationType, + keyID, + publicKey, + aaguid, + signCount, + ), + } +} + +func HumanU2FVerifiedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNVerifiedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanU2FVerifiedEvent{HumanWebAuthNVerifiedEvent: *e.(*HumanWebAuthNVerifiedEvent)}, nil +} + +type HumanU2FSignCountChangedEvent struct { + HumanWebAuthNSignCountChangedEvent +} + +func NewHumanU2FSignCountChangedEvent( + ctx context.Context, + webAuthNTokenID string, + signCount uint32, +) *HumanU2FSignCountChangedEvent { + return &HumanU2FSignCountChangedEvent{ + HumanWebAuthNSignCountChangedEvent: *NewHumanWebAuthNSignCountChangedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanU2FTokenSignCountChangedType, + ), + webAuthNTokenID, + signCount, + ), + } +} + +func HumanU2FSignCountChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNSignCountChangedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanU2FSignCountChangedEvent{HumanWebAuthNSignCountChangedEvent: *e.(*HumanWebAuthNSignCountChangedEvent)}, nil +} + +type HumanU2FRemovedEvent struct { + HumanWebAuthNRemovedEvent +} + +func NewHumanU2FRemovedEvent( + ctx context.Context, + webAuthNTokenID string, +) *HumanU2FRemovedEvent { + return &HumanU2FRemovedEvent{ + HumanWebAuthNRemovedEvent: *NewHumanWebAuthNRemovedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanU2FTokenRemovedType, + ), + webAuthNTokenID, + ), + } +} + +func HumanU2FRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNRemovedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanU2FRemovedEvent{HumanWebAuthNRemovedEvent: *e.(*HumanWebAuthNRemovedEvent)}, nil +} + +type HumanU2FBeginLoginEvent struct { + HumanWebAuthNBeginLoginEvent +} + +func NewHumanU2FBeginLoginEvent( + ctx context.Context, + challenge string, + info *AuthRequestInfo, +) *HumanU2FBeginLoginEvent { + return &HumanU2FBeginLoginEvent{ + HumanWebAuthNBeginLoginEvent: *NewHumanWebAuthNBeginLoginEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanU2FTokenVerifiedType, + ), + challenge, + info, + ), + } +} + +func HumanU2FBeginLoginEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNBeginLoginEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanU2FBeginLoginEvent{HumanWebAuthNBeginLoginEvent: *e.(*HumanWebAuthNBeginLoginEvent)}, nil +} + +type HumanU2FCheckSucceededEvent struct { + HumanWebAuthNCheckSucceededEvent +} + +func NewHumanU2FCheckSucceededEvent(ctx context.Context) *HumanU2FCheckSucceededEvent { + return &HumanU2FCheckSucceededEvent{ + HumanWebAuthNCheckSucceededEvent: *NewHumanWebAuthNCheckSucceededEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanU2FTokenCheckSucceededType, + ), + ), + } +} + +func HumanU2FCheckSucceededEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNCheckSucceededEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanU2FCheckSucceededEvent{HumanWebAuthNCheckSucceededEvent: *e.(*HumanWebAuthNCheckSucceededEvent)}, nil +} + +type HumanU2FCheckFailedEvent struct { + HumanWebAuthNCheckFailedEvent +} + +func NewHumanU2FCheckFailedEvent(ctx context.Context) *HumanU2FCheckFailedEvent { + return &HumanU2FCheckFailedEvent{ + HumanWebAuthNCheckFailedEvent: *NewHumanWebAuthNCheckFailedEvent( + eventstore.NewBaseEventForPush( + ctx, + HumanU2FTokenCheckFailedType, + ), + ), + } +} + +func HumanU2FCheckFailedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := HumanWebAuthNCheckFailedEventMapper(event) + if err != nil { + return nil, err + } + + return &HumanU2FCheckFailedEvent{HumanWebAuthNCheckFailedEvent: *e.(*HumanWebAuthNCheckFailedEvent)}, nil +} 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 8f4914d9db..7f6bbf3758 100644 --- a/internal/v2/repository/user/human_mfa_web_auth_n.go +++ b/internal/v2/repository/user/human_mfa_web_auth_n.go @@ -1,7 +1,6 @@ package user import ( - "context" "encoding/json" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/v2" @@ -9,26 +8,6 @@ import ( "github.com/caos/zitadel/internal/v2/domain" ) -const ( - u2fEventPrefix = mfaEventPrefix + "u2f.token." - HumanU2FTokenAddedType = u2fEventPrefix + "added" - HumanU2FTokenVerifiedType = u2fEventPrefix + "verified" - HumanU2FTokenSignCountChangedType = u2fEventPrefix + "signcount.changed" - HumanU2FTokenRemovedType = u2fEventPrefix + "removed" - HumanU2FTokenBeginLoginType = u2fEventPrefix + "begin.login" - HumanU2FTokenCheckSucceededType = u2fEventPrefix + "check.succeeded" - HumanU2FTokenCheckFailedType = u2fEventPrefix + "check.failed" - - passwordlessEventPrefix = humanEventPrefix + "passwordless.token." - HumanPasswordlessTokenAddedType = passwordlessEventPrefix + "added" - HumanPasswordlessTokenVerifiedType = passwordlessEventPrefix + "verified" - HumanPasswordlessTokenSignCountChangedType = passwordlessEventPrefix + "signcount.changed" - HumanPasswordlessTokenRemovedType = passwordlessEventPrefix + "removed" - HumanPasswordlessTokenBeginLoginType = passwordlessEventPrefix + "begin.login" - HumanPasswordlessTokenCheckSucceededType = passwordlessEventPrefix + "check.succeeded" - HumanPasswordlessTokenCheckFailedType = passwordlessEventPrefix + "check.failed" -) - type HumanWebAuthNAddedEvent struct { eventstore.BaseEvent `json:"-"` @@ -44,37 +23,19 @@ func (e *HumanWebAuthNAddedEvent) UniqueConstraints() []*eventstore.EventUniqueC return nil } -func NewHumanU2FAddedEvent( - ctx context.Context, +func NewHumanWebAuthNAddedEvent( + base *eventstore.BaseEvent, webAuthNTokenID, challenge string, ) *HumanWebAuthNAddedEvent { return &HumanWebAuthNAddedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanU2FTokenAddedType, - ), + BaseEvent: *base, WebAuthNTokenID: webAuthNTokenID, Challenge: challenge, } } -func NewHumanPasswordlessAddedEvent( - ctx context.Context, - webAuthNTokenID, - challenge string, -) *HumanWebAuthNAddedEvent { - return &HumanWebAuthNAddedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanPasswordlessTokenAddedType, - ), - WebAuthNTokenID: webAuthNTokenID, - Challenge: challenge, - } -} - -func WebAuthNAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { +func HumanWebAuthNAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { webAuthNAdded := &HumanWebAuthNAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), } @@ -105,8 +66,8 @@ func (e *HumanWebAuthNVerifiedEvent) UniqueConstraints() []*eventstore.EventUniq return nil } -func NewHumanU2FVerifiedEvent( - ctx context.Context, +func NewHumanWebAuthNVerifiedEvent( + base *eventstore.BaseEvent, webAuthNTokenID, webAuthNTokenName, attestationType string, @@ -116,35 +77,7 @@ func NewHumanU2FVerifiedEvent( signCount uint32, ) *HumanWebAuthNVerifiedEvent { return &HumanWebAuthNVerifiedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanU2FTokenVerifiedType, - ), - WebAuthNTokenID: webAuthNTokenID, - KeyID: keyID, - PublicKey: publicKey, - AttestationType: attestationType, - AAGUID: aaguid, - SignCount: signCount, - WebAuthNTokenName: webAuthNTokenName, - } -} - -func NewHumanPasswordlessVerifiedEvent( - ctx context.Context, - webAuthNTokenID, - webAuthNTokenName, - attestationType string, - keyID, - publicKey, - aaguid []byte, - signCount uint32, -) *HumanWebAuthNVerifiedEvent { - return &HumanWebAuthNVerifiedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanPasswordlessTokenVerifiedType, - ), + BaseEvent: *base, WebAuthNTokenID: webAuthNTokenID, KeyID: keyID, PublicKey: publicKey, @@ -181,31 +114,13 @@ func (e *HumanWebAuthNSignCountChangedEvent) UniqueConstraints() []*eventstore.E return nil } -func NewHumanU2FSignCountChangedEvent( - ctx context.Context, +func NewHumanWebAuthNSignCountChangedEvent( + base *eventstore.BaseEvent, webAuthNTokenID string, signCount uint32, ) *HumanWebAuthNSignCountChangedEvent { return &HumanWebAuthNSignCountChangedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanU2FTokenSignCountChangedType, - ), - WebAuthNTokenID: webAuthNTokenID, - SignCount: signCount, - } -} - -func NewHumanPasswordlessSignCountChangedEvent( - ctx context.Context, - webAuthNTokenID string, - signCount uint32, -) *HumanWebAuthNSignCountChangedEvent { - return &HumanWebAuthNSignCountChangedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanPasswordlessTokenSignCountChangedType, - ), + BaseEvent: *base, WebAuthNTokenID: webAuthNTokenID, SignCount: signCount, } @@ -237,28 +152,12 @@ func (e *HumanWebAuthNRemovedEvent) UniqueConstraints() []*eventstore.EventUniqu return nil } -func NewHumanU2FRemovedEvent( - ctx context.Context, +func NewHumanWebAuthNRemovedEvent( + base *eventstore.BaseEvent, webAuthNTokenID string, ) *HumanWebAuthNRemovedEvent { return &HumanWebAuthNRemovedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanU2FTokenRemovedType, - ), - WebAuthNTokenID: webAuthNTokenID, - } -} - -func NewHumanPasswordlessRemovedEvent( - ctx context.Context, - webAuthNTokenID string, -) *HumanWebAuthNRemovedEvent { - return &HumanWebAuthNRemovedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanPasswordlessTokenRemovedType, - ), + BaseEvent: *base, WebAuthNTokenID: webAuthNTokenID, } } @@ -277,10 +176,8 @@ func HumanWebAuthNRemovedEventMapper(event *repository.Event) (eventstore.EventR type HumanWebAuthNBeginLoginEvent struct { eventstore.BaseEvent `json:"-"` - WebAuthNTokenID string `json:"webAuthNTokenId"` - Challenge string `json:"challenge"` - //TODO: Handle Auth Req?? - //*AuthRequest + Challenge string `json:"challenge"` + *AuthRequestInfo } func (e *HumanWebAuthNBeginLoginEvent) Data() interface{} { @@ -291,33 +188,15 @@ func (e *HumanWebAuthNBeginLoginEvent) UniqueConstraints() []*eventstore.EventUn return nil } -func NewHumanU2FBeginLoginEvent( - ctx context.Context, - webAuthNTokenID, +func NewHumanWebAuthNBeginLoginEvent( + base *eventstore.BaseEvent, challenge string, + info *AuthRequestInfo, ) *HumanWebAuthNBeginLoginEvent { return &HumanWebAuthNBeginLoginEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanU2FTokenRemovedType, - ), - WebAuthNTokenID: webAuthNTokenID, - Challenge: challenge, - } -} - -func NewHumanPasswordlessBeginLoginEvent( - ctx context.Context, - webAuthNTokenID, - challenge string, -) *HumanWebAuthNBeginLoginEvent { - return &HumanWebAuthNBeginLoginEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanPasswordlessTokenRemovedType, - ), - WebAuthNTokenID: webAuthNTokenID, + BaseEvent: *base, Challenge: challenge, + AuthRequestInfo: info, } } @@ -347,21 +226,9 @@ func (e *HumanWebAuthNCheckSucceededEvent) UniqueConstraints() []*eventstore.Eve return nil } -func NewHumanU2FCheckSucceededEvent(ctx context.Context) *HumanWebAuthNCheckSucceededEvent { +func NewHumanWebAuthNCheckSucceededEvent(base *eventstore.BaseEvent) *HumanWebAuthNCheckSucceededEvent { return &HumanWebAuthNCheckSucceededEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanU2FTokenCheckSucceededType, - ), - } -} - -func NewHumanPasswordlessCheckSucceededEvent(ctx context.Context) *HumanWebAuthNCheckSucceededEvent { - return &HumanWebAuthNCheckSucceededEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanPasswordlessTokenCheckSucceededType, - ), + BaseEvent: *base, } } @@ -391,21 +258,9 @@ func (e *HumanWebAuthNCheckFailedEvent) UniqueConstraints() []*eventstore.EventU return nil } -func NewHumanU2FCheckFailedEvent(ctx context.Context) *HumanWebAuthNCheckFailedEvent { +func NewHumanWebAuthNCheckFailedEvent(base *eventstore.BaseEvent) *HumanWebAuthNCheckFailedEvent { return &HumanWebAuthNCheckFailedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanU2FTokenCheckFailedType, - ), - } -} - -func NewHumanPasswordlessCheckFailedEvent(ctx context.Context) *HumanWebAuthNCheckFailedEvent { - return &HumanWebAuthNCheckFailedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanPasswordlessTokenCheckFailedType, - ), + BaseEvent: *base, } } diff --git a/internal/v2/repository/user/human_password.go b/internal/v2/repository/user/human_password.go index f281e72215..f35d948b3b 100644 --- a/internal/v2/repository/user/human_password.go +++ b/internal/v2/repository/user/human_password.go @@ -139,54 +139,70 @@ func HumanPasswordCodeSentEventMapper(event *repository.Event) (eventstore.Event type HumanPasswordCheckSucceededEvent struct { eventstore.BaseEvent `json:"-"` + *AuthRequestInfo } func (e *HumanPasswordCheckSucceededEvent) Data() interface{} { - return nil + return e } func (e *HumanPasswordCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { return nil } -func NewHumanPasswordCheckSucceededEvent(ctx context.Context) *HumanPasswordCheckSucceededEvent { +func NewHumanPasswordCheckSucceededEvent(ctx context.Context, info *AuthRequestInfo) *HumanPasswordCheckSucceededEvent { return &HumanPasswordCheckSucceededEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanPasswordCheckSucceededType, ), + AuthRequestInfo: info, } } func HumanPasswordCheckSucceededEventMapper(event *repository.Event) (eventstore.EventReader, error) { - return &HumanPasswordCheckSucceededEvent{ + humanAdded := &HumanPasswordCheckSucceededEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - }, nil + } + err := json.Unmarshal(event.Data, humanAdded) + if err != nil { + return nil, errors.ThrowInternal(err, "USER-5M9sd", "unable to unmarshal human password check succeeded") + } + + return humanAdded, nil } type HumanPasswordCheckFailedEvent struct { eventstore.BaseEvent `json:"-"` + *AuthRequestInfo } func (e *HumanPasswordCheckFailedEvent) Data() interface{} { - return nil + return e } func (e *HumanPasswordCheckFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { return nil } -func NewHumanPasswordCheckFailedEvent(ctx context.Context) *HumanPasswordCheckFailedEvent { +func NewHumanPasswordCheckFailedEvent(ctx context.Context, info *AuthRequestInfo) *HumanPasswordCheckFailedEvent { return &HumanPasswordCheckFailedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, HumanPasswordCheckFailedType, ), + AuthRequestInfo: info, } } func HumanPasswordCheckFailedEventMapper(event *repository.Event) (eventstore.EventReader, error) { - return &HumanPasswordCheckFailedEvent{ + humanAdded := &HumanPasswordCheckFailedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - }, nil + } + err := json.Unmarshal(event.Data, humanAdded) + if err != nil { + return nil, errors.ThrowInternal(err, "USER-4m9fs", "unable to unmarshal human password check failed") + } + + return humanAdded, nil } diff --git a/internal/v2/repository/usergrant/user_grant.go b/internal/v2/repository/usergrant/user_grant.go index f799c05060..0ca5a40f64 100644 --- a/internal/v2/repository/usergrant/user_grant.go +++ b/internal/v2/repository/usergrant/user_grant.go @@ -11,7 +11,7 @@ import ( const ( uniqueUserGrant = "user_grant" - userGrantEventTypePrefix = eventstore.EventType("user.grant") + userGrantEventTypePrefix = eventstore.EventType("user.grant.") UserGrantAddedType = userGrantEventTypePrefix + "added" UserGrantChangedType = userGrantEventTypePrefix + "changed" UserGrantCascadeChangedType = userGrantEventTypePrefix + "cascade.changed" @@ -78,7 +78,7 @@ func UserGrantAddedEventMapper(event *repository.Event) (eventstore.EventReader, err := json.Unmarshal(event.Data, e) if err != nil { - return nil, errors.ThrowInternal(err, "UGRANT-2M9fs", "unable to unmarshal user grant") + return nil, errors.ThrowInternal(err, "UGRANT-0p9ol", "unable to unmarshal user grant") } return e, nil diff --git a/internal/webauthn/webauthn.go b/internal/webauthn/webauthn.go index 2be843742b..ca4e427b14 100644 --- a/internal/webauthn/webauthn.go +++ b/internal/webauthn/webauthn.go @@ -119,7 +119,8 @@ func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAu &webUser{ Human: user, }, - sessionData, credentialData) + sessionData, + credentialData) if err != nil { return nil, caos_errs.ThrowInternal(err, "WEBAU-3Vb9s", "Errors.User.WebAuthN.CreateCredentialFailed") }