feat(crypto): use passwap for machine and app secrets (#7657)

* feat(crypto): use passwap for machine and app secrets

* fix command package tests

* add hash generator command test

* naming convention, fix query tests

* rename PasswordHasher and cleanup start commands

* add reducer tests

* fix intergration tests, cleanup old config

* add app secret unit tests

* solve setup panics

* fix push of updated events

* add missing event translations

* update documentation

* solve linter errors

* remove nolint:SA1019 as it doesn't seem to help anyway

* add nolint to deprecated filter usage

* update users migration version

* remove unused ClientSecret from APIConfigChangedEvent

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Tim Möhlmann 2024-04-05 12:35:49 +03:00 committed by GitHub
parent 5931fb8f28
commit 2089992d75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
135 changed files with 2407 additions and 1779 deletions

View File

@ -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 && \

View File

@ -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

View File

@ -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"
}

View File

@ -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);
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);

View File

@ -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)
}

View File

@ -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:

View File

@ -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
}
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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),
}
}

View File

@ -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),
}
}

View File

@ -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
}

View File

@ -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,

View File

@ -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) {

View File

@ -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),
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
})
}
}

View File

@ -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)
}

View File

@ -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,

View File

@ -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),
)
}

View File

@ -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},

View File

@ -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,

View File

@ -93,7 +93,7 @@ func (wm *InstanceIDPOIDCConfigWriteModel) NewChangedEvent(
authorizationEndpoint,
tokenEndpoint,
clientSecretString string,
secretCrypto crypto.Crypto,
secretCrypto crypto.EncryptionAlgorithm,
idpDisplayNameMapping,
userNameMapping domain.OIDCMappingField,
scopes ...string,

View File

@ -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$"},
}

View File

@ -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,

View File

@ -92,7 +92,7 @@ func (wm *IDPOIDCConfigWriteModel) NewChangedEvent(
authorizationEndpoint,
tokenEndpoint,
clientSecretString string,
secretCrypto crypto.Crypto,
secretCrypto crypto.EncryptionAlgorithm,
idpDisplayNameMapping,
userNameMapping domain.OIDCMappingField,
scopes ...string,

View File

@ -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,

View File

@ -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)
}

View File

@ -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
}

View File

@ -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")

View File

@ -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))
}

View File

@ -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(

View File

@ -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)
})
}
}

View File

@ -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),
),
),

View File

@ -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))
}

View File

@ -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(

View File

@ -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)
})
}
}

View File

@ -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,
}

View File

@ -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
}

View File

@ -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{

View File

@ -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) {

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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))

View File

@ -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(

View File

@ -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) {

View File

@ -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))
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
})
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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]{

View File

@ -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;

View File

@ -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
),

View File

@ -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

View File

@ -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' " +

View File

@ -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,

View File

@ -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,
},
},
}

View File

@ -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"`

View File

@ -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"},

View File

@ -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 " +

View File

@ -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 " +

View File

@ -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 " +

View File

@ -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),

View File

@ -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",

View File

@ -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),

Some files were not shown because too many files have changed in this diff Show More