diff --git a/Makefile b/Makefile index c2f0b029d6..8f9be14bc0 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,10 @@ core_integration_setup: core_integration_test: core_integration_setup go test -tags=integration -race -p 1 -coverprofile=profile.cov -coverpkg=./internal/...,./cmd/... ./... +.PHONY: core_integration_test_fast +core_integration_test_fast: core_integration_setup + go test -tags=integration -p 1 ./... + .PHONY: console_lint console_lint: cd console && \ diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index 3c93e1d490..f85e185ecd 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -428,7 +428,6 @@ SystemAPIUsers: SystemDefaults: SecretGenerators: - PasswordSaltCost: 14 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_PASSWORDSALTCOST MachineKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_MACHINEKEYSIZE ApplicationKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_APPLICATIONKEYSIZE PasswordHasher: @@ -482,6 +481,13 @@ SystemDefaults: # - "md5" # - "scrypt" # - "pbkdf2" # verifier for all pbkdf2 hash modes. + SecretHasher: + # Set hasher configuration for machine users, API and OIDC client secrets. + # See PasswordHasher for all possible options + Hasher: + Algorithm: "bcrypt" # ZITADEL_SYSTEMDEFAULTS_SECRETHASHER_HASHER_ALGORITHM + Cost: 4 # ZITADEL_SYSTEMDEFAULTS_SECRETHASHER_HASHER_COST + Verifiers: Multifactors: OTP: # If this is empty, the issuer is the requested domain @@ -590,7 +596,6 @@ DefaultInstance: # date format: 2023-01-01T00:00:00Z ExpirationDate: # ZITADEL_DEFAULTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE SecretGenerators: - PasswordSaltCost: 14 # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_PASSWORDSALTCOST ClientSecret: Length: 64 # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_CLIENTSECRET_LENGTH IncludeLowerLetters: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_CLIENTSECRET_INCLUDELOWERLETTERS diff --git a/cmd/setup/25.go b/cmd/setup/25.go index 1747981468..28444ef3a8 100644 --- a/cmd/setup/25.go +++ b/cmd/setup/25.go @@ -23,5 +23,5 @@ func (mig *User11AddLowerFieldsToVerifiedEmail) Execute(ctx context.Context, _ e } func (mig *User11AddLowerFieldsToVerifiedEmail) String() string { - return "25_user11_add_lower_fields_to_verified_email" + return "25_user12_add_lower_fields_to_verified_email" } diff --git a/cmd/setup/25.sql b/cmd/setup/25.sql index 870c04ea66..8450865bb2 100644 --- a/cmd/setup/25.sql +++ b/cmd/setup/25.sql @@ -1,2 +1,2 @@ -ALTER TABLE IF EXISTS projections.users11_notifications ADD COLUMN IF NOT EXISTS verified_email_lower TEXT GENERATED ALWAYS AS (lower(verified_email)) STORED; -CREATE INDEX IF NOT EXISTS users11_notifications_email_search ON projections.users11_notifications (instance_id, verified_email_lower); \ No newline at end of file +ALTER TABLE IF EXISTS projections.users12_notifications ADD COLUMN IF NOT EXISTS verified_email_lower TEXT GENERATED ALWAYS AS (lower(verified_email)) STORED; +CREATE INDEX IF NOT EXISTS users12_notifications_email_search ON projections.users12_notifications (instance_id, verified_email_lower); diff --git a/cmd/start/start.go b/cmd/start/start.go index 991c7e816b..97f5d6a078 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -439,7 +439,7 @@ func startAPIs( } apis.RegisterHandlerOnPrefix(openapi.HandlerPrefix, openAPIHandler) - oidcServer, err := oidc.NewServer(config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, limitingAccessInterceptor, config.Log.Slog()) + oidcServer, err := oidc.NewServer(ctx, config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, limitingAccessInterceptor, config.Log.Slog(), config.SystemDefaults.SecretHasher) if err != nil { return nil, fmt.Errorf("unable to start oidc provider: %w", err) } diff --git a/docs/docs/concepts/architecture/secrets.md b/docs/docs/concepts/architecture/secrets.md index 4bfe0af9ec..a2b936b71e 100644 --- a/docs/docs/concepts/architecture/secrets.md +++ b/docs/docs/concepts/architecture/secrets.md @@ -64,7 +64,7 @@ ZITADEL hashes all Passwords and Client Secrets in an non reversible way to furt Passwords and secrets are always hashed with a random salt and stored as an encoded string that contains the Algorithm, its Parameters, Salt and Hash. The storage encoding used by ZITADEL is Modular Crypt Format and a full reference can be found in our [Passwap library](https://github.com/zitadel/passwap#encoding). -The following hash algorithms are supported for user passwords: +The following hash algorithms are supported: - argon2i / id[^1] - bcrypt (Default) @@ -82,8 +82,6 @@ This allows to increase cost along with growing computing power. ZITADEL allows to import user passwords from systems that use any of the above hashing algorithms. ::: -Client Secrets always use bcrypt. - ### Encrypted Secrets Some secrets cannot be hashed because they need to be used in their raw form. These include: diff --git a/internal/api/grpc/admin/import.go b/internal/api/grpc/admin/import.go index 6aeb761e9a..5eab0fb8d7 100644 --- a/internal/api/grpc/admin/import.go +++ b/internal/api/grpc/admin/import.go @@ -292,7 +292,7 @@ func getFileFromGCS(ctx context.Context, input *admin_pb.ImportDataRequest_GCSIn return ioutil.ReadAll(reader) } -func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, ctxData authz.CtxData, org *admin_pb.DataOrg, success *admin_pb.ImportDataSuccess, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator crypto.Generator) error { +func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, ctxData authz.CtxData, org *admin_pb.DataOrg, success *admin_pb.ImportDataSuccess, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode crypto.Generator) error { _, err := s.command.AddOrgWithID(ctx, org.GetOrg().GetName(), ctxData.UserID, ctxData.ResourceOwner, org.GetOrgId(), []string{}) if err != nil { *errors = append(*errors, &admin_pb.ImportDataError{Type: "org", Id: org.GetOrgId(), Message: err.Error()}) @@ -325,7 +325,7 @@ func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataEr *errors = append(*errors, &admin_pb.ImportDataError{Type: "domain_policy", Id: org.GetOrgId(), Message: err.Error()}) } } - return importResources(ctx, s, errors, successOrg, org, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator) + return importResources(ctx, s, errors, successOrg, org, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode) } func importLabelPolicy(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, org *admin_pb.DataOrg) error { @@ -584,13 +584,13 @@ func importProjects(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDa return nil } -func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, appSecretGenerator crypto.Generator) error { +func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts) error { if org.OidcApps == nil { return nil } for _, app := range org.GetOidcApps() { logging.Debugf("import oidcapplication: %s", app.GetAppId()) - _, err := s.command.AddOIDCApplicationWithID(ctx, management.AddOIDCAppRequestToDomain(app.App), org.GetOrgId(), app.GetAppId(), appSecretGenerator) + _, err := s.command.AddOIDCApplicationWithID(ctx, management.AddOIDCAppRequestToDomain(app.App), org.GetOrgId(), app.GetAppId()) if err != nil { *errors = append(*errors, &admin_pb.ImportDataError{Type: "oidc_app", Id: app.GetAppId(), Message: err.Error()}) if isCtxTimeout(ctx) { @@ -605,13 +605,13 @@ func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDa return nil } -func importAPIApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, appSecretGenerator crypto.Generator) error { +func importAPIApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts) error { if org.ApiApps == nil { return nil } for _, app := range org.GetApiApps() { logging.Debugf("import apiapplication: %s", app.GetAppId()) - _, err := s.command.AddAPIApplicationWithID(ctx, management.AddAPIAppRequestToDomain(app.GetApp()), org.GetOrgId(), app.GetAppId(), appSecretGenerator) + _, err := s.command.AddAPIApplicationWithID(ctx, management.AddAPIAppRequestToDomain(app.GetApp()), org.GetOrgId(), app.GetAppId()) if err != nil { *errors = append(*errors, &admin_pb.ImportDataError{Type: "api_app", Id: app.GetAppId(), Message: err.Error()}) if isCtxTimeout(ctx) { @@ -700,7 +700,7 @@ func importProjectRoles(ctx context.Context, s *Server, errors *[]*admin_pb.Impo return nil } -func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator crypto.Generator) error { +func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode crypto.Generator) error { if err := importOrgDomains(ctx, s, errors, successOrg, org); err != nil { return err } @@ -742,10 +742,10 @@ func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportD if err := importProjects(ctx, s, errors, successOrg, org, count); err != nil { return err } - if err := importOIDCApps(ctx, s, errors, successOrg, org, count, appSecretGenerator); err != nil { + if err := importOIDCApps(ctx, s, errors, successOrg, org, count); err != nil { return err } - if err := importAPIApps(ctx, s, errors, successOrg, org, count, appSecretGenerator); err != nil { + if err := importAPIApps(ctx, s, errors, successOrg, org, count); err != nil { return err } if err := importAppKeys(ctx, s, errors, successOrg, org, count); err != nil { @@ -1023,10 +1023,6 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm success := &admin_pb.ImportDataSuccess{} count := &counts{} - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, nil, err - } initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg) if err != nil { return nil, nil, err @@ -1064,7 +1060,7 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm count.appKeysCount += len(org.GetAppKeys()) } for _, org := range orgs { - if err = importOrg1(ctx, s, &errors, ctxData, org, success, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator); err != nil { + if err = importOrg1(ctx, s, &errors, ctxData, org, success, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode); err != nil { return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err } } diff --git a/internal/api/grpc/admin/server.go b/internal/api/grpc/admin/server.go index e367765983..96bf208347 100644 --- a/internal/api/grpc/admin/server.go +++ b/internal/api/grpc/admin/server.go @@ -30,7 +30,6 @@ type Server struct { query *query.Queries assetsAPIDomain func(context.Context) string userCodeAlg crypto.EncryptionAlgorithm - passwordHashAlg crypto.HashAlgorithm auditLogRetention time.Duration } @@ -53,7 +52,6 @@ func CreateServer( query: query, assetsAPIDomain: assets.AssetAPI(externalSecure), userCodeAlg: userCodeAlg, - passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost), auditLogRetention: auditLogRetention, } } diff --git a/internal/api/grpc/management/project_application.go b/internal/api/grpc/management/project_application.go index 09612ce072..730b6ff22f 100644 --- a/internal/api/grpc/management/project_application.go +++ b/internal/api/grpc/management/project_application.go @@ -8,7 +8,6 @@ import ( change_grpc "github.com/zitadel/zitadel/internal/api/grpc/change" object_grpc "github.com/zitadel/zitadel/internal/api/grpc/object" project_grpc "github.com/zitadel/zitadel/internal/api/grpc/project" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/repository/project" @@ -81,11 +80,7 @@ func (s *Server) ListAppChanges(ctx context.Context, req *mgmt_pb.ListAppChanges } func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest) (*mgmt_pb.AddOIDCAppResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } - app, err := s.command.AddOIDCApplication(ctx, AddOIDCAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID, appSecretGenerator) + app, err := s.command.AddOIDCApplication(ctx, AddOIDCAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } @@ -110,11 +105,7 @@ func (s *Server) AddSAMLApp(ctx context.Context, req *mgmt_pb.AddSAMLAppRequest) } func (s *Server) AddAPIApp(ctx context.Context, req *mgmt_pb.AddAPIAppRequest) (*mgmt_pb.AddAPIAppResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } - app, err := s.command.AddAPIApplication(ctx, AddAPIAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID, appSecretGenerator) + app, err := s.command.AddAPIApplication(ctx, AddAPIAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } @@ -209,11 +200,7 @@ func (s *Server) RemoveApp(ctx context.Context, req *mgmt_pb.RemoveAppRequest) ( } func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.RegenerateOIDCClientSecretRequest) (*mgmt_pb.RegenerateOIDCClientSecretResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } - config, err := s.command.ChangeOIDCApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID, appSecretGenerator) + config, err := s.command.ChangeOIDCApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } @@ -228,11 +215,7 @@ func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.Re } func (s *Server) RegenerateAPIClientSecret(ctx context.Context, req *mgmt_pb.RegenerateAPIClientSecretRequest) (*mgmt_pb.RegenerateAPIClientSecretResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } - config, err := s.command.ChangeAPIApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID, appSecretGenerator) + config, err := s.command.ChangeAPIApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/server.go b/internal/api/grpc/management/server.go index 638da236b7..cdefabeab9 100644 --- a/internal/api/grpc/management/server.go +++ b/internal/api/grpc/management/server.go @@ -23,13 +23,12 @@ var _ management.ManagementServiceServer = (*Server)(nil) type Server struct { management.UnimplementedManagementServiceServer - command *command.Commands - query *query.Queries - systemDefaults systemdefaults.SystemDefaults - assetAPIPrefix func(context.Context) string - passwordHashAlg crypto.HashAlgorithm - userCodeAlg crypto.EncryptionAlgorithm - externalSecure bool + command *command.Commands + query *query.Queries + systemDefaults systemdefaults.SystemDefaults + assetAPIPrefix func(context.Context) string + userCodeAlg crypto.EncryptionAlgorithm + externalSecure bool } func CreateServer( @@ -40,13 +39,12 @@ func CreateServer( externalSecure bool, ) *Server { return &Server{ - command: command, - query: query, - systemDefaults: sd, - assetAPIPrefix: assets.AssetAPI(externalSecure), - passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost), - userCodeAlg: userCodeAlg, - externalSecure: externalSecure, + command: command, + query: query, + systemDefaults: sd, + assetAPIPrefix: assets.AssetAPI(externalSecure), + userCodeAlg: userCodeAlg, + externalSecure: externalSecure, } } diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index ef7ed0d731..eae4f40d35 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -800,18 +800,13 @@ func (s *Server) RemoveMachineKey(ctx context.Context, req *mgmt_pb.RemoveMachin } func (s *Server) GenerateMachineSecret(ctx context.Context, req *mgmt_pb.GenerateMachineSecretRequest) (*mgmt_pb.GenerateMachineSecretResponse, error) { - // use SecretGeneratorTypeAppSecret as the secrets will be used in the client_credentials grant like a client secret - secretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) - if err != nil { - return nil, err - } user, err := s.getUserByID(ctx, req.GetUserId()) if err != nil { return nil, err } set := new(command.GenerateMachineSecret) - details, err := s.command.GenerateMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, secretGenerator, set) + details, err := s.command.GenerateMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, set) if err != nil { return nil, err } diff --git a/internal/api/grpc/user/converter.go b/internal/api/grpc/user/converter.go index f5381f9001..50c47faaa9 100644 --- a/internal/api/grpc/user/converter.go +++ b/internal/api/grpc/user/converter.go @@ -73,7 +73,7 @@ func MachineToPb(view *query.Machine) *user_pb.Machine { return &user_pb.Machine{ Name: view.Name, Description: view.Description, - HasSecret: view.Secret != nil, + HasSecret: view.EncodedSecret != "", AccessTokenType: AccessTokenTypeToPb(view.AccessTokenType), } } diff --git a/internal/api/grpc/user/v2/query.go b/internal/api/grpc/user/v2/query.go index 2d0798c2ce..aac070cede 100644 --- a/internal/api/grpc/user/v2/query.go +++ b/internal/api/grpc/user/v2/query.go @@ -109,7 +109,7 @@ func machineToPb(userQ *query.Machine) *user.MachineUser { return &user.MachineUser{ Name: userQ.Name, Description: userQ.Description, - HasSecret: userQ.Secret != nil, + HasSecret: userQ.EncodedSecret != "", AccessTokenType: accessTokenTypeToPb(userQ.AccessTokenType), } } diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index 2857991b8a..b6e8e8f45f 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -1050,8 +1050,13 @@ func (s *Server) verifyClientSecret(ctx context.Context, client *query.OIDCClien if secret == "" { return oidc.ErrInvalidClient().WithDescription("empty client secret") } - if err = crypto.CompareHash(client.ClientSecret, []byte(secret), s.hashAlg); err != nil { + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := s.hasher.Verify(client.HashedSecret, secret) + spanPasswordComparison.EndWithError(err) + if err != nil { + s.command.OIDCSecretCheckFailed(ctx, client.AppID, client.ProjectID, client.Settings.ResourceOwner) return oidc.ErrInvalidClient().WithParent(err).WithDescription("invalid secret") } + s.command.OIDCSecretCheckSucceeded(ctx, client.AppID, client.ProjectID, client.Settings.ResourceOwner, updated) return nil } diff --git a/internal/api/oidc/client_credentials.go b/internal/api/oidc/client_credentials.go index c3622680c9..a62fa54a2b 100644 --- a/internal/api/oidc/client_credentials.go +++ b/internal/api/oidc/client_credentials.go @@ -7,8 +7,8 @@ import ( "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/op" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -41,15 +41,18 @@ func (s *Server) clientCredentialsAuth(ctx context.Context, clientID, clientSecr if err != nil { return nil, err // defaults to server error } - if user.Machine == nil || user.Machine.Secret == nil { + if user.Machine == nil || user.Machine.EncodedSecret == "" { return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-pieP8", "Errors.User.Machine.Secret.NotExisting") } - if err = crypto.CompareHash(user.Machine.Secret, []byte(clientSecret), s.hashAlg); err != nil { + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := s.hasher.Verify(user.Machine.EncodedSecret, clientSecret) + spanPasswordComparison.EndWithError(err) + if err != nil { s.command.MachineSecretCheckFailed(ctx, user.ID, user.ResourceOwner) return nil, zerrors.ThrowInvalidArgument(err, "OIDC-VoXo6", "Errors.User.Machine.Secret.Invalid") } - s.command.MachineSecretCheckSucceeded(ctx, user.ID, user.ResourceOwner) + s.command.MachineSecretCheckSucceeded(ctx, user.ID, user.ResourceOwner, updated) return &clientCredentialsClient{ id: clientID, user: user, diff --git a/internal/api/oidc/introspect.go b/internal/api/oidc/introspect.go index 04934e0dc8..ccc061f8e5 100644 --- a/internal/api/oidc/introspect.go +++ b/internal/api/oidc/introspect.go @@ -11,7 +11,6 @@ import ( "github.com/zitadel/oidc/v3/pkg/op" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" @@ -149,8 +148,8 @@ func (s *Server) introspectionClientAuth(ctx context.Context, cc *op.ClientCrede return client.ClientID, client.ProjectID, nil } - if client.ClientSecret != nil { - if err := crypto.CompareHash(client.ClientSecret, []byte(cc.ClientSecret), s.hashAlg); err != nil { + if client.HashedSecret != "" { + if err := s.introspectionClientSecretAuth(ctx, client, cc.ClientSecret); err != nil { return "", "", oidc.ErrUnauthorizedClient().WithParent(err) } return client.ClientID, client.ProjectID, nil @@ -167,6 +166,35 @@ func (s *Server) introspectionClientAuth(ctx context.Context, cc *op.ClientCrede } } +var errNoAppType = errors.New("introspection client without app type") + +func (s *Server) introspectionClientSecretAuth(ctx context.Context, client *query.IntrospectionClient, secret string) error { + var ( + successCommand func(ctx context.Context, appID, projectID, resourceOwner, updated string) + failedCommand func(ctx context.Context, appID, projectID, resourceOwner string) + ) + switch client.AppType { + case query.AppTypeAPI: + successCommand = s.command.APISecretCheckSucceeded + failedCommand = s.command.APISecretCheckFailed + case query.AppTypeOIDC: + successCommand = s.command.OIDCSecretCheckSucceeded + failedCommand = s.command.OIDCSecretCheckFailed + default: + return zerrors.ThrowInternal(errNoAppType, "OIDC-ooD5Ot", "Errors.Internal") + } + + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := s.hasher.Verify(client.HashedSecret, secret) + spanPasswordComparison.EndWithError(err) + if err != nil { + failedCommand(ctx, client.AppID, client.ProjectID, client.ResourceOwner) + return err + } + successCommand(ctx, client.AppID, client.ProjectID, client.ResourceOwner, updated) + return nil +} + // clientFromCredentials parses the client ID early, // and makes a single query for the client for either auth methods. func (s *Server) clientFromCredentials(ctx context.Context, cc *op.ClientCredentials) (client *query.IntrospectionClient, err error) { diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index 98a69a77d0..3daa7e61ff 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -80,6 +80,7 @@ type OPStorage struct { } func NewServer( + ctx context.Context, config Config, defaultLogoutRedirectURI string, externalSecure bool, @@ -93,13 +94,14 @@ func NewServer( userAgentCookie, instanceHandler func(http.Handler) http.Handler, accessHandler *middleware.AccessInterceptor, fallbackLogger *slog.Logger, + hashConfig crypto.HashConfig, ) (*Server, error) { opConfig, err := createOPConfig(config, defaultLogoutRedirectURI, cryptoKey) if err != nil { return nil, zerrors.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w") } storage := newStorage(config, command, query, repo, encryptionAlg, es, projections, externalSecure) - keyCache := newPublicKeyCache(context.TODO(), config.PublicKeyCacheMaxAge, query.GetPublicKeyByID) + keyCache := newPublicKeyCache(ctx, config.PublicKeyCacheMaxAge, query.GetPublicKeyByID) accessTokenKeySet := newOidcKeySet(keyCache, withKeyExpiryCheck(true)) idTokenHintKeySet := newOidcKeySet(keyCache) @@ -119,7 +121,10 @@ func NewServer( if err != nil { return nil, zerrors.ThrowInternal(err, "OIDC-DAtg3", "cannot create provider") } - + hasher, err := hashConfig.NewHasher() + if err != nil { + return nil, zerrors.ThrowInternal(err, "OIDC-Aij4e", "cannot create secret hasher") + } server := &Server{ LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)), repo: repo, @@ -133,7 +138,7 @@ func NewServer( defaultAccessTokenLifetime: config.DefaultAccessTokenLifetime, defaultIdTokenLifetime: config.DefaultIdTokenLifetime, fallbackLogger: fallbackLogger, - hashAlg: crypto.NewBCrypt(10), // as we are only verifying in oidc, the cost is already part of the hash string and the config here is irrelevant. + hasher: hasher, signingKeyAlgorithm: config.SigningKeyAlgorithm, assetAPIPrefix: assets.AssetAPI(externalSecure), } diff --git a/internal/api/oidc/server.go b/internal/api/oidc/server.go index fe10db3219..9b051c7785 100644 --- a/internal/api/oidc/server.go +++ b/internal/api/oidc/server.go @@ -35,7 +35,7 @@ type Server struct { defaultIdTokenLifetime time.Duration fallbackLogger *slog.Logger - hashAlg crypto.HashAlgorithm + hasher *crypto.Hasher signingKeyAlgorithm string assetAPIPrefix func(ctx context.Context) string } diff --git a/internal/command/command.go b/internal/command/command.go index 95a76acb8b..86730fabcc 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" "math/big" "net/http" "strconv" @@ -33,9 +34,10 @@ type Commands struct { jobs sync.WaitGroup - checkPermission domain.PermissionCheck - newCode cryptoCodeFunc - newCodeWithDefault cryptoCodeWithDefaultFunc + checkPermission domain.PermissionCheck + newEncryptedCode encrypedCodeFunc + newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc + newHashedSecret hashedSecretFunc eventstore *eventstore.Eventstore static static.Storage @@ -49,8 +51,8 @@ type Commands struct { smtpEncryption crypto.EncryptionAlgorithm smsEncryption crypto.EncryptionAlgorithm userEncryption crypto.EncryptionAlgorithm - userPasswordHasher *crypto.PasswordHasher - codeAlg crypto.HashAlgorithm + userPasswordHasher *crypto.Hasher + secretHasher *crypto.Hasher machineKeySize int applicationKeySize int domainVerificationAlg crypto.EncryptionAlgorithm @@ -106,6 +108,15 @@ func StartCommands( idGenerator := id.SonyFlakeGenerator() // reuse the oidcEncryption to be able to handle both tokens in the interceptor later on sessionAlg := oidcEncryption + + secretHasher, err := defaults.SecretHasher.NewHasher() + if err != nil { + return nil, fmt.Errorf("secret hasher: %w", err) + } + userPasswordHasher, err := defaults.PasswordHasher.NewHasher() + if err != nil { + return nil, fmt.Errorf("password hasher: %w", err) + } repo = &Commands{ eventstore: es, static: staticStore, @@ -123,14 +134,20 @@ func StartCommands( smtpEncryption: smtpEncryption, smsEncryption: smsEncryption, userEncryption: userEncryption, + userPasswordHasher: userPasswordHasher, + secretHasher: secretHasher, + machineKeySize: int(defaults.SecretGenerators.MachineKeySize), + applicationKeySize: int(defaults.SecretGenerators.ApplicationKeySize), domainVerificationAlg: domainVerificationEncryption, + domainVerificationGenerator: crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, domainVerificationEncryption), + domainVerificationValidator: api_http.ValidateDomain, keyAlgorithm: oidcEncryption, certificateAlgorithm: samlEncryption, webauthnConfig: webAuthN, httpClient: httpClient, checkPermission: permissionCheck, - newCode: newCryptoCode, - newCodeWithDefault: newCryptoCodeWithDefaultConfig, + newEncryptedCode: newEncryptedCode, + newEncryptedCodeWithDefault: newEncryptedCodeWithDefaultConfig, sessionTokenCreator: sessionTokenCreator(idGenerator, sessionAlg), sessionTokenVerifier: sessionTokenVerifier, defaultAccessTokenLifetime: defaultAccessTokenLifetime, @@ -145,25 +162,17 @@ func StartCommands( GrpcServiceExisting: func(service string) bool { return false }, GrpcMethodExisting: func(method string) bool { return false }, ActionFunctionExisting: domain.FunctionExists(), - } - - repo.codeAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost) - repo.userPasswordHasher, err = defaults.PasswordHasher.PasswordHasher() - if err != nil { - return nil, err - } - repo.machineKeySize = int(defaults.SecretGenerators.MachineKeySize) - repo.applicationKeySize = int(defaults.SecretGenerators.ApplicationKeySize) - - repo.multifactors = domain.MultifactorConfigs{ - OTP: domain.OTPConfig{ - CryptoMFA: otpEncryption, - Issuer: defaults.Multifactors.OTP.Issuer, + multifactors: domain.MultifactorConfigs{ + OTP: domain.OTPConfig{ + CryptoMFA: otpEncryption, + Issuer: defaults.Multifactors.OTP.Issuer, + }, }, } - repo.domainVerificationGenerator = crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, repo.domainVerificationAlg) - repo.domainVerificationValidator = api_http.ValidateDomain + if defaultSecretGenerators != nil && defaultSecretGenerators.ClientSecret != nil { + repo.newHashedSecret = newHashedSecretWithDefault(secretHasher, defaultSecretGenerators.ClientSecret) + } return repo, nil } diff --git a/internal/command/crypto.go b/internal/command/crypto.go index dfbbc00012..5db4764f28 100644 --- a/internal/command/crypto.go +++ b/internal/command/crypto.go @@ -7,27 +7,26 @@ import ( "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" - "github.com/zitadel/zitadel/internal/zerrors" ) -type cryptoCodeFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) +type encrypedCodeFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) -type cryptoCodeWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error) +type encryptedCodeWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, error) var emptyConfig = &crypto.GeneratorConfig{} -type CryptoCode struct { +type EncryptedCode struct { Crypted *crypto.CryptoValue Plain string Expiry time.Duration } -func newCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) { - return newCryptoCodeWithDefaultConfig(ctx, filter, typ, alg, emptyConfig) +func newEncryptedCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return newEncryptedCodeWithDefaultConfig(ctx, filter, typ, alg, emptyConfig) } -func newCryptoCodeWithDefaultConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error) { - gen, config, err := secretGenerator(ctx, filter, typ, alg, defaultConfig) +func newEncryptedCodeWithDefaultConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, error) { + gen, config, err := encryptedCodeGenerator(ctx, filter, typ, alg, defaultConfig) if err != nil { return nil, err } @@ -35,41 +34,47 @@ func newCryptoCodeWithDefaultConfig(ctx context.Context, filter preparation.Filt if err != nil { return nil, err } - return &CryptoCode{ + return &EncryptedCode{ Crypted: crypted, Plain: plain, Expiry: config.Expiry, }, nil } -func verifyCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error { - gen, _, err := secretGenerator(ctx, filter, typ, alg, emptyConfig) +func verifyEncryptedCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error { + gen, _, err := encryptedCodeGenerator(ctx, filter, typ, alg, emptyConfig) if err != nil { return err } - return crypto.VerifyCode(creation, expiry, crypted, plain, gen) + return crypto.VerifyCode(creation, expiry, crypted, plain, gen.Alg()) } -func secretGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) { - config, err := secretGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig) +func encryptedCodeGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) { + config, err := cryptoGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig) if err != nil { return nil, nil, err } - switch a := alg.(type) { - case crypto.HashAlgorithm: - return crypto.NewHashGenerator(*config, a), config, nil - case crypto.EncryptionAlgorithm: - return crypto.NewEncryptionGenerator(*config, a), config, nil - default: - return nil, nil, zerrors.ThrowInternalf(nil, "COMMA-RreV6", "Errors.Internal unsupported crypto algorithm type %T", a) + return crypto.NewEncryptionGenerator(*config, alg), config, nil +} + +type hashedSecretFunc func(ctx context.Context, filter preparation.FilterToQueryReducer) (encodedHash, plain string, err error) + +func newHashedSecretWithDefault(hasher *crypto.Hasher, defaultConfig *crypto.GeneratorConfig) hashedSecretFunc { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) (encodedHash string, plain string, err error) { + config, err := cryptoGeneratorConfigWithDefault(ctx, filter, domain.SecretGeneratorTypeAppSecret, defaultConfig) + if err != nil { + return "", "", err + } + generator := crypto.NewHashGenerator(*config, hasher) + return generator.NewCode() } } -func secretGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) { - return secretGeneratorConfigWithDefault(ctx, filter, typ, emptyConfig) +func cryptoGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) { + return cryptoGeneratorConfigWithDefault(ctx, filter, typ, emptyConfig) } -func secretGeneratorConfigWithDefault(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, defaultConfig *crypto.GeneratorConfig) (*crypto.GeneratorConfig, error) { +func cryptoGeneratorConfigWithDefault(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, defaultConfig *crypto.GeneratorConfig) (*crypto.GeneratorConfig, error) { wm := NewInstanceSecretGeneratorConfigWriteModel(ctx, typ) events, err := filter(ctx, wm.Query()) if err != nil { diff --git a/internal/command/crypto_test.go b/internal/command/crypto_test.go index 14df176d12..07db20e2ec 100644 --- a/internal/command/crypto_test.go +++ b/internal/command/crypto_test.go @@ -8,6 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/passwap" + "github.com/zitadel/passwap/bcrypt" "go.uber.org/mock/gomock" "github.com/zitadel/zitadel/internal/command/preparation" @@ -15,12 +17,11 @@ import ( "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" - "github.com/zitadel/zitadel/internal/zerrors" ) -func mockCode(code string, exp time.Duration) cryptoCodeFunc { - return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) { - return &CryptoCode{ +func mockEncryptedCode(code string, exp time.Duration) encrypedCodeFunc { + return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return &EncryptedCode{ Crypted: &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -33,9 +34,9 @@ func mockCode(code string, exp time.Duration) cryptoCodeFunc { } } -func mockCodeWithDefault(code string, exp time.Duration) cryptoCodeWithDefaultFunc { - return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.Crypto, _ *crypto.GeneratorConfig) (*CryptoCode, error) { - return &CryptoCode{ +func mockEncryptedCodeWithDefault(code string, exp time.Duration) encryptedCodeWithDefaultFunc { + return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, _ *crypto.GeneratorConfig) (*EncryptedCode, error) { + return &EncryptedCode{ Crypted: &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -48,6 +49,12 @@ func mockCodeWithDefault(code string, exp time.Duration) cryptoCodeWithDefaultFu } } +func mockHashedSecret(secret string) hashedSecretFunc { + return func(_ context.Context, _ preparation.FilterToQueryReducer) (encodedHash string, plain string, err error) { + return secret, secret, nil + } +} + var ( testGeneratorConfig = crypto.GeneratorConfig{ Length: 12, @@ -74,7 +81,7 @@ func testSecretGeneratorAddedEvent(typ domain.SecretGeneratorType) *instance.Sec func Test_newCryptoCode(t *testing.T) { type args struct { typ domain.SecretGeneratorType - alg crypto.Crypto + alg crypto.EncryptionAlgorithm } tests := []struct { name string @@ -87,7 +94,7 @@ func Test_newCryptoCode(t *testing.T) { eventstore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, wantErr: io.ErrClosedPipe, }, @@ -98,13 +105,13 @@ func Test_newCryptoCode(t *testing.T) { )), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := newCryptoCode(context.Background(), tt.eventstore.Filter, tt.args.typ, tt.args.alg) + got, err := newEncryptedCode(context.Background(), tt.eventstore.Filter, tt.args.typ, tt.args.alg) //nolint:staticcheck require.ErrorIs(t, err, tt.wantErr) if tt.wantErr == nil { require.NotNil(t, got) @@ -120,12 +127,12 @@ func Test_verifyCryptoCode(t *testing.T) { es := eventstoreExpect(t, expectFilter( eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)), )) - code, err := newCryptoCode(context.Background(), es.Filter, domain.SecretGeneratorTypeVerifyEmailCode, crypto.CreateMockHashAlg(gomock.NewController(t))) + code, err := newEncryptedCode(context.Background(), es.Filter, domain.SecretGeneratorTypeVerifyEmailCode, crypto.CreateMockEncryptionAlg(gomock.NewController(t))) //nolint:staticcheck require.NoError(t, err) type args struct { typ domain.SecretGeneratorType - alg crypto.Crypto + alg crypto.EncryptionAlgorithm expiry time.Duration crypted *crypto.CryptoValue plain string @@ -141,7 +148,7 @@ func Test_verifyCryptoCode(t *testing.T) { eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), expiry: code.Expiry, crypted: code.Crypted, plain: code.Plain, @@ -155,7 +162,7 @@ func Test_verifyCryptoCode(t *testing.T) { )), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), expiry: code.Expiry, crypted: code.Crypted, plain: code.Plain, @@ -168,7 +175,7 @@ func Test_verifyCryptoCode(t *testing.T) { )), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), expiry: code.Expiry, crypted: code.Crypted, plain: "wrong", @@ -178,7 +185,7 @@ func Test_verifyCryptoCode(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := verifyCryptoCode(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, time.Now(), tt.args.expiry, tt.args.crypted, tt.args.plain) + err := verifyEncryptedCode(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, time.Now(), tt.args.expiry, tt.args.crypted, tt.args.plain) //nolint:staticcheck if tt.wantErr { assert.Error(t, err) return @@ -188,10 +195,10 @@ func Test_verifyCryptoCode(t *testing.T) { } } -func Test_secretGenerator(t *testing.T) { +func Test_cryptoCodeGenerator(t *testing.T) { type args struct { typ domain.SecretGeneratorType - alg crypto.Crypto + alg crypto.EncryptionAlgorithm defaultConfig *crypto.GeneratorConfig } tests := []struct { @@ -207,24 +214,11 @@ func Test_secretGenerator(t *testing.T) { eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), args: args{ typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), defaultConfig: emptyConfig, }, wantErr: io.ErrClosedPipe, }, - { - name: "hash generator", - eventsore: eventstoreExpect(t, expectFilter( - eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)), - )), - args: args{ - typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), - defaultConfig: emptyConfig, - }, - want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))), - wantConf: &testGeneratorConfig, - }, { name: "encryption generator", eventsore: eventstoreExpect(t, expectFilter( @@ -238,17 +232,6 @@ func Test_secretGenerator(t *testing.T) { want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))), wantConf: &testGeneratorConfig, }, - { - name: "hash generator with default config", - eventsore: eventstoreExpect(t, expectFilter()), - args: args{ - typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: crypto.CreateMockHashAlg(gomock.NewController(t)), - defaultConfig: &testGeneratorConfig, - }, - want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))), - wantConf: &testGeneratorConfig, - }, { name: "encryption generator with default config", eventsore: eventstoreExpect(t, expectFilter()), @@ -260,25 +243,79 @@ func Test_secretGenerator(t *testing.T) { want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))), wantConf: &testGeneratorConfig, }, - { - name: "unsupported type", - eventsore: eventstoreExpect(t, expectFilter( - eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)), - )), - args: args{ - typ: domain.SecretGeneratorTypeVerifyEmailCode, - alg: nil, - defaultConfig: emptyConfig, - }, - wantErr: zerrors.ThrowInternalf(nil, "COMMA-RreV6", "Errors.Internal unsupported crypto algorithm type %T", nil), - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, gotConf, err := secretGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, tt.args.defaultConfig) + got, gotConf, err := encryptedCodeGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, tt.args.defaultConfig) //nolint:staticcheck require.ErrorIs(t, err, tt.wantErr) assert.IsType(t, tt.want, got) assert.Equal(t, tt.wantConf, gotConf) }) } } + +func Test_newHashedSecretWithDefault(t *testing.T) { + tests := []struct { + name string + eventstore func(*testing.T) *eventstore.Eventstore + wantLen int + wantErr bool + }{ + { + name: "filter error", + eventstore: expectEventstore( + expectFilterError(io.ErrClosedPipe), + ), + wantErr: true, + }, + { + name: "default config", + eventstore: expectEventstore( + expectFilter(), + ), + wantLen: 32, + }, + { + name: "instance config", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSecretGeneratorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecretGeneratorTypeAppSecret, + 24, + time.Hour*1, + true, + true, + true, + true, + ), + ), + ), + ), + wantLen: 24, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hasher := &crypto.Hasher{ + Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)), + } + defaultConfig := &crypto.GeneratorConfig{ + Length: 32, + Expiry: time.Minute, + IncludeLowerLetters: true, + } + generate := newHashedSecretWithDefault(hasher, defaultConfig) + encodedHash, plain, err := generate(context.Background(), tt.eventstore(t).Filter) //nolint:staticcheck + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Len(t, plain, tt.wantLen) + _, err = hasher.Verify(encodedHash, plain) + require.NoError(t, err) + }) + } +} diff --git a/internal/command/email.go b/internal/command/email.go index 519887af61..bb7dfc3f7d 100644 --- a/internal/command/email.go +++ b/internal/command/email.go @@ -23,6 +23,6 @@ func (e *Email) Validate() error { return e.Address.Validate() } -func (c *Commands) newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypeVerifyEmailCode, alg) +func (c *Commands) newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeVerifyEmailCode, alg) } diff --git a/internal/command/idp_model.go b/internal/command/idp_model.go index a71391d511..12bccd39f7 100644 --- a/internal/command/idp_model.go +++ b/internal/command/idp_model.go @@ -108,7 +108,7 @@ func (wm *OAuthIDPWriteModel) NewChanges( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint, @@ -278,7 +278,7 @@ func (wm *OIDCIDPWriteModel) NewChanges( issuer, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, idTokenMapping bool, options idp.Options, @@ -636,7 +636,7 @@ func (wm *AzureADIDPWriteModel) NewChanges( name string, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, tenant string, isEmailVerified bool, @@ -772,7 +772,7 @@ func (wm *GitHubIDPWriteModel) NewChanges( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.GitHubIDPChanges, error) { @@ -904,7 +904,7 @@ func (wm *GitHubEnterpriseIDPWriteModel) NewChanges( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint string, @@ -1037,7 +1037,7 @@ func (wm *GitLabIDPWriteModel) NewChanges( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.GitLabIDPChanges, error) { @@ -1161,7 +1161,7 @@ func (wm *GitLabSelfHostedIDPWriteModel) NewChanges( issuer string, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.GitLabSelfHostedIDPChanges, error) { @@ -1285,7 +1285,7 @@ func (wm *GoogleIDPWriteModel) NewChanges( name string, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.GoogleIDPChanges, error) { @@ -1460,7 +1460,7 @@ func (wm *LDAPIDPWriteModel) NewChanges( userObjectClasses []string, userFilters []string, timeout time.Duration, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, attributes idp.LDAPAttributes, options idp.Options, ) ([]idp.LDAPIDPChanges, error) { @@ -1653,7 +1653,7 @@ func (wm *AppleIDPWriteModel) NewChanges( teamID string, keyID string, privateKey []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) ([]idp.AppleIDPChanges, error) { @@ -1790,7 +1790,7 @@ func (wm *SAMLIDPWriteModel) NewChanges( metadata, key, certificate []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, binding string, withSignedRequest bool, options idp.Options, diff --git a/internal/command/instance.go b/internal/command/instance.go index e8d2a1078c..7f379d976b 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -126,7 +126,6 @@ type SetQuotas struct { } type SecretGenerators struct { - PasswordSaltCost uint ClientSecret *crypto.GeneratorConfig InitializeUserCode *crypto.GeneratorConfig EmailVerificationCode *crypto.GeneratorConfig @@ -457,7 +456,6 @@ func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Valid }, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, }, - nil, ), commands.AddAPIAppCommand( @@ -469,7 +467,6 @@ func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Valid }, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, }, - nil, ), commands.AddAPIAppCommand( @@ -481,10 +478,9 @@ func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Valid }, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, }, - nil, ), - commands.AddOIDCAppCommand(cnsl, nil), + commands.AddOIDCAppCommand(cnsl), SetIAMConsoleID(instanceAgg, &cnsl.ClientID, &ids.consoleAppID), ) } diff --git a/internal/command/instance_domain_test.go b/internal/command/instance_domain_test.go index 97129ec4ee..aa844cefa3 100644 --- a/internal/command/instance_domain_test.go +++ b/internal/command/instance_domain_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" @@ -141,12 +140,7 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) { domain.OIDCVersionV1, "consoleApplicationID", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, diff --git a/internal/command/instance_idp_model.go b/internal/command/instance_idp_model.go index 87bd8de3c8..6759442553 100644 --- a/internal/command/instance_idp_model.go +++ b/internal/command/instance_idp_model.go @@ -61,7 +61,7 @@ func (wm *InstanceOAuthIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint, @@ -171,7 +171,7 @@ func (wm *InstanceOIDCIDPWriteModel) NewChangedEvent( issuer, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, idTokenMapping bool, options idp.Options, @@ -344,7 +344,7 @@ func (wm *InstanceAzureADIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, tenant string, isEmailVerified bool, @@ -420,7 +420,7 @@ func (wm *InstanceGitHubIDPWriteModel) NewChangedEvent( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.GitHubIDPChangedEvent, error) { @@ -485,7 +485,7 @@ func (wm *InstanceGitHubEnterpriseIDPWriteModel) NewChangedEvent( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint string, @@ -563,7 +563,7 @@ func (wm *InstanceGitLabIDPWriteModel) NewChangedEvent( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.GitLabIDPChangedEvent, error) { @@ -629,7 +629,7 @@ func (wm *InstanceGitLabSelfHostedIDPWriteModel) NewChangedEvent( issuer, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.GitLabSelfHostedIDPChangedEvent, error) { @@ -695,7 +695,7 @@ func (wm *InstanceGoogleIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.GoogleIDPChangedEvent, error) { @@ -767,7 +767,7 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent( userObjectClasses []string, userFilters []string, timeout time.Duration, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, attributes idp.LDAPAttributes, options idp.Options, ) (*instance.LDAPIDPChangedEvent, error) { @@ -848,7 +848,7 @@ func (wm *InstanceAppleIDPWriteModel) NewChangedEvent( teamID, keyID string, privateKey []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*instance.AppleIDPChangedEvent, error) { @@ -912,7 +912,7 @@ func (wm *InstanceSAMLIDPWriteModel) NewChangedEvent( metadata, key, certificate []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, binding string, withSignedRequest bool, options idp.Options, diff --git a/internal/command/instance_idp_oidc_config_model.go b/internal/command/instance_idp_oidc_config_model.go index 2eca9b7a54..d0f81e86aa 100644 --- a/internal/command/instance_idp_oidc_config_model.go +++ b/internal/command/instance_idp_oidc_config_model.go @@ -93,7 +93,7 @@ func (wm *InstanceIDPOIDCConfigWriteModel) NewChangedEvent( authorizationEndpoint, tokenEndpoint, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, idpDisplayNameMapping, userNameMapping domain.OIDCMappingField, scopes ...string, diff --git a/internal/command/main_test.go b/internal/command/main_test.go index cbfcc42382..a5991dd16f 100644 --- a/internal/command/main_test.go +++ b/internal/command/main_test.go @@ -279,8 +279,8 @@ func (h plainHasher) Verify(encoded, password string) (verifier.Result, error) { // // With `x` set to "foo", the following encoded string would be produced by Hash: // $plain$foo$password -func mockPasswordHasher(x string) *crypto.PasswordHasher { - return &crypto.PasswordHasher{ +func mockPasswordHasher(x string) *crypto.Hasher { + return &crypto.Hasher{ Swapper: passwap.NewSwapper(plainHasher{x: x}), Prefixes: []string{"$plain$"}, } diff --git a/internal/command/org_idp_model.go b/internal/command/org_idp_model.go index 04eeeb8ac2..2d8bc8f8f1 100644 --- a/internal/command/org_idp_model.go +++ b/internal/command/org_idp_model.go @@ -63,7 +63,7 @@ func (wm *OrgOAuthIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint, @@ -173,7 +173,7 @@ func (wm *OrgOIDCIDPWriteModel) NewChangedEvent( issuer, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, idTokenMapping bool, options idp.Options, @@ -350,7 +350,7 @@ func (wm *OrgAzureADIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, tenant string, isEmailVerified bool, @@ -426,7 +426,7 @@ func (wm *OrgGitHubIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.GitHubIDPChangedEvent, error) { @@ -492,7 +492,7 @@ func (wm *OrgGitHubEnterpriseIDPWriteModel) NewChangedEvent( name, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, authorizationEndpoint, tokenEndpoint, userEndpoint string, @@ -571,7 +571,7 @@ func (wm *OrgGitLabIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.GitLabIDPChangedEvent, error) { @@ -637,7 +637,7 @@ func (wm *OrgGitLabSelfHostedIDPWriteModel) NewChangedEvent( issuer, clientID string, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.GitLabSelfHostedIDPChangedEvent, error) { @@ -705,7 +705,7 @@ func (wm *OrgGoogleIDPWriteModel) NewChangedEvent( name, clientID, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.GoogleIDPChangedEvent, error) { @@ -777,7 +777,7 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent( userObjectClasses []string, userFilters []string, timeout time.Duration, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, attributes idp.LDAPAttributes, options idp.Options, ) (*org.LDAPIDPChangedEvent, error) { @@ -858,7 +858,7 @@ func (wm *OrgAppleIDPWriteModel) NewChangedEvent( teamID, keyID string, privateKey []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, scopes []string, options idp.Options, ) (*org.AppleIDPChangedEvent, error) { @@ -924,7 +924,7 @@ func (wm *OrgSAMLIDPWriteModel) NewChangedEvent( metadata, key, certificate []byte, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, binding string, withSignedRequest bool, options idp.Options, diff --git a/internal/command/org_idp_oidc_config_model.go b/internal/command/org_idp_oidc_config_model.go index 972e0fabf7..571f0aa91b 100644 --- a/internal/command/org_idp_oidc_config_model.go +++ b/internal/command/org_idp_oidc_config_model.go @@ -92,7 +92,7 @@ func (wm *IDPOIDCConfigWriteModel) NewChangedEvent( authorizationEndpoint, tokenEndpoint, clientSecretString string, - secretCrypto crypto.Crypto, + secretCrypto crypto.EncryptionAlgorithm, idpDisplayNameMapping, userNameMapping domain.OIDCMappingField, scopes ...string, diff --git a/internal/command/org_test.go b/internal/command/org_test.go index 12199a998f..919c86b80f 100644 --- a/internal/command/org_test.go +++ b/internal/command/org_test.go @@ -1260,7 +1260,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { type fields struct { eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator - newCode cryptoCodeFunc + newCode encrypedCodeFunc keyAlgorithm crypto.EncryptionAlgorithm } type args struct { @@ -1437,7 +1437,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { ), ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID", "userID"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: authz.WithRequestedDomain(context.Background(), "iam-domain"), @@ -1616,7 +1616,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) { ), ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID", "userID", "tokenID"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ @@ -1669,10 +1669,10 @@ func TestCommandSide_SetUpOrg(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore(t), - idGenerator: tt.fields.idGenerator, - newCode: tt.fields.newCode, - keyAlgorithm: tt.fields.keyAlgorithm, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + keyAlgorithm: tt.fields.keyAlgorithm, zitadelRoles: []authz.RoleMapping{ { Role: domain.RoleOrgOwner, diff --git a/internal/command/phone.go b/internal/command/phone.go index 9b0a422b26..0770231969 100644 --- a/internal/command/phone.go +++ b/internal/command/phone.go @@ -16,6 +16,6 @@ type Phone struct { ReturnCode bool } -func (c *Commands) newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg) +func (c *Commands) newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg) } diff --git a/internal/command/preparation_test.go b/internal/command/preparation_test.go index 766807856c..9ffd815b39 100644 --- a/internal/command/preparation_test.go +++ b/internal/command/preparation_test.go @@ -22,7 +22,7 @@ type CommandVerifier interface { Validate(eventstore.Command) bool } -// AssertValidation checks if the validation works as inteded +// AssertValidation checks if the validation works as intended func AssertValidation(t *testing.T, ctx context.Context, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) { t.Helper() @@ -51,7 +51,7 @@ func AssertValidation(t *testing.T, ctx context.Context, validation preparation. for i, cmd := range want.Commands { if v, ok := cmd.(CommandVerifier); ok { if verified := v.Validate(cmds[i]); !verified { - t.Errorf("verification failed on command: = %v, want %v", cmds[i], cmd) + t.Errorf("verification failed on command: =\n%v\nwant\n%v", cmds[i], cmd) } continue } diff --git a/internal/command/project_application.go b/internal/command/project_application.go index 700f3f68eb..07bfa837b1 100644 --- a/internal/command/project_application.go +++ b/internal/command/project_application.go @@ -3,8 +3,6 @@ package command import ( "context" - "github.com/zitadel/zitadel/internal/command/preparation" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/repository/project" "github.com/zitadel/zitadel/internal/zerrors" @@ -16,10 +14,6 @@ type AddApp struct { Name string } -func (c *Commands) newAppClientSecret(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.HashAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypeAppSecret, alg) -} - func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) { if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid") diff --git a/internal/command/project_application_api.go b/internal/command/project_application_api.go index cbbfb87ea3..b1af6c1e5d 100644 --- a/internal/command/project_application_api.go +++ b/internal/command/project_application_api.go @@ -4,10 +4,7 @@ import ( "context" "strings" - "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/command/preparation" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" project_repo "github.com/zitadel/zitadel/internal/repository/project" @@ -20,11 +17,11 @@ type addAPIApp struct { AuthMethodType domain.APIAuthMethodType ClientID string - ClientSecret *crypto.CryptoValue + EncodedHash string ClientSecretPlain string } -func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation { +func (c *Commands) AddAPIAppCommand(app *addAPIApp) preparation.Validation { return func() (preparation.CreateCommands, error) { if app.ID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument") @@ -44,11 +41,10 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA } if app.AuthMethodType == domain.APIAuthMethodTypeBasic { - code, err := c.newAppClientSecret(ctx, filter, clientSecretAlg) + app.EncodedHash, app.ClientSecretPlain, err = c.newHashedSecret(ctx, filter) if err != nil { return nil, err } - app.ClientSecret, app.ClientSecretPlain = code.Crypted, code.Plain } return []eventstore.Command{ @@ -63,7 +59,7 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA &app.Aggregate.Aggregate, app.ID, app.ClientID, - app.ClientSecret, + app.EncodedHash, app.AuthMethodType, ), }, nil @@ -71,7 +67,7 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA } } -func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) { +func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner, appID string) (_ *domain.APIApp, err error) { existingAPI, err := c.getAPIAppWriteModel(ctx, apiApp.AggregateID, appID, resourceOwner) if err != nil { return nil, err @@ -84,10 +80,10 @@ func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.A return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-9fnsa", "Errors.Project.NotFound") } - return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID, appSecretGenerator) + return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID) } -func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) { +func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (_ *domain.APIApp, err error) { if apiApp == nil || apiApp.AggregateID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid") } @@ -105,10 +101,10 @@ func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, return nil, err } - return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID, appSecretGenerator) + return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID) } -func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, project *domain.Project, appID string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) { +func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, project *domain.Project, appID string) (_ *domain.APIApp, err error) { apiApp.AppID = appID addedApplication := NewAPIApplicationWriteModel(apiApp.AggregateID, resourceOwner) @@ -118,12 +114,14 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A project_repo.NewApplicationAddedEvent(ctx, projectAgg, apiApp.AppID, apiApp.AppName), } - var stringPw string + var plain string err = domain.SetNewClientID(apiApp, c.idGenerator, project) if err != nil { return nil, err } - stringPw, err = domain.SetNewClientSecretIfNeeded(apiApp, appSecretGenerator) + plain, err = domain.SetNewClientSecretIfNeeded(apiApp, func() (string, string, error) { + return c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck + }) if err != nil { return nil, err } @@ -131,7 +129,7 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A projectAgg, apiApp.AppID, apiApp.ClientID, - apiApp.ClientSecret, + apiApp.EncodedHash, apiApp.AuthMethodType)) addedApplication.AppID = apiApp.AppID @@ -144,7 +142,7 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A return nil, err } result := apiWriteModelToAPIConfig(addedApplication) - result.ClientSecretString = stringPw + result.ClientSecretString = plain return result, nil } @@ -188,7 +186,7 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA return apiWriteModelToAPIConfig(existingAPI), nil } -func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.APIApp, error) { +func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.APIApp, error) { if projectID == "" || appID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing") } @@ -203,14 +201,14 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap if !existingAPI.IsAPI() { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI") } - cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator) + encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck if err != nil { return nil, err } projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret)) + pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, encodedHash)) if err != nil { return nil, err } @@ -220,7 +218,7 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap } result := apiWriteModelToAPIConfig(existingAPI) - result.ClientSecretString = stringPW + result.ClientSecretString = plain return result, err } @@ -233,26 +231,35 @@ func (c *Commands) VerifyAPIClientSecret(ctx context.Context, projectID, appID, return err } if !app.State.Exists() { - return zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NoExisting") + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NotExisting") } if !app.IsAPI() { return zerrors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI") } - if app.ClientSecret == nil { + if app.HashedSecret == "" { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid") } projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel) - ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash") - err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.codeAlg) + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := c.secretHasher.Verify(app.HashedSecret, secret) spanPasswordComparison.EndWithError(err) if err == nil { - _, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID)) + c.apiSecretCheckSucceeded(ctx, projectAgg, app.AppID, updated) return err } - _, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID)) - logging.Log("COMMAND-g3f12").OnError(err).Error("could not push event APIClientSecretCheckFailed") - return zerrors.ThrowInvalidArgument(nil, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid") + c.apiSecretCheckFailed(ctx, projectAgg, app.AppID) + return zerrors.ThrowInvalidArgument(err, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid") +} + +func (c *Commands) APISecretCheckSucceeded(ctx context.Context, appID, projectID, resourceOwner, updated string) { + agg := project_repo.NewAggregate(projectID, resourceOwner) + c.apiSecretCheckSucceeded(ctx, &agg.Aggregate, appID, updated) +} + +func (c *Commands) APISecretCheckFailed(ctx context.Context, appID, projectID, resourceOwner string) { + agg := project_repo.NewAggregate(projectID, resourceOwner) + c.apiSecretCheckFailed(ctx, &agg.Aggregate, appID) } func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*APIApplicationWriteModel, error) { @@ -263,3 +270,18 @@ func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, re } return appWriteModel, nil } + +func (c *Commands) apiSecretCheckSucceeded(ctx context.Context, agg *eventstore.Aggregate, appID, updated string) { + cmds := append( + make([]eventstore.Command, 0, 2), + project_repo.NewAPIConfigSecretCheckSucceededEvent(ctx, agg, appID), + ) + if updated != "" { + cmds = append(cmds, project_repo.NewAPIConfigSecretHashUpdatedEvent(ctx, agg, appID, updated)) + } + c.asyncPush(ctx, cmds...) +} + +func (c *Commands) apiSecretCheckFailed(ctx context.Context, agg *eventstore.Aggregate, appID string) { + c.asyncPush(ctx, project_repo.NewAPIConfigSecretCheckFailedEvent(ctx, agg, appID)) +} diff --git a/internal/command/project_application_api_model.go b/internal/command/project_application_api_model.go index bb95341850..a6df8d77e4 100644 --- a/internal/command/project_application_api_model.go +++ b/internal/command/project_application_api_model.go @@ -15,7 +15,7 @@ type APIApplicationWriteModel struct { AppID string AppName string ClientID string - ClientSecret *crypto.CryptoValue + HashedSecret string ClientSecretString string AuthMethodType domain.APIAuthMethodType State domain.AppState @@ -83,6 +83,11 @@ func (wm *APIApplicationWriteModel) AppendEvents(events ...eventstore.Event) { continue } wm.WriteModel.AppendEvents(e) + case *project.APIConfigSecretHashUpdatedEvent: + if e.AppID != wm.AppID { + continue + } + wm.WriteModel.AppendEvents(e) case *project.ProjectRemovedEvent: wm.WriteModel.AppendEvents(e) } @@ -114,7 +119,9 @@ func (wm *APIApplicationWriteModel) Reduce() error { case *project.APIConfigChangedEvent: wm.appendChangeAPIEvent(e) case *project.APIConfigSecretChangedEvent: - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) + case *project.APIConfigSecretHashUpdatedEvent: + wm.HashedSecret = e.HashedSecret case *project.ProjectRemovedEvent: wm.State = domain.AppStateRemoved } @@ -125,7 +132,7 @@ func (wm *APIApplicationWriteModel) Reduce() error { func (wm *APIApplicationWriteModel) appendAddAPIEvent(e *project.APIConfigAddedEvent) { wm.api = true wm.ClientID = e.ClientID - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) wm.AuthMethodType = e.AuthMethodType } @@ -150,8 +157,9 @@ func (wm *APIApplicationWriteModel) Query() *eventstore.SearchQueryBuilder { project.APIConfigAddedType, project.APIConfigChangedType, project.APIConfigSecretChangedType, - project.ProjectRemovedType). - Builder() + project.APIConfigSecretHashUpdatedType, + project.ProjectRemovedType, + ).Builder() } func (wm *APIApplicationWriteModel) NewChangedEvent( diff --git a/internal/command/project_application_api_test.go b/internal/command/project_application_api_test.go index f5d8fb7836..7f3d249bbc 100644 --- a/internal/command/project_application_api_test.go +++ b/internal/command/project_application_api_test.go @@ -2,9 +2,13 @@ package command import ( "context" + "io" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zitadel/passwap" + "github.com/zitadel/passwap/bcrypt" "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" @@ -114,7 +118,7 @@ func TestAddAPIConfig(t *testing.T) { project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate, "appID", "clientID@project", - nil, + "", domain.APIAuthMethodTypePrivateKeyJWT, ), }, @@ -137,7 +141,6 @@ func TestAddAPIConfig(t *testing.T) { }, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, }, - nil, ), tt.args.filter, tt.want) }) } @@ -145,14 +148,13 @@ func TestAddAPIConfig(t *testing.T) { func TestCommandSide_AddAPIApplication(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator } type args struct { - ctx context.Context - apiApp *domain.APIApp - resourceOwner string - secretGenerator crypto.Generator + ctx context.Context + apiApp *domain.APIApp + resourceOwner string } type res struct { want *domain.APIApp @@ -167,9 +169,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "no aggregate id, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -183,8 +183,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "project not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -206,8 +205,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "invalid app, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -236,8 +234,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "create api app basic, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -256,12 +253,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), @@ -276,8 +268,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { AppName: "app", AuthMethodType: domain.APIAuthMethodTypeBasic, }, - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + resourceOwner: "org1", }, res: res{ want: &domain.APIApp{ @@ -288,7 +279,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.APIAuthMethodTypeBasic, State: domain.AppStateActive, }, @@ -297,8 +288,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { { name: "create api app jwt, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -317,7 +307,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - nil, + "", domain.APIAuthMethodTypePrivateKeyJWT), ), ), @@ -352,10 +342,14 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner, tt.args.secretGenerator) + got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner) if tt.res.err == nil { assert.NoError(t, err) } @@ -371,7 +365,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { func TestCommandSide_ChangeAPIApplication(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore } type args struct { ctx context.Context @@ -391,9 +385,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "missing appid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -414,9 +406,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "missing aggregateid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -437,8 +427,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "app not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -460,8 +449,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "no changes, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewApplicationAddedEvent(context.Background(), @@ -475,7 +463,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - nil, + "", domain.APIAuthMethodTypePrivateKeyJWT), ), ), @@ -500,8 +488,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { { name: "change api app, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewApplicationAddedEvent(context.Background(), @@ -515,12 +502,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), @@ -563,7 +545,11 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } got, err := r.ChangeAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner) if tt.res.err == nil { @@ -581,14 +567,13 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) { func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(*testing.T) *eventstore.Eventstore } type args struct { - ctx context.Context - appID string - projectID string - resourceOwner string - secretGenerator crypto.Generator + ctx context.Context + appID string + projectID string + resourceOwner string } type res struct { want *domain.APIApp @@ -603,9 +588,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { { name: "no projectid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -619,9 +602,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { { name: "no appid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -636,8 +617,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { { name: "app not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -654,8 +634,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { { name: "change secret, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewApplicationAddedEvent(context.Background(), @@ -669,12 +648,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), @@ -682,22 +656,16 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { project.NewAPIConfigSecretChangedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, "app1", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", ), ), ), }, args: args{ - ctx: context.Background(), - projectID: "project1", - appID: "app1", - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + ctx: context.Background(), + projectID: "project1", + appID: "app1", + resourceOwner: "org1", }, res: res{ want: &domain.APIApp{ @@ -708,7 +676,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.APIAuthMethodTypeBasic, State: domain.AppStateActive, }, @@ -718,9 +686,13 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner, tt.args.secretGenerator) + got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner) if tt.res.err == nil { assert.NoError(t, err) } @@ -745,3 +717,105 @@ func newAPIAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner ) return event } + +func TestCommands_VerifyAPIClientSecret(t *testing.T) { + hasher := &crypto.Hasher{ + Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)), + } + hashedSecret, err := hasher.Hash("secret") + require.NoError(t, err) + agg := project.NewAggregate("projectID", "orgID") + + tests := []struct { + name string + secret string + eventstore func(*testing.T) *eventstore.Eventstore + wantErr error + }{ + { + name: "filter error", + eventstore: expectEventstore( + expectFilterError(io.ErrClosedPipe), + ), + wantErr: io.ErrClosedPipe, + }, + { + name: "app not exists", + eventstore: expectEventstore( + expectFilter(), + ), + wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NotExisting"), + }, + { + name: "wrong app type", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + ), + ), + wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI"), + }, + { + name: "no secret set", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", "", domain.APIAuthMethodTypePrivateKeyJWT), + ), + ), + ), + wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid"), + }, + { + name: "check succeeded", + secret: "secret", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", hashedSecret, domain.APIAuthMethodTypePrivateKeyJWT), + ), + ), + expectPush( + project.NewAPIConfigSecretCheckSucceededEvent(context.Background(), &agg.Aggregate, "appID"), + ), + ), + }, + { + name: "check failed", + secret: "wrong!", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", hashedSecret, domain.APIAuthMethodTypePrivateKeyJWT), + ), + ), + expectPush( + project.NewAPIConfigSecretCheckFailedEvent(context.Background(), &agg.Aggregate, "appID"), + ), + ), + wantErr: zerrors.ThrowInvalidArgument(err, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.eventstore(t), + secretHasher: hasher, + } + err := c.VerifyAPIClientSecret(context.Background(), "projectID", "appID", tt.secret) + c.jobs.Wait() + require.ErrorIs(t, err, tt.wantErr) + }) + } +} diff --git a/internal/command/project_application_key_test.go b/internal/command/project_application_key_test.go index ceeae93f70..9fd46c75f3 100644 --- a/internal/command/project_application_key_test.go +++ b/internal/command/project_application_key_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/assert" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/v1/models" @@ -117,12 +116,7 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), @@ -163,12 +157,7 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) { &project.NewAggregate("project1", "org1").Aggregate, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", domain.APIAuthMethodTypeBasic), ), ), diff --git a/internal/command/project_application_oidc.go b/internal/command/project_application_oidc.go index 0ece1bf364..b4a9eaae9b 100644 --- a/internal/command/project_application_oidc.go +++ b/internal/command/project_application_oidc.go @@ -5,11 +5,8 @@ import ( "strings" "time" - "github.com/zitadel/logging" - http_util "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/command/preparation" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" project_repo "github.com/zitadel/zitadel/internal/repository/project" @@ -36,12 +33,12 @@ type addOIDCApp struct { SkipSuccessPageForNativeApp bool ClientID string - ClientSecret *crypto.CryptoValue + ClientSecret string ClientSecretPlain string } // AddOIDCAppCommand prepares the commands to add an oidc app. The ClientID will be set during the CreateCommands -func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation { +func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation { return func() (preparation.CreateCommands, error) { if app.ID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument") @@ -77,11 +74,10 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.Has } if app.AuthMethodType == domain.OIDCAuthMethodTypeBasic || app.AuthMethodType == domain.OIDCAuthMethodTypePost { - code, err := c.newAppClientSecret(ctx, filter, clientSecretAlg) + app.ClientSecret, app.ClientSecretPlain, err = c.newHashedSecret(ctx, filter) if err != nil { return nil, err } - app.ClientSecret, app.ClientSecretPlain = code.Crypted, code.Plain } return []eventstore.Command{ @@ -118,7 +114,7 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.Has } } -func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) { +func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string) (_ *domain.OIDCApp, err error) { existingApp, err := c.getOIDCAppWriteModel(ctx, oidcApp.AggregateID, appID, resourceOwner) if err != nil { return nil, err @@ -132,10 +128,10 @@ func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-3m9s2", "Errors.Project.NotFound") } - return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID, appSecretGenerator) + return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID) } -func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) { +func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string) (_ *domain.OIDCApp, err error) { if oidcApp == nil || oidcApp.AggregateID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid") } @@ -153,11 +149,10 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCA return nil, err } - return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID, appSecretGenerator) + return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID) } -func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, project *domain.Project, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) { - +func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, project *domain.Project, appID string) (_ *domain.OIDCApp, err error) { addedApplication := NewOIDCApplicationWriteModel(oidcApp.AggregateID, resourceOwner) projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel) @@ -167,12 +162,14 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName), } - var stringPw string + var plain string err = domain.SetNewClientID(oidcApp, c.idGenerator, project) if err != nil { return nil, err } - stringPw, err = domain.SetNewClientSecretIfNeeded(oidcApp, appSecretGenerator) + plain, err = domain.SetNewClientSecretIfNeeded(oidcApp, func() (string, string, error) { + return c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck + }) if err != nil { return nil, err } @@ -181,7 +178,7 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain oidcApp.OIDCVersion, oidcApp.AppID, oidcApp.ClientID, - oidcApp.ClientSecret, + oidcApp.EncodedHash, trimStringSliceWhiteSpaces(oidcApp.RedirectUris), oidcApp.ResponseTypes, oidcApp.GrantTypes, @@ -208,7 +205,7 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain return nil, err } result := oidcWriteModelToOIDCConfig(addedApplication) - result.ClientSecretString = stringPw + result.ClientSecretString = plain result.FillCompliance() return result, nil } @@ -270,7 +267,7 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA return result, nil } -func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.OIDCApp, error) { +func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.OIDCApp, error) { if projectID == "" || appID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing") } @@ -285,14 +282,14 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a if !existingOIDC.IsOIDC() { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC") } - cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator) + encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck if err != nil { return nil, err } projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret)) + pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, encodedHash)) if err != nil { return nil, err } @@ -302,7 +299,7 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a } result := oidcWriteModelToOIDCConfig(existingOIDC) - result.ClientSecretString = stringPW + result.ClientSecretString = plain return result, err } @@ -315,26 +312,35 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID, return err } if !app.State.Exists() { - return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.NotExisting") + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D8hba", "Errors.Project.App.NotExisting") } if !app.IsOIDC() { return zerrors.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC") } - if app.ClientSecret == nil { + if app.HashedSecret == "" { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.OIDCConfigInvalid") } projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel) - ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash") - err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.codeAlg) + ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify") + updated, err := c.secretHasher.Verify(app.HashedSecret, secret) spanPasswordComparison.EndWithError(err) if err == nil { - _, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID)) - return err + c.oidcSecretCheckSucceeded(ctx, projectAgg, appID, updated) + return nil } - _, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID)) - logging.OnError(err).Error("could not push event OIDCClientSecretCheckFailed") - return zerrors.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid") + c.oidcSecretCheckFailed(ctx, projectAgg, appID) + return zerrors.ThrowInvalidArgument(err, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid") +} + +func (c *Commands) OIDCSecretCheckSucceeded(ctx context.Context, appID, projectID, resourceOwner, updated string) { + agg := project_repo.NewAggregate(projectID, resourceOwner) + c.oidcSecretCheckSucceeded(ctx, &agg.Aggregate, appID, updated) +} + +func (c *Commands) OIDCSecretCheckFailed(ctx context.Context, appID, projectID, resourceOwner string) { + agg := project_repo.NewAggregate(projectID, resourceOwner) + c.oidcSecretCheckFailed(ctx, &agg.Aggregate, appID) } func (c *Commands) getOIDCAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*OIDCApplicationWriteModel, error) { @@ -366,3 +372,18 @@ func trimStringSliceWhiteSpaces(slice []string) []string { } return slice } + +func (c *Commands) oidcSecretCheckSucceeded(ctx context.Context, agg *eventstore.Aggregate, appID, updated string) { + cmds := append( + make([]eventstore.Command, 0, 2), + project_repo.NewOIDCConfigSecretCheckSucceededEvent(ctx, agg, appID), + ) + if updated != "" { + cmds = append(cmds, project_repo.NewOIDCConfigSecretHashUpdatedEvent(ctx, agg, appID, updated)) + } + c.asyncPush(ctx, cmds...) +} + +func (c *Commands) oidcSecretCheckFailed(ctx context.Context, agg *eventstore.Aggregate, appID string) { + c.asyncPush(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, agg, appID)) +} diff --git a/internal/command/project_application_oidc_model.go b/internal/command/project_application_oidc_model.go index 7b70d89554..585fdf6c1d 100644 --- a/internal/command/project_application_oidc_model.go +++ b/internal/command/project_application_oidc_model.go @@ -17,7 +17,7 @@ type OIDCApplicationWriteModel struct { AppID string AppName string ClientID string - ClientSecret *crypto.CryptoValue + HashedSecret string ClientSecretString string RedirectUris []string ResponseTypes []domain.OIDCResponseType @@ -100,6 +100,11 @@ func (wm *OIDCApplicationWriteModel) AppendEvents(events ...eventstore.Event) { continue } wm.WriteModel.AppendEvents(e) + case *project.OIDCConfigSecretHashUpdatedEvent: + if e.AppID != wm.AppID { + continue + } + wm.WriteModel.AppendEvents(e) case *project.ProjectRemovedEvent: wm.WriteModel.AppendEvents(e) } @@ -131,7 +136,9 @@ func (wm *OIDCApplicationWriteModel) Reduce() error { case *project.OIDCConfigChangedEvent: wm.appendChangeOIDCEvent(e) case *project.OIDCConfigSecretChangedEvent: - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) + case *project.OIDCConfigSecretHashUpdatedEvent: + wm.HashedSecret = e.HashedSecret case *project.ProjectRemovedEvent: wm.State = domain.AppStateRemoved } @@ -142,7 +149,7 @@ func (wm *OIDCApplicationWriteModel) Reduce() error { func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAddedEvent) { wm.oidc = true wm.ClientID = e.ClientID - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) wm.RedirectUris = e.RedirectUris wm.ResponseTypes = e.ResponseTypes wm.GrantTypes = e.GrantTypes @@ -223,8 +230,9 @@ func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder { project.OIDCConfigAddedType, project.OIDCConfigChangedType, project.OIDCConfigSecretChangedType, - project.ProjectRemovedType). - Builder() + project.OIDCConfigSecretHashUpdatedType, + project.ProjectRemovedType, + ).Builder() } func (wm *OIDCApplicationWriteModel) NewChangedEvent( diff --git a/internal/command/project_application_oidc_test.go b/internal/command/project_application_oidc_test.go index 55a3f70515..73cb837057 100644 --- a/internal/command/project_application_oidc_test.go +++ b/internal/command/project_application_oidc_test.go @@ -2,10 +2,14 @@ package command import ( "context" + "io" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zitadel/passwap" + "github.com/zitadel/passwap/bcrypt" "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/crypto" @@ -23,9 +27,8 @@ func TestAddOIDCApp(t *testing.T) { idGenerator id.Generator } type args struct { - app *addOIDCApp - clientSecretAlg crypto.HashAlgorithm - filter preparation.FilterToQueryReducer + app *addOIDCApp + filter preparation.FilterToQueryReducer } ctx := context.Background() @@ -156,7 +159,7 @@ func TestAddOIDCApp(t *testing.T) { domain.OIDCVersionV1, "id", "clientID@project", - nil, + "", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -221,7 +224,7 @@ func TestAddOIDCApp(t *testing.T) { domain.OIDCVersionV1, "id", "clientID@project", - nil, + "", nil, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -240,17 +243,85 @@ func TestAddOIDCApp(t *testing.T) { }, }, }, + { + name: "with secret", + fields: fields{ + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "clientID"), + }, + args: args{ + app: &addOIDCApp{ + AddApp: AddApp{ + Aggregate: *agg, + ID: "id", + Name: "name", + }, + GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + Version: domain.OIDCVersionV1, + + ApplicationType: domain.OIDCApplicationTypeWeb, + AuthMethodType: domain.OIDCAuthMethodTypeBasic, + AccessTokenType: domain.OIDCTokenTypeBearer, + }, + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + project.NewProjectAddedEvent( + ctx, + &agg.Aggregate, + "project", + false, + false, + false, + domain.PrivateLabelingSettingUnspecified, + ), + }, nil + }). + Filter(), + }, + want: Want{ + Commands: []eventstore.Command{ + project.NewApplicationAddedEvent(ctx, &agg.Aggregate, + "id", + "name", + ), + project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate, + domain.OIDCVersionV1, + "id", + "clientID@project", + "secret", + nil, + []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypeBasic, + nil, + false, + domain.OIDCTokenTypeBearer, + false, + false, + false, + 0, + nil, + false, + ), + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := Commands{ - idGenerator: tt.fields.idGenerator, + idGenerator: tt.fields.idGenerator, + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } AssertValidation(t, context.Background(), c.AddOIDCAppCommand( tt.args.app, - tt.args.clientSecretAlg, ), tt.args.filter, tt.want) }) } @@ -258,14 +329,13 @@ func TestAddOIDCApp(t *testing.T) { func TestCommandSide_AddOIDCApplication(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator } type args struct { - ctx context.Context - oidcApp *domain.OIDCApp - resourceOwner string - secretGenerator crypto.Generator + ctx context.Context + oidcApp *domain.OIDCApp + resourceOwner string } type res struct { want *domain.OIDCApp @@ -280,9 +350,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "no aggregate id, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -296,8 +364,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "project not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -319,8 +386,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "invalid app, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -349,8 +415,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "create oidc app basic using whitespaces in uris, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -370,12 +435,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -418,8 +478,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { AdditionalOrigins: []string{" https://sub.test.ch "}, SkipNativeAppSuccessPage: true, }, - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + resourceOwner: "org1", }, res: res{ want: &domain.OIDCApp{ @@ -430,7 +489,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.OIDCAuthMethodTypePost, OIDCVersion: domain.OIDCVersionV1, RedirectUris: []string{"https://test.ch"}, @@ -454,8 +513,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { { name: "create oidc app basic, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), @@ -475,12 +533,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -523,8 +576,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { AdditionalOrigins: []string{"https://sub.test.ch"}, SkipNativeAppSuccessPage: true, }, - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + resourceOwner: "org1", }, res: res{ want: &domain.OIDCApp{ @@ -535,7 +587,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.OIDCAuthMethodTypePost, OIDCVersion: domain.OIDCVersionV1, RedirectUris: []string{"https://test.ch"}, @@ -560,10 +612,14 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner, tt.args.secretGenerator) + got, err := r.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner) if tt.res.err == nil { assert.NoError(t, err) } @@ -709,12 +765,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -783,12 +834,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -857,12 +903,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -965,14 +1006,13 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(*testing.T) *eventstore.Eventstore } type args struct { - ctx context.Context - appID string - projectID string - resourceOwner string - secretGenerator crypto.Generator + ctx context.Context + appID string + projectID string + resourceOwner string } type res struct { want *domain.OIDCApp @@ -987,9 +1027,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { { name: "no projectid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -1003,9 +1041,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { { name: "no appid, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -1020,8 +1056,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { { name: "app not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -1038,8 +1073,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { { name: "change secret, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( project.NewApplicationAddedEvent(context.Background(), @@ -1054,12 +1088,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { domain.OIDCVersionV1, "app1", "client1@project", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", []string{"https://test.ch"}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -1081,22 +1110,16 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { project.NewOIDCConfigSecretChangedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, "app1", - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", ), ), ), }, args: args{ - ctx: context.Background(), - projectID: "project1", - appID: "app1", - resourceOwner: "org1", - secretGenerator: GetMockSecretGenerator(t), + ctx: context.Background(), + projectID: "project1", + appID: "app1", + resourceOwner: "org1", }, res: res{ want: &domain.OIDCApp{ @@ -1107,7 +1130,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { AppID: "app1", AppName: "app", ClientID: "client1@project", - ClientSecretString: "a", + ClientSecretString: "secret", AuthMethodType: domain.OIDCAuthMethodTypePost, OIDCVersion: domain.OIDCVersionV1, RedirectUris: []string{"https://test.ch"}, @@ -1129,11 +1152,15 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.name, func(*testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner, tt.args.secretGenerator) + got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner) if tt.res.err == nil { assert.NoError(t, err) } @@ -1165,3 +1192,165 @@ func newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner ) return event } + +func TestCommands_VerifyOIDCClientSecret(t *testing.T) { + hasher := &crypto.Hasher{ + Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)), + } + hashedSecret, err := hasher.Hash("secret") + require.NoError(t, err) + agg := project.NewAggregate("projectID", "orgID") + + tests := []struct { + name string + secret string + eventstore func(*testing.T) *eventstore.Eventstore + wantErr error + }{ + { + name: "filter error", + eventstore: expectEventstore( + expectFilterError(io.ErrClosedPipe), + ), + wantErr: io.ErrClosedPipe, + }, + { + name: "app not exists", + eventstore: expectEventstore( + expectFilter(), + ), + wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D8hba", "Errors.Project.App.NotExisting"), + }, + { + name: "wrong app type", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + ), + ), + wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC"), + }, + { + name: "no secret set", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewOIDCConfigAddedEvent(context.Background(), + &agg.Aggregate, + domain.OIDCVersionV1, + "appID", + "client1@project", + "", + []string{"https://test.ch"}, + []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypePost, + []string{"https://test.ch/logout"}, + true, + domain.OIDCTokenTypeBearer, + true, + true, + true, + time.Second*1, + []string{"https://sub.test.ch"}, + false, + ), + ), + ), + ), + wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.OIDCConfigInvalid"), + }, + { + name: "check succeeded", + secret: "secret", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewOIDCConfigAddedEvent(context.Background(), + &agg.Aggregate, + domain.OIDCVersionV1, + "appID", + "client1@project", + hashedSecret, + []string{"https://test.ch"}, + []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypePost, + []string{"https://test.ch/logout"}, + true, + domain.OIDCTokenTypeBearer, + true, + true, + true, + time.Second*1, + []string{"https://sub.test.ch"}, + false, + ), + ), + ), + expectPush( + project.NewOIDCConfigSecretCheckSucceededEvent(context.Background(), &agg.Aggregate, "appID"), + ), + ), + }, + { + name: "check failed", + secret: "wrong!", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"), + ), + eventFromEventPusher( + project.NewOIDCConfigAddedEvent(context.Background(), + &agg.Aggregate, + domain.OIDCVersionV1, + "appID", + "client1@project", + hashedSecret, + []string{"https://test.ch"}, + []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypePost, + []string{"https://test.ch/logout"}, + true, + domain.OIDCTokenTypeBearer, + true, + true, + true, + time.Second*1, + []string{"https://sub.test.ch"}, + false, + ), + ), + ), + expectPush( + project.NewOIDCConfigSecretCheckFailedEvent(context.Background(), &agg.Aggregate, "appID"), + ), + ), + wantErr: zerrors.ThrowInvalidArgument(err, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.eventstore(t), + secretHasher: hasher, + } + err := c.VerifyOIDCClientSecret(context.Background(), "projectID", "appID", tt.secret) + c.jobs.Wait() + require.ErrorIs(t, err, tt.wantErr) + }) + } +} diff --git a/internal/command/session.go b/internal/command/session.go index ae63f3d6ec..f7e0f889cd 100644 --- a/internal/command/session.go +++ b/internal/command/session.go @@ -31,11 +31,11 @@ type SessionCommands struct { eventstore *eventstore.Eventstore eventCommands []eventstore.Command - hasher *crypto.PasswordHasher + hasher *crypto.Hasher intentAlg crypto.EncryptionAlgorithm totpAlg crypto.EncryptionAlgorithm otpAlg crypto.EncryptionAlgorithm - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc createToken func(sessionID string) (id string, token string, err error) now func() time.Time } @@ -49,7 +49,7 @@ func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWri intentAlg: c.idpConfigEncryption, totpAlg: c.multifactors.OTP.CryptoMFA, otpAlg: c.userEncryption, - createCode: c.newCodeWithDefault, + createCode: c.newEncryptedCodeWithDefault, createToken: c.sessionTokenCreator, now: time.Now, } diff --git a/internal/command/session_otp.go b/internal/command/session_otp.go index 8afee5d2d1..859f893400 100644 --- a/internal/command/session_otp.go +++ b/internal/command/session_otp.go @@ -120,7 +120,7 @@ func CheckOTPSMS(code string) SessionCommand { if challenge == nil { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3tv", "Errors.User.Code.NotFound") } - err = crypto.VerifyCodeWithAlgorithm(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) + err = crypto.VerifyCode(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) if err != nil { return err } @@ -138,7 +138,7 @@ func CheckOTPEmail(code string) SessionCommand { if challenge == nil { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-zF3g3", "Errors.User.Code.NotFound") } - err = crypto.VerifyCodeWithAlgorithm(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) + err = crypto.VerifyCode(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) if err != nil { return err } diff --git a/internal/command/session_otp_test.go b/internal/command/session_otp_test.go index f4db8241ce..c5be0aecae 100644 --- a/internal/command/session_otp_test.go +++ b/internal/command/session_otp_test.go @@ -20,7 +20,7 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type res struct { err error @@ -65,7 +65,7 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ returnCode: "1234567", @@ -122,7 +122,7 @@ func TestCommands_CreateOTPSMSChallenge(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type res struct { err error @@ -166,7 +166,7 @@ func TestCommands_CreateOTPSMSChallenge(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ commands: []eventstore.Command{ @@ -292,7 +292,7 @@ func TestCommands_CreateOTPEmailChallengeURLTemplate(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type args struct { urlTmpl string @@ -361,7 +361,7 @@ func TestCommands_CreateOTPEmailChallengeURLTemplate(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ commands: []eventstore.Command{ @@ -421,7 +421,7 @@ func TestCommands_CreateOTPEmailChallengeReturnCode(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type res struct { err error @@ -465,7 +465,7 @@ func TestCommands_CreateOTPEmailChallengeReturnCode(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ returnCode: "1234567", @@ -523,7 +523,7 @@ func TestCommands_CreateOTPEmailChallenge(t *testing.T) { type fields struct { userID string eventstore func(*testing.T) *eventstore.Eventstore - createCode cryptoCodeWithDefaultFunc + createCode encryptedCodeWithDefaultFunc } type res struct { err error @@ -566,7 +566,7 @@ func TestCommands_CreateOTPEmailChallenge(t *testing.T) { ), ), ), - createCode: mockCodeWithDefault("1234567", 5*time.Minute), + createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute), }, res: res{ commands: []eventstore.Command{ diff --git a/internal/command/user.go b/internal/command/user.go index 6d22b212e0..9253da5719 100644 --- a/internal/command/user.go +++ b/internal/command/user.go @@ -476,8 +476,8 @@ func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id return exists, nil } -func (c *Commands) newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypeInitCode, alg) +func (c *Commands) newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeInitCode, alg) } func userWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, resourceOwner string) (*UserWriteModel, error) { diff --git a/internal/command/user_human.go b/internal/command/user_human.go index f8993ae6b4..a0cfff5bd3 100644 --- a/internal/command/user_human.go +++ b/internal/command/user_human.go @@ -12,6 +12,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/repository/user" + "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -77,7 +78,7 @@ type AddLink struct { IDPExternalID string } -func (h *AddHuman) Validate(hasher *crypto.PasswordHasher) (err error) { +func (h *AddHuman) Validate(hasher *crypto.Hasher) (err error) { if err := h.Email.Validate(); err != nil { return err } @@ -164,7 +165,7 @@ type humanCreationCommand interface { } //nolint:gocognit -func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto.PasswordHasher, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation { +func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto.Hasher, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation { return func() (_ preparation.CreateCommands, err error) { if err := human.Validate(hasher); err != nil { return nil, err @@ -329,17 +330,19 @@ func (c *Commands) addHumanCommandCheckID(ctx context.Context, filter preparatio return nil } -func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQueryReducer, createCmd humanCreationCommand, human *AddHuman, hasher *crypto.PasswordHasher) (err error) { +func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQueryReducer, createCmd humanCreationCommand, human *AddHuman, hasher *crypto.Hasher) (err error) { if human.Password != "" { if err = humanValidatePassword(ctx, filter, human.Password); err != nil { return err } - secret, err := hasher.Hash(human.Password) + _, spanHash := tracing.NewNamedSpan(ctx, "passwap.Hash") + encodedHash, err := hasher.Hash(human.Password) + spanHash.EndWithError(err) if err != nil { return err } - createCmd.AddPasswordData(secret, human.PasswordChangeRequired) + createCmd.AddPasswordData(encodedHash, human.PasswordChangeRequired) return nil } @@ -589,7 +592,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain. human.EnsureDisplayName() if human.Password != nil { - if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordHasher, human.Password.ChangeRequired); err != nil { + if err := human.HashPasswordIfExisting(ctx, pwPolicy, c.userPasswordHasher, human.Password.ChangeRequired); err != nil { return nil, nil, err } } diff --git a/internal/command/user_human_email.go b/internal/command/user_human_email.go index c98b396dbf..4607b1294e 100644 --- a/internal/command/user_human_email.go +++ b/internal/command/user_human_email.go @@ -81,7 +81,7 @@ func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceo } userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) - err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, emailCodeGenerator) + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, emailCodeGenerator.Alg()) if err == nil { pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailVerifiedEvent(ctx, userAgg)) if err != nil { diff --git a/internal/command/user_human_init.go b/internal/command/user_human_init.go index af807486d4..f3ec27a199 100644 --- a/internal/command/user_human_init.go +++ b/internal/command/user_human_init.go @@ -67,7 +67,7 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne } userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) - err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, initCodeGenerator) + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, initCodeGenerator.Alg()) if err != nil { _, err = c.eventstore.Push(ctx, user.NewHumanInitializedCheckFailedEvent(ctx, userAgg)) logging.WithFields("userID", userAgg.ID).OnError(err).Error("NewHumanInitializedCheckFailedEvent push failed") diff --git a/internal/command/user_human_init_test.go b/internal/command/user_human_init_test.go index eeffcf7201..d89671d768 100644 --- a/internal/command/user_human_init_test.go +++ b/internal/command/user_human_init_test.go @@ -292,7 +292,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) { func TestCommandSide_VerifyInitCode(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context diff --git a/internal/command/user_human_otp.go b/internal/command/user_human_otp.go index 38f360649d..21e0021d44 100644 --- a/internal/command/user_human_otp.go +++ b/internal/command/user_human_otp.go @@ -455,7 +455,7 @@ func (c *Commands) sendHumanOTP( if !existingOTP.OTPAdded() { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SFD52", "Errors.User.MFA.OTP.NotReady") } - config, err := secretGeneratorConfigWithDefault(ctx, c.eventstore.Filter, secretGeneratorType, defaultSecretGenerator) + config, err := cryptoGeneratorConfigWithDefault(ctx, c.eventstore.Filter, secretGeneratorType, defaultSecretGenerator) //nolint:staticcheck if err != nil { return err } @@ -515,7 +515,7 @@ func (c *Commands) humanCheckOTP( return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound") } userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate - err = crypto.VerifyCodeWithAlgorithm(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption) + err = crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption) if err == nil { _, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) return err diff --git a/internal/command/user_human_password.go b/internal/command/user_human_password.go index 3c8282409d..8831bff907 100644 --- a/internal/command/user_human_password.go +++ b/internal/command/user_human_password.go @@ -53,7 +53,7 @@ func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound") } - err = crypto.VerifyCodeWithAlgorithm(wm.CodeCreationDate, wm.CodeExpiry, wm.Code, code, c.userEncryption) + err = crypto.VerifyCode(wm.CodeCreationDate, wm.CodeExpiry, wm.Code, code, c.userEncryption) if err != nil { return nil, err } diff --git a/internal/command/user_human_password_model.go b/internal/command/user_human_password_model.go index 535907b3d8..ce23769812 100644 --- a/internal/command/user_human_password_model.go +++ b/internal/command/user_human_password_model.go @@ -36,11 +36,11 @@ func (wm *HumanPasswordWriteModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { case *user.HumanAddedEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.UserState = domain.UserStateActive case *user.HumanRegisteredEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.UserState = domain.UserStateActive case *user.HumanInitialCodeAddedEvent: @@ -48,7 +48,7 @@ func (wm *HumanPasswordWriteModel) Reduce() error { case *user.HumanInitializedCheckSucceededEvent: wm.UserState = domain.UserStateActive case *user.HumanPasswordChangedEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.Code = nil wm.PasswordCheckFailedCount = 0 diff --git a/internal/command/user_human_password_test.go b/internal/command/user_human_password_test.go index 970033a2f8..bb0fa9b26f 100644 --- a/internal/command/user_human_password_test.go +++ b/internal/command/user_human_password_test.go @@ -23,7 +23,7 @@ import ( func TestCommandSide_SetOneTimePassword(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher checkPermission domain.PermissionCheck } type args struct { @@ -270,7 +270,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore userEncryption crypto.EncryptionAlgorithm - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context @@ -598,7 +598,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) { func TestCommandSide_ChangePassword(t *testing.T) { type fields struct { - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context @@ -1202,7 +1202,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) { func TestCommandSide_CheckPassword(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context diff --git a/internal/command/user_human_phone.go b/internal/command/user_human_phone.go index 67a25f9bcf..7003c0530c 100644 --- a/internal/command/user_human_phone.go +++ b/internal/command/user_human_phone.go @@ -79,7 +79,7 @@ func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceo } userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) - err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, phoneCodeGenerator) + err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, phoneCodeGenerator.Alg()) if err == nil { pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPhoneVerifiedEvent(ctx, userAgg)) if err != nil { @@ -115,7 +115,7 @@ func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID, if existingPhone.IsPhoneVerified { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified") } - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck if err != nil { return nil, err } diff --git a/internal/command/user_human_test.go b/internal/command/user_human_test.go index 009c0ec994..689d8419b9 100644 --- a/internal/command/user_human_test.go +++ b/internal/command/user_human_test.go @@ -28,9 +28,9 @@ func TestCommandSide_AddHuman(t *testing.T) { type fields struct { eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher codeAlg crypto.EncryptionAlgorithm - newCode cryptoCodeFunc + newCode encrypedCodeFunc } type args struct { ctx context.Context @@ -245,7 +245,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -312,7 +312,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -380,7 +380,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -450,7 +450,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -521,7 +521,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -593,7 +593,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -996,7 +996,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("phonecode", time.Hour), + newCode: mockEncryptedCode("phonecode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1061,7 +1061,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -1136,7 +1136,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("phoneCode", time.Hour), + newCode: mockEncryptedCode("phoneCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1204,7 +1204,7 @@ func TestCommandSide_AddHuman(t *testing.T) { ), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -1242,7 +1242,7 @@ func TestCommandSide_AddHuman(t *testing.T) { userPasswordHasher: tt.fields.userPasswordHasher, userEncryption: tt.fields.codeAlg, idGenerator: tt.fields.idGenerator, - newCode: tt.fields.newCode, + newEncryptedCode: tt.fields.newCode, } err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail) if tt.res.err == nil { @@ -1266,7 +1266,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore idGenerator id.Generator - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context @@ -2483,7 +2483,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore idGenerator id.Generator - userPasswordHasher *crypto.PasswordHasher + userPasswordHasher *crypto.Hasher } type args struct { ctx context.Context @@ -4328,7 +4328,7 @@ func TestAddHumanCommand(t *testing.T) { type args struct { human *AddHuman orgID string - hasher *crypto.PasswordHasher + hasher *crypto.Hasher filter preparation.FilterToQueryReducer codeAlg crypto.EncryptionAlgorithm allowInitMail bool diff --git a/internal/command/user_human_webauthn.go b/internal/command/user_human_webauthn.go index 3328e95455..5588ed7286 100644 --- a/internal/command/user_human_webauthn.go +++ b/internal/command/user_human_webauthn.go @@ -575,7 +575,7 @@ func (c *Commands) humanVerifyPasswordlessInitCode(ctx context.Context, userID, if err != nil { return err } - err = crypto.VerifyCode(initCode.ChangeDate, initCode.Expiration, initCode.CryptoCode, verificationCode, passwordlessCodeGenerator) + err = crypto.VerifyCode(initCode.ChangeDate, initCode.Expiration, initCode.CryptoCode, verificationCode, passwordlessCodeGenerator.Alg()) if err != nil || initCode.State != domain.PasswordlessInitCodeStateActive { userAgg := UserAggregateFromWriteModel(&initCode.WriteModel) _, err = c.eventstore.Push(ctx, usr_repo.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, codeID)) diff --git a/internal/command/user_machine_model.go b/internal/command/user_machine_model.go index a5569faaf3..b7dfb02d32 100644 --- a/internal/command/user_machine_model.go +++ b/internal/command/user_machine_model.go @@ -18,8 +18,7 @@ type MachineWriteModel struct { Description string UserState domain.UserState AccessTokenType domain.OIDCTokenType - - ClientSecret *crypto.CryptoValue + HashedSecret string } func NewMachineWriteModel(userID, resourceOwner string) *MachineWriteModel { @@ -71,9 +70,11 @@ func (wm *MachineWriteModel) Reduce() error { case *user.UserRemovedEvent: wm.UserState = domain.UserStateDeleted case *user.MachineSecretSetEvent: - wm.ClientSecret = e.ClientSecret + wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) case *user.MachineSecretRemovedEvent: - wm.ClientSecret = nil + wm.HashedSecret = "" + case *user.MachineSecretHashUpdatedEvent: + wm.HashedSecret = e.HashedSecret } } return wm.WriteModel.Reduce() @@ -94,8 +95,9 @@ func (wm *MachineWriteModel) Query() *eventstore.SearchQueryBuilder { user.UserReactivatedType, user.UserRemovedType, user.MachineSecretSetType, - user.MachineSecretRemovedType). - Builder() + user.MachineSecretRemovedType, + user.MachineSecretHashUpdatedType, + ).Builder() } func (wm *MachineWriteModel) NewChangedEvent( diff --git a/internal/command/user_machine_secret.go b/internal/command/user_machine_secret.go index fbfd63441f..3349fc90a5 100644 --- a/internal/command/user_machine_secret.go +++ b/internal/command/user_machine_secret.go @@ -4,7 +4,6 @@ import ( "context" "github.com/zitadel/zitadel/internal/command/preparation" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" @@ -15,9 +14,9 @@ type GenerateMachineSecret struct { ClientSecret string } -func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, resourceOwner string, generator crypto.Generator, set *GenerateMachineSecret) (*domain.ObjectDetails, error) { +func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, resourceOwner string, set *GenerateMachineSecret) (*domain.ObjectDetails, error) { agg := user.NewAggregate(userID, resourceOwner) - cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareGenerateMachineSecret(agg, generator, set)) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareGenerateMachineSecret(agg, set)) //nolint:staticcheck if err != nil { return nil, err } @@ -34,7 +33,7 @@ func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, res }, nil } -func prepareGenerateMachineSecret(a *user.Aggregate, generator crypto.Generator, set *GenerateMachineSecret) preparation.Validation { +func (c *Commands) prepareGenerateMachineSecret(a *user.Aggregate, set *GenerateMachineSecret) preparation.Validation { return func() (_ preparation.CreateCommands, err error) { if a.ResourceOwner == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-x0992n", "Errors.ResourceOwnerMissing") @@ -50,15 +49,14 @@ func prepareGenerateMachineSecret(a *user.Aggregate, generator crypto.Generator, if !isUserStateExists(writeModel.UserState) { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-x8910n", "Errors.User.NotExisting") } - - clientSecret, secretString, err := domain.NewMachineClientSecret(generator) + encodedHash, plain, err := c.newHashedSecret(ctx, filter) if err != nil { return nil, err } - set.ClientSecret = secretString + set.ClientSecret = plain return []eventstore.Command{ - user.NewMachineSecretSetEvent(ctx, &a.Aggregate, clientSecret), + user.NewMachineSecretSetEvent(ctx, &a.Aggregate, encodedHash), }, nil }, nil } @@ -99,7 +97,7 @@ func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation { if !isUserStateExists(writeModel.UserState) { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-x7s802", "Errors.User.NotExisting") } - if writeModel.ClientSecret == nil { + if writeModel.HashedSecret == "" { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-coi82n", "Errors.User.Machine.Secret.NotExisting") } return []eventstore.Command{ @@ -109,9 +107,16 @@ func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation { } } -func (c *Commands) MachineSecretCheckSucceeded(ctx context.Context, userID, resourceOwner string) { +func (c *Commands) MachineSecretCheckSucceeded(ctx context.Context, userID, resourceOwner, updated string) { agg := user.NewAggregate(userID, resourceOwner) - c.asyncPush(ctx, user.NewMachineSecretCheckSucceededEvent(ctx, &agg.Aggregate)) + cmds := append( + make([]eventstore.Command, 0, 2), + user.NewMachineSecretCheckSucceededEvent(ctx, &agg.Aggregate), + ) + if updated != "" { + cmds = append(cmds, user.NewMachineSecretHashUpdatedEvent(ctx, &agg.Aggregate, updated)) + } + c.asyncPush(ctx, cmds...) } func (c *Commands) MachineSecretCheckFailed(ctx context.Context, userID, resourceOwner string) { diff --git a/internal/command/user_machine_secret_test.go b/internal/command/user_machine_secret_test.go index ee3d869391..ee58b42d82 100644 --- a/internal/command/user_machine_secret_test.go +++ b/internal/command/user_machine_secret_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" @@ -17,13 +16,12 @@ import ( func TestCommandSide_GenerateMachineSecret(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(*testing.T) *eventstore.Eventstore } type args struct { ctx context.Context userID string resourceOwner string - generator crypto.Generator set *GenerateMachineSecret } type res struct { @@ -40,15 +38,12 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { { name: "user invalid, invalid argument error userID", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), userID: "", resourceOwner: "org1", - generator: GetMockSecretGenerator(t), set: nil, }, res: res{ @@ -58,15 +53,12 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { { name: "user invalid, invalid argument error resourceowner", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), userID: "user1", resourceOwner: "", - generator: GetMockSecretGenerator(t), set: nil, }, res: res{ @@ -76,8 +68,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { { name: "user not existing, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -85,7 +76,6 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { ctx: context.Background(), userID: "user1", resourceOwner: "org1", - generator: GetMockSecretGenerator(t), set: nil, }, res: res{ @@ -95,8 +85,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { { name: "add machine secret, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( user.NewMachineAddedEvent(context.Background(), @@ -112,12 +101,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { expectPush( user.NewMachineSecretSetEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", ), ), ), @@ -126,7 +110,6 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { ctx: context.Background(), userID: "user1", resourceOwner: "org1", - generator: GetMockSecretGenerator(t), set: &GenerateMachineSecret{}, }, res: res{ @@ -134,7 +117,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { ResourceOwner: "org1", }, secret: &GenerateMachineSecret{ - ClientSecret: "a", + ClientSecret: "secret", }, }, }, @@ -142,9 +125,13 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), + newHashedSecret: mockHashedSecret("secret"), + defaultSecretGenerators: &SecretGenerators{ + ClientSecret: emptyConfig, + }, } - got, err := r.GenerateMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.generator, tt.args.set) + got, err := r.GenerateMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.set) if tt.res.err == nil { assert.NoError(t, err) } @@ -274,12 +261,7 @@ func TestCommandSide_RemoveMachineSecret(t *testing.T) { eventFromEventPusher( user.NewMachineSecretSetEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, - &crypto.CryptoValue{ - CryptoType: crypto.TypeEncryption, - Algorithm: "enc", - KeyID: "id", - Crypted: []byte("a"), - }, + "secret", ), ), ), @@ -333,7 +315,7 @@ func TestCommands_MachineSecretCheckSucceeded(t *testing.T) { expectPushSlow(time.Second/100, cmd), ), } - c.MachineSecretCheckSucceeded(ctx, "userID", "orgID") + c.MachineSecretCheckSucceeded(ctx, "userID", "orgID", "") require.NoError(t, c.Close(ctx)) } diff --git a/internal/command/user_v2_email.go b/internal/command/user_v2_email.go index 6dec15e2b2..9053b577dd 100644 --- a/internal/command/user_v2_email.go +++ b/internal/command/user_v2_email.go @@ -76,7 +76,7 @@ func (c *Commands) ChangeUserEmailVerified(ctx context.Context, userID, email st } func (c *Commands) changeUserEmailWithCode(ctx context.Context, userID, email string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl string) (*domain.Email, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck if err != nil { return nil, err } @@ -85,7 +85,7 @@ func (c *Commands) changeUserEmailWithCode(ctx context.Context, userID, email st } func (c *Commands) resendUserEmailCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl string) (*domain.Email, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck if err != nil { return nil, err } @@ -152,7 +152,7 @@ func (c *Commands) resendUserEmailCodeWithGeneratorEvents(ctx context.Context, u } func (c *Commands) VerifyUserEmail(ctx context.Context, userID, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck if err != nil { return nil, err } @@ -262,7 +262,7 @@ func (c *UserEmailEvents) VerifyCode(ctx context.Context, code string, gen crypt return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty") } - err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen) + err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen.Alg()) if err == nil { c.events = append(c.events, user.NewHumanEmailVerifiedEvent(ctx, c.aggregate)) return nil diff --git a/internal/command/user_v2_human.go b/internal/command/user_v2_human.go index 524f90a83b..ab05492b51 100644 --- a/internal/command/user_v2_human.go +++ b/internal/command/user_v2_human.go @@ -51,7 +51,7 @@ type Password struct { ChangeRequired bool } -func (h *ChangeHuman) Validate(hasher *crypto.PasswordHasher) (err error) { +func (h *ChangeHuman) Validate(hasher *crypto.Hasher) (err error) { if h.Email != nil && h.Email.Address != "" { if err := h.Email.Validate(); err != nil { return err @@ -72,7 +72,7 @@ func (h *ChangeHuman) Validate(hasher *crypto.PasswordHasher) (err error) { return nil } -func (p *Password) Validate(hasher *crypto.PasswordHasher) error { +func (p *Password) Validate(hasher *crypto.Hasher) error { if p.EncodedPasswordHash != nil { if !hasher.EncodingSupported(*p.EncodedPasswordHash) { return zerrors.ThrowInvalidArgument(nil, "USER-oz74onzvqr", "Errors.User.Password.NotSupported") @@ -373,7 +373,7 @@ func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Com // Either have a code to set the password if password.PasswordCode != nil { - if err := crypto.VerifyCodeWithAlgorithm(wm.PasswordCodeCreationDate, wm.PasswordCodeExpiry, wm.PasswordCode, *password.PasswordCode, alg); err != nil { + if err := crypto.VerifyCode(wm.PasswordCodeCreationDate, wm.PasswordCodeExpiry, wm.PasswordCode, *password.PasswordCode, alg); err != nil { return cmds, err } } diff --git a/internal/command/user_v2_human_test.go b/internal/command/user_v2_human_test.go index e0f99034bb..32a838b27e 100644 --- a/internal/command/user_v2_human_test.go +++ b/internal/command/user_v2_human_test.go @@ -25,8 +25,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) { type fields struct { eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator - userPasswordHasher *crypto.PasswordHasher - newCode cryptoCodeFunc + userPasswordHasher *crypto.Hasher + newCode encrypedCodeFunc checkPermission domain.PermissionCheck } type args struct { @@ -247,7 +247,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -283,7 +283,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckNotAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -349,7 +349,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -420,7 +420,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -492,7 +492,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -565,7 +565,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -974,7 +974,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("phonecode", time.Hour), + newCode: mockEncryptedCode("phonecode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1040,7 +1040,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -1116,7 +1116,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), userPasswordHasher: mockPasswordHasher("x"), - newCode: mockCode("phoneCode", time.Hour), + newCode: mockEncryptedCode("phoneCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1185,7 +1185,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { ), checkPermission: newMockPermissionCheckAllowed(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), - newCode: mockCode("userinit", time.Hour), + newCode: mockEncryptedCode("userinit", time.Hour), }, args: args{ ctx: context.Background(), @@ -1223,7 +1223,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) { eventstore: tt.fields.eventstore(t), userPasswordHasher: tt.fields.userPasswordHasher, idGenerator: tt.fields.idGenerator, - newCode: tt.fields.newCode, + newEncryptedCode: tt.fields.newCode, checkPermission: tt.fields.checkPermission, } err := r.AddUserHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail, tt.args.codeAlg) @@ -1247,8 +1247,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) { func TestCommandSide_ChangeUserHuman(t *testing.T) { type fields struct { eventstore func(t *testing.T) *eventstore.Eventstore - userPasswordHasher *crypto.PasswordHasher - newCode cryptoCodeFunc + userPasswordHasher *crypto.Hasher + newCode encrypedCodeFunc checkPermission domain.PermissionCheck } type args struct { @@ -1562,7 +1562,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1741,7 +1741,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("emailCode", time.Hour), + newCode: mockEncryptedCode("emailCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1791,7 +1791,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("phoneCode", time.Hour), + newCode: mockEncryptedCode("phoneCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -1939,7 +1939,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("phoneCode", time.Hour), + newCode: mockEncryptedCode("phoneCode", time.Hour), }, args: args{ ctx: context.Background(), @@ -2546,7 +2546,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore(t), userPasswordHasher: tt.fields.userPasswordHasher, - newCode: tt.fields.newCode, + newEncryptedCode: tt.fields.newCode, checkPermission: tt.fields.checkPermission, } err := r.ChangeUserHuman(tt.args.ctx, tt.args.human, tt.args.codeAlg) diff --git a/internal/command/user_v2_model.go b/internal/command/user_v2_model.go index 381a463884..d31c3e6676 100644 --- a/internal/command/user_v2_model.go +++ b/internal/command/user_v2_model.go @@ -266,7 +266,7 @@ func (wm *UserV2WriteModel) Reduce() error { case *user.HumanPasswordCheckSucceededEvent: wm.PasswordCheckFailedCount = 0 case *user.HumanPasswordChangedEvent: - wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.PasswordChangeRequired = e.ChangeRequired wm.EmptyPasswordCode() case *user.HumanPasswordCodeAddedEvent: @@ -470,7 +470,7 @@ func (wm *UserV2WriteModel) reduceHumanAddedEvent(e *user.HumanAddedEvent) { wm.Email = e.EmailAddress wm.Phone = e.PhoneNumber wm.UserState = domain.UserStateActive - wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.PasswordChangeRequired = e.ChangeRequired } @@ -485,7 +485,7 @@ func (wm *UserV2WriteModel) reduceHumanRegisteredEvent(e *user.HumanRegisteredEv wm.Email = e.EmailAddress wm.Phone = e.PhoneNumber wm.UserState = domain.UserStateActive - wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.PasswordChangeRequired = e.ChangeRequired } diff --git a/internal/command/user_v2_passkey.go b/internal/command/user_v2_passkey.go index 698b805d76..4d42d105dd 100644 --- a/internal/command/user_v2_passkey.go +++ b/internal/command/user_v2_passkey.go @@ -48,7 +48,7 @@ func (c *Commands) verifyUserPasskeyCode(ctx context.Context, userID, resourceOw if err != nil { return nil, err } - err = verifyCryptoCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg, wm.ChangeDate, wm.Expiration, wm.CryptoCode, code) + err = verifyEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg, wm.ChangeDate, wm.Expiration, wm.CryptoCode, code) //nolint:staticcheck if err != nil || wm.State != domain.PasswordlessInitCodeStateActive { c.verifyUserPasskeyCodeFailed(ctx, wm) return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Eeb2a", "Errors.User.Code.Invalid") @@ -156,6 +156,6 @@ func (c *Commands) addUserPasskeyCode(ctx context.Context, userID, resourceOwner }, nil } -func (c *Commands) newPasskeyCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { - return c.newCode(ctx, filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) +func (c *Commands) newPasskeyCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) { + return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) } diff --git a/internal/command/user_v2_passkey_test.go b/internal/command/user_v2_passkey_test.go index 1e972bbfd6..15286bea11 100644 --- a/internal/command/user_v2_passkey_test.go +++ b/internal/command/user_v2_passkey_test.go @@ -138,7 +138,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) { es := eventstoreExpect(t, expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))), ) - code, err := newCryptoCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) + code, err := newEncryptedCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) //nolint:staticcheck require.NoError(t, err) userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { @@ -236,7 +236,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) { es := eventstoreExpect(t, expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))), ) - code, err := newCryptoCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) + code, err := newEncryptedCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) //nolint:staticcheck require.NoError(t, err) userAgg := &user.NewAggregate("user1", "org1").Aggregate @@ -457,7 +457,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) { alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t)) userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { - newCode cryptoCodeFunc + newCode encrypedCodeFunc eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator } @@ -475,7 +475,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) { { name: "id generator error", fields: fields{ - newCode: mockCode("passkey1", time.Hour), + newCode: mockEncryptedCode("passkey1", time.Hour), eventstore: expectEventstore(), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), }, @@ -488,7 +488,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) { { name: "success", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: expectEventstore( expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -530,9 +530,9 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - newCode: tt.fields.newCode, - eventstore: tt.fields.eventstore(t), - idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, } got, err := c.AddUserPasskeyCode(context.Background(), tt.args.userID, tt.args.resourceOwner, alg) require.ErrorIs(t, err, tt.wantErr) @@ -546,7 +546,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { - newCode cryptoCodeFunc + newCode encrypedCodeFunc eventstore *eventstore.Eventstore idGenerator id.Generator } @@ -565,7 +565,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { { name: "template error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t), }, args: args{ @@ -578,7 +578,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { { name: "id generator error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), }, @@ -592,7 +592,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { { name: "success", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -638,9 +638,9 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - newCode: tt.fields.newCode, - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + eventstore: tt.fields.eventstore, + idGenerator: tt.fields.idGenerator, } got, err := c.AddUserPasskeyCodeURLTemplate(context.Background(), tt.args.userID, tt.args.resourceOwner, alg, tt.args.urlTmpl) require.ErrorIs(t, err, tt.wantErr) @@ -653,7 +653,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) { alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t)) userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { - newCode cryptoCodeFunc + newCode encrypedCodeFunc eventstore *eventstore.Eventstore idGenerator id.Generator } @@ -671,7 +671,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) { { name: "id generator error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), }, @@ -684,7 +684,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) { { name: "success", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -730,9 +730,9 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - newCode: tt.fields.newCode, - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + eventstore: tt.fields.eventstore, + idGenerator: tt.fields.idGenerator, } got, err := c.AddUserPasskeyCodeReturn(context.Background(), tt.args.userID, tt.args.resourceOwner, alg) require.ErrorIs(t, err, tt.wantErr) @@ -745,7 +745,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t)) userAgg := &user.NewAggregate("user1", "org1").Aggregate type fields struct { - newCode cryptoCodeFunc + newCode encrypedCodeFunc eventstore *eventstore.Eventstore idGenerator id.Generator } @@ -763,7 +763,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "id generator error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), }, @@ -776,7 +776,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "crypto error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"), }, @@ -789,7 +789,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "filter query error", fields: fields{ - newCode: newCryptoCode, + newCode: newEncryptedCode, eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))), expectFilterError(io.ErrClosedPipe), @@ -805,7 +805,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "push error", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -844,7 +844,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { { name: "success", fields: fields{ - newCode: mockCode("passkey1", time.Minute), + newCode: mockEncryptedCode("passkey1", time.Minute), eventstore: eventstoreExpect(t, expectFilter(eventFromEventPusher( user.NewHumanAddedEvent(context.Background(), @@ -890,9 +890,9 @@ func TestCommands_addUserPasskeyCode(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - newCode: tt.fields.newCode, - eventstore: tt.fields.eventstore, - idGenerator: tt.fields.idGenerator, + newEncryptedCode: tt.fields.newCode, + eventstore: tt.fields.eventstore, + idGenerator: tt.fields.idGenerator, } got, err := c.addUserPasskeyCode(context.Background(), tt.args.userID, tt.args.resourceOwner, alg, "", false) require.ErrorIs(t, err, tt.wantErr) diff --git a/internal/command/user_v2_password.go b/internal/command/user_v2_password.go index d94bc0286c..3cddf956a1 100644 --- a/internal/command/user_v2_password.go +++ b/internal/command/user_v2_password.go @@ -55,7 +55,7 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu return nil, nil, err } } - code, err := c.newCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) + code, err := c.newEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) //nolint:staticcheck if err != nil { return nil, nil, err } diff --git a/internal/command/user_v2_password_test.go b/internal/command/user_v2_password_test.go index 0b72c49220..b07fbb3de7 100644 --- a/internal/command/user_v2_password_test.go +++ b/internal/command/user_v2_password_test.go @@ -330,7 +330,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { checkPermission domain.PermissionCheck eventstore func(t *testing.T) *eventstore.Eventstore userEncryption crypto.EncryptionAlgorithm - newCode cryptoCodeFunc + newCode encrypedCodeFunc } type args struct { ctx context.Context @@ -452,7 +452,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("code", 10*time.Minute), + newCode: mockEncryptedCode("code", 10*time.Minute), }, args: args{ ctx: context.Background(), @@ -492,7 +492,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("code", 10*time.Minute), + newCode: mockEncryptedCode("code", 10*time.Minute), }, args: args{ ctx: context.Background(), @@ -533,7 +533,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("code", 10*time.Minute), + newCode: mockEncryptedCode("code", 10*time.Minute), }, args: args{ ctx: context.Background(), @@ -575,7 +575,7 @@ func TestCommands_requestPasswordReset(t *testing.T) { ), ), checkPermission: newMockPermissionCheckAllowed(), - newCode: mockCode("code", 10*time.Minute), + newCode: mockEncryptedCode("code", 10*time.Minute), }, args: args{ ctx: context.Background(), @@ -593,10 +593,10 @@ func TestCommands_requestPasswordReset(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - checkPermission: tt.fields.checkPermission, - eventstore: tt.fields.eventstore(t), - userEncryption: tt.fields.userEncryption, - newCode: tt.fields.newCode, + checkPermission: tt.fields.checkPermission, + eventstore: tt.fields.eventstore(t), + userEncryption: tt.fields.userEncryption, + newEncryptedCode: tt.fields.newCode, } got, gotPlainCode, err := c.requestPasswordReset(tt.args.ctx, tt.args.userID, tt.args.returnCode, tt.args.urlTmpl, tt.args.notificationType) require.ErrorIs(t, err, tt.res.err) diff --git a/internal/command/user_v2_phone.go b/internal/command/user_v2_phone.go index d2582b17c8..b53946c22e 100644 --- a/internal/command/user_v2_phone.go +++ b/internal/command/user_v2_phone.go @@ -55,7 +55,7 @@ func (c *Commands) ResendUserPhoneCodeReturnCode(ctx context.Context, userID str } func (c *Commands) changeUserPhoneWithCode(ctx context.Context, userID, phone string, alg crypto.EncryptionAlgorithm, returnCode bool) (*domain.Phone, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck if err != nil { return nil, err } @@ -64,7 +64,7 @@ func (c *Commands) changeUserPhoneWithCode(ctx context.Context, userID, phone st } func (c *Commands) resendUserPhoneCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, returnCode bool) (*domain.Phone, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck if err != nil { return nil, err } @@ -117,7 +117,7 @@ func (c *Commands) resendUserPhoneCodeWithGenerator(ctx context.Context, userID } func (c *Commands) VerifyUserPhone(ctx context.Context, userID, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) { - config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) + config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck if err != nil { return nil, err } @@ -216,7 +216,7 @@ func (c *UserPhoneEvents) VerifyCode(ctx context.Context, code string, gen crypt return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty") } - err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen) + err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen.Alg()) if err == nil { c.events = append(c.events, user.NewHumanPhoneVerifiedEvent(ctx, c.aggregate)) return nil diff --git a/internal/config/systemdefaults/system_defaults.go b/internal/config/systemdefaults/system_defaults.go index 012c37ac07..1d89115c8e 100644 --- a/internal/config/systemdefaults/system_defaults.go +++ b/internal/config/systemdefaults/system_defaults.go @@ -8,7 +8,8 @@ import ( type SystemDefaults struct { SecretGenerators SecretGenerators - PasswordHasher crypto.PasswordHashConfig + PasswordHasher crypto.HashConfig + SecretHasher crypto.HashConfig Multifactors MultifactorConfig DomainVerification DomainVerification Notifications Notifications @@ -16,7 +17,6 @@ type SystemDefaults struct { } type SecretGenerators struct { - PasswordSaltCost int MachineKeySize uint32 ApplicationKeySize uint32 } diff --git a/internal/crypto/bcrypt.go b/internal/crypto/bcrypt.go deleted file mode 100644 index d9b172478f..0000000000 --- a/internal/crypto/bcrypt.go +++ /dev/null @@ -1,27 +0,0 @@ -package crypto - -import ( - "golang.org/x/crypto/bcrypt" -) - -var _ HashAlgorithm = (*BCrypt)(nil) - -type BCrypt struct { - cost int -} - -func NewBCrypt(cost int) *BCrypt { - return &BCrypt{cost: cost} -} - -func (b *BCrypt) Algorithm() string { - return "bcrypt" -} - -func (b *BCrypt) Hash(value []byte) ([]byte, error) { - return bcrypt.GenerateFromPassword(value, b.cost) -} - -func (b *BCrypt) CompareHash(hashed, value []byte) error { - return bcrypt.CompareHashAndPassword(hashed, value) -} diff --git a/internal/crypto/code.go b/internal/crypto/code.go index 2c67c39cd6..d017a974f2 100644 --- a/internal/crypto/code.go +++ b/internal/crypto/code.go @@ -26,7 +26,7 @@ type GeneratorConfig struct { type Generator interface { Length() uint Expiry() time.Duration - Alg() Crypto + Alg() EncryptionAlgorithm Runes() []rune } @@ -53,7 +53,7 @@ type encryptionGenerator struct { alg EncryptionAlgorithm } -func (g *encryptionGenerator) Alg() Crypto { +func (g *encryptionGenerator) Alg() EncryptionAlgorithm { return g.alg } @@ -64,22 +64,30 @@ func NewEncryptionGenerator(config GeneratorConfig, algorithm EncryptionAlgorith } } -type hashGenerator struct { +type HashGenerator struct { generator - alg HashAlgorithm + hasher *Hasher } -func (g *hashGenerator) Alg() Crypto { - return g.alg -} - -func NewHashGenerator(config GeneratorConfig, algorithm HashAlgorithm) Generator { - return &hashGenerator{ +func NewHashGenerator(config GeneratorConfig, hasher *Hasher) *HashGenerator { + return &HashGenerator{ newGenerator(config), - algorithm, + hasher, } } +func (g *HashGenerator) NewCode() (encoded, plain string, err error) { + plain, err = GenerateRandomString(g.Length(), g.Runes()) + if err != nil { + return "", "", err + } + encoded, err = g.hasher.Hash(plain) + if err != nil { + return "", "", err + } + return encoded, plain, nil +} + func newGenerator(config GeneratorConfig) generator { var runes []rune if config.IncludeLowerLetters { @@ -120,21 +128,11 @@ func IsCodeExpired(creationDate time.Time, expiry time.Duration) bool { return creationDate.Add(expiry).Before(time.Now().UTC()) } -func VerifyCode(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, g Generator) error { - return VerifyCodeWithAlgorithm(creationDate, expiry, cryptoCode, verificationCode, g.Alg()) -} - -func VerifyCodeWithAlgorithm(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, algorithm Crypto) error { +func VerifyCode(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, algorithm EncryptionAlgorithm) error { if IsCodeExpired(creationDate, expiry) { return zerrors.ThrowPreconditionFailed(nil, "CODE-QvUQ4P", "Errors.User.Code.Expired") } - switch alg := algorithm.(type) { - case EncryptionAlgorithm: - return verifyEncryptedCode(cryptoCode, verificationCode, alg) - case HashAlgorithm: - return verifyHashedCode(cryptoCode, verificationCode, alg) - } - return zerrors.ThrowInvalidArgument(nil, "CODE-fW2gNa", "Errors.User.Code.GeneratorAlgNotSupported") + return verifyEncryptedCode(cryptoCode, verificationCode, algorithm) } func GenerateRandomString(length uint, chars []rune) (string, error) { @@ -173,10 +171,3 @@ func verifyEncryptedCode(cryptoCode *CryptoValue, verificationCode string, alg E } return nil } - -func verifyHashedCode(cryptoCode *CryptoValue, verificationCode string, alg HashAlgorithm) error { - if cryptoCode == nil { - return zerrors.ThrowInvalidArgument(nil, "CRYPT-2q3r", "cryptoCode must not be nil") - } - return CompareHash(cryptoCode, []byte(verificationCode), alg) -} diff --git a/internal/crypto/code_mock.go b/internal/crypto/code_mock.go index 2a353fb4f9..3ed342c7d4 100644 --- a/internal/crypto/code_mock.go +++ b/internal/crypto/code_mock.go @@ -5,6 +5,7 @@ // // mockgen -source code.go -destination ./code_mock.go -package crypto // + // Package crypto is a generated GoMock package. package crypto @@ -39,10 +40,10 @@ func (m *MockGenerator) EXPECT() *MockGeneratorMockRecorder { } // Alg mocks base method. -func (m *MockGenerator) Alg() Crypto { +func (m *MockGenerator) Alg() EncryptionAlgorithm { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Alg") - ret0, _ := ret[0].(Crypto) + ret0, _ := ret[0].(EncryptionAlgorithm) return ret0 } diff --git a/internal/crypto/code_mocker.go b/internal/crypto/code_mocker.go index 66071ccab4..59cece2af2 100644 --- a/internal/crypto/code_mocker.go +++ b/internal/crypto/code_mocker.go @@ -60,32 +60,13 @@ func createMockEncryptionAlgorithm(ctrl *gomock.Controller, encryptFunction func return mCrypto } -func CreateMockHashAlg(ctrl *gomock.Controller) HashAlgorithm { - mCrypto := NewMockHashAlgorithm(ctrl) - mCrypto.EXPECT().Algorithm().AnyTimes().Return("hash") - mCrypto.EXPECT().Hash(gomock.Any()).AnyTimes().DoAndReturn( - func(code []byte) ([]byte, error) { - return code, nil - }, - ) - mCrypto.EXPECT().CompareHash(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( - func(hashed, comparer []byte) error { - if string(hashed) != string(comparer) { - return zerrors.ThrowInternal(nil, "id", "invalid") - } - return nil - }, - ) - return mCrypto -} - -func createMockCrypto(t *testing.T) Crypto { - mCrypto := NewMockCrypto(gomock.NewController(t)) +func createMockCrypto(t *testing.T) EncryptionAlgorithm { + mCrypto := NewMockEncryptionAlgorithm(gomock.NewController(t)) mCrypto.EXPECT().Algorithm().AnyTimes().Return("crypto") return mCrypto } -func createMockGenerator(t *testing.T, crypto Crypto) Generator { +func createMockGenerator(t *testing.T, crypto EncryptionAlgorithm) Generator { mGenerator := NewMockGenerator(gomock.NewController(t)) mGenerator.EXPECT().Alg().AnyTimes().Return(crypto) return mGenerator diff --git a/internal/crypto/code_test.go b/internal/crypto/code_test.go index 2b4e3d36c9..e766871a30 100644 --- a/internal/crypto/code_test.go +++ b/internal/crypto/code_test.go @@ -102,25 +102,10 @@ func TestVerifyCode(t *testing.T) { }, false, }, - { - "hash alg ok", - args{ - creationDate: time.Now(), - expiry: 5 * time.Minute, - cryptoCode: &CryptoValue{ - CryptoType: TypeHash, - Algorithm: "hash", - Crypted: []byte("code"), - }, - verificationCode: "code", - g: createMockGenerator(t, CreateMockHashAlg(gomock.NewController(t))), - }, - false, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := VerifyCode(tt.args.creationDate, tt.args.expiry, tt.args.cryptoCode, tt.args.verificationCode, tt.args.g); (err != nil) != tt.wantErr { + if err := VerifyCode(tt.args.creationDate, tt.args.expiry, tt.args.cryptoCode, tt.args.verificationCode, tt.args.g.Alg()); (err != nil) != tt.wantErr { t.Errorf("VerifyCode() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -222,85 +207,3 @@ func Test_verifyEncryptedCode(t *testing.T) { }) } } - -func Test_verifyHashedCode(t *testing.T) { - type args struct { - cryptoCode *CryptoValue - verificationCode string - alg HashAlgorithm - } - tests := []struct { - name string - args args - wantErr bool - }{ - - { - "nil error", - args{ - cryptoCode: nil, - verificationCode: "", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - true, - }, - { - "wrong cryptotype error", - args{ - cryptoCode: &CryptoValue{ - CryptoType: TypeEncryption, - Crypted: nil, - }, - verificationCode: "", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - true, - }, - { - "wrong algorithm error", - args{ - cryptoCode: &CryptoValue{ - CryptoType: TypeHash, - Algorithm: "hash2", - Crypted: nil, - }, - verificationCode: "", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - true, - }, - { - "wrong verification code error", - args{ - cryptoCode: &CryptoValue{ - CryptoType: TypeHash, - Algorithm: "hash", - Crypted: []byte("code"), - }, - verificationCode: "wrong", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - true, - }, - { - "verification code ok", - args{ - cryptoCode: &CryptoValue{ - CryptoType: TypeHash, - Algorithm: "hash", - Crypted: []byte("code"), - }, - verificationCode: "code", - alg: CreateMockHashAlg(gomock.NewController(t)), - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := verifyHashedCode(tt.args.cryptoCode, tt.args.verificationCode, tt.args.alg); (err != nil) != tt.wantErr { - t.Errorf("verifyHashedCode() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go index 14c249e987..2e8e4a71b0 100644 --- a/internal/crypto/crypto.go +++ b/internal/crypto/crypto.go @@ -13,12 +13,8 @@ const ( TypeHash // Depcrecated: use [passwap.Swapper] instead ) -type Crypto interface { - Algorithm() string -} - type EncryptionAlgorithm interface { - Crypto + Algorithm() string EncryptionKeyID() string DecryptionKeyIDs() []string Encrypt(value []byte) ([]byte, error) @@ -26,13 +22,6 @@ type EncryptionAlgorithm interface { DecryptString(hashed []byte, keyID string) (string, error) } -// Depcrecated: use [passwap.Swapper] instead -type HashAlgorithm interface { - Crypto - Hash(value []byte) ([]byte, error) - CompareHash(hashed, comparer []byte) error -} - type CryptoValue struct { CryptoType CryptoType Algorithm string @@ -59,14 +48,8 @@ func (c *CryptoValue) Scan(src interface{}) error { type CryptoType int -func Crypt(value []byte, c Crypto) (*CryptoValue, error) { - switch alg := c.(type) { - case EncryptionAlgorithm: - return Encrypt(value, alg) - case HashAlgorithm: - return Hash(value, alg) - } - return nil, zerrors.ThrowInternal(nil, "CRYPT-r4IaHZ", "algorithm not supported") +func Crypt(value []byte, alg EncryptionAlgorithm) (*CryptoValue, error) { + return Encrypt(value, alg) } func Encrypt(value []byte, alg EncryptionAlgorithm) (*CryptoValue, error) { @@ -108,33 +91,6 @@ func checkEncryptionAlgorithm(value *CryptoValue, alg EncryptionAlgorithm) error return zerrors.ThrowInvalidArgument(nil, "CRYPT-Kq12vn", "value was encrypted with a different key") } -func Hash(value []byte, alg HashAlgorithm) (*CryptoValue, error) { - hashed, err := alg.Hash(value) - if err != nil { - return nil, zerrors.ThrowInternal(err, "CRYPT-rBVaJU", "error hashing value") - } - return &CryptoValue{ - CryptoType: TypeHash, - Algorithm: alg.Algorithm(), - Crypted: hashed, - }, nil -} - -func CompareHash(value *CryptoValue, comparer []byte, alg HashAlgorithm) error { - if value.Algorithm != alg.Algorithm() { - return zerrors.ThrowInvalidArgument(nil, "CRYPT-HF32f", "value was hashed with a different algorithm") - } - return alg.CompareHash(value.Crypted, comparer) -} - -func FillHash(value []byte, alg HashAlgorithm) *CryptoValue { - return &CryptoValue{ - CryptoType: TypeHash, - Algorithm: alg.Algorithm(), - Crypted: value, - } -} - func CheckToken(alg EncryptionAlgorithm, token string, content string) error { if token == "" { return zerrors.ThrowPermissionDenied(nil, "CRYPTO-Sfefs", "Errors.Intent.InvalidToken") @@ -152,3 +108,12 @@ func CheckToken(alg EncryptionAlgorithm, token string, content string) error { } return nil } + +// SecretOrEncodedHash returns the Crypted value from legacy [CryptoValue] if it is not nil. +// otherwise it will returns the encoded hash string. +func SecretOrEncodedHash(secret *CryptoValue, encoded string) string { + if secret != nil { + return string(secret.Crypted) + } + return encoded +} diff --git a/internal/crypto/crypto_mock.go b/internal/crypto/crypto_mock.go index 60795e4b25..4f2adea475 100644 --- a/internal/crypto/crypto_mock.go +++ b/internal/crypto/crypto_mock.go @@ -5,6 +5,7 @@ // // mockgen -source crypto.go -destination ./crypto_mock.go -package crypto // + // Package crypto is a generated GoMock package. package crypto @@ -14,43 +15,6 @@ import ( gomock "go.uber.org/mock/gomock" ) -// MockCrypto is a mock of Crypto interface. -type MockCrypto struct { - ctrl *gomock.Controller - recorder *MockCryptoMockRecorder -} - -// MockCryptoMockRecorder is the mock recorder for MockCrypto. -type MockCryptoMockRecorder struct { - mock *MockCrypto -} - -// NewMockCrypto creates a new mock instance. -func NewMockCrypto(ctrl *gomock.Controller) *MockCrypto { - mock := &MockCrypto{ctrl: ctrl} - mock.recorder = &MockCryptoMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockCrypto) EXPECT() *MockCryptoMockRecorder { - return m.recorder -} - -// Algorithm mocks base method. -func (m *MockCrypto) Algorithm() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Algorithm") - ret0, _ := ret[0].(string) - return ret0 -} - -// Algorithm indicates an expected call of Algorithm. -func (mr *MockCryptoMockRecorder) Algorithm() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockCrypto)(nil).Algorithm)) -} - // MockEncryptionAlgorithm is a mock of EncryptionAlgorithm interface. type MockEncryptionAlgorithm struct { ctrl *gomock.Controller @@ -160,69 +124,3 @@ func (mr *MockEncryptionAlgorithmMockRecorder) EncryptionKeyID() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncryptionKeyID", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).EncryptionKeyID)) } - -// MockHashAlgorithm is a mock of HashAlgorithm interface. -type MockHashAlgorithm struct { - ctrl *gomock.Controller - recorder *MockHashAlgorithmMockRecorder -} - -// MockHashAlgorithmMockRecorder is the mock recorder for MockHashAlgorithm. -type MockHashAlgorithmMockRecorder struct { - mock *MockHashAlgorithm -} - -// NewMockHashAlgorithm creates a new mock instance. -func NewMockHashAlgorithm(ctrl *gomock.Controller) *MockHashAlgorithm { - mock := &MockHashAlgorithm{ctrl: ctrl} - mock.recorder = &MockHashAlgorithmMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockHashAlgorithm) EXPECT() *MockHashAlgorithmMockRecorder { - return m.recorder -} - -// Algorithm mocks base method. -func (m *MockHashAlgorithm) Algorithm() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Algorithm") - ret0, _ := ret[0].(string) - return ret0 -} - -// Algorithm indicates an expected call of Algorithm. -func (mr *MockHashAlgorithmMockRecorder) Algorithm() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockHashAlgorithm)(nil).Algorithm)) -} - -// CompareHash mocks base method. -func (m *MockHashAlgorithm) CompareHash(hashed, comparer []byte) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CompareHash", hashed, comparer) - ret0, _ := ret[0].(error) - return ret0 -} - -// CompareHash indicates an expected call of CompareHash. -func (mr *MockHashAlgorithmMockRecorder) CompareHash(hashed, comparer any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompareHash", reflect.TypeOf((*MockHashAlgorithm)(nil).CompareHash), hashed, comparer) -} - -// Hash mocks base method. -func (m *MockHashAlgorithm) Hash(value []byte) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Hash", value) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Hash indicates an expected call of Hash. -func (mr *MockHashAlgorithmMockRecorder) Hash(value any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockHashAlgorithm)(nil).Hash), value) -} diff --git a/internal/crypto/crypto_test.go b/internal/crypto/crypto_test.go index fa5c4a5194..c5b4d17dde 100644 --- a/internal/crypto/crypto_test.go +++ b/internal/crypto/crypto_test.go @@ -60,7 +60,7 @@ func (a *alg) Algorithm() string { func TestCrypt(t *testing.T) { type args struct { value []byte - c Crypto + c EncryptionAlgorithm } tests := []struct { name string @@ -74,18 +74,6 @@ func TestCrypt(t *testing.T) { &CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")}, false, }, - { - "hash", - args{[]byte("test"), &mockHashCrypto{}}, - &CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, - false, - }, - { - "wrong type", - args{[]byte("test"), &alg{}}, - nil, - true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -208,66 +196,3 @@ func TestDecryptString(t *testing.T) { }) } } - -func TestHash(t *testing.T) { - type args struct { - value []byte - c HashAlgorithm - } - tests := []struct { - name string - args args - want *CryptoValue - wantErr bool - }{ - { - "ok", - args{[]byte("test"), &mockHashCrypto{}}, - &CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := Hash(tt.args.value, tt.args.c) - if (err != nil) != tt.wantErr { - t.Errorf("Hash() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Hash() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCompareHash(t *testing.T) { - type args struct { - value *CryptoValue - comparer []byte - c HashAlgorithm - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "ok", - args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test"), &mockHashCrypto{}}, - false, - }, - { - "wrong", - args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test2"), &mockHashCrypto{}}, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := CompareHash(tt.args.value, tt.args.comparer, tt.args.c); (err != nil) != tt.wantErr { - t.Errorf("CompareHash() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/internal/crypto/passwap.go b/internal/crypto/passwap.go index 280fbe4d86..986019c5ba 100644 --- a/internal/crypto/passwap.go +++ b/internal/crypto/passwap.go @@ -16,12 +16,12 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) -type PasswordHasher struct { +type Hasher struct { *passwap.Swapper Prefixes []string } -func (h *PasswordHasher) EncodingSupported(encodedHash string) bool { +func (h *Hasher) EncodingSupported(encodedHash string) bool { for _, prefix := range h.Prefixes { if strings.HasPrefix(encodedHash, prefix) { return true @@ -54,12 +54,12 @@ const ( HashModeSHA512 HashMode = "sha512" ) -type PasswordHashConfig struct { +type HashConfig struct { Verifiers []HashName Hasher HasherConfig } -func (c *PasswordHashConfig) PasswordHasher() (*PasswordHasher, error) { +func (c *HashConfig) NewHasher() (*Hasher, error) { verifiers, vPrefixes, err := c.buildVerifiers() if err != nil { return nil, zerrors.ThrowInvalidArgument(err, "CRYPT-sahW9", "password hash config invalid") @@ -68,7 +68,7 @@ func (c *PasswordHashConfig) PasswordHasher() (*PasswordHasher, error) { if err != nil { return nil, zerrors.ThrowInvalidArgument(err, "CRYPT-Que4r", "password hash config invalid") } - return &PasswordHasher{ + return &Hasher{ Swapper: passwap.NewSwapper(hasher, verifiers...), Prefixes: append(hPrefixes, vPrefixes...), }, nil @@ -105,7 +105,7 @@ var knowVerifiers = map[HashName]prefixVerifier{ }, } -func (c *PasswordHashConfig) buildVerifiers() (verifiers []verifier.Verifier, prefixes []string, err error) { +func (c *HashConfig) buildVerifiers() (verifiers []verifier.Verifier, prefixes []string, err error) { verifiers = make([]verifier.Verifier, len(c.Verifiers)) prefixes = make([]string, 0, len(c.Verifiers)+1) for i, name := range c.Verifiers { diff --git a/internal/crypto/passwap_test.go b/internal/crypto/passwap_test.go index 38ca0d5d3d..cbc7202501 100644 --- a/internal/crypto/passwap_test.go +++ b/internal/crypto/passwap_test.go @@ -49,7 +49,7 @@ func TestPasswordHasher_EncodingSupported(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - h := &PasswordHasher{ + h := &Hasher{ Prefixes: []string{bcrypt.Prefix, argon2.Prefix}, } got := h.EncodingSupported(tt.encodedHash) @@ -340,11 +340,11 @@ func TestPasswordHashConfig_PasswordHasher(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := &PasswordHashConfig{ + c := &HashConfig{ Verifiers: tt.fields.Verifiers, Hasher: tt.fields.Hasher, } - got, err := c.PasswordHasher() + got, err := c.NewHasher() if tt.wantErr { assert.Error(t, err) return diff --git a/internal/domain/application_api.go b/internal/domain/application_api.go index 06d5dc7e64..8e4f30a783 100644 --- a/internal/domain/application_api.go +++ b/internal/domain/application_api.go @@ -11,7 +11,7 @@ type APIApp struct { AppID string AppName string ClientID string - ClientSecret *crypto.CryptoValue + EncodedHash string ClientSecretString string AuthMethodType APIAuthMethodType @@ -41,21 +41,21 @@ func (a *APIApp) setClientID(clientID string) { a.ClientID = clientID } -func (a *APIApp) setClientSecret(clientSecret *crypto.CryptoValue) { - a.ClientSecret = clientSecret +func (a *APIApp) setClientSecret(encodedHash string) { + a.EncodedHash = encodedHash } func (a *APIApp) requiresClientSecret() bool { return a.AuthMethodType == APIAuthMethodTypeBasic } -func (a *APIApp) GenerateClientSecretIfNeeded(generator crypto.Generator) (secret string, err error) { +func (a *APIApp) GenerateClientSecretIfNeeded(generator *crypto.HashGenerator) (plain string, err error) { if a.AuthMethodType == APIAuthMethodTypePrivateKeyJWT { return "", nil } - a.ClientSecret, secret, err = NewClientSecret(generator) + a.EncodedHash, plain, err = generator.NewCode() if err != nil { return "", err } - return secret, nil + return plain, nil } diff --git a/internal/domain/application_oauth.go b/internal/domain/application_oauth.go index 816eba4fda..0f2179d534 100644 --- a/internal/domain/application_oauth.go +++ b/internal/domain/application_oauth.go @@ -4,16 +4,12 @@ import ( "fmt" "strings" - "github.com/zitadel/logging" - - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/id" - "github.com/zitadel/zitadel/internal/zerrors" ) type oAuthApplication interface { setClientID(clientID string) - setClientSecret(secret *crypto.CryptoValue) + setClientSecret(encodedHash string) requiresClientSecret() bool } @@ -37,23 +33,14 @@ func NewClientID(idGenerator id.Generator, projectName string) (string, error) { return fmt.Sprintf("%s@%s", rndID, strings.ReplaceAll(strings.ToLower(projectName), " ", "_")), nil } -func SetNewClientSecretIfNeeded(a oAuthApplication, generator crypto.Generator) (string, error) { +func SetNewClientSecretIfNeeded(a oAuthApplication, generate func() (encodedHash, plain string, err error)) (string, error) { if !a.requiresClientSecret() { return "", nil } - clientSecret, secretString, err := NewClientSecret(generator) + encodedHash, plain, err := generate() if err != nil { return "", err } - a.setClientSecret(clientSecret) - return secretString, nil -} - -func NewClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) { - cryptoValue, stringSecret, err := crypto.NewCode(generator) - if err != nil { - logging.Log("MODEL-UpnTI").OnError(err).Error("unable to create client secret") - return nil, "", zerrors.ThrowInternal(err, "MODEL-gH2Wl", "Errors.Project.CouldNotGenerateClientSecret") - } - return cryptoValue, stringSecret, nil + a.setClientSecret(encodedHash) + return plain, nil } diff --git a/internal/domain/application_oidc.go b/internal/domain/application_oidc.go index 0f981197f0..9fe526d684 100644 --- a/internal/domain/application_oidc.go +++ b/internal/domain/application_oidc.go @@ -5,7 +5,6 @@ import ( "time" http_util "github.com/zitadel/zitadel/internal/api/http" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/eventstore/v1/models" ) @@ -28,7 +27,7 @@ type OIDCApp struct { AppID string AppName string ClientID string - ClientSecret *crypto.CryptoValue + EncodedHash string ClientSecretString string RedirectUris []string ResponseTypes []OIDCResponseType @@ -62,8 +61,8 @@ func (a *OIDCApp) setClientID(clientID string) { a.ClientID = clientID } -func (a *OIDCApp) setClientSecret(clientSecret *crypto.CryptoValue) { - a.ClientSecret = clientSecret +func (a *OIDCApp) setClientSecret(encodedHash string) { + a.EncodedHash = encodedHash } func (a *OIDCApp) requiresClientSecret() bool { diff --git a/internal/domain/human.go b/internal/domain/human.go index 6c2ec4daa0..d81aed1f71 100644 --- a/internal/domain/human.go +++ b/internal/domain/human.go @@ -4,6 +4,8 @@ import ( "strings" "time" + "golang.org/x/net/context" + "github.com/zitadel/zitadel/internal/crypto" es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/zerrors" @@ -102,10 +104,10 @@ func (u *Human) EnsureDisplayName() { u.DisplayName = u.Username } -func (u *Human) HashPasswordIfExisting(policy *PasswordComplexityPolicy, hasher *crypto.PasswordHasher, onetime bool) error { +func (u *Human) HashPasswordIfExisting(ctx context.Context, policy *PasswordComplexityPolicy, hasher *crypto.Hasher, onetime bool) error { if u.Password != nil { u.Password.ChangeRequired = onetime - return u.Password.HashPasswordIfExisting(policy, hasher) + return u.Password.HashPasswordIfExisting(ctx, policy, hasher) } return nil } diff --git a/internal/domain/human_password.go b/internal/domain/human_password.go index 779ed4dba7..3afa0826cf 100644 --- a/internal/domain/human_password.go +++ b/internal/domain/human_password.go @@ -1,10 +1,12 @@ package domain import ( + "context" "time" "github.com/zitadel/zitadel/internal/crypto" es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models" + "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -30,7 +32,7 @@ type PasswordCode struct { NotificationType NotificationType } -func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, hasher *crypto.PasswordHasher) error { +func (p *Password) HashPasswordIfExisting(ctx context.Context, policy *PasswordComplexityPolicy, hasher *crypto.Hasher) error { if p.SecretString == "" { return nil } @@ -40,7 +42,9 @@ func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, hash if err := policy.Check(p.SecretString); err != nil { return err } + _, spanHash := tracing.NewNamedSpan(ctx, "passwap.Hash") encoded, err := hasher.Hash(p.SecretString) + spanHash.EndWithError(err) if err != nil { return err } diff --git a/internal/domain/machine_secret.go b/internal/domain/machine_secret.go deleted file mode 100644 index ba9bfe7b99..0000000000 --- a/internal/domain/machine_secret.go +++ /dev/null @@ -1,14 +0,0 @@ -package domain - -import ( - "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/zitadel/internal/zerrors" -) - -func NewMachineClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) { - cryptoValue, stringSecret, err := crypto.NewCode(generator) - if err != nil { - return nil, "", zerrors.ThrowInternal(err, "MODEL-57cjsiw", "Errors.User.Machine.Secret.CouldNotGenerate") - } - return cryptoValue, stringSecret, nil -} diff --git a/internal/query/app_test.go b/internal/query/app_test.go index 9f56bab962..3ccec2e4c8 100644 --- a/internal/query/app_test.go +++ b/internal/query/app_test.go @@ -15,98 +15,98 @@ import ( ) var ( - expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps6.id,` + - ` projections.apps6.name,` + - ` projections.apps6.project_id,` + - ` projections.apps6.creation_date,` + - ` projections.apps6.change_date,` + - ` projections.apps6.resource_owner,` + - ` projections.apps6.state,` + - ` projections.apps6.sequence,` + + expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` + + ` projections.apps7.name,` + + ` projections.apps7.project_id,` + + ` projections.apps7.creation_date,` + + ` projections.apps7.change_date,` + + ` projections.apps7.resource_owner,` + + ` projections.apps7.state,` + + ` projections.apps7.sequence,` + // api config - ` projections.apps6_api_configs.app_id,` + - ` projections.apps6_api_configs.client_id,` + - ` projections.apps6_api_configs.auth_method,` + + ` projections.apps7_api_configs.app_id,` + + ` projections.apps7_api_configs.client_id,` + + ` projections.apps7_api_configs.auth_method,` + // oidc config - ` projections.apps6_oidc_configs.app_id,` + - ` projections.apps6_oidc_configs.version,` + - ` projections.apps6_oidc_configs.client_id,` + - ` projections.apps6_oidc_configs.redirect_uris,` + - ` projections.apps6_oidc_configs.response_types,` + - ` projections.apps6_oidc_configs.grant_types,` + - ` projections.apps6_oidc_configs.application_type,` + - ` projections.apps6_oidc_configs.auth_method_type,` + - ` projections.apps6_oidc_configs.post_logout_redirect_uris,` + - ` projections.apps6_oidc_configs.is_dev_mode,` + - ` projections.apps6_oidc_configs.access_token_type,` + - ` projections.apps6_oidc_configs.access_token_role_assertion,` + - ` projections.apps6_oidc_configs.id_token_role_assertion,` + - ` projections.apps6_oidc_configs.id_token_userinfo_assertion,` + - ` projections.apps6_oidc_configs.clock_skew,` + - ` projections.apps6_oidc_configs.additional_origins,` + - ` projections.apps6_oidc_configs.skip_native_app_success_page,` + + ` projections.apps7_oidc_configs.app_id,` + + ` projections.apps7_oidc_configs.version,` + + ` projections.apps7_oidc_configs.client_id,` + + ` projections.apps7_oidc_configs.redirect_uris,` + + ` projections.apps7_oidc_configs.response_types,` + + ` projections.apps7_oidc_configs.grant_types,` + + ` projections.apps7_oidc_configs.application_type,` + + ` projections.apps7_oidc_configs.auth_method_type,` + + ` projections.apps7_oidc_configs.post_logout_redirect_uris,` + + ` projections.apps7_oidc_configs.is_dev_mode,` + + ` projections.apps7_oidc_configs.access_token_type,` + + ` projections.apps7_oidc_configs.access_token_role_assertion,` + + ` projections.apps7_oidc_configs.id_token_role_assertion,` + + ` projections.apps7_oidc_configs.id_token_userinfo_assertion,` + + ` projections.apps7_oidc_configs.clock_skew,` + + ` projections.apps7_oidc_configs.additional_origins,` + + ` projections.apps7_oidc_configs.skip_native_app_success_page,` + //saml config - ` projections.apps6_saml_configs.app_id,` + - ` projections.apps6_saml_configs.entity_id,` + - ` projections.apps6_saml_configs.metadata,` + - ` projections.apps6_saml_configs.metadata_url` + - ` FROM projections.apps6` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + + ` projections.apps7_saml_configs.app_id,` + + ` projections.apps7_saml_configs.entity_id,` + + ` projections.apps7_saml_configs.metadata,` + + ` projections.apps7_saml_configs.metadata_url` + + ` FROM projections.apps7` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps6.id,` + - ` projections.apps6.name,` + - ` projections.apps6.project_id,` + - ` projections.apps6.creation_date,` + - ` projections.apps6.change_date,` + - ` projections.apps6.resource_owner,` + - ` projections.apps6.state,` + - ` projections.apps6.sequence,` + + expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` + + ` projections.apps7.name,` + + ` projections.apps7.project_id,` + + ` projections.apps7.creation_date,` + + ` projections.apps7.change_date,` + + ` projections.apps7.resource_owner,` + + ` projections.apps7.state,` + + ` projections.apps7.sequence,` + // api config - ` projections.apps6_api_configs.app_id,` + - ` projections.apps6_api_configs.client_id,` + - ` projections.apps6_api_configs.auth_method,` + + ` projections.apps7_api_configs.app_id,` + + ` projections.apps7_api_configs.client_id,` + + ` projections.apps7_api_configs.auth_method,` + // oidc config - ` projections.apps6_oidc_configs.app_id,` + - ` projections.apps6_oidc_configs.version,` + - ` projections.apps6_oidc_configs.client_id,` + - ` projections.apps6_oidc_configs.redirect_uris,` + - ` projections.apps6_oidc_configs.response_types,` + - ` projections.apps6_oidc_configs.grant_types,` + - ` projections.apps6_oidc_configs.application_type,` + - ` projections.apps6_oidc_configs.auth_method_type,` + - ` projections.apps6_oidc_configs.post_logout_redirect_uris,` + - ` projections.apps6_oidc_configs.is_dev_mode,` + - ` projections.apps6_oidc_configs.access_token_type,` + - ` projections.apps6_oidc_configs.access_token_role_assertion,` + - ` projections.apps6_oidc_configs.id_token_role_assertion,` + - ` projections.apps6_oidc_configs.id_token_userinfo_assertion,` + - ` projections.apps6_oidc_configs.clock_skew,` + - ` projections.apps6_oidc_configs.additional_origins,` + - ` projections.apps6_oidc_configs.skip_native_app_success_page,` + + ` projections.apps7_oidc_configs.app_id,` + + ` projections.apps7_oidc_configs.version,` + + ` projections.apps7_oidc_configs.client_id,` + + ` projections.apps7_oidc_configs.redirect_uris,` + + ` projections.apps7_oidc_configs.response_types,` + + ` projections.apps7_oidc_configs.grant_types,` + + ` projections.apps7_oidc_configs.application_type,` + + ` projections.apps7_oidc_configs.auth_method_type,` + + ` projections.apps7_oidc_configs.post_logout_redirect_uris,` + + ` projections.apps7_oidc_configs.is_dev_mode,` + + ` projections.apps7_oidc_configs.access_token_type,` + + ` projections.apps7_oidc_configs.access_token_role_assertion,` + + ` projections.apps7_oidc_configs.id_token_role_assertion,` + + ` projections.apps7_oidc_configs.id_token_userinfo_assertion,` + + ` projections.apps7_oidc_configs.clock_skew,` + + ` projections.apps7_oidc_configs.additional_origins,` + + ` projections.apps7_oidc_configs.skip_native_app_success_page,` + //saml config - ` projections.apps6_saml_configs.app_id,` + - ` projections.apps6_saml_configs.entity_id,` + - ` projections.apps6_saml_configs.metadata,` + - ` projections.apps6_saml_configs.metadata_url,` + + ` projections.apps7_saml_configs.app_id,` + + ` projections.apps7_saml_configs.entity_id,` + + ` projections.apps7_saml_configs.metadata,` + + ` projections.apps7_saml_configs.metadata_url,` + ` COUNT(*) OVER ()` + - ` FROM projections.apps6` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + + ` FROM projections.apps7` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps6_api_configs.client_id,` + - ` projections.apps6_oidc_configs.client_id` + - ` FROM projections.apps6` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + + expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps7_api_configs.client_id,` + + ` projections.apps7_oidc_configs.client_id` + + ` FROM projections.apps7` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps6.project_id` + - ` FROM projections.apps6` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + + expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps7.project_id` + + ` FROM projections.apps7` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) expectedProjectByAppQuery = regexp.QuoteMeta(`SELECT projections.projects4.id,` + ` projections.projects4.creation_date,` + @@ -120,10 +120,10 @@ var ( ` projections.projects4.has_project_check,` + ` projections.projects4.private_labeling_setting` + ` FROM projections.projects4` + - ` JOIN projections.apps6 ON projections.projects4.id = projections.apps6.project_id AND projections.projects4.instance_id = projections.apps6.instance_id` + - ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + - ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + + ` JOIN projections.apps7 ON projections.projects4.id = projections.apps7.project_id AND projections.projects4.instance_id = projections.apps7.instance_id` + + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` + + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) appCols = database.TextArray[string]{ diff --git a/internal/query/embed/introspection_client_by_id.sql b/internal/query/embed/introspection_client_by_id.sql index b8c85806b0..9bceff9118 100644 --- a/internal/query/embed/introspection_client_by_id.sql +++ b/internal/query/embed/introspection_client_by_id.sql @@ -1,11 +1,11 @@ with config as ( - select app_id, client_id, client_secret - from projections.apps6_api_configs + select app_id, client_id, client_secret, 'api' as app_type + from projections.apps7_api_configs where instance_id = $1 and client_id = $2 union - select app_id, client_id, client_secret - from projections.apps6_oidc_configs + select app_id, client_id, client_secret, 'oidc' as app_type + from projections.apps7_oidc_configs where instance_id = $1 and client_id = $2 ), @@ -18,6 +18,7 @@ keys as ( and expiration > current_timestamp group by identifier ) -select config.client_id, config.client_secret, apps.project_id, keys.public_keys from config -join projections.apps6 apps on apps.id = config.app_id +select config.app_id, config.client_id, config.client_secret, config.app_type, apps.project_id, apps.resource_owner, keys.public_keys +from config +join projections.apps7 apps on apps.id = config.app_id left join keys on keys.client_id = config.client_id; diff --git a/internal/query/embed/oidc_client_by_id.sql b/internal/query/embed/oidc_client_by_id.sql index 8759b513d9..07f45cf68f 100644 --- a/internal/query/embed/oidc_client_by_id.sql +++ b/internal/query/embed/oidc_client_by_id.sql @@ -7,8 +7,8 @@ with client as ( c.application_type, c.auth_method_type, c.post_logout_redirect_uris, c.is_dev_mode, c.access_token_type, c.access_token_role_assertion, c.id_token_role_assertion, c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, a.state - from projections.apps6_oidc_configs c - join projections.apps6 a on a.id = c.app_id and a.instance_id = c.instance_id + from projections.apps7_oidc_configs c + join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id where c.instance_id = $1 and c.client_id = $2 ), diff --git a/internal/query/embed/userinfo_by_id.sql b/internal/query/embed/userinfo_by_id.sql index 58e0ae10dd..95e6ad90c7 100644 --- a/internal/query/embed/userinfo_by_id.sql +++ b/internal/query/embed/userinfo_by_id.sql @@ -1,6 +1,6 @@ with usr as ( select u.id, u.creation_date, u.change_date, u.sequence, u.state, u.resource_owner, u.username, n.login_name as preferred_login_name - from projections.users11 u + from projections.users12 u left join projections.login_names3 n on u.id = n.user_id and u.instance_id = n.instance_id where u.id = $1 and u.instance_id = $2 @@ -9,7 +9,7 @@ with usr as ( human as ( select $1 as user_id, row_to_json(r) as human from ( select first_name, last_name, nick_name, display_name, avatar_key, preferred_language, gender, email, is_email_verified, phone, is_phone_verified - from projections.users11_humans + from projections.users12_humans where user_id = $1 and instance_id = $2 ) r @@ -17,7 +17,7 @@ human as ( machine as ( select $1 as user_id, row_to_json(r) as machine from ( select name, description - from projections.users11_machines + from projections.users12_machines where user_id = $1 and instance_id = $2 ) r diff --git a/internal/query/iam_member_test.go b/internal/query/iam_member_test.go index 074819b0c1..36bb476d22 100644 --- a/internal/query/iam_member_test.go +++ b/internal/query/iam_member_test.go @@ -21,21 +21,21 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.display_name" + - ", projections.users11_machines.name" + - ", projections.users11_humans.avatar_key" + - ", projections.users11.type" + + ", projections.users12_humans.email" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.display_name" + + ", projections.users12_machines.name" + + ", projections.users12_humans.avatar_key" + + ", projections.users12.type" + ", COUNT(*) OVER () " + "FROM projections.instance_members4 AS members " + - "LEFT JOIN projections.users11_humans " + - "ON members.user_id = projections.users11_humans.user_id AND members.instance_id = projections.users11_humans.instance_id " + - "LEFT JOIN projections.users11_machines " + - "ON members.user_id = projections.users11_machines.user_id AND members.instance_id = projections.users11_machines.instance_id " + - "LEFT JOIN projections.users11 " + - "ON members.user_id = projections.users11.id AND members.instance_id = projections.users11.instance_id " + + "LEFT JOIN projections.users12_humans " + + "ON members.user_id = projections.users12_humans.user_id AND members.instance_id = projections.users12_humans.instance_id " + + "LEFT JOIN projections.users12_machines " + + "ON members.user_id = projections.users12_machines.user_id AND members.instance_id = projections.users12_machines.instance_id " + + "LEFT JOIN projections.users12 " + + "ON members.user_id = projections.users12.id AND members.instance_id = projections.users12.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id AND members.instance_id = projections.login_names3.instance_id " + "AS OF SYSTEM TIME '-1 ms' " + diff --git a/internal/query/introspection.go b/internal/query/introspection.go index 0e190da25d..3f516062fe 100644 --- a/internal/query/introspection.go +++ b/internal/query/introspection.go @@ -7,7 +7,6 @@ import ( "sync" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/query/projection" @@ -30,11 +29,21 @@ func TriggerIntrospectionProjections(ctx context.Context) { triggerBatch(ctx, introspectionTriggerHandlers()...) } +type AppType string + +const ( + AppTypeAPI = "api" + AppTypeOIDC = "oidc" +) + type IntrospectionClient struct { - ClientID string - ClientSecret *crypto.CryptoValue - ProjectID string - PublicKeys database.Map[[]byte] + AppID string + ClientID string + HashedSecret string + AppType AppType + ProjectID string + ResourceOwner string + PublicKeys database.Map[[]byte] } //go:embed embed/introspection_client_by_id.sql @@ -50,7 +59,15 @@ func (q *Queries) GetIntrospectionClientByID(ctx context.Context, clientID strin ) err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { - return row.Scan(&client.ClientID, &client.ClientSecret, &client.ProjectID, &client.PublicKeys) + return row.Scan( + &client.AppID, + &client.ClientID, + &client.HashedSecret, + &client.AppType, + &client.ProjectID, + &client.ResourceOwner, + &client.PublicKeys, + ) }, introspectionClientByIDQuery, instanceID, clientID, getKeys, diff --git a/internal/query/introspection_test.go b/internal/query/introspection_test.go index d666adc51f..04998c65cf 100644 --- a/internal/query/introspection_test.go +++ b/internal/query/introspection_test.go @@ -4,7 +4,6 @@ import ( "database/sql" "database/sql/driver" _ "embed" - "encoding/json" "regexp" "testing" @@ -12,20 +11,10 @@ import ( "github.com/stretchr/testify/require" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" ) func TestQueries_GetIntrospectionClientByID(t *testing.T) { - secret := &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "alg", - KeyID: "keyID", - Crypted: []byte("secret"), - } - encSecret, err := json.Marshal(secret) - require.NoError(t, err) - pubkeys := database.Map[[]byte]{ "key1": {1, 2, 3}, "key2": {4, 5, 6}, @@ -61,14 +50,17 @@ func TestQueries_GetIntrospectionClientByID(t *testing.T) { getKeys: false, }, mock: mockQuery(expQuery, - []string{"client_id", "client_secret", "project_id", "public_keys"}, - []driver.Value{"clientID", encSecret, "projectID", nil}, + []string{"app_id", "client_id", "client_secret", "app_type", "project_id", "resource_owner", "public_keys"}, + []driver.Value{"appID", "clientID", "secret", "oidc", "projectID", "orgID", nil}, "instanceID", "clientID", false), want: &IntrospectionClient{ - ClientID: "clientID", - ClientSecret: secret, - ProjectID: "projectID", - PublicKeys: nil, + AppID: "appID", + ClientID: "clientID", + HashedSecret: "secret", + AppType: AppTypeOIDC, + ProjectID: "projectID", + ResourceOwner: "orgID", + PublicKeys: nil, }, }, { @@ -78,14 +70,17 @@ func TestQueries_GetIntrospectionClientByID(t *testing.T) { getKeys: true, }, mock: mockQuery(expQuery, - []string{"client_id", "client_secret", "project_id", "public_keys"}, - []driver.Value{"clientID", nil, "projectID", encPubkeys}, + []string{"app_id", "client_id", "client_secret", "app_type", "project_id", "resource_owner", "public_keys"}, + []driver.Value{"appID", "clientID", "", "oidc", "projectID", "orgID", encPubkeys}, "instanceID", "clientID", true), want: &IntrospectionClient{ - ClientID: "clientID", - ClientSecret: nil, - ProjectID: "projectID", - PublicKeys: pubkeys, + AppID: "appID", + ClientID: "clientID", + HashedSecret: "", + AppType: AppTypeOIDC, + ProjectID: "projectID", + ResourceOwner: "orgID", + PublicKeys: pubkeys, }, }, } diff --git a/internal/query/oidc_client.go b/internal/query/oidc_client.go index 7157f9e6ef..67a9c7d5eb 100644 --- a/internal/query/oidc_client.go +++ b/internal/query/oidc_client.go @@ -8,7 +8,6 @@ import ( "time" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/telemetry/tracing" @@ -20,7 +19,7 @@ type OIDCClient struct { AppID string `json:"app_id,omitempty"` State domain.AppState `json:"state,omitempty"` ClientID string `json:"client_id,omitempty"` - ClientSecret *crypto.CryptoValue `json:"client_secret,omitempty"` + HashedSecret string `json:"client_secret,omitempty"` RedirectURIs []string `json:"redirect_uris,omitempty"` ResponseTypes []domain.OIDCResponseType `json:"response_types,omitempty"` GrantTypes []domain.OIDCGrantType `json:"grant_types,omitempty"` diff --git a/internal/query/oidc_client_test.go b/internal/query/oidc_client_test.go index 44f642ccea..bfbbe74098 100644 --- a/internal/query/oidc_client_test.go +++ b/internal/query/oidc_client_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" @@ -66,7 +65,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx AppID: "236647088211886082", State: domain.AppStateActive, ClientID: "236647088211951618@tests", - ClientSecret: nil, + HashedSecret: "", RedirectURIs: []string{"http://localhost:9999/auth/callback"}, ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode, domain.OIDCGrantTypeRefreshToken}, @@ -97,7 +96,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx AppID: "236646457053020162", State: domain.AppStateActive, ClientID: "236646457053085698@tests", - ClientSecret: nil, + HashedSecret: "", RedirectURIs: []string{"http://localhost:9999/auth/callback"}, ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, @@ -124,15 +123,11 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx name: "secret client", mock: mockQuery(expQuery, cols, []driver.Value{testdataOidcClientSecret}, "instanceID", "clientID", true), want: &OIDCClient{ - InstanceID: "230690539048009730", - AppID: "236646858984783874", - State: domain.AppStateActive, - ClientID: "236646858984849410@tests", - ClientSecret: &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "bcrypt", - Crypted: []byte(`$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq`), - }, + InstanceID: "230690539048009730", + AppID: "236646858984783874", + State: domain.AppStateActive, + ClientID: "236646858984849410@tests", + HashedSecret: "$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq", RedirectURIs: []string{"http://localhost:9999/auth/callback"}, ResponseTypes: []domain.OIDCResponseType{0}, GrantTypes: []domain.OIDCGrantType{0}, @@ -163,7 +158,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx AppID: "239520764276441090", State: domain.AppStateActive, ClientID: "239520764779364354@zitadel", - ClientSecret: nil, + HashedSecret: "", RedirectURIs: []string{ "http://test2-qucuh5.localhost:9000/ui/console/auth/callback", "http://test.localhost.com:9000/ui/console/auth/callback"}, diff --git a/internal/query/org_member_test.go b/internal/query/org_member_test.go index 6bbac65772..8ac13e8d73 100644 --- a/internal/query/org_member_test.go +++ b/internal/query/org_member_test.go @@ -21,24 +21,24 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.display_name" + - ", projections.users11_machines.name" + - ", projections.users11_humans.avatar_key" + - ", projections.users11.type" + + ", projections.users12_humans.email" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.display_name" + + ", projections.users12_machines.name" + + ", projections.users12_humans.avatar_key" + + ", projections.users12.type" + ", COUNT(*) OVER () " + "FROM projections.org_members4 AS members " + - "LEFT JOIN projections.users11_humans " + - "ON members.user_id = projections.users11_humans.user_id " + - "AND members.instance_id = projections.users11_humans.instance_id " + - "LEFT JOIN projections.users11_machines " + - "ON members.user_id = projections.users11_machines.user_id " + - "AND members.instance_id = projections.users11_machines.instance_id " + - "LEFT JOIN projections.users11 " + - "ON members.user_id = projections.users11.id " + - "AND members.instance_id = projections.users11.instance_id " + + "LEFT JOIN projections.users12_humans " + + "ON members.user_id = projections.users12_humans.user_id " + + "AND members.instance_id = projections.users12_humans.instance_id " + + "LEFT JOIN projections.users12_machines " + + "ON members.user_id = projections.users12_machines.user_id " + + "AND members.instance_id = projections.users12_machines.instance_id " + + "LEFT JOIN projections.users12 " + + "ON members.user_id = projections.users12.id " + + "AND members.instance_id = projections.users12.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id " + "AND members.instance_id = projections.login_names3.instance_id " + diff --git a/internal/query/project_grant_member_test.go b/internal/query/project_grant_member_test.go index 70cdfb7c6a..6d0ec6d822 100644 --- a/internal/query/project_grant_member_test.go +++ b/internal/query/project_grant_member_test.go @@ -21,24 +21,24 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.display_name" + - ", projections.users11_machines.name" + - ", projections.users11_humans.avatar_key" + - ", projections.users11.type" + + ", projections.users12_humans.email" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.display_name" + + ", projections.users12_machines.name" + + ", projections.users12_humans.avatar_key" + + ", projections.users12.type" + ", COUNT(*) OVER () " + "FROM projections.project_grant_members4 AS members " + - "LEFT JOIN projections.users11_humans " + - "ON members.user_id = projections.users11_humans.user_id " + - "AND members.instance_id = projections.users11_humans.instance_id " + - "LEFT JOIN projections.users11_machines " + - "ON members.user_id = projections.users11_machines.user_id " + - "AND members.instance_id = projections.users11_machines.instance_id " + - "LEFT JOIN projections.users11 " + - "ON members.user_id = projections.users11.id " + - "AND members.instance_id = projections.users11.instance_id " + + "LEFT JOIN projections.users12_humans " + + "ON members.user_id = projections.users12_humans.user_id " + + "AND members.instance_id = projections.users12_humans.instance_id " + + "LEFT JOIN projections.users12_machines " + + "ON members.user_id = projections.users12_machines.user_id " + + "AND members.instance_id = projections.users12_machines.instance_id " + + "LEFT JOIN projections.users12 " + + "ON members.user_id = projections.users12.id " + + "AND members.instance_id = projections.users12.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id " + "AND members.instance_id = projections.login_names3.instance_id " + diff --git a/internal/query/project_member_test.go b/internal/query/project_member_test.go index 4e7fe0b6e0..2662016564 100644 --- a/internal/query/project_member_test.go +++ b/internal/query/project_member_test.go @@ -21,24 +21,24 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.display_name" + - ", projections.users11_machines.name" + - ", projections.users11_humans.avatar_key" + - ", projections.users11.type" + + ", projections.users12_humans.email" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.display_name" + + ", projections.users12_machines.name" + + ", projections.users12_humans.avatar_key" + + ", projections.users12.type" + ", COUNT(*) OVER () " + "FROM projections.project_members4 AS members " + - "LEFT JOIN projections.users11_humans " + - "ON members.user_id = projections.users11_humans.user_id " + - "AND members.instance_id = projections.users11_humans.instance_id " + - "LEFT JOIN projections.users11_machines " + - "ON members.user_id = projections.users11_machines.user_id " + - "AND members.instance_id = projections.users11_machines.instance_id " + - "LEFT JOIN projections.users11 " + - "ON members.user_id = projections.users11.id " + - "AND members.instance_id = projections.users11.instance_id " + + "LEFT JOIN projections.users12_humans " + + "ON members.user_id = projections.users12_humans.user_id " + + "AND members.instance_id = projections.users12_humans.instance_id " + + "LEFT JOIN projections.users12_machines " + + "ON members.user_id = projections.users12_machines.user_id " + + "AND members.instance_id = projections.users12_machines.instance_id " + + "LEFT JOIN projections.users12 " + + "ON members.user_id = projections.users12.id " + + "AND members.instance_id = projections.users12.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id " + "AND members.instance_id = projections.login_names3.instance_id " + diff --git a/internal/query/projection/app.go b/internal/query/projection/app.go index 2d81627c7c..a162548dd4 100644 --- a/internal/query/projection/app.go +++ b/internal/query/projection/app.go @@ -3,6 +3,7 @@ package projection import ( "context" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" @@ -15,7 +16,7 @@ import ( ) const ( - AppProjectionTable = "projections.apps6" + AppProjectionTable = "projections.apps7" AppAPITable = AppProjectionTable + "_" + appAPITableSuffix AppOIDCTable = AppProjectionTable + "_" + appOIDCTableSuffix AppSAMLTable = AppProjectionTable + "_" + appSAMLTableSuffix @@ -96,7 +97,7 @@ func (*appProjection) Init() *old_handler.Check { handler.NewColumn(AppAPIConfigColumnAppID, handler.ColumnTypeText), handler.NewColumn(AppAPIConfigColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(AppAPIConfigColumnClientID, handler.ColumnTypeText), - handler.NewColumn(AppAPIConfigColumnClientSecret, handler.ColumnTypeJSONB, handler.Nullable()), + handler.NewColumn(AppAPIConfigColumnClientSecret, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(AppAPIConfigColumnAuthMethod, handler.ColumnTypeEnum), }, handler.NewPrimaryKey(AppAPIConfigColumnInstanceID, AppAPIConfigColumnAppID), @@ -109,7 +110,7 @@ func (*appProjection) Init() *old_handler.Check { handler.NewColumn(AppOIDCConfigColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(AppOIDCConfigColumnVersion, handler.ColumnTypeEnum), handler.NewColumn(AppOIDCConfigColumnClientID, handler.ColumnTypeText), - handler.NewColumn(AppOIDCConfigColumnClientSecret, handler.ColumnTypeJSONB, handler.Nullable()), + handler.NewColumn(AppOIDCConfigColumnClientSecret, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnRedirectUris, handler.ColumnTypeTextArray, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnResponseTypes, handler.ColumnTypeEnumArray, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnGrantTypes, handler.ColumnTypeEnumArray, handler.Nullable()), @@ -186,6 +187,10 @@ func (p *appProjection) Reducers() []handler.AggregateReducer { Event: project.APIConfigSecretChangedType, Reduce: p.reduceAPIConfigSecretChanged, }, + { + Event: project.APIConfigSecretHashUpdatedType, + Reduce: p.reduceAPIConfigSecretHashUpdated, + }, { Event: project.OIDCConfigAddedType, Reduce: p.reduceOIDCConfigAdded, @@ -198,6 +203,10 @@ func (p *appProjection) Reducers() []handler.AggregateReducer { Event: project.OIDCConfigSecretChangedType, Reduce: p.reduceOIDCConfigSecretChanged, }, + { + Event: project.OIDCConfigSecretHashUpdatedType, + Reduce: p.reduceOIDCConfigSecretHashUpdated, + }, { Event: project.SAMLConfigAddedType, Reduce: p.reduceSAMLConfigAdded, @@ -350,7 +359,7 @@ func (p *appProjection) reduceAPIConfigAdded(event eventstore.Event) (*handler.S handler.NewCol(AppAPIConfigColumnAppID, e.AppID), handler.NewCol(AppAPIConfigColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(AppAPIConfigColumnClientID, e.ClientID), - handler.NewCol(AppAPIConfigColumnClientSecret, e.ClientSecret), + handler.NewCol(AppAPIConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), handler.NewCol(AppAPIConfigColumnAuthMethod, e.AuthMethodType), }, handler.WithTableSuffix(appAPITableSuffix), @@ -374,9 +383,6 @@ func (p *appProjection) reduceAPIConfigChanged(event eventstore.Event) (*handler return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-vnZKi", "reduce.wrong.event.type %s", project.APIConfigChangedType) } cols := make([]handler.Column, 0, 2) - if e.ClientSecret != nil { - cols = append(cols, handler.NewCol(AppAPIConfigColumnClientSecret, e.ClientSecret)) - } if e.AuthMethodType != nil { cols = append(cols, handler.NewCol(AppAPIConfigColumnAuthMethod, *e.AuthMethodType)) } @@ -415,7 +421,37 @@ func (p *appProjection) reduceAPIConfigSecretChanged(event eventstore.Event) (*h e, handler.AddUpdateStatement( []handler.Column{ - handler.NewCol(AppAPIConfigColumnClientSecret, e.ClientSecret), + handler.NewCol(AppAPIConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), + }, + []handler.Condition{ + handler.NewCond(AppAPIConfigColumnAppID, e.AppID), + handler.NewCond(AppAPIConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + handler.WithTableSuffix(appAPITableSuffix), + ), + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(AppColumnChangeDate, e.CreationDate()), + handler.NewCol(AppColumnSequence, e.Sequence()), + }, + []handler.Condition{ + handler.NewCond(AppColumnID, e.AppID), + handler.NewCond(AppColumnInstanceID, e.Aggregate().InstanceID), + }, + ), + ), nil +} + +func (p *appProjection) reduceAPIConfigSecretHashUpdated(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*project.APIConfigSecretHashUpdatedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ttb0I", "reduce.wrong.event.type %s", project.APIConfigSecretHashUpdatedType) + } + return handler.NewMultiStatement( + e, + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(AppAPIConfigColumnClientSecret, e.HashedSecret), }, []handler.Condition{ handler.NewCond(AppAPIConfigColumnAppID, e.AppID), @@ -449,7 +485,7 @@ func (p *appProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler. handler.NewCol(AppOIDCConfigColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(AppOIDCConfigColumnVersion, e.Version), handler.NewCol(AppOIDCConfigColumnClientID, e.ClientID), - handler.NewCol(AppOIDCConfigColumnClientSecret, e.ClientSecret), + handler.NewCol(AppOIDCConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), handler.NewCol(AppOIDCConfigColumnRedirectUris, database.TextArray[string](e.RedirectUris)), handler.NewCol(AppOIDCConfigColumnResponseTypes, database.NumberArray[domain.OIDCResponseType](e.ResponseTypes)), handler.NewCol(AppOIDCConfigColumnGrantTypes, database.NumberArray[domain.OIDCGrantType](e.GrantTypes)), @@ -569,7 +605,37 @@ func (p *appProjection) reduceOIDCConfigSecretChanged(event eventstore.Event) (* e, handler.AddUpdateStatement( []handler.Column{ - handler.NewCol(AppOIDCConfigColumnClientSecret, e.ClientSecret), + handler.NewCol(AppOIDCConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), + }, + []handler.Condition{ + handler.NewCond(AppOIDCConfigColumnAppID, e.AppID), + handler.NewCond(AppOIDCConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + handler.WithTableSuffix(appOIDCTableSuffix), + ), + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(AppColumnChangeDate, e.CreationDate()), + handler.NewCol(AppColumnSequence, e.Sequence()), + }, + []handler.Condition{ + handler.NewCond(AppColumnID, e.AppID), + handler.NewCond(AppColumnInstanceID, e.Aggregate().InstanceID), + }, + ), + ), nil +} + +func (p *appProjection) reduceOIDCConfigSecretHashUpdated(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*project.OIDCConfigSecretHashUpdatedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-toSh1", "reduce.wrong.event.type %s", project.OIDCConfigSecretHashUpdatedType) + } + return handler.NewMultiStatement( + e, + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(AppOIDCConfigColumnClientSecret, e.HashedSecret), }, []handler.Condition{ handler.NewCond(AppOIDCConfigColumnAppID, e.AppID), diff --git a/internal/query/projection/app_test.go b/internal/query/projection/app_test.go index 6d08d2e98a..49979c4698 100644 --- a/internal/query/projection/app_test.go +++ b/internal/query/projection/app_test.go @@ -46,7 +46,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps6 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.apps7 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "app-id", "my-app", @@ -83,7 +83,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps7 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "my-app", anyArg{}, @@ -136,7 +136,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps7 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ domain.AppStateInactive, anyArg{}, @@ -168,7 +168,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps7 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ domain.AppStateActive, anyArg{}, @@ -200,7 +200,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps6 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.apps7 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "app-id", "instance-id", @@ -227,7 +227,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps6 WHERE (project_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.apps7 WHERE (project_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -254,7 +254,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps6 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.apps7 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -264,7 +264,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - name: "project reduceAPIConfigAdded", + name: "project reduceAPIConfigAdded, v1 secret", args: args{ event: getEvent( testEvent( @@ -273,7 +273,7 @@ func TestAppProjection_reduces(t *testing.T) { []byte(`{ "appId": "app-id", "clientId": "client-id", - "clientSecret": {}, + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"}, "authMethodType": 1 }`), ), project.APIConfigAddedEventMapper), @@ -285,17 +285,61 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps6_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.apps7_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "app-id", "instance-id", "client-id", - anyArg{}, + "secret", domain.APIAuthMethodTypePrivateKeyJWT, }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceAPIConfigAdded, v2 secret", + args: args{ + event: getEvent( + testEvent( + project.APIConfigAddedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "clientId": "client-id", + "hashedSecret": "secret", + "authMethodType": 1 + }`), + ), project.APIConfigAddedEventMapper), + }, + reduce: (&appProjection{}).reduceAPIConfigAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO projections.apps7_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", + expectedArgs: []interface{}{ + "app-id", + "instance-id", + "client-id", + "secret", + domain.APIAuthMethodTypePrivateKeyJWT, + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -317,7 +361,6 @@ func TestAppProjection_reduces(t *testing.T) { []byte(`{ "appId": "app-id", "clientId": "client-id", - "clientSecret": {}, "authMethodType": 1 }`), ), project.APIConfigChangedEventMapper), @@ -329,16 +372,15 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6_api_configs SET (client_secret, auth_method) = ($1, $2) WHERE (app_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7_api_configs SET auth_method = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ - anyArg{}, domain.APIAuthMethodTypePrivateKeyJWT, "app-id", "instance-id", }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -372,16 +414,16 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - name: "project reduceAPIConfigSecretChanged", + name: "project reduceAPIConfigSecretChanged, v1 secret", args: args{ event: getEvent( testEvent( project.APIConfigSecretChangedType, project.AggregateType, []byte(`{ - "appId": "app-id", - "client_secret": {} - }`), + "appId": "app-id", + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"} + }`), ), project.APIConfigSecretChangedEventMapper), }, reduce: (&appProjection{}).reduceAPIConfigSecretChanged, @@ -391,15 +433,15 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.apps7_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ - anyArg{}, + "secret", "app-id", "instance-id", }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -412,7 +454,87 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - name: "project reduceOIDCConfigAdded", + name: "project reduceAPIConfigSecretChanged, v2 secret", + args: args{ + event: getEvent( + testEvent( + project.APIConfigSecretChangedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "hashedSecret": "secret" + }`), + ), project.APIConfigSecretChangedEventMapper), + }, + reduce: (&appProjection{}).reduceAPIConfigSecretChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.apps7_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "app-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceAPIConfigSecretHashUpdated", + args: args{ + event: getEvent( + testEvent( + project.APIConfigSecretHashUpdatedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "hashedSecret": "secret" + }`), + ), eventstore.GenericEventMapper[project.APIConfigSecretHashUpdatedEvent]), + }, + reduce: (&appProjection{}).reduceAPIConfigSecretHashUpdated, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.apps7_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "app-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceOIDCConfigAdded, v1 secret", args: args{ event: getEvent( testEvent( @@ -422,7 +544,7 @@ func TestAppProjection_reduces(t *testing.T) { "oidcVersion": 0, "appId": "app-id", "clientId": "client-id", - "clientSecret": {}, + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"}, "redirectUris": ["redirect.one.ch", "redirect.two.ch"], "responseTypes": [1,2], "grantTypes": [1,2], @@ -447,13 +569,13 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps6_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", + expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", expectedArgs: []interface{}{ "app-id", "instance-id", domain.OIDCVersionV1, "client-id", - anyArg{}, + "secret", database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"}, database.NumberArray[domain.OIDCResponseType]{1, 2}, database.NumberArray[domain.OIDCGrantType]{1, 2}, @@ -471,7 +593,79 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceOIDCConfigAdded, v2 secret", + args: args{ + event: getEvent( + testEvent( + project.OIDCConfigAddedType, + project.AggregateType, + []byte(`{ + "oidcVersion": 0, + "appId": "app-id", + "clientId": "client-id", + "hashedSecret": "secret", + "redirectUris": ["redirect.one.ch", "redirect.two.ch"], + "responseTypes": [1,2], + "grantTypes": [1,2], + "applicationType": 2, + "authMethodType": 2, + "postLogoutRedirectUris": ["logout.one.ch", "logout.two.ch"], + "devMode": true, + "accessTokenType": 1, + "accessTokenRoleAssertion": true, + "idTokenRoleAssertion": true, + "idTokenUserinfoAssertion": true, + "clockSkew": 1000, + "additionalOrigins": ["origin.one.ch", "origin.two.ch"], + "skipNativeAppSuccessPage": true + }`), + ), project.OIDCConfigAddedEventMapper), + }, + reduce: (&appProjection{}).reduceOIDCConfigAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", + expectedArgs: []interface{}{ + "app-id", + "instance-id", + domain.OIDCVersionV1, + "client-id", + "secret", + database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"}, + database.NumberArray[domain.OIDCResponseType]{1, 2}, + database.NumberArray[domain.OIDCGrantType]{1, 2}, + domain.OIDCApplicationTypeNative, + domain.OIDCAuthMethodTypeNone, + database.TextArray[string]{"logout.one.ch", "logout.two.ch"}, + true, + domain.OIDCTokenTypeJWT, + true, + true, + true, + 1 * time.Microsecond, + database.TextArray[string]{"origin.one.ch", "origin.two.ch"}, + true, + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -518,7 +712,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)", + expectedStmt: "UPDATE projections.apps7_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)", expectedArgs: []interface{}{ domain.OIDCVersionV1, database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"}, @@ -540,7 +734,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -574,7 +768,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - name: "project reduceOIDCConfigSecretChanged", + name: "project reduceOIDCConfigSecretChanged, v1 secret", args: args{ event: getEvent( testEvent( @@ -582,7 +776,7 @@ func TestAppProjection_reduces(t *testing.T) { project.AggregateType, []byte(`{ "appId": "app-id", - "client_secret": {} + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"} }`), ), project.OIDCConfigSecretChangedEventMapper), }, @@ -593,15 +787,95 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps6_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.apps7_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ - anyArg{}, + "secret", "app-id", "instance-id", }, }, { - expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceOIDCConfigSecretChanged, v2 secret", + args: args{ + event: getEvent( + testEvent( + project.OIDCConfigSecretChangedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "hashedSecret": "secret" + }`), + ), project.OIDCConfigSecretChangedEventMapper), + }, + reduce: (&appProjection{}).reduceOIDCConfigSecretChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.apps7_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "app-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "app-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "project reduceOIDCConfigSecretHashUpdated", + args: args{ + event: getEvent( + testEvent( + project.OIDCConfigSecretHashUpdatedType, + project.AggregateType, + []byte(`{ + "appId": "app-id", + "hashedSecret": "secret" + }`), + ), eventstore.GenericEventMapper[project.OIDCConfigSecretHashUpdatedEvent]), + }, + reduce: (&appProjection{}).reduceOIDCConfigSecretHashUpdated, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.apps7_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "app-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -630,7 +904,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps6 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.apps7 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", diff --git a/internal/query/projection/user.go b/internal/query/projection/user.go index 0a75abc368..d9c5f78b22 100644 --- a/internal/query/projection/user.go +++ b/internal/query/projection/user.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" old_handler "github.com/zitadel/zitadel/internal/eventstore/handler" @@ -15,7 +16,7 @@ import ( ) const ( - UserTable = "projections.users11" + UserTable = "projections.users12" UserHumanTable = UserTable + "_" + UserHumanSuffix UserMachineTable = UserTable + "_" + UserMachineSuffix UserNotifyTable = UserTable + "_" + UserNotifySuffix @@ -125,7 +126,7 @@ func (*userProjection) Init() *old_handler.Check { handler.NewColumn(MachineUserInstanceIDCol, handler.ColumnTypeText), handler.NewColumn(MachineNameCol, handler.ColumnTypeText), handler.NewColumn(MachineDescriptionCol, handler.ColumnTypeText, handler.Nullable()), - handler.NewColumn(MachineSecretCol, handler.ColumnTypeJSONB, handler.Nullable()), + handler.NewColumn(MachineSecretCol, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(MachineAccessTokenTypeCol, handler.ColumnTypeEnum, handler.Default(0)), }, handler.NewPrimaryKey(MachineUserInstanceIDCol, MachineUserIDCol), @@ -285,6 +286,10 @@ func (p *userProjection) Reducers() []handler.AggregateReducer { Event: user.MachineSecretSetType, Reduce: p.reduceMachineSecretSet, }, + { + Event: user.MachineSecretHashUpdatedType, + Reduce: p.reduceMachineSecretHashUpdated, + }, { Event: user.MachineSecretRemovedType, Reduce: p.reduceMachineSecretRemoved, @@ -354,7 +359,7 @@ func (p *userProjection) reduceHumanAdded(event eventstore.Event) (*handler.Stat handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID), handler.NewCol(NotifyLastEmailCol, e.EmailAddress), handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}), - handler.NewCol(NotifyPasswordSetCol, user.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), + handler.NewCol(NotifyPasswordSetCol, crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), }, handler.WithTableSuffix(UserNotifySuffix), ), @@ -403,7 +408,7 @@ func (p *userProjection) reduceHumanRegistered(event eventstore.Event) (*handler handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID), handler.NewCol(NotifyLastEmailCol, e.EmailAddress), handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}), - handler.NewCol(NotifyPasswordSetCol, user.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), + handler.NewCol(NotifyPasswordSetCol, crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), }, handler.WithTableSuffix(UserNotifySuffix), ), @@ -952,7 +957,37 @@ func (p *userProjection) reduceMachineSecretSet(event eventstore.Event) (*handle ), handler.AddUpdateStatement( []handler.Column{ - handler.NewCol(MachineSecretCol, e.ClientSecret), + handler.NewCol(MachineSecretCol, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)), + }, + []handler.Condition{ + handler.NewCond(MachineUserIDCol, e.Aggregate().ID), + handler.NewCond(MachineUserInstanceIDCol, e.Aggregate().InstanceID), + }, + handler.WithTableSuffix(UserMachineSuffix), + ), + ), nil +} + +func (p *userProjection) reduceMachineSecretHashUpdated(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*user.MachineSecretHashUpdatedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Wieng4u", "reduce.wrong.event.type %s", user.MachineSecretHashUpdatedType) + } + return handler.NewMultiStatement( + e, + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(UserChangeDateCol, e.CreationDate()), + handler.NewCol(UserSequenceCol, e.Sequence()), + }, + []handler.Condition{ + handler.NewCond(UserIDCol, e.Aggregate().ID), + handler.NewCond(UserInstanceIDCol, e.Aggregate().InstanceID), + }, + ), + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(MachineSecretCol, e.HashedSecret), }, []handler.Condition{ handler.NewCond(MachineUserIDCol, e.Aggregate().ID), diff --git a/internal/query/projection/user_test.go b/internal/query/projection/user_test.go index 2ef6caff93..1359b624e4 100644 --- a/internal/query/projection/user_test.go +++ b/internal/query/projection/user_test.go @@ -4,7 +4,6 @@ import ( "database/sql" "testing" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" @@ -52,7 +51,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -66,7 +65,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -82,7 +81,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -122,7 +121,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -136,7 +135,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -152,7 +151,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -187,7 +186,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -201,7 +200,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -217,7 +216,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -258,7 +257,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -272,7 +271,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -288,7 +287,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -328,7 +327,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -342,7 +341,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -358,7 +357,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -393,7 +392,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -407,7 +406,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.users12_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone, password_change_required) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -423,7 +422,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -453,7 +452,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateInitial, "agg-id", @@ -481,7 +480,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateInitial, "agg-id", @@ -509,7 +508,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateActive, "agg-id", @@ -537,7 +536,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateActive, "agg-id", @@ -565,7 +564,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateLocked, @@ -595,7 +594,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateActive, @@ -625,7 +624,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateInactive, @@ -655,7 +654,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateActive, @@ -685,7 +684,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.users11 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.users12 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -714,7 +713,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, "username", @@ -746,7 +745,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users12 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, "id@temporary.domain", @@ -783,7 +782,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -792,7 +791,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.users12_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ "first-name", "last-name", @@ -832,7 +831,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -841,7 +840,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.users12_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ "first-name", "last-name", @@ -876,7 +875,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -885,7 +884,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.PhoneNumber("+41 00 000 00 00"), false, @@ -894,7 +893,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "+41 00 000 00 00", Valid: true}, "agg-id", @@ -924,7 +923,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -933,7 +932,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.PhoneNumber("+41 00 000 00 00"), false, @@ -942,7 +941,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "+41 00 000 00 00", Valid: true}, "agg-id", @@ -970,7 +969,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -979,7 +978,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -988,7 +987,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -1017,7 +1016,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1026,7 +1025,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -1035,7 +1034,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -1064,7 +1063,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1073,7 +1072,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1081,7 +1080,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users12_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1108,7 +1107,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1117,7 +1116,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1125,7 +1124,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users12_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1154,7 +1153,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1163,7 +1162,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.EmailAddress("email@zitadel.com"), false, @@ -1172,7 +1171,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "email@zitadel.com", Valid: true}, "agg-id", @@ -1202,7 +1201,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1211,7 +1210,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.EmailAddress("email@zitadel.com"), false, @@ -1220,7 +1219,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "email@zitadel.com", Valid: true}, "agg-id", @@ -1248,7 +1247,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1257,7 +1256,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1265,7 +1264,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users12_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1292,7 +1291,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1301,7 +1300,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1309,7 +1308,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users12_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1338,7 +1337,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1347,7 +1346,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "users/agg-id/avatar", "agg-id", @@ -1375,7 +1374,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1384,7 +1383,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ nil, "agg-id", @@ -1414,7 +1413,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11_humans SET password_change_required = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET password_change_required = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1422,7 +1421,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET password_set = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET password_set = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1452,7 +1451,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11_humans SET password_change_required = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_humans SET password_change_required = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ false, "agg-id", @@ -1460,7 +1459,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_notifications SET password_set = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_notifications SET password_set = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1491,7 +1490,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -1505,7 +1504,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1539,7 +1538,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users11 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users12 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -1553,7 +1552,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users11_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users12_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1586,7 +1585,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1595,7 +1594,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ "machine-name", "description", @@ -1626,7 +1625,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1635,7 +1634,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "machine-name", "agg-id", @@ -1665,7 +1664,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1674,7 +1673,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "description", "agg-id", @@ -1705,14 +1704,14 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - name: "reduceMachineSecretSet", + name: "reduceMachineSecretSet v1 value", args: args{ event: getEvent( testEvent( user.MachineSecretSetType, user.AggregateType, []byte(`{ - "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"deadbeef"} + "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"} }`), ), user.MachineSecretSetEventMapper), }, @@ -1723,7 +1722,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1732,13 +1731,87 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ - &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "bcrypt", - Crypted: []byte{117, 230, 157, 109, 231, 159}, - }, + "secret", + "agg-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceMachineSecretSet v2 value", + args: args{ + event: getEvent( + testEvent( + user.MachineSecretSetType, + user.AggregateType, + []byte(`{ + "hashedSecret": "secret" + }`), + ), user.MachineSecretSetEventMapper), + }, + reduce: (&userProjection{}).reduceMachineSecretSet, + want: wantReduce{ + aggregateType: user.AggregateType, + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "agg-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.users12_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", + "agg-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceMachineSecretHashUpdated", + args: args{ + event: getEvent( + testEvent( + user.MachineSecretHashUpdatedType, + user.AggregateType, + []byte(`{ + "hashedSecret": "secret" + }`), + ), eventstore.GenericEventMapper[user.MachineSecretHashUpdatedEvent]), + }, + reduce: (&userProjection{}).reduceMachineSecretHashUpdated, + want: wantReduce{ + aggregateType: user.AggregateType, + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "agg-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.users12_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "secret", "agg-id", "instance-id", }, @@ -1764,7 +1837,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users11 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users12 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1773,7 +1846,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users11_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users12_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ nil, "agg-id", @@ -1801,7 +1874,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.users11 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.users12 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -1828,7 +1901,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.users11 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.users12 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, diff --git a/internal/query/secret_generators.go b/internal/query/secret_generators.go index d8f838b82a..9517f53f4c 100644 --- a/internal/query/secret_generators.go +++ b/internal/query/secret_generators.go @@ -118,22 +118,6 @@ func (q *Queries) InitEncryptionGenerator(ctx context.Context, generatorType dom return crypto.NewEncryptionGenerator(cryptoConfig, algorithm), nil } -func (q *Queries) InitHashGenerator(ctx context.Context, generatorType domain.SecretGeneratorType, algorithm crypto.HashAlgorithm) (crypto.Generator, error) { - generatorConfig, err := q.SecretGeneratorByType(ctx, generatorType) - if err != nil { - return nil, err - } - cryptoConfig := crypto.GeneratorConfig{ - Length: generatorConfig.Length, - Expiry: generatorConfig.Expiry, - IncludeLowerLetters: generatorConfig.IncludeLowerLetters, - IncludeUpperLetters: generatorConfig.IncludeUpperLetters, - IncludeDigits: generatorConfig.IncludeDigits, - IncludeSymbols: generatorConfig.IncludeSymbols, - } - return crypto.NewHashGenerator(cryptoConfig, algorithm), nil -} - func (q *Queries) SecretGeneratorByType(ctx context.Context, generatorType domain.SecretGeneratorType) (generator *SecretGenerator, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/query/sessions_test.go b/internal/query/sessions_test.go index 6f6a330e03..9b08b43441 100644 --- a/internal/query/sessions_test.go +++ b/internal/query/sessions_test.go @@ -31,7 +31,7 @@ var ( ` projections.sessions8.user_resource_owner,` + ` projections.sessions8.user_checked_at,` + ` projections.login_names3.login_name,` + - ` projections.users11_humans.display_name,` + + ` projections.users12_humans.display_name,` + ` projections.sessions8.password_checked_at,` + ` projections.sessions8.intent_checked_at,` + ` projections.sessions8.webauthn_checked_at,` + @@ -48,8 +48,8 @@ var ( ` projections.sessions8.expiration` + ` FROM projections.sessions8` + ` LEFT JOIN projections.login_names3 ON projections.sessions8.user_id = projections.login_names3.user_id AND projections.sessions8.instance_id = projections.login_names3.instance_id` + - ` LEFT JOIN projections.users11_humans ON projections.sessions8.user_id = projections.users11_humans.user_id AND projections.sessions8.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11 ON projections.sessions8.user_id = projections.users11.id AND projections.sessions8.instance_id = projections.users11.instance_id` + + ` LEFT JOIN projections.users12_humans ON projections.sessions8.user_id = projections.users12_humans.user_id AND projections.sessions8.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12 ON projections.sessions8.user_id = projections.users12.id AND projections.sessions8.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) expectedSessionsQuery = regexp.QuoteMeta(`SELECT projections.sessions8.id,` + ` projections.sessions8.creation_date,` + @@ -62,7 +62,7 @@ var ( ` projections.sessions8.user_resource_owner,` + ` projections.sessions8.user_checked_at,` + ` projections.login_names3.login_name,` + - ` projections.users11_humans.display_name,` + + ` projections.users12_humans.display_name,` + ` projections.sessions8.password_checked_at,` + ` projections.sessions8.intent_checked_at,` + ` projections.sessions8.webauthn_checked_at,` + @@ -75,8 +75,8 @@ var ( ` COUNT(*) OVER ()` + ` FROM projections.sessions8` + ` LEFT JOIN projections.login_names3 ON projections.sessions8.user_id = projections.login_names3.user_id AND projections.sessions8.instance_id = projections.login_names3.instance_id` + - ` LEFT JOIN projections.users11_humans ON projections.sessions8.user_id = projections.users11_humans.user_id AND projections.sessions8.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11 ON projections.sessions8.user_id = projections.users11.id AND projections.sessions8.instance_id = projections.users11.instance_id` + + ` LEFT JOIN projections.users12_humans ON projections.sessions8.user_id = projections.users12_humans.user_id AND projections.sessions8.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12 ON projections.sessions8.user_id = projections.users12.id AND projections.sessions8.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) sessionCols = []string{ diff --git a/internal/query/testdata/oidc_client_secret.json b/internal/query/testdata/oidc_client_secret.json index e7d5926f7f..d12544f23d 100644 --- a/internal/query/testdata/oidc_client_secret.json +++ b/internal/query/testdata/oidc_client_secret.json @@ -2,12 +2,7 @@ "instance_id": "230690539048009730", "app_id": "236646858984783874", "client_id": "236646858984849410@tests", - "client_secret": { - "KeyID": "", - "Crypted": "JDJhJDE0JE96WjBYRVpaRXREMTNweS9FUGJhMmV2c1M2V2NLWjVvclZNajlwV0hFR0VIbUx1MmgzUEZx", - "Algorithm": "bcrypt", - "CryptoType": 1 - }, + "client_secret": "$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq", "redirect_uris": ["http://localhost:9999/auth/callback"], "response_types": [0], "grant_types": [0], diff --git a/internal/query/user.go b/internal/query/user.go index a6bfaedf0f..cb48346e45 100644 --- a/internal/query/user.go +++ b/internal/query/user.go @@ -13,7 +13,6 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/call" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query/projection" @@ -94,7 +93,7 @@ type Phone struct { type Machine struct { Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` - Secret *crypto.CryptoValue `json:"secret,omitempty"` + EncodedSecret string `json:"encoded_hash,omitempty"` AccessTokenType domain.OIDCTokenType `json:"access_token_type,omitempty"` } @@ -827,7 +826,7 @@ func scanUser(row *sql.Row) (*User, error) { machineID := sql.NullString{} name := sql.NullString{} description := sql.NullString{} - var secret *crypto.CryptoValue + encodedHash := sql.NullString{} accessTokenType := sql.NullInt32{} err := row.Scan( @@ -857,7 +856,7 @@ func scanUser(row *sql.Row) (*User, error) { &machineID, &name, &description, - &secret, + &encodedHash, &accessTokenType, &count, ) @@ -890,7 +889,7 @@ func scanUser(row *sql.Row) (*User, error) { u.Machine = &Machine{ Name: name.String, Description: description.String, - Secret: secret, + EncodedSecret: encodedHash.String, AccessTokenType: domain.OIDCTokenType(accessTokenType.Int32), } } @@ -1360,7 +1359,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde machineID := sql.NullString{} name := sql.NullString{} description := sql.NullString{} - secret := new(crypto.CryptoValue) + encodedHash := sql.NullString{} accessTokenType := sql.NullInt32{} err := rows.Scan( @@ -1390,7 +1389,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde &machineID, &name, &description, - secret, + &encodedHash, &accessTokenType, &count, ) @@ -1422,7 +1421,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde u.Machine = &Machine{ Name: name.String, Description: description.String, - Secret: secret, + EncodedSecret: encodedHash.String, AccessTokenType: domain.OIDCTokenType(accessTokenType.Int32), } } diff --git a/internal/query/user_auth_method_test.go b/internal/query/user_auth_method_test.go index 66433c208f..a4a66cac04 100644 --- a/internal/query/user_auth_method_test.go +++ b/internal/query/user_auth_method_test.go @@ -39,38 +39,38 @@ var ( "method_type", "count", } - prepareActiveAuthMethodTypesStmt = `SELECT projections.users11_notifications.password_set,` + + prepareActiveAuthMethodTypesStmt = `SELECT projections.users12_notifications.password_set,` + ` auth_method_types.method_type,` + ` user_idps_count.count` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_notifications ON projections.users11.id = projections.users11_notifications.user_id AND projections.users11.instance_id = projections.users11_notifications.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_notifications ON projections.users12.id = projections.users12_notifications.user_id AND projections.users12.instance_id = projections.users12_notifications.instance_id` + ` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods4 AS auth_method_types` + ` WHERE auth_method_types.state = $1) AS auth_method_types` + - ` ON auth_method_types.user_id = projections.users11.id AND auth_method_types.instance_id = projections.users11.instance_id` + + ` ON auth_method_types.user_id = projections.users12.id AND auth_method_types.instance_id = projections.users12.instance_id` + ` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` + ` GROUP BY user_idps_count.user_id, user_idps_count.instance_id) AS user_idps_count` + - ` ON user_idps_count.user_id = projections.users11.id AND user_idps_count.instance_id = projections.users11.instance_id` + + ` ON user_idps_count.user_id = projections.users12.id AND user_idps_count.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms` prepareActiveAuthMethodTypesCols = []string{ "password_set", "method_type", "idps_count", } - prepareAuthMethodTypesRequiredStmt = `SELECT projections.users11_notifications.password_set,` + + prepareAuthMethodTypesRequiredStmt = `SELECT projections.users12_notifications.password_set,` + ` auth_method_types.method_type,` + ` user_idps_count.count,` + ` auth_methods_force_mfa.force_mfa,` + ` auth_methods_force_mfa.force_mfa_local_only` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_notifications ON projections.users11.id = projections.users11_notifications.user_id AND projections.users11.instance_id = projections.users11_notifications.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_notifications ON projections.users12.id = projections.users12_notifications.user_id AND projections.users12.instance_id = projections.users12_notifications.instance_id` + ` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods4 AS auth_method_types` + ` WHERE auth_method_types.state = $1) AS auth_method_types` + - ` ON auth_method_types.user_id = projections.users11.id AND auth_method_types.instance_id = projections.users11.instance_id` + + ` ON auth_method_types.user_id = projections.users12.id AND auth_method_types.instance_id = projections.users12.instance_id` + ` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` + ` GROUP BY user_idps_count.user_id, user_idps_count.instance_id) AS user_idps_count` + - ` ON user_idps_count.user_id = projections.users11.id AND user_idps_count.instance_id = projections.users11.instance_id` + + ` ON user_idps_count.user_id = projections.users12.id AND user_idps_count.instance_id = projections.users12.instance_id` + ` LEFT JOIN (SELECT auth_methods_force_mfa.force_mfa, auth_methods_force_mfa.force_mfa_local_only, auth_methods_force_mfa.instance_id, auth_methods_force_mfa.aggregate_id FROM projections.login_policies5 AS auth_methods_force_mfa ORDER BY auth_methods_force_mfa.is_default) AS auth_methods_force_mfa` + - ` ON (auth_methods_force_mfa.aggregate_id = projections.users11.instance_id OR auth_methods_force_mfa.aggregate_id = projections.users11.resource_owner) AND auth_methods_force_mfa.instance_id = projections.users11.instance_id` + + ` ON (auth_methods_force_mfa.aggregate_id = projections.users12.instance_id OR auth_methods_force_mfa.aggregate_id = projections.users12.resource_owner) AND auth_methods_force_mfa.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms ` prepareAuthMethodTypesRequiredCols = []string{ diff --git a/internal/query/user_by_id.sql b/internal/query/user_by_id.sql index de96daab80..766464605d 100644 --- a/internal/query/user_by_id.sql +++ b/internal/query/user_by_id.sql @@ -65,14 +65,14 @@ SELECT , m.secret , m.access_token_type , count(*) OVER () -FROM projections.users11 u +FROM projections.users12 u LEFT JOIN - projections.users11_humans h + projections.users12_humans h ON u.id = h.user_id AND u.instance_id = h.instance_id LEFT JOIN - projections.users11_machines m + projections.users12_machines m ON u.id = m.user_id AND u.instance_id = m.instance_id diff --git a/internal/query/user_by_login_name.sql b/internal/query/user_by_login_name.sql index 08f46aa105..d600e50699 100644 --- a/internal/query/user_by_login_name.sql +++ b/internal/query/user_by_login_name.sql @@ -103,17 +103,17 @@ SELECT , count(*) OVER () FROM found_users fu JOIN - projections.users11 u + projections.users12 u ON fu.id = u.id AND fu.instance_id = u.instance_id LEFT JOIN - projections.users11_humans h + projections.users12_humans h ON fu.id = h.user_id AND fu.instance_id = h.instance_id LEFT JOIN - projections.users11_machines m + projections.users12_machines m ON fu.id = m.user_id AND fu.instance_id = m.instance_id diff --git a/internal/query/user_grant_test.go b/internal/query/user_grant_test.go index c1753c34af..2e5b1c3bf6 100644 --- a/internal/query/user_grant_test.go +++ b/internal/query/user_grant_test.go @@ -23,14 +23,14 @@ var ( ", projections.user_grants5.roles" + ", projections.user_grants5.state" + ", projections.user_grants5.user_id" + - ", projections.users11.username" + - ", projections.users11.type" + - ", projections.users11.resource_owner" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.display_name" + - ", projections.users11_humans.avatar_key" + + ", projections.users12.username" + + ", projections.users12.type" + + ", projections.users12.resource_owner" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.email" + + ", projections.users12_humans.display_name" + + ", projections.users12_humans.avatar_key" + ", projections.login_names3.login_name" + ", projections.user_grants5.resource_owner" + ", projections.orgs1.name" + @@ -41,11 +41,11 @@ var ( ", granted_orgs.name" + ", granted_orgs.primary_domain" + " FROM projections.user_grants5" + - " LEFT JOIN projections.users11 ON projections.user_grants5.user_id = projections.users11.id AND projections.user_grants5.instance_id = projections.users11.instance_id" + - " LEFT JOIN projections.users11_humans ON projections.user_grants5.user_id = projections.users11_humans.user_id AND projections.user_grants5.instance_id = projections.users11_humans.instance_id" + + " LEFT JOIN projections.users12 ON projections.user_grants5.user_id = projections.users12.id AND projections.user_grants5.instance_id = projections.users12.instance_id" + + " LEFT JOIN projections.users12_humans ON projections.user_grants5.user_id = projections.users12_humans.user_id AND projections.user_grants5.instance_id = projections.users12_humans.instance_id" + " LEFT JOIN projections.orgs1 ON projections.user_grants5.resource_owner = projections.orgs1.id AND projections.user_grants5.instance_id = projections.orgs1.instance_id" + " LEFT JOIN projections.projects4 ON projections.user_grants5.project_id = projections.projects4.id AND projections.user_grants5.instance_id = projections.projects4.instance_id" + - " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users11.resource_owner = granted_orgs.id AND projections.users11.instance_id = granted_orgs.instance_id" + + " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users12.resource_owner = granted_orgs.id AND projections.users12.instance_id = granted_orgs.instance_id" + " LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" + ` AS OF SYSTEM TIME '-1 ms' ` + " WHERE projections.login_names3.is_primary = $1") @@ -85,14 +85,14 @@ var ( ", projections.user_grants5.roles" + ", projections.user_grants5.state" + ", projections.user_grants5.user_id" + - ", projections.users11.username" + - ", projections.users11.type" + - ", projections.users11.resource_owner" + - ", projections.users11_humans.first_name" + - ", projections.users11_humans.last_name" + - ", projections.users11_humans.email" + - ", projections.users11_humans.display_name" + - ", projections.users11_humans.avatar_key" + + ", projections.users12.username" + + ", projections.users12.type" + + ", projections.users12.resource_owner" + + ", projections.users12_humans.first_name" + + ", projections.users12_humans.last_name" + + ", projections.users12_humans.email" + + ", projections.users12_humans.display_name" + + ", projections.users12_humans.avatar_key" + ", projections.login_names3.login_name" + ", projections.user_grants5.resource_owner" + ", projections.orgs1.name" + @@ -104,11 +104,11 @@ var ( ", granted_orgs.primary_domain" + ", COUNT(*) OVER ()" + " FROM projections.user_grants5" + - " LEFT JOIN projections.users11 ON projections.user_grants5.user_id = projections.users11.id AND projections.user_grants5.instance_id = projections.users11.instance_id" + - " LEFT JOIN projections.users11_humans ON projections.user_grants5.user_id = projections.users11_humans.user_id AND projections.user_grants5.instance_id = projections.users11_humans.instance_id" + + " LEFT JOIN projections.users12 ON projections.user_grants5.user_id = projections.users12.id AND projections.user_grants5.instance_id = projections.users12.instance_id" + + " LEFT JOIN projections.users12_humans ON projections.user_grants5.user_id = projections.users12_humans.user_id AND projections.user_grants5.instance_id = projections.users12_humans.instance_id" + " LEFT JOIN projections.orgs1 ON projections.user_grants5.resource_owner = projections.orgs1.id AND projections.user_grants5.instance_id = projections.orgs1.instance_id" + " LEFT JOIN projections.projects4 ON projections.user_grants5.project_id = projections.projects4.id AND projections.user_grants5.instance_id = projections.projects4.instance_id" + - " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users11.resource_owner = granted_orgs.id AND projections.users11.instance_id = granted_orgs.instance_id" + + " LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users12.resource_owner = granted_orgs.id AND projections.users12.instance_id = granted_orgs.instance_id" + " LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" + ` AS OF SYSTEM TIME '-1 ms' ` + " WHERE projections.login_names3.is_primary = $1") diff --git a/internal/query/user_notify_by_id.sql b/internal/query/user_notify_by_id.sql index 84c4b93e0b..94134c2b9f 100644 --- a/internal/query/user_notify_by_id.sql +++ b/internal/query/user_notify_by_id.sql @@ -62,14 +62,14 @@ SELECT , n.verified_phone , n.password_set , count(*) OVER () -FROM projections.users11 u +FROM projections.users12 u LEFT JOIN - projections.users11_humans h + projections.users12_humans h ON u.id = h.user_id AND u.instance_id = h.instance_id LEFT JOIN - projections.users11_notifications n + projections.users12_notifications n ON u.id = n.user_id AND u.instance_id = n.instance_id diff --git a/internal/query/user_notify_by_login_name.sql b/internal/query/user_notify_by_login_name.sql index 34e699ee8a..3fd17dae52 100644 --- a/internal/query/user_notify_by_login_name.sql +++ b/internal/query/user_notify_by_login_name.sql @@ -99,17 +99,17 @@ SELECT , count(*) OVER () FROM found_users fu JOIN - projections.users11 u + projections.users12 u ON fu.id = u.id AND fu.instance_id = u.instance_id LEFT JOIN - projections.users11_humans h + projections.users12_humans h ON fu.id = h.user_id AND fu.instance_id = h.instance_id LEFT JOIN - projections.users11_notifications n + projections.users12_notifications n ON fu.id = n.user_id AND fu.instance_id = n.instance_id diff --git a/internal/query/user_password.go b/internal/query/user_password.go index aa0bc60d2c..ed77d0d3ae 100644 --- a/internal/query/user_password.go +++ b/internal/query/user_password.go @@ -72,11 +72,11 @@ func (wm *HumanPasswordReadModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { case *user.HumanAddedEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.UserState = domain.UserStateActive case *user.HumanRegisteredEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.UserState = domain.UserStateActive case *user.HumanInitialCodeAddedEvent: @@ -84,7 +84,7 @@ func (wm *HumanPasswordReadModel) Reduce() error { case *user.HumanInitializedCheckSucceededEvent: wm.UserState = domain.UserStateActive case *user.HumanPasswordChangedEvent: - wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) + wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.SecretChangeRequired = e.ChangeRequired wm.Code = nil wm.PasswordCheckFailedCount = 0 diff --git a/internal/query/user_test.go b/internal/query/user_test.go index 0119fe1c13..55dd1f1b69 100644 --- a/internal/query/user_test.go +++ b/internal/query/user_test.go @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/text/language" - "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" @@ -148,44 +147,44 @@ var ( preferredLoginNameQuery = `SELECT preferred_login_name.user_id, preferred_login_name.login_name, preferred_login_name.instance_id` + ` FROM projections.login_names3 AS preferred_login_name` + ` WHERE preferred_login_name.is_primary = $1` - userQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11.state,` + - ` projections.users11.type,` + - ` projections.users11.username,` + + userQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12.state,` + + ` projections.users12.type,` + + ` projections.users12.username,` + ` login_names.loginnames,` + ` preferred_login_name.login_name,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.first_name,` + - ` projections.users11_humans.last_name,` + - ` projections.users11_humans.nick_name,` + - ` projections.users11_humans.display_name,` + - ` projections.users11_humans.preferred_language,` + - ` projections.users11_humans.gender,` + - ` projections.users11_humans.avatar_key,` + - ` projections.users11_humans.email,` + - ` projections.users11_humans.is_email_verified,` + - ` projections.users11_humans.phone,` + - ` projections.users11_humans.is_phone_verified,` + - ` projections.users11_humans.password_change_required,` + - ` projections.users11_machines.user_id,` + - ` projections.users11_machines.name,` + - ` projections.users11_machines.description,` + - ` projections.users11_machines.secret,` + - ` projections.users11_machines.access_token_type,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.first_name,` + + ` projections.users12_humans.last_name,` + + ` projections.users12_humans.nick_name,` + + ` projections.users12_humans.display_name,` + + ` projections.users12_humans.preferred_language,` + + ` projections.users12_humans.gender,` + + ` projections.users12_humans.avatar_key,` + + ` projections.users12_humans.email,` + + ` projections.users12_humans.is_email_verified,` + + ` projections.users12_humans.phone,` + + ` projections.users12_humans.is_phone_verified,` + + ` projections.users12_humans.password_change_required,` + + ` projections.users12_machines.user_id,` + + ` projections.users12_machines.name,` + + ` projections.users12_machines.description,` + + ` projections.users12_machines.secret,` + + ` projections.users12_machines.access_token_type,` + ` COUNT(*) OVER ()` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11_machines ON projections.users11.id = projections.users11_machines.user_id AND projections.users11.instance_id = projections.users11_machines.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12_machines ON projections.users12.id = projections.users12_machines.user_id AND projections.users12.instance_id = projections.users12_machines.instance_id` + ` LEFT JOIN` + ` (` + loginNamesQuery + `) AS login_names` + - ` ON login_names.user_id = projections.users11.id AND login_names.instance_id = projections.users11.instance_id` + + ` ON login_names.user_id = projections.users12.id AND login_names.instance_id = projections.users12.instance_id` + ` LEFT JOIN` + ` (` + preferredLoginNameQuery + `) AS preferred_login_name` + - ` ON preferred_login_name.user_id = projections.users11.id AND preferred_login_name.instance_id = projections.users11.instance_id` + + ` ON preferred_login_name.user_id = projections.users12.id AND preferred_login_name.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` userCols = []string{ "id", @@ -220,21 +219,21 @@ var ( "access_token_type", "count", } - profileQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.first_name,` + - ` projections.users11_humans.last_name,` + - ` projections.users11_humans.nick_name,` + - ` projections.users11_humans.display_name,` + - ` projections.users11_humans.preferred_language,` + - ` projections.users11_humans.gender,` + - ` projections.users11_humans.avatar_key` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + + profileQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.first_name,` + + ` projections.users12_humans.last_name,` + + ` projections.users12_humans.nick_name,` + + ` projections.users12_humans.display_name,` + + ` projections.users12_humans.preferred_language,` + + ` projections.users12_humans.gender,` + + ` projections.users12_humans.avatar_key` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` profileCols = []string{ "id", @@ -251,16 +250,16 @@ var ( "gender", "avatar_key", } - emailQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.email,` + - ` projections.users11_humans.is_email_verified` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + + emailQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.email,` + + ` projections.users12_humans.is_email_verified` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` emailCols = []string{ "id", @@ -272,16 +271,16 @@ var ( "email", "is_email_verified", } - phoneQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.phone,` + - ` projections.users11_humans.is_phone_verified` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + + phoneQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.phone,` + + ` projections.users12_humans.is_phone_verified` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` phoneCols = []string{ "id", @@ -293,14 +292,14 @@ var ( "phone", "is_phone_verified", } - userUniqueQuery = `SELECT projections.users11.id,` + - ` projections.users11.state,` + - ` projections.users11.username,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.email,` + - ` projections.users11_humans.is_email_verified` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + + userUniqueQuery = `SELECT projections.users12.id,` + + ` projections.users12.state,` + + ` projections.users12.username,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.email,` + + ` projections.users12_humans.is_email_verified` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` userUniqueCols = []string{ "id", @@ -310,40 +309,40 @@ var ( "email", "is_email_verified", } - notifyUserQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11.state,` + - ` projections.users11.type,` + - ` projections.users11.username,` + + notifyUserQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12.state,` + + ` projections.users12.type,` + + ` projections.users12.username,` + ` login_names.loginnames,` + ` preferred_login_name.login_name,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.first_name,` + - ` projections.users11_humans.last_name,` + - ` projections.users11_humans.nick_name,` + - ` projections.users11_humans.display_name,` + - ` projections.users11_humans.preferred_language,` + - ` projections.users11_humans.gender,` + - ` projections.users11_humans.avatar_key,` + - ` projections.users11_notifications.user_id,` + - ` projections.users11_notifications.last_email,` + - ` projections.users11_notifications.verified_email,` + - ` projections.users11_notifications.last_phone,` + - ` projections.users11_notifications.verified_phone,` + - ` projections.users11_notifications.password_set,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.first_name,` + + ` projections.users12_humans.last_name,` + + ` projections.users12_humans.nick_name,` + + ` projections.users12_humans.display_name,` + + ` projections.users12_humans.preferred_language,` + + ` projections.users12_humans.gender,` + + ` projections.users12_humans.avatar_key,` + + ` projections.users12_notifications.user_id,` + + ` projections.users12_notifications.last_email,` + + ` projections.users12_notifications.verified_email,` + + ` projections.users12_notifications.last_phone,` + + ` projections.users12_notifications.verified_phone,` + + ` projections.users12_notifications.password_set,` + ` COUNT(*) OVER ()` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11_notifications ON projections.users11.id = projections.users11_notifications.user_id AND projections.users11.instance_id = projections.users11_notifications.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12_notifications ON projections.users12.id = projections.users12_notifications.user_id AND projections.users12.instance_id = projections.users12_notifications.instance_id` + ` LEFT JOIN` + ` (` + loginNamesQuery + `) AS login_names` + - ` ON login_names.user_id = projections.users11.id AND login_names.instance_id = projections.users11.instance_id` + + ` ON login_names.user_id = projections.users12.id AND login_names.instance_id = projections.users12.instance_id` + ` LEFT JOIN` + ` (` + preferredLoginNameQuery + `) AS preferred_login_name` + - ` ON preferred_login_name.user_id = projections.users11.id AND preferred_login_name.instance_id = projections.users11.instance_id` + + ` ON preferred_login_name.user_id = projections.users12.id AND preferred_login_name.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` notifyUserCols = []string{ "id", @@ -374,44 +373,44 @@ var ( "password_set", "count", } - usersQuery = `SELECT projections.users11.id,` + - ` projections.users11.creation_date,` + - ` projections.users11.change_date,` + - ` projections.users11.resource_owner,` + - ` projections.users11.sequence,` + - ` projections.users11.state,` + - ` projections.users11.type,` + - ` projections.users11.username,` + + usersQuery = `SELECT projections.users12.id,` + + ` projections.users12.creation_date,` + + ` projections.users12.change_date,` + + ` projections.users12.resource_owner,` + + ` projections.users12.sequence,` + + ` projections.users12.state,` + + ` projections.users12.type,` + + ` projections.users12.username,` + ` login_names.loginnames,` + ` preferred_login_name.login_name,` + - ` projections.users11_humans.user_id,` + - ` projections.users11_humans.first_name,` + - ` projections.users11_humans.last_name,` + - ` projections.users11_humans.nick_name,` + - ` projections.users11_humans.display_name,` + - ` projections.users11_humans.preferred_language,` + - ` projections.users11_humans.gender,` + - ` projections.users11_humans.avatar_key,` + - ` projections.users11_humans.email,` + - ` projections.users11_humans.is_email_verified,` + - ` projections.users11_humans.phone,` + - ` projections.users11_humans.is_phone_verified,` + - ` projections.users11_humans.password_change_required,` + - ` projections.users11_machines.user_id,` + - ` projections.users11_machines.name,` + - ` projections.users11_machines.description,` + - ` projections.users11_machines.secret,` + - ` projections.users11_machines.access_token_type,` + + ` projections.users12_humans.user_id,` + + ` projections.users12_humans.first_name,` + + ` projections.users12_humans.last_name,` + + ` projections.users12_humans.nick_name,` + + ` projections.users12_humans.display_name,` + + ` projections.users12_humans.preferred_language,` + + ` projections.users12_humans.gender,` + + ` projections.users12_humans.avatar_key,` + + ` projections.users12_humans.email,` + + ` projections.users12_humans.is_email_verified,` + + ` projections.users12_humans.phone,` + + ` projections.users12_humans.is_phone_verified,` + + ` projections.users12_humans.password_change_required,` + + ` projections.users12_machines.user_id,` + + ` projections.users12_machines.name,` + + ` projections.users12_machines.description,` + + ` projections.users12_machines.secret,` + + ` projections.users12_machines.access_token_type,` + ` COUNT(*) OVER ()` + - ` FROM projections.users11` + - ` LEFT JOIN projections.users11_humans ON projections.users11.id = projections.users11_humans.user_id AND projections.users11.instance_id = projections.users11_humans.instance_id` + - ` LEFT JOIN projections.users11_machines ON projections.users11.id = projections.users11_machines.user_id AND projections.users11.instance_id = projections.users11_machines.instance_id` + + ` FROM projections.users12` + + ` LEFT JOIN projections.users12_humans ON projections.users12.id = projections.users12_humans.user_id AND projections.users12.instance_id = projections.users12_humans.instance_id` + + ` LEFT JOIN projections.users12_machines ON projections.users12.id = projections.users12_machines.user_id AND projections.users12.instance_id = projections.users12_machines.instance_id` + ` LEFT JOIN` + ` (` + loginNamesQuery + `) AS login_names` + - ` ON login_names.user_id = projections.users11.id AND login_names.instance_id = projections.users11.instance_id` + + ` ON login_names.user_id = projections.users12.id AND login_names.instance_id = projections.users12.instance_id` + ` LEFT JOIN` + ` (` + preferredLoginNameQuery + `) AS preferred_login_name` + - ` ON preferred_login_name.user_id = projections.users11.id AND preferred_login_name.instance_id = projections.users11.instance_id` + + ` ON preferred_login_name.user_id = projections.users12.id AND preferred_login_name.instance_id = projections.users12.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` usersCols = []string{ "id", @@ -602,7 +601,7 @@ func Test_UserPrepares(t *testing.T) { Machine: &Machine{ Name: "name", Description: "description", - Secret: nil, + EncodedSecret: "", AccessTokenType: domain.OIDCTokenTypeBearer, }, }, @@ -643,7 +642,7 @@ func Test_UserPrepares(t *testing.T) { "id", "name", "description", - `{"CryptoType":1,"Algorithm":"bcrypt","Crypted":"deadbeef"}`, + "secret", domain.OIDCTokenTypeBearer, 1, }, @@ -661,13 +660,9 @@ func Test_UserPrepares(t *testing.T) { LoginNames: database.TextArray[string]{"login_name1", "login_name2"}, PreferredLoginName: "login_name1", Machine: &Machine{ - Name: "name", - Description: "description", - Secret: &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "bcrypt", - Crypted: []byte{117, 230, 157, 109, 231, 159}, - }, + Name: "name", + Description: "description", + EncodedSecret: "secret", AccessTokenType: domain.OIDCTokenTypeBearer, }, }, @@ -1344,7 +1339,7 @@ func Test_UserPrepares(t *testing.T) { "id", "name", "description", - `{"CryptoType":1,"Algorithm":"bcrypt","Crypted":"deadbeef"}`, + "secret", domain.OIDCTokenTypeBearer, }, }, @@ -1393,13 +1388,9 @@ func Test_UserPrepares(t *testing.T) { LoginNames: database.TextArray[string]{"login_name1", "login_name2"}, PreferredLoginName: "login_name1", Machine: &Machine{ - Name: "name", - Description: "description", - Secret: &crypto.CryptoValue{ - CryptoType: crypto.TypeHash, - Algorithm: "bcrypt", - Crypted: []byte{117, 230, 157, 109, 231, 159}, - }, + Name: "name", + Description: "description", + EncodedSecret: "secret", AccessTokenType: domain.OIDCTokenTypeBearer, }, }, diff --git a/internal/repository/project/api_config.go b/internal/repository/project/api_config.go index 7da3103125..eac410847b 100644 --- a/internal/repository/project/api_config.go +++ b/internal/repository/project/api_config.go @@ -15,14 +15,20 @@ const ( APIConfigSecretChangedType = applicationEventTypePrefix + "config.api.secret.changed" APIClientSecretCheckSucceededType = applicationEventTypePrefix + "api.secret.check.succeeded" APIClientSecretCheckFailedType = applicationEventTypePrefix + "api.secret.check.failed" + APIConfigSecretHashUpdatedType = applicationEventTypePrefix + "config.api.secret.updated" ) type APIConfigAddedEvent struct { eventstore.BaseEvent `json:"-"` - AppID string `json:"appId"` - ClientID string `json:"clientId,omitempty"` - ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + AppID string `json:"appId"` + ClientID string `json:"clientId,omitempty"` + + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. + ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` + AuthMethodType domain.APIAuthMethodType `json:"authMethodType,omitempty"` } @@ -39,7 +45,7 @@ func NewAPIConfigAddedEvent( aggregate *eventstore.Aggregate, appID, clientID string, - clientSecret *crypto.CryptoValue, + hashedSecret string, authMethodType domain.APIAuthMethodType, ) *APIConfigAddedEvent { return &APIConfigAddedEvent{ @@ -50,7 +56,7 @@ func NewAPIConfigAddedEvent( ), AppID: appID, ClientID: clientID, - ClientSecret: clientSecret, + HashedSecret: hashedSecret, AuthMethodType: authMethodType, } } @@ -91,7 +97,6 @@ type APIConfigChangedEvent struct { eventstore.BaseEvent `json:"-"` AppID string `json:"appId"` - ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` AuthMethodType *domain.APIAuthMethodType `json:"authMethodType,omitempty"` } @@ -151,8 +156,12 @@ func APIConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, erro type APIConfigSecretChangedEvent struct { eventstore.BaseEvent `json:"-"` - AppID string `json:"appId"` + AppID string `json:"appId"` + + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` } func (e *APIConfigSecretChangedEvent) Payload() interface{} { @@ -167,7 +176,7 @@ func NewAPIConfigSecretChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, appID string, - clientSecret *crypto.CryptoValue, + hashedSecret string, ) *APIConfigSecretChangedEvent { return &APIConfigSecretChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -176,7 +185,7 @@ func NewAPIConfigSecretChangedEvent( APIConfigSecretChangedType, ), AppID: appID, - ClientSecret: clientSecret, + HashedSecret: hashedSecret, } } @@ -276,3 +285,39 @@ func APIConfigSecretCheckFailedEventMapper(event eventstore.Event) (eventstore.E return e, nil } + +type APIConfigSecretHashUpdatedEvent struct { + *eventstore.BaseEvent `json:"-"` + + AppID string `json:"appId"` + HashedSecret string `json:"hashedSecret,omitempty"` +} + +func NewAPIConfigSecretHashUpdatedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + appID string, + hashedSecret string, +) *APIConfigSecretHashUpdatedEvent { + return &APIConfigSecretHashUpdatedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + APIConfigSecretHashUpdatedType, + ), + AppID: appID, + HashedSecret: hashedSecret, + } +} + +func (e *APIConfigSecretHashUpdatedEvent) SetBaseEvent(b *eventstore.BaseEvent) { + e.BaseEvent = b +} + +func (e *APIConfigSecretHashUpdatedEvent) Payload() interface{} { + return e +} + +func (e *APIConfigSecretHashUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} diff --git a/internal/repository/project/eventstore.go b/internal/repository/project/eventstore.go index 6e9bccbd3f..fe8e14a508 100644 --- a/internal/repository/project/eventstore.go +++ b/internal/repository/project/eventstore.go @@ -37,9 +37,11 @@ func init() { eventstore.RegisterFilterEventMapper(AggregateType, OIDCConfigSecretChangedType, OIDCConfigSecretChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, OIDCClientSecretCheckSucceededType, OIDCConfigSecretCheckSucceededEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, OIDCClientSecretCheckFailedType, OIDCConfigSecretCheckFailedEventMapper) + eventstore.RegisterFilterEventMapper(AggregateType, OIDCConfigSecretHashUpdatedType, eventstore.GenericEventMapper[OIDCConfigSecretHashUpdatedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, APIConfigAddedType, APIConfigAddedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, APIConfigChangedType, APIConfigChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, APIConfigSecretChangedType, APIConfigSecretChangedEventMapper) + eventstore.RegisterFilterEventMapper(AggregateType, APIConfigSecretHashUpdatedType, eventstore.GenericEventMapper[APIConfigSecretHashUpdatedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, ApplicationKeyAddedEventType, ApplicationKeyAddedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, ApplicationKeyRemovedEventType, ApplicationKeyRemovedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SAMLConfigAddedType, SAMLConfigAddedEventMapper) diff --git a/internal/repository/project/oidc_config.go b/internal/repository/project/oidc_config.go index 5534bd571e..68f2a59949 100644 --- a/internal/repository/project/oidc_config.go +++ b/internal/repository/project/oidc_config.go @@ -16,15 +16,21 @@ const ( OIDCConfigSecretChangedType = applicationEventTypePrefix + "config.oidc.secret.changed" OIDCClientSecretCheckSucceededType = applicationEventTypePrefix + "oidc.secret.check.succeeded" OIDCClientSecretCheckFailedType = applicationEventTypePrefix + "oidc.secret.check.failed" + OIDCConfigSecretHashUpdatedType = applicationEventTypePrefix + "config.oidc.secret.updated" ) type OIDCConfigAddedEvent struct { eventstore.BaseEvent `json:"-"` - Version domain.OIDCVersion `json:"oidcVersion,omitempty"` - AppID string `json:"appId"` - ClientID string `json:"clientId,omitempty"` - ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + Version domain.OIDCVersion `json:"oidcVersion,omitempty"` + AppID string `json:"appId"` + ClientID string `json:"clientId,omitempty"` + + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. + ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` + RedirectUris []string `json:"redirectUris,omitempty"` ResponseTypes []domain.OIDCResponseType `json:"responseTypes,omitempty"` GrantTypes []domain.OIDCGrantType `json:"grantTypes,omitempty"` @@ -55,7 +61,7 @@ func NewOIDCConfigAddedEvent( version domain.OIDCVersion, appID string, clientID string, - clientSecret *crypto.CryptoValue, + hashedSecret string, redirectUris []string, responseTypes []domain.OIDCResponseType, grantTypes []domain.OIDCGrantType, @@ -80,7 +86,7 @@ func NewOIDCConfigAddedEvent( Version: version, AppID: appID, ClientID: clientID, - ClientSecret: clientSecret, + HashedSecret: hashedSecret, RedirectUris: redirectUris, ResponseTypes: responseTypes, GrantTypes: grantTypes, @@ -357,8 +363,12 @@ func OIDCConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, err type OIDCConfigSecretChangedEvent struct { eventstore.BaseEvent `json:"-"` - AppID string `json:"appId"` + AppID string `json:"appId"` + + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` } func (e *OIDCConfigSecretChangedEvent) Payload() interface{} { @@ -373,7 +383,7 @@ func NewOIDCConfigSecretChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, appID string, - clientSecret *crypto.CryptoValue, + hashedSecret string, ) *OIDCConfigSecretChangedEvent { return &OIDCConfigSecretChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -382,7 +392,7 @@ func NewOIDCConfigSecretChangedEvent( OIDCConfigSecretChangedType, ), AppID: appID, - ClientSecret: clientSecret, + HashedSecret: hashedSecret, } } @@ -482,3 +492,39 @@ func OIDCConfigSecretCheckFailedEventMapper(event eventstore.Event) (eventstore. return e, nil } + +type OIDCConfigSecretHashUpdatedEvent struct { + *eventstore.BaseEvent `json:"-"` + + AppID string `json:"appId"` + HashedSecret string `json:"hashedSecret,omitempty"` +} + +func NewOIDCConfigSecretHashUpdatedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + appID string, + hashedSecret string, +) *OIDCConfigSecretHashUpdatedEvent { + return &OIDCConfigSecretHashUpdatedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + OIDCConfigSecretHashUpdatedType, + ), + AppID: appID, + HashedSecret: hashedSecret, + } +} + +func (e *OIDCConfigSecretHashUpdatedEvent) SetBaseEvent(b *eventstore.BaseEvent) { + e.BaseEvent = b +} + +func (e *OIDCConfigSecretHashUpdatedEvent) Payload() interface{} { + return e +} + +func (e *OIDCConfigSecretHashUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} diff --git a/internal/repository/user/eventstore.go b/internal/repository/user/eventstore.go index d9e88c427d..02df90f369 100644 --- a/internal/repository/user/eventstore.go +++ b/internal/repository/user/eventstore.go @@ -135,4 +135,5 @@ func init() { eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretRemovedType, MachineSecretRemovedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretCheckSucceededType, MachineSecretCheckSucceededEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretCheckFailedType, MachineSecretCheckFailedEventMapper) + eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretHashUpdatedType, eventstore.GenericEventMapper[MachineSecretHashUpdatedEvent]) } diff --git a/internal/repository/user/human_password.go b/internal/repository/user/human_password.go index 9df7e6c7fd..e468cc8324 100644 --- a/internal/repository/user/human_password.go +++ b/internal/repository/user/human_password.go @@ -314,12 +314,3 @@ func NewHumanPasswordHashUpdatedEvent( EncodedHash: encoded, } } - -// SecretOrEncodedHash returns the legacy *crypto.CryptoValue if it is not nil. -// orherwise it will returns the encoded hash string. -func SecretOrEncodedHash(secret *crypto.CryptoValue, encoded string) string { - if secret != nil { - return string(secret.Crypted) - } - return encoded -} diff --git a/internal/repository/user/machine_secret.go b/internal/repository/user/machine_secret.go index 9d2f9fa97f..3231eacd29 100644 --- a/internal/repository/user/machine_secret.go +++ b/internal/repository/user/machine_secret.go @@ -11,6 +11,7 @@ import ( const ( machineSecretPrefix = machineEventPrefix + "secret." MachineSecretSetType = machineSecretPrefix + "set" + MachineSecretHashUpdatedType = machineSecretPrefix + "updated" MachineSecretRemovedType = machineSecretPrefix + "removed" MachineSecretCheckSucceededType = machineSecretPrefix + "check.succeeded" MachineSecretCheckFailedType = machineSecretPrefix + "check.failed" @@ -19,7 +20,10 @@ const ( type MachineSecretSetEvent struct { eventstore.BaseEvent `json:"-"` + // New events only use EncodedHash. However, the ClientSecret field + // is preserved to handle events older than the switch to Passwap. ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + HashedSecret string `json:"hashedSecret,omitempty"` } func (e *MachineSecretSetEvent) Payload() interface{} { @@ -33,7 +37,7 @@ func (e *MachineSecretSetEvent) UniqueConstraints() []*eventstore.UniqueConstrai func NewMachineSecretSetEvent( ctx context.Context, aggregate *eventstore.Aggregate, - clientSecret *crypto.CryptoValue, + hashedSecret string, ) *MachineSecretSetEvent { return &MachineSecretSetEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -41,7 +45,7 @@ func NewMachineSecretSetEvent( aggregate, MachineSecretSetType, ), - ClientSecret: clientSecret, + HashedSecret: hashedSecret, } } @@ -167,3 +171,35 @@ func MachineSecretCheckFailedEventMapper(event eventstore.Event) (eventstore.Eve return check, nil } + +type MachineSecretHashUpdatedEvent struct { + *eventstore.BaseEvent `json:"-"` + HashedSecret string `json:"hashedSecret,omitempty"` +} + +func NewMachineSecretHashUpdatedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + encoded string, +) *MachineSecretHashUpdatedEvent { + return &MachineSecretHashUpdatedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + MachineSecretHashUpdatedType, + ), + HashedSecret: encoded, + } +} + +func (e *MachineSecretHashUpdatedEvent) SetBaseEvent(b *eventstore.BaseEvent) { + e.BaseEvent = b +} + +func (e *MachineSecretHashUpdatedEvent) Payload() interface{} { + return e +} + +func (e *MachineSecretHashUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} diff --git a/internal/static/i18n/bg.yaml b/internal/static/i18n/bg.yaml index b8ae47069d..61c290cf27 100644 --- a/internal/static/i18n/bg.yaml +++ b/internal/static/i18n/bg.yaml @@ -643,6 +643,7 @@ EventTypes: removed: Ключът е премахнат secret: set: Секретен комплект + updated: Тайният хеш е актуализиран removed: Тайната е премахната check: succeeded: Тайната проверка е успешна @@ -1019,6 +1020,11 @@ EventTypes: check: succeeded: Проверката на OIDC Client Secret е успешна failed: Проверката на OIDC Client Secret е неуспешна + api: + secret: + check: + succeeded: Тайната проверка на API е успешна + failed: Тайната проверка на API е неуспешна key: added: Добавен ключ за приложение removed: Ключът на приложението е премахнат @@ -1031,11 +1037,13 @@ EventTypes: changed: Конфигурацията на OIDC е променена secret: changed: Тайната на OIDC е променена + updated: Тайният хеш на OIDC е актуализиран api: added: Добавена е конфигурация на API changed: Променена конфигурация на API secret: changed: Тайната на API е променена + updated: Тайният хеш на API е актуализиран policy: password: complexity: diff --git a/internal/static/i18n/cs.yaml b/internal/static/i18n/cs.yaml index 6046783716..fcc448ea90 100644 --- a/internal/static/i18n/cs.yaml +++ b/internal/static/i18n/cs.yaml @@ -624,6 +624,7 @@ EventTypes: removed: Klíč odstraněn secret: set: Tajemství nastaveno + updated: Tajný hash aktualizován removed: Tajemství odstraněno check: succeeded: Kontrola tajemství byla úspěšná @@ -998,6 +999,11 @@ EventTypes: check: succeeded: Kontrola tajného klíče OIDC klienta byla úspěšná failed: Kontrola tajného klíče OIDC klienta selhala + api: + secret: + check: + succeeded: Tajná kontrola API byla úspěšná + failed: Tajná kontrola API se nezdařila key: added: Klíč aplikace přidán removed: Klíč aplikace odstraněn @@ -1010,11 +1016,13 @@ EventTypes: changed: Konfigurace OIDC změněna secret: changed: Tajný klíč OIDC změněn + updated: Tajný hash OIDC byl aktualizován api: added: Konfigurace API přidána changed: Konfigurace API změněna secret: changed: Tajný klíč API změněn + updated: Tajný hash API byl aktualizován policy: password: complexity: diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 09241c3f11..53b9b83db9 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -626,6 +626,7 @@ EventTypes: removed: Key entfernt secret: set: Secret gesetzt + updated: Geheimer Hash aktualisiert removed: Secret entfernt check: succeeded: Secret Überprüfung erfolgreich @@ -1000,6 +1001,11 @@ EventTypes: check: succeeded: OIDC Client Secret Validierung erfolgreich failed: OIDC Client Secret Validierung fehlgeschlagen + api: + secret: + check: + succeeded: Überprüfung des API-Geheimnisses erfolgreich + failed: Die Überprüfung des API-Geheimnisses ist fehlgeschlagen key: added: Applikations Schlüssel hinzugefügt removed: Applikations Schlüssel entfernt @@ -1012,11 +1018,13 @@ EventTypes: changed: OIDC Konfiguration geändert secret: changed: OIDC Client Secret geändert + updated: OIDC-Geheim-Hash aktualisiert api: added: API Konfiguration hinzugefügt changed: API Konfiguration geändert secret: changed: API Client Secret geändert + updated: API-Geheimnis-Hash aktualisiert policy: password: complexity: diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 36c9de9eb4..826e8f265b 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -626,6 +626,7 @@ EventTypes: removed: Key removed secret: set: Secret set + updated: Secret hash updated removed: Secret removed check: succeeded: Secret check succeeded @@ -1000,6 +1001,11 @@ EventTypes: check: succeeded: OIDC Client Secret check succeeded failed: OIDC Client Secret check failed + api: + secret: + check: + succeeded: API secret check succeeded + failed: API secret check failed key: added: Application key added removed: Application key removed @@ -1012,11 +1018,13 @@ EventTypes: changed: OIDC Configuration changed secret: changed: OIDC secret changed + updated: OIDC secret hash updated api: added: API Configuration added changed: API Configuration changed secret: changed: API secret changed + updated: API secret hash updated policy: password: complexity: diff --git a/internal/static/i18n/es.yaml b/internal/static/i18n/es.yaml index 15d29456f6..1d572a139b 100644 --- a/internal/static/i18n/es.yaml +++ b/internal/static/i18n/es.yaml @@ -626,6 +626,7 @@ EventTypes: removed: Clave eliminada secret: set: Secreto establecido + updated: Hash secreto actualizado removed: Secreto eliminado check: succeeded: Comprobación exitosa del secreto @@ -1000,6 +1001,11 @@ EventTypes: check: succeeded: Comprobación con éxito del secreto del cliente OIDC failed: Comprobación fallida del secreto del cliente OIDC + api: + secret: + check: + succeeded: La verificación del secreto de API fue exitosa + failed: Error en la comprobación del secreto de API key: added: Clave de aplicación añadida removed: Clave de aplicación eliminada @@ -1012,11 +1018,13 @@ EventTypes: changed: Configuracion OIDC modificada secret: changed: Secreto OIDC modificado + updated: Hash secreto OIDC actualizado api: added: Configuración API añadida changed: Configuración API modificada secret: changed: Configuración de secreto API modificada + updated: Hash secreto de API actualizado policy: password: complexity: diff --git a/internal/static/i18n/fr.yaml b/internal/static/i18n/fr.yaml index 5f029e9b81..1b90e1f194 100644 --- a/internal/static/i18n/fr.yaml +++ b/internal/static/i18n/fr.yaml @@ -624,6 +624,7 @@ EventTypes: removed: Clé supprimée secret: set: Secret défini + updated: Hachage secret mis à jour removed: Secret supprimée check: succeeded: La vérification de Secret réussie @@ -961,6 +962,11 @@ EventTypes: verified: check: Vérification du secret du client OIDC réussie failed: La vérification du secret du client OIDC a échoué + api: + secret: + check: + succeeded: La vérification du secret de l'API a réussi + failed: La vérification du secret de l'API a échoué key: added: Clé d'application ajoutée removed: Clé d'application supprimée @@ -973,11 +979,13 @@ EventTypes: changed: Modification de la configuration de l'OIDC secret: changed: Le secret de l'OIDC a été modifié + updated: Hachage secret OIDC mis à jour api: added: Configuration API ajoutée changed: La configuration de l'API a été modifiée secret: changed: Le secret de l'API a été modifié + updated: Hachage secret de l'API mis à jour policy: password: complexity: diff --git a/internal/static/i18n/it.yaml b/internal/static/i18n/it.yaml index e7d0ffabe3..9746ccfc03 100644 --- a/internal/static/i18n/it.yaml +++ b/internal/static/i18n/it.yaml @@ -625,6 +625,7 @@ EventTypes: removed: Chiave rimossa secret: set: Secret set + updated: Hash segreto aggiornato removed: Secret rimosso check: succeeded: Controllo della Secret riuscito @@ -965,6 +966,11 @@ EventTypes: key: added: Chiave di applicazione aggiunta removed: Chiave di applicazione rimossa + api: + secret: + check: + succeeded: Il controllo del segreto API è riuscito + failed: Il controllo del segreto API non è riuscito config: saml: added: Configurazione SAML aggiunta @@ -974,11 +980,13 @@ EventTypes: changed: Configurazione OIDC modificata secret: changed: Segreto OIDC cambiato + updated: Hash segreto OIDC aggiornato api: added: Configurazione API aggiunta changed: Configurazione API modificata secret: changed: Segreto API cambiato + updated: Hash segreto API aggiornato policy: password: complexity: diff --git a/internal/static/i18n/ja.yaml b/internal/static/i18n/ja.yaml index 8451041738..aa2faa4a26 100644 --- a/internal/static/i18n/ja.yaml +++ b/internal/static/i18n/ja.yaml @@ -615,6 +615,7 @@ EventTypes: removed: キーの削除 secret: set: シークレットのセット + updated: 秘密のハッシュが更新されました removed: シークレットの削除 check: succeeded: シークレットチェックの成功 @@ -989,6 +990,11 @@ EventTypes: check: succeeded: OIDCクライアントシークレットチェックの成功 failed: OIDCクライアントシークレットチェックの失敗 + api: + secret: + check: + succeeded: APIシークレットチェックに成功しました + failed: APIシークレットチェックに失敗しました key: added: アプリケーションキーの追加 removed: アプリケーションキーの削除 @@ -1001,11 +1007,13 @@ EventTypes: changed: OIDC構成の変更 secret: changed: OIDCシークレットの変更 + updated: OIDC シークレットハッシュが更新されました api: added: API構成の追加 changed: API構成の変更 secret: changed: APIのシークレットの変更 + updated: API シークレット ハッシュが更新されました policy: password: complexity: diff --git a/internal/static/i18n/mk.yaml b/internal/static/i18n/mk.yaml index 3b6b5a2e3b..d14f06cc53 100644 --- a/internal/static/i18n/mk.yaml +++ b/internal/static/i18n/mk.yaml @@ -625,6 +625,7 @@ EventTypes: removed: Клучот е отстранет secret: set: Тајната е додадена + updated: Тајниот хаш е ажуриран removed: Тајната е отстрането check: succeeded: Проверката на тајната е успешна @@ -999,6 +1000,11 @@ EventTypes: check: succeeded: Проверката на OIDC клиентска тајна е успешна failed: Проверката на OIDC клиентска тајна е неуспешна + api: + secret: + check: + succeeded: Проверката на тајната на API беше успешна + failed: Проверката на тајната на API не успеа key: added: Додаден клуч за апликацијата removed: Отстранет клуч за апликацијата @@ -1011,11 +1017,13 @@ EventTypes: changed: Променета OIDC конфигурација secret: changed: Променета OIDC тајна + updated: Тајниот хаш на OIDC е ажуриран api: added: Додадена API конфигурација changed: Променета API конфигурација secret: changed: Променета API тајна + updated: Тајниот хаш на API е ажуриран policy: password: complexity: diff --git a/internal/static/i18n/nl.yaml b/internal/static/i18n/nl.yaml index 0c64a13c0b..be7265fc39 100644 --- a/internal/static/i18n/nl.yaml +++ b/internal/static/i18n/nl.yaml @@ -626,6 +626,7 @@ EventTypes: removed: Sleutel verwijderd secret: set: Geheim ingesteld + updated: Geheime hash bijgewerkt removed: Geheim verwijderd check: succeeded: Geheimcontrole geslaagd @@ -1000,6 +1001,11 @@ EventTypes: check: succeeded: OIDC Client Secret controle geslaagd failed: OIDC Client Secret controle mislukt + api: + secret: + check: + succeeded: API-geheimcontrole geslaagd + failed: API-geheimcontrole mislukt key: added: Applicatiesleutel toegevoegd removed: Applicatiesleutel verwijderd @@ -1012,11 +1018,13 @@ EventTypes: changed: OIDC Configuratie gewijzigd secret: changed: OIDC geheim gewijzigd + updated: OIDC geheime hash bijgewerkt api: added: API Configuratie toegevoegd changed: API Configuratie gewijzigd secret: changed: API geheim gewijzigd + updated: API-geheime hash bijgewerkt policy: password: complexity: diff --git a/internal/static/i18n/pl.yaml b/internal/static/i18n/pl.yaml index 38381a2c3a..dea7e173f9 100644 --- a/internal/static/i18n/pl.yaml +++ b/internal/static/i18n/pl.yaml @@ -626,6 +626,7 @@ EventTypes: removed: Usunięto klucz secret: set: Sekret ustawiony + updated: Zaktualizowano tajny skrót removed: Sekret usunięty check: succeeded: Sprawdzenie sekretu powiodło się @@ -1000,6 +1001,11 @@ EventTypes: check: succeeded: Sprawdzenie sekretu OIDC Klienta powiodło się failed: Sprawdzenie sekretu OIDC Klienta nie powiodło się + api: + secret: + check: + succeeded: Sprawdzenie tajnego interfejsu API powiodło się + failed: Sprawdzanie tajnego interfejsu API nie powiodło się key: added: Dodano klucz aplikacji removed: Usunięto klucz aplikacji @@ -1012,11 +1018,13 @@ EventTypes: changed: Zmieniono konfigurację OIDC secret: changed: Zmieniono sekret OIDC + updated: Zaktualizowano tajny skrót OIDC api: added: Dodano konfigurację API changed: Zmieniono konfigurację API secret: changed: Zmieniono sekret API + updated: Zaktualizowano tajny skrót API policy: password: complexity: diff --git a/internal/static/i18n/pt.yaml b/internal/static/i18n/pt.yaml index b7faeb7f11..c85c83b03c 100644 --- a/internal/static/i18n/pt.yaml +++ b/internal/static/i18n/pt.yaml @@ -620,6 +620,7 @@ EventTypes: removed: Chave removida secret: set: Segredo definido + updated: Hash secreto atualizado removed: Segredo removido check: succeeded: Verificação de segredo bem-sucedida @@ -994,6 +995,11 @@ EventTypes: check: succeeded: Verificação de segredo do cliente OIDC bem-sucedida failed: Verificação de segredo do cliente OIDC falhou + api: + secret: + check: + succeeded: Verificação secreta da API bem-sucedida + failed: Falha na verificação do segredo da API key: added: Chave do aplicativo adicionada removed: Chave do aplicativo removida @@ -1006,11 +1012,13 @@ EventTypes: changed: Configuração OIDC alterada secret: changed: Segredo OIDC alterado + updated: Hash secreto do OIDC atualizado api: added: Configuração de API adicionada changed: Configuração de API alterada secret: changed: Segredo da API alterado + updated: Hash secreto da API atualizado policy: password: complexity: diff --git a/internal/static/i18n/ru.yaml b/internal/static/i18n/ru.yaml index 0c0847d367..88acaea5e2 100644 --- a/internal/static/i18n/ru.yaml +++ b/internal/static/i18n/ru.yaml @@ -615,6 +615,7 @@ EventTypes: removed: Ключ удалён secret: set: Ключ установлен + updated: Секретный хеш обновлен. removed: Ключ удалён check: succeeded: Проверка ключа прошла успешно @@ -989,6 +990,11 @@ EventTypes: check: succeeded: Проверка клиентского ключа OIDC прошла успешно failed: Проверки клиентского ключа OIDC не удалась + api: + secret: + check: + succeeded: Проверка секретности API прошла успешно + failed: Проверка секретности API не удалась key: added: Ключ приложения добавлен removed: Ключ приложения удалён @@ -1001,11 +1007,13 @@ EventTypes: changed: Конфигурация OIDC изменена secret: changed: Ключ OIDC изменён + updated: Секретный хеш OIDC обновлен. api: added: Конфигурация API добавлена changed: Конфигурация API изменена secret: changed: Ключ API изменён + updated: Секретный хэш API обновлен. policy: password: complexity: diff --git a/internal/static/i18n/zh.yaml b/internal/static/i18n/zh.yaml index 2a92127a78..59ee22c205 100644 --- a/internal/static/i18n/zh.yaml +++ b/internal/static/i18n/zh.yaml @@ -624,6 +624,7 @@ EventTypes: removed: 删除服务用户 Key secret: set: 秘密套装 + updated: 秘密哈希已更新 removed: 秘密删除 check: succeeded: 成功的秘密控制 @@ -961,6 +962,11 @@ EventTypes: check: succeeded: 检查 OIDC Client Secret 成功 failed: 检查 OIDC Client Secret 失败 + api: + secret: + check: + succeeded: API密检成功 + failed: API 秘密检查失败 key: added: 添加应用 Key removed: 删除应用 Key @@ -973,11 +979,13 @@ EventTypes: changed: 更改 OIDC 配置 secret: changed: 更改 OIDC Secret + updated: OIDC 秘密哈希已更新 api: added: 添加 API 配置 changed: 更改 API 配置 secret: changed: 更改 API Secret + updated: API 秘密哈希已更新 policy: password: complexity: diff --git a/internal/user/repository/view/user_session_by_id.sql b/internal/user/repository/view/user_session_by_id.sql index 0f9d8c04e9..1a9f3c250e 100644 --- a/internal/user/repository/view/user_session_by_id.sql +++ b/internal/user/repository/view/user_session_by_id.sql @@ -19,8 +19,8 @@ SELECT s.creation_date, s.sequence, s.instance_id FROM auth.user_sessions s - LEFT JOIN projections.users11 u ON s.user_id = u.id AND s.instance_id = u.instance_id - LEFT JOIN projections.users11_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id + LEFT JOIN projections.users12 u ON s.user_id = u.id AND s.instance_id = u.instance_id + LEFT JOIN projections.users12_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id LEFT JOIN projections.login_names3 l ON s.user_id = l.user_id AND s.instance_id = l.instance_id AND l.is_primary = true WHERE (s.user_agent_id = $1) AND (s.user_id = $2) diff --git a/internal/user/repository/view/user_sessions_by_user_agent.sql b/internal/user/repository/view/user_sessions_by_user_agent.sql index 1adb11f44c..ac72f27e3c 100644 --- a/internal/user/repository/view/user_sessions_by_user_agent.sql +++ b/internal/user/repository/view/user_sessions_by_user_agent.sql @@ -19,8 +19,8 @@ SELECT s.creation_date, s.sequence, s.instance_id FROM auth.user_sessions s - LEFT JOIN projections.users11 u ON s.user_id = u.id AND s.instance_id = u.instance_id - LEFT JOIN projections.users11_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id + LEFT JOIN projections.users12 u ON s.user_id = u.id AND s.instance_id = u.instance_id + LEFT JOIN projections.users12_humans h ON s.user_id = h.user_id AND s.instance_id = h.instance_id LEFT JOIN projections.login_names3 l ON s.user_id = l.user_id AND s.instance_id = l.instance_id AND l.is_primary = true WHERE (s.user_agent_id = $1) AND (s.instance_id = $2)