feat: Config to eventstore (#3158)

* feat: add default language to eventstore

* feat: add secret generator configs events

* feat: tests

* feat: secret generators in eventstore

* feat: secret generators in eventstore

* feat: smtp config in eventstore

* feat: smtp config in eventstore

* feat: smtp config in eventstore

* feat: smtp config in eventstore

* feat: smtp config in eventstore

* fix: migrations

* fix migration version

* fix test

* feat: change secret generator type to enum

* feat: change smtp attribute names

* feat: change smtp attribute names

* feat: remove engryption algorithms from command side

* feat: remove engryption algorithms from command side

* feat: smtp config

* feat: smtp config

* format smtp from header

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi 2022-02-16 16:49:17 +01:00 committed by GitHub
parent 4272ea6fe1
commit e3528ff0b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
118 changed files with 5216 additions and 686 deletions

View File

@ -142,6 +142,9 @@ func startZitadel(config *startConfig) error {
if err != nil { if err != nil {
return fmt.Errorf("cannot start eventstore for queries: %w", err) return fmt.Errorf("cannot start eventstore for queries: %w", err)
} }
smtpPasswordCrypto, err := crypto.NewAESCrypto(config.SystemDefaults.SMTPPasswordVerificationKey)
logging.Log("MAIN-en9ew").OnError(err).Fatal("cannot create smtp crypto")
queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections.Config, config.SystemDefaults, config.Projections.KeyConfig, keyChan, config.InternalAuthZ.RolePermissionMappings) queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections.Config, config.SystemDefaults, config.Projections.KeyConfig, keyChan, config.InternalAuthZ.RolePermissionMappings)
if err != nil { if err != nil {
return fmt.Errorf("cannot start queries: %w", err) return fmt.Errorf("cannot start queries: %w", err)
@ -156,12 +159,12 @@ func startZitadel(config *startConfig) error {
Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure),
DisplayName: "ZITADEL", DisplayName: "ZITADEL",
} }
commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ, storage, authZRepo, config.OIDC.KeyConfig, webAuthNConfig) commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ, storage, authZRepo, config.OIDC.KeyConfig, smtpPasswordCrypto, webAuthNConfig)
if err != nil { if err != nil {
return fmt.Errorf("cannot start commands: %w", err) return fmt.Errorf("cannot start commands: %w", err)
} }
notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix) notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, smtpPasswordCrypto)
router := mux.NewRouter() router := mux.NewRouter()
err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, keyChan, config, storage, authZRepo) err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, keyChan, config, storage, authZRepo)
@ -181,9 +184,12 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
} }
verifier := internal_authz.Start(repo) verifier := internal_authz.Start(repo)
apis := api.New(config.Port, router, &repo, config.InternalAuthZ, config.SystemDefaults, config.ExternalSecure) apis := api.New(config.Port, router, &repo, config.InternalAuthZ, config.ExternalSecure)
userEncryptionAlgorithm, err := crypto.NewAESCrypto(config.SystemDefaults.UserVerificationKey)
authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, config.OIDC.KeyConfig, assets.HandlerPrefix) if err != nil {
return nil
}
authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, config.OIDC.KeyConfig, assets.HandlerPrefix, userEncryptionAlgorithm)
if err != nil { if err != nil {
return fmt.Errorf("error starting auth repo: %w", err) return fmt.Errorf("error starting auth repo: %w", err)
} }
@ -191,13 +197,13 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
if err != nil { if err != nil {
return fmt.Errorf("error starting admin repo: %w", err) return fmt.Errorf("error starting admin repo: %w", err)
} }
if err := apis.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.SystemDefaults.Domain, assets.HandlerPrefix)); err != nil { if err := apis.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.SystemDefaults.Domain, assets.HandlerPrefix, userEncryptionAlgorithm)); err != nil {
return err return err
} }
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, assets.HandlerPrefix)); err != nil { if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, assets.HandlerPrefix, userEncryptionAlgorithm)); err != nil {
return err return err
} }
if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, assets.HandlerPrefix)); err != nil { if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, assets.HandlerPrefix, userEncryptionAlgorithm)); err != nil {
return err return err
} }
@ -231,7 +237,7 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
} }
apis.RegisterHandler(console.HandlerPrefix, c) apis.RegisterHandler(console.HandlerPrefix, c)
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix, config.ExternalDomain, oidc.AuthCallback, config.ExternalSecure, userAgentInterceptor) l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix, config.ExternalDomain, oidc.AuthCallback, config.ExternalSecure, userAgentInterceptor, userEncryptionAlgorithm)
if err != nil { if err != nil {
return fmt.Errorf("unable to start login: %w", err) return fmt.Errorf("unable to start login: %w", err)
} }

View File

@ -141,6 +141,8 @@ SystemDefaults:
EncryptionKeyID: $ZITADEL_USER_VERIFICATION_KEY EncryptionKeyID: $ZITADEL_USER_VERIFICATION_KEY
IDPConfigVerificationKey: IDPConfigVerificationKey:
EncryptionKeyID: $ZITADEL_IDP_CONFIG_VERIFICATION_KEY EncryptionKeyID: $ZITADEL_IDP_CONFIG_VERIFICATION_KEY
SMTPPasswordVerificationKey:
EncryptionKeyID: $ZITADEL_SMTP_PASSWORD_VERIFICATION_KEY
SecretGenerators: SecretGenerators:
PasswordSaltCost: 14 PasswordSaltCost: 14
ClientSecretGenerator: ClientSecretGenerator:
@ -197,7 +199,6 @@ SystemDefaults:
MFAInitSkip: 720h #30d MFAInitSkip: 720h #30d
SecondFactorCheck: 18h SecondFactorCheck: 18h
MultiFactorCheck: 12h MultiFactorCheck: 12h
IamID: 'IAM'
DomainVerification: DomainVerification:
VerificationKey: VerificationKey:
EncryptionKeyID: $ZITADEL_DOMAIN_VERIFICATION_KEY EncryptionKeyID: $ZITADEL_DOMAIN_VERIFICATION_KEY
@ -240,42 +241,6 @@ SystemDefaults:
Url: $CHAT_URL Url: $CHAT_URL
# Compact: $CHAT_COMPACT # Compact: $CHAT_COMPACT
SplitCount: 4000 SplitCount: 4000
TemplateData:
InitCode:
Title: 'InitCode.Title'
PreHeader: 'InitCode.PreHeader'
Subject: 'InitCode.Subject'
Greeting: 'InitCode.Greeting'
Text: 'InitCode.Text'
ButtonText: 'InitCode.ButtonText'
PasswordReset:
Title: 'PasswordReset.Title'
PreHeader: 'PasswordReset.PreHeader'
Subject: 'PasswordReset.Subject'
Greeting: 'PasswordReset.Greeting'
Text: 'PasswordReset.Text'
ButtonText: 'PasswordReset.ButtonText'
VerifyEmail:
Title: 'VerifyEmail.Title'
PreHeader: 'VerifyEmail.PreHeader'
Subject: 'VerifyEmail.Subject'
Greeting: 'VerifyEmail.Greeting'
Text: 'VerifyEmail.Text'
ButtonText: 'VerifyEmail.ButtonText'
VerifyPhone:
Title: 'VerifyPhone.Title'
PreHeader: 'VerifyPhone.PreHeader'
Subject: 'VerifyPhone.Subject'
Greeting: 'VerifyPhone.Greeting'
Text: 'VerifyPhone.Text'
ButtonText: 'VerifyPhone.ButtonText'
DomainClaimed:
Title: 'DomainClaimed.Title'
PreHeader: 'DomainClaimed.PreHeader'
Subject: 'DomainClaimed.Subject'
Greeting: 'DomainClaimed.Greeting'
Text: 'DomainClaimed.Text'
ButtonText: 'DomainClaimed.ButtonText'
KeyConfig: KeyConfig:
Size: 2048 Size: 2048
PrivateKeyLifetime: 6h PrivateKeyLifetime: 6h

View File

@ -32,6 +32,102 @@ Returns the default languages
GET: /languages GET: /languages
### SetDefaultLanguage
> **rpc** SetDefaultLanguage([SetDefaultLanguageRequest](#setdefaultlanguagerequest))
[SetDefaultLanguageResponse](#setdefaultlanguageresponse)
Set the default language
PUT: /languages/default/{language}
### GetDefaultLanguage
> **rpc** GetDefaultLanguage([GetDefaultLanguageRequest](#getdefaultlanguagerequest))
[GetDefaultLanguageResponse](#getdefaultlanguageresponse)
Set the default language
GET: /languages/default
### ListSecretGenerators
> **rpc** ListSecretGenerators([ListSecretGeneratorsRequest](#listsecretgeneratorsrequest))
[ListSecretGeneratorsResponse](#listsecretgeneratorsresponse)
Set the default language
POST: /secretgenerators/_search
### GetSecretGenerator
> **rpc** GetSecretGenerator([GetSecretGeneratorRequest](#getsecretgeneratorrequest))
[GetSecretGeneratorResponse](#getsecretgeneratorresponse)
Get Secret Generator by type (e.g PasswordResetCode)
GET: /secretgenerators/{generator_type}
### UpdateSecretGenerator
> **rpc** UpdateSecretGenerator([UpdateSecretGeneratorRequest](#updatesecretgeneratorrequest))
[UpdateSecretGeneratorResponse](#updatesecretgeneratorresponse)
Update secret generator configuration
PUT: /secretgenerators/{generator_type}
### GetSMTPConfig
> **rpc** GetSMTPConfig([GetSMTPConfigRequest](#getsmtpconfigrequest))
[GetSMTPConfigResponse](#getsmtpconfigresponse)
Get system smtp configuration
GET: /smtp
### UpdateSMTPConfig
> **rpc** UpdateSMTPConfig([UpdateSMTPConfigRequest](#updatesmtpconfigrequest))
[UpdateSMTPConfigResponse](#updatesmtpconfigresponse)
Update system smtp configuration
PUT: /smtp
### UpdateSMTPConfigPassword
> **rpc** UpdateSMTPConfigPassword([UpdateSMTPConfigPasswordRequest](#updatesmtpconfigpasswordrequest))
[UpdateSMTPConfigPasswordResponse](#updatesmtpconfigpasswordresponse)
Update system smtp configuration password for host
PUT: /smtp/password
### GetOrgByID ### GetOrgByID
> **rpc** GetOrgByID([GetOrgByIDRequest](#getorgbyidrequest)) > **rpc** GetOrgByID([GetOrgByIDRequest](#getorgbyidrequest))
@ -1665,6 +1761,23 @@ This is an empty response
### GetDefaultLanguageRequest
This is an empty request
### GetDefaultLanguageResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | |
### GetDefaultLoginTextsRequest ### GetDefaultLoginTextsRequest
@ -1977,6 +2090,45 @@ This is an empty request
### GetSMTPConfigRequest
This is an empty request
### GetSMTPConfigResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| smtp_config | zitadel.settings.v1.SMTPConfig | - | |
### GetSecretGeneratorRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| generator_type | zitadel.settings.v1.SecretGeneratorType | - | enum.defined_only: true<br /> enum.not_in: [0]<br /> |
### GetSecretGeneratorResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| secret_generator | zitadel.settings.v1.SecretGenerator | - | |
### GetSupportedLanguagesRequest ### GetSupportedLanguagesRequest
This is an empty request This is an empty request
@ -2211,6 +2363,30 @@ This is an empty request
### ListSecretGeneratorsRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| query | zitadel.v1.ListQuery | list limitations and ordering | |
| queries | repeated zitadel.settings.v1.SecretGeneratorQuery | criterias the client is looking for | |
### ListSecretGeneratorsResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ListDetails | - | |
| result | repeated zitadel.settings.v1.SecretGenerator | - | |
### ListViewsRequest ### ListViewsRequest
This is an empty request This is an empty request
@ -2820,6 +2996,28 @@ This is an empty request
### SetDefaultLanguageRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 10<br /> |
### SetDefaultLanguageResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetDefaultPasswordResetMessageTextRequest ### SetDefaultPasswordResetMessageTextRequest
@ -3374,6 +3572,82 @@ This is an empty request
### UpdateSMTPConfigPasswordRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| password | string | - | |
### UpdateSMTPConfigPasswordResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### UpdateSMTPConfigRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| sender_address | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| sender_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| tls | bool | - | |
| host | string | - | string.min_len: 1<br /> string.max_len: 500<br /> |
| user | string | - | |
### UpdateSMTPConfigResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### UpdateSecretGeneratorRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| generator_type | zitadel.settings.v1.SecretGeneratorType | - | enum.defined_only: true<br /> enum.not_in: [0]<br /> |
| length | uint32 | - | |
| expiry | google.protobuf.Duration | - | |
| include_lower_letters | bool | - | |
| include_upper_letters | bool | - | |
| include_digits | bool | - | |
| include_symbols | bool | - | |
### UpdateSecretGeneratorResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### View ### View

2
go.mod
View File

@ -44,7 +44,6 @@ require (
github.com/pquerna/otp v1.3.0 github.com/pquerna/otp v1.3.0
github.com/rakyll/statik v0.1.7 github.com/rakyll/statik v0.1.7
github.com/rs/cors v1.8.0 github.com/rs/cors v1.8.0
github.com/sirupsen/logrus v1.8.1
github.com/sony/sonyflake v1.0.0 github.com/sony/sonyflake v1.0.0
github.com/spf13/cobra v1.3.0 github.com/spf13/cobra v1.3.0
github.com/spf13/viper v1.10.1 github.com/spf13/viper v1.10.1
@ -140,6 +139,7 @@ require (
github.com/prometheus/procfs v0.6.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect
github.com/rs/xid v1.2.1 // indirect github.com/rs/xid v1.2.1 // indirect
github.com/satori/go.uuid v1.2.0 // indirect github.com/satori/go.uuid v1.2.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/afero v1.8.1 // indirect github.com/spf13/afero v1.8.1 // indirect
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect

2
go.sum
View File

@ -124,8 +124,6 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/logging v0.3.0 h1:PMKd47Aqr98FjAPcAE8v3SCe8eqTEclytruND/zc7QU=
github.com/caos/logging v0.3.0/go.mod h1:B8QNS0WDmR2Keac52Fw+XN4ZJkzLDGrcRIPB2Ux4uRo=
github.com/caos/logging v0.3.1 h1:892AMeHs09D0e3ZcGB+QDRsZ5+2xtPAsAhOy8eKfztc= github.com/caos/logging v0.3.1 h1:892AMeHs09D0e3ZcGB+QDRsZ5+2xtPAsAhOy8eKfztc=
github.com/caos/logging v0.3.1/go.mod h1:B8QNS0WDmR2Keac52Fw+XN4ZJkzLDGrcRIPB2Ux4uRo= github.com/caos/logging v0.3.1/go.mod h1:B8QNS0WDmR2Keac52Fw+XN4ZJkzLDGrcRIPB2Ux4uRo=
github.com/caos/oidc v1.0.1 h1:8UHAPynCObwaqortppDtJFktjqLDLYSLidkNy0Num4o= github.com/caos/oidc v1.0.1 h1:8UHAPynCObwaqortppDtJFktjqLDLYSLidkNy0Num4o=

View File

@ -15,7 +15,6 @@ import (
"github.com/caos/zitadel/internal/api/grpc/server" "github.com/caos/zitadel/internal/api/grpc/server"
http_util "github.com/caos/zitadel/internal/api/http" http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/authz/repository" "github.com/caos/zitadel/internal/authz/repository"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
@ -44,7 +43,6 @@ func New(
*query.Queries *query.Queries
}, },
authZ internal_authz.Config, authZ internal_authz.Config,
sd systemdefaults.SystemDefaults,
externalSecure bool, externalSecure bool,
) *API { ) *API {
verifier := internal_authz.Start(repo) verifier := internal_authz.Start(repo)
@ -55,7 +53,7 @@ func New(
router: router, router: router,
externalSecure: externalSecure, externalSecure: externalSecure,
} }
api.grpcServer = server.CreateServer(api.verifier, authZ, sd.DefaultLanguage) api.grpcServer = server.CreateServer(api.verifier, authZ, repo.Queries)
api.routeGRPC() api.routeGRPC()
api.RegisterHandler("/debug", api.healthHandler()) api.RegisterHandler("/debug", api.healthHandler())

View File

@ -0,0 +1,82 @@
package admin
import (
"context"
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/domain"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
)
func (s *Server) ListSecretGenerators(ctx context.Context, req *admin_pb.ListSecretGeneratorsRequest) (*admin_pb.ListSecretGeneratorsResponse, error) {
queries, err := listSecretGeneratorToModel(req)
if err != nil {
return nil, err
}
result, err := s.query.SearchSecretGenerators(ctx, queries)
if err != nil {
return nil, err
}
return &admin_pb.ListSecretGeneratorsResponse{
Details: object.ToListDetails(result.Count, result.Sequence, result.Timestamp),
}, nil
}
func (s *Server) GetSecretGenerator(ctx context.Context, req *admin_pb.GetSecretGeneratorRequest) (*admin_pb.GetSecretGeneratorResponse, error) {
generator, err := s.query.SecretGeneratorByType(ctx, SecretGeneratorTypeToDomain(req.GeneratorType))
if err != nil {
return nil, err
}
return &admin_pb.GetSecretGeneratorResponse{
SecretGenerator: SecretGeneratorToPb(generator),
}, nil
}
func (s *Server) UpdateSecretGenerator(ctx context.Context, req *admin_pb.UpdateSecretGeneratorRequest) (*admin_pb.UpdateSecretGeneratorResponse, error) {
details, err := s.command.ChangeSecretGeneratorConfig(ctx, SecretGeneratorTypeToDomain(req.GeneratorType), UpdateSecretGeneratorToConfig(req))
if err != nil {
return nil, err
}
return &admin_pb.UpdateSecretGeneratorResponse{
Details: object.ChangeToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner),
}, nil
}
func (s *Server) GetSMTPConfig(ctx context.Context, req *admin_pb.GetSMTPConfigRequest) (*admin_pb.GetSMTPConfigResponse, error) {
smtp, err := s.query.SMTPConfigByAggregateID(ctx, domain.IAMID)
if err != nil {
return nil, err
}
return &admin_pb.GetSMTPConfigResponse{
SmtpConfig: SMTPConfigToPb(smtp),
}, nil
}
func (s *Server) UpdateSMTPConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigRequest) (*admin_pb.UpdateSMTPConfigResponse, error) {
details, err := s.command.ChangeSMTPConfig(ctx, UpdateSMTPToConfig(req))
if err != nil {
return nil, err
}
return &admin_pb.UpdateSMTPConfigResponse{
Details: object.ChangeToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner),
}, nil
}
func (s *Server) UpdateSMTPConfigPassword(ctx context.Context, req *admin_pb.UpdateSMTPConfigPasswordRequest) (*admin_pb.UpdateSMTPConfigPasswordResponse, error) {
details, err := s.command.ChangeSMTPConfigPassword(ctx, req.Password)
if err != nil {
return nil, err
}
return &admin_pb.UpdateSMTPConfigPasswordResponse{
Details: object.ChangeToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner),
}, nil
}

View File

@ -0,0 +1,138 @@
package admin
import (
"github.com/caos/zitadel/internal/domain"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/caos/zitadel/internal/api/grpc/object"
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/query"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
settings_pb "github.com/caos/zitadel/pkg/grpc/settings"
)
func listSecretGeneratorToModel(req *admin_pb.ListSecretGeneratorsRequest) (*query.SecretGeneratorSearchQueries, error) {
offset, limit, asc := object.ListQueryToModel(req.Query)
queries, err := SecretGeneratorQueriesToModel(req.Queries)
if err != nil {
return nil, err
}
return &query.SecretGeneratorSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
},
Queries: queries,
}, nil
}
func SecretGeneratorQueriesToModel(queries []*settings_pb.SecretGeneratorQuery) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = SecretGeneratorQueryToModel(query)
if err != nil {
return nil, err
}
}
return q, nil
}
func SecretGeneratorQueryToModel(apiQuery *settings_pb.SecretGeneratorQuery) (query.SearchQuery, error) {
switch q := apiQuery.Query.(type) {
case *settings_pb.SecretGeneratorQuery_TypeQuery:
domainType := SecretGeneratorTypeToDomain(q.TypeQuery.GeneratorType)
return query.NewSecretGeneratorTypeSearchQuery(int32(domainType))
default:
return nil, errors.ThrowInvalidArgument(nil, "ORG-fm9es", "List.Query.Invalid")
}
}
func UpdateSecretGeneratorToConfig(req *admin_pb.UpdateSecretGeneratorRequest) *crypto.GeneratorConfig {
return &crypto.GeneratorConfig{
Length: uint(req.Length),
Expiry: req.Expiry.AsDuration(),
IncludeUpperLetters: req.IncludeUpperLetters,
IncludeLowerLetters: req.IncludeLowerLetters,
IncludeDigits: req.IncludeDigits,
IncludeSymbols: req.IncludeSymbols,
}
}
func SecretGeneratorToPb(generator *query.SecretGenerator) *settings_pb.SecretGenerator {
mapped := &settings_pb.SecretGenerator{
Length: uint32(generator.Length),
Expiry: durationpb.New(generator.Expiry),
IncludeUpperLetters: generator.IncludeUpperLetters,
IncludeLowerLetters: generator.IncludeLowerLetters,
IncludeDigits: generator.IncludeDigits,
IncludeSymbols: generator.IncludeSymbols,
Details: obj_grpc.ToViewDetailsPb(generator.Sequence, generator.CreationDate, generator.ChangeDate, generator.AggregateID),
}
return mapped
}
func SecretGeneratorTypeToPb(generatorType domain.SecretGeneratorType) settings_pb.SecretGeneratorType {
switch generatorType {
case domain.SecretGeneratorTypeInitCode:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_INIT_CODE
case domain.SecretGeneratorTypeVerifyEmailCode:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_VERIFY_EMAIL_CODE
case domain.SecretGeneratorTypeVerifyPhoneCode:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_VERIFY_PHONE_CODE
case domain.SecretGeneratorTypePasswordResetCode:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_PASSWORD_RESET_CODE
case domain.SecretGeneratorTypePasswordlessInitCode:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE
case domain.SecretGeneratorTypeAppSecret:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_APP_SECRET
default:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_UNSPECIFIED
}
}
func SecretGeneratorTypeToDomain(generatorType settings_pb.SecretGeneratorType) domain.SecretGeneratorType {
switch generatorType {
case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_INIT_CODE:
return domain.SecretGeneratorTypeInitCode
case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_VERIFY_EMAIL_CODE:
return domain.SecretGeneratorTypeVerifyEmailCode
case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_VERIFY_PHONE_CODE:
return domain.SecretGeneratorTypeVerifyPhoneCode
case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_PASSWORD_RESET_CODE:
return domain.SecretGeneratorTypePasswordResetCode
case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE:
return domain.SecretGeneratorTypePasswordlessInitCode
case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_APP_SECRET:
return domain.SecretGeneratorTypeAppSecret
default:
return domain.SecretGeneratorTypeUnspecified
}
}
func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.EmailConfig {
return &smtp.EmailConfig{
Tls: req.Tls,
From: req.SenderAddress,
FromName: req.SenderName,
SMTP: smtp.SMTP{
Host: req.Host,
User: req.User,
},
}
}
func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
mapped := &settings_pb.SMTPConfig{
Tls: smtp.TLS,
SenderAddress: smtp.SenderAddress,
SenderName: smtp.SenderName,
Host: smtp.Host,
User: smtp.User,
Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.AggregateID),
}
return mapped
}

View File

@ -3,8 +3,12 @@ package admin
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/api/grpc/text" "github.com/caos/zitadel/internal/api/grpc/text"
"github.com/caos/zitadel/internal/domain"
caos_errors "github.com/caos/zitadel/internal/errors"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin" admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
"golang.org/x/text/language"
) )
func (s *Server) GetSupportedLanguages(ctx context.Context, req *admin_pb.GetSupportedLanguagesRequest) (*admin_pb.GetSupportedLanguagesResponse, error) { func (s *Server) GetSupportedLanguages(ctx context.Context, req *admin_pb.GetSupportedLanguagesRequest) (*admin_pb.GetSupportedLanguagesResponse, error) {
@ -14,3 +18,25 @@ func (s *Server) GetSupportedLanguages(ctx context.Context, req *admin_pb.GetSup
} }
return &admin_pb.GetSupportedLanguagesResponse{Languages: text.LanguageTagsToStrings(langs)}, nil return &admin_pb.GetSupportedLanguagesResponse{Languages: text.LanguageTagsToStrings(langs)}, nil
} }
func (s *Server) SetDefaultLanguage(ctx context.Context, req *admin_pb.SetDefaultLanguageRequest) (*admin_pb.SetDefaultLanguageResponse, error) {
lang, err := language.Parse(req.Language)
if err != nil {
return nil, caos_errors.ThrowInvalidArgument(err, "API-39nnf", "Errors.Language.Parse")
}
details, err := s.command.SetDefaultLanguage(ctx, lang)
if err != nil {
return nil, err
}
return &admin_pb.SetDefaultLanguageResponse{
Details: object.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) GetDefaultLanguage(ctx context.Context, req *admin_pb.GetDefaultLanguageRequest) (*admin_pb.GetDefaultLanguageResponse, error) {
iam, err := s.query.IAMByID(ctx, domain.IAMID)
if err != nil {
return nil, err
}
return &admin_pb.GetDefaultLanguageResponse{Language: iam.DefaultLanguage.String()}, nil
}

View File

@ -53,7 +53,15 @@ func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (*
human := setUpOrgHumanToDomain(req.User.(*admin_pb.SetUpOrgRequest_Human_).Human) //TODO: handle machine human := setUpOrgHumanToDomain(req.User.(*admin_pb.SetUpOrgRequest_Human_).Human) //TODO: handle machine
org := setUpOrgOrgToDomain(req.Org) org := setUpOrgOrgToDomain(req.Org)
objectDetails, err := s.command.SetUpOrg(ctx, org, human, userIDs, false) initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
objectDetails, err := s.command.SetUpOrg(ctx, org, human, initCodeGenerator, phoneCodeGenerator, userIDs, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,6 +1,7 @@
package admin package admin
import ( import (
"github.com/caos/zitadel/internal/crypto"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/caos/zitadel/internal/admin/repository" "github.com/caos/zitadel/internal/admin/repository"
@ -25,19 +26,22 @@ type Server struct {
administrator repository.AdministratorRepository administrator repository.AdministratorRepository
iamDomain string iamDomain string
assetsAPIDomain string assetsAPIDomain string
UserCodeAlg crypto.EncryptionAlgorithm
} }
type Config struct { type Config struct {
Repository eventsourcing.Config Repository eventsourcing.Config
} }
func CreateServer(command *command.Commands, query *query.Queries, repo repository.Repository, iamDomain, assetsAPIDomain string) *Server { func CreateServer(command *command.Commands, query *query.Queries, repo repository.Repository, iamDomain, assetsAPIDomain string, userCrypto *crypto.AESCrypto) *Server {
return &Server{ return &Server{
command: command, command: command,
query: query, query: query,
administrator: repo, administrator: repo,
iamDomain: iamDomain, iamDomain: iamDomain,
assetsAPIDomain: assetsAPIDomain, assetsAPIDomain: assetsAPIDomain,
UserCodeAlg: userCrypto,
} }
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/api/grpc/user" "github.com/caos/zitadel/internal/api/grpc/user"
"github.com/caos/zitadel/internal/domain"
auth_pb "github.com/caos/zitadel/pkg/grpc/auth" auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
) )
@ -26,7 +27,11 @@ func (s *Server) GetMyEmail(ctx context.Context, _ *auth_pb.GetMyEmailRequest) (
} }
func (s *Server) SetMyEmail(ctx context.Context, req *auth_pb.SetMyEmailRequest) (*auth_pb.SetMyEmailResponse, error) { func (s *Server) SetMyEmail(ctx context.Context, req *auth_pb.SetMyEmailRequest) (*auth_pb.SetMyEmailResponse, error) {
email, err := s.command.ChangeHumanEmail(ctx, UpdateMyEmailToDomain(ctx, req)) emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
email, err := s.command.ChangeHumanEmail(ctx, UpdateMyEmailToDomain(ctx, req), emailCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -40,8 +45,12 @@ func (s *Server) SetMyEmail(ctx context.Context, req *auth_pb.SetMyEmailRequest)
} }
func (s *Server) VerifyMyEmail(ctx context.Context, req *auth_pb.VerifyMyEmailRequest) (*auth_pb.VerifyMyEmailResponse, error) { func (s *Server) VerifyMyEmail(ctx context.Context, req *auth_pb.VerifyMyEmailRequest) (*auth_pb.VerifyMyEmailResponse, error) {
emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
objectDetails, err := s.command.VerifyHumanEmail(ctx, ctxData.UserID, req.Code, ctxData.ResourceOwner) objectDetails, err := s.command.VerifyHumanEmail(ctx, ctxData.UserID, req.Code, ctxData.ResourceOwner, emailCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -52,7 +61,11 @@ func (s *Server) VerifyMyEmail(ctx context.Context, req *auth_pb.VerifyMyEmailRe
func (s *Server) ResendMyEmailVerification(ctx context.Context, _ *auth_pb.ResendMyEmailVerificationRequest) (*auth_pb.ResendMyEmailVerificationResponse, error) { func (s *Server) ResendMyEmailVerification(ctx context.Context, _ *auth_pb.ResendMyEmailVerificationRequest) (*auth_pb.ResendMyEmailVerificationResponse, error) {
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner) emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner, emailCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -57,7 +57,11 @@ func (s *Server) AddMyPasswordless(ctx context.Context, _ *auth_pb.AddMyPassword
func (s *Server) AddMyPasswordlessLink(ctx context.Context, _ *auth_pb.AddMyPasswordlessLinkRequest) (*auth_pb.AddMyPasswordlessLinkResponse, error) { func (s *Server) AddMyPasswordlessLink(ctx context.Context, _ *auth_pb.AddMyPasswordlessLinkRequest) (*auth_pb.AddMyPasswordlessLinkResponse, error) {
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
initCode, err := s.command.HumanAddPasswordlessInitCode(ctx, ctxData.UserID, ctxData.ResourceOwner) passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
initCode, err := s.command.HumanAddPasswordlessInitCode(ctx, ctxData.UserID, ctxData.ResourceOwner, passwordlessInitCode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -70,7 +74,11 @@ func (s *Server) AddMyPasswordlessLink(ctx context.Context, _ *auth_pb.AddMyPass
func (s *Server) SendMyPasswordlessLink(ctx context.Context, _ *auth_pb.SendMyPasswordlessLinkRequest) (*auth_pb.SendMyPasswordlessLinkResponse, error) { func (s *Server) SendMyPasswordlessLink(ctx context.Context, _ *auth_pb.SendMyPasswordlessLinkRequest) (*auth_pb.SendMyPasswordlessLinkResponse, error) {
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
initCode, err := s.command.HumanSendPasswordlessInitCode(ctx, ctxData.UserID, ctxData.ResourceOwner) passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
initCode, err := s.command.HumanSendPasswordlessInitCode(ctx, ctxData.UserID, ctxData.ResourceOwner, passwordlessInitCode)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/api/grpc/user" "github.com/caos/zitadel/internal/api/grpc/user"
"github.com/caos/zitadel/internal/domain"
auth_pb "github.com/caos/zitadel/pkg/grpc/auth" auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
) )
@ -26,7 +27,11 @@ func (s *Server) GetMyPhone(ctx context.Context, _ *auth_pb.GetMyPhoneRequest) (
} }
func (s *Server) SetMyPhone(ctx context.Context, req *auth_pb.SetMyPhoneRequest) (*auth_pb.SetMyPhoneResponse, error) { func (s *Server) SetMyPhone(ctx context.Context, req *auth_pb.SetMyPhoneRequest) (*auth_pb.SetMyPhoneResponse, error) {
phone, err := s.command.ChangeHumanPhone(ctx, UpdateMyPhoneToDomain(ctx, req), authz.GetCtxData(ctx).ResourceOwner) phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
phone, err := s.command.ChangeHumanPhone(ctx, UpdateMyPhoneToDomain(ctx, req), authz.GetCtxData(ctx).ResourceOwner, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -41,7 +46,11 @@ func (s *Server) SetMyPhone(ctx context.Context, req *auth_pb.SetMyPhoneRequest)
func (s *Server) VerifyMyPhone(ctx context.Context, req *auth_pb.VerifyMyPhoneRequest) (*auth_pb.VerifyMyPhoneResponse, error) { func (s *Server) VerifyMyPhone(ctx context.Context, req *auth_pb.VerifyMyPhoneRequest) (*auth_pb.VerifyMyPhoneResponse, error) {
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
objectDetails, err := s.command.VerifyHumanPhone(ctx, ctxData.UserID, req.Code, ctxData.ResourceOwner) phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
objectDetails, err := s.command.VerifyHumanPhone(ctx, ctxData.UserID, req.Code, ctxData.ResourceOwner, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -53,7 +62,11 @@ func (s *Server) VerifyMyPhone(ctx context.Context, req *auth_pb.VerifyMyPhoneRe
func (s *Server) ResendMyPhoneVerification(ctx context.Context, _ *auth_pb.ResendMyPhoneVerificationRequest) (*auth_pb.ResendMyPhoneVerificationResponse, error) { func (s *Server) ResendMyPhoneVerification(ctx context.Context, _ *auth_pb.ResendMyPhoneVerificationRequest) (*auth_pb.ResendMyPhoneVerificationResponse, error) {
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
objectDetails, err := s.command.CreateHumanPhoneVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner) phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
objectDetails, err := s.command.CreateHumanPhoneVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,6 +1,7 @@
package auth package auth
import ( import (
"github.com/caos/zitadel/internal/crypto"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
@ -26,19 +27,21 @@ type Server struct {
repo repository.Repository repo repository.Repository
defaults systemdefaults.SystemDefaults defaults systemdefaults.SystemDefaults
assetsAPIDomain string assetsAPIDomain string
UserCodeAlg crypto.EncryptionAlgorithm
} }
type Config struct { type Config struct {
Repository eventsourcing.Config Repository eventsourcing.Config
} }
func CreateServer(command *command.Commands, query *query.Queries, authRepo repository.Repository, defaults systemdefaults.SystemDefaults, assetsAPIDomain string) *Server { func CreateServer(command *command.Commands, query *query.Queries, authRepo repository.Repository, defaults systemdefaults.SystemDefaults, assetsAPIDomain string, userCrypto *crypto.AESCrypto) *Server {
return &Server{ return &Server{
command: command, command: command,
query: query, query: query,
repo: authRepo, repo: authRepo,
defaults: defaults, defaults: defaults,
assetsAPIDomain: assetsAPIDomain, assetsAPIDomain: assetsAPIDomain,
UserCodeAlg: userCrypto,
} }
} }

View File

@ -3,11 +3,12 @@ package management
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/domain"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
) )
func (s *Server) GetIAM(ctx context.Context, req *mgmt_pb.GetIAMRequest) (*mgmt_pb.GetIAMResponse, error) { func (s *Server) GetIAM(ctx context.Context, req *mgmt_pb.GetIAMRequest) (*mgmt_pb.GetIAMResponse, error) {
iam, err := s.query.IAMByID(ctx, s.systemDefaults.IamID) iam, err := s.query.IAMByID(ctx, domain.IAMID)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -8,6 +8,7 @@ import (
change_grpc "github.com/caos/zitadel/internal/api/grpc/change" change_grpc "github.com/caos/zitadel/internal/api/grpc/change"
object_grpc "github.com/caos/zitadel/internal/api/grpc/object" object_grpc "github.com/caos/zitadel/internal/api/grpc/object"
project_grpc "github.com/caos/zitadel/internal/api/grpc/project" project_grpc "github.com/caos/zitadel/internal/api/grpc/project"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
) )
@ -57,7 +58,11 @@ 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) { func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest) (*mgmt_pb.AddOIDCAppResponse, error) {
app, err := s.command.AddOIDCApplication(ctx, AddOIDCAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID) 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -72,7 +77,11 @@ func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest)
} }
func (s *Server) AddAPIApp(ctx context.Context, req *mgmt_pb.AddAPIAppRequest) (*mgmt_pb.AddAPIAppResponse, error) { func (s *Server) AddAPIApp(ctx context.Context, req *mgmt_pb.AddAPIAppRequest) (*mgmt_pb.AddAPIAppResponse, error) {
app, err := s.command.AddAPIApplication(ctx, AddAPIAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID) 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -153,7 +162,11 @@ 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) { func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.RegenerateOIDCClientSecretRequest) (*mgmt_pb.RegenerateOIDCClientSecretResponse, error) {
config, err := s.command.ChangeOIDCApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID) 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -168,7 +181,11 @@ 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) { func (s *Server) RegenerateAPIClientSecret(ctx context.Context, req *mgmt_pb.RegenerateAPIClientSecretRequest) (*mgmt_pb.RegenerateAPIClientSecretResponse, error) {
config, err := s.command.ChangeAPIApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID) 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,6 +1,7 @@
package management package management
import ( import (
"github.com/caos/zitadel/internal/crypto"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
@ -19,18 +20,22 @@ var _ management.ManagementServiceServer = (*Server)(nil)
type Server struct { type Server struct {
management.UnimplementedManagementServiceServer management.UnimplementedManagementServiceServer
command *command.Commands command *command.Commands
query *query.Queries query *query.Queries
systemDefaults systemdefaults.SystemDefaults systemDefaults systemdefaults.SystemDefaults
assetAPIPrefix string assetAPIPrefix string
PasswordHashAlg crypto.HashAlgorithm
UserCodeAlg crypto.EncryptionAlgorithm
} }
func CreateServer(command *command.Commands, query *query.Queries, sd systemdefaults.SystemDefaults, assetAPIPrefix string) *Server { func CreateServer(command *command.Commands, query *query.Queries, sd systemdefaults.SystemDefaults, assetAPIPrefix string, userCrypto *crypto.AESCrypto) *Server {
return &Server{ return &Server{
command: command, command: command,
query: query, query: query,
systemDefaults: sd, systemDefaults: sd,
assetAPIPrefix: assetAPIPrefix, assetAPIPrefix: assetAPIPrefix,
PasswordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost),
UserCodeAlg: userCrypto,
} }
} }

View File

@ -192,7 +192,15 @@ func (s *Server) BulkRemoveUserMetadata(ctx context.Context, req *mgmt_pb.BulkRe
} }
func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequest) (*mgmt_pb.AddHumanUserResponse, error) { func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequest) (*mgmt_pb.AddHumanUserResponse, error) {
human, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, AddHumanUserRequestToDomain(req)) initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
human, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, AddHumanUserRequestToDomain(req), initCodeGenerator, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -208,7 +216,19 @@ func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequ
func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUserRequest) (*mgmt_pb.ImportHumanUserResponse, error) { func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUserRequest) (*mgmt_pb.ImportHumanUserResponse, error) {
human, passwordless := ImportHumanUserRequestToDomain(req) human, passwordless := ImportHumanUserRequestToDomain(req)
addedHuman, code, err := s.command.ImportHuman(ctx, authz.GetCtxData(ctx).OrgID, human, passwordless) initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
addedHuman, code, err := s.command.ImportHuman(ctx, authz.GetCtxData(ctx).OrgID, human, passwordless, initCodeGenerator, phoneCodeGenerator, passwordlessInitCode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -388,7 +408,11 @@ func (s *Server) GetHumanEmail(ctx context.Context, req *mgmt_pb.GetHumanEmailRe
} }
func (s *Server) UpdateHumanEmail(ctx context.Context, req *mgmt_pb.UpdateHumanEmailRequest) (*mgmt_pb.UpdateHumanEmailResponse, error) { func (s *Server) UpdateHumanEmail(ctx context.Context, req *mgmt_pb.UpdateHumanEmailRequest) (*mgmt_pb.UpdateHumanEmailResponse, error) {
email, err := s.command.ChangeHumanEmail(ctx, UpdateHumanEmailRequestToDomain(ctx, req)) emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
email, err := s.command.ChangeHumanEmail(ctx, UpdateHumanEmailRequestToDomain(ctx, req), emailCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -402,7 +426,11 @@ func (s *Server) UpdateHumanEmail(ctx context.Context, req *mgmt_pb.UpdateHumanE
} }
func (s *Server) ResendHumanInitialization(ctx context.Context, req *mgmt_pb.ResendHumanInitializationRequest) (*mgmt_pb.ResendHumanInitializationResponse, error) { func (s *Server) ResendHumanInitialization(ctx context.Context, req *mgmt_pb.ResendHumanInitializationRequest) (*mgmt_pb.ResendHumanInitializationResponse, error) {
details, err := s.command.ResendInitialMail(ctx, req.UserId, req.Email, authz.GetCtxData(ctx).OrgID) initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
details, err := s.command.ResendInitialMail(ctx, req.UserId, req.Email, authz.GetCtxData(ctx).OrgID, initCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -412,7 +440,11 @@ func (s *Server) ResendHumanInitialization(ctx context.Context, req *mgmt_pb.Res
} }
func (s *Server) ResendHumanEmailVerification(ctx context.Context, req *mgmt_pb.ResendHumanEmailVerificationRequest) (*mgmt_pb.ResendHumanEmailVerificationResponse, error) { func (s *Server) ResendHumanEmailVerification(ctx context.Context, req *mgmt_pb.ResendHumanEmailVerificationRequest) (*mgmt_pb.ResendHumanEmailVerificationResponse, error) {
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID) emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
objectDetails, err := s.command.CreateHumanEmailVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, emailCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -442,7 +474,11 @@ func (s *Server) GetHumanPhone(ctx context.Context, req *mgmt_pb.GetHumanPhoneRe
} }
func (s *Server) UpdateHumanPhone(ctx context.Context, req *mgmt_pb.UpdateHumanPhoneRequest) (*mgmt_pb.UpdateHumanPhoneResponse, error) { func (s *Server) UpdateHumanPhone(ctx context.Context, req *mgmt_pb.UpdateHumanPhoneRequest) (*mgmt_pb.UpdateHumanPhoneResponse, error) {
phone, err := s.command.ChangeHumanPhone(ctx, UpdateHumanPhoneRequestToDomain(req), authz.GetCtxData(ctx).OrgID) phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
phone, err := s.command.ChangeHumanPhone(ctx, UpdateHumanPhoneRequestToDomain(req), authz.GetCtxData(ctx).OrgID, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -466,7 +502,11 @@ func (s *Server) RemoveHumanPhone(ctx context.Context, req *mgmt_pb.RemoveHumanP
} }
func (s *Server) ResendHumanPhoneVerification(ctx context.Context, req *mgmt_pb.ResendHumanPhoneVerificationRequest) (*mgmt_pb.ResendHumanPhoneVerificationResponse, error) { func (s *Server) ResendHumanPhoneVerification(ctx context.Context, req *mgmt_pb.ResendHumanPhoneVerificationRequest) (*mgmt_pb.ResendHumanPhoneVerificationResponse, error) {
objectDetails, err := s.command.CreateHumanPhoneVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID) phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
objectDetails, err := s.command.CreateHumanPhoneVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -507,7 +547,11 @@ func (s *Server) SetHumanPassword(ctx context.Context, req *mgmt_pb.SetHumanPass
} }
func (s *Server) SendHumanResetPasswordNotification(ctx context.Context, req *mgmt_pb.SendHumanResetPasswordNotificationRequest) (*mgmt_pb.SendHumanResetPasswordNotificationResponse, error) { func (s *Server) SendHumanResetPasswordNotification(ctx context.Context, req *mgmt_pb.SendHumanResetPasswordNotificationRequest) (*mgmt_pb.SendHumanResetPasswordNotificationResponse, error) {
objectDetails, err := s.command.RequestSetPassword(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, notifyTypeToDomain(req.Type)) passwordCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordResetCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
objectDetails, err := s.command.RequestSetPassword(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, notifyTypeToDomain(req.Type), passwordCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -584,7 +628,11 @@ func (s *Server) ListHumanPasswordless(ctx context.Context, req *mgmt_pb.ListHum
func (s *Server) AddPasswordlessRegistration(ctx context.Context, req *mgmt_pb.AddPasswordlessRegistrationRequest) (*mgmt_pb.AddPasswordlessRegistrationResponse, error) { func (s *Server) AddPasswordlessRegistration(ctx context.Context, req *mgmt_pb.AddPasswordlessRegistrationRequest) (*mgmt_pb.AddPasswordlessRegistrationResponse, error) {
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
initCode, err := s.command.HumanAddPasswordlessInitCode(ctx, req.UserId, ctxData.OrgID) passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
initCode, err := s.command.HumanAddPasswordlessInitCode(ctx, req.UserId, ctxData.OrgID, passwordlessInitCode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -597,7 +645,11 @@ func (s *Server) AddPasswordlessRegistration(ctx context.Context, req *mgmt_pb.A
func (s *Server) SendPasswordlessRegistration(ctx context.Context, req *mgmt_pb.SendPasswordlessRegistrationRequest) (*mgmt_pb.SendPasswordlessRegistrationResponse, error) { func (s *Server) SendPasswordlessRegistration(ctx context.Context, req *mgmt_pb.SendPasswordlessRegistrationRequest) (*mgmt_pb.SendPasswordlessRegistrationResponse, error) {
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
initCode, err := s.command.HumanSendPasswordlessInitCode(ctx, req.UserId, ctxData.OrgID) passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg)
if err != nil {
return nil, err
}
initCode, err := s.command.HumanSendPasswordlessInitCode(ctx, req.UserId, ctxData.OrgID, passwordlessInitCode)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,15 +3,14 @@ package middleware
import ( import (
"context" "context"
"golang.org/x/text/language" "github.com/caos/zitadel/internal/query"
"google.golang.org/grpc" "google.golang.org/grpc"
_ "github.com/caos/zitadel/internal/statik" _ "github.com/caos/zitadel/internal/statik"
) )
func TranslationHandler(defaultLanguage language.Tag) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { func TranslationHandler(query *query.Queries) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
translator := newZitadelTranslator(defaultLanguage) translator := newZitadelTranslator(query.GetDefaultLanguage(context.Background()))
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
resp, err := handler(ctx, req) resp, err := handler(ctx, req)

View File

@ -2,10 +2,10 @@ package server
import ( import (
grpc_api "github.com/caos/zitadel/internal/api/grpc" grpc_api "github.com/caos/zitadel/internal/api/grpc"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/telemetry/metrics" "github.com/caos/zitadel/internal/telemetry/metrics"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"golang.org/x/text/language"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
@ -20,7 +20,7 @@ type Server interface {
AuthMethods() authz.MethodMapping AuthMethods() authz.MethodMapping
} }
func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, lang language.Tag) *grpc.Server { func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, queries *query.Queries) *grpc.Server {
metricTypes := []metrics.MetricType{metrics.MetricTypeTotalCount, metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode} metricTypes := []metrics.MetricType{metrics.MetricTypeTotalCount, metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode}
return grpc.NewServer( return grpc.NewServer(
grpc.UnaryInterceptor( grpc.UnaryInterceptor(
@ -31,7 +31,7 @@ func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, lang l
middleware.NoCacheInterceptor(), middleware.NoCacheInterceptor(),
middleware.ErrorHandler(), middleware.ErrorHandler(),
middleware.AuthorizationInterceptor(verifier, authConfig), middleware.AuthorizationInterceptor(verifier, authConfig),
middleware.TranslationHandler(lang), middleware.TranslationHandler(queries),
middleware.ValidationHandler(), middleware.ValidationHandler(),
middleware.ServiceHandler(), middleware.ServiceHandler(),
), ),

View File

@ -145,7 +145,17 @@ func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, aut
memberRoles = nil memberRoles = nil
resourceOwner = authReq.RequestedOrgID resourceOwner = authReq.RequestedOrgID
} }
_, err := l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, externalIDP, memberRoles) initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg)
if err != nil {
l.renderRegisterOption(w, r, authReq, err)
return
}
phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.UserCodeAlg)
if err != nil {
l.renderRegisterOption(w, r, authReq, err)
return
}
_, err = l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, externalIDP, memberRoles, initCodeGenerator, phoneCodeGenerator)
if err != nil { if err != nil {
l.renderRegisterOption(w, r, authReq, err) l.renderRegisterOption(w, r, authReq, err)
return return
@ -216,7 +226,17 @@ func (l *Login) handleExternalRegisterCheck(w http.ResponseWriter, r *http.Reque
l.renderRegisterOption(w, r, authReq, err) l.renderRegisterOption(w, r, authReq, err)
return return
} }
_, err = l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, externalIDP, memberRoles) initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg)
if err != nil {
l.renderRegisterOption(w, r, authReq, err)
return
}
phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.UserCodeAlg)
if err != nil {
l.renderRegisterOption(w, r, authReq, err)
return
}
_, err = l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, externalIDP, memberRoles, initCodeGenerator, phoneCodeGenerator)
if err != nil { if err != nil {
l.renderRegisterOption(w, r, authReq, err) l.renderRegisterOption(w, r, authReq, err)
return return

View File

@ -70,7 +70,12 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *dom
userOrg = authReq.UserOrgID userOrg = authReq.UserOrgID
} }
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.command.SetPasswordWithVerifyCode(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password, userAgentID) passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.UserCodeAlg)
if err != nil {
l.renderInitPassword(w, r, authReq, data.UserID, "", err)
return
}
err = l.command.SetPasswordWithVerifyCode(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password, userAgentID, passwordCodeGenerator)
if err != nil { if err != nil {
l.renderInitPassword(w, r, authReq, data.UserID, "", err) l.renderInitPassword(w, r, authReq, data.UserID, "", err)
return return
@ -92,12 +97,17 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
return return
} }
passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.UserCodeAlg)
if err != nil {
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
return
}
user, err := l.query.GetUser(setContext(r.Context(), userOrg), loginName) user, err := l.query.GetUser(setContext(r.Context(), userOrg), loginName)
if err != nil { if err != nil {
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
return return
} }
_, err = l.command.RequestSetPassword(setContext(r.Context(), userOrg), user.ID, user.ResourceOwner, domain.NotificationTypeEmail) _, err = l.command.RequestSetPassword(setContext(r.Context(), userOrg), user.ID, user.ResourceOwner, domain.NotificationTypeEmail, passwordCodeGenerator)
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
} }

View File

@ -73,7 +73,12 @@ func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authRe
if authReq != nil { if authReq != nil {
userOrgID = authReq.UserOrgID userOrgID = authReq.UserOrgID
} }
err = l.command.HumanVerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, userOrgID, data.Code, data.Password) initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg)
if err != nil {
l.renderInitUser(w, r, authReq, data.UserID, "", data.PasswordSet, err)
return
}
err = l.command.HumanVerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, userOrgID, data.Code, data.Password, initCodeGenerator)
if err != nil { if err != nil {
l.renderInitUser(w, r, authReq, data.UserID, "", data.PasswordSet, err) l.renderInitUser(w, r, authReq, data.UserID, "", data.PasswordSet, err)
return return
@ -86,7 +91,12 @@ func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *
if authReq != nil { if authReq != nil {
userOrgID = authReq.UserOrgID userOrgID = authReq.UserOrgID
} }
_, err := l.command.ResendInitialMail(setContext(r.Context(), userOrgID), userID, "", userOrgID) initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg)
if err != nil {
l.renderInitUser(w, r, authReq, userID, "", showPassword, err)
return
}
_, err = l.command.ResendInitialMail(setContext(r.Context(), userOrgID), userID, "", userOrgID, initCodeGenerator)
l.renderInitUser(w, r, authReq, userID, "", showPassword, err) l.renderInitUser(w, r, authReq, userID, "", showPassword, err)
} }

View File

@ -38,6 +38,7 @@ type Login struct {
zitadelURL string zitadelURL string
oidcAuthCallbackURL string oidcAuthCallbackURL string
IDPConfigAesCrypto crypto.EncryptionAlgorithm IDPConfigAesCrypto crypto.EncryptionAlgorithm
UserCodeAlg crypto.EncryptionAlgorithm
iamDomain string iamDomain string
} }
@ -59,7 +60,7 @@ const (
DefaultLoggedOutPath = HandlerPrefix + EndpointLogoutDone DefaultLoggedOutPath = HandlerPrefix + EndpointLogoutDone
) )
func CreateLogin(config Config, command *command.Commands, query *query.Queries, authRepo *eventsourcing.EsRepository, staticStorage static.Storage, systemDefaults systemdefaults.SystemDefaults, zitadelURL, domain, oidcAuthCallbackURL string, externalSecure bool, userAgentCookie mux.MiddlewareFunc) (*Login, error) { func CreateLogin(config Config, command *command.Commands, query *query.Queries, authRepo *eventsourcing.EsRepository, staticStorage static.Storage, systemDefaults systemdefaults.SystemDefaults, zitadelURL, domain, oidcAuthCallbackURL string, externalSecure bool, userAgentCookie mux.MiddlewareFunc, userCrypto *crypto.AESCrypto) (*Login, error) {
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey) aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("error create new aes crypto: %w", err) return nil, fmt.Errorf("error create new aes crypto: %w", err)
@ -74,6 +75,7 @@ func CreateLogin(config Config, command *command.Commands, query *query.Queries,
authRepo: authRepo, authRepo: authRepo,
IDPConfigAesCrypto: aesCrypto, IDPConfigAesCrypto: aesCrypto,
iamDomain: domain, iamDomain: domain,
UserCodeAlg: userCrypto,
} }
//TODO: enable when storage is implemented again //TODO: enable when storage is implemented again
//login.staticCache, err = config.StaticCache.Config.NewCache() //login.staticCache, err = config.StaticCache.Config.NewCache()

View File

@ -51,7 +51,12 @@ func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Reque
if authReq != nil { if authReq != nil {
userOrg = authReq.UserOrgID userOrg = authReq.UserOrgID
} }
_, err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg) emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.UserCodeAlg)
if err != nil {
l.checkMailCode(w, r, authReq, data.UserID, data.Code)
return
}
_, err = l.command.CreateHumanEmailVerificationCode(setContext(r.Context(), userOrg), data.UserID, userOrg, emailCodeGenerator)
l.renderMailVerification(w, r, authReq, data.UserID, err) l.renderMailVerification(w, r, authReq, data.UserID, err)
} }
@ -61,7 +66,12 @@ func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *d
userID = authReq.UserID userID = authReq.UserID
userOrg = authReq.UserOrgID userOrg = authReq.UserOrgID
} }
_, err := l.command.VerifyHumanEmail(setContext(r.Context(), userOrg), userID, code, userOrg) emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.UserCodeAlg)
if err != nil {
l.renderMailVerification(w, r, authReq, userID, err)
return
}
_, err = l.command.VerifyHumanEmail(setContext(r.Context(), userOrg), userID, code, userOrg, emailCodeGenerator)
if err != nil { if err != nil {
l.renderMailVerification(w, r, authReq, userID, err) l.renderMailVerification(w, r, authReq, userID, err)
return return

View File

@ -27,7 +27,12 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
l.renderPasswordResetDone(w, r, authReq, err) l.renderPasswordResetDone(w, r, authReq, err)
return return
} }
_, err = l.command.RequestSetPassword(setContext(r.Context(), authReq.UserOrgID), user.ID, authReq.UserOrgID, domain.NotificationTypeEmail) passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.UserCodeAlg)
if err != nil {
l.renderPasswordResetDone(w, r, authReq, err)
return
}
_, err = l.command.RequestSetPassword(setContext(r.Context(), authReq.UserOrgID), user.ID, authReq.UserOrgID, domain.NotificationTypeEmail, passwordCodeGenerator)
l.renderPasswordResetDone(w, r, authReq, err) l.renderPasswordResetDone(w, r, authReq, err)
} }

View File

@ -74,7 +74,17 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
memberRoles = nil memberRoles = nil
resourceOwner = authRequest.RequestedOrgID resourceOwner = authRequest.RequestedOrgID
} }
user, err := l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, data.toHumanDomain(), nil, memberRoles) initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
}
phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.UserCodeAlg)
if err != nil {
l.renderRegister(w, r, authRequest, data, err)
return
}
user, err := l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, data.toHumanDomain(), nil, memberRoles, initCodeGenerator, phoneCodeGenerator)
if err != nil { if err != nil {
l.renderRegister(w, r, authRequest, data, err) l.renderRegister(w, r, authRequest, data, err)
return return

View File

@ -65,7 +65,17 @@ func (l *Login) handleRegisterOrgCheck(w http.ResponseWriter, r *http.Request) {
l.renderRegisterOrg(w, r, authRequest, data, err) l.renderRegisterOrg(w, r, authRequest, data, err)
return return
} }
_, err = l.command.SetUpOrg(ctx, data.toOrgDomain(), data.toUserDomain(), userIDs, true) initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordlessInitCode, l.UserCodeAlg)
if err != nil {
l.renderRegisterOrg(w, r, authRequest, data, err)
return
}
phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.UserCodeAlg)
if err != nil {
l.renderRegisterOrg(w, r, authRequest, data, err)
return
}
_, err = l.command.SetUpOrg(ctx, data.toOrgDomain(), data.toUserDomain(), initCodeGenerator, phoneCodeGenerator, userIDs, true)
if err != nil { if err != nil {
l.renderRegisterOrg(w, r, authRequest, data, err) l.renderRegisterOrg(w, r, authRequest, data, err)
return return

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
@ -32,6 +33,7 @@ type AuthRequestRepo struct {
AuthRequests cache.AuthRequestCache AuthRequests cache.AuthRequestCache
View *view.View View *view.View
Eventstore v1.Eventstore Eventstore v1.Eventstore
UserCodeAlg crypto.EncryptionAlgorithm
LabelPolicyProvider labelPolicyProvider LabelPolicyProvider labelPolicyProvider
UserSessionViewProvider userSessionViewProvider UserSessionViewProvider userSessionViewProvider
@ -54,8 +56,6 @@ type AuthRequestRepo struct {
MFAInitSkippedLifeTime time.Duration MFAInitSkippedLifeTime time.Duration
SecondFactorCheckLifeTime time.Duration SecondFactorCheckLifeTime time.Duration
MultiFactorCheckLifeTime time.Duration MultiFactorCheckLifeTime time.Duration
IAMID string
} }
type labelPolicyProvider interface { type labelPolicyProvider interface {
@ -381,13 +381,21 @@ func (repo *AuthRequestRepo) VerifyPasswordlessSetup(ctx context.Context, userID
func (repo *AuthRequestRepo) BeginPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, codeID, verificationCode string, preferredPlatformType domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error) { func (repo *AuthRequestRepo) BeginPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, codeID, verificationCode string, preferredPlatformType domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
return repo.Command.HumanAddPasswordlessSetupInitCode(ctx, userID, resourceOwner, codeID, verificationCode, preferredPlatformType) passwordlessInitCode, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, repo.UserCodeAlg)
if err != nil {
return nil, err
}
return repo.Command.HumanAddPasswordlessSetupInitCode(ctx, userID, resourceOwner, codeID, verificationCode, preferredPlatformType, passwordlessInitCode)
} }
func (repo *AuthRequestRepo) VerifyPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, userAgentID, tokenName, codeID, verificationCode string, credentialData []byte) (err error) { func (repo *AuthRequestRepo) VerifyPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, userAgentID, tokenName, codeID, verificationCode string, credentialData []byte) (err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
_, err = repo.Command.HumanPasswordlessSetupInitCode(ctx, userID, resourceOwner, tokenName, userAgentID, codeID, verificationCode, credentialData) passwordlessInitCode, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, repo.UserCodeAlg)
if err != nil {
return err
}
_, err = repo.Command.HumanPasswordlessSetupInitCode(ctx, userID, resourceOwner, tokenName, userAgentID, codeID, verificationCode, credentialData, passwordlessInitCode)
return err return err
} }
@ -447,7 +455,15 @@ func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, regis
if err != nil { if err != nil {
return err return err
} }
human, err := repo.Command.RegisterHuman(ctx, resourceOwner, registerUser, externalIDP, orgMemberRoles) initCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, repo.UserCodeAlg)
if err != nil {
return err
}
phoneCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, repo.UserCodeAlg)
if err != nil {
return err
}
human, err := repo.Command.RegisterHuman(ctx, resourceOwner, registerUser, externalIDP, orgMemberRoles, initCodeGenerator, phoneCodeGenerator)
if err != nil { if err != nil {
return err return err
} }
@ -519,7 +535,7 @@ func (repo *AuthRequestRepo) getLoginPolicyAndIDPProviders(ctx context.Context,
if !policy.AllowExternalIDPs { if !policy.AllowExternalIDPs {
return policy, nil, nil return policy, nil, nil
} }
idpProviders, err := getLoginPolicyIDPProviders(repo.IDPProviderViewProvider, repo.IAMID, orgID, policy.IsDefault) idpProviders, err := getLoginPolicyIDPProviders(repo.IDPProviderViewProvider, domain.IAMID, orgID, policy.IsDefault)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -534,7 +550,7 @@ func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.A
orgID = request.UserOrgID orgID = request.UserOrgID
} }
if orgID == "" { if orgID == "" {
orgID = repo.IAMID orgID = domain.IAMID
} }
loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, orgID) loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, orgID)

View File

@ -53,5 +53,5 @@ func (repo *OrgRepository) GetLoginText(ctx context.Context, orgID string) ([]*d
} }
func (p *OrgRepository) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) { func (p *OrgRepository) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) {
return p.Eventstore.FilterEvents(ctx, models.NewSearchQuery().AggregateIDFilter(p.SystemDefaults.IamID).AggregateTypeFilter(iam.AggregateType)) return p.Eventstore.FilterEvents(ctx, models.NewSearchQuery().AggregateIDFilter(domain.IAMID).AggregateTypeFilter(iam.AggregateType))
} }

View File

@ -32,8 +32,7 @@ func (h *handler) Eventstore() v1.Eventstore {
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, systemDefaults sd.SystemDefaults, queries *query2.Queries) []query.Handler { func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, systemDefaults sd.SystemDefaults, queries *query2.Queries) []query.Handler {
return []query.Handler{ return []query.Handler{
newUser( newUser(
handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es}, handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es}, queries),
systemDefaults.IamID, queries),
newUserSession( newUserSession(
handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount, es}),
newToken( newToken(

View File

@ -111,7 +111,7 @@ func (i *IDPProvider) processIdpProvider(event *es_models.Event) (err error) {
case model.IDPConfigChanged, org_es_model.IDPConfigChanged: case model.IDPConfigChanged, org_es_model.IDPConfigChanged:
esConfig := new(iam_view_model.IDPConfigView) esConfig := new(iam_view_model.IDPConfigView)
providerType := iam_model.IDPProviderTypeSystem providerType := iam_model.IDPProviderTypeSystem
if event.AggregateID != i.systemDefaults.IamID { if event.AggregateID != domain.IAMID {
providerType = iam_model.IDPProviderTypeOrg providerType = iam_model.IDPProviderTypeOrg
} }
esConfig.AppendEvent(providerType, event) esConfig.AppendEvent(providerType, event)
@ -120,7 +120,7 @@ func (i *IDPProvider) processIdpProvider(event *es_models.Event) (err error) {
return err return err
} }
config := new(query2.IDP) config := new(query2.IDP)
if event.AggregateID == i.systemDefaults.IamID { if event.AggregateID == domain.IAMID {
config, err = i.getDefaultIDPConfig(context.TODO(), esConfig.IDPConfigID) config, err = i.getDefaultIDPConfig(context.TODO(), esConfig.IDPConfigID)
} else { } else {
config, err = i.getOrgIDPConfig(context.TODO(), event.AggregateID, esConfig.IDPConfigID) config, err = i.getOrgIDPConfig(context.TODO(), event.AggregateID, esConfig.IDPConfigID)

View File

@ -25,19 +25,16 @@ const (
type User struct { type User struct {
handler handler
iamID string
subscription *v1.Subscription subscription *v1.Subscription
queries *query2.Queries queries *query2.Queries
} }
func newUser( func newUser(
handler handler, handler handler,
iamID string,
queries *query2.Queries, queries *query2.Queries,
) *User { ) *User {
h := &User{ h := &User{
handler: handler, handler: handler,
iamID: iamID,
queries: queries, queries: queries,
} }

View File

@ -33,7 +33,7 @@ type EsRepository struct {
eventstore.OrgRepository eventstore.OrgRepository
} }
func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, keyConfig *crypto.KeyConfig, assetsPrefix string) (*EsRepository, error) { func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, keyConfig *crypto.KeyConfig, assetsPrefix string, userCrypto *crypto.AESCrypto) (*EsRepository, error) {
es, err := v1.Start(dbClient) es, err := v1.Start(dbClient)
if err != nil { if err != nil {
return nil, err return nil, err
@ -80,6 +80,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma
AuthRequests: authReq, AuthRequests: authReq,
View: view, View: view,
Eventstore: es, Eventstore: es,
UserCodeAlg: userCrypto,
UserSessionViewProvider: view, UserSessionViewProvider: view,
UserViewProvider: view, UserViewProvider: view,
UserCommandProvider: command, UserCommandProvider: command,
@ -96,7 +97,6 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma
MFAInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MFAInitSkip, MFAInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MFAInitSkip,
SecondFactorCheckLifeTime: systemDefaults.VerificationLifetimes.SecondFactorCheck, SecondFactorCheckLifeTime: systemDefaults.VerificationLifetimes.SecondFactorCheck,
MultiFactorCheckLifeTime: systemDefaults.VerificationLifetimes.MultiFactorCheck, MultiFactorCheckLifeTime: systemDefaults.VerificationLifetimes.MultiFactorCheck,
IAMID: systemDefaults.IamID,
}, },
eventstore.TokenRepo{ eventstore.TokenRepo{
View: view, View: view,

View File

@ -31,8 +31,7 @@ func (h *handler) Eventstore() v1.Eventstore {
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, systemDefaults sd.SystemDefaults) []query.Handler { func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, systemDefaults sd.SystemDefaults) []query.Handler {
return []query.Handler{ return []query.Handler{
newUserGrant( newUserGrant(
handler{view, bulkLimit, configs.cycleDuration("UserGrants"), errorCount, es}, handler{view, bulkLimit, configs.cycleDuration("UserGrants"), errorCount, es}),
systemDefaults.IamID),
newUserMembership( newUserMembership(
handler{view, bulkLimit, configs.cycleDuration("UserMemberships"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("UserMemberships"), errorCount, es}),
} }

View File

@ -29,18 +29,15 @@ const (
type UserGrant struct { type UserGrant struct {
handler handler
iamID string
iamProjectID string iamProjectID string
subscription *v1.Subscription subscription *v1.Subscription
} }
func newUserGrant( func newUserGrant(
handler handler, handler handler,
iamID string,
) *UserGrant { ) *UserGrant {
h := &UserGrant{ h := &UserGrant{
handler: handler, handler: handler,
iamID: iamID,
} }
h.subscribe() h.subscribe()
@ -151,15 +148,15 @@ func (u *UserGrant) processIAMMember(event *es_models.Event, rolePrefix string,
case iam_es_model.IAMMemberAdded, iam_es_model.IAMMemberChanged: case iam_es_model.IAMMemberAdded, iam_es_model.IAMMemberChanged:
member.SetData(event) member.SetData(event)
grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID) grant, err := u.view.UserGrantByIDs(domain.IAMID, u.iamProjectID, member.UserID)
if err != nil && !errors.IsNotFound(err) { if err != nil && !errors.IsNotFound(err) {
return err return err
} }
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
grant = &view_model.UserGrantView{ grant = &view_model.UserGrantView{
ID: u.iamProjectID + member.UserID, ID: u.iamProjectID + member.UserID,
ResourceOwner: u.iamID, ResourceOwner: domain.IAMID,
OrgName: u.iamID, OrgName: domain.IAMID,
ProjectID: u.iamProjectID, ProjectID: u.iamProjectID,
UserID: member.UserID, UserID: member.UserID,
RoleKeys: member.Roles, RoleKeys: member.Roles,
@ -182,7 +179,7 @@ func (u *UserGrant) processIAMMember(event *es_models.Event, rolePrefix string,
case iam_es_model.IAMMemberRemoved, case iam_es_model.IAMMemberRemoved,
iam_es_model.IAMMemberCascadeRemoved: iam_es_model.IAMMemberCascadeRemoved:
member.SetData(event) member.SetData(event)
grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID) grant, err := u.view.UserGrantByIDs(domain.IAMID, u.iamProjectID, member.UserID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -20,7 +20,6 @@ import (
usr_repo "github.com/caos/zitadel/internal/repository/user" usr_repo "github.com/caos/zitadel/internal/repository/user"
usr_grant_repo "github.com/caos/zitadel/internal/repository/usergrant" usr_grant_repo "github.com/caos/zitadel/internal/repository/usergrant"
"github.com/caos/zitadel/internal/static" "github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/telemetry/tracing"
webauthn_helper "github.com/caos/zitadel/internal/webauthn" webauthn_helper "github.com/caos/zitadel/internal/webauthn"
) )
@ -32,17 +31,11 @@ type Commands struct {
zitadelRoles []authz.RoleMapping zitadelRoles []authz.RoleMapping
idpConfigSecretCrypto crypto.EncryptionAlgorithm idpConfigSecretCrypto crypto.EncryptionAlgorithm
smtpPasswordCrypto crypto.EncryptionAlgorithm
userPasswordAlg crypto.HashAlgorithm userPasswordAlg crypto.HashAlgorithm
initializeUserCode crypto.Generator
emailVerificationCode crypto.Generator
phoneVerificationCode crypto.Generator
passwordVerificationCode crypto.Generator
passwordlessInitCode crypto.Generator
machineKeyAlg crypto.EncryptionAlgorithm
machineKeySize int machineKeySize int
applicationKeySize int applicationKeySize int
applicationSecretGenerator crypto.Generator
domainVerificationAlg crypto.EncryptionAlgorithm domainVerificationAlg crypto.EncryptionAlgorithm
domainVerificationGenerator crypto.Generator domainVerificationGenerator crypto.Generator
domainVerificationValidator func(domain, token, verifier string, checkType http.CheckType) error domainVerificationValidator func(domain, token, verifier string, checkType http.CheckType) error
@ -60,7 +53,16 @@ type orgFeatureChecker interface {
CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error
} }
func StartCommands(es *eventstore.Eventstore, defaults sd.SystemDefaults, authZConfig authz.Config, staticStore static.Storage, authZRepo authz_repo.Repository, keyConfig *crypto.KeyConfig, webAuthN webauthn_helper.Config) (repo *Commands, err error) { func StartCommands(
es *eventstore.Eventstore,
defaults sd.SystemDefaults,
authZConfig authz.Config,
staticStore static.Storage,
authZRepo authz_repo.Repository,
keyConfig *crypto.KeyConfig,
smtpPasswordEncAlg crypto.EncryptionAlgorithm,
webAuthN webauthn_helper.Config,
) (repo *Commands, err error) {
repo = &Commands{ repo = &Commands{
eventstore: es, eventstore: es,
static: staticStore, static: staticStore,
@ -70,6 +72,7 @@ func StartCommands(es *eventstore.Eventstore, defaults sd.SystemDefaults, authZC
keySize: defaults.KeyConfig.Size, keySize: defaults.KeyConfig.Size,
privateKeyLifetime: defaults.KeyConfig.PrivateKeyLifetime, privateKeyLifetime: defaults.KeyConfig.PrivateKeyLifetime,
publicKeyLifetime: defaults.KeyConfig.PublicKeyLifetime, publicKeyLifetime: defaults.KeyConfig.PublicKeyLifetime,
smtpPasswordCrypto: smtpPasswordEncAlg,
} }
iam_repo.RegisterEventMappers(repo.eventstore) iam_repo.RegisterEventMappers(repo.eventstore)
org.RegisterEventMappers(repo.eventstore) org.RegisterEventMappers(repo.eventstore)
@ -83,17 +86,8 @@ func StartCommands(es *eventstore.Eventstore, defaults sd.SystemDefaults, authZC
if err != nil { if err != nil {
return nil, err return nil, err
} }
userEncryptionAlgorithm, err := crypto.NewAESCrypto(defaults.UserVerificationKey)
if err != nil {
return nil, err
}
repo.initializeUserCode = crypto.NewEncryptionGenerator(defaults.SecretGenerators.InitializeUserCode, userEncryptionAlgorithm)
repo.emailVerificationCode = crypto.NewEncryptionGenerator(defaults.SecretGenerators.EmailVerificationCode, userEncryptionAlgorithm)
repo.phoneVerificationCode = crypto.NewEncryptionGenerator(defaults.SecretGenerators.PhoneVerificationCode, userEncryptionAlgorithm)
repo.passwordVerificationCode = crypto.NewEncryptionGenerator(defaults.SecretGenerators.PasswordVerificationCode, userEncryptionAlgorithm)
repo.passwordlessInitCode = crypto.NewEncryptionGenerator(defaults.SecretGenerators.PasswordlessInitCode, userEncryptionAlgorithm)
repo.userPasswordAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost) repo.userPasswordAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost)
repo.machineKeyAlg = userEncryptionAlgorithm
repo.machineKeySize = int(defaults.SecretGenerators.MachineKeySize) repo.machineKeySize = int(defaults.SecretGenerators.MachineKeySize)
repo.applicationKeySize = int(defaults.SecretGenerators.ApplicationKeySize) repo.applicationKeySize = int(defaults.SecretGenerators.ApplicationKeySize)
@ -107,8 +101,6 @@ func StartCommands(es *eventstore.Eventstore, defaults sd.SystemDefaults, authZC
Issuer: defaults.Multifactors.OTP.Issuer, Issuer: defaults.Multifactors.OTP.Issuer,
}, },
} }
passwordAlg := crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost)
repo.applicationSecretGenerator = crypto.NewHashGenerator(defaults.SecretGenerators.ClientSecretGenerator, passwordAlg)
repo.domainVerificationAlg, err = crypto.NewAESCrypto(defaults.DomainVerification.VerificationKey) repo.domainVerificationAlg, err = crypto.NewAESCrypto(defaults.DomainVerification.VerificationKey)
if err != nil { if err != nil {
@ -132,19 +124,6 @@ func StartCommands(es *eventstore.Eventstore, defaults sd.SystemDefaults, authZC
return repo, nil return repo, nil
} }
func (c *Commands) getIAMWriteModel(ctx context.Context) (_ *IAMWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel := NewIAMWriteModel()
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}
func AppendAndReduce(object interface { func AppendAndReduce(object interface {
AppendEvents(...eventstore.Event) AppendEvents(...eventstore.Event)
Reduce() error Reduce() error

View File

@ -7,6 +7,8 @@ import (
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam" "github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/telemetry/tracing"
"golang.org/x/text/language"
) )
//TODO: private as soon as setup uses query //TODO: private as soon as setup uses query
@ -40,3 +42,33 @@ func (c *Commands) setIAMProject(ctx context.Context, iamAgg *eventstore.Aggrega
} }
return iam.NewIAMProjectSetEvent(ctx, iamAgg, projectID), nil return iam.NewIAMProjectSetEvent(ctx, iamAgg, projectID), nil
} }
func (c *Commands) SetDefaultLanguage(ctx context.Context, language language.Tag) (*domain.ObjectDetails, error) {
iamWriteModel, err := c.getIAMWriteModel(ctx)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&iamWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, iam.NewDefaultLanguageSetEvent(ctx, iamAgg, language))
if err != nil {
return nil, err
}
err = AppendAndReduce(iamWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&iamWriteModel.WriteModel), nil
}
func (c *Commands) getIAMWriteModel(ctx context.Context) (_ *IAMWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel := NewIAMWriteModel()
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -15,9 +15,8 @@ import (
func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPConfig) (*domain.IDPConfig, error) { func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPConfig) (*domain.IDPConfig, error) {
if config.OIDCConfig == nil && config.JWTConfig == nil { if config.OIDCConfig == nil && config.JWTConfig == nil {
return nil, errors.ThrowInvalidArgument(nil, "IAM-eUpQU", "Errors.idp.config.notset") return nil, caos_errs.ThrowInvalidArgument(nil, "IDP-s8nn3", "Errors.IDPConfig.Invalid")
} }
idpConfigID, err := c.idGenerator.Next() idpConfigID, err := c.idGenerator.Next()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -4,6 +4,7 @@ import (
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam" "github.com/caos/zitadel/internal/repository/iam"
"golang.org/x/text/language"
) )
type IAMWriteModel struct { type IAMWriteModel struct {
@ -12,8 +13,9 @@ type IAMWriteModel struct {
SetUpStarted domain.Step SetUpStarted domain.Step
SetUpDone domain.Step SetUpDone domain.Step
GlobalOrgID string GlobalOrgID string
ProjectID string ProjectID string
DefaultLanguage language.Tag
} }
func NewIAMWriteModel() *IAMWriteModel { func NewIAMWriteModel() *IAMWriteModel {
@ -32,6 +34,8 @@ func (wm *IAMWriteModel) Reduce() error {
wm.ProjectID = e.ProjectID wm.ProjectID = e.ProjectID
case *iam.GlobalOrgSetEvent: case *iam.GlobalOrgSetEvent:
wm.GlobalOrgID = e.OrgID wm.GlobalOrgID = e.OrgID
case *iam.DefaultLanguageSetEvent:
wm.DefaultLanguage = e.Language
case *iam.SetupStepEvent: case *iam.SetupStepEvent:
if e.Done { if e.Done {
wm.SetUpDone = e.Step wm.SetUpDone = e.Step
@ -52,6 +56,7 @@ func (wm *IAMWriteModel) Query() *eventstore.SearchQueryBuilder {
EventTypes( EventTypes(
iam.ProjectSetEventType, iam.ProjectSetEventType,
iam.GlobalOrgSetEventType, iam.GlobalOrgSetEventType,
iam.DefaultLanguageSetEventType,
iam.SetupStartedEventType, iam.SetupStartedEventType,
iam.SetupDoneEventType). iam.SetupDoneEventType).
Builder() Builder()

View File

@ -0,0 +1,140 @@
package command
import (
"context"
"time"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
)
type IAMSecretGeneratorConfigWriteModel struct {
eventstore.WriteModel
GeneratorType domain.SecretGeneratorType
Length uint
Expiry time.Duration
IncludeLowerLetters bool
IncludeUpperLetters bool
IncludeDigits bool
IncludeSymbols bool
State domain.SecretGeneratorState
}
func NewIAMSecretGeneratorConfigWriteModel(GeneratorType domain.SecretGeneratorType) *IAMSecretGeneratorConfigWriteModel {
return &IAMSecretGeneratorConfigWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
ResourceOwner: domain.IAMID,
},
GeneratorType: GeneratorType,
}
}
func (wm *IAMSecretGeneratorConfigWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *iam.SecretGeneratorAddedEvent:
if wm.GeneratorType != e.GeneratorType {
continue
}
wm.Length = e.Length
wm.Expiry = e.Expiry
wm.IncludeLowerLetters = e.IncludeLowerLetters
wm.IncludeUpperLetters = e.IncludeUpperLetters
wm.IncludeDigits = e.IncludeDigits
wm.IncludeSymbols = e.IncludeDigits
wm.State = domain.SecretGeneratorStateActive
case *iam.SecretGeneratorChangedEvent:
if wm.GeneratorType != e.GeneratorType {
continue
}
if e.Length != nil {
wm.Length = *e.Length
}
if e.Expiry != nil {
wm.Expiry = *e.Expiry
}
if e.IncludeUpperLetters != nil {
wm.IncludeUpperLetters = *e.IncludeUpperLetters
}
if e.IncludeLowerLetters != nil {
wm.IncludeLowerLetters = *e.IncludeLowerLetters
}
if e.IncludeDigits != nil {
wm.IncludeDigits = *e.IncludeDigits
}
if e.IncludeSymbols != nil {
wm.IncludeSymbols = *e.IncludeSymbols
}
case *iam.SecretGeneratorRemovedEvent:
if wm.GeneratorType != e.GeneratorType {
continue
}
wm.State = domain.SecretGeneratorStateRemoved
wm.Length = 0
wm.Expiry = 0
wm.IncludeLowerLetters = false
wm.IncludeUpperLetters = false
wm.IncludeDigits = false
wm.IncludeSymbols = false
}
}
return wm.WriteModel.Reduce()
}
func (wm *IAMSecretGeneratorConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(iam.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
iam.SecretGeneratorAddedEventType,
iam.SecretGeneratorChangedEventType,
iam.SecretGeneratorRemovedEventType).
Builder()
}
func (wm *IAMSecretGeneratorConfigWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
generatorType domain.SecretGeneratorType,
length uint,
expiry time.Duration,
includeLowerLetters,
includeUpperLetters,
includeDigits,
includeSymbols bool,
) (*iam.SecretGeneratorChangedEvent, bool, error) {
changes := make([]iam.SecretGeneratorChanges, 0)
var err error
if wm.Length != length {
changes = append(changes, iam.ChangeSecretGeneratorLength(length))
}
if wm.Expiry != expiry {
changes = append(changes, iam.ChangeSecretGeneratorExpiry(expiry))
}
if wm.IncludeLowerLetters != includeLowerLetters {
changes = append(changes, iam.ChangeSecretGeneratorIncludeLowerLetters(includeLowerLetters))
}
if wm.IncludeUpperLetters != includeUpperLetters {
changes = append(changes, iam.ChangeSecretGeneratorIncludeUpperLetters(includeUpperLetters))
}
if wm.IncludeDigits != includeDigits {
changes = append(changes, iam.ChangeSecretGeneratorIncludeDigits(includeDigits))
}
if wm.IncludeSymbols != includeSymbols {
changes = append(changes, iam.ChangeSecretGeneratorIncludeSymbols(includeSymbols))
}
if len(changes) == 0 {
return nil, false, nil
}
changeEvent, err := iam.NewSecretGeneratorChangeEvent(ctx, aggregate, generatorType, changes)
if err != nil {
return nil, false, err
}
return changeEvent, true, nil
}

View File

@ -0,0 +1,118 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/iam"
)
func (c *Commands) AddSecretGeneratorConfig(ctx context.Context, generatorType domain.SecretGeneratorType, config *crypto.GeneratorConfig) (*domain.ObjectDetails, error) {
if generatorType == domain.SecretGeneratorTypeUnspecified {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-0pkwf", "Errors.SecretGenerator.TypeMissing")
}
generatorWriteModel, err := c.getSecretConfig(ctx, generatorType)
if err != nil {
return nil, err
}
if generatorWriteModel.State == domain.SecretGeneratorStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-3n9ls", "Errors.SecretGenerator.AlreadyExists")
}
iamAgg := IAMAggregateFromWriteModel(&generatorWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, iam.NewSecretGeneratorAddedEvent(
ctx,
iamAgg,
generatorType,
config.Length,
config.Expiry,
config.IncludeLowerLetters,
config.IncludeUpperLetters,
config.IncludeDigits,
config.IncludeSymbols))
if err != nil {
return nil, err
}
err = AppendAndReduce(generatorWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&generatorWriteModel.WriteModel), nil
}
func (c *Commands) ChangeSecretGeneratorConfig(ctx context.Context, generatorType domain.SecretGeneratorType, config *crypto.GeneratorConfig) (*domain.ObjectDetails, error) {
if generatorType == domain.SecretGeneratorTypeUnspecified {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-33k9f", "Errors.SecretGenerator.TypeMissing")
}
generatorWriteModel, err := c.getSecretConfig(ctx, generatorType)
if err != nil {
return nil, err
}
if generatorWriteModel.State == domain.SecretGeneratorStateUnspecified || generatorWriteModel.State == domain.SecretGeneratorStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SecretGenerator.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&generatorWriteModel.WriteModel)
changedEvent, hasChanged, err := generatorWriteModel.NewChangedEvent(
ctx,
iamAgg,
generatorType,
config.Length,
config.Expiry,
config.IncludeLowerLetters,
config.IncludeUpperLetters,
config.IncludeDigits,
config.IncludeSymbols)
if err != nil {
return nil, err
}
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound")
}
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(generatorWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&generatorWriteModel.WriteModel), nil
}
func (c *Commands) RemoveSecretGeneratorConfig(ctx context.Context, generatorType domain.SecretGeneratorType) (*domain.ObjectDetails, error) {
if generatorType == domain.SecretGeneratorTypeUnspecified {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2j9lw", "Errors.SecretGenerator.TypeMissing")
}
generatorWriteModel, err := c.getSecretConfig(ctx, generatorType)
if err != nil {
return nil, err
}
if generatorWriteModel.State == domain.SecretGeneratorStateUnspecified || generatorWriteModel.State == domain.SecretGeneratorStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-b8les", "Errors.SecretGenerator.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&generatorWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, iam.NewSecretGeneratorRemovedEvent(ctx, iamAgg, generatorType))
if err != nil {
return nil, err
}
err = AppendAndReduce(generatorWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&generatorWriteModel.WriteModel), nil
}
func (c *Commands) getSecretConfig(ctx context.Context, generatorType domain.SecretGeneratorType) (_ *IAMSecretGeneratorConfigWriteModel, err error) {
writeModel := NewIAMSecretGeneratorConfigWriteModel(generatorType)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -0,0 +1,512 @@
package command
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/iam"
)
func TestCommandSide_AddSecretGenerator(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
generator *crypto.GeneratorConfig
generatorType domain.SecretGeneratorType
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid empty type, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
generator: &crypto.GeneratorConfig{},
generatorType: domain.SecretGeneratorTypeUnspecified,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "secret generator config, error already exists",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSecretGeneratorAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode,
4,
time.Hour*1,
true,
true,
true,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
generator: &crypto.GeneratorConfig{
Length: 4,
Expiry: 1 * time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
generatorType: domain.SecretGeneratorTypeInitCode,
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add secret generator, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(iam.NewSecretGeneratorAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode,
4,
time.Hour*1,
true,
true,
true,
true,
),
),
},
uniqueConstraintsFromEventConstraint(iam.NewAddSecretGeneratorTypeUniqueConstraint(domain.SecretGeneratorTypeInitCode)),
),
),
},
args: args{
ctx: context.Background(),
generator: &crypto.GeneratorConfig{
Length: 4,
Expiry: 1 * time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
generatorType: domain.SecretGeneratorTypeInitCode,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddSecretGeneratorConfig(tt.args.ctx, tt.args.generatorType, tt.args.generator)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
generator *crypto.GeneratorConfig
generatorType domain.SecretGeneratorType
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "empty generatortype, invalid error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
generator: &crypto.GeneratorConfig{},
generatorType: domain.SecretGeneratorTypeUnspecified,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "generator not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
generatorType: domain.SecretGeneratorTypeInitCode,
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "generator removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSecretGeneratorAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode,
4,
time.Hour*1,
true,
true,
true,
true,
),
),
eventFromEventPusher(
iam.NewSecretGeneratorRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode),
),
),
),
},
args: args{
ctx: context.Background(),
generatorType: domain.SecretGeneratorTypeInitCode,
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSecretGeneratorAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode,
4,
time.Hour*1,
true,
true,
true,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
generator: &crypto.GeneratorConfig{
Length: 4,
Expiry: 1 * time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
generatorType: domain.SecretGeneratorTypeInitCode,
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "secret generator change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSecretGeneratorAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode,
4,
time.Hour*1,
true,
true,
true,
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newSecretGeneratorChangedEvent(context.Background(),
domain.SecretGeneratorTypeInitCode,
8,
time.Hour*2,
false,
false,
false,
false),
),
},
),
),
},
args: args{
ctx: context.Background(),
generator: &crypto.GeneratorConfig{
Length: 8,
Expiry: 2 * time.Hour,
IncludeLowerLetters: false,
IncludeUpperLetters: false,
IncludeDigits: false,
IncludeSymbols: false,
},
generatorType: domain.SecretGeneratorTypeInitCode,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeSecretGeneratorConfig(tt.args.ctx, tt.args.generatorType, tt.args.generator)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveSecretGenerator(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
generatorType domain.SecretGeneratorType
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "empty type, invalid error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
generatorType: domain.SecretGeneratorTypeUnspecified,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "generator not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
generatorType: domain.SecretGeneratorTypeInitCode,
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "generator removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSecretGeneratorAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode,
4,
time.Hour*1,
true,
true,
true,
true,
),
),
eventFromEventPusher(
iam.NewSecretGeneratorRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode),
),
),
),
},
args: args{
ctx: context.Background(),
generatorType: domain.SecretGeneratorTypeInitCode,
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "generator config remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSecretGeneratorAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode,
4,
time.Hour*1,
true,
true,
true,
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewSecretGeneratorRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecretGeneratorTypeInitCode),
),
},
uniqueConstraintsFromEventConstraint(iam.NewRemoveSecretGeneratorTypeUniqueConstraint(domain.SecretGeneratorTypeInitCode)),
),
),
},
args: args{
ctx: context.Background(),
generatorType: domain.SecretGeneratorTypeInitCode,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveSecretGeneratorConfig(tt.args.ctx, tt.args.generatorType)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newSecretGeneratorChangedEvent(ctx context.Context, generatorType domain.SecretGeneratorType, length uint, expiry time.Duration, lowerCase, upperCase, digits, symbols bool) *iam.SecretGeneratorChangedEvent {
changes := []iam.SecretGeneratorChanges{
iam.ChangeSecretGeneratorLength(length),
iam.ChangeSecretGeneratorExpiry(expiry),
iam.ChangeSecretGeneratorIncludeLowerLetters(lowerCase),
iam.ChangeSecretGeneratorIncludeUpperLetters(upperCase),
iam.ChangeSecretGeneratorIncludeDigits(digits),
iam.ChangeSecretGeneratorIncludeSymbols(symbols),
}
event, _ := iam.NewSecretGeneratorChangeEvent(ctx,
&iam.NewAggregate().Aggregate,
generatorType,
changes,
)
return event
}

View File

@ -0,0 +1,106 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
)
type IAMSMTPConfigWriteModel struct {
eventstore.WriteModel
SenderAddress string
SenderName string
TLS bool
Host string
User string
Password *crypto.CryptoValue
State domain.SMTPConfigState
}
func NewIAMSMTPConfigWriteModel() *IAMSMTPConfigWriteModel {
return &IAMSMTPConfigWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
ResourceOwner: domain.IAMID,
},
}
}
func (wm *IAMSMTPConfigWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *iam.SMTPConfigAddedEvent:
wm.TLS = e.TLS
wm.SenderAddress = e.SenderAddress
wm.SenderName = e.SenderName
wm.Host = e.Host
wm.User = e.User
wm.Password = e.Password
wm.State = domain.SMTPConfigStateActive
case *iam.SMTPConfigChangedEvent:
if e.TLS != nil {
wm.TLS = *e.TLS
}
if e.FromAddress != nil {
wm.SenderAddress = *e.FromAddress
}
if e.FromName != nil {
wm.SenderName = *e.FromName
}
if e.Host != nil {
wm.Host = *e.Host
}
if e.User != nil {
wm.User = *e.User
}
}
}
return wm.WriteModel.Reduce()
}
func (wm *IAMSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(iam.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
iam.SMTPConfigAddedEventType,
iam.SMTPConfigChangedEventType,
iam.SMTPConfigPasswordChangedEventType).
Builder()
}
func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, smtpHost, smtpUser string) (*iam.SMTPConfigChangedEvent, bool, error) {
changes := make([]iam.SMTPConfigChanges, 0)
var err error
if wm.TLS != tls {
changes = append(changes, iam.ChangeSMTPConfigTLS(tls))
}
if wm.SenderAddress != fromAddress {
changes = append(changes, iam.ChangeSMTPConfigFromAddress(fromAddress))
}
if wm.SenderName != fromName {
changes = append(changes, iam.ChangeSMTPConfigFromName(fromName))
}
if wm.Host != smtpHost {
changes = append(changes, iam.ChangeSMTPConfigSMTPHost(smtpHost))
}
if wm.User != smtpUser {
changes = append(changes, iam.ChangeSMTPConfigSMTPUser(smtpUser))
}
if len(changes) == 0 {
return nil, false, nil
}
changeEvent, err := iam.NewSMTPConfigChangeEvent(ctx, aggregate, changes)
if err != nil {
return nil, false, err
}
return changeEvent, true, nil
}

View File

@ -3,6 +3,7 @@ package command
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
@ -31,7 +32,7 @@ func (c *Commands) checkOrgExists(ctx context.Context, orgID string) error {
return nil return nil
} }
func (c *Commands) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human, claimedUserIDs []string, selfregistered bool) (*domain.ObjectDetails, error) { func (c *Commands) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, claimedUserIDs []string, selfregistered bool) (*domain.ObjectDetails, error) {
orgIAMPolicy, err := c.getDefaultOrgIAMPolicy(ctx) orgIAMPolicy, err := c.getDefaultOrgIAMPolicy(ctx)
if err != nil { if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.IAM.OrgIAMPolicy.NotFound") return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.IAM.OrgIAMPolicy.NotFound")
@ -40,7 +41,7 @@ func (c *Commands) SetUpOrg(ctx context.Context, organisation *domain.Org, admin
if err != nil { if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.IAM.PasswordComplexity.NotFound") return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.IAM.PasswordComplexity.NotFound")
} }
_, orgWriteModel, _, _, events, err := c.setUpOrg(ctx, organisation, admin, orgIAMPolicy, pwPolicy, claimedUserIDs, selfregistered) _, orgWriteModel, _, _, events, err := c.setUpOrg(ctx, organisation, admin, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, claimedUserIDs, selfregistered)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -168,6 +169,8 @@ func (c *Commands) setUpOrg(
admin *domain.Human, admin *domain.Human,
loginPolicy *domain.OrgIAMPolicy, loginPolicy *domain.OrgIAMPolicy,
pwPolicy *domain.PasswordComplexityPolicy, pwPolicy *domain.PasswordComplexityPolicy,
initCodeGenerator crypto.Generator,
phoneCodeGenerator crypto.Generator,
claimedUserIDs []string, claimedUserIDs []string,
selfregistered bool, selfregistered bool,
) (orgAgg *eventstore.Aggregate, org *OrgWriteModel, human *HumanWriteModel, orgMember *OrgMemberWriteModel, events []eventstore.Command, err error) { ) (orgAgg *eventstore.Aggregate, org *OrgWriteModel, human *HumanWriteModel, orgMember *OrgMemberWriteModel, events []eventstore.Command, err error) {
@ -178,9 +181,9 @@ func (c *Commands) setUpOrg(
var userEvents []eventstore.Command var userEvents []eventstore.Command
if selfregistered { if selfregistered {
userEvents, human, err = c.registerHuman(ctx, orgAgg.ID, admin, nil, loginPolicy, pwPolicy) userEvents, human, err = c.registerHuman(ctx, orgAgg.ID, admin, nil, loginPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
} else { } else {
userEvents, human, err = c.addHuman(ctx, orgAgg.ID, admin, loginPolicy, pwPolicy) userEvents, human, err = c.addHuman(ctx, orgAgg.ID, admin, loginPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
} }
if err != nil { if err != nil {
return nil, nil, nil, nil, nil, err return nil, nil, nil, nil, nil, err

View File

@ -13,7 +13,7 @@ import (
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
) )
func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.APIApp, resourceOwner string) (_ *domain.APIApp, err error) { func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) {
if application == nil || application.AggregateID == "" { if application == nil || application.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Application.Invalid") return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Application.Invalid")
} }
@ -23,7 +23,7 @@ func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.AP
} }
addedApplication := NewAPIApplicationWriteModel(application.AggregateID, resourceOwner) addedApplication := NewAPIApplicationWriteModel(application.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
events, stringPw, err := c.addAPIApplication(ctx, projectAgg, project, application, resourceOwner) events, stringPw, err := c.addAPIApplication(ctx, projectAgg, project, application, resourceOwner, appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -41,7 +41,7 @@ func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.AP
return result, nil return result, nil
} }
func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, apiAppApp *domain.APIApp, resourceOwner string) (events []eventstore.Command, stringPW string, err error) { func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, apiAppApp *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (events []eventstore.Command, stringPW string, err error) {
if !apiAppApp.IsValid() { if !apiAppApp.IsValid() {
return nil, "", caos_errs.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid") return nil, "", caos_errs.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
} }
@ -59,7 +59,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
stringPw, err = domain.SetNewClientSecretIfNeeded(apiAppApp, c.applicationSecretGenerator) stringPw, err = domain.SetNewClientSecretIfNeeded(apiAppApp, appSecretGenerator)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -113,7 +113,7 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
return apiWriteModelToAPIConfig(existingAPI), nil return apiWriteModelToAPIConfig(existingAPI), nil
} }
func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.APIApp, error) { func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.APIApp, error) {
if projectID == "" || appID == "" { if projectID == "" || appID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
} }
@ -128,7 +128,7 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
if !existingAPI.IsAPI() { if !existingAPI.IsAPI() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI")
} }
cryptoSecret, stringPW, err := domain.NewClientSecret(c.applicationSecretGenerator) cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,6 +2,8 @@ package command
import ( import (
"context" "context"
"testing"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
@ -12,19 +14,18 @@ import (
id_mock "github.com/caos/zitadel/internal/id/mock" id_mock "github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/project" "github.com/caos/zitadel/internal/repository/project"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing"
) )
func TestCommandSide_AddAPIApplication(t *testing.T) { func TestCommandSide_AddAPIApplication(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
apiApp *domain.APIApp apiApp *domain.APIApp
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.APIApp want *domain.APIApp
@ -144,8 +145,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")), uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -156,7 +156,8 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
AppName: "app", AppName: "app",
AuthMethodType: domain.APIAuthMethodTypeBasic, AuthMethodType: domain.APIAuthMethodTypeBasic,
}, },
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.APIApp{ want: &domain.APIApp{
@ -238,11 +239,10 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
applicationSecretGenerator: tt.fields.secretGenerator,
} }
got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner) got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -472,14 +472,14 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
appID string appID string
projectID string projectID string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.APIApp want *domain.APIApp
@ -585,13 +585,13 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
projectID: "project1", projectID: "project1",
appID: "app1", appID: "app1",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.APIApp{ want: &domain.APIApp{
@ -612,10 +612,9 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
applicationSecretGenerator: tt.fields.secretGenerator,
} }
got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner) got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -2,6 +2,8 @@ package command
import ( import (
"context" "context"
"testing"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
@ -11,15 +13,13 @@ import (
id_mock "github.com/caos/zitadel/internal/id/mock" id_mock "github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/project" "github.com/caos/zitadel/internal/repository/project"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing"
) )
func TestCommandSide_AddAPIApplicationKey(t *testing.T) { func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
secretGenerator crypto.Generator keySize int
keySize int
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -126,8 +126,7 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
), ),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -173,9 +172,8 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
), ),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
secretGenerator: GetMockSecretGenerator(t), keySize: 10,
keySize: 10,
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -195,10 +193,9 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
applicationSecretGenerator: tt.fields.secretGenerator, applicationKeySize: tt.fields.keySize,
applicationKeySize: tt.fields.keySize,
} }
got, err := r.AddApplicationKey(tt.args.ctx, tt.args.key, tt.args.resourceOwner) got, err := r.AddApplicationKey(tt.args.ctx, tt.args.key, tt.args.resourceOwner)
if tt.res.err == nil { if tt.res.err == nil {

View File

@ -13,7 +13,7 @@ import (
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
) )
func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.OIDCApp, resourceOwner string) (_ *domain.OIDCApp, err error) { func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) {
if application == nil || application.AggregateID == "" { if application == nil || application.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Application.Invalid") return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Application.Invalid")
} }
@ -23,7 +23,7 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.O
} }
addedApplication := NewOIDCApplicationWriteModel(application.AggregateID, resourceOwner) addedApplication := NewOIDCApplicationWriteModel(application.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
events, stringPw, err := c.addOIDCApplication(ctx, projectAgg, project, application, resourceOwner) events, stringPw, err := c.addOIDCApplication(ctx, projectAgg, project, application, resourceOwner, appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -42,7 +42,7 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.O
return result, nil return result, nil
} }
func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, oidcApp *domain.OIDCApp, resourceOwner string) (events []eventstore.Command, stringPW string, err error) { func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, oidcApp *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (events []eventstore.Command, stringPW string, err error) {
if oidcApp.AppName == "" || !oidcApp.IsValid() { if oidcApp.AppName == "" || !oidcApp.IsValid() {
return nil, "", caos_errs.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Application.Invalid") return nil, "", caos_errs.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Application.Invalid")
} }
@ -60,7 +60,7 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
stringPw, err = domain.SetNewClientSecretIfNeeded(oidcApp, c.applicationSecretGenerator) stringPw, err = domain.SetNewClientSecretIfNeeded(oidcApp, appSecretGenerator)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -142,7 +142,7 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
return result, nil return result, nil
} }
func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.OIDCApp, error) { func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.OIDCApp, error) {
if projectID == "" || appID == "" { if projectID == "" || appID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
} }
@ -157,7 +157,7 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a
if !existingOIDC.IsOIDC() { if !existingOIDC.IsOIDC() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC")
} }
cryptoSecret, stringPW, err := domain.NewClientSecret(c.applicationSecretGenerator) cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -20,14 +20,14 @@ import (
func TestCommandSide_AddOIDCApplication(t *testing.T) { func TestCommandSide_AddOIDCApplication(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
oidcApp *domain.OIDCApp oidcApp *domain.OIDCApp
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.OIDCApp want *domain.OIDCApp
@ -160,8 +160,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")), uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -185,7 +184,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
ClockSkew: time.Second * 1, ClockSkew: time.Second * 1,
AdditionalOrigins: []string{"https://sub.test.ch"}, AdditionalOrigins: []string{"https://sub.test.ch"},
}, },
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.OIDCApp{ want: &domain.OIDCApp{
@ -220,11 +220,10 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
applicationSecretGenerator: tt.fields.secretGenerator,
} }
got, err := r.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner) got, err := r.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -549,14 +548,14 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
appID string appID string
projectID string projectID string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.OIDCApp want *domain.OIDCApp
@ -675,13 +674,13 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
projectID: "project1", projectID: "project1",
appID: "app1", appID: "app1",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.OIDCApp{ want: &domain.OIDCApp{
@ -715,10 +714,9 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
applicationSecretGenerator: tt.fields.secretGenerator,
} }
got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner) got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -3,6 +3,7 @@ package command
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/v1/models" "github.com/caos/zitadel/internal/eventstore/v1/models"
@ -134,7 +135,10 @@ func (c *Commands) SetupStep1(ctx context.Context, step1 *Step1) error {
EmailAddress: organisation.Owner.Email, EmailAddress: organisation.Owner.Email,
IsEmailVerified: true, IsEmailVerified: true,
}, },
}, orgIAMPolicy, pwPolicy, nil, false) }, orgIAMPolicy, pwPolicy,
nil, //TODO: Code Generator missing! Should be setuped in step1 create iam
nil, //TODO: Code Generator missing! Should be setuped in step1 create iam
nil, false)
if err != nil { if err != nil {
return err return err
} }
@ -180,14 +184,16 @@ func (c *Commands) SetupStep1(ctx context.Context, step1 *Step1) error {
} }
//create applications //create applications
for _, app := range proj.OIDCApps { for _, app := range proj.OIDCApps {
applicationEvents, err := setUpOIDCApplication(ctx, c, projectWriteModel, project, app, orgAgg.ID) //TODO: Add Secret Generator
applicationEvents, err := setUpOIDCApplication(ctx, c, projectWriteModel, project, app, orgAgg.ID, nil)
if err != nil { if err != nil {
return err return err
} }
events = append(events, applicationEvents...) events = append(events, applicationEvents...)
} }
for _, app := range proj.APIs { for _, app := range proj.APIs {
applicationEvents, err := setUpAPI(ctx, c, projectWriteModel, project, app, orgAgg.ID) //TODO: Add Secret Generator
applicationEvents, err := setUpAPI(ctx, c, projectWriteModel, project, app, orgAgg.ID, nil)
if err != nil { if err != nil {
return err return err
} }
@ -205,7 +211,7 @@ func (c *Commands) SetupStep1(ctx context.Context, step1 *Step1) error {
return nil return nil
} }
func setUpOIDCApplication(ctx context.Context, r *Commands, projectWriteModel *ProjectWriteModel, project *domain.Project, oidcApp OIDCApp, resourceOwner string) ([]eventstore.Command, error) { func setUpOIDCApplication(ctx context.Context, r *Commands, projectWriteModel *ProjectWriteModel, project *domain.Project, oidcApp OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) ([]eventstore.Command, error) {
app := &domain.OIDCApp{ app := &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{ ObjectRoot: models.ObjectRoot{
AggregateID: projectWriteModel.AggregateID, AggregateID: projectWriteModel.AggregateID,
@ -220,7 +226,7 @@ func setUpOIDCApplication(ctx context.Context, r *Commands, projectWriteModel *P
} }
projectAgg := ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel)
events, _, err := r.addOIDCApplication(ctx, projectAgg, project, app, resourceOwner) events, _, err := r.addOIDCApplication(ctx, projectAgg, project, app, resourceOwner, appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -228,7 +234,7 @@ func setUpOIDCApplication(ctx context.Context, r *Commands, projectWriteModel *P
return events, nil return events, nil
} }
func setUpAPI(ctx context.Context, r *Commands, projectWriteModel *ProjectWriteModel, project *domain.Project, apiApp API, resourceOwner string) ([]eventstore.Command, error) { func setUpAPI(ctx context.Context, r *Commands, projectWriteModel *ProjectWriteModel, project *domain.Project, apiApp API, resourceOwner string, appSecretGenerator crypto.Generator) ([]eventstore.Command, error) {
app := &domain.APIApp{ app := &domain.APIApp{
ObjectRoot: models.ObjectRoot{ ObjectRoot: models.ObjectRoot{
AggregateID: projectWriteModel.AggregateID, AggregateID: projectWriteModel.AggregateID,
@ -238,7 +244,7 @@ func setUpAPI(ctx context.Context, r *Commands, projectWriteModel *ProjectWriteM
} }
projectAgg := ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel)
events, _, err := r.addAPIApplication(ctx, projectAgg, project, app, resourceOwner) events, _, err := r.addAPIApplication(ctx, projectAgg, project, app, resourceOwner, appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }

119
internal/command/smtp.go Normal file
View File

@ -0,0 +1,119 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/repository/iam"
)
func (c *Commands) AddSMTPConfig(ctx context.Context, config *smtp.EmailConfig) (*domain.ObjectDetails, error) {
smtpConfigWriteModel, err := c.getSMTPConfig(ctx)
if err != nil {
return nil, err
}
if smtpConfigWriteModel.State == domain.SMTPConfigStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-en9lw", "Errors.SMTPConfig.AlreadyExists")
}
var smtpPassword *crypto.CryptoValue
if config.SMTP.Password != "" {
smtpPassword, err = crypto.Encrypt([]byte(config.SMTP.Password), c.smtpPasswordCrypto)
if err != nil {
return nil, err
}
}
iamAgg := IAMAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, iam.NewSMTPConfigAddedEvent(
ctx,
iamAgg,
config.Tls,
config.From,
config.FromName,
config.SMTP.Host,
config.SMTP.User,
smtpPassword))
if err != nil {
return nil, err
}
err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil
}
func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *smtp.EmailConfig) (*domain.ObjectDetails, error) {
smtpConfigWriteModel, err := c.getSMTPConfig(ctx)
if err != nil {
return nil, err
}
if smtpConfigWriteModel.State == domain.SMTPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SMTPConfig.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel)
changedEvent, hasChanged, err := smtpConfigWriteModel.NewChangedEvent(
ctx,
iamAgg,
config.Tls,
config.From,
config.FromName,
config.SMTP.Host,
config.SMTP.User)
if err != nil {
return nil, err
}
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound")
}
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil
}
func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, password string) (*domain.ObjectDetails, error) {
smtpConfigWriteModel, err := c.getSMTPConfig(ctx)
if err != nil {
return nil, err
}
if smtpConfigWriteModel.State == domain.SMTPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SMTPConfig.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel)
newPW, err := crypto.Encrypt([]byte(password), c.smtpPasswordCrypto)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, iam.NewSMTPConfigPasswordChangedEvent(
ctx,
iamAgg,
newPW))
if err != nil {
return nil, err
}
err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil
}
func (c *Commands) getSMTPConfig(ctx context.Context) (_ *IAMSMTPConfigWriteModel, err error) {
writeModel := NewIAMSMTPConfigWriteModel()
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -0,0 +1,398 @@
package command
import (
"context"
"testing"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/iam"
)
func TestCommandSide_AddSMTPConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
alg crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
smtp *smtp.EmailConfig
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "smtp config, error already exists",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSMTPConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
"from",
"name",
"host",
"user",
&crypto.CryptoValue{},
),
),
),
),
},
args: args{
ctx: context.Background(),
smtp: &smtp.EmailConfig{
Tls: true,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add smtp config, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(iam.NewSMTPConfigAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
true,
"from",
"name",
"host",
"user",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
),
),
},
),
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
smtp: &smtp.EmailConfig{
Tls: true,
From: "from",
FromName: "name",
SMTP: smtp.SMTP{
Host: "host",
User: "user",
Password: "password",
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
smtpPasswordCrypto: tt.fields.alg,
}
got, err := r.AddSMTPConfig(tt.args.ctx, tt.args.smtp)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
smtp *smtp.EmailConfig
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "smtp not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
smtp: &smtp.EmailConfig{},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSMTPConfigAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
true,
"from",
"name",
"host",
"user",
&crypto.CryptoValue{},
),
),
),
),
},
args: args{
ctx: context.Background(),
smtp: &smtp.EmailConfig{
Tls: true,
From: "from",
FromName: "name",
SMTP: smtp.SMTP{
Host: "host",
User: "user",
},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "smtp config change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSMTPConfigAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
true,
"from",
"name",
"host",
"user",
&crypto.CryptoValue{},
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newSMTPConfigChangedEvent(
context.Background(),
false,
"from2",
"name2",
"host2",
"user2",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
smtp: &smtp.EmailConfig{
Tls: false,
From: "from2",
FromName: "name2",
SMTP: smtp.SMTP{
Host: "host2",
User: "user2",
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeSMTPConfig(tt.args.ctx, tt.args.smtp)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
alg crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
password string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "smtp config, error not found",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
password: "",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "change smtp config password, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewSMTPConfigAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
true,
"from",
"name",
"host",
"user",
&crypto.CryptoValue{},
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(iam.NewSMTPConfigPasswordChangedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
),
),
},
),
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
password: "password",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
smtpPasswordCrypto: tt.fields.alg,
}
got, err := r.ChangeSMTPConfigPassword(tt.args.ctx, tt.args.password)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newSMTPConfigChangedEvent(ctx context.Context, tls bool, fromAddress, fromName, host, user string) *iam.SMTPConfigChangedEvent {
changes := []iam.SMTPConfigChanges{
iam.ChangeSMTPConfigTLS(tls),
iam.ChangeSMTPConfigFromAddress(fromAddress),
iam.ChangeSMTPConfigFromName(fromName),
iam.ChangeSMTPConfigSMTPHost(host),
iam.ChangeSMTPConfigSMTPUser(user),
}
event, _ := iam.NewSMTPConfigChangeEvent(ctx,
&iam.NewAggregate().Aggregate,
changes,
)
return event
}

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"strings" "strings"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
@ -23,7 +24,7 @@ func (c *Commands) getHuman(ctx context.Context, userID, resourceowner string) (
return writeModelToHuman(human), nil return writeModelToHuman(human), nil
} }
func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Human) (*domain.Human, error) { func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Human, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
if orgID == "" { if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-XYFk9", "Errors.ResourceOwnerMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-XYFk9", "Errors.ResourceOwnerMissing")
} }
@ -35,7 +36,7 @@ func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Hum
if err != nil { if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexity.NotFound") return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexity.NotFound")
} }
events, addedHuman, err := c.addHuman(ctx, orgID, human, orgIAMPolicy, pwPolicy) events, addedHuman, err := c.addHuman(ctx, orgID, human, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -52,7 +53,7 @@ func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Hum
return writeModelToHuman(addedHuman), nil return writeModelToHuman(addedHuman), nil
} }
func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool) (_ *domain.Human, passwordlessCode *domain.PasswordlessInitCode, err error) { func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (_ *domain.Human, passwordlessCode *domain.PasswordlessInitCode, err error) {
if orgID == "" { if orgID == "" {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing") return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing")
} }
@ -64,7 +65,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
if err != nil { if err != nil {
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexity.NotFound") return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexity.NotFound")
} }
events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, orgIAMPolicy, pwPolicy) events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -88,27 +89,27 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
return writeModelToHuman(addedHuman), passwordlessCode, nil return writeModelToHuman(addedHuman), passwordlessCode, nil
} }
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.Command, *HumanWriteModel, error) { func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if orgID == "" || !human.IsValid() { if orgID == "" || !human.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid") return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
} }
if human.Password != nil && human.SecretString != "" { if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = true human.ChangeRequired = true
} }
return c.createHuman(ctx, orgID, human, nil, false, false, orgIAMPolicy, pwPolicy) return c.createHuman(ctx, orgID, human, nil, false, false, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
} }
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) { func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
if orgID == "" || !human.IsValid() { if orgID == "" || !human.IsValid() {
return nil, nil, nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid") return nil, nil, nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid")
} }
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, orgIAMPolicy, pwPolicy) events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, nil, nil, "", err return nil, nil, nil, "", err
} }
if passwordless { if passwordless {
var codeEvent eventstore.Command var codeEvent eventstore.Command
codeEvent, passwordlessCodeWriteModel, code, err = c.humanAddPasswordlessInitCode(ctx, human.AggregateID, orgID, true) codeEvent, passwordlessCodeWriteModel, code, err = c.humanAddPasswordlessInitCode(ctx, human.AggregateID, orgID, true, passwordlessCodeGenerator)
if err != nil { if err != nil {
return nil, nil, nil, "", err return nil, nil, nil, "", err
} }
@ -117,7 +118,7 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
} }
func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgMemberRoles []string) (*domain.Human, error) { func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgMemberRoles []string, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
if orgID == "" { if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
} }
@ -136,7 +137,7 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
if !loginPolicy.AllowRegister { if !loginPolicy.AllowRegister {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed") return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
} }
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, orgIAMPolicy, pwPolicy) userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -170,7 +171,7 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
return writeModelToHuman(registeredHuman), nil return writeModelToHuman(registeredHuman), nil
} }
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.Command, *HumanWriteModel, error) { func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if human != nil && human.Username == "" { if human != nil && human.Username == "" {
human.Username = human.EmailAddress human.Username = human.EmailAddress
} }
@ -180,10 +181,10 @@ func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domai
if human.Password != nil && human.SecretString != "" { if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = false human.ChangeRequired = false
} }
return c.createHuman(ctx, orgID, human, link, true, false, orgIAMPolicy, pwPolicy) return c.createHuman(ctx, orgID, human, link, true, false, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
} }
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.Command, *HumanWriteModel, error) { func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if err := human.CheckOrgIAMPolicy(orgIAMPolicy); err != nil { if err := human.CheckOrgIAMPolicy(orgIAMPolicy); err != nil {
return nil, nil, err return nil, nil, err
} }
@ -232,7 +233,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
} }
if human.IsInitialState(passwordless, link != nil) { if human.IsInitialState(passwordless, link != nil) {
initCode, err := domain.NewInitUserCode(c.initializeUserCode) initCode, err := domain.NewInitUserCode(initCodeGenerator)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -244,7 +245,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
} }
if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified { if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified {
phoneCode, err := domain.NewPhoneCode(c.phoneVerificationCode) phoneCode, err := domain.NewPhoneCode(phoneCodeGenerator)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -12,7 +12,7 @@ import (
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
) )
func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email) (*domain.Email, error) { func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email, emailCodeGenerator crypto.Generator) (*domain.Email, error) {
if !email.IsValid() || email.AggregateID == "" { if !email.IsValid() || email.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M9sf", "Errors.Email.Invalid") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M9sf", "Errors.Email.Invalid")
} }
@ -38,7 +38,7 @@ func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email) (*
if email.IsEmailVerified { if email.IsEmailVerified {
events = append(events, user.NewHumanEmailVerifiedEvent(ctx, userAgg)) events = append(events, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
} else { } else {
emailCode, err := domain.NewEmailCode(c.emailVerificationCode) emailCode, err := domain.NewEmailCode(emailCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -56,7 +56,7 @@ func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email) (*
return writeModelToEmail(existingEmail), nil return writeModelToEmail(existingEmail), nil
} }
func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceowner string) (*domain.ObjectDetails, error) { func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceowner string, emailCodeGenerator crypto.Generator) (*domain.ObjectDetails, error) {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
} }
@ -73,7 +73,7 @@ func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceo
} }
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, c.emailVerificationCode) err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, emailCodeGenerator)
if err == nil { if err == nil {
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailVerifiedEvent(ctx, userAgg)) pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
if err != nil { if err != nil {
@ -91,7 +91,7 @@ func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceo
return nil, caos_errs.ThrowInvalidArgument(err, "COMMAND-Gdsgs", "Errors.User.Code.Invalid") return nil, caos_errs.ThrowInvalidArgument(err, "COMMAND-Gdsgs", "Errors.User.Code.Invalid")
} }
func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) { func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string, emailCodeGenerator crypto.Generator) (*domain.ObjectDetails, error) {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
} }
@ -110,7 +110,7 @@ func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID,
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9ds", "Errors.User.Email.AlreadyVerified") return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9ds", "Errors.User.Email.AlreadyVerified")
} }
userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel) userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel)
emailCode, err := domain.NewEmailCode(c.emailVerificationCode) emailCode, err := domain.NewEmailCode(emailCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -19,13 +19,13 @@ import (
func TestCommandSide_ChangeHumanEmail(t *testing.T) { func TestCommandSide_ChangeHumanEmail(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
email *domain.Email email *domain.Email
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.Email want *domain.Email
@ -263,7 +263,6 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -273,7 +272,8 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
}, },
EmailAddress: "email-changed@test.ch", EmailAddress: "email-changed@test.ch",
}, },
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Email{ want: &domain.Email{
@ -289,10 +289,9 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
emailVerificationCode: tt.fields.secretGenerator,
} }
got, err := r.ChangeHumanEmail(tt.args.ctx, tt.args.email) got, err := r.ChangeHumanEmail(tt.args.ctx, tt.args.email, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -308,14 +307,14 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
func TestCommandSide_VerifyHumanEmail(t *testing.T) { func TestCommandSide_VerifyHumanEmail(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
code string code string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -453,13 +452,13 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
code: "test", code: "test",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
err: caos_errs.IsErrorInvalidArgument, err: caos_errs.IsErrorInvalidArgument,
@ -508,13 +507,13 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
code: "a", code: "a",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -526,10 +525,9 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
emailVerificationCode: tt.fields.secretGenerator,
} }
got, err := r.VerifyHumanEmail(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner) got, err := r.VerifyHumanEmail(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -545,13 +543,13 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) { func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -719,12 +717,12 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -736,10 +734,9 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
emailVerificationCode: tt.fields.secretGenerator,
} }
got, err := r.CreateHumanEmailVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner) got, err := r.CreateHumanEmailVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -12,7 +12,7 @@ import (
) )
//ResendInitialMail resend inital mail and changes email if provided //ResendInitialMail resend inital mail and changes email if provided
func (c *Commands) ResendInitialMail(ctx context.Context, userID, email, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) { func (c *Commands) ResendInitialMail(ctx context.Context, userID, email, resourceOwner string, initCodeGenerator crypto.Generator) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing")
} }
@ -33,7 +33,7 @@ func (c *Commands) ResendInitialMail(ctx context.Context, userID, email, resourc
changedEvent, _ := existingCode.NewChangedEvent(ctx, userAgg, email) changedEvent, _ := existingCode.NewChangedEvent(ctx, userAgg, email)
events = append(events, changedEvent) events = append(events, changedEvent)
} }
initCode, err := domain.NewInitUserCode(c.initializeUserCode) initCode, err := domain.NewInitUserCode(initCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -49,7 +49,7 @@ func (c *Commands) ResendInitialMail(ctx context.Context, userID, email, resourc
return writeModelToObjectDetails(&existingCode.WriteModel), nil return writeModelToObjectDetails(&existingCode.WriteModel), nil
} }
func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwner, code, passwordString string) error { func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwner, code, passwordString string, initCodeGenerator crypto.Generator) error {
if userID == "" { if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-mkM9f", "Errors.User.UserIDMissing") return caos_errs.ThrowInvalidArgument(nil, "COMMAND-mkM9f", "Errors.User.UserIDMissing")
} }
@ -66,7 +66,7 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne
} }
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, c.initializeUserCode) err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, initCodeGenerator)
if err != nil { if err != nil {
_, err = c.eventstore.Push(ctx, user.NewHumanInitializedCheckFailedEvent(ctx, userAgg)) _, err = c.eventstore.Push(ctx, user.NewHumanInitializedCheckFailedEvent(ctx, userAgg))
logging.LogWithFields("COMMAND-Dg2z5", "userID", userAgg.ID).OnError(err).Error("NewHumanInitializedCheckFailedEvent push failed") logging.LogWithFields("COMMAND-Dg2z5", "userID", userAgg.ID).OnError(err).Error("NewHumanInitializedCheckFailedEvent push failed")

View File

@ -20,14 +20,14 @@ import (
func TestCommandSide_ResendInitialMail(t *testing.T) { func TestCommandSide_ResendInitialMail(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
email string email string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -150,13 +150,13 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "org1", resourceOwner: "org1",
email: "email@test.ch", email: "email@test.ch",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -208,12 +208,12 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -269,13 +269,13 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "org1", resourceOwner: "org1",
email: "email2@test.ch", email: "email2@test.ch",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -287,10 +287,9 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
initializeUserCode: tt.fields.secretGenerator,
} }
got, err := r.ResendInitialMail(tt.args.ctx, tt.args.userID, tt.args.email, tt.args.resourceOwner) got, err := r.ResendInitialMail(tt.args.ctx, tt.args.userID, tt.args.email, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -307,15 +306,15 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
func TestCommandSide_VerifyInitCode(t *testing.T) { func TestCommandSide_VerifyInitCode(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
userPasswordAlg crypto.HashAlgorithm userPasswordAlg crypto.HashAlgorithm
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
code string code string
resourceOwner string resourceOwner string
password string password string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -453,13 +452,13 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
code: "test", code: "test",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
err: caos_errs.IsErrorInvalidArgument, err: caos_errs.IsErrorInvalidArgument,
@ -513,13 +512,13 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
code: "a", code: "a",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -596,15 +595,15 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
code: "a", code: "a",
resourceOwner: "org1", resourceOwner: "org1",
password: "password", password: "password",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -616,11 +615,10 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
initializeUserCode: tt.fields.secretGenerator, userPasswordAlg: tt.fields.userPasswordAlg,
userPasswordAlg: tt.fields.userPasswordAlg,
} }
err := r.HumanVerifyInitCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.code, tt.args.password) err := r.HumanVerifyInitCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.code, tt.args.password, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -46,7 +46,7 @@ func (c *Commands) SetPassword(ctx context.Context, orgID, userID, passwordStrin
return writeModelToObjectDetails(&existingPassword.WriteModel), nil return writeModelToObjectDetails(&existingPassword.WriteModel), nil
} }
func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, passwordString, userAgentID string) (err error) { func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, passwordString, userAgentID string, passwordVerificationCode crypto.Generator) (err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
@ -65,7 +65,7 @@ func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID,
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound")
} }
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, c.passwordVerificationCode) err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, passwordVerificationCode)
if err != nil { if err != nil {
return err return err
} }
@ -148,7 +148,7 @@ func (c *Commands) changePassword(ctx context.Context, userAgentID string, passw
return user.NewHumanPasswordChangedEvent(ctx, userAgg, password.SecretCrypto, password.ChangeRequired, userAgentID), nil return user.NewHumanPasswordChangedEvent(ctx, userAgg, password.SecretCrypto, password.ChangeRequired, userAgentID), nil
} }
func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType) (objectDetails *domain.ObjectDetails, err error) { func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType, passwordVerificationCode crypto.Generator) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-M00oL", "Errors.User.UserIDMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-M00oL", "Errors.User.UserIDMissing")
} }
@ -164,7 +164,7 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.NotInitialised") return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.NotInitialised")
} }
userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel) userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel)
passwordCode, err := domain.NewPasswordCode(c.passwordVerificationCode) passwordCode, err := domain.NewPasswordCode(passwordVerificationCode)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -239,15 +239,15 @@ func TestCommandSide_SetPassword(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
userPasswordAlg crypto.HashAlgorithm userPasswordAlg crypto.HashAlgorithm
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
code string code string
resourceOwner string resourceOwner string
password string password string
agentID string agentID string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -377,14 +377,14 @@ func TestCommandSide_SetPassword(t *testing.T) {
), ),
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
code: "test", code: "test",
resourceOwner: "org1", resourceOwner: "org1",
password: "password", password: "password",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
err: caos_errs.IsPreconditionFailed, err: caos_errs.IsPreconditionFailed,
@ -459,15 +459,15 @@ func TestCommandSide_SetPassword(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "org1", resourceOwner: "org1",
password: "password", password: "password",
code: "a", code: "a",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -479,11 +479,10 @@ func TestCommandSide_SetPassword(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
userPasswordAlg: tt.fields.userPasswordAlg, userPasswordAlg: tt.fields.userPasswordAlg,
passwordVerificationCode: tt.fields.secretGenerator,
} }
err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password, tt.args.agentID) err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password, tt.args.agentID, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -778,14 +777,14 @@ func TestCommandSide_ChangePassword(t *testing.T) {
func TestCommandSide_RequestSetPassword(t *testing.T) { func TestCommandSide_RequestSetPassword(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
resourceOwner string resourceOwner string
notifyType domain.NotificationType notifyType domain.NotificationType
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -925,12 +924,12 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -942,10 +941,9 @@ func TestCommandSide_RequestSetPassword(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
passwordVerificationCode: tt.fields.secretGenerator,
} }
got, err := r.RequestSetPassword(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.notifyType) got, err := r.RequestSetPassword(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.notifyType, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -13,7 +13,7 @@ import (
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
) )
func (c *Commands) ChangeHumanPhone(ctx context.Context, phone *domain.Phone, resourceOwner string) (*domain.Phone, error) { func (c *Commands) ChangeHumanPhone(ctx context.Context, phone *domain.Phone, resourceOwner string, phoneCodeGenerator crypto.Generator) (*domain.Phone, error) {
if !phone.IsValid() { if !phone.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6M0ds", "Errors.Phone.Invalid") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6M0ds", "Errors.Phone.Invalid")
} }
@ -36,7 +36,7 @@ func (c *Commands) ChangeHumanPhone(ctx context.Context, phone *domain.Phone, re
if phone.IsPhoneVerified { if phone.IsPhoneVerified {
events = append(events, user.NewHumanPhoneVerifiedEvent(ctx, userAgg)) events = append(events, user.NewHumanPhoneVerifiedEvent(ctx, userAgg))
} else { } else {
phoneCode, err := domain.NewPhoneCode(c.phoneVerificationCode) phoneCode, err := domain.NewPhoneCode(phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -55,7 +55,7 @@ func (c *Commands) ChangeHumanPhone(ctx context.Context, phone *domain.Phone, re
return writeModelToPhone(existingPhone), nil return writeModelToPhone(existingPhone), nil
} }
func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceowner string) (*domain.ObjectDetails, error) { func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceowner string, phoneCodeGenerator crypto.Generator) (*domain.ObjectDetails, error) {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Km9ds", "Errors.User.UserIDMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Km9ds", "Errors.User.UserIDMissing")
} }
@ -75,7 +75,7 @@ func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceo
} }
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, c.phoneVerificationCode) err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, phoneCodeGenerator)
if err == nil { if err == nil {
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPhoneVerifiedEvent(ctx, userAgg)) pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPhoneVerifiedEvent(ctx, userAgg))
if err != nil { if err != nil {
@ -92,7 +92,7 @@ func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceo
return nil, caos_errs.ThrowInvalidArgument(err, "COMMAND-sM0cs", "Errors.User.Code.Invalid") return nil, caos_errs.ThrowInvalidArgument(err, "COMMAND-sM0cs", "Errors.User.Code.Invalid")
} }
func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceowner string) (*domain.ObjectDetails, error) { func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceowner string, phoneCodeGenerator crypto.Generator) (*domain.ObjectDetails, error) {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
} }
@ -112,7 +112,7 @@ func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID,
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified") return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified")
} }
phoneCode, err := domain.NewPhoneCode(c.phoneVerificationCode) phoneCode, err := domain.NewPhoneCode(phoneCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -19,13 +19,13 @@ import (
func TestCommandSide_ChangeHumanPhone(t *testing.T) { func TestCommandSide_ChangeHumanPhone(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
email *domain.Phone email *domain.Phone
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.Phone want *domain.Phone
@ -232,7 +232,6 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -242,7 +241,8 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
}, },
PhoneNumber: "0711234567", PhoneNumber: "0711234567",
}, },
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Phone{ want: &domain.Phone{
@ -258,10 +258,9 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
phoneVerificationCode: tt.fields.secretGenerator,
} }
got, err := r.ChangeHumanPhone(tt.args.ctx, tt.args.email, tt.args.resourceOwner) got, err := r.ChangeHumanPhone(tt.args.ctx, tt.args.email, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -277,14 +276,14 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
func TestCommandSide_VerifyHumanPhone(t *testing.T) { func TestCommandSide_VerifyHumanPhone(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
code string code string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -428,13 +427,13 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
code: "test", code: "test",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
err: caos_errs.IsErrorInvalidArgument, err: caos_errs.IsErrorInvalidArgument,
@ -489,13 +488,13 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
code: "a", code: "a",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -507,10 +506,9 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
phoneVerificationCode: tt.fields.secretGenerator,
} }
got, err := r.VerifyHumanPhone(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner) got, err := r.VerifyHumanPhone(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -526,13 +524,13 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) { func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -663,12 +661,12 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
}, },
), ),
), ),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -680,10 +678,9 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
phoneVerificationCode: tt.fields.secretGenerator,
} }
got, err := r.CreateHumanPhoneVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner) got, err := r.CreateHumanPhoneVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -25,13 +25,13 @@ func TestCommandSide_AddHuman(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
secretGenerator crypto.Generator
userPasswordAlg crypto.HashAlgorithm userPasswordAlg crypto.HashAlgorithm
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
orgID string orgID string
human *domain.Human human *domain.Human
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.Human want *domain.Human
@ -228,8 +228,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)), uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -244,6 +243,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
EmailAddress: "email@test.ch", EmailAddress: "email@test.ch",
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -312,7 +312,6 @@ func TestCommandSide_AddHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -331,6 +330,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
EmailAddress: "email@test.ch", EmailAddress: "email@test.ch",
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -391,7 +391,6 @@ func TestCommandSide_AddHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -411,6 +410,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
IsEmailVerified: true, IsEmailVerified: true,
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -489,8 +489,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)), uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -508,6 +507,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
PhoneNumber: "+41711234567", PhoneNumber: "+41711234567",
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -582,8 +582,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)), uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -602,6 +601,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
IsPhoneVerified: true, IsPhoneVerified: true,
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -630,13 +630,11 @@ func TestCommandSide_AddHuman(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
initializeUserCode: tt.fields.secretGenerator, userPasswordAlg: tt.fields.userPasswordAlg,
phoneVerificationCode: tt.fields.secretGenerator,
userPasswordAlg: tt.fields.userPasswordAlg,
} }
got, err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human) got, err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.secretGenerator, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -652,17 +650,17 @@ func TestCommandSide_AddHuman(t *testing.T) {
func TestCommandSide_ImportHuman(t *testing.T) { func TestCommandSide_ImportHuman(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
secretGenerator crypto.Generator userPasswordAlg crypto.HashAlgorithm
userPasswordAlg crypto.HashAlgorithm
passwordlessInitCode crypto.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
orgID string orgID string
human *domain.Human human *domain.Human
passwordless bool passwordless bool
secretGenerator crypto.Generator
passwordlessInitCode crypto.Generator
} }
type res struct { type res struct {
wantHuman *domain.Human wantHuman *domain.Human
@ -850,7 +848,6 @@ func TestCommandSide_ImportHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -870,6 +867,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
EmailAddress: "email@test.ch", EmailAddress: "email@test.ch",
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
wantHuman: &domain.Human{ wantHuman: &domain.Human{
@ -930,7 +928,6 @@ func TestCommandSide_ImportHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -951,6 +948,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
IsEmailVerified: true, IsEmailVerified: true,
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
wantHuman: &domain.Human{ wantHuman: &domain.Human{
@ -1025,10 +1023,8 @@ func TestCommandSide_ImportHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)), uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1", "code1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1", "code1"),
secretGenerator: GetMockSecretGenerator(t), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
passwordlessInitCode: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1044,7 +1040,9 @@ func TestCommandSide_ImportHuman(t *testing.T) {
IsEmailVerified: true, IsEmailVerified: true,
}, },
}, },
passwordless: true, passwordless: true,
secretGenerator: GetMockSecretGenerator(t),
passwordlessInitCode: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
wantHuman: &domain.Human{ wantHuman: &domain.Human{
@ -1129,10 +1127,8 @@ func TestCommandSide_ImportHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)), uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1", "code1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1", "code1"),
secretGenerator: GetMockSecretGenerator(t), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
passwordlessInitCode: GetMockSecretGenerator(t),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1152,7 +1148,9 @@ func TestCommandSide_ImportHuman(t *testing.T) {
IsEmailVerified: true, IsEmailVerified: true,
}, },
}, },
passwordless: true, passwordless: true,
secretGenerator: GetMockSecretGenerator(t),
passwordlessInitCode: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
wantHuman: &domain.Human{ wantHuman: &domain.Human{
@ -1242,7 +1240,6 @@ func TestCommandSide_ImportHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -1265,6 +1262,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
PhoneNumber: "+41711234567", PhoneNumber: "+41711234567",
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
wantHuman: &domain.Human{ wantHuman: &domain.Human{
@ -1340,7 +1338,6 @@ func TestCommandSide_ImportHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -1364,6 +1361,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
IsPhoneVerified: true, IsPhoneVerified: true,
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
wantHuman: &domain.Human{ wantHuman: &domain.Human{
@ -1392,14 +1390,11 @@ func TestCommandSide_ImportHuman(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
initializeUserCode: tt.fields.secretGenerator, userPasswordAlg: tt.fields.userPasswordAlg,
phoneVerificationCode: tt.fields.secretGenerator,
userPasswordAlg: tt.fields.userPasswordAlg,
passwordlessInitCode: tt.fields.passwordlessInitCode,
} }
gotHuman, gotCode, err := r.ImportHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.passwordless) gotHuman, gotCode, err := r.ImportHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.passwordless, tt.args.secretGenerator, tt.args.secretGenerator, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -1418,15 +1413,15 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
secretGenerator crypto.Generator
userPasswordAlg crypto.HashAlgorithm userPasswordAlg crypto.HashAlgorithm
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
orgID string orgID string
human *domain.Human human *domain.Human
link *domain.UserIDPLink link *domain.UserIDPLink
orgMemberRoles []string orgMemberRoles []string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.Human want *domain.Human
@ -1858,7 +1853,6 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -1876,6 +1870,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
EmailAddress: "email@test.ch", EmailAddress: "email@test.ch",
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -1957,7 +1952,6 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -1976,6 +1970,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
EmailAddress: "email@test.ch", EmailAddress: "email@test.ch",
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -2049,7 +2044,6 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -2069,6 +2063,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
IsEmailVerified: true, IsEmailVerified: true,
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -2161,7 +2156,6 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -2183,6 +2177,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
SecretString: "password", SecretString: "password",
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -2271,7 +2266,6 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -2294,6 +2288,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
SecretString: "password", SecretString: "password",
}, },
}, },
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.Human{ want: &domain.Human{
@ -2322,13 +2317,11 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
initializeUserCode: tt.fields.secretGenerator, userPasswordAlg: tt.fields.userPasswordAlg,
phoneVerificationCode: tt.fields.secretGenerator,
userPasswordAlg: tt.fields.userPasswordAlg,
} }
got, err := r.RegisterHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.link, tt.args.orgMemberRoles) got, err := r.RegisterHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.link, tt.args.orgMemberRoles, tt.args.secretGenerator, tt.args.secretGenerator)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -129,8 +129,8 @@ func (c *Commands) HumanAddPasswordlessSetup(ctx context.Context, userID, resour
return createdWebAuthN, nil return createdWebAuthN, nil
} }
func (c *Commands) HumanAddPasswordlessSetupInitCode(ctx context.Context, userID, resourceowner, codeID, verificationCode string, preferredPlatformType domain.AuthenticatorAttachment) (*domain.WebAuthNToken, error) { func (c *Commands) HumanAddPasswordlessSetupInitCode(ctx context.Context, userID, resourceowner, codeID, verificationCode string, preferredPlatformType domain.AuthenticatorAttachment, passwordlessCodeGenerator crypto.Generator) (*domain.WebAuthNToken, error) {
err := c.humanVerifyPasswordlessInitCode(ctx, userID, resourceowner, codeID, verificationCode) err := c.humanVerifyPasswordlessInitCode(ctx, userID, resourceowner, codeID, verificationCode, passwordlessCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -208,8 +208,8 @@ func (c *Commands) HumanVerifyU2FSetup(ctx context.Context, userID, resourceowne
return writeModelToObjectDetails(&verifyWebAuthN.WriteModel), nil return writeModelToObjectDetails(&verifyWebAuthN.WriteModel), nil
} }
func (c *Commands) HumanPasswordlessSetupInitCode(ctx context.Context, userID, resourceowner, tokenName, userAgentID, codeID, verificationCode string, credentialData []byte) (*domain.ObjectDetails, error) { func (c *Commands) HumanPasswordlessSetupInitCode(ctx context.Context, userID, resourceowner, tokenName, userAgentID, codeID, verificationCode string, credentialData []byte, passwordlessCodeGenerator crypto.Generator) (*domain.ObjectDetails, error) {
err := c.humanVerifyPasswordlessInitCode(ctx, userID, resourceowner, codeID, verificationCode) err := c.humanVerifyPasswordlessInitCode(ctx, userID, resourceowner, codeID, verificationCode, passwordlessCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -483,8 +483,8 @@ func (c *Commands) HumanRemovePasswordless(ctx context.Context, userID, webAuthN
return c.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event) return c.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
} }
func (c *Commands) HumanAddPasswordlessInitCode(ctx context.Context, userID, resourceOwner string) (*domain.PasswordlessInitCode, error) { func (c *Commands) HumanAddPasswordlessInitCode(ctx context.Context, userID, resourceOwner string, passwordlessCodeGenerator crypto.Generator) (*domain.PasswordlessInitCode, error) {
codeEvent, initCode, code, err := c.humanAddPasswordlessInitCode(ctx, userID, resourceOwner, true) codeEvent, initCode, code, err := c.humanAddPasswordlessInitCode(ctx, userID, resourceOwner, true, passwordlessCodeGenerator)
pushedEvents, err := c.eventstore.Push(ctx, codeEvent) pushedEvents, err := c.eventstore.Push(ctx, codeEvent)
if err != nil { if err != nil {
return nil, err return nil, err
@ -496,8 +496,8 @@ func (c *Commands) HumanAddPasswordlessInitCode(ctx context.Context, userID, res
return writeModelToPasswordlessInitCode(initCode, code), nil return writeModelToPasswordlessInitCode(initCode, code), nil
} }
func (c *Commands) HumanSendPasswordlessInitCode(ctx context.Context, userID, resourceOwner string) (*domain.PasswordlessInitCode, error) { func (c *Commands) HumanSendPasswordlessInitCode(ctx context.Context, userID, resourceOwner string, passwordlessCodeGenerator crypto.Generator) (*domain.PasswordlessInitCode, error) {
codeEvent, initCode, code, err := c.humanAddPasswordlessInitCode(ctx, userID, resourceOwner, false) codeEvent, initCode, code, err := c.humanAddPasswordlessInitCode(ctx, userID, resourceOwner, false, passwordlessCodeGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -512,7 +512,7 @@ func (c *Commands) HumanSendPasswordlessInitCode(ctx context.Context, userID, re
return writeModelToPasswordlessInitCode(initCode, code), nil return writeModelToPasswordlessInitCode(initCode, code), nil
} }
func (c *Commands) humanAddPasswordlessInitCode(ctx context.Context, userID, resourceOwner string, direct bool) (eventstore.Command, *HumanPasswordlessInitCodeWriteModel, string, error) { func (c *Commands) humanAddPasswordlessInitCode(ctx context.Context, userID, resourceOwner string, direct bool, passwordlessCodeGenerator crypto.Generator) (eventstore.Command, *HumanPasswordlessInitCodeWriteModel, string, error) {
if userID == "" { if userID == "" {
return nil, nil, "", caos_errs.ThrowPreconditionFailed(nil, "COMMAND-GVfg3", "Errors.IDMissing") return nil, nil, "", caos_errs.ThrowPreconditionFailed(nil, "COMMAND-GVfg3", "Errors.IDMissing")
} }
@ -527,7 +527,7 @@ func (c *Commands) humanAddPasswordlessInitCode(ctx context.Context, userID, res
return nil, nil, "", err return nil, nil, "", err
} }
cryptoCode, code, err := crypto.NewCode(c.passwordlessInitCode) cryptoCode, code, err := crypto.NewCode(passwordlessCodeGenerator)
if err != nil { if err != nil {
return nil, nil, "", err return nil, nil, "", err
} }
@ -539,7 +539,7 @@ func (c *Commands) humanAddPasswordlessInitCode(ctx context.Context, userID, res
return usr_repo.NewHumanPasswordlessInitCodeRequestedEvent(ctx, agg, id, cryptoCode, exp) return usr_repo.NewHumanPasswordlessInitCodeRequestedEvent(ctx, agg, id, cryptoCode, exp)
} }
} }
codeEvent := codeEventCreator(ctx, UserAggregateFromWriteModel(&initCode.WriteModel), codeID, cryptoCode, c.passwordlessInitCode.Expiry()) codeEvent := codeEventCreator(ctx, UserAggregateFromWriteModel(&initCode.WriteModel), codeID, cryptoCode, passwordlessCodeGenerator.Expiry())
return codeEvent, initCode, code, nil return codeEvent, initCode, code, nil
} }
@ -563,7 +563,7 @@ func (c *Commands) HumanPasswordlessInitCodeSent(ctx context.Context, userID, re
return err return err
} }
func (c *Commands) humanVerifyPasswordlessInitCode(ctx context.Context, userID, resourceOwner, codeID, verificationCode string) error { func (c *Commands) humanVerifyPasswordlessInitCode(ctx context.Context, userID, resourceOwner, codeID, verificationCode string, passwordlessCodeGenerator crypto.Generator) error {
if userID == "" || codeID == "" { if userID == "" || codeID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-GVfg3", "Errors.IDMissing") return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-GVfg3", "Errors.IDMissing")
} }
@ -572,7 +572,7 @@ func (c *Commands) humanVerifyPasswordlessInitCode(ctx context.Context, userID,
if err != nil { if err != nil {
return err return err
} }
err = crypto.VerifyCode(initCode.ChangeDate, initCode.Expiration, initCode.CryptoCode, verificationCode, c.passwordlessInitCode) err = crypto.VerifyCode(initCode.ChangeDate, initCode.Expiration, initCode.CryptoCode, verificationCode, passwordlessCodeGenerator)
if err != nil || initCode.State != domain.PasswordlessInitCodeStateActive { if err != nil || initCode.State != domain.PasswordlessInitCodeStateActive {
userAgg := UserAggregateFromWriteModel(&initCode.WriteModel) userAgg := UserAggregateFromWriteModel(&initCode.WriteModel)
_, err = c.eventstore.Push(ctx, usr_repo.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, codeID)) _, err = c.eventstore.Push(ctx, usr_repo.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, codeID))

View File

@ -9,24 +9,23 @@ import (
"github.com/caos/zitadel/internal/notification/channels/chat" "github.com/caos/zitadel/internal/notification/channels/chat"
"github.com/caos/zitadel/internal/notification/channels/fs" "github.com/caos/zitadel/internal/notification/channels/fs"
"github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/log"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/notification/channels/twilio" "github.com/caos/zitadel/internal/notification/channels/twilio"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
) )
type SystemDefaults struct { type SystemDefaults struct {
DefaultLanguage language.Tag DefaultLanguage language.Tag
Domain string Domain string
ZitadelDocs ZitadelDocs ZitadelDocs ZitadelDocs
SecretGenerators SecretGenerators SecretGenerators SecretGenerators
UserVerificationKey *crypto.KeyConfig UserVerificationKey *crypto.KeyConfig
IDPConfigVerificationKey *crypto.KeyConfig IDPConfigVerificationKey *crypto.KeyConfig
Multifactors MultifactorConfig SMTPPasswordVerificationKey *crypto.KeyConfig
VerificationLifetimes VerificationLifetimes Multifactors MultifactorConfig
DomainVerification DomainVerification VerificationLifetimes VerificationLifetimes
IamID string DomainVerification DomainVerification
Notifications Notifications Notifications Notifications
KeyConfig KeyConfig KeyConfig KeyConfig
} }
type ZitadelDocs struct { type ZitadelDocs struct {
@ -35,15 +34,9 @@ type ZitadelDocs struct {
} }
type SecretGenerators struct { type SecretGenerators struct {
PasswordSaltCost int PasswordSaltCost int
ClientSecretGenerator crypto.GeneratorConfig MachineKeySize uint32
InitializeUserCode crypto.GeneratorConfig ApplicationKeySize uint32
EmailVerificationCode crypto.GeneratorConfig
PhoneVerificationCode crypto.GeneratorConfig
PasswordVerificationCode crypto.GeneratorConfig
PasswordlessInitCode crypto.GeneratorConfig
MachineKeySize uint32
ApplicationKeySize uint32
} }
type MultifactorConfig struct { type MultifactorConfig struct {
@ -69,10 +62,9 @@ type DomainVerification struct {
} }
type Notifications struct { type Notifications struct {
DebugMode bool DebugMode bool
Endpoints Endpoints Endpoints Endpoints
Providers Channels Providers Channels
TemplateData TemplateData
} }
type Endpoints struct { type Endpoints struct {
@ -85,7 +77,6 @@ type Endpoints struct {
type Channels struct { type Channels struct {
Chat chat.ChatConfig Chat chat.ChatConfig
Email smtp.EmailConfig
Twilio twilio.TwilioConfig Twilio twilio.TwilioConfig
FileSystem fs.FSConfig FileSystem fs.FSConfig
Log log.LogConfig Log log.LogConfig

View File

@ -0,0 +1,23 @@
package domain
type SecretGeneratorType int32
const (
SecretGeneratorTypeUnspecified SecretGeneratorType = iota
SecretGeneratorTypeInitCode
SecretGeneratorTypeVerifyEmailCode
SecretGeneratorTypeVerifyPhoneCode
SecretGeneratorTypePasswordResetCode
SecretGeneratorTypePasswordlessInitCode
SecretGeneratorTypeAppSecret
secretGeneratorTypeCount
)
type SecretGeneratorState int32
const (
SecretGeneratorStateUnspecified SecretGeneratorState = iota
SecretGeneratorStateActive
SecretGeneratorStateRemoved
)

8
internal/domain/smtp.go Normal file
View File

@ -0,0 +1,8 @@
package domain
type SMTPConfigState int32
const (
SMTPConfigStateUnspecified SMTPConfigState = iota
SMTPConfigStateActive
)

View File

@ -1,26 +1,30 @@
package smtp package smtp
import ( import (
"context"
"crypto/tls" "crypto/tls"
"net" "net"
"net/smtp" "net/smtp"
"github.com/caos/zitadel/internal/notification/messages"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/pkg/errors"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/notification/channels" "github.com/caos/zitadel/internal/notification/channels"
"github.com/pkg/errors" "github.com/caos/zitadel/internal/notification/messages"
) )
var _ channels.NotificationChannel = (*Email)(nil) var _ channels.NotificationChannel = (*Email)(nil)
type Email struct { type Email struct {
smtpClient *smtp.Client smtpClient *smtp.Client
senderAddress string
senderName string
} }
func InitSMTPChannel(config EmailConfig) (*Email, error) { func InitSMTPChannel(ctx context.Context, getSMTPConfig func(ctx context.Context) (*EmailConfig, error)) (*Email, error) {
client, err := config.SMTP.connectToSMTP(config.Tls) smtpConfig, err := getSMTPConfig(ctx)
client, err := smtpConfig.SMTP.connectToSMTP(smtpConfig.Tls)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -42,12 +46,12 @@ func (email *Email) HandleMessage(message channels.Message) error {
if emailMsg.Content == "" || emailMsg.Subject == "" || len(emailMsg.Recipients) == 0 { if emailMsg.Content == "" || emailMsg.Subject == "" || len(emailMsg.Recipients) == 0 {
return caos_errs.ThrowInternalf(nil, "EMAIL-zGemZ", "subject, recipients and content must be set but got subject %s, recipients length %d and content length %d", emailMsg.Subject, len(emailMsg.Recipients), len(emailMsg.Content)) return caos_errs.ThrowInternalf(nil, "EMAIL-zGemZ", "subject, recipients and content must be set but got subject %s, recipients length %d and content length %d", emailMsg.Subject, len(emailMsg.Recipients), len(emailMsg.Content))
} }
emailMsg.SenderEmail = email.senderAddress
emailMsg.SenderName = email.senderName
// To && From // To && From
if err := email.smtpClient.Mail(emailMsg.SenderEmail); err != nil { if err := email.smtpClient.Mail(emailMsg.SenderEmail); err != nil {
return caos_errs.ThrowInternalf(err, "EMAIL-s3is3", "could not set sender: %v", emailMsg.SenderEmail) return caos_errs.ThrowInternalf(err, "EMAIL-s3is3", "could not set sender: %v", emailMsg.SenderEmail)
} }
for _, recp := range append(append(emailMsg.Recipients, emailMsg.CC...), emailMsg.BCC...) { for _, recp := range append(append(emailMsg.Recipients, emailMsg.CC...), emailMsg.BCC...) {
if err := email.smtpClient.Rcpt(recp); err != nil { if err := email.smtpClient.Rcpt(recp); err != nil {
return caos_errs.ThrowInternalf(err, "EMAIL-s4is4", "could not set recipient: %v", recp) return caos_errs.ThrowInternalf(err, "EMAIL-s4is4", "could not set recipient: %v", recp)

View File

@ -20,13 +20,18 @@ type Email struct {
BCC []string BCC []string
CC []string CC []string
SenderEmail string SenderEmail string
SenderName string
Subject string Subject string
Content string Content string
} }
func (msg *Email) GetContent() string { func (msg *Email) GetContent() string {
headers := make(map[string]string) headers := make(map[string]string)
headers["From"] = msg.SenderEmail from := msg.SenderEmail
if msg.SenderName != "" {
from = fmt.Sprintf("%s <%s>", msg.SenderName, msg.SenderEmail)
}
headers["From"] = from
headers["To"] = strings.Join(msg.Recipients, ", ") headers["To"] = strings.Join(msg.Recipients, ", ")
headers["Cc"] = strings.Join(msg.CC, ", ") headers["Cc"] = strings.Join(msg.CC, ", ")

View File

@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
"github.com/rakyll/statik/fs" "github.com/rakyll/statik/fs"
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
@ -17,10 +18,10 @@ type Config struct {
Repository eventsourcing.Config Repository eventsourcing.Config
} }
func Start(config Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string) { func Start(config Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm) {
statikFS, err := fs.NewWithNamespace("notification") statikFS, err := fs.NewWithNamespace("notification")
logging.OnError(err).Panic("unable to start listener") logging.OnError(err).Panic("unable to start listener")
_, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, queries, dbClient, assetsPrefix) _, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, queries, dbClient, assetsPrefix, smtpPasswordEncAlg)
logging.OnError(err).Panic("unable to start app") logging.OnError(err).Panic("unable to start app")
} }

View File

@ -34,14 +34,13 @@ func (h *handler) Eventstore() v1.Eventstore {
return h.es return h.es
} }
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string) []queryv1.Handler { func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm) []queryv1.Handler {
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey) aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey)
logging.OnError(err).Fatal("error create new aes crypto") logging.OnError(err).Fatal("error create new aes crypto")
return []queryv1.Handler{ return []queryv1.Handler{
newNotifyUser( newNotifyUser(
handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es}, handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es},
systemDefaults.IamID,
queries, queries,
), ),
newNotification( newNotification(
@ -52,6 +51,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
aesCrypto, aesCrypto,
dir, dir,
assetsPrefix, assetsPrefix,
smtpPasswordEncAlg,
), ),
} }
} }

View File

@ -19,6 +19,7 @@ import (
queryv1 "github.com/caos/zitadel/internal/eventstore/v1/query" queryv1 "github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler" "github.com/caos/zitadel/internal/eventstore/v1/spooler"
"github.com/caos/zitadel/internal/i18n" "github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/notification/types" "github.com/caos/zitadel/internal/notification/types"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
user_repo "github.com/caos/zitadel/internal/repository/user" user_repo "github.com/caos/zitadel/internal/repository/user"
@ -34,13 +35,14 @@ const (
type Notification struct { type Notification struct {
handler handler
command *command.Commands command *command.Commands
systemDefaults sd.SystemDefaults systemDefaults sd.SystemDefaults
AesCrypto crypto.EncryptionAlgorithm AesCrypto crypto.EncryptionAlgorithm
statikDir http.FileSystem statikDir http.FileSystem
subscription *v1.Subscription subscription *v1.Subscription
assetsPrefix string assetsPrefix string
queries *query.Queries queries *query.Queries
smtpPasswordCrypto crypto.EncryptionAlgorithm
} }
func newNotification( func newNotification(
@ -51,15 +53,17 @@ func newNotification(
aesCrypto crypto.EncryptionAlgorithm, aesCrypto crypto.EncryptionAlgorithm,
statikDir http.FileSystem, statikDir http.FileSystem,
assetsPrefix string, assetsPrefix string,
smtpPasswordEncAlg crypto.EncryptionAlgorithm,
) *Notification { ) *Notification {
h := &Notification{ h := &Notification{
handler: handler, handler: handler,
command: command, command: command,
systemDefaults: defaults, systemDefaults: defaults,
statikDir: statikDir, statikDir: statikDir,
AesCrypto: aesCrypto, AesCrypto: aesCrypto,
assetsPrefix: assetsPrefix, assetsPrefix: assetsPrefix,
queries: query, queries: query,
smtpPasswordCrypto: smtpPasswordEncAlg,
} }
h.subscribe() h.subscribe()
@ -161,7 +165,7 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
return err return err
} }
err = types.SendUserInitCode(string(template.Template), translator, user, initCode, n.systemDefaults, n.AesCrypto, colors, n.assetsPrefix) err = types.SendUserInitCode(ctx, string(template.Template), translator, user, initCode, n.systemDefaults, n.getSMTPConfig, n.AesCrypto, colors, n.assetsPrefix)
if err != nil { if err != nil {
return err return err
} }
@ -199,7 +203,7 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
if err != nil { if err != nil {
return err return err
} }
err = types.SendPasswordCode(string(template.Template), translator, user, pwCode, n.systemDefaults, n.AesCrypto, colors, n.assetsPrefix) err = types.SendPasswordCode(ctx, string(template.Template), translator, user, pwCode, n.systemDefaults, n.getSMTPConfig, n.AesCrypto, colors, n.assetsPrefix)
if err != nil { if err != nil {
return err return err
} }
@ -238,7 +242,7 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
return err return err
} }
err = types.SendEmailVerificationCode(string(template.Template), translator, user, emailCode, n.systemDefaults, n.AesCrypto, colors, n.assetsPrefix) err = types.SendEmailVerificationCode(ctx, string(template.Template), translator, user, emailCode, n.systemDefaults, n.getSMTPConfig, n.AesCrypto, colors, n.assetsPrefix)
if err != nil { if err != nil {
return err return err
} }
@ -303,7 +307,8 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) {
if err != nil { if err != nil {
return err return err
} }
err = types.SendDomainClaimed(string(template.Template), translator, user, data["userName"], n.systemDefaults, colors, n.assetsPrefix)
err = types.SendDomainClaimed(ctx, string(template.Template), translator, user, data["userName"], n.systemDefaults, n.getSMTPConfig, colors, n.assetsPrefix)
if err != nil { if err != nil {
return err return err
} }
@ -349,7 +354,8 @@ func (n *Notification) handlePasswordlessRegistrationLink(event *models.Event) (
if err != nil { if err != nil {
return err return err
} }
err = types.SendPasswordlessRegistrationLink(string(template.Template), translator, user, addedEvent, n.systemDefaults, n.AesCrypto, colors, n.assetsPrefix)
err = types.SendPasswordlessRegistrationLink(ctx, string(template.Template), translator, user, addedEvent, n.systemDefaults, n.getSMTPConfig, n.AesCrypto, colors, n.assetsPrefix)
if err != nil { if err != nil {
return err return err
} }
@ -410,12 +416,34 @@ func (n *Notification) getMailTemplate(ctx context.Context) (*query.MailTemplate
return n.queries.MailTemplateByOrg(ctx, authz.GetCtxData(ctx).OrgID) return n.queries.MailTemplateByOrg(ctx, authz.GetCtxData(ctx).OrgID)
} }
func (n *Notification) getTranslatorWithOrgTexts(orgID, textType string) (*i18n.Translator, error) { // Read iam smtp config
translator, err := i18n.NewTranslator(n.statikDir, i18n.TranslatorConfig{DefaultLanguage: n.systemDefaults.DefaultLanguage}) func (n *Notification) getSMTPConfig(ctx context.Context) (*smtp.EmailConfig, error) {
config, err := n.queries.SMTPConfigByAggregateID(ctx, domain.IAMID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ctx := context.TODO() password, err := crypto.Decrypt(config.Password, n.smtpPasswordCrypto)
if err != nil {
return nil, err
}
return &smtp.EmailConfig{
From: config.SenderAddress,
FromName: config.SenderName,
SMTP: smtp.SMTP{
Host: config.Host,
User: config.User,
Password: string(password),
},
}, nil
}
func (n *Notification) getTranslatorWithOrgTexts(orgID, textType string) (*i18n.Translator, error) {
ctx := context.Background()
translator, err := i18n.NewTranslator(n.statikDir, i18n.TranslatorConfig{DefaultLanguage: n.queries.GetDefaultLanguage(ctx)})
if err != nil {
return nil, err
}
allCustomTexts, err := n.queries.CustomTextListByTemplate(ctx, domain.IAMID, textType) allCustomTexts, err := n.queries.CustomTextListByTemplate(ctx, domain.IAMID, textType)
if err != nil { if err != nil {
return translator, nil return translator, nil

View File

@ -26,19 +26,16 @@ const (
type NotifyUser struct { type NotifyUser struct {
handler handler
iamID string
subscription *v1.Subscription subscription *v1.Subscription
queries *query2.Queries queries *query2.Queries
} }
func newNotifyUser( func newNotifyUser(
handler handler, handler handler,
iamID string,
queries *query2.Queries, queries *query2.Queries,
) *NotifyUser { ) *NotifyUser {
h := &NotifyUser{ h := &NotifyUser{
handler: handler, handler: handler,
iamID: iamID,
queries: queries, queries: queries,
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
sd "github.com/caos/zitadel/internal/config/systemdefaults" sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
v1 "github.com/caos/zitadel/internal/eventstore/v1" v1 "github.com/caos/zitadel/internal/eventstore/v1"
es_spol "github.com/caos/zitadel/internal/eventstore/v1/spooler" es_spol "github.com/caos/zitadel/internal/eventstore/v1/spooler"
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/spooler" "github.com/caos/zitadel/internal/notification/repository/eventsourcing/spooler"
@ -21,7 +22,7 @@ type EsRepository struct {
spooler *es_spol.Spooler spooler *es_spol.Spooler
} }
func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string) (*EsRepository, error) { func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm) (*EsRepository, error) {
es, err := v1.Start(dbClient) es, err := v1.Start(dbClient)
if err != nil { if err != nil {
return nil, err return nil, err
@ -32,7 +33,7 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, c
return nil, err return nil, err
} }
spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, command, queries, systemDefaults, dir, assetsPrefix) spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, command, queries, systemDefaults, dir, assetsPrefix, smtpPasswordEncAlg)
return &EsRepository{ return &EsRepository{
spool, spool,

View File

@ -6,6 +6,7 @@ import (
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
sd "github.com/caos/zitadel/internal/config/systemdefaults" sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
v1 "github.com/caos/zitadel/internal/eventstore/v1" v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/spooler" "github.com/caos/zitadel/internal/eventstore/v1/spooler"
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/handler" "github.com/caos/zitadel/internal/notification/repository/eventsourcing/handler"
@ -20,12 +21,12 @@ type SpoolerConfig struct {
Handlers handler.Configs Handlers handler.Configs
} }
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string) *spooler.Spooler { func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm) *spooler.Spooler {
spoolerConfig := spooler.Config{ spoolerConfig := spooler.Config{
Eventstore: es, Eventstore: es,
Locker: &locker{dbClient: sql}, Locker: &locker{dbClient: sql},
ConcurrentWorkers: c.ConcurrentWorkers, ConcurrentWorkers: c.ConcurrentWorkers,
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, queries, systemDefaults, dir, assetsPrefix), ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, queries, systemDefaults, dir, assetsPrefix, smtpPasswordEncAlg),
} }
spool := spoolerConfig.New() spool := spoolerConfig.New()
spool.Start() spool.Start()

View File

@ -1,12 +1,14 @@
package senders package senders
import ( import (
"context"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/notification/channels" "github.com/caos/zitadel/internal/notification/channels"
"github.com/caos/zitadel/internal/notification/channels/smtp" "github.com/caos/zitadel/internal/notification/channels/smtp"
) )
func EmailChannels(config systemdefaults.Notifications) (channels.NotificationChannel, error) { func EmailChannels(ctx context.Context, config systemdefaults.Notifications, emailConfig func(ctx context.Context) (*smtp.EmailConfig, error)) (channels.NotificationChannel, error) {
debug, err := debugChannels(config) debug, err := debugChannels(config)
if err != nil { if err != nil {
@ -14,7 +16,7 @@ func EmailChannels(config systemdefaults.Notifications) (channels.NotificationCh
} }
if !config.DebugMode { if !config.DebugMode {
p, err := smtp.InitSMTPChannel(config.Providers.Email) p, err := smtp.InitSMTPChannel(ctx, emailConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,11 +1,13 @@
package types package types
import ( import (
"context"
"strings" "strings"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/i18n" "github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
view_model "github.com/caos/zitadel/internal/user/repository/view/model" view_model "github.com/caos/zitadel/internal/user/repository/view/model"
@ -16,7 +18,7 @@ type DomainClaimedData struct {
URL string URL string
} }
func SendDomainClaimed(mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *query.LabelPolicy, assetsPrefix string) error { func SendDomainClaimed(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, emailConfig func(ctx context.Context) (*smtp.EmailConfig, error), colors *query.LabelPolicy, assetsPrefix string) error {
url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.DomainClaimed, &UrlData{UserID: user.ID}) url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.DomainClaimed, &UrlData{UserID: user.ID})
if err != nil { if err != nil {
return err return err
@ -33,5 +35,5 @@ func SendDomainClaimed(mailhtml string, translator *i18n.Translator, user *view_
if err != nil { if err != nil {
return err return err
} }
return generateEmail(user, domainClaimedData.Subject, template, systemDefaults.Notifications, true) return generateEmail(ctx, user, domainClaimedData.Subject, template, systemDefaults.Notifications, emailConfig, true)
} }

View File

@ -1,10 +1,13 @@
package types package types
import ( import (
"context"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/i18n" "github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
@ -16,7 +19,7 @@ type EmailVerificationCodeData struct {
URL string URL string
} }
func SendEmailVerificationCode(mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { func SendEmailVerificationCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error {
codeString, err := crypto.DecryptString(code.Code, alg) codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil { if err != nil {
return err return err
@ -38,5 +41,5 @@ func SendEmailVerificationCode(mailhtml string, translator *i18n.Translator, use
if err != nil { if err != nil {
return err return err
} }
return generateEmail(user, emailCodeData.Subject, template, systemDefaults.Notifications, true) return generateEmail(ctx, user, emailCodeData.Subject, template, systemDefaults.Notifications, smtpConfig, true)
} }

View File

@ -1,10 +1,13 @@
package types package types
import ( import (
"context"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/i18n" "github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
@ -22,7 +25,7 @@ type UrlData struct {
PasswordSet bool PasswordSet bool
} }
func SendUserInitCode(mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { func SendUserInitCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error {
codeString, err := crypto.DecryptString(code.Code, alg) codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil { if err != nil {
return err return err
@ -42,5 +45,5 @@ func SendUserInitCode(mailhtml string, translator *i18n.Translator, user *view_m
if err != nil { if err != nil {
return err return err
} }
return generateEmail(user, initCodeData.Subject, template, systemDefaults.Notifications, true) return generateEmail(ctx, user, initCodeData.Subject, template, systemDefaults.Notifications, smtpConfig, true)
} }

View File

@ -1,10 +1,13 @@
package types package types
import ( import (
"context"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/i18n" "github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
@ -18,7 +21,7 @@ type PasswordCodeData struct {
URL string URL string
} }
func SendPasswordCode(mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { func SendPasswordCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error {
codeString, err := crypto.DecryptString(code.Code, alg) codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil { if err != nil {
return err return err
@ -43,6 +46,6 @@ func SendPasswordCode(mailhtml string, translator *i18n.Translator, user *view_m
if code.NotificationType == int32(domain.NotificationTypeSms) { if code.NotificationType == int32(domain.NotificationTypeSms) {
return generateSms(user, passwordResetData.Text, systemDefaults.Notifications, false) return generateSms(user, passwordResetData.Text, systemDefaults.Notifications, false)
} }
return generateEmail(user, passwordResetData.Subject, template, systemDefaults.Notifications, true) return generateEmail(ctx, user, passwordResetData.Subject, template, systemDefaults.Notifications, smtpConfig, true)
} }

View File

@ -1,10 +1,13 @@
package types package types
import ( import (
"context"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/i18n" "github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/repository/user" "github.com/caos/zitadel/internal/repository/user"
@ -16,7 +19,7 @@ type PasswordlessRegistrationLinkData struct {
URL string URL string
} }
func SendPasswordlessRegistrationLink(mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *user.HumanPasswordlessInitCodeRequestedEvent, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { func SendPasswordlessRegistrationLink(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *user.HumanPasswordlessInitCodeRequestedEvent, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error {
codeString, err := crypto.DecryptString(code.Code, alg) codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil { if err != nil {
return err return err
@ -33,5 +36,5 @@ func SendPasswordlessRegistrationLink(mailhtml string, translator *i18n.Translat
if err != nil { if err != nil {
return err return err
} }
return generateEmail(user, emailCodeData.Subject, template, systemDefaults.Notifications, true) return generateEmail(ctx, user, emailCodeData.Subject, template, systemDefaults.Notifications, smtpConfig, true)
} }

View File

@ -1,8 +1,10 @@
package types package types
import ( import (
"context"
"html" "html"
"github.com/caos/zitadel/internal/notification/channels/smtp"
"github.com/caos/zitadel/internal/notification/messages" "github.com/caos/zitadel/internal/notification/messages"
"github.com/caos/zitadel/internal/notification/senders" "github.com/caos/zitadel/internal/notification/senders"
@ -10,19 +12,18 @@ import (
view_model "github.com/caos/zitadel/internal/user/repository/view/model" view_model "github.com/caos/zitadel/internal/user/repository/view/model"
) )
func generateEmail(user *view_model.NotifyUser, subject, content string, config systemdefaults.Notifications, lastEmail bool) error { func generateEmail(ctx context.Context, user *view_model.NotifyUser, subject, content string, config systemdefaults.Notifications, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), lastEmail bool) error {
content = html.UnescapeString(content) content = html.UnescapeString(content)
message := &messages.Email{ message := &messages.Email{
SenderEmail: config.Providers.Email.From, Recipients: []string{user.VerifiedEmail},
Recipients: []string{user.VerifiedEmail}, Subject: subject,
Subject: subject, Content: content,
Content: content,
} }
if lastEmail { if lastEmail {
message.Recipients = []string{user.LastEmail} message.Recipients = []string{user.LastEmail}
} }
channels, err := senders.EmailChannels(config) channels, err := senders.EmailChannels(ctx, config, smtpConfig)
if err != nil { if err != nil {
return err return err
} }

View File

@ -186,7 +186,7 @@ func (q *Queries) readLoginTranslationFile(lang string) ([]byte, error) {
if !ok { if !ok {
contents, err = q.readTranslationFile(q.LoginDir, fmt.Sprintf("/i18n/%s.yaml", lang)) contents, err = q.readTranslationFile(q.LoginDir, fmt.Sprintf("/i18n/%s.yaml", lang))
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
contents, err = q.readTranslationFile(q.LoginDir, fmt.Sprintf("/i18n/%s.yaml", q.DefaultLanguage.String())) contents, err = q.readTranslationFile(q.LoginDir, fmt.Sprintf("/i18n/%s.yaml", q.GetDefaultLanguage(context.Background()).String()))
} }
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -10,6 +10,7 @@ import (
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/query/projection" "github.com/caos/zitadel/internal/query/projection"
"golang.org/x/text/language"
) )
var ( var (
@ -44,6 +45,10 @@ var (
name: projection.IAMColumnSetUpDone, name: projection.IAMColumnSetUpDone,
table: iamTable, table: iamTable,
} }
IAMColumnDefaultLanguage = Column{
name: projection.IAMColumnDefaultLanguage,
table: iamTable,
}
) )
type IAM struct { type IAM struct {
@ -51,10 +56,11 @@ type IAM struct {
ChangeDate time.Time ChangeDate time.Time
Sequence uint64 Sequence uint64
GlobalOrgID string GlobalOrgID string
IAMProjectID string IAMProjectID string
SetupStarted domain.Step DefaultLanguage language.Tag
SetupDone domain.Step SetupStarted domain.Step
SetupDone domain.Step
} }
type IAMSearchQueries struct { type IAMSearchQueries struct {
@ -83,6 +89,14 @@ func (q *Queries) IAMByID(ctx context.Context, id string) (*IAM, error) {
return scan(row) return scan(row)
} }
func (q *Queries) GetDefaultLanguage(ctx context.Context) language.Tag {
iam, err := q.IAMByID(ctx, domain.IAMID)
if err != nil {
return language.Und
}
return iam.DefaultLanguage
}
func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*IAM, error)) { func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*IAM, error)) {
return sq.Select( return sq.Select(
IAMColumnID.identifier(), IAMColumnID.identifier(),
@ -92,18 +106,21 @@ func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*IAM, error)) {
IAMColumnProjectID.identifier(), IAMColumnProjectID.identifier(),
IAMColumnSetupStarted.identifier(), IAMColumnSetupStarted.identifier(),
IAMColumnSetupDone.identifier(), IAMColumnSetupDone.identifier(),
IAMColumnDefaultLanguage.identifier(),
). ).
From(iamTable.identifier()).PlaceholderFormat(sq.Dollar), From(iamTable.identifier()).PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*IAM, error) { func(row *sql.Row) (*IAM, error) {
o := new(IAM) iam := new(IAM)
lang := ""
err := row.Scan( err := row.Scan(
&o.ID, &iam.ID,
&o.ChangeDate, &iam.ChangeDate,
&o.Sequence, &iam.Sequence,
&o.GlobalOrgID, &iam.GlobalOrgID,
&o.IAMProjectID, &iam.IAMProjectID,
&o.SetupStarted, &iam.SetupStarted,
&o.SetupDone, &iam.SetupDone,
&lang,
) )
if err != nil { if err != nil {
if errs.Is(err, sql.ErrNoRows) { if errs.Is(err, sql.ErrNoRows) {
@ -111,6 +128,7 @@ func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*IAM, error)) {
} }
return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal") return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
} }
return o, nil iam.DefaultLanguage = language.Make(lang)
return iam, nil
} }
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
errs "github.com/caos/zitadel/internal/errors" errs "github.com/caos/zitadel/internal/errors"
"golang.org/x/text/language"
) )
func Test_IAMPrepares(t *testing.T) { func Test_IAMPrepares(t *testing.T) {
@ -34,7 +35,8 @@ func Test_IAMPrepares(t *testing.T) {
` zitadel.projections.iam.global_org_id,`+ ` zitadel.projections.iam.global_org_id,`+
` zitadel.projections.iam.iam_project_id,`+ ` zitadel.projections.iam.iam_project_id,`+
` zitadel.projections.iam.setup_started,`+ ` zitadel.projections.iam.setup_started,`+
` zitadel.projections.iam.setup_done`+ ` zitadel.projections.iam.setup_done,`+
` zitadel.projections.iam.default_language`+
` FROM zitadel.projections.iam`), ` FROM zitadel.projections.iam`),
nil, nil,
nil, nil,
@ -59,7 +61,8 @@ func Test_IAMPrepares(t *testing.T) {
` zitadel.projections.iam.global_org_id,`+ ` zitadel.projections.iam.global_org_id,`+
` zitadel.projections.iam.iam_project_id,`+ ` zitadel.projections.iam.iam_project_id,`+
` zitadel.projections.iam.setup_started,`+ ` zitadel.projections.iam.setup_started,`+
` zitadel.projections.iam.setup_done`+ ` zitadel.projections.iam.setup_done,`+
` zitadel.projections.iam.default_language`+
` FROM zitadel.projections.iam`), ` FROM zitadel.projections.iam`),
[]string{ []string{
"id", "id",
@ -69,6 +72,7 @@ func Test_IAMPrepares(t *testing.T) {
"iam_project_id", "iam_project_id",
"setup_started", "setup_started",
"setup_done", "setup_done",
"default_language",
}, },
[]driver.Value{ []driver.Value{
"id", "id",
@ -78,17 +82,19 @@ func Test_IAMPrepares(t *testing.T) {
"project-id", "project-id",
domain.Step2, domain.Step2,
domain.Step1, domain.Step1,
"en",
}, },
), ),
}, },
object: &IAM{ object: &IAM{
ID: "id", ID: "id",
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211108, Sequence: 20211108,
GlobalOrgID: "global-org-id", GlobalOrgID: "global-org-id",
IAMProjectID: "project-id", IAMProjectID: "project-id",
SetupStarted: domain.Step2, SetupStarted: domain.Step2,
SetupDone: domain.Step1, SetupDone: domain.Step1,
DefaultLanguage: language.English,
}, },
}, },
{ {
@ -102,7 +108,8 @@ func Test_IAMPrepares(t *testing.T) {
` zitadel.projections.iam.global_org_id,`+ ` zitadel.projections.iam.global_org_id,`+
` zitadel.projections.iam.iam_project_id,`+ ` zitadel.projections.iam.iam_project_id,`+
` zitadel.projections.iam.setup_started,`+ ` zitadel.projections.iam.setup_started,`+
` zitadel.projections.iam.setup_done`+ ` zitadel.projections.iam.setup_done,`+
` zitadel.projections.iam.default_language`+
` FROM zitadel.projections.iam`), ` FROM zitadel.projections.iam`),
sql.ErrConnDone, sql.ErrConnDone,
), ),

View File

@ -8,7 +8,7 @@ import (
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
@ -186,7 +186,7 @@ func (q *Queries) IDPByIDAndResourceOwner(ctx context.Context, id, resourceOwner
IDPResourceOwnerCol.identifier(): resourceOwner, IDPResourceOwnerCol.identifier(): resourceOwner,
}, },
sq.Eq{ sq.Eq{
IDPResourceOwnerCol.identifier(): q.iamID, IDPResourceOwnerCol.identifier(): domain.IAMID,
}, },
}, },
}, },

View File

@ -47,7 +47,7 @@ func (q *Queries) ActiveLabelPolicyByOrg(ctx context.Context, orgID string) (*La
LabelPolicyColID.identifier(): orgID, LabelPolicyColID.identifier(): orgID,
}, },
sq.Eq{ sq.Eq{
LabelPolicyColID.identifier(): q.iamID, LabelPolicyColID.identifier(): domain.IAMID,
}, },
}, },
sq.Eq{ sq.Eq{
@ -73,7 +73,7 @@ func (q *Queries) PreviewLabelPolicyByOrg(ctx context.Context, orgID string) (*L
LabelPolicyColID.identifier(): orgID, LabelPolicyColID.identifier(): orgID,
}, },
sq.Eq{ sq.Eq{
LabelPolicyColID.identifier(): q.iamID, LabelPolicyColID.identifier(): domain.IAMID,
}, },
}, },
sq.Eq{ sq.Eq{
@ -93,7 +93,7 @@ func (q *Queries) PreviewLabelPolicyByOrg(ctx context.Context, orgID string) (*L
func (q *Queries) DefaultActiveLabelPolicy(ctx context.Context) (*LabelPolicy, error) { func (q *Queries) DefaultActiveLabelPolicy(ctx context.Context) (*LabelPolicy, error) {
stmt, scan := prepareLabelPolicyQuery() stmt, scan := prepareLabelPolicyQuery()
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
LabelPolicyColID.identifier(): q.iamID, LabelPolicyColID.identifier(): domain.IAMID,
LabelPolicyColState.identifier(): domain.LabelPolicyStateActive, LabelPolicyColState.identifier(): domain.LabelPolicyStateActive,
}). }).
OrderBy(LabelPolicyColIsDefault.identifier()). OrderBy(LabelPolicyColIsDefault.identifier()).
@ -109,7 +109,7 @@ func (q *Queries) DefaultActiveLabelPolicy(ctx context.Context) (*LabelPolicy, e
func (q *Queries) DefaultPreviewLabelPolicy(ctx context.Context) (*LabelPolicy, error) { func (q *Queries) DefaultPreviewLabelPolicy(ctx context.Context) (*LabelPolicy, error) {
stmt, scan := prepareLabelPolicyQuery() stmt, scan := prepareLabelPolicyQuery()
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
LabelPolicyColID.identifier(): q.iamID, LabelPolicyColID.identifier(): domain.IAMID,
LabelPolicyColState.identifier(): domain.LabelPolicyStatePreview, LabelPolicyColState.identifier(): domain.LabelPolicyStatePreview,
}). }).
OrderBy(LabelPolicyColIsDefault.identifier()). OrderBy(LabelPolicyColIsDefault.identifier()).

View File

@ -76,7 +76,7 @@ func (q *Queries) LockoutPolicyByOrg(ctx context.Context, orgID string) (*Lockou
LockoutColID.identifier(): orgID, LockoutColID.identifier(): orgID,
}, },
sq.Eq{ sq.Eq{
LockoutColID.identifier(): q.iamID, LockoutColID.identifier(): domain.IAMID,
}, },
}). }).
OrderBy(LockoutColIsDefault.identifier()). OrderBy(LockoutColIsDefault.identifier()).
@ -92,7 +92,7 @@ func (q *Queries) LockoutPolicyByOrg(ctx context.Context, orgID string) (*Lockou
func (q *Queries) DefaultLockoutPolicy(ctx context.Context) (*LockoutPolicy, error) { func (q *Queries) DefaultLockoutPolicy(ctx context.Context) (*LockoutPolicy, error) {
stmt, scan := prepareLockoutPolicyQuery() stmt, scan := prepareLockoutPolicyQuery()
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
LockoutColID.identifier(): q.iamID, LockoutColID.identifier(): domain.IAMID,
}). }).
OrderBy(LockoutColIsDefault.identifier()). OrderBy(LockoutColIsDefault.identifier()).
Limit(1).ToSql() Limit(1).ToSql()

View File

@ -65,7 +65,7 @@ func (q *Queries) MailTemplateByOrg(ctx context.Context, orgID string) (*MailTem
MailTemplateColAggregateID.identifier(): orgID, MailTemplateColAggregateID.identifier(): orgID,
}, },
sq.Eq{ sq.Eq{
MailTemplateColAggregateID.identifier(): q.iamID, MailTemplateColAggregateID.identifier(): domain.IAMID,
}, },
}). }).
OrderBy(MailTemplateColIsDefault.identifier()). OrderBy(MailTemplateColIsDefault.identifier()).
@ -81,7 +81,7 @@ func (q *Queries) MailTemplateByOrg(ctx context.Context, orgID string) (*MailTem
func (q *Queries) DefaultMailTemplate(ctx context.Context) (*MailTemplate, error) { func (q *Queries) DefaultMailTemplate(ctx context.Context) (*MailTemplate, error) {
stmt, scan := prepareMailTemplateQuery() stmt, scan := prepareMailTemplateQuery()
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
MailTemplateColAggregateID.identifier(): q.iamID, MailTemplateColAggregateID.identifier(): domain.IAMID,
}). }).
OrderBy(MailTemplateColIsDefault.identifier()). OrderBy(MailTemplateColIsDefault.identifier()).
Limit(1).ToSql() Limit(1).ToSql()

View File

@ -119,7 +119,7 @@ func (q *Queries) MessageTextByOrg(ctx context.Context, orgID string) (*MessageT
MessageTextColAggregateID.identifier(): orgID, MessageTextColAggregateID.identifier(): orgID,
}, },
sq.Eq{ sq.Eq{
MessageTextColAggregateID.identifier(): q.iamID, MessageTextColAggregateID.identifier(): domain.IAMID,
}, },
}). }).
OrderBy(MessageTextColAggregateID.identifier()). OrderBy(MessageTextColAggregateID.identifier()).
@ -135,7 +135,7 @@ func (q *Queries) MessageTextByOrg(ctx context.Context, orgID string) (*MessageT
func (q *Queries) DefaultMessageText(ctx context.Context) (*MessageText, error) { func (q *Queries) DefaultMessageText(ctx context.Context) (*MessageText, error) {
stmt, scan := prepareMessageTextQuery() stmt, scan := prepareMessageTextQuery()
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
MessageTextColAggregateID.identifier(): q.iamID, MessageTextColAggregateID.identifier(): domain.IAMID,
}). }).
Limit(1).ToSql() Limit(1).ToSql()
if err != nil { if err != nil {
@ -230,7 +230,7 @@ func (q *Queries) readNotificationTextMessages(language string) ([]byte, error)
if !ok { if !ok {
contents, err = q.readTranslationFile(q.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", language)) contents, err = q.readTranslationFile(q.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", language))
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
contents, err = q.readTranslationFile(q.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", q.DefaultLanguage.String())) contents, err = q.readTranslationFile(q.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", q.GetDefaultLanguage(context.Background()).String()))
} }
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -71,7 +71,7 @@ func (q *Queries) OrgIAMPolicyByOrg(ctx context.Context, orgID string) (*OrgIAMP
OrgIAMColID.identifier(): orgID, OrgIAMColID.identifier(): orgID,
}, },
sq.Eq{ sq.Eq{
OrgIAMColID.identifier(): q.iamID, OrgIAMColID.identifier(): domain.IAMID,
}, },
}). }).
OrderBy(OrgIAMColIsDefault.identifier()). OrderBy(OrgIAMColIsDefault.identifier()).
@ -87,7 +87,7 @@ func (q *Queries) OrgIAMPolicyByOrg(ctx context.Context, orgID string) (*OrgIAMP
func (q *Queries) DefaultOrgIAMPolicy(ctx context.Context) (*OrgIAMPolicy, error) { func (q *Queries) DefaultOrgIAMPolicy(ctx context.Context) (*OrgIAMPolicy, error) {
stmt, scan := prepareOrgIAMPolicyQuery() stmt, scan := prepareOrgIAMPolicyQuery()
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
OrgIAMColID.identifier(): q.iamID, OrgIAMColID.identifier(): domain.IAMID,
}). }).
OrderBy(OrgIAMColIsDefault.identifier()). OrderBy(OrgIAMColIsDefault.identifier()).
Limit(1).ToSql() Limit(1).ToSql()

View File

@ -76,7 +76,7 @@ func (q *Queries) PasswordAgePolicyByOrg(ctx context.Context, orgID string) (*Pa
PasswordAgeColID.identifier(): orgID, PasswordAgeColID.identifier(): orgID,
}, },
sq.Eq{ sq.Eq{
PasswordAgeColID.identifier(): q.iamID, PasswordAgeColID.identifier(): domain.IAMID,
}, },
}). }).
OrderBy(PasswordAgeColIsDefault.identifier()). OrderBy(PasswordAgeColIsDefault.identifier()).
@ -92,7 +92,7 @@ func (q *Queries) PasswordAgePolicyByOrg(ctx context.Context, orgID string) (*Pa
func (q *Queries) DefaultPasswordAgePolicy(ctx context.Context) (*PasswordAgePolicy, error) { func (q *Queries) DefaultPasswordAgePolicy(ctx context.Context) (*PasswordAgePolicy, error) {
stmt, scan := preparePasswordAgePolicyQuery() stmt, scan := preparePasswordAgePolicyQuery()
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
PasswordAgeColID.identifier(): q.iamID, PasswordAgeColID.identifier(): domain.IAMID,
}). }).
OrderBy(PasswordAgeColIsDefault.identifier()). OrderBy(PasswordAgeColIsDefault.identifier()).
Limit(1).ToSql() Limit(1).ToSql()

View File

@ -37,7 +37,7 @@ func (q *Queries) PasswordComplexityPolicyByOrg(ctx context.Context, orgID strin
PasswordComplexityColID.identifier(): orgID, PasswordComplexityColID.identifier(): orgID,
}, },
sq.Eq{ sq.Eq{
PasswordComplexityColID.identifier(): q.iamID, PasswordComplexityColID.identifier(): domain.IAMID,
}, },
}). }).
OrderBy(PasswordComplexityColIsDefault.identifier()). OrderBy(PasswordComplexityColIsDefault.identifier()).
@ -53,7 +53,7 @@ func (q *Queries) PasswordComplexityPolicyByOrg(ctx context.Context, orgID strin
func (q *Queries) DefaultPasswordComplexityPolicy(ctx context.Context) (*PasswordComplexityPolicy, error) { func (q *Queries) DefaultPasswordComplexityPolicy(ctx context.Context) (*PasswordComplexityPolicy, error) {
stmt, scan := preparePasswordComplexityPolicyQuery() stmt, scan := preparePasswordComplexityPolicyQuery()
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
PasswordComplexityColID.identifier(): q.iamID, PasswordComplexityColID.identifier(): domain.IAMID,
}). }).
OrderBy(PasswordComplexityColIsDefault.identifier()). OrderBy(PasswordComplexityColIsDefault.identifier()).
Limit(1).ToSql() Limit(1).ToSql()

View File

@ -76,7 +76,7 @@ func (q *Queries) PrivacyPolicyByOrg(ctx context.Context, orgID string) (*Privac
PrivacyColID.identifier(): orgID, PrivacyColID.identifier(): orgID,
}, },
sq.Eq{ sq.Eq{
PrivacyColID.identifier(): q.iamID, PrivacyColID.identifier(): domain.IAMID,
}, },
}). }).
OrderBy(PrivacyColIsDefault.identifier()). OrderBy(PrivacyColIsDefault.identifier()).
@ -92,7 +92,7 @@ func (q *Queries) PrivacyPolicyByOrg(ctx context.Context, orgID string) (*Privac
func (q *Queries) DefaultPrivacyPolicy(ctx context.Context) (*PrivacyPolicy, error) { func (q *Queries) DefaultPrivacyPolicy(ctx context.Context) (*PrivacyPolicy, error) {
stmt, scan := preparePrivacyPolicyQuery() stmt, scan := preparePrivacyPolicyQuery()
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
PrivacyColID.identifier(): q.iamID, PrivacyColID.identifier(): domain.IAMID,
}). }).
OrderBy(PrivacyColIsDefault.identifier()). OrderBy(PrivacyColIsDefault.identifier()).
Limit(1).ToSql() Limit(1).ToSql()

View File

@ -40,6 +40,10 @@ func (p *IAMProjection) reducers() []handler.AggregateReducer {
Event: iam.ProjectSetEventType, Event: iam.ProjectSetEventType,
Reduce: p.reduceIAMProjectSet, Reduce: p.reduceIAMProjectSet,
}, },
{
Event: iam.DefaultLanguageSetEventType,
Reduce: p.reduceDefaultLanguageSet,
},
{ {
Event: iam.SetupStartedEventType, Event: iam.SetupStartedEventType,
Reduce: p.reduceSetupEvent, Reduce: p.reduceSetupEvent,
@ -56,13 +60,14 @@ func (p *IAMProjection) reducers() []handler.AggregateReducer {
type IAMColumn string type IAMColumn string
const ( const (
IAMColumnID = "id" IAMColumnID = "id"
IAMColumnChangeDate = "change_date" IAMColumnChangeDate = "change_date"
IAMColumnGlobalOrgID = "global_org_id" IAMColumnGlobalOrgID = "global_org_id"
IAMColumnProjectID = "iam_project_id" IAMColumnProjectID = "iam_project_id"
IAMColumnSequence = "sequence" IAMColumnSequence = "sequence"
IAMColumnSetUpStarted = "setup_started" IAMColumnSetUpStarted = "setup_started"
IAMColumnSetUpDone = "setup_done" IAMColumnSetUpDone = "setup_done"
IAMColumnDefaultLanguage = "default_language"
) )
func (p *IAMProjection) reduceGlobalOrgSet(event eventstore.Event) (*handler.Statement, error) { func (p *IAMProjection) reduceGlobalOrgSet(event eventstore.Event) (*handler.Statement, error) {
@ -99,6 +104,23 @@ func (p *IAMProjection) reduceIAMProjectSet(event eventstore.Event) (*handler.St
), nil ), nil
} }
func (p *IAMProjection) reduceDefaultLanguageSet(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*iam.DefaultLanguageSetEvent)
if !ok {
logging.LogWithFields("HANDL-3n9le", "seq", event.Sequence(), "expectedType", iam.DefaultLanguageSetEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-30o0e", "reduce.wrong.event.type")
}
return crdb.NewUpsertStatement(
e,
[]handler.Column{
handler.NewCol(IAMColumnID, e.Aggregate().ID),
handler.NewCol(IAMColumnChangeDate, e.CreationDate()),
handler.NewCol(IAMColumnSequence, e.Sequence()),
handler.NewCol(IAMColumnDefaultLanguage, e.Language.String()),
},
), nil
}
func (p *IAMProjection) reduceSetupEvent(event eventstore.Event) (*handler.Statement, error) { func (p *IAMProjection) reduceSetupEvent(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*iam.SetupStepEvent) e, ok := event.(*iam.SetupStepEvent)
if !ok { if !ok {

View File

@ -52,7 +52,7 @@ func TestIAMProjection_reduces(t *testing.T) {
}, },
}, },
{ {
name: "reduceGlobalOrgSet", name: "reduceProjectIDSet",
args: args{ args: args{
event: getEvent(testEvent( event: getEvent(testEvent(
repository.EventType(iam.ProjectSetEventType), repository.EventType(iam.ProjectSetEventType),
@ -81,6 +81,36 @@ func TestIAMProjection_reduces(t *testing.T) {
}, },
}, },
}, },
{
name: "reduceDefaultLanguageSet",
args: args{
event: getEvent(testEvent(
repository.EventType(iam.DefaultLanguageSetEventType),
iam.AggregateType,
[]byte(`{"language": "en"}`),
), iam.DefaultLanguageSetMapper),
},
reduce: (&IAMProjection{}).reduceDefaultLanguageSet,
want: wantReduce{
projection: IAMProjectionTable,
aggregateType: eventstore.AggregateType("iam"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPSERT INTO zitadel.projections.iam (id, change_date, sequence, default_language) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
uint64(15),
"en",
},
},
},
},
},
},
{ {
name: "reduceSetupStarted", name: "reduceSetupStarted",
args: args{ args: args{

View File

@ -69,6 +69,8 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co
NewUserMetadataProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["user_metadata"])) NewUserMetadataProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["user_metadata"]))
NewUserAuthMethodProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["user_auth_method"])) NewUserAuthMethodProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["user_auth_method"]))
NewIAMProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["iam"])) NewIAMProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["iam"]))
NewSecretGeneratorProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["secret_generators"]))
NewSMTPConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["smtp_configs"]))
_, err := NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyConfig, keyChan) _, err := NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyConfig, keyChan)
return err return err

View File

@ -0,0 +1,144 @@
package projection
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/handler"
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/project"
)
type SecretGeneratorProjection struct {
crdb.StatementHandler
}
const (
SecretGeneratorProjectionTable = "zitadel.projections.secret_generators"
)
func NewSecretGeneratorProjection(ctx context.Context, config crdb.StatementHandlerConfig) *SecretGeneratorProjection {
p := &SecretGeneratorProjection{}
config.ProjectionName = SecretGeneratorProjectionTable
config.Reducers = p.reducers()
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
return p
}
func (p *SecretGeneratorProjection) reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: project.AggregateType,
EventRedusers: []handler.EventReducer{
{
Event: iam.SecretGeneratorAddedEventType,
Reduce: p.reduceSecretGeneratorAdded,
},
{
Event: iam.SecretGeneratorChangedEventType,
Reduce: p.reduceSecretGeneratorChanged,
},
{
Event: iam.SecretGeneratorRemovedEventType,
Reduce: p.reduceSecretGeneratorRemoved,
},
},
},
}
}
const (
SecretGeneratorColumnGeneratorType = "generator_type"
SecretGeneratorColumnAggregateID = "aggregate_id"
SecretGeneratorColumnCreationDate = "creation_date"
SecretGeneratorColumnChangeDate = "change_date"
SecretGeneratorColumnResourceOwner = "resource_owner"
SecretGeneratorColumnSequence = "sequence"
SecretGeneratorColumnLength = "length"
SecretGeneratorColumnExpiry = "expiry"
SecretGeneratorColumnIncludeLowerLetters = "include_lower_letters"
SecretGeneratorColumnIncludeUpperLetters = "include_upper_letters"
SecretGeneratorColumnIncludeDigits = "include_digits"
SecretGeneratorColumnIncludeSymbols = "include_symbols"
)
func (p *SecretGeneratorProjection) reduceSecretGeneratorAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*iam.SecretGeneratorAddedEvent)
if !ok {
logging.LogWithFields("HANDL-nf9sl", "seq", event.Sequence(), "expectedType", iam.SecretGeneratorAddedEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-sk99F", "reduce.wrong.event.type")
}
return crdb.NewCreateStatement(
e,
[]handler.Column{
handler.NewCol(SecretGeneratorColumnAggregateID, e.Aggregate().ID),
handler.NewCol(SecretGeneratorColumnGeneratorType, e.GeneratorType),
handler.NewCol(SecretGeneratorColumnCreationDate, e.CreationDate()),
handler.NewCol(SecretGeneratorColumnChangeDate, e.CreationDate()),
handler.NewCol(SecretGeneratorColumnResourceOwner, e.Aggregate().ResourceOwner),
handler.NewCol(SecretGeneratorColumnSequence, e.Sequence()),
handler.NewCol(SecretGeneratorColumnLength, e.Length),
handler.NewCol(SecretGeneratorColumnExpiry, e.Expiry),
handler.NewCol(SecretGeneratorColumnIncludeLowerLetters, e.IncludeLowerLetters),
handler.NewCol(SecretGeneratorColumnIncludeUpperLetters, e.IncludeUpperLetters),
handler.NewCol(SecretGeneratorColumnIncludeDigits, e.IncludeDigits),
handler.NewCol(SecretGeneratorColumnIncludeSymbols, e.IncludeSymbols),
},
), nil
}
func (p *SecretGeneratorProjection) reduceSecretGeneratorChanged(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*iam.SecretGeneratorChangedEvent)
if !ok {
logging.LogWithFields("HANDL-sn9jd", "seq", event.Sequence(), "expected", iam.SecretGeneratorChangedEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-s00Fs", "reduce.wrong.event.type")
}
columns := make([]handler.Column, 0, 7)
columns = append(columns, handler.NewCol(SecretGeneratorColumnChangeDate, e.CreationDate()),
handler.NewCol(SecretGeneratorColumnSequence, e.Sequence()))
if e.Length != nil {
columns = append(columns, handler.NewCol(SecretGeneratorColumnLength, *e.Length))
}
if e.Expiry != nil {
columns = append(columns, handler.NewCol(SecretGeneratorColumnExpiry, *e.Expiry))
}
if e.IncludeLowerLetters != nil {
columns = append(columns, handler.NewCol(SecretGeneratorColumnIncludeLowerLetters, *e.IncludeLowerLetters))
}
if e.IncludeUpperLetters != nil {
columns = append(columns, handler.NewCol(SecretGeneratorColumnIncludeUpperLetters, *e.IncludeUpperLetters))
}
if e.IncludeDigits != nil {
columns = append(columns, handler.NewCol(SecretGeneratorColumnIncludeDigits, *e.IncludeDigits))
}
if e.IncludeSymbols != nil {
columns = append(columns, handler.NewCol(SecretGeneratorColumnIncludeSymbols, *e.IncludeSymbols))
}
return crdb.NewUpdateStatement(
e,
columns,
[]handler.Condition{
handler.NewCond(SecretGeneratorColumnAggregateID, e.Aggregate().ID),
handler.NewCond(SecretGeneratorColumnGeneratorType, e.GeneratorType),
},
), nil
}
func (p *SecretGeneratorProjection) reduceSecretGeneratorRemoved(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*iam.SecretGeneratorRemovedEvent)
if !ok {
logging.LogWithFields("HANDL-30oEF", "seq", event.Sequence(), "expectedType", iam.SecretGeneratorRemovedEventType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-fmiIf", "reduce.wrong.event.type")
}
return crdb.NewDeleteStatement(
e,
[]handler.Condition{
handler.NewCond(SecretGeneratorColumnAggregateID, e.Aggregate().ID),
handler.NewCond(SecretGeneratorColumnGeneratorType, e.GeneratorType),
},
), nil
}

View File

@ -0,0 +1,141 @@
package projection
import (
"testing"
"time"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/handler"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/iam"
)
func TestSecretGeneratorProjection_reduces(t *testing.T) {
type args struct {
event func(t *testing.T) eventstore.Event
}
tests := []struct {
name string
args args
reduce func(event eventstore.Event) (*handler.Statement, error)
want wantReduce
}{
{
name: "reduceSecretGeneratorRemoved",
args: args{
event: getEvent(testEvent(
repository.EventType(iam.SecretGeneratorRemovedEventType),
iam.AggregateType,
[]byte(`{"generatorType": 1}`),
), iam.SecretGeneratorRemovedEventMapper),
},
reduce: (&SecretGeneratorProjection{}).reduceSecretGeneratorRemoved,
want: wantReduce{
projection: SecretGeneratorProjectionTable,
aggregateType: eventstore.AggregateType("iam"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM zitadel.projections.secret_generators WHERE (aggregate_id = $1) AND (generator_type = $2)",
expectedArgs: []interface{}{
"agg-id",
domain.SecretGeneratorTypeInitCode,
},
},
},
},
},
},
{
name: "reduceSecretGeneratorChanged",
args: args{
event: getEvent(testEvent(
repository.EventType(iam.SecretGeneratorChangedEventType),
iam.AggregateType,
[]byte(`{"generatorType": 1, "length": 4, "expiry": 10000000, "includeLowerLetters": true, "includeUpperLetters": true, "includeDigits": true, "includeSymbols": true}`),
), iam.SecretGeneratorChangedEventMapper),
},
reduce: (&SecretGeneratorProjection{}).reduceSecretGeneratorChanged,
want: wantReduce{
projection: SecretGeneratorProjectionTable,
aggregateType: eventstore.AggregateType("iam"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE zitadel.projections.secret_generators SET (change_date, sequence, length, expiry, include_lower_letters, include_upper_letters, include_digits, include_symbols) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9) AND (generator_type = $10)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
uint(4),
time.Millisecond * 10,
true,
true,
true,
true,
"agg-id",
domain.SecretGeneratorTypeInitCode,
},
},
},
},
},
},
{
name: "reduceSecretGeneratorAdded",
args: args{
event: getEvent(testEvent(
repository.EventType(iam.SecretGeneratorAddedEventType),
iam.AggregateType,
[]byte(`{"generatorType": 1, "length": 4, "expiry": 10000000, "includeLowerLetters": true, "includeUpperLetters": true, "includeDigits": true, "includeSymbols": true}`),
), iam.SecretGeneratorAddedEventMapper),
},
reduce: (&SecretGeneratorProjection{}).reduceSecretGeneratorAdded,
want: wantReduce{
projection: SecretGeneratorProjectionTable,
aggregateType: eventstore.AggregateType("iam"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.secret_generators (aggregate_id, generator_type, creation_date, change_date, resource_owner, sequence, length, expiry, include_lower_letters, include_upper_letters, include_digits, include_symbols) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
expectedArgs: []interface{}{
"agg-id",
domain.SecretGeneratorTypeInitCode,
anyArg{},
anyArg{},
"ro-id",
uint64(15),
uint(4),
time.Millisecond * 10,
true,
true,
true,
true,
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := baseEvent(t)
got, err := tt.reduce(event)
if _, ok := err.(errors.InvalidArgument); !ok {
t.Errorf("no wrong event mapping: %v, got: %v", err, got)
}
event = tt.args.event(t)
got, err = tt.reduce(event)
assertReduce(t, got, err, tt.want)
})
}
}

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