From 21c38b061d3ab6f6a2e855ec5f02b6bee9462a8f Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 12 Sep 2024 06:27:29 +0200 Subject: [PATCH] feat: add http as smtp provider (#8545) # Which Problems Are Solved Send Email messages as a HTTP call to a relay, for own logic on handling different Email providers # How the Problems Are Solved Create endpoints under Email provider to manage SMTP and HTTP in the notification handlers. # Additional Changes Clean up old logic in command and query side to handle the general Email providers with deactivate, activate and remove. # Additional Context Partially closes #8270 --------- Co-authored-by: Livio Spring --- internal/api/grpc/admin/email.go | 140 ++ internal/api/grpc/admin/email_converters.go | 145 ++ .../api/grpc/admin/iam_settings_converter.go | 57 +- internal/api/grpc/admin/smtp.go | 45 +- internal/api/grpc/admin/smtp_converters.go | 26 +- internal/command/instance.go | 13 +- .../command/instance_smtp_config_model.go | 160 ++- internal/command/smtp.go | 386 ++++-- internal/command/smtp_test.go | 1206 ++++++++++++----- internal/notification/channels.go | 10 +- .../notification/channels/email/config.go | 17 + internal/notification/channels/smtp/config.go | 5 +- .../notification/handlers/config_email.go | 56 + internal/notification/handlers/config_smtp.go | 33 - .../handlers/user_notifier_test.go | 28 +- internal/notification/senders/email.go | 55 +- internal/notification/types/notification.go | 7 +- internal/notification/types/user_email.go | 67 +- internal/query/projection/smtp.go | 402 ++++-- internal/query/projection/smtp_test.go | 370 ++++- internal/query/sms.go | 14 +- internal/query/smtp.go | 250 +++- internal/query/smtp_test.go | 200 ++- internal/repository/instance/eventstore.go | 14 +- internal/repository/instance/smtp_config.go | 242 ++-- pkg/grpc/settings/settings.go | 2 + proto/zitadel/admin.proto | 726 +++++++++- proto/zitadel/settings.proto | 51 + 28 files changed, 3575 insertions(+), 1152 deletions(-) create mode 100644 internal/api/grpc/admin/email.go create mode 100644 internal/api/grpc/admin/email_converters.go create mode 100644 internal/notification/channels/email/config.go create mode 100644 internal/notification/handlers/config_email.go delete mode 100644 internal/notification/handlers/config_smtp.go diff --git a/internal/api/grpc/admin/email.go b/internal/api/grpc/admin/email.go new file mode 100644 index 0000000000..c207970000 --- /dev/null +++ b/internal/api/grpc/admin/email.go @@ -0,0 +1,140 @@ +package admin + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/api/grpc/object" + admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" +) + +func (s *Server) GetEmailProvider(ctx context.Context, req *admin_pb.GetEmailProviderRequest) (*admin_pb.GetEmailProviderResponse, error) { + smtp, err := s.query.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID()) + if err != nil { + return nil, err + } + return &admin_pb.GetEmailProviderResponse{ + Config: emailProviderToProviderPb(smtp), + }, nil +} + +func (s *Server) GetEmailProviderById(ctx context.Context, req *admin_pb.GetEmailProviderByIdRequest) (*admin_pb.GetEmailProviderByIdResponse, error) { + smtp, err := s.query.SMTPConfigByID(ctx, authz.GetInstance(ctx).InstanceID(), req.Id) + if err != nil { + return nil, err + } + return &admin_pb.GetEmailProviderByIdResponse{ + Config: emailProviderToProviderPb(smtp), + }, nil +} + +func (s *Server) AddEmailProviderSMTP(ctx context.Context, req *admin_pb.AddEmailProviderSMTPRequest) (*admin_pb.AddEmailProviderSMTPResponse, error) { + config := addEmailProviderSMTPToConfig(ctx, req) + if err := s.command.AddSMTPConfig(ctx, config); err != nil { + return nil, err + } + return &admin_pb.AddEmailProviderSMTPResponse{ + Details: object.DomainToChangeDetailsPb(config.Details), + Id: config.ID, + }, nil +} + +func (s *Server) UpdateEmailProviderSMTP(ctx context.Context, req *admin_pb.UpdateEmailProviderSMTPRequest) (*admin_pb.UpdateEmailProviderSMTPResponse, error) { + config := updateEmailProviderSMTPToConfig(ctx, req) + if err := s.command.ChangeSMTPConfig(ctx, config); err != nil { + return nil, err + } + return &admin_pb.UpdateEmailProviderSMTPResponse{ + Details: object.DomainToChangeDetailsPb(config.Details), + }, nil +} + +func (s *Server) AddEmailProviderHTTP(ctx context.Context, req *admin_pb.AddEmailProviderHTTPRequest) (*admin_pb.AddEmailProviderHTTPResponse, error) { + config := addEmailProviderHTTPToConfig(ctx, req) + if err := s.command.AddSMTPConfigHTTP(ctx, config); err != nil { + return nil, err + } + return &admin_pb.AddEmailProviderHTTPResponse{ + Details: object.DomainToChangeDetailsPb(config.Details), + Id: config.ID, + }, nil +} + +func (s *Server) UpdateEmailProviderHTTP(ctx context.Context, req *admin_pb.UpdateEmailProviderHTTPRequest) (*admin_pb.UpdateEmailProviderHTTPResponse, error) { + config := updateEmailProviderHTTPToConfig(ctx, req) + if err := s.command.ChangeSMTPConfigHTTP(ctx, config); err != nil { + return nil, err + } + return &admin_pb.UpdateEmailProviderHTTPResponse{ + Details: object.DomainToChangeDetailsPb(config.Details), + }, nil +} + +func (s *Server) RemoveEmailProvider(ctx context.Context, req *admin_pb.RemoveEmailProviderRequest) (*admin_pb.RemoveEmailProviderResponse, error) { + details, err := s.command.RemoveSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id) + if err != nil { + return nil, err + } + return &admin_pb.RemoveEmailProviderResponse{ + Details: object.DomainToChangeDetailsPb(details), + }, nil +} + +func (s *Server) UpdateEmailProviderSMTPPassword(ctx context.Context, req *admin_pb.UpdateEmailProviderSMTPPasswordRequest) (*admin_pb.UpdateEmailProviderSMTPPasswordResponse, error) { + details, err := s.command.ChangeSMTPConfigPassword(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.Password) + if err != nil { + return nil, err + } + return &admin_pb.UpdateEmailProviderSMTPPasswordResponse{ + Details: object.DomainToChangeDetailsPb(details), + }, nil +} + +func (s *Server) ListEmailProviders(ctx context.Context, req *admin_pb.ListEmailProvidersRequest) (*admin_pb.ListEmailProvidersResponse, error) { + queries, err := listEmailProvidersToModel(req) + if err != nil { + return nil, err + } + result, err := s.query.SearchSMTPConfigs(ctx, queries) + if err != nil { + return nil, err + } + return &admin_pb.ListEmailProvidersResponse{ + Details: object.ToListDetails(result.Count, result.Sequence, result.LastRun), + Result: emailProvidersToPb(result.Configs), + }, nil +} + +func (s *Server) ActivateEmailProvider(ctx context.Context, req *admin_pb.ActivateEmailProviderRequest) (*admin_pb.ActivateEmailProviderResponse, error) { + result, err := s.command.ActivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id) + if err != nil { + return nil, err + } + return &admin_pb.ActivateEmailProviderResponse{ + Details: object.DomainToAddDetailsPb(result), + }, nil +} + +func (s *Server) DeactivateEmailProvider(ctx context.Context, req *admin_pb.DeactivateEmailProviderRequest) (*admin_pb.DeactivateEmailProviderResponse, error) { + result, err := s.command.DeactivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id) + if err != nil { + return nil, err + } + return &admin_pb.DeactivateEmailProviderResponse{ + Details: object.DomainToAddDetailsPb(result), + }, nil +} + +func (s *Server) TestEmailProviderById(ctx context.Context, req *admin_pb.TestEmailProviderSMTPByIdRequest) (*admin_pb.TestEmailProviderSMTPByIdResponse, error) { + if err := s.command.TestSMTPConfigById(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.ReceiverAddress); err != nil { + return nil, err + } + return &admin_pb.TestEmailProviderSMTPByIdResponse{}, nil +} + +func (s *Server) TestEmailProviderSMTP(ctx context.Context, req *admin_pb.TestEmailProviderSMTPRequest) (*admin_pb.TestEmailProviderSMTPResponse, error) { + if err := s.command.TestSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.ReceiverAddress, testEmailProviderSMTPToConfig(req)); err != nil { + return nil, err + } + return &admin_pb.TestEmailProviderSMTPResponse{}, nil +} diff --git a/internal/api/grpc/admin/email_converters.go b/internal/api/grpc/admin/email_converters.go new file mode 100644 index 0000000000..d1f566ec70 --- /dev/null +++ b/internal/api/grpc/admin/email_converters.go @@ -0,0 +1,145 @@ +package admin + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/notification/channels/smtp" + "github.com/zitadel/zitadel/internal/query" + admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" + settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings" +) + +func listEmailProvidersToModel(req *admin_pb.ListEmailProvidersRequest) (*query.SMTPConfigsSearchQueries, error) { + offset, limit, asc := object.ListQueryToModel(req.Query) + return &query.SMTPConfigsSearchQueries{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + }, + }, nil +} + +func emailProvidersToPb(configs []*query.SMTPConfig) []*settings_pb.EmailProvider { + c := make([]*settings_pb.EmailProvider, len(configs)) + for i, config := range configs { + c[i] = emailProviderToProviderPb(config) + } + return c +} + +func emailProviderToProviderPb(config *query.SMTPConfig) *settings_pb.EmailProvider { + return &settings_pb.EmailProvider{ + Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner), + Id: config.ID, + Description: config.Description, + State: emailProviderStateToPb(config.State), + Config: emailProviderToPb(config), + } +} + +func emailProviderStateToPb(state domain.SMTPConfigState) settings_pb.EmailProviderState { + switch state { + case domain.SMTPConfigStateUnspecified, domain.SMTPConfigStateRemoved: + return settings_pb.EmailProviderState_EMAIL_PROVIDER_STATE_UNSPECIFIED + case domain.SMTPConfigStateActive: + return settings_pb.EmailProviderState_EMAIL_PROVIDER_ACTIVE + case domain.SMTPConfigStateInactive: + return settings_pb.EmailProviderState_EMAIL_PROVIDER_INACTIVE + default: + return settings_pb.EmailProviderState_EMAIL_PROVIDER_STATE_UNSPECIFIED + } +} + +func emailProviderToPb(config *query.SMTPConfig) settings_pb.EmailConfig { + if config.SMTPConfig != nil { + return smtpToPb(config.SMTPConfig) + } + if config.HTTPConfig != nil { + return httpToPb(config.HTTPConfig) + } + return nil +} + +func httpToPb(http *query.HTTP) *settings_pb.EmailProvider_Http { + return &settings_pb.EmailProvider_Http{ + Http: &settings_pb.EmailProviderHTTP{ + Endpoint: http.Endpoint, + }, + } +} + +func smtpToPb(config *query.SMTP) *settings_pb.EmailProvider_Smtp { + return &settings_pb.EmailProvider_Smtp{ + Smtp: &settings_pb.EmailProviderSMTP{ + Tls: config.TLS, + Host: config.Host, + User: config.User, + SenderAddress: config.SenderAddress, + SenderName: config.SenderName, + }, + } +} + +func addEmailProviderSMTPToConfig(ctx context.Context, req *admin_pb.AddEmailProviderSMTPRequest) *command.AddSMTPConfig { + return &command.AddSMTPConfig{ + ResourceOwner: authz.GetInstance(ctx).InstanceID(), + Description: req.Description, + Tls: req.Tls, + From: req.SenderAddress, + FromName: req.SenderName, + ReplyToAddress: req.ReplyToAddress, + Host: req.Host, + User: req.User, + Password: req.Password, + } +} + +func updateEmailProviderSMTPToConfig(ctx context.Context, req *admin_pb.UpdateEmailProviderSMTPRequest) *command.ChangeSMTPConfig { + return &command.ChangeSMTPConfig{ + ResourceOwner: authz.GetInstance(ctx).InstanceID(), + ID: req.Id, + Description: req.Description, + Tls: req.Tls, + From: req.SenderAddress, + FromName: req.SenderName, + ReplyToAddress: req.ReplyToAddress, + Host: req.Host, + User: req.User, + Password: req.Password, + } +} + +func addEmailProviderHTTPToConfig(ctx context.Context, req *admin_pb.AddEmailProviderHTTPRequest) *command.AddSMTPConfigHTTP { + return &command.AddSMTPConfigHTTP{ + ResourceOwner: authz.GetInstance(ctx).InstanceID(), + Description: req.Description, + Endpoint: req.Endpoint, + } +} + +func updateEmailProviderHTTPToConfig(ctx context.Context, req *admin_pb.UpdateEmailProviderHTTPRequest) *command.ChangeSMTPConfigHTTP { + return &command.ChangeSMTPConfigHTTP{ + ResourceOwner: authz.GetInstance(ctx).InstanceID(), + ID: req.Id, + Description: req.Description, + Endpoint: req.Endpoint, + } +} + +func testEmailProviderSMTPToConfig(req *admin_pb.TestEmailProviderSMTPRequest) *smtp.Config { + return &smtp.Config{ + Tls: req.Tls, + From: req.SenderAddress, + FromName: req.SenderName, + SMTP: smtp.SMTP{ + Host: req.Host, + User: req.User, + Password: req.Password, + }, + } +} diff --git a/internal/api/grpc/admin/iam_settings_converter.go b/internal/api/grpc/admin/iam_settings_converter.go index b967faf4c5..bfdebe35f0 100644 --- a/internal/api/grpc/admin/iam_settings_converter.go +++ b/internal/api/grpc/admin/iam_settings_converter.go @@ -1,14 +1,16 @@ package admin import ( + "context" + "google.golang.org/protobuf/types/known/durationpb" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/object" obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" - "github.com/zitadel/zitadel/internal/notification/channels/smtp" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/zerrors" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" @@ -131,50 +133,51 @@ func SecretGeneratorTypeToDomain(generatorType settings_pb.SecretGeneratorType) } } -func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config { - return &smtp.Config{ +func addSMTPToConfig(ctx context.Context, req *admin_pb.AddSMTPConfigRequest) *command.AddSMTPConfig { + return &command.AddSMTPConfig{ + ResourceOwner: authz.GetInstance(ctx).InstanceID(), Description: req.Description, Tls: req.Tls, From: req.SenderAddress, FromName: req.SenderName, ReplyToAddress: req.ReplyToAddress, - SMTP: smtp.SMTP{ - Host: req.Host, - User: req.User, - Password: req.Password, - }, + Host: req.Host, + User: req.User, + Password: req.Password, } } -func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config { - return &smtp.Config{ +func updateSMTPToConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigRequest) *command.ChangeSMTPConfig { + return &command.ChangeSMTPConfig{ + ResourceOwner: authz.GetInstance(ctx).InstanceID(), + ID: req.Id, Description: req.Description, Tls: req.Tls, From: req.SenderAddress, FromName: req.SenderName, ReplyToAddress: req.ReplyToAddress, - SMTP: smtp.SMTP{ - Host: req.Host, - User: req.User, - Password: req.Password, - }, + Host: req.Host, + User: req.User, + Password: req.Password, } } func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig { - mapped := &settings_pb.SMTPConfig{ - Description: smtp.Description, - Tls: smtp.TLS, - SenderAddress: smtp.SenderAddress, - SenderName: smtp.SenderName, - ReplyToAddress: smtp.ReplyToAddress, - Host: smtp.Host, - User: smtp.User, - Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.ResourceOwner), - Id: smtp.ID, - State: settings_pb.SMTPConfigState(smtp.State), + if smtp.SMTPConfig != nil { + return &settings_pb.SMTPConfig{ + Description: smtp.Description, + Tls: smtp.SMTPConfig.TLS, + SenderAddress: smtp.SMTPConfig.SenderAddress, + SenderName: smtp.SMTPConfig.SenderName, + ReplyToAddress: smtp.SMTPConfig.ReplyToAddress, + Host: smtp.SMTPConfig.Host, + User: smtp.SMTPConfig.User, + Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.ResourceOwner), + Id: smtp.ID, + State: settings_pb.SMTPConfigState(smtp.State), + } } - return mapped + return nil } func SecurityPolicyToPb(policy *query.SecurityPolicy) *settings_pb.SecurityPolicy { diff --git a/internal/api/grpc/admin/smtp.go b/internal/api/grpc/admin/smtp.go index f5abaff5bd..b695e7022e 100644 --- a/internal/api/grpc/admin/smtp.go +++ b/internal/api/grpc/admin/smtp.go @@ -20,10 +20,7 @@ func (s *Server) GetSMTPConfig(ctx context.Context, req *admin_pb.GetSMTPConfigR } func (s *Server) GetSMTPConfigById(ctx context.Context, req *admin_pb.GetSMTPConfigByIdRequest) (*admin_pb.GetSMTPConfigByIdResponse, error) { - instanceID := authz.GetInstance(ctx).InstanceID() - resourceOwner := instanceID // Will be replaced when orgs have smtp configs - - smtp, err := s.query.SMTPConfigByID(ctx, instanceID, resourceOwner, req.Id) + smtp, err := s.query.SMTPConfigByID(ctx, authz.GetInstance(ctx).InstanceID(), req.Id) if err != nil { return nil, err } @@ -33,29 +30,23 @@ func (s *Server) GetSMTPConfigById(ctx context.Context, req *admin_pb.GetSMTPCon } func (s *Server) AddSMTPConfig(ctx context.Context, req *admin_pb.AddSMTPConfigRequest) (*admin_pb.AddSMTPConfigResponse, error) { - id, details, err := s.command.AddSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), AddSMTPToConfig(req)) - if err != nil { + config := addSMTPToConfig(ctx, req) + if err := s.command.AddSMTPConfig(ctx, config); err != nil { return nil, err } return &admin_pb.AddSMTPConfigResponse{ - Details: object.ChangeToDetailsPb( - details.Sequence, - details.EventDate, - details.ResourceOwner), - Id: id, + Details: object.DomainToChangeDetailsPb(config.Details), + Id: config.ID, }, nil } func (s *Server) UpdateSMTPConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigRequest) (*admin_pb.UpdateSMTPConfigResponse, error) { - details, err := s.command.ChangeSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, UpdateSMTPToConfig(req)) - if err != nil { + config := updateSMTPToConfig(ctx, req) + if err := s.command.ChangeSMTPConfig(ctx, config); err != nil { return nil, err } return &admin_pb.UpdateSMTPConfigResponse{ - Details: object.ChangeToDetailsPb( - details.Sequence, - details.EventDate, - details.ResourceOwner), + Details: object.DomainToChangeDetailsPb(config.Details), }, nil } @@ -65,10 +56,7 @@ func (s *Server) RemoveSMTPConfig(ctx context.Context, req *admin_pb.RemoveSMTPC return nil, err } return &admin_pb.RemoveSMTPConfigResponse{ - Details: object.ChangeToDetailsPb( - details.Sequence, - details.EventDate, - details.ResourceOwner), + Details: object.DomainToChangeDetailsPb(details), }, nil } @@ -78,10 +66,7 @@ func (s *Server) UpdateSMTPConfigPassword(ctx context.Context, req *admin_pb.Upd return nil, err } return &admin_pb.UpdateSMTPConfigPasswordResponse{ - Details: object.ChangeToDetailsPb( - details.Sequence, - details.EventDate, - details.ResourceOwner), + Details: object.DomainToChangeDetailsPb(details), }, nil } @@ -101,19 +86,11 @@ func (s *Server) ListSMTPConfigs(ctx context.Context, req *admin_pb.ListSMTPConf } func (s *Server) ActivateSMTPConfig(ctx context.Context, req *admin_pb.ActivateSMTPConfigRequest) (*admin_pb.ActivateSMTPConfigResponse, error) { - // Get the ID of current SMTP active provider if any - currentActiveProviderID := "" - smtp, err := s.query.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID()) - if err == nil { - currentActiveProviderID = smtp.ID - } - - result, err := s.command.ActivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, currentActiveProviderID) + result, err := s.command.ActivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id) if err != nil { return nil, err } - return &admin_pb.ActivateSMTPConfigResponse{ Details: object.DomainToAddDetailsPb(result), }, nil diff --git a/internal/api/grpc/admin/smtp_converters.go b/internal/api/grpc/admin/smtp_converters.go index 2ebeed58ef..961000c33f 100644 --- a/internal/api/grpc/admin/smtp_converters.go +++ b/internal/api/grpc/admin/smtp_converters.go @@ -2,6 +2,7 @@ package admin import ( "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings" @@ -23,12 +24,12 @@ func SMTPConfigToProviderPb(config *query.SMTPConfig) *settings_pb.SMTPConfig { Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner), Id: config.ID, Description: config.Description, - Tls: config.TLS, - Host: config.Host, - User: config.User, - State: settings_pb.SMTPConfigState(config.State), - SenderAddress: config.SenderAddress, - SenderName: config.SenderName, + Tls: config.SMTPConfig.TLS, + Host: config.SMTPConfig.Host, + User: config.SMTPConfig.User, + State: SMTPConfigStateToPb(config.State), + SenderAddress: config.SMTPConfig.SenderAddress, + SenderName: config.SMTPConfig.SenderName, } } @@ -39,3 +40,16 @@ func SMTPConfigsToPb(configs []*query.SMTPConfig) []*settings_pb.SMTPConfig { } return c } + +func SMTPConfigStateToPb(state domain.SMTPConfigState) settings_pb.SMTPConfigState { + switch state { + case domain.SMTPConfigStateUnspecified, domain.SMTPConfigStateRemoved: + return settings_pb.SMTPConfigState_SMTP_CONFIG_STATE_UNSPECIFIED + case domain.SMTPConfigStateActive: + return settings_pb.SMTPConfigState_SMTP_CONFIG_ACTIVE + case domain.SMTPConfigStateInactive: + return settings_pb.SMTPConfigState_SMTP_CONFIG_INACTIVE + default: + return settings_pb.SMTPConfigState_SMTP_CONFIG_STATE_UNSPECIFIED + } +} diff --git a/internal/command/instance.go b/internal/command/instance.go index c70b7541f5..f220c0c961 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -116,7 +116,7 @@ type InstanceSetup struct { } EmailTemplate []byte MessageTexts []*domain.CustomMessageText - SMTPConfiguration *smtp.Config + SMTPConfiguration *SMTPConfiguration OIDCSettings *OIDCSettings Quotas *SetQuotas Features *InstanceFeatures @@ -124,6 +124,15 @@ type InstanceSetup struct { Restrictions *SetRestrictions } +type SMTPConfiguration struct { + Description string + SMTP smtp.SMTP + Tls bool + From string + FromName string + ReplyToAddress string +} + type OIDCSettings struct { AccessTokenLifetime time.Duration IdTokenLifetime time.Duration @@ -440,7 +449,7 @@ func setupOIDCSettings(commands *Commands, validations *[]preparation.Validation ) } -func setupSMTPSettings(commands *Commands, validations *[]preparation.Validation, smtpConfig *smtp.Config, instanceAgg *instance.Aggregate) { +func setupSMTPSettings(commands *Commands, validations *[]preparation.Validation, smtpConfig *SMTPConfiguration, instanceAgg *instance.Aggregate) { if smtpConfig == nil { return } diff --git a/internal/command/instance_smtp_config_model.go b/internal/command/instance_smtp_config_model.go index e165419093..8af867f9f0 100644 --- a/internal/command/instance_smtp_config_model.go +++ b/internal/command/instance_smtp_config_model.go @@ -12,8 +12,20 @@ import ( type IAMSMTPConfigWriteModel struct { eventstore.WriteModel - ID string - Description string + ID string + Description string + + SMTPConfig *SMTPConfig + HTTPConfig *HTTPConfig + + State domain.SMTPConfigState + + domain string + domainState domain.InstanceDomainState + smtpSenderAddressMatchesInstanceDomain bool +} + +type SMTPConfig struct { TLS bool Host string User string @@ -21,11 +33,6 @@ type IAMSMTPConfigWriteModel struct { SenderAddress string SenderName string ReplyToAddress string - State domain.SMTPConfigState - - domain string - domainState domain.InstanceDomainState - smtpSenderAddressMatchesInstanceDomain bool } func NewIAMSMTPConfigWriteModel(instanceID, id, domain string) *IAMSMTPConfigWriteModel { @@ -73,6 +80,23 @@ func (wm *IAMSMTPConfigWriteModel) Reduce() error { continue } wm.reduceSMTPConfigChangedEvent(e) + case *instance.SMTPConfigPasswordChangedEvent: + if wm.ID != e.ID { + continue + } + if e.Password != nil { + wm.SMTPConfig.Password = e.Password + } + case *instance.SMTPConfigHTTPAddedEvent: + if wm.ID != e.ID { + continue + } + wm.reduceSMTPConfigHTTPAddedEvent(e) + case *instance.SMTPConfigHTTPChangedEvent: + if wm.ID != e.ID { + continue + } + wm.reduceSMTPConfigHTTPChangedEvent(e) case *instance.SMTPConfigRemovedEvent: if wm.ID != e.ID { continue @@ -120,6 +144,8 @@ func (wm *IAMSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder { instance.SMTPConfigRemovedEventType, instance.SMTPConfigChangedEventType, instance.SMTPConfigPasswordChangedEventType, + instance.SMTPConfigHTTPAddedEventType, + instance.SMTPConfigHTTPChangedEventType, instance.SMTPConfigActivatedEventType, instance.SMTPConfigDeactivatedEventType, instance.SMTPConfigRemovedEventType, @@ -133,6 +159,9 @@ func (wm *IAMSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder { func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id, description string, tls bool, fromAddress, fromName, replyToAddress, smtpHost, smtpUser string, smtpPassword *crypto.CryptoValue) (*instance.SMTPConfigChangedEvent, bool, error) { changes := make([]instance.SMTPConfigChanges, 0) var err error + if wm.SMTPConfig == nil { + return nil, false, nil + } if wm.ID != id { changes = append(changes, instance.ChangeSMTPConfigID(id)) @@ -140,22 +169,22 @@ func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregat if wm.Description != description { changes = append(changes, instance.ChangeSMTPConfigDescription(description)) } - if wm.TLS != tls { + if wm.SMTPConfig.TLS != tls { changes = append(changes, instance.ChangeSMTPConfigTLS(tls)) } - if wm.SenderAddress != fromAddress { + if wm.SMTPConfig.SenderAddress != fromAddress { changes = append(changes, instance.ChangeSMTPConfigFromAddress(fromAddress)) } - if wm.SenderName != fromName { + if wm.SMTPConfig.SenderName != fromName { changes = append(changes, instance.ChangeSMTPConfigFromName(fromName)) } - if wm.ReplyToAddress != replyToAddress { + if wm.SMTPConfig.ReplyToAddress != replyToAddress { changes = append(changes, instance.ChangeSMTPConfigReplyToAddress(replyToAddress)) } - if wm.Host != smtpHost { + if wm.SMTPConfig.Host != smtpHost { changes = append(changes, instance.ChangeSMTPConfigSMTPHost(smtpHost)) } - if wm.User != smtpUser { + if wm.SMTPConfig.User != smtpUser { changes = append(changes, instance.ChangeSMTPConfigSMTPUser(smtpUser)) } if smtpPassword != nil { @@ -171,15 +200,58 @@ func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregat return changeEvent, true, nil } +func (wm *IAMSMTPConfigWriteModel) NewHTTPChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id, description, endpoint string) (*instance.SMTPConfigHTTPChangedEvent, bool, error) { + changes := make([]instance.SMTPConfigHTTPChanges, 0) + var err error + if wm.HTTPConfig == nil { + return nil, false, nil + } + + if wm.ID != id { + changes = append(changes, instance.ChangeSMTPConfigHTTPID(id)) + } + if wm.Description != description { + changes = append(changes, instance.ChangeSMTPConfigHTTPDescription(description)) + } + if wm.HTTPConfig.Endpoint != endpoint { + changes = append(changes, instance.ChangeSMTPConfigHTTPEndpoint(endpoint)) + } + if len(changes) == 0 { + return nil, false, nil + } + changeEvent, err := instance.NewSMTPConfigHTTPChangeEvent(ctx, aggregate, id, changes) + if err != nil { + return nil, false, err + } + return changeEvent, true, nil +} + func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigAddedEvent(e *instance.SMTPConfigAddedEvent) { wm.Description = e.Description - wm.TLS = e.TLS - wm.Host = e.Host - wm.User = e.User - wm.Password = e.Password - wm.SenderAddress = e.SenderAddress - wm.SenderName = e.SenderName - wm.ReplyToAddress = e.ReplyToAddress + wm.SMTPConfig = &SMTPConfig{ + TLS: e.TLS, + Host: e.Host, + User: e.User, + Password: e.Password, + SenderName: e.SenderName, + SenderAddress: e.SenderAddress, + ReplyToAddress: e.ReplyToAddress, + } + wm.State = domain.SMTPConfigStateInactive + // If ID has empty value we're dealing with the old and unique smtp settings + // These would be the default values for ID and State + if e.ID == "" { + wm.Description = "generic" + wm.ID = e.Aggregate().ResourceOwner + wm.State = domain.SMTPConfigStateActive + } +} + +func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigHTTPAddedEvent(e *instance.SMTPConfigHTTPAddedEvent) { + wm.Description = e.Description + wm.HTTPConfig = &HTTPConfig{ + Endpoint: e.Endpoint, + } wm.State = domain.SMTPConfigStateInactive // If ID has empty value we're dealing with the old and unique smtp settings // These would be the default values for ID and State @@ -191,29 +263,54 @@ func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigAddedEvent(e *instance.SMTPCo } func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigChangedEvent(e *instance.SMTPConfigChangedEvent) { + if wm.SMTPConfig == nil { + return + } + if e.Description != nil { wm.Description = *e.Description } if e.TLS != nil { - wm.TLS = *e.TLS + wm.SMTPConfig.TLS = *e.TLS } if e.Host != nil { - wm.Host = *e.Host + wm.SMTPConfig.Host = *e.Host } if e.User != nil { - wm.User = *e.User + wm.SMTPConfig.User = *e.User } if e.Password != nil { - wm.Password = e.Password + wm.SMTPConfig.Password = e.Password } if e.FromAddress != nil { - wm.SenderAddress = *e.FromAddress + wm.SMTPConfig.SenderAddress = *e.FromAddress } if e.FromName != nil { - wm.SenderName = *e.FromName + wm.SMTPConfig.SenderName = *e.FromName } if e.ReplyToAddress != nil { - wm.ReplyToAddress = *e.ReplyToAddress + wm.SMTPConfig.ReplyToAddress = *e.ReplyToAddress + } + + // If ID has empty value we're dealing with the old and unique smtp settings + // These would be the default values for ID and State + if e.ID == "" { + wm.Description = "generic" + wm.ID = e.Aggregate().ResourceOwner + wm.State = domain.SMTPConfigStateActive + } +} + +func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigHTTPChangedEvent(e *instance.SMTPConfigHTTPChangedEvent) { + if wm.HTTPConfig == nil { + return + } + + if e.Description != nil { + wm.Description = *e.Description + } + if e.Endpoint != nil { + wm.HTTPConfig.Endpoint = *e.Endpoint } // If ID has empty value we're dealing with the old and unique smtp settings @@ -227,13 +324,8 @@ func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigChangedEvent(e *instance.SMTP func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigRemovedEvent(e *instance.SMTPConfigRemovedEvent) { wm.Description = "" - wm.TLS = false - wm.SenderName = "" - wm.SenderAddress = "" - wm.ReplyToAddress = "" - wm.Host = "" - wm.User = "" - wm.Password = nil + wm.HTTPConfig = nil + wm.SMTPConfig = nil wm.State = domain.SMTPConfigStateRemoved // If ID has empty value we're dealing with the old and unique smtp settings diff --git a/internal/command/smtp.go b/internal/command/smtp.go index f51bef9b53..d856b9a3da 100644 --- a/internal/command/smtp.go +++ b/internal/command/smtp.go @@ -15,151 +15,189 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) -func (c *Commands) AddSMTPConfig(ctx context.Context, instanceID string, config *smtp.Config) (string, *domain.ObjectDetails, error) { - id, err := c.idGenerator.Next() - if err != nil { - return "", nil, err +type AddSMTPConfig struct { + Details *domain.ObjectDetails + ResourceOwner string + ID string + + Description string + Host string + User string + Password string + Tls bool + From string + FromName string + ReplyToAddress string +} + +func (c *Commands) AddSMTPConfig(ctx context.Context, config *AddSMTPConfig) (err error) { + if config.ResourceOwner == "" { + return zerrors.ThrowInvalidArgument(nil, "COMMAND-PQN0wsqSyi", "Errors.ResourceOwnerMissing") + } + if config.ID == "" { + config.ID, err = c.idGenerator.Next() + if err != nil { + return err + } } from := strings.TrimSpace(config.From) if from == "" { - return "", nil, zerrors.ThrowInvalidArgument(nil, "INST-ASv2d", "Errors.Invalid.Argument") + return zerrors.ThrowInvalidArgument(nil, "COMMAND-SAAFpV8VKV", "Errors.Invalid.Argument") } fromSplitted := strings.Split(from, "@") senderDomain := fromSplitted[len(fromSplitted)-1] description := strings.TrimSpace(config.Description) replyTo := strings.TrimSpace(config.ReplyToAddress) - hostAndPort := strings.TrimSpace(config.SMTP.Host) + hostAndPort := strings.TrimSpace(config.Host) if _, _, err := net.SplitHostPort(hostAndPort); err != nil { - return "", nil, zerrors.ThrowInvalidArgument(nil, "INST-9JdRe", "Errors.Invalid.Argument") + return zerrors.ThrowInvalidArgument(nil, "COMMAND-EvAtufIinh", "Errors.Invalid.Argument") } var smtpPassword *crypto.CryptoValue - if config.SMTP.Password != "" { - smtpPassword, err = crypto.Encrypt([]byte(config.SMTP.Password), c.smtpEncryption) + if config.Password != "" { + smtpPassword, err = crypto.Encrypt([]byte(config.Password), c.smtpEncryption) if err != nil { - return "", nil, err + return err } } - smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, senderDomain) + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, config.ResourceOwner, config.ID, senderDomain) if err != nil { - return "", nil, err + return err } err = checkSenderAddress(smtpConfigWriteModel) if err != nil { - return "", nil, err + return err } - iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigAddedEvent( - ctx, - iamAgg, - id, - description, - config.Tls, - config.From, - config.FromName, - replyTo, - hostAndPort, - config.SMTP.User, - smtpPassword, - )) + err = c.pushAppendAndReduce(ctx, + smtpConfigWriteModel, + instance.NewSMTPConfigAddedEvent( + ctx, + InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), + config.ID, + description, + config.Tls, + config.From, + config.FromName, + replyTo, + hostAndPort, + config.User, + smtpPassword, + ), + ) if err != nil { - return "", nil, err + return err } - - err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) - if err != nil { - return "", nil, err - } - return id, writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil + config.Details = writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel) + return nil } -func (c *Commands) ChangeSMTPConfig(ctx context.Context, instanceID string, id string, config *smtp.Config) (*domain.ObjectDetails, error) { - if id == "" { - return nil, zerrors.ThrowInvalidArgument(nil, "SMTP-x8vo9", "Errors.IDMissing") +type ChangeSMTPConfig struct { + Details *domain.ObjectDetails + ResourceOwner string + ID string + + Description string + Host string + User string + Password string + Tls bool + From string + FromName string + ReplyToAddress string +} + +func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *ChangeSMTPConfig) error { + if config.ResourceOwner == "" { + return zerrors.ThrowInvalidArgument(nil, "COMMAND-jwA8gxldy3", "Errors.ResourceOwnerMissing") + } + if config.ID == "" { + return zerrors.ThrowInvalidArgument(nil, "COMMAND-2JPlSRzuHy", "Errors.IDMissing") } from := strings.TrimSpace(config.From) if from == "" { - return nil, zerrors.ThrowInvalidArgument(nil, "INST-HSv2d", "Errors.Invalid.Argument") + return zerrors.ThrowInvalidArgument(nil, "COMMAND-gyPUXOTA4N", "Errors.Invalid.Argument") } fromSplitted := strings.Split(from, "@") senderDomain := fromSplitted[len(fromSplitted)-1] description := strings.TrimSpace(config.Description) replyTo := strings.TrimSpace(config.ReplyToAddress) - hostAndPort := strings.TrimSpace(config.SMTP.Host) + hostAndPort := strings.TrimSpace(config.Host) if _, _, err := net.SplitHostPort(hostAndPort); err != nil { - return nil, zerrors.ThrowInvalidArgument(nil, "INST-Kv875", "Errors.Invalid.Argument") + return zerrors.ThrowInvalidArgument(nil, "COMMAND-kZNVkuL32L", "Errors.Invalid.Argument") } var smtpPassword *crypto.CryptoValue var err error - if config.SMTP.Password != "" { - smtpPassword, err = crypto.Encrypt([]byte(config.SMTP.Password), c.smtpEncryption) + if config.Password != "" { + smtpPassword, err = crypto.Encrypt([]byte(config.Password), c.smtpEncryption) if err != nil { - return nil, err + return err } } - smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, senderDomain) + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, config.ResourceOwner, config.ID, senderDomain) if err != nil { - return nil, err + return err } if !smtpConfigWriteModel.State.Exists() { - return nil, zerrors.ThrowNotFound(nil, "COMMAND-7j8gv", "Errors.SMTPConfig.NotFound") + return zerrors.ThrowNotFound(nil, "COMMAND-j5IDFtt3T1", "Errors.SMTPConfig.NotFound") } err = checkSenderAddress(smtpConfigWriteModel) if err != nil { - return nil, err + return err } - iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) - changedEvent, hasChanged, err := smtpConfigWriteModel.NewChangedEvent( ctx, - iamAgg, - id, + InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), + config.ID, description, config.Tls, from, config.FromName, replyTo, hostAndPort, - config.SMTP.User, + config.User, smtpPassword, ) if err != nil { - return nil, err + return err } if !hasChanged { - return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-lh3op", "Errors.NoChangesFound") + config.Details = writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel) + return nil } - pushedEvents, err := c.eventstore.Push(ctx, changedEvent) + err = c.pushAppendAndReduce(ctx, smtpConfigWriteModel, changedEvent) if err != nil { - return nil, err + return err } - err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) - if err != nil { - return nil, err - } - return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil + config.Details = writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel) + return nil } -func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, instanceID, id string, password string) (*domain.ObjectDetails, error) { - instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) - smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "") +func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, resourceOwner, id string, password string) (*domain.ObjectDetails, error) { + if resourceOwner == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-gHAyvUXCAF", "Errors.ResourceOwnerMissing") + } + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-BCkAf7LcJA", "Errors.IDMissing") + } + + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, resourceOwner, id, "") if err != nil { return nil, err } if smtpConfigWriteModel.State != domain.SMTPConfigStateActive { - return nil, zerrors.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SMTPConfig.NotFound") + return nil, zerrors.ThrowNotFound(nil, "COMMAND-rDHzqjGuKQ", "Errors.SMTPConfig.NotFound") } var smtpPassword *crypto.CryptoValue @@ -170,68 +208,152 @@ func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, instanceID, id } } - pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigPasswordChangedEvent( - ctx, - &instanceAgg.Aggregate, - id, - smtpPassword)) + err = c.pushAppendAndReduce(ctx, + smtpConfigWriteModel, + instance.NewSMTPConfigPasswordChangedEvent( + ctx, + InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), + id, + 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) ActivateSMTPConfig(ctx context.Context, instanceID, id, activatedId string) (*domain.ObjectDetails, error) { - if id == "" { - return nil, zerrors.ThrowInvalidArgument(nil, "SMTP-nm56k", "Errors.IDMissing") - } +type AddSMTPConfigHTTP struct { + Details *domain.ObjectDetails + ResourceOwner string + ID string - if len(activatedId) > 0 { - _, err := c.DeactivateSMTPConfig(ctx, instanceID, activatedId) + Description string + Endpoint string +} + +func (c *Commands) AddSMTPConfigHTTP(ctx context.Context, config *AddSMTPConfigHTTP) (err error) { + if config.ResourceOwner == "" { + return zerrors.ThrowInvalidArgument(nil, "COMMAND-FTNDXc8ACS", "Errors.ResourceOwnerMissing") + } + if config.ID == "" { + config.ID, err = c.idGenerator.Next() if err != nil { - return nil, err + return err } } - smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "") + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, config.ResourceOwner, config.ID, "") + if err != nil { + return err + } + + err = c.pushAppendAndReduce(ctx, smtpConfigWriteModel, instance.NewSMTPConfigHTTPAddedEvent( + ctx, + InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), + config.ID, + config.Description, + config.Endpoint, + )) + if err != nil { + return err + } + config.Details = writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel) + return nil +} + +type ChangeSMTPConfigHTTP struct { + Details *domain.ObjectDetails + ResourceOwner string + ID string + + Description string + Endpoint string +} + +func (c *Commands) ChangeSMTPConfigHTTP(ctx context.Context, config *ChangeSMTPConfigHTTP) (err error) { + if config.ResourceOwner == "" { + return zerrors.ThrowInvalidArgument(nil, "COMMAND-k7QCGOWyJA", "Errors.ResourceOwnerMissing") + } + if config.ID == "" { + return zerrors.ThrowInvalidArgument(nil, "COMMAND-2MHkV8ObWo", "Errors.IDMissing") + } + + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, config.ResourceOwner, config.ID, "") + if err != nil { + return err + } + + if !smtpConfigWriteModel.State.Exists() || smtpConfigWriteModel.HTTPConfig == nil { + return zerrors.ThrowNotFound(nil, "COMMAND-xIrdledqv4", "Errors.SMTPConfig.NotFound") + } + + changedEvent, hasChanged, err := smtpConfigWriteModel.NewHTTPChangedEvent( + ctx, + InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), + config.ID, + config.Description, + config.Endpoint, + ) + if err != nil { + return err + } + if !hasChanged { + config.Details = writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel) + return nil + } + + err = c.pushAppendAndReduce(ctx, smtpConfigWriteModel, changedEvent) + if err != nil { + return err + } + config.Details = writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel) + return nil +} + +func (c *Commands) ActivateSMTPConfig(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { + if resourceOwner == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-h5htMCebv3", "Errors.ResourceOwnerMissing") + } + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-1hPl6oVMJa", "Errors.IDMissing") + } + + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, resourceOwner, id, "") if err != nil { return nil, err } if !smtpConfigWriteModel.State.Exists() { - return nil, zerrors.ThrowNotFound(nil, "COMMAND-kg8yr", "Errors.SMTPConfig.NotFound") + return nil, zerrors.ThrowNotFound(nil, "COMMAND-E9K20hxOS9", "Errors.SMTPConfig.NotFound") } - if smtpConfigWriteModel.State == domain.SMTPConfigStateActive { - return nil, zerrors.ThrowNotFound(nil, "COMMAND-ed3lr", "Errors.SMTPConfig.AlreadyActive") + return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-vUHBSmBzaw", "Errors.SMTPConfig.AlreadyActive") } - iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigActivatedEvent( - ctx, - iamAgg, - id)) - if err != nil { - return nil, err - } - err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) + err = c.pushAppendAndReduce(ctx, + smtpConfigWriteModel, + instance.NewSMTPConfigActivatedEvent( + ctx, + InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), + id, + ), + ) if err != nil { return nil, err } return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil } -func (c *Commands) DeactivateSMTPConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) { +func (c *Commands) DeactivateSMTPConfig(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { + if resourceOwner == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-pvNHou89Tw", "Errors.ResourceOwnerMissing") + } if id == "" { - return nil, zerrors.ThrowInvalidArgument(nil, "SMTP-98ikl", "Errors.IDMissing") + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-jLTIMrtApO", "Errors.IDMissing") } - smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "") + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, resourceOwner, id, "") if err != nil { return nil, err } @@ -239,46 +361,47 @@ func (c *Commands) DeactivateSMTPConfig(ctx context.Context, instanceID, id stri return nil, zerrors.ThrowNotFound(nil, "COMMAND-k39PJ", "Errors.SMTPConfig.NotFound") } if smtpConfigWriteModel.State == domain.SMTPConfigStateInactive { - return nil, zerrors.ThrowNotFound(nil, "COMMAND-km8g3", "Errors.SMTPConfig.AlreadyDeactivated") + return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-km8g3", "Errors.SMTPConfig.AlreadyDeactivated") } - iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigDeactivatedEvent( - ctx, - iamAgg, - id)) - if err != nil { - return nil, err - } - err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) + err = c.pushAppendAndReduce(ctx, + smtpConfigWriteModel, + instance.NewSMTPConfigDeactivatedEvent( + ctx, + InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), + id, + ), + ) if err != nil { return nil, err } return writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel), nil } -func (c *Commands) RemoveSMTPConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) { +func (c *Commands) RemoveSMTPConfig(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { + if resourceOwner == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-t2WsPRgGaK", "Errors.ResourceOwnerMissing") + } if id == "" { - return nil, zerrors.ThrowInvalidArgument(nil, "SMTP-7f5cv", "Errors.IDMissing") + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-0ZV5whuUfu", "Errors.IDMissing") } - smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "") + smtpConfigWriteModel, err := c.getSMTPConfig(ctx, resourceOwner, id, "") if err != nil { return nil, err } if !smtpConfigWriteModel.State.Exists() { - return nil, zerrors.ThrowNotFound(nil, "COMMAND-kg8rt", "Errors.SMTPConfig.NotFound") + return nil, zerrors.ThrowNotFound(nil, "COMMAND-09CXlTDL6w", "Errors.SMTPConfig.NotFound") } - iamAgg := InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMTPConfigRemovedEvent( - ctx, - iamAgg, - id)) - if err != nil { - return nil, err - } - err = AppendAndReduce(smtpConfigWriteModel, pushedEvents...) + err = c.pushAppendAndReduce(ctx, + smtpConfigWriteModel, + instance.NewSMTPConfigRemovedEvent( + ctx, + InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), + id, + ), + ) if err != nil { return nil, err } @@ -303,11 +426,11 @@ func (c *Commands) TestSMTPConfig(ctx context.Context, instanceID, id, email str if err != nil { return err } - if !smtpConfigWriteModel.State.Exists() { + if !smtpConfigWriteModel.State.Exists() || smtpConfigWriteModel.SMTPConfig == nil { return zerrors.ThrowNotFound(nil, "SMTP-p9cc", "Errors.SMTPConfig.NotFound") } - password, err = crypto.DecryptString(smtpConfigWriteModel.Password, c.smtpEncryption) + password, err = crypto.DecryptString(smtpConfigWriteModel.SMTPConfig.Password, c.smtpEncryption) if err != nil { return err } @@ -338,23 +461,22 @@ func (c *Commands) TestSMTPConfigById(ctx context.Context, instanceID, id, email return err } - if !smtpConfigWriteModel.State.Exists() { + if !smtpConfigWriteModel.State.Exists() || smtpConfigWriteModel.SMTPConfig == nil { return zerrors.ThrowNotFound(nil, "SMTP-99klw", "Errors.SMTPConfig.NotFound") } - password, err := crypto.DecryptString(smtpConfigWriteModel.Password, c.smtpEncryption) + password, err := crypto.DecryptString(smtpConfigWriteModel.SMTPConfig.Password, c.smtpEncryption) if err != nil { return err } smtpConfig := &smtp.Config{ - Description: smtpConfigWriteModel.Description, - Tls: smtpConfigWriteModel.TLS, - From: smtpConfigWriteModel.SenderAddress, - FromName: smtpConfigWriteModel.SenderName, + Tls: smtpConfigWriteModel.SMTPConfig.TLS, + From: smtpConfigWriteModel.SMTPConfig.SenderAddress, + FromName: smtpConfigWriteModel.SMTPConfig.SenderName, SMTP: smtp.SMTP{ - Host: smtpConfigWriteModel.Host, - User: smtpConfigWriteModel.User, + Host: smtpConfigWriteModel.SMTPConfig.Host, + User: smtpConfigWriteModel.SMTPConfig.User, Password: password, }, } @@ -373,7 +495,7 @@ func checkSenderAddress(writeModel *IAMSMTPConfigWriteModel) error { return nil } if !writeModel.domainState.Exists() { - return zerrors.ThrowInvalidArgument(nil, "INST-83nl8", "Errors.SMTPConfig.SenderAdressNotCustomDomain") + return zerrors.ThrowInvalidArgument(nil, "INST-xtWIiR2ZbR", "Errors.SMTPConfig.SenderAdressNotCustomDomain") } return nil } diff --git a/internal/command/smtp_test.go b/internal/command/smtp_test.go index 17f7f088d0..7aa224f251 100644 --- a/internal/command/smtp_test.go +++ b/internal/command/smtp_test.go @@ -2,6 +2,7 @@ package command import ( "context" + "errors" "testing" "github.com/stretchr/testify/assert" @@ -20,14 +21,13 @@ import ( func TestCommandSide_AddSMTPConfig(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore idGenerator id.Generator alg crypto.EncryptionAlgorithm } type args struct { - ctx context.Context instanceID string - smtp *smtp.Config + smtp *AddSMTPConfig } type res struct { want *domain.ObjectDetails @@ -39,11 +39,24 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { args args res res }{ + { + name: "resourceowner empty, invalid argument", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{ + smtp: &AddSMTPConfig{}, + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-PQN0wsqSyi", "Errors.ResourceOwnerMissing")) + }, + }, + }, { name: "smtp config, custom domain not existing", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewDomainPolicyAddedEvent(context.Background(), @@ -59,25 +72,42 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - From: "from@domain.ch", - SMTP: smtp.SMTP{ - Host: "host:587", - User: "user", - Password: "password", - }, + smtp: &AddSMTPConfig{ + ResourceOwner: "INSTANCE", + From: "from@domain.ch", + Host: "host:587", + User: "user", + Password: "password", }, }, res: res{ - err: zerrors.IsErrorInvalidArgument, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "INST-xtWIiR2ZbR", "Errors.SMTPConfig.SenderAdressNotCustomDomain")) + }, + }, + }, + { + name: "add smtp config, from empty", + fields: fields{ + eventstore: expectEventstore(), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), + }, + args: args{ + smtp: &AddSMTPConfig{ + ResourceOwner: "INSTANCE", + From: " ", + }, + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-SAAFpV8VKV", "Errors.Invalid.Argument")) + }, }, }, { name: "add smtp config, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewDomainAddedEvent(context.Background(), @@ -118,17 +148,15 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - Description: "test", - Tls: true, - From: "from@domain.ch", - FromName: "name", - SMTP: smtp.SMTP{ - Host: "host:587", - User: "user", - Password: "password", - }, + smtp: &AddSMTPConfig{ + ResourceOwner: "INSTANCE", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", + Host: "host:587", + User: "user", + Password: "password", }, }, res: res{ @@ -140,8 +168,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { { name: "add smtp config with reply to address, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewDomainAddedEvent(context.Background(), @@ -182,18 +209,16 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ + smtp: &AddSMTPConfig{ + ResourceOwner: "INSTANCE", Description: "test", Tls: true, From: "from@domain.ch", FromName: "name", ReplyToAddress: "replyto@domain.ch", - SMTP: smtp.SMTP{ - Host: "host:587", - User: "user", - Password: "password", - }, + Host: "host:587", + User: "user", + Password: "password", }, }, res: res{ @@ -205,58 +230,57 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { { name: "smtp config, port is missing", fields: fields{ - eventstore: eventstoreExpect(t), + eventstore: expectEventstore(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - Description: "test", - Tls: true, - From: "from@domain.ch", - FromName: "name", - SMTP: smtp.SMTP{ - Host: "host", - User: "user", - Password: "password", - }, + smtp: &AddSMTPConfig{ + ResourceOwner: "INSTANCE", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", + Host: "host", + User: "user", + Password: "password", }, }, res: res{ - err: zerrors.IsErrorInvalidArgument, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-EvAtufIinh", "Errors.Invalid.Argument")) + }, }, }, { name: "smtp config, host is empty", fields: fields{ - eventstore: eventstoreExpect(t), + eventstore: expectEventstore(), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - Description: "test", - Tls: true, - From: "from@domain.ch", - FromName: "name", - SMTP: smtp.SMTP{ - Host: " ", - User: "user", - Password: "password", - }, + smtp: &AddSMTPConfig{ + ResourceOwner: "INSTANCE", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", + Host: " ", + User: "user", + Password: "password", }, }, res: res{ - err: zerrors.IsErrorInvalidArgument, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-EvAtufIinh", "Errors.Invalid.Argument")) + }, }, }, { name: "add smtp config, ipv6 works", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewDomainAddedEvent(context.Background(), @@ -297,17 +321,15 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - Description: "test", - Tls: true, - From: "from@domain.ch", - FromName: "name", - SMTP: smtp.SMTP{ - Host: "[2001:db8::1]:2525", - User: "user", - Password: "password", - }, + smtp: &AddSMTPConfig{ + ResourceOwner: "INSTANCE", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", + Host: "[2001:db8::1]:2525", + User: "user", + Password: "password", }, }, res: res{ @@ -320,11 +342,11 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), idGenerator: tt.fields.idGenerator, smtpEncryption: tt.fields.alg, } - _, got, err := r.AddSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.smtp) + err := r.AddSMTPConfig(context.Background(), tt.args.smtp) if tt.res.err == nil { assert.NoError(t, err) } @@ -332,7 +354,8 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assertObjectDetails(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, tt.args.smtp.Details) + assert.NotEmpty(t, tt.args.smtp.ID) } }) } @@ -340,13 +363,10 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) { func TestCommandSide_ChangeSMTPConfig(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore } type args struct { - ctx context.Context - instanceID string - id string - smtp *smtp.Config + smtp *ChangeSMTPConfig } type res struct { want *domain.ObjectDetails @@ -359,68 +379,81 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { res res }{ { - name: "id empty, precondition error", + name: "resourceowner empty, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{}, + smtp: &ChangeSMTPConfig{}, }, res: res{ - err: zerrors.IsErrorInvalidArgument, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-jwA8gxldy3", "Errors.ResourceOwnerMissing")) + }, + }, + }, + { + name: "id empty, precondition error", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{ + smtp: &ChangeSMTPConfig{ + ResourceOwner: "INSTANCE", + }, + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-2JPlSRzuHy", "Errors.IDMissing")) + }, }, }, { name: "empty config, invalid argument error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{}, - id: "configID", + smtp: &ChangeSMTPConfig{ + ResourceOwner: "INSTANCE", + ID: "configID", + }, }, res: res{ - err: zerrors.IsErrorInvalidArgument, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-gyPUXOTA4N", "Errors.Invalid.Argument")) + }, }, }, { name: "smtp not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - Description: "test", - Tls: true, - From: "from@domain.ch", - FromName: "name", - SMTP: smtp.SMTP{ - Host: "host:587", - User: "user", - }, + smtp: &ChangeSMTPConfig{ + ResourceOwner: "INSTANCE", + ID: "ID", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", + Host: "host:587", + User: "user", }, - instanceID: "INSTANCE", - id: "ID", }, res: res{ - err: zerrors.IsNotFound, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-j5IDFtt3T1", "Errors.SMTPConfig.NotFound")) + }, }, }, { name: "smtp domain not matched", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewDomainAddedEvent(context.Background(), @@ -454,18 +487,15 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - instanceID: "INSTANCE", - id: "ID", - smtp: &smtp.Config{ - Description: "test", - Tls: true, - From: "from@wrongdomain.ch", - FromName: "name", - SMTP: smtp.SMTP{ - Host: "host:587", - User: "user", - }, + smtp: &ChangeSMTPConfig{ + ResourceOwner: "INSTANCE", + ID: "ID", + Description: "test", + Tls: true, + From: "from@wrongdomain.ch", + FromName: "name", + Host: "host:587", + User: "user", }, }, res: res{ @@ -475,8 +505,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { { name: "no changes, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewDomainAddedEvent(context.Background(), @@ -510,29 +539,27 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - Description: "test", - Tls: true, - From: "from@domain.ch", - FromName: "name", - SMTP: smtp.SMTP{ - Host: "host:587", - User: "user", - }, + smtp: &ChangeSMTPConfig{ + ResourceOwner: "INSTANCE", + ID: "ID", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", + Host: "host:587", + User: "user", }, - instanceID: "INSTANCE", - id: "ID", }, res: res{ - err: zerrors.IsPreconditionFailed, + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, }, }, { name: "smtp config change, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewDomainAddedEvent(context.Background(), @@ -579,20 +606,17 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ + smtp: &ChangeSMTPConfig{ + ResourceOwner: "INSTANCE", + ID: "ID", Description: "test", Tls: false, From: "from2@domain.ch", FromName: "name2", ReplyToAddress: "replyto@domain.ch", - SMTP: smtp.SMTP{ - Host: "host2:587", - User: "user2", - }, + Host: "host2:587", + User: "user2", }, - id: "ID", - instanceID: "INSTANCE", }, res: res{ want: &domain.ObjectDetails{ @@ -603,58 +627,55 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { { name: "smtp config, port is missing", fields: fields{ - eventstore: eventstoreExpect(t), + eventstore: expectEventstore(), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - Description: "test", - Tls: true, - From: "from@domain.ch", - FromName: "name", - SMTP: smtp.SMTP{ - Host: "host", - User: "user", - Password: "password", - }, + smtp: &ChangeSMTPConfig{ + ResourceOwner: "INSTANCE", + ID: "ID", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", + Host: "host", + User: "user", + Password: "password", }, - instanceID: "INSTANCE", - id: "ID", }, res: res{ - err: zerrors.IsErrorInvalidArgument, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-kZNVkuL32L", "Errors.Invalid.Argument")) + }, }, }, { name: "smtp config, host is empty", fields: fields{ - eventstore: eventstoreExpect(t), + eventstore: expectEventstore(), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ - Description: "test", - Tls: true, - From: "from@domain.ch", - FromName: "name", - SMTP: smtp.SMTP{ - Host: " ", - User: "user", - Password: "password", - }, + smtp: &ChangeSMTPConfig{ + ResourceOwner: "INSTANCE", + ID: "ID", + Description: "test", + Tls: true, + From: "from@domain.ch", + FromName: "name", + Host: " ", + User: "user", + Password: "password", }, - instanceID: "INSTANCE", - id: "ID", }, res: res{ - err: zerrors.IsErrorInvalidArgument, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-kZNVkuL32L", "Errors.Invalid.Argument")) + }, }, }, { name: "smtp config change, ipv6 works", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewDomainAddedEvent(context.Background(), @@ -701,20 +722,17 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - smtp: &smtp.Config{ + smtp: &ChangeSMTPConfig{ + ResourceOwner: "INSTANCE", + ID: "ID", Description: "test", Tls: false, From: "from2@domain.ch", FromName: "name2", ReplyToAddress: "replyto@domain.ch", - SMTP: smtp.SMTP{ - Host: "[2001:db8::1]:2525", - User: "user2", - }, + Host: "[2001:db8::1]:2525", + User: "user2", }, - instanceID: "INSTANCE", - id: "ID", }, res: res{ want: &domain.ObjectDetails{ @@ -726,9 +744,9 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), } - got, err := r.ChangeSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id, tt.args.smtp) + err := r.ChangeSMTPConfig(context.Background(), tt.args.smtp) if tt.res.err == nil { assert.NoError(t, err) } @@ -736,7 +754,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { t.Errorf("got wrong err: %v ", err) } if tt.res.err == nil { - assertObjectDetails(t, tt.res.want, got) + assertObjectDetails(t, tt.res.want, tt.args.smtp.Details) } }) } @@ -744,11 +762,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) { func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore alg crypto.EncryptionAlgorithm } type args struct { - ctx context.Context instanceID string id string password string @@ -763,28 +780,54 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { args args res res }{ + { + name: "smtp config, error resourceOwner empty", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{}, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-gHAyvUXCAF", "Errors.ResourceOwnerMissing")) + }, + }, + }, + { + name: "smtp config, error id empty", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{ + instanceID: "INSTANCE", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-BCkAf7LcJA", "Errors.IDMissing")) + }, + }, + }, { name: "smtp config, error not found", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, args: args{ - ctx: context.Background(), - password: "", - id: "ID", + instanceID: "INSTANCE", + password: "", + id: "ID", }, res: res{ - err: zerrors.IsNotFound, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-rDHzqjGuKQ", "Errors.SMTPConfig.NotFound")) + }, }, }, { name: "change smtp config password, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewSMTPConfigAddedEvent( @@ -826,7 +869,6 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), password: "password", id: "ID", instanceID: "INSTANCE", @@ -841,10 +883,10 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), smtpEncryption: tt.fields.alg, } - got, err := r.ChangeSMTPConfigPassword(tt.args.ctx, tt.args.instanceID, tt.args.id, tt.args.password) + got, err := r.ChangeSMTPConfigPassword(context.Background(), tt.args.instanceID, tt.args.id, tt.args.password) if tt.res.err == nil { assert.NoError(t, err) } @@ -858,16 +900,13 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) { } } -func TestCommandSide_ActivateSMTPConfig(t *testing.T) { +func TestCommandSide_AddSMTPConfigHTTP(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore - alg crypto.EncryptionAlgorithm + eventstore func(t *testing.T) *eventstore.Eventstore + idGenerator id.Generator } type args struct { - ctx context.Context - instanceID string - id string - activatedId string + http *AddSMTPConfigHTTP } type res struct { want *domain.ObjectDetails @@ -880,115 +919,42 @@ func TestCommandSide_ActivateSMTPConfig(t *testing.T) { res res }{ { - name: "id empty, precondition error", + name: "add smtp config, resourceowner empty", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + http: &AddSMTPConfigHTTP{}, }, res: res{ - err: zerrors.IsErrorInvalidArgument, - }, - }, - { - name: "smtp not existing, not found error", - fields: fields{ - eventstore: eventstoreExpect( - t, - expectFilter(), - ), - }, - args: args{ - ctx: context.Background(), - instanceID: "INSTANCE", - id: "id", - }, - res: res{ - err: zerrors.IsNotFound, - }, - }, - { - name: "activate smtp config, ok", - fields: fields{ - eventstore: eventstoreExpect( - t, - expectFilter( - eventFromEventPusher( - instance.NewSMTPConfigAddedEvent( - context.Background(), - &instance.NewAggregate("INSTANCE").Aggregate, - "ID", - "test", - true, - "from", - "name", - "", - "host:587", - "user", - &crypto.CryptoValue{}, - ), - ), - ), - expectPush( - instance.NewSMTPConfigActivatedEvent( - context.Background(), - &instance.NewAggregate("INSTANCE").Aggregate, - "ID", - ), - ), - ), - }, - args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - id: "ID", - instanceID: "INSTANCE", - activatedId: "", - }, - res: res{ - want: &domain.ObjectDetails{ - ResourceOwner: "INSTANCE", + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-FTNDXc8ACS", "Errors.ResourceOwnerMissing")) }, }, }, { - name: "activate smtp config, already active, ok", + name: "add smtp config, ok", fields: fields{ - eventstore: eventstoreExpect( - t, - expectFilter( - eventFromEventPusher( - instance.NewSMTPConfigAddedEvent( - context.Background(), - &instance.NewAggregate("INSTANCE").Aggregate, - "ID", - "test", - true, - "from", - "name", - "", - "host:587", - "user", - &crypto.CryptoValue{}, - ), - ), - ), + eventstore: expectEventstore( + expectFilter(), expectPush( - instance.NewSMTPConfigActivatedEvent( + instance.NewSMTPConfigHTTPAddedEvent( context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, - "ID", + "configid", + "test", + "endpoint", ), ), ), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - id: "ID", - instanceID: "INSTANCE", - activatedId: "", + http: &AddSMTPConfigHTTP{ + ResourceOwner: "INSTANCE", + Description: "test", + Endpoint: "endpoint", + }, }, res: res{ want: &domain.ObjectDetails{ @@ -1000,10 +966,371 @@ func TestCommandSide_ActivateSMTPConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + } + err := r.AddSMTPConfigHTTP(context.Background(), tt.args.http) + 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 { + assertObjectDetails(t, tt.res.want, tt.args.http.Details) + assert.NotEmpty(t, tt.args.http.ID) + } + }) + } +} + +func TestCommandSide_ChangeSMTPConfigHTTP(t *testing.T) { + type fields struct { + eventstore func(t *testing.T) *eventstore.Eventstore + } + type args struct { + http *ChangeSMTPConfigHTTP + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{{ + name: "resourceowner empty, precondition error", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{ + http: &ChangeSMTPConfigHTTP{}, + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-k7QCGOWyJA", "Errors.ResourceOwnerMissing")) + }, + }, + }, + { + name: "id empty, precondition error", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{ + http: &ChangeSMTPConfigHTTP{ + ResourceOwner: "INSTANCE", + }, + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-2MHkV8ObWo", "Errors.IDMissing")) + }, + }, + }, + { + name: "smtp not existing, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + ), + }, + args: args{ + http: &ChangeSMTPConfigHTTP{ + ResourceOwner: "INSTANCE", + ID: "ID", + Description: "test", + Endpoint: "endpoint", + }, + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-xIrdledqv4", "Errors.SMTPConfig.NotFound")) + }, + }, + }, + { + name: "no changes, precondition error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigHTTPAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", + "endpoint", + ), + ), + ), + ), + }, + args: args{ + http: &ChangeSMTPConfigHTTP{ + ResourceOwner: "INSTANCE", + ID: "ID", + Description: "test", + Endpoint: "endpoint", + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "smtp config change, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigHTTPAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "", + "endpoint", + ), + ), + ), + expectPush( + newSMTPConfigHTTPChangedEvent( + context.Background(), + "ID", + "test", + "endpoint2", + ), + ), + ), + }, + args: args{ + http: &ChangeSMTPConfigHTTP{ + ResourceOwner: "INSTANCE", + ID: "ID", + Description: "test", + Endpoint: "endpoint2", + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore(t), + } + err := r.ChangeSMTPConfigHTTP(context.Background(), tt.args.http) + 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 { + assertObjectDetails(t, tt.res.want, tt.args.http.Details) + } + }) + } +} + +func TestCommandSide_ActivateSMTPConfig(t *testing.T) { + type fields struct { + eventstore func(t *testing.T) *eventstore.Eventstore + alg crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + instanceID string + id string + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{{ + name: "resourceowner empty, precondition error", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{ + instanceID: "", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-h5htMCebv3", "Errors.ResourceOwnerMissing")) + }, + }, + }, + { + name: "id empty, precondition error", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{ + instanceID: "INSTANCE", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-1hPl6oVMJa", "Errors.IDMissing")) + }, + }, + }, + { + name: "smtp not existing, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + instanceID: "INSTANCE", + id: "id", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-E9K20hxOS9", "Errors.SMTPConfig.NotFound")) + }, + }, + }, + { + name: "activate smtp config, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", + true, + "from", + "name", + "", + "host:587", + "user", + &crypto.CryptoValue{}, + ), + ), + ), + expectPush( + instance.NewSMTPConfigActivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + id: "ID", + instanceID: "INSTANCE", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "activate smtp config, already active", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", + true, + "from", + "name", + "", + "host:587", + "user", + &crypto.CryptoValue{}, + ), + ), + eventFromEventPusher( + instance.NewSMTPConfigActivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + id: "ID", + instanceID: "INSTANCE", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-vUHBSmBzaw", "Errors.SMTPConfig.AlreadyActive")) + }, + }, + }, + { + name: "activate smtp config http, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigHTTPAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", + "endpoint", + ), + ), + ), + expectPush( + instance.NewSMTPConfigActivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + id: "ID", + instanceID: "INSTANCE", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore(t), smtpEncryption: tt.fields.alg, } - got, err := r.ActivateSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id, tt.args.activatedId) + got, err := r.ActivateSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id) if tt.res.err == nil { assert.NoError(t, err) } @@ -1019,14 +1346,12 @@ func TestCommandSide_ActivateSMTPConfig(t *testing.T) { func TestCommandSide_DeactivateSMTPConfig(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore alg crypto.EncryptionAlgorithm } type args struct { - ctx context.Context - instanceID string - id string - activatedId string + instanceID string + id string } type res struct { want *domain.ObjectDetails @@ -1037,43 +1362,53 @@ func TestCommandSide_DeactivateSMTPConfig(t *testing.T) { fields fields args args res res - }{ + }{{ + name: "resourceOwner empty, precondition error", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{}, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-pvNHou89Tw", "Errors.ResourceOwnerMissing")) + }, + }, + }, { name: "id empty, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + instanceID: "INSTANCE", }, res: res{ - err: zerrors.IsErrorInvalidArgument, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-jLTIMrtApO", "Errors.IDMissing")) + }, }, }, { name: "smtp not existing, not found error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, args: args{ - ctx: context.Background(), instanceID: "INSTANCE", id: "id", }, res: res{ - err: zerrors.IsNotFound, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-k39PJ", "Errors.SMTPConfig.NotFound")) + }, }, }, { name: "deactivate smtp config, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewSMTPConfigAddedEvent( @@ -1108,10 +1443,96 @@ func TestCommandSide_DeactivateSMTPConfig(t *testing.T) { ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - id: "ID", - instanceID: "INSTANCE", - activatedId: "", + id: "ID", + instanceID: "INSTANCE", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "deactivate smtp config, already deactivated", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", + true, + "from", + "name", + "", + "host:587", + "user", + &crypto.CryptoValue{}, + ), + ), + eventFromEventPusher( + instance.NewSMTPConfigActivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + eventFromEventPusher( + instance.NewSMTPConfigDeactivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + ), + }, + args: args{ + id: "ID", + instanceID: "INSTANCE", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-km8g3", "Errors.SMTPConfig.AlreadyDeactivated")) + }, + }, + }, + { + name: "deactivate smtp config http, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigHTTPAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", + "endpoint", + ), + ), + eventFromEventPusher( + instance.NewSMTPConfigActivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + expectPush( + instance.NewSMTPConfigDeactivatedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + }, + args: args{ + id: "ID", + instanceID: "INSTANCE", }, res: res{ want: &domain.ObjectDetails{ @@ -1123,10 +1544,10 @@ func TestCommandSide_DeactivateSMTPConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), smtpEncryption: tt.fields.alg, } - got, err := r.DeactivateSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id) + got, err := r.DeactivateSMTPConfig(context.Background(), tt.args.instanceID, tt.args.id) if tt.res.err == nil { assert.NoError(t, err) } @@ -1142,11 +1563,10 @@ func TestCommandSide_DeactivateSMTPConfig(t *testing.T) { func TestCommandSide_RemoveSMTPConfig(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore alg crypto.EncryptionAlgorithm } type args struct { - ctx context.Context instanceID string id string } @@ -1160,27 +1580,55 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { args args res res }{ + { + name: "resourceowner empty, invalid argument error", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{ + id: "ID", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-t2WsPRgGaK", "Errors.ResourceOwnerMissing")) + }, + }, + }, + { + name: "id empty, invalid argument error", + fields: fields{ + eventstore: expectEventstore(), + }, + args: args{ + instanceID: "INSTANCE", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-0ZV5whuUfu", "Errors.IDMissing")) + }, + }, + }, { name: "smtp config, error not found", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, args: args{ - ctx: context.Background(), - id: "ID", + instanceID: "INSTANCE", + id: "ID", }, res: res{ - err: zerrors.IsNotFound, + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-09CXlTDL6w", "Errors.SMTPConfig.NotFound")) + }, }, }, { name: "remove smtp config, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewSMTPConfigAddedEvent( @@ -1208,7 +1656,40 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + id: "ID", + instanceID: "INSTANCE", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "remove smtp config http, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + instance.NewSMTPConfigHTTPAddedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + "test", + "endpoint", + ), + ), + ), + expectPush( + instance.NewSMTPConfigRemovedEvent( + context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + "ID", + ), + ), + ), + }, + args: args{ id: "ID", instanceID: "INSTANCE", }, @@ -1222,10 +1703,10 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), smtpEncryption: tt.fields.alg, } - got, err := r.RemoveSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id) + got, err := r.RemoveSMTPConfig(context.Background(), tt.args.instanceID, tt.args.id) if tt.res.err == nil { assert.NoError(t, err) } @@ -1241,7 +1722,7 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) { func TestCommandSide_TestSMTPConfig(t *testing.T) { type fields struct { - eventstore *eventstore.Eventstore + eventstore func(t *testing.T) *eventstore.Eventstore alg crypto.EncryptionAlgorithm } type args struct { @@ -1263,9 +1744,7 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) { { name: "id empty, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), @@ -1277,9 +1756,7 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) { { name: "email empty, precondition error", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), @@ -1293,9 +1770,7 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) { { name: "if password is empty, smtp id must not", fields: fields{ - eventstore: eventstoreExpect( - t, - ), + eventstore: expectEventstore(), }, args: args{ ctx: context.Background(), @@ -1319,8 +1794,7 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) { { name: "password empty and smtp config not found, error", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter(), ), }, @@ -1346,10 +1820,8 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) { { name: "valid new smtp config, wrong auth, ok", fields: fields{ - eventstore: eventstoreExpect( - t, - ), - alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + eventstore: expectEventstore(), + alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), }, args: args{ ctx: context.Background(), @@ -1372,8 +1844,7 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) { { name: "valid smtp config using stored password, wrong auth, ok", fields: fields{ - eventstore: eventstoreExpect( - t, + eventstore: expectEventstore( expectFilter( eventFromEventPusher( instance.NewSMTPConfigAddedEvent( @@ -1422,7 +1893,7 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Commands{ - eventstore: tt.fields.eventstore, + eventstore: tt.fields.eventstore(t), smtpEncryption: tt.fields.alg, } err := r.TestSMTPConfig(tt.args.ctx, tt.args.instanceID, tt.args.id, tt.args.email, &tt.args.config) @@ -1579,3 +2050,16 @@ func newSMTPConfigChangedEvent(ctx context.Context, id, description string, tls ) return event } + +func newSMTPConfigHTTPChangedEvent(ctx context.Context, id, description, endpoint string) *instance.SMTPConfigHTTPChangedEvent { + changes := []instance.SMTPConfigHTTPChanges{ + instance.ChangeSMTPConfigHTTPDescription(description), + instance.ChangeSMTPConfigHTTPEndpoint(endpoint), + } + event, _ := instance.NewSMTPConfigHTTPChangeEvent(ctx, + &instance.NewAggregate("INSTANCE").Aggregate, + id, + changes, + ) + return event +} diff --git a/internal/notification/channels.go b/internal/notification/channels.go index 0511aaf78b..c70eaecbcc 100644 --- a/internal/notification/channels.go +++ b/internal/notification/channels.go @@ -5,8 +5,8 @@ import ( "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/notification/channels/email" "github.com/zitadel/zitadel/internal/notification/channels/sms" - "github.com/zitadel/zitadel/internal/notification/channels/smtp" "github.com/zitadel/zitadel/internal/notification/channels/webhook" "github.com/zitadel/zitadel/internal/notification/handlers" "github.com/zitadel/zitadel/internal/notification/senders" @@ -62,20 +62,20 @@ func registerCounter(counter, desc string) { logging.WithFields("metric", counter).OnError(err).Panic("unable to register counter") } -func (c *channels) Email(ctx context.Context) (*senders.Chain, *smtp.Config, error) { - smtpCfg, err := c.q.GetSMTPConfig(ctx) +func (c *channels) Email(ctx context.Context) (*senders.Chain, *email.Config, error) { + emailCfg, err := c.q.GetActiveEmailConfig(ctx) if err != nil { return nil, nil, err } chain, err := senders.EmailChannels( ctx, - smtpCfg, + emailCfg, c.q.GetFileSystemProvider, c.q.GetLogProvider, c.counters.success.email, c.counters.failed.email, ) - return chain, smtpCfg, err + return chain, emailCfg, err } func (c *channels) SMS(ctx context.Context) (*senders.Chain, *sms.Config, error) { diff --git a/internal/notification/channels/email/config.go b/internal/notification/channels/email/config.go new file mode 100644 index 0000000000..d06029f8c2 --- /dev/null +++ b/internal/notification/channels/email/config.go @@ -0,0 +1,17 @@ +package email + +import ( + "github.com/zitadel/zitadel/internal/notification/channels/smtp" + "github.com/zitadel/zitadel/internal/notification/channels/webhook" +) + +type Config struct { + ProviderConfig *Provider + SMTPConfig *smtp.Config + WebhookConfig *webhook.Config +} + +type Provider struct { + ID string `json:"id,omitempty"` + Description string `json:"description,omitempty"` +} diff --git a/internal/notification/channels/smtp/config.go b/internal/notification/channels/smtp/config.go index 865a2f4cd1..d8323ae574 100644 --- a/internal/notification/channels/smtp/config.go +++ b/internal/notification/channels/smtp/config.go @@ -1,7 +1,6 @@ package smtp type Config struct { - Description string SMTP SMTP Tls bool From string @@ -18,3 +17,7 @@ type SMTP struct { func (smtp *SMTP) HasAuth() bool { return smtp.User != "" && smtp.Password != "" } + +type ConfigHTTP struct { + Endpoint string +} diff --git a/internal/notification/handlers/config_email.go b/internal/notification/handlers/config_email.go new file mode 100644 index 0000000000..b78540a423 --- /dev/null +++ b/internal/notification/handlers/config_email.go @@ -0,0 +1,56 @@ +package handlers + +import ( + "context" + "net/http" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/notification/channels/email" + "github.com/zitadel/zitadel/internal/notification/channels/smtp" + "github.com/zitadel/zitadel/internal/notification/channels/webhook" + "github.com/zitadel/zitadel/internal/zerrors" +) + +// GetSMTPConfig reads the iam SMTP provider config +func (n *NotificationQueries) GetActiveEmailConfig(ctx context.Context) (*email.Config, error) { + config, err := n.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID()) + if err != nil { + return nil, err + } + provider := &email.Provider{ + ID: config.ID, + Description: config.Description, + } + if config.SMTPConfig != nil { + password, err := crypto.DecryptString(config.SMTPConfig.Password, n.SMTPPasswordCrypto) + if err != nil { + return nil, err + } + return &email.Config{ + ProviderConfig: provider, + SMTPConfig: &smtp.Config{ + From: config.SMTPConfig.SenderAddress, + FromName: config.SMTPConfig.SenderName, + ReplyToAddress: config.SMTPConfig.ReplyToAddress, + Tls: config.SMTPConfig.TLS, + SMTP: smtp.SMTP{ + Host: config.SMTPConfig.Host, + User: config.SMTPConfig.User, + Password: password, + }, + }, + }, nil + } + if config.HTTPConfig != nil { + return &email.Config{ + ProviderConfig: provider, + WebhookConfig: &webhook.Config{ + CallURL: config.HTTPConfig.Endpoint, + Method: http.MethodPost, + Headers: nil, + }, + }, nil + } + return nil, zerrors.ThrowNotFound(err, "QUERY-KPQleOckOV", "Errors.SMTPConfig.NotFound") +} diff --git a/internal/notification/handlers/config_smtp.go b/internal/notification/handlers/config_smtp.go deleted file mode 100644 index 8cce47dec6..0000000000 --- a/internal/notification/handlers/config_smtp.go +++ /dev/null @@ -1,33 +0,0 @@ -package handlers - -import ( - "context" - - "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/zitadel/internal/notification/channels/smtp" -) - -// GetSMTPConfig reads the iam SMTP provider config -func (n *NotificationQueries) GetSMTPConfig(ctx context.Context) (*smtp.Config, error) { - config, err := n.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID()) - if err != nil { - return nil, err - } - password, err := crypto.DecryptString(config.Password, n.SMTPPasswordCrypto) - if err != nil { - return nil, err - } - return &smtp.Config{ - Description: config.Description, - From: config.SenderAddress, - FromName: config.SenderName, - ReplyToAddress: config.ReplyToAddress, - Tls: config.TLS, - SMTP: smtp.SMTP{ - Host: config.Host, - User: config.User, - Password: password, - }, - }, nil -} diff --git a/internal/notification/handlers/user_notifier_test.go b/internal/notification/handlers/user_notifier_test.go index 60fa1791dd..c18ddc1df3 100644 --- a/internal/notification/handlers/user_notifier_test.go +++ b/internal/notification/handlers/user_notifier_test.go @@ -15,6 +15,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/repository" es_repo_mock "github.com/zitadel/zitadel/internal/eventstore/repository/mock" + "github.com/zitadel/zitadel/internal/notification/channels/email" channel_mock "github.com/zitadel/zitadel/internal/notification/channels/mock" "github.com/zitadel/zitadel/internal/notification/channels/sms" "github.com/zitadel/zitadel/internal/notification/channels/smtp" @@ -1449,7 +1450,27 @@ func newUserNotifier(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQu f.SMSTokenCrypto, ), otpEmailTmpl: defaultOTPEmailTemplate, - channels: &channels{Chain: *senders.ChainChannels(channel)}, + channels: &channels{ + Chain: *senders.ChainChannels(channel), + EmailConfig: &email.Config{ + ProviderConfig: &email.Provider{ + ID: "ID", + Description: "Description", + }, + SMTPConfig: &smtp.Config{ + SMTP: smtp.SMTP{ + Host: "host", + User: "user", + Password: "password", + }, + Tls: true, + From: "from", + FromName: "fromName", + ReplyToAddress: "replyToAddress", + }, + WebhookConfig: nil, + }, + }, } } @@ -1457,10 +1478,11 @@ var _ types.ChannelChains = (*channels)(nil) type channels struct { senders.Chain + EmailConfig *email.Config } -func (c *channels) Email(context.Context) (*senders.Chain, *smtp.Config, error) { - return &c.Chain, nil, nil +func (c *channels) Email(context.Context) (*senders.Chain, *email.Config, error) { + return &c.Chain, c.EmailConfig, nil } func (c *channels) SMS(context.Context) (*senders.Chain, *sms.Config, error) { diff --git a/internal/notification/senders/email.go b/internal/notification/senders/email.go index ea93c0911e..4dfc815919 100644 --- a/internal/notification/senders/email.go +++ b/internal/notification/senders/email.go @@ -7,38 +7,61 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/notification/channels" + "github.com/zitadel/zitadel/internal/notification/channels/email" "github.com/zitadel/zitadel/internal/notification/channels/fs" "github.com/zitadel/zitadel/internal/notification/channels/instrumenting" "github.com/zitadel/zitadel/internal/notification/channels/log" "github.com/zitadel/zitadel/internal/notification/channels/smtp" + "github.com/zitadel/zitadel/internal/notification/channels/webhook" ) const smtpSpanName = "smtp.NotificationChannel" func EmailChannels( ctx context.Context, - emailConfig *smtp.Config, + emailConfig *email.Config, getFileSystemProvider func(ctx context.Context) (*fs.Config, error), getLogProvider func(ctx context.Context) (*log.Config, error), successMetricName, failureMetricName string, ) (chain *Chain, err error) { channels := make([]channels.NotificationChannel, 0, 3) - p, err := smtp.InitChannel(emailConfig) - logging.WithFields( - "instance", authz.GetInstance(ctx).InstanceID(), - ).OnError(err).Debug("initializing SMTP channel failed") - if err == nil { - channels = append( - channels, - instrumenting.Wrap( - ctx, - p, - smtpSpanName, - successMetricName, - failureMetricName, - ), - ) + if emailConfig.SMTPConfig != nil { + p, err := smtp.InitChannel(emailConfig.SMTPConfig) + logging.WithFields( + "instance", authz.GetInstance(ctx).InstanceID(), + ).OnError(err).Debug("initializing SMTP channel failed") + if err == nil { + channels = append( + channels, + instrumenting.Wrap( + ctx, + p, + smtpSpanName, + successMetricName, + failureMetricName, + ), + ) + } + } + if emailConfig.WebhookConfig != nil { + webhookChannel, err := webhook.InitChannel(ctx, *emailConfig.WebhookConfig) + logging.WithFields( + "instance", authz.GetInstance(ctx).InstanceID(), + "callurl", emailConfig.WebhookConfig.CallURL, + ).OnError(err).Debug("initializing JSON channel failed") + if err == nil { + channels = append( + channels, + instrumenting.Wrap( + ctx, + webhookChannel, + webhookSpanName, + successMetricName, + failureMetricName, + ), + ) + } } channels = append(channels, debugChannels(ctx, getFileSystemProvider, getLogProvider)...) return ChainChannels(channels...), nil diff --git a/internal/notification/types/notification.go b/internal/notification/types/notification.go index 4d72d911f9..8d1b013164 100644 --- a/internal/notification/types/notification.go +++ b/internal/notification/types/notification.go @@ -7,8 +7,8 @@ import ( "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/i18n" + "github.com/zitadel/zitadel/internal/notification/channels/email" "github.com/zitadel/zitadel/internal/notification/channels/sms" - "github.com/zitadel/zitadel/internal/notification/channels/smtp" "github.com/zitadel/zitadel/internal/notification/channels/webhook" "github.com/zitadel/zitadel/internal/notification/senders" "github.com/zitadel/zitadel/internal/notification/templates" @@ -23,7 +23,7 @@ type Notify func( ) error type ChannelChains interface { - Email(context.Context) (*senders.Chain, *smtp.Config, error) + Email(context.Context) (*senders.Chain, *email.Config, error) SMS(context.Context) (*senders.Chain, *sms.Config, error) Webhook(context.Context, webhook.Config) (*senders.Chain, error) } @@ -54,8 +54,9 @@ func SendEmail( ctx, channels, user, - data.Subject, template, + data, + args, allowUnverifiedNotificationChannel, triggeringEvent, ) diff --git a/internal/notification/types/user_email.go b/internal/notification/types/user_email.go index d3c3bfdd4a..210ca14cf8 100644 --- a/internal/notification/types/user_email.go +++ b/internal/notification/types/user_email.go @@ -3,9 +3,13 @@ package types import ( "context" "html" + "strings" + + "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/notification/messages" + "github.com/zitadel/zitadel/internal/notification/templates" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -14,29 +18,56 @@ func generateEmail( ctx context.Context, channels ChannelChains, user *query.NotifyUser, - subject, - content string, + template string, + data templates.TemplateData, + args map[string]interface{}, lastEmail bool, triggeringEvent eventstore.Event, ) error { - content = html.UnescapeString(content) - message := &messages.Email{ - Recipients: []string{user.VerifiedEmail}, - Subject: subject, - Content: content, - TriggeringEvent: triggeringEvent, - } - if lastEmail { - message.Recipients = []string{user.LastEmail} - } - emailChannels, _, err := channels.Email(ctx) - if err != nil { - return err - } + emailChannels, config, err := channels.Email(ctx) + logging.OnError(err).Error("could not create email channel") if emailChannels == nil || emailChannels.Len() == 0 { - return zerrors.ThrowPreconditionFailed(nil, "MAIL-83nof", "Errors.Notification.Channels.NotPresent") + return zerrors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent") } - return emailChannels.HandleMessage(message) + recipient := user.VerifiedEmail + if lastEmail { + recipient = user.LastEmail + } + if config.SMTPConfig != nil { + message := &messages.Email{ + Recipients: []string{recipient}, + Subject: data.Subject, + Content: html.UnescapeString(template), + TriggeringEvent: triggeringEvent, + } + return emailChannels.HandleMessage(message) + } + if config.WebhookConfig != nil { + caseArgs := make(map[string]interface{}, len(args)) + for k, v := range args { + caseArgs[strings.ToLower(string(k[0]))+k[1:]] = v + } + contextInfo := map[string]interface{}{ + "recipientEmailAddress": recipient, + "eventType": triggeringEvent.Type(), + "provider": config.ProviderConfig, + } + + message := &messages.JSON{ + Serializable: &serializableData{ + ContextInfo: contextInfo, + TemplateData: data, + Args: caseArgs, + }, + TriggeringEvent: triggeringEvent, + } + webhookChannels, err := channels.Webhook(ctx, *config.WebhookConfig) + if err != nil { + return err + } + return webhookChannels.HandleMessage(message) + } + return zerrors.ThrowPreconditionFailed(nil, "MAIL-83nof", "Errors.Notification.Channels.NotPresent") } func mapNotifyUserToArgs(user *query.NotifyUser, args map[string]interface{}) map[string]interface{} { diff --git a/internal/query/projection/smtp.go b/internal/query/projection/smtp.go index 9b53da4150..dd1d9c9c09 100644 --- a/internal/query/projection/smtp.go +++ b/internal/query/projection/smtp.go @@ -8,26 +8,38 @@ import ( old_handler "github.com/zitadel/zitadel/internal/eventstore/handler" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/repository/instance" - "github.com/zitadel/zitadel/internal/zerrors" ) const ( - SMTPConfigProjectionTable = "projections.smtp_configs2" - SMTPConfigColumnInstanceID = "instance_id" - SMTPConfigColumnResourceOwner = "resource_owner" - SMTPConfigColumnID = "id" - SMTPConfigColumnCreationDate = "creation_date" - SMTPConfigColumnChangeDate = "change_date" - SMTPConfigColumnSequence = "sequence" - SMTPConfigColumnTLS = "tls" - SMTPConfigColumnSenderAddress = "sender_address" - SMTPConfigColumnSenderName = "sender_name" - SMTPConfigColumnReplyToAddress = "reply_to_address" - SMTPConfigColumnSMTPHost = "host" - SMTPConfigColumnSMTPUser = "username" - SMTPConfigColumnSMTPPassword = "password" - SMTPConfigColumnState = "state" - SMTPConfigColumnDescription = "description" + SMTPConfigProjectionTable = "projections.smtp_configs3" + SMTPConfigTable = SMTPConfigProjectionTable + "_" + smtpConfigSMTPTableSuffix + SMTPConfigHTTPTable = SMTPConfigProjectionTable + "_" + smtpConfigHTTPTableSuffix + + SMTPConfigColumnInstanceID = "instance_id" + SMTPConfigColumnResourceOwner = "resource_owner" + SMTPConfigColumnAggregateID = "aggregate_id" + SMTPConfigColumnID = "id" + SMTPConfigColumnCreationDate = "creation_date" + SMTPConfigColumnChangeDate = "change_date" + SMTPConfigColumnSequence = "sequence" + SMTPConfigColumnState = "state" + SMTPConfigColumnDescription = "description" + + smtpConfigSMTPTableSuffix = "smtp" + SMTPConfigSMTPColumnInstanceID = "instance_id" + SMTPConfigSMTPColumnID = "id" + SMTPConfigSMTPColumnTLS = "tls" + SMTPConfigSMTPColumnSenderAddress = "sender_address" + SMTPConfigSMTPColumnSenderName = "sender_name" + SMTPConfigSMTPColumnReplyToAddress = "reply_to_address" + SMTPConfigSMTPColumnHost = "host" + SMTPConfigSMTPColumnUser = "username" + SMTPConfigSMTPColumnPassword = "password" + + smtpConfigHTTPTableSuffix = "http" + SMTPConfigHTTPColumnInstanceID = "instance_id" + SMTPConfigHTTPColumnID = "id" + SMTPConfigHTTPColumnEndpoint = "endpoint" ) type smtpConfigProjection struct{} @@ -41,25 +53,43 @@ func (*smtpConfigProjection) Name() string { } func (*smtpConfigProjection) Init() *old_handler.Check { - return handler.NewTableCheck( + return handler.NewMultiTableCheck( handler.NewTable([]*handler.InitColumn{ handler.NewColumn(SMTPConfigColumnID, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigColumnAggregateID, handler.ColumnTypeText), handler.NewColumn(SMTPConfigColumnCreationDate, handler.ColumnTypeTimestamp), handler.NewColumn(SMTPConfigColumnChangeDate, handler.ColumnTypeTimestamp), handler.NewColumn(SMTPConfigColumnSequence, handler.ColumnTypeInt64), handler.NewColumn(SMTPConfigColumnResourceOwner, handler.ColumnTypeText), handler.NewColumn(SMTPConfigColumnInstanceID, handler.ColumnTypeText), - handler.NewColumn(SMTPConfigColumnTLS, handler.ColumnTypeBool), - handler.NewColumn(SMTPConfigColumnSenderAddress, handler.ColumnTypeText), - handler.NewColumn(SMTPConfigColumnSenderName, handler.ColumnTypeText), - handler.NewColumn(SMTPConfigColumnReplyToAddress, handler.ColumnTypeText), - handler.NewColumn(SMTPConfigColumnSMTPHost, handler.ColumnTypeText), - handler.NewColumn(SMTPConfigColumnSMTPUser, handler.ColumnTypeText), - handler.NewColumn(SMTPConfigColumnSMTPPassword, handler.ColumnTypeJSONB, handler.Nullable()), - handler.NewColumn(SMTPConfigColumnState, handler.ColumnTypeEnum), handler.NewColumn(SMTPConfigColumnDescription, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigColumnState, handler.ColumnTypeEnum), }, - handler.NewPrimaryKey(SMTPConfigColumnInstanceID, SMTPConfigColumnResourceOwner, SMTPConfigColumnID), + handler.NewPrimaryKey(SMTPConfigColumnInstanceID, SMTPConfigColumnID), + ), + handler.NewSuffixedTable([]*handler.InitColumn{ + handler.NewColumn(SMTPConfigSMTPColumnID, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigSMTPColumnInstanceID, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigSMTPColumnTLS, handler.ColumnTypeBool), + handler.NewColumn(SMTPConfigSMTPColumnSenderAddress, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigSMTPColumnSenderName, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigSMTPColumnReplyToAddress, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigSMTPColumnHost, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigSMTPColumnUser, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigSMTPColumnPassword, handler.ColumnTypeJSONB, handler.Nullable()), + }, + handler.NewPrimaryKey(SMTPConfigSMTPColumnInstanceID, SMTPConfigSMTPColumnID), + smtpConfigSMTPTableSuffix, + handler.WithForeignKey(handler.NewForeignKeyOfPublicKeys()), + ), + handler.NewSuffixedTable([]*handler.InitColumn{ + handler.NewColumn(SMTPConfigHTTPColumnID, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigHTTPColumnInstanceID, handler.ColumnTypeText), + handler.NewColumn(SMTPConfigHTTPColumnEndpoint, handler.ColumnTypeText), + }, + handler.NewPrimaryKey(SMTPConfigHTTPColumnInstanceID, SMTPConfigHTTPColumnID), + smtpConfigHTTPTableSuffix, + handler.WithForeignKey(handler.NewForeignKeyOfPublicKeys()), ), ) } @@ -81,6 +111,14 @@ func (p *smtpConfigProjection) Reducers() []handler.AggregateReducer { Event: instance.SMTPConfigPasswordChangedEventType, Reduce: p.reduceSMTPConfigPasswordChanged, }, + { + Event: instance.SMTPConfigHTTPAddedEventType, + Reduce: p.reduceSMTPConfigHTTPAdded, + }, + { + Event: instance.SMTPConfigHTTPChangedEventType, + Reduce: p.reduceSMTPConfigHTTPChanged, + }, { Event: instance.SMTPConfigActivatedEventType, Reduce: p.reduceSMTPConfigActivated, @@ -103,9 +141,9 @@ func (p *smtpConfigProjection) Reducers() []handler.AggregateReducer { } func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*handler.Statement, error) { - e, ok := event.(*instance.SMTPConfigAddedEvent) - if !ok { - return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-sk99F", "reduce.wrong.event.type %s", instance.SMTPConfigAddedEventType) + e, err := assertEvent[*instance.SMTPConfigAddedEvent](event) + if err != nil { + return nil, err } // Deal with old and unique SMTP settings (empty ID) @@ -118,83 +156,116 @@ func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*h state = domain.SMTPConfigStateActive } - return handler.NewCreateStatement( + return handler.NewMultiStatement( e, - []handler.Column{ - handler.NewCol(SMTPConfigColumnCreationDate, e.CreationDate()), - handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), - handler.NewCol(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), - handler.NewCol(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), - handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), - handler.NewCol(SMTPConfigColumnID, id), - handler.NewCol(SMTPConfigColumnTLS, e.TLS), - handler.NewCol(SMTPConfigColumnSenderAddress, e.SenderAddress), - handler.NewCol(SMTPConfigColumnSenderName, e.SenderName), - handler.NewCol(SMTPConfigColumnReplyToAddress, e.ReplyToAddress), - handler.NewCol(SMTPConfigColumnSMTPHost, e.Host), - handler.NewCol(SMTPConfigColumnSMTPUser, e.User), - handler.NewCol(SMTPConfigColumnSMTPPassword, e.Password), - handler.NewCol(SMTPConfigColumnState, state), - handler.NewCol(SMTPConfigColumnDescription, description), - }, + handler.AddCreateStatement( + []handler.Column{ + handler.NewCol(SMTPConfigColumnCreationDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + handler.NewCol(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), + handler.NewCol(SMTPConfigColumnAggregateID, e.Aggregate().ID), + handler.NewCol(SMTPConfigColumnID, id), + handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), + handler.NewCol(SMTPConfigColumnState, state), + handler.NewCol(SMTPConfigColumnDescription, description), + }, + ), + handler.AddCreateStatement( + []handler.Column{ + handler.NewCol(SMTPConfigSMTPColumnInstanceID, e.Aggregate().InstanceID), + handler.NewCol(SMTPConfigSMTPColumnID, e.ID), + handler.NewCol(SMTPConfigSMTPColumnTLS, e.TLS), + handler.NewCol(SMTPConfigSMTPColumnSenderAddress, e.SenderAddress), + handler.NewCol(SMTPConfigSMTPColumnSenderName, e.SenderName), + handler.NewCol(SMTPConfigSMTPColumnReplyToAddress, e.ReplyToAddress), + handler.NewCol(SMTPConfigSMTPColumnHost, e.Host), + handler.NewCol(SMTPConfigSMTPColumnUser, e.User), + handler.NewCol(SMTPConfigSMTPColumnPassword, e.Password), + }, + handler.WithTableSuffix(smtpConfigSMTPTableSuffix), + ), ), nil } -func (p *smtpConfigProjection) reduceSMTPConfigChanged(event eventstore.Event) (*handler.Statement, error) { - e, ok := event.(*instance.SMTPConfigChangedEvent) - if !ok { - return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-wl0wd", "reduce.wrong.event.type %s", instance.SMTPConfigChangedEventType) +func (p *smtpConfigProjection) reduceSMTPConfigHTTPAdded(event eventstore.Event) (*handler.Statement, error) { + e, err := assertEvent[*instance.SMTPConfigHTTPAddedEvent](event) + if err != nil { + return nil, err } - columns := make([]handler.Column, 0, 8) - columns = append(columns, handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), - handler.NewCol(SMTPConfigColumnSequence, e.Sequence())) + return handler.NewMultiStatement( + e, + handler.AddCreateStatement( + []handler.Column{ + handler.NewCol(SMTPConfigColumnCreationDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + handler.NewCol(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), + handler.NewCol(SMTPConfigColumnAggregateID, e.Aggregate().ID), + handler.NewCol(SMTPConfigColumnID, e.ID), + handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), + handler.NewCol(SMTPConfigColumnState, domain.SMTPConfigStateInactive), + handler.NewCol(SMTPConfigColumnDescription, e.Description), + }, + ), + handler.AddCreateStatement( + []handler.Column{ + handler.NewCol(SMTPConfigSMTPColumnInstanceID, e.Aggregate().InstanceID), + handler.NewCol(SMTPConfigSMTPColumnID, e.ID), + handler.NewCol(SMTPConfigHTTPColumnEndpoint, e.Endpoint), + }, + handler.WithTableSuffix(smtpConfigHTTPTableSuffix), + ), + ), nil +} - // Deal with old and unique SMTP settings (empty ID) - id := e.ID - if e.ID == "" { - id = e.Aggregate().ResourceOwner +func (p *smtpConfigProjection) reduceSMTPConfigHTTPChanged(event eventstore.Event) (*handler.Statement, error) { + e, err := assertEvent[*instance.SMTPConfigHTTPChangedEvent](event) + if err != nil { + return nil, err } - if e.TLS != nil { - columns = append(columns, handler.NewCol(SMTPConfigColumnTLS, *e.TLS)) - } - if e.FromAddress != nil { - columns = append(columns, handler.NewCol(SMTPConfigColumnSenderAddress, *e.FromAddress)) - } - if e.FromName != nil { - columns = append(columns, handler.NewCol(SMTPConfigColumnSenderName, *e.FromName)) - } - if e.ReplyToAddress != nil { - columns = append(columns, handler.NewCol(SMTPConfigColumnReplyToAddress, *e.ReplyToAddress)) - } - if e.Host != nil { - columns = append(columns, handler.NewCol(SMTPConfigColumnSMTPHost, *e.Host)) - } - if e.User != nil { - columns = append(columns, handler.NewCol(SMTPConfigColumnSMTPUser, *e.User)) - } - if e.Password != nil { - columns = append(columns, handler.NewCol(SMTPConfigColumnSMTPPassword, *e.Password)) + stmts := make([]func(eventstore.Event) handler.Exec, 0, 3) + columns := []handler.Column{ + handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), } if e.Description != nil { columns = append(columns, handler.NewCol(SMTPConfigColumnDescription, *e.Description)) } - return handler.NewUpdateStatement( - e, - columns, - []handler.Condition{ - handler.NewCond(SMTPConfigColumnID, id), - handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), - handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), - }, - ), nil + if len(columns) > 0 { + stmts = append(stmts, handler.AddUpdateStatement( + columns, + []handler.Condition{ + handler.NewCond(SMTPConfigColumnID, e.ID), + handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + )) + } + + smtpColumns := make([]handler.Column, 0, 1) + if e.Endpoint != nil { + smtpColumns = append(smtpColumns, handler.NewCol(SMTPConfigHTTPColumnEndpoint, *e.Endpoint)) + } + if len(smtpColumns) > 0 { + stmts = append(stmts, handler.AddUpdateStatement( + smtpColumns, + []handler.Condition{ + handler.NewCond(SMTPConfigHTTPColumnID, e.ID), + handler.NewCond(SMTPConfigHTTPColumnInstanceID, e.Aggregate().InstanceID), + }, + handler.WithTableSuffix(smtpConfigHTTPTableSuffix), + )) + } + + return handler.NewMultiStatement(e, stmts...), nil } -func (p *smtpConfigProjection) reduceSMTPConfigPasswordChanged(event eventstore.Event) (*handler.Statement, error) { - e, ok := event.(*instance.SMTPConfigPasswordChangedEvent) - if !ok { - return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fk02f", "reduce.wrong.event.type %s", instance.SMTPConfigChangedEventType) +func (p *smtpConfigProjection) reduceSMTPConfigChanged(event eventstore.Event) (*handler.Statement, error) { + e, err := assertEvent[*instance.SMTPConfigChangedEvent](event) + if err != nil { + return nil, err } // Deal with old and unique SMTP settings (empty ID) @@ -203,25 +274,101 @@ func (p *smtpConfigProjection) reduceSMTPConfigPasswordChanged(event eventstore. id = e.Aggregate().ResourceOwner } - return handler.NewUpdateStatement( + stmts := make([]func(eventstore.Event) handler.Exec, 0, 3) + columns := []handler.Column{ + handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), + } + if e.Description != nil { + columns = append(columns, handler.NewCol(SMTPConfigColumnDescription, *e.Description)) + } + if len(columns) > 0 { + stmts = append(stmts, handler.AddUpdateStatement( + columns, + []handler.Condition{ + handler.NewCond(SMTPConfigColumnID, id), + handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + )) + } + + httpColumns := make([]handler.Column, 0, 7) + if e.TLS != nil { + httpColumns = append(httpColumns, handler.NewCol(SMTPConfigSMTPColumnTLS, *e.TLS)) + } + if e.FromAddress != nil { + httpColumns = append(httpColumns, handler.NewCol(SMTPConfigSMTPColumnSenderAddress, *e.FromAddress)) + } + if e.FromName != nil { + httpColumns = append(httpColumns, handler.NewCol(SMTPConfigSMTPColumnSenderName, *e.FromName)) + } + if e.ReplyToAddress != nil { + httpColumns = append(httpColumns, handler.NewCol(SMTPConfigSMTPColumnReplyToAddress, *e.ReplyToAddress)) + } + if e.Host != nil { + httpColumns = append(httpColumns, handler.NewCol(SMTPConfigSMTPColumnHost, *e.Host)) + } + if e.User != nil { + httpColumns = append(httpColumns, handler.NewCol(SMTPConfigSMTPColumnUser, *e.User)) + } + if e.Password != nil { + httpColumns = append(httpColumns, handler.NewCol(SMTPConfigSMTPColumnPassword, *e.Password)) + } + if len(httpColumns) > 0 { + stmts = append(stmts, handler.AddUpdateStatement( + httpColumns, + []handler.Condition{ + handler.NewCond(SMTPConfigSMTPColumnID, e.ID), + handler.NewCond(SMTPConfigSMTPColumnInstanceID, e.Aggregate().InstanceID), + }, + handler.WithTableSuffix(smtpConfigSMTPTableSuffix), + )) + } + + return handler.NewMultiStatement(e, stmts...), nil +} + +func (p *smtpConfigProjection) reduceSMTPConfigPasswordChanged(event eventstore.Event) (*handler.Statement, error) { + e, err := assertEvent[*instance.SMTPConfigPasswordChangedEvent](event) + if err != nil { + return nil, err + } + + // Deal with old and unique SMTP settings (empty ID) + id := e.ID + if e.ID == "" { + id = e.Aggregate().ResourceOwner + } + + return handler.NewMultiStatement( e, - []handler.Column{ - handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), - handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), - handler.NewCol(SMTPConfigColumnSMTPPassword, e.Password), - }, - []handler.Condition{ - handler.NewCond(SMTPConfigColumnID, id), - handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), - handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), - }, + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(SMTPConfigSMTPColumnPassword, e.Password), + }, + []handler.Condition{ + handler.NewCond(SMTPConfigColumnID, id), + handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + handler.WithTableSuffix(smtpConfigSMTPTableSuffix), + ), + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), + }, + []handler.Condition{ + handler.NewCond(SMTPConfigColumnID, id), + handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + ), ), nil } func (p *smtpConfigProjection) reduceSMTPConfigActivated(event eventstore.Event) (*handler.Statement, error) { - e, ok := event.(*instance.SMTPConfigActivatedEvent) - if !ok { - return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fq92r", "reduce.wrong.event.type %s", instance.SMTPConfigActivatedEventType) + e, err := assertEvent[*instance.SMTPConfigActivatedEvent](event) + if err != nil { + return nil, err } // Deal with old and unique SMTP settings (empty ID) @@ -230,25 +377,38 @@ func (p *smtpConfigProjection) reduceSMTPConfigActivated(event eventstore.Event) id = e.Aggregate().ResourceOwner } - return handler.NewUpdateStatement( + return handler.NewMultiStatement( e, - []handler.Column{ - handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), - handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), - handler.NewCol(SMTPConfigColumnState, domain.SMTPConfigStateActive), - }, - []handler.Condition{ - handler.NewCond(SMTPConfigColumnID, id), - handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), - handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), - }, + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), + handler.NewCol(SMTPConfigColumnState, domain.SMTPConfigStateInactive), + }, + []handler.Condition{ + handler.Not(handler.NewCond(SMTPConfigColumnID, e.ID)), + handler.NewCond(SMTPConfigColumnState, domain.SMTPConfigStateActive), + handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + ), + handler.AddUpdateStatement( + []handler.Column{ + handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()), + handler.NewCol(SMTPConfigColumnSequence, e.Sequence()), + handler.NewCol(SMTPConfigColumnState, domain.SMTPConfigStateActive), + }, + []handler.Condition{ + handler.NewCond(SMTPConfigColumnID, id), + handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), + }, + ), ), nil } func (p *smtpConfigProjection) reduceSMTPConfigDeactivated(event eventstore.Event) (*handler.Statement, error) { - e, ok := event.(*instance.SMTPConfigDeactivatedEvent) - if !ok { - return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-hv89j", "reduce.wrong.event.type %s", instance.SMTPConfigDeactivatedEventType) + e, err := assertEvent[*instance.SMTPConfigDeactivatedEvent](event) + if err != nil { + return nil, err } // Deal with old and unique SMTP settings (empty ID) @@ -266,7 +426,6 @@ func (p *smtpConfigProjection) reduceSMTPConfigDeactivated(event eventstore.Even }, []handler.Condition{ handler.NewCond(SMTPConfigColumnID, id), - handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), }, ), nil @@ -288,7 +447,6 @@ func (p *smtpConfigProjection) reduceSMTPConfigRemoved(event eventstore.Event) ( e, []handler.Condition{ handler.NewCond(SMTPConfigColumnID, id), - handler.NewCond(SMTPConfigColumnResourceOwner, e.Aggregate().ResourceOwner), handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), }, ), nil diff --git a/internal/query/projection/smtp_test.go b/internal/query/projection/smtp_test.go index 4d7b5e4a99..135caade07 100644 --- a/internal/query/projection/smtp_test.go +++ b/internal/query/projection/smtp_test.go @@ -28,19 +28,20 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { instance.SMTPConfigChangedEventType, instance.AggregateType, []byte(`{ + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id", "description": "test", "tls": true, "senderAddress": "sender", "senderName": "name", "replyToAddress": "reply-to", "host": "host", - "user": "user", - "id": "44444", - "resource_owner": "ro-id", - "instance_id": "instance-id" + "user": "user" }`, ), - ), instance.SMTPConfigChangedEventMapper), + ), eventstore.GenericEventMapper[instance.SMTPConfigChangedEvent]), }, reduce: (&smtpConfigProjection{}).reduceSMTPConfigChanged, want: wantReduce{ @@ -49,19 +50,233 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.smtp_configs2 SET (change_date, sequence, tls, sender_address, sender_name, reply_to_address, host, username, description) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (resource_owner = $11) AND (instance_id = $12)", + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), + "test", + "config-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.smtp_configs3_smtp SET (tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)", + expectedArgs: []interface{}{ true, "sender", "name", "reply-to", "host", "user", + "config-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceSMTPConfigChanged, description", + args: args{ + event: getEvent( + testEvent( + instance.SMTPConfigChangedEventType, + instance.AggregateType, + []byte(`{ + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id", + "description": "test" + }`, + ), + ), eventstore.GenericEventMapper[instance.SMTPConfigChangedEvent]), + }, + reduce: (&smtpConfigProjection{}).reduceSMTPConfigChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), "test", - "44444", - "ro-id", + "config-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceSMTPConfigChanged, senderAddress", + args: args{ + event: getEvent( + testEvent( + instance.SMTPConfigChangedEventType, + instance.AggregateType, + []byte(`{ + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id", + "senderAddress": "sender" + }`, + ), + ), eventstore.GenericEventMapper[instance.SMTPConfigChangedEvent]), + }, + reduce: (&smtpConfigProjection{}).reduceSMTPConfigChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "config-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.smtp_configs3_smtp SET sender_address = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "sender", + "config-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceSMTPConfigHTTPChanged", + args: args{ + event: getEvent( + testEvent( + instance.SMTPConfigHTTPChangedEventType, + instance.AggregateType, + []byte(`{ + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id", + "description": "test", + "endpoint": "endpoint" + }`, + ), + ), eventstore.GenericEventMapper[instance.SMTPConfigHTTPChangedEvent]), + }, + reduce: (&smtpConfigProjection{}).reduceSMTPConfigHTTPChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "test", + "config-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.smtp_configs3_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "endpoint", + "config-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceSMTPConfigHTTPChanged, description", + args: args{ + event: getEvent( + testEvent( + instance.SMTPConfigHTTPChangedEventType, + instance.AggregateType, + []byte(`{ + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id", + "description": "test" + }`, + ), + ), eventstore.GenericEventMapper[instance.SMTPConfigHTTPChangedEvent]), + }, + reduce: (&smtpConfigProjection{}).reduceSMTPConfigHTTPChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "test", + "config-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceSMTPConfigHTTPChanged, endpoint", + args: args{ + event: getEvent( + testEvent( + instance.SMTPConfigHTTPChangedEventType, + instance.AggregateType, + []byte(`{ + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id", + "endpoint": "endpoint" + }`, + ), + ), eventstore.GenericEventMapper[instance.SMTPConfigHTTPChangedEvent]), + }, + reduce: (&smtpConfigProjection{}).reduceSMTPConfigHTTPChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "config-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.smtp_configs3_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + "endpoint", + "config-id", "instance-id", }, }, @@ -77,9 +292,12 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { instance.SMTPConfigAddedEventType, instance.AggregateType, []byte(`{ - "tls": true, - "id": "id", + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id", "description": "test", + "tls": true, "senderAddress": "sender", "senderName": "name", "replyToAddress": "reply-to", @@ -91,7 +309,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { "keyId": "key-id" } }`), - ), instance.SMTPConfigAddedEventMapper), + ), eventstore.GenericEventMapper[instance.SMTPConfigAddedEvent]), }, reduce: (&smtpConfigProjection{}).reduceSMTPConfigAdded, want: wantReduce{ @@ -100,14 +318,24 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.smtp_configs2 (creation_date, change_date, resource_owner, instance_id, sequence, id, tls, sender_address, sender_name, reply_to_address, host, username, password, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)", + expectedStmt: "INSERT INTO projections.smtp_configs3 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, - "ro-id", "instance-id", + "ro-id", + "agg-id", + "config-id", uint64(15), - "id", + domain.SMTPConfigStateInactive, + "test", + }, + }, + { + expectedStmt: "INSERT INTO projections.smtp_configs3_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedArgs: []interface{}{ + "instance-id", + "config-id", true, "sender", "name", @@ -115,10 +343,58 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { "host", "user", anyArg{}, - domain.SMTPConfigState(3), + }, + }, + }, + }, + }, + }, + { + name: "reduceSMTPConfigHTTPAdded", + args: args{ + event: getEvent( + testEvent( + instance.SMTPConfigHTTPAddedEventType, + instance.AggregateType, + []byte(`{ + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id", + "description": "test", + "senderAddress": "sender", + "endpoint": "endpoint" + }`), + ), eventstore.GenericEventMapper[instance.SMTPConfigHTTPAddedEvent]), + }, + reduce: (&smtpConfigProjection{}).reduceSMTPConfigHTTPAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO projections.smtp_configs3 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedArgs: []interface{}{ + anyArg{}, + anyArg{}, + "instance-id", + "ro-id", + "agg-id", + "config-id", + uint64(15), + domain.SMTPConfigStateInactive, "test", }, }, + { + expectedStmt: "INSERT INTO projections.smtp_configs3_http (instance_id, id, endpoint) VALUES ($1, $2, $3)", + expectedArgs: []interface{}{ + "instance-id", + "config-id", + "endpoint", + }, + }, }, }, }, @@ -130,9 +406,12 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { instance.SMTPConfigActivatedEventType, instance.AggregateType, []byte(`{ - "id": "config-id" + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id" }`), - ), instance.SMTPConfigActivatedEventMapper), + ), eventstore.GenericEventMapper[instance.SMTPConfigActivatedEvent]), }, reduce: (&smtpConfigProjection{}).reduceSMTPConfigActivated, want: wantReduce{ @@ -141,13 +420,23 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.smtp_configs2 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (resource_owner = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.SMTPConfigStateInactive, + "config-id", + domain.SMTPConfigStateActive, + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), domain.SMTPConfigStateActive, "config-id", - "ro-id", "instance-id", }, }, @@ -162,9 +451,12 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { instance.SMTPConfigDeactivatedEventType, instance.AggregateType, []byte(`{ - "id": "config-id" + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id" }`), - ), instance.SMTPConfigDeactivatedEventMapper), + ), eventstore.GenericEventMapper[instance.SMTPConfigDeactivatedEvent]), }, reduce: (&smtpConfigProjection{}).reduceSMTPConfigDeactivated, want: wantReduce{ @@ -173,13 +465,12 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.smtp_configs2 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (resource_owner = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), domain.SMTPConfigStateInactive, "config-id", - "ro-id", "instance-id", }, }, @@ -195,14 +486,17 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { instance.SMTPConfigPasswordChangedEventType, instance.AggregateType, []byte(`{ - "id": "config-id", + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id", "password": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" } }`), - ), instance.SMTPConfigPasswordChangedEventMapper), + ), eventstore.GenericEventMapper[instance.SMTPConfigPasswordChangedEvent]), }, reduce: (&smtpConfigProjection{}).reduceSMTPConfigPasswordChanged, want: wantReduce{ @@ -211,13 +505,19 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.smtp_configs2 SET (change_date, sequence, password) = ($1, $2, $3) WHERE (id = $4) AND (resource_owner = $5) AND (instance_id = $6)", + expectedStmt: "UPDATE projections.smtp_configs3_smtp SET password = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedArgs: []interface{}{ + anyArg{}, + "config-id", + "instance-id", + }, + }, + { + expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), - anyArg{}, "config-id", - "ro-id", "instance-id", }, }, @@ -231,8 +531,13 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { event: getEvent(testEvent( instance.SMTPConfigRemovedEventType, instance.AggregateType, - []byte(`{ "id": "config-id"}`), - ), instance.SMTPConfigRemovedEventMapper), + []byte(`{ + "instance_id": "instance-id", + "resource_owner": "ro-id", + "aggregate_id": "agg-id", + "id": "config-id" +}`), + ), eventstore.GenericEventMapper[instance.SMTPConfigRemovedEvent]), }, reduce: (&smtpConfigProjection{}).reduceSMTPConfigRemoved, want: wantReduce{ @@ -241,10 +546,9 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.smtp_configs2 WHERE (id = $1) AND (resource_owner = $2) AND (instance_id = $3)", + expectedStmt: "DELETE FROM projections.smtp_configs3 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "config-id", - "ro-id", "instance-id", }, }, @@ -269,7 +573,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.smtp_configs2 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.smtp_configs3 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, diff --git a/internal/query/sms.go b/internal/query/sms.go index 7aa4be2310..6f0555634f 100644 --- a/internal/query/sms.go +++ b/internal/query/sms.go @@ -256,7 +256,7 @@ func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu &twilioConfig.token, &twilioConfig.senderNumber, - &httpConfig.smsID, + &httpConfig.id, &httpConfig.endpoint, ) @@ -268,7 +268,7 @@ func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu } twilioConfig.set(config) - httpConfig.set(config) + httpConfig.setSMS(config) return config, nil } @@ -322,7 +322,7 @@ func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB &twilioConfig.token, &twilioConfig.senderNumber, - &httpConfig.smsID, + &httpConfig.id, &httpConfig.endpoint, &configs.Count, @@ -333,7 +333,7 @@ func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB } twilioConfig.set(config) - httpConfig.set(config) + httpConfig.setSMS(config) configs.Configs = append(configs.Configs, config) } @@ -361,12 +361,12 @@ func (c sqlTwilioConfig) set(smsConfig *SMSConfig) { } type sqlHTTPConfig struct { - smsID sql.NullString + id sql.NullString endpoint sql.NullString } -func (c sqlHTTPConfig) set(smsConfig *SMSConfig) { - if !c.smsID.Valid { +func (c sqlHTTPConfig) setSMS(smsConfig *SMSConfig) { + if !c.id.Valid { return } smsConfig.HTTPConfig = &HTTP{ diff --git a/internal/query/smtp.go b/internal/query/smtp.go index b9dd3e0bff..7c45fe33fe 100644 --- a/internal/query/smtp.go +++ b/internal/query/smtp.go @@ -52,34 +52,6 @@ var ( name: projection.SMTPConfigColumnSequence, table: smtpConfigsTable, } - SMTPConfigColumnTLS = Column{ - name: projection.SMTPConfigColumnTLS, - table: smtpConfigsTable, - } - SMTPConfigColumnSenderAddress = Column{ - name: projection.SMTPConfigColumnSenderAddress, - table: smtpConfigsTable, - } - SMTPConfigColumnSenderName = Column{ - name: projection.SMTPConfigColumnSenderName, - table: smtpConfigsTable, - } - SMTPConfigColumnReplyToAddress = Column{ - name: projection.SMTPConfigColumnReplyToAddress, - table: smtpConfigsTable, - } - SMTPConfigColumnSMTPHost = Column{ - name: projection.SMTPConfigColumnSMTPHost, - table: smtpConfigsTable, - } - SMTPConfigColumnSMTPUser = Column{ - name: projection.SMTPConfigColumnSMTPUser, - table: smtpConfigsTable, - } - SMTPConfigColumnSMTPPassword = Column{ - name: projection.SMTPConfigColumnSMTPPassword, - table: smtpConfigsTable, - } SMTPConfigColumnID = Column{ name: projection.SMTPConfigColumnID, table: smtpConfigsTable, @@ -92,13 +64,82 @@ var ( name: projection.SMTPConfigColumnDescription, table: smtpConfigsTable, } + + smtpConfigsSMTPTable = table{ + name: projection.SMTPConfigTable, + instanceIDCol: projection.SMTPConfigColumnInstanceID, + } + SMTPConfigSMTPColumnInstanceID = Column{ + name: projection.SMTPConfigColumnInstanceID, + table: smtpConfigsSMTPTable, + } + SMTPConfigSMTPColumnID = Column{ + name: projection.SMTPConfigColumnID, + table: smtpConfigsSMTPTable, + } + SMTPConfigSMTPColumnTLS = Column{ + name: projection.SMTPConfigSMTPColumnTLS, + table: smtpConfigsSMTPTable, + } + SMTPConfigSMTPColumnSenderAddress = Column{ + name: projection.SMTPConfigSMTPColumnSenderAddress, + table: smtpConfigsSMTPTable, + } + SMTPConfigSMTPColumnSenderName = Column{ + name: projection.SMTPConfigSMTPColumnSenderName, + table: smtpConfigsSMTPTable, + } + SMTPConfigSMTPColumnReplyToAddress = Column{ + name: projection.SMTPConfigSMTPColumnReplyToAddress, + table: smtpConfigsSMTPTable, + } + SMTPConfigSMTPColumnHost = Column{ + name: projection.SMTPConfigSMTPColumnHost, + table: smtpConfigsSMTPTable, + } + SMTPConfigSMTPColumnUser = Column{ + name: projection.SMTPConfigSMTPColumnUser, + table: smtpConfigsSMTPTable, + } + SMTPConfigSMTPColumnPassword = Column{ + name: projection.SMTPConfigSMTPColumnPassword, + table: smtpConfigsSMTPTable, + } + + smtpConfigsHTTPTable = table{ + name: projection.SMTPConfigHTTPTable, + instanceIDCol: projection.SMTPConfigHTTPColumnInstanceID, + } + SMTPConfigHTTPColumnInstanceID = Column{ + name: projection.SMTPConfigHTTPColumnInstanceID, + table: smtpConfigsHTTPTable, + } + SMTPConfigHTTPColumnID = Column{ + name: projection.SMTPConfigHTTPColumnID, + table: smtpConfigsHTTPTable, + } + SMTPConfigHTTPColumnEndpoint = Column{ + name: projection.SMTPConfigHTTPColumnEndpoint, + table: smtpConfigsHTTPTable, + } ) type SMTPConfig struct { - CreationDate time.Time - ChangeDate time.Time - ResourceOwner string - Sequence uint64 + CreationDate time.Time + ChangeDate time.Time + ResourceOwner string + AggregateID string + ID string + Sequence uint64 + Description string + + SMTPConfig *SMTP + HTTPConfig *HTTP + + State domain.SMTPConfigState +} + +type SMTP struct { TLS bool SenderAddress string SenderName string @@ -106,9 +147,6 @@ type SMTPConfig struct { Host string User string Password *crypto.CryptoValue - ID string - State domain.SMTPConfigState - Description string } func (q *Queries) SMTPConfigActive(ctx context.Context, resourceOwner string) (config *SMTPConfig, err error) { @@ -132,15 +170,14 @@ func (q *Queries) SMTPConfigActive(ctx context.Context, resourceOwner string) (c return config, err } -func (q *Queries) SMTPConfigByID(ctx context.Context, instanceID, resourceOwner, id string) (config *SMTPConfig, err error) { +func (q *Queries) SMTPConfigByID(ctx context.Context, instanceID, id string) (config *SMTPConfig, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareSMTPConfigQuery(ctx, q.client) query, args, err := stmt.Where(sq.Eq{ - SMTPConfigColumnResourceOwner.identifier(): resourceOwner, - SMTPConfigColumnInstanceID.identifier(): instanceID, - SMTPConfigColumnID.identifier(): id, + SMTPConfigColumnInstanceID.identifier(): instanceID, + SMTPConfigColumnID.identifier(): id, }).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-8f8gw", "Errors.Query.SQLStatement") @@ -161,35 +198,49 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB SMTPConfigColumnChangeDate.identifier(), SMTPConfigColumnResourceOwner.identifier(), SMTPConfigColumnSequence.identifier(), - SMTPConfigColumnTLS.identifier(), - SMTPConfigColumnSenderAddress.identifier(), - SMTPConfigColumnSenderName.identifier(), - SMTPConfigColumnReplyToAddress.identifier(), - SMTPConfigColumnSMTPHost.identifier(), - SMTPConfigColumnSMTPUser.identifier(), - SMTPConfigColumnSMTPPassword.identifier(), SMTPConfigColumnID.identifier(), SMTPConfigColumnState.identifier(), - SMTPConfigColumnDescription.identifier()). - From(smtpConfigsTable.identifier() + db.Timetravel(call.Took(ctx))). + SMTPConfigColumnDescription.identifier(), + + SMTPConfigSMTPColumnID.identifier(), + SMTPConfigSMTPColumnTLS.identifier(), + SMTPConfigSMTPColumnSenderAddress.identifier(), + SMTPConfigSMTPColumnSenderName.identifier(), + SMTPConfigSMTPColumnReplyToAddress.identifier(), + SMTPConfigSMTPColumnHost.identifier(), + SMTPConfigSMTPColumnUser.identifier(), + SMTPConfigSMTPColumnPassword.identifier(), + + SMTPConfigHTTPColumnID.identifier(), + SMTPConfigHTTPColumnEndpoint.identifier()). + From(smtpConfigsTable.identifier()). + LeftJoin(join(SMTPConfigSMTPColumnID, SMTPConfigColumnID)). + LeftJoin(join(SMTPConfigHTTPColumnID, SMTPConfigColumnID) + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*SMTPConfig, error) { config := new(SMTPConfig) + var ( + smtpConfig = sqlSmtpConfig{} + httpConfig = sqlHTTPConfig{} + ) err := row.Scan( &config.CreationDate, &config.ChangeDate, &config.ResourceOwner, &config.Sequence, - &config.TLS, - &config.SenderAddress, - &config.SenderName, - &config.ReplyToAddress, - &config.Host, - &config.User, - &password, &config.ID, &config.State, &config.Description, + &smtpConfig.id, + &smtpConfig.tls, + &smtpConfig.senderAddress, + &smtpConfig.senderName, + &smtpConfig.replyToAddress, + &smtpConfig.host, + &smtpConfig.user, + &password, + &httpConfig.id, + &httpConfig.endpoint, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -197,7 +248,9 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB } return nil, zerrors.ThrowInternal(err, "QUERY-9k87F", "Errors.Internal") } - config.Password = password + smtpConfig.password = password + smtpConfig.set(config) + httpConfig.setSMTP(config) return config, nil } } @@ -208,38 +261,53 @@ func prepareSMTPConfigsQuery(ctx context.Context, db prepareDatabase) (sq.Select SMTPConfigColumnChangeDate.identifier(), SMTPConfigColumnResourceOwner.identifier(), SMTPConfigColumnSequence.identifier(), - SMTPConfigColumnTLS.identifier(), - SMTPConfigColumnSenderAddress.identifier(), - SMTPConfigColumnSenderName.identifier(), - SMTPConfigColumnReplyToAddress.identifier(), - SMTPConfigColumnSMTPHost.identifier(), - SMTPConfigColumnSMTPUser.identifier(), - SMTPConfigColumnSMTPPassword.identifier(), SMTPConfigColumnID.identifier(), SMTPConfigColumnState.identifier(), SMTPConfigColumnDescription.identifier(), - countColumn.identifier()). - From(smtpConfigsTable.identifier() + db.Timetravel(call.Took(ctx))). + + SMTPConfigSMTPColumnID.identifier(), + SMTPConfigSMTPColumnTLS.identifier(), + SMTPConfigSMTPColumnSenderAddress.identifier(), + SMTPConfigSMTPColumnSenderName.identifier(), + SMTPConfigSMTPColumnReplyToAddress.identifier(), + SMTPConfigSMTPColumnHost.identifier(), + SMTPConfigSMTPColumnUser.identifier(), + SMTPConfigSMTPColumnPassword.identifier(), + + SMTPConfigHTTPColumnID.identifier(), + SMTPConfigHTTPColumnEndpoint.identifier(), + countColumn.identifier(), + ).From(smtpConfigsTable.identifier()). + LeftJoin(join(SMTPConfigSMTPColumnID, SMTPConfigColumnID)). + LeftJoin(join(SMTPConfigHTTPColumnID, SMTPConfigColumnID) + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(rows *sql.Rows) (*SMTPConfigs, error) { configs := &SMTPConfigs{Configs: []*SMTPConfig{}} for rows.Next() { config := new(SMTPConfig) + password := new(crypto.CryptoValue) + var ( + smtpConfig = sqlSmtpConfig{} + httpConfig = sqlHTTPConfig{} + ) err := rows.Scan( &config.CreationDate, &config.ChangeDate, &config.ResourceOwner, &config.Sequence, - &config.TLS, - &config.SenderAddress, - &config.SenderName, - &config.ReplyToAddress, - &config.Host, - &config.User, - &config.Password, &config.ID, &config.State, &config.Description, + &smtpConfig.id, + &smtpConfig.tls, + &smtpConfig.senderAddress, + &smtpConfig.senderName, + &smtpConfig.replyToAddress, + &smtpConfig.host, + &smtpConfig.user, + &password, + &httpConfig.id, + &httpConfig.endpoint, &configs.Count, ) if err != nil { @@ -248,6 +316,9 @@ func prepareSMTPConfigsQuery(ctx context.Context, db prepareDatabase) (sq.Select } return nil, zerrors.ThrowInternal(err, "QUERY-9k87F", "Errors.Internal") } + smtpConfig.password = password + smtpConfig.set(config) + httpConfig.setSMTP(config) configs.Configs = append(configs.Configs, config) } return configs, nil @@ -277,3 +348,38 @@ func (q *Queries) SearchSMTPConfigs(ctx context.Context, queries *SMTPConfigsSea configs.State, err = q.latestState(ctx, smsConfigsTable) return configs, err } + +type sqlSmtpConfig struct { + id sql.NullString + tls sql.NullBool + senderAddress sql.NullString + senderName sql.NullString + replyToAddress sql.NullString + host sql.NullString + user sql.NullString + password *crypto.CryptoValue +} + +func (c sqlSmtpConfig) set(smtpConfig *SMTPConfig) { + if !c.id.Valid { + return + } + smtpConfig.SMTPConfig = &SMTP{ + TLS: c.tls.Bool, + SenderAddress: c.senderAddress.String, + SenderName: c.senderName.String, + ReplyToAddress: c.replyToAddress.String, + Host: c.host.String, + User: c.user.String, + Password: c.password, + } +} + +func (c sqlHTTPConfig) setSMTP(smtpConfig *SMTPConfig) { + if !c.id.Valid { + return + } + smtpConfig.HTTPConfig = &HTTP{ + Endpoint: c.endpoint.String, + } +} diff --git a/internal/query/smtp_test.go b/internal/query/smtp_test.go index e1824fd277..080fc951c1 100644 --- a/internal/query/smtp_test.go +++ b/internal/query/smtp_test.go @@ -14,27 +14,36 @@ import ( ) var ( - prepareSMTPConfigStmt = `SELECT projections.smtp_configs2.creation_date,` + - ` projections.smtp_configs2.change_date,` + - ` projections.smtp_configs2.resource_owner,` + - ` projections.smtp_configs2.sequence,` + - ` projections.smtp_configs2.tls,` + - ` projections.smtp_configs2.sender_address,` + - ` projections.smtp_configs2.sender_name,` + - ` projections.smtp_configs2.reply_to_address,` + - ` projections.smtp_configs2.host,` + - ` projections.smtp_configs2.username,` + - ` projections.smtp_configs2.password,` + - ` projections.smtp_configs2.id,` + - ` projections.smtp_configs2.state,` + - ` projections.smtp_configs2.description` + - ` FROM projections.smtp_configs2` + + prepareSMTPConfigStmt = `SELECT projections.smtp_configs3.creation_date,` + + ` projections.smtp_configs3.change_date,` + + ` projections.smtp_configs3.resource_owner,` + + ` projections.smtp_configs3.sequence,` + + ` projections.smtp_configs3.id,` + + ` projections.smtp_configs3.state,` + + ` projections.smtp_configs3.description,` + + ` projections.smtp_configs3_smtp.id,` + + ` projections.smtp_configs3_smtp.tls,` + + ` projections.smtp_configs3_smtp.sender_address,` + + ` projections.smtp_configs3_smtp.sender_name,` + + ` projections.smtp_configs3_smtp.reply_to_address,` + + ` projections.smtp_configs3_smtp.host,` + + ` projections.smtp_configs3_smtp.username,` + + ` projections.smtp_configs3_smtp.password,` + + ` projections.smtp_configs3_http.id,` + + ` projections.smtp_configs3_http.endpoint` + + ` FROM projections.smtp_configs3` + + ` LEFT JOIN projections.smtp_configs3_smtp ON projections.smtp_configs3.id = projections.smtp_configs3_smtp.id AND projections.smtp_configs3.instance_id = projections.smtp_configs3_smtp.instance_id` + + ` LEFT JOIN projections.smtp_configs3_http ON projections.smtp_configs3.id = projections.smtp_configs3_http.id AND projections.smtp_configs3.instance_id = projections.smtp_configs3_http.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` prepareSMTPConfigCols = []string{ "creation_date", "change_date", "resource_owner", "sequence", + "id", + "state", + "description", + "id", "tls", "sender_address", "sender_name", @@ -43,8 +52,7 @@ var ( "smtp_user", "smtp_password", "id", - "state", - "description", + "endpoint", } ) @@ -89,6 +97,10 @@ func Test_SMTPConfigsPrepares(t *testing.T) { testNow, "ro", uint64(20211108), + "2232323", + domain.SMTPConfigStateActive, + "test", + "2232323", true, "sender", "name", @@ -96,27 +108,69 @@ func Test_SMTPConfigsPrepares(t *testing.T) { "host", "user", &crypto.CryptoValue{}, - "2232323", - domain.SMTPConfigStateActive, - "test", + nil, + nil, }, ), }, object: &SMTPConfig{ - CreationDate: testNow, - ChangeDate: testNow, - ResourceOwner: "ro", - Sequence: 20211108, - TLS: true, - SenderAddress: "sender", - SenderName: "name", - ReplyToAddress: "reply-to", - Host: "host", - User: "user", - Password: &crypto.CryptoValue{}, - ID: "2232323", - State: domain.SMTPConfigStateActive, - Description: "test", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211108, + SMTPConfig: &SMTP{ + TLS: true, + SenderAddress: "sender", + SenderName: "name", + ReplyToAddress: "reply-to", + Host: "host", + User: "user", + Password: &crypto.CryptoValue{}, + }, + ID: "2232323", + State: domain.SMTPConfigStateActive, + Description: "test", + }, + }, + { + name: "prepareSMTPConfigQuery found, http", + prepare: prepareSMTPConfigQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(prepareSMTPConfigStmt), + prepareSMTPConfigCols, + []driver.Value{ + testNow, + testNow, + "ro", + uint64(20211108), + "2232323", + domain.SMTPConfigStateActive, + "test", + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + "2232323", + "endpoint", + }, + ), + }, + object: &SMTPConfig{ + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211108, + HTTPConfig: &HTTP{ + Endpoint: "endpoint", + }, + ID: "2232323", + State: domain.SMTPConfigStateActive, + Description: "test", }, }, { @@ -131,6 +185,10 @@ func Test_SMTPConfigsPrepares(t *testing.T) { testNow, "ro", uint64(20211109), + "44442323", + domain.SMTPConfigStateInactive, + "test2", + "44442323", true, "sender2", "name2", @@ -138,27 +196,28 @@ func Test_SMTPConfigsPrepares(t *testing.T) { "host2", "user2", &crypto.CryptoValue{}, - "44442323", - domain.SMTPConfigStateInactive, - "test2", + nil, + nil, }, ), }, object: &SMTPConfig{ - CreationDate: testNow, - ChangeDate: testNow, - ResourceOwner: "ro", - Sequence: 20211109, - TLS: true, - SenderAddress: "sender2", - SenderName: "name2", - ReplyToAddress: "reply-to2", - Host: "host2", - User: "user2", - Password: &crypto.CryptoValue{}, - ID: "44442323", - State: domain.SMTPConfigStateInactive, - Description: "test2", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211109, + SMTPConfig: &SMTP{ + TLS: true, + SenderAddress: "sender2", + SenderName: "name2", + ReplyToAddress: "reply-to2", + Host: "host2", + User: "user2", + Password: &crypto.CryptoValue{}, + }, + ID: "44442323", + State: domain.SMTPConfigStateInactive, + Description: "test2", }, }, { @@ -173,6 +232,10 @@ func Test_SMTPConfigsPrepares(t *testing.T) { testNow, "ro", uint64(20211109), + "23234444", + domain.SMTPConfigStateInactive, + "test3", + "23234444", true, "sender3", "name3", @@ -180,27 +243,28 @@ func Test_SMTPConfigsPrepares(t *testing.T) { "host3", "user3", &crypto.CryptoValue{}, - "23234444", - domain.SMTPConfigStateInactive, - "test3", + nil, + nil, }, ), }, object: &SMTPConfig{ - CreationDate: testNow, - ChangeDate: testNow, - ResourceOwner: "ro", - Sequence: 20211109, - TLS: true, - SenderAddress: "sender3", - SenderName: "name3", - ReplyToAddress: "reply-to3", - Host: "host3", - User: "user3", - Password: &crypto.CryptoValue{}, - ID: "23234444", - State: domain.SMTPConfigStateInactive, - Description: "test3", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211109, + SMTPConfig: &SMTP{ + TLS: true, + SenderAddress: "sender3", + SenderName: "name3", + ReplyToAddress: "reply-to3", + Host: "host3", + User: "user3", + Password: &crypto.CryptoValue{}, + }, + ID: "23234444", + State: domain.SMTPConfigStateInactive, + Description: "test3", }, }, { diff --git a/internal/repository/instance/eventstore.go b/internal/repository/instance/eventstore.go index d88793a399..68621597a8 100644 --- a/internal/repository/instance/eventstore.go +++ b/internal/repository/instance/eventstore.go @@ -12,12 +12,14 @@ func init() { eventstore.RegisterFilterEventMapper(AggregateType, SecretGeneratorAddedEventType, SecretGeneratorAddedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SecretGeneratorChangedEventType, SecretGeneratorChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SecretGeneratorRemovedEventType, SecretGeneratorRemovedEventMapper) - eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigAddedEventType, SMTPConfigAddedEventMapper) - eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigChangedEventType, SMTPConfigChangedEventMapper) - eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigActivatedEventType, SMTPConfigActivatedEventMapper) - eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigDeactivatedEventType, SMTPConfigDeactivatedEventMapper) - eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigPasswordChangedEventType, SMTPConfigPasswordChangedEventMapper) - eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigRemovedEventType, SMTPConfigRemovedEventMapper) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigAddedEventType, eventstore.GenericEventMapper[SMTPConfigAddedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigChangedEventType, eventstore.GenericEventMapper[SMTPConfigChangedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigActivatedEventType, eventstore.GenericEventMapper[SMTPConfigActivatedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigDeactivatedEventType, eventstore.GenericEventMapper[SMTPConfigDeactivatedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigPasswordChangedEventType, eventstore.GenericEventMapper[SMTPConfigPasswordChangedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigHTTPAddedEventType, eventstore.GenericEventMapper[SMTPConfigHTTPAddedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigHTTPChangedEventType, eventstore.GenericEventMapper[SMTPConfigHTTPChangedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigRemovedEventType, eventstore.GenericEventMapper[SMTPConfigRemovedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioAddedEventType, eventstore.GenericEventMapper[SMSConfigTwilioAddedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioChangedEventType, eventstore.GenericEventMapper[SMSConfigTwilioChangedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioTokenChangedEventType, eventstore.GenericEventMapper[SMSConfigTwilioTokenChangedEvent]) diff --git a/internal/repository/instance/smtp_config.go b/internal/repository/instance/smtp_config.go index 3f08fc8e8a..403cc569a3 100644 --- a/internal/repository/instance/smtp_config.go +++ b/internal/repository/instance/smtp_config.go @@ -10,16 +10,19 @@ import ( const ( smtpConfigPrefix = "smtp.config." + httpConfigPrefix = "http." SMTPConfigAddedEventType = instanceEventTypePrefix + smtpConfigPrefix + "added" SMTPConfigChangedEventType = instanceEventTypePrefix + smtpConfigPrefix + "changed" SMTPConfigPasswordChangedEventType = instanceEventTypePrefix + smtpConfigPrefix + "password.changed" + SMTPConfigHTTPAddedEventType = instanceEventTypePrefix + smtpConfigPrefix + httpConfigPrefix + "added" + SMTPConfigHTTPChangedEventType = instanceEventTypePrefix + smtpConfigPrefix + httpConfigPrefix + "changed" SMTPConfigRemovedEventType = instanceEventTypePrefix + smtpConfigPrefix + "removed" SMTPConfigActivatedEventType = instanceEventTypePrefix + smtpConfigPrefix + "activated" SMTPConfigDeactivatedEventType = instanceEventTypePrefix + smtpConfigPrefix + "deactivated" ) type SMTPConfigAddedEvent struct { - eventstore.BaseEvent `json:"-"` + *eventstore.BaseEvent `json:"-"` ID string `json:"id,omitempty"` Description string `json:"description,omitempty"` @@ -45,7 +48,7 @@ func NewSMTPConfigAddedEvent( password *crypto.CryptoValue, ) *SMTPConfigAddedEvent { return &SMTPConfigAddedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, SMTPConfigAddedEventType, @@ -61,6 +64,9 @@ func NewSMTPConfigAddedEvent( Password: password, } } +func (e *SMTPConfigAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} func (e *SMTPConfigAddedEvent) Payload() interface{} { return e @@ -70,29 +76,21 @@ func (e *SMTPConfigAddedEvent) UniqueConstraints() []*eventstore.UniqueConstrain return nil } -func SMTPConfigAddedEventMapper(event eventstore.Event) (eventstore.Event, error) { - smtpConfigAdded := &SMTPConfigAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := event.Unmarshal(smtpConfigAdded) - if err != nil { - return nil, zerrors.ThrowInternal(err, "IAM-39fks", "unable to unmarshal smtp config added") - } - - return smtpConfigAdded, nil +type SMTPConfigChangedEvent struct { + *eventstore.BaseEvent `json:"-"` + ID string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + FromAddress *string `json:"senderAddress,omitempty"` + FromName *string `json:"senderName,omitempty"` + ReplyToAddress *string `json:"replyToAddress,omitempty"` + TLS *bool `json:"tls,omitempty"` + Host *string `json:"host,omitempty"` + User *string `json:"user,omitempty"` + Password *crypto.CryptoValue `json:"password,omitempty"` } -type SMTPConfigChangedEvent struct { - eventstore.BaseEvent `json:"-"` - ID string `json:"id,omitempty"` - Description *string `json:"description,omitempty"` - FromAddress *string `json:"senderAddress,omitempty"` - FromName *string `json:"senderName,omitempty"` - ReplyToAddress *string `json:"replyToAddress,omitempty"` - TLS *bool `json:"tls,omitempty"` - Host *string `json:"host,omitempty"` - User *string `json:"user,omitempty"` - Password *crypto.CryptoValue `json:"password,omitempty"` +func (e *SMTPConfigChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event } func (e *SMTPConfigChangedEvent) Payload() interface{} { @@ -113,7 +111,7 @@ func NewSMTPConfigChangeEvent( return nil, zerrors.ThrowPreconditionFailed(nil, "IAM-o0pWf", "Errors.NoChangesFound") } changeEvent := &SMTPConfigChangedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, SMTPConfigChangedEventType, @@ -182,23 +180,10 @@ func ChangeSMTPConfigSMTPPassword(password *crypto.CryptoValue) func(event *SMTP } } -func SMTPConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, error) { - e := &SMTPConfigChangedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - - err := event.Unmarshal(e) - if err != nil { - return nil, zerrors.ThrowInternal(err, "IAM-m09oo", "unable to unmarshal smtp changed") - } - - return e, nil -} - type SMTPConfigPasswordChangedEvent struct { - eventstore.BaseEvent `json:"-"` - ID string `json:"id,omitempty"` - Password *crypto.CryptoValue `json:"password,omitempty"` + *eventstore.BaseEvent `json:"-"` + ID string `json:"id,omitempty"` + Password *crypto.CryptoValue `json:"password,omitempty"` } func NewSMTPConfigPasswordChangedEvent( @@ -208,7 +193,7 @@ func NewSMTPConfigPasswordChangedEvent( password *crypto.CryptoValue, ) *SMTPConfigPasswordChangedEvent { return &SMTPConfigPasswordChangedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, SMTPConfigPasswordChangedEventType, @@ -217,6 +202,10 @@ func NewSMTPConfigPasswordChangedEvent( } } +func (e *SMTPConfigPasswordChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + func (e *SMTPConfigPasswordChangedEvent) Payload() interface{} { return e } @@ -225,21 +214,109 @@ func (e *SMTPConfigPasswordChangedEvent) UniqueConstraints() []*eventstore.Uniqu return nil } -func SMTPConfigPasswordChangedEventMapper(event eventstore.Event) (eventstore.Event, error) { - smtpConfigPasswordChanged := &SMTPConfigPasswordChangedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := event.Unmarshal(smtpConfigPasswordChanged) - if err != nil { - return nil, zerrors.ThrowInternal(err, "IAM-99iNF", "unable to unmarshal smtp config password changed") - } +type SMTPConfigHTTPAddedEvent struct { + *eventstore.BaseEvent `json:"-"` - return smtpConfigPasswordChanged, nil + ID string `json:"id,omitempty"` + Description string `json:"description,omitempty"` + Endpoint string `json:"endpoint,omitempty"` +} + +func NewSMTPConfigHTTPAddedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id, description string, + endpoint string, +) *SMTPConfigHTTPAddedEvent { + return &SMTPConfigHTTPAddedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + SMTPConfigHTTPAddedEventType, + ), + ID: id, + Description: description, + Endpoint: endpoint, + } +} + +func (e *SMTPConfigHTTPAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + +func (e *SMTPConfigHTTPAddedEvent) Payload() interface{} { + return e +} + +func (e *SMTPConfigHTTPAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +type SMTPConfigHTTPChangedEvent struct { + *eventstore.BaseEvent `json:"-"` + ID string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + Endpoint *string `json:"endpoint,omitempty"` +} + +func (e *SMTPConfigHTTPChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + +func (e *SMTPConfigHTTPChangedEvent) Payload() interface{} { + return e +} + +func (e *SMTPConfigHTTPChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func NewSMTPConfigHTTPChangeEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id string, + changes []SMTPConfigHTTPChanges, +) (*SMTPConfigHTTPChangedEvent, error) { + if len(changes) == 0 { + return nil, zerrors.ThrowPreconditionFailed(nil, "IAM-o0pWf", "Errors.NoChangesFound") + } + changeEvent := &SMTPConfigHTTPChangedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + SMTPConfigHTTPChangedEventType, + ), + ID: id, + } + for _, change := range changes { + change(changeEvent) + } + return changeEvent, nil +} + +type SMTPConfigHTTPChanges func(event *SMTPConfigHTTPChangedEvent) + +func ChangeSMTPConfigHTTPID(id string) func(event *SMTPConfigHTTPChangedEvent) { + return func(e *SMTPConfigHTTPChangedEvent) { + e.ID = id + } +} + +func ChangeSMTPConfigHTTPDescription(description string) func(event *SMTPConfigHTTPChangedEvent) { + return func(e *SMTPConfigHTTPChangedEvent) { + e.Description = &description + } +} + +func ChangeSMTPConfigHTTPEndpoint(endpoint string) func(event *SMTPConfigHTTPChangedEvent) { + return func(e *SMTPConfigHTTPChangedEvent) { + e.Endpoint = &endpoint + } } type SMTPConfigActivatedEvent struct { - eventstore.BaseEvent `json:"-"` - ID string `json:"id,omitempty"` + *eventstore.BaseEvent `json:"-"` + ID string `json:"id,omitempty"` } func NewSMTPConfigActivatedEvent( @@ -248,7 +325,7 @@ func NewSMTPConfigActivatedEvent( id string, ) *SMTPConfigActivatedEvent { return &SMTPConfigActivatedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, SMTPConfigActivatedEventType, @@ -257,6 +334,10 @@ func NewSMTPConfigActivatedEvent( } } +func (e *SMTPConfigActivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + func (e *SMTPConfigActivatedEvent) Payload() interface{} { return e } @@ -265,21 +346,9 @@ func (e *SMTPConfigActivatedEvent) UniqueConstraints() []*eventstore.UniqueConst return nil } -func SMTPConfigActivatedEventMapper(event eventstore.Event) (eventstore.Event, error) { - smtpConfigActivated := &SMTPConfigActivatedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := event.Unmarshal(smtpConfigActivated) - if err != nil { - return nil, zerrors.ThrowInternal(err, "IAM-KPr5t", "unable to unmarshal smtp config removed") - } - - return smtpConfigActivated, nil -} - type SMTPConfigDeactivatedEvent struct { - eventstore.BaseEvent `json:"-"` - ID string `json:"id,omitempty"` + *eventstore.BaseEvent `json:"-"` + ID string `json:"id,omitempty"` } func NewSMTPConfigDeactivatedEvent( @@ -288,7 +357,7 @@ func NewSMTPConfigDeactivatedEvent( id string, ) *SMTPConfigDeactivatedEvent { return &SMTPConfigDeactivatedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, SMTPConfigDeactivatedEventType, @@ -297,6 +366,10 @@ func NewSMTPConfigDeactivatedEvent( } } +func (e *SMTPConfigDeactivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + func (e *SMTPConfigDeactivatedEvent) Payload() interface{} { return e } @@ -305,21 +378,9 @@ func (e *SMTPConfigDeactivatedEvent) UniqueConstraints() []*eventstore.UniqueCon return nil } -func SMTPConfigDeactivatedEventMapper(event eventstore.Event) (eventstore.Event, error) { - smtpConfigDeactivated := &SMTPConfigDeactivatedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := event.Unmarshal(smtpConfigDeactivated) - if err != nil { - return nil, zerrors.ThrowInternal(err, "IAM-KPr5t", "unable to unmarshal smtp config removed") - } - - return smtpConfigDeactivated, nil -} - type SMTPConfigRemovedEvent struct { - eventstore.BaseEvent `json:"-"` - ID string `json:"id,omitempty"` + *eventstore.BaseEvent `json:"-"` + ID string `json:"id,omitempty"` } func NewSMTPConfigRemovedEvent( @@ -328,7 +389,7 @@ func NewSMTPConfigRemovedEvent( id string, ) *SMTPConfigRemovedEvent { return &SMTPConfigRemovedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, SMTPConfigRemovedEventType, @@ -337,6 +398,9 @@ func NewSMTPConfigRemovedEvent( } } +func (e *SMTPConfigRemovedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} func (e *SMTPConfigRemovedEvent) Payload() interface{} { return e } @@ -344,15 +408,3 @@ func (e *SMTPConfigRemovedEvent) Payload() interface{} { func (e *SMTPConfigRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { return nil } - -func SMTPConfigRemovedEventMapper(event eventstore.Event) (eventstore.Event, error) { - smtpConfigRemoved := &SMTPConfigRemovedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := event.Unmarshal(smtpConfigRemoved) - if err != nil { - return nil, zerrors.ThrowInternal(err, "IAM-DVw1s", "unable to unmarshal smtp config removed") - } - - return smtpConfigRemoved, nil -} diff --git a/pkg/grpc/settings/settings.go b/pkg/grpc/settings/settings.go index 32b6d83125..3daa4ed141 100644 --- a/pkg/grpc/settings/settings.go +++ b/pkg/grpc/settings/settings.go @@ -1,3 +1,5 @@ package settings type SMSConfig = isSMSProvider_Config + +type EmailConfig = isEmailProvider_Config diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index c9b44a61ef..278421deac 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -143,32 +143,32 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { } security_definitions: { - security: { - key: "BasicAuth"; - value: { - type: TYPE_BASIC; - } - } - security: { - key: "OAuth2"; - value: { - type: TYPE_OAUTH2; - flow: FLOW_ACCESS_CODE; - authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; - token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; - scopes: { - scope: { - key: "openid"; - value: "openid"; - } - scope: { - key: "urn:zitadel:iam:org:project:id:zitadel:aud"; - value: "urn:zitadel:iam:org:project:id:zitadel:aud"; - } - } - } - } - } + security: { + key: "BasicAuth"; + value: { + type: TYPE_BASIC; + } + } + security: { + key: "OAuth2"; + value: { + type: TYPE_OAUTH2; + flow: FLOW_ACCESS_CODE; + authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize"; + token_url: "$CUSTOM-DOMAIN/oauth/v2/token"; + scopes: { + scope: { + key: "openid"; + value: "openid"; + } + scope: { + key: "urn:zitadel:iam:org:project:id:zitadel:aud"; + value: "urn:zitadel:iam:org:project:id:zitadel:aud"; + } + } + } + } + } security: { security_requirement: { key: "OAuth2"; @@ -422,6 +422,11 @@ service AdminService { }; } + // Deprecated: Get active SMTP Configuration + // + // Returns the active SMTP configuration from the system. This is used to send E-Mails to the users. + // + // Deprecated: please move to the new endpoint GetEmailProvider. rpc GetSMTPConfig(GetSMTPConfigRequest) returns (GetSMTPConfigResponse) { option (google.api.http) = { get: "/smtp"; @@ -433,11 +438,15 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP"; - summary: "Get active SMTP Configuration"; - description: "Returns the active SMTP configuration from the system. This is used to send E-Mails to the users." + deprecated: true; }; } + // Deprecated: Get SMTP provider configuration by its id + // + // Get a specific SMTP provider configuration by its ID. + // + // Deprecated: please move to the new endpoint GetEmailProviderById. rpc GetSMTPConfigById(GetSMTPConfigByIdRequest) returns (GetSMTPConfigByIdResponse) { option (google.api.http) = { get: "/smtp/{id}"; @@ -449,11 +458,15 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP"; - summary: "Get SMTP provider configuration by its id"; - description: "Get a specific SMTP provider configuration by its ID."; + deprecated: true; }; } + // Deprecated: Add SMTP Configuration + // + // Add a new SMTP configuration if nothing is set yet. + // + // Deprecated: please move to the new endpoint AddEmailProviderSMTP. rpc AddSMTPConfig(AddSMTPConfigRequest) returns (AddSMTPConfigResponse) { option (google.api.http) = { post: "/smtp"; @@ -466,11 +479,15 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP"; - summary: "Add SMTP Configuration"; - description: "Add a new SMTP configuration if nothing is set yet." + deprecated: true; }; } + // Deprecated: Update SMTP Configuration + // + // Update the SMTP configuration, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP. + // + // Deprecated: please move to the new endpoint UpdateEmailProviderSMTP. rpc UpdateSMTPConfig(UpdateSMTPConfigRequest) returns (UpdateSMTPConfigResponse) { option (google.api.http) = { put: "/smtp/{id}"; @@ -483,11 +500,15 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP"; - summary: "Update SMTP Configuration"; - description: "Update the SMTP configuration, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP." + deprecated: true; }; } + // Deprecated: Update SMTP Password + // + // Update the SMTP password that is used for the host, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP. + // + // Deprecated: please move to the new endpoint UpdateEmailProviderSMTPPassword. rpc UpdateSMTPConfigPassword(UpdateSMTPConfigPasswordRequest) returns (UpdateSMTPConfigPasswordResponse) { option (google.api.http) = { put: "/smtp/{id}/password"; @@ -500,11 +521,15 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP"; - summary: "Update SMTP Password"; - description: "Update the SMTP password that is used for the host, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP." + deprecated: true; }; } + // Deprecated: Activate SMTP Provider + // + // Activate an SMTP provider. + // + // Deprecated: please move to the new endpoint ActivateEmailProvider. rpc ActivateSMTPConfig(ActivateSMTPConfigRequest) returns (ActivateSMTPConfigResponse) { option (google.api.http) = { post: "/smtp/{id}/_activate"; @@ -519,9 +544,15 @@ service AdminService { tags: "SMTP Provider"; summary: "Activate SMTP Provider"; description: "Activate an SMTP provider." + deprecated: true; }; } + // Deprecated: Deactivate SMTP Provider + // + // Deactivate an SMTP provider. After deactivating the provider, the users will not be able to receive SMTP notifications from that provider anymore. + // + // Deprecated: please move to the new endpoint DeactivateEmailProvider. rpc DeactivateSMTPConfig(DeactivateSMTPConfigRequest) returns (DeactivateSMTPConfigResponse) { option (google.api.http) = { post: "/smtp/{id}/_deactivate"; @@ -534,11 +565,15 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP Provider"; - summary: "Deactivate SMTP Provider"; - description: "Deactivate an SMTP provider. After deactivating the provider, the users will not be able to receive SMTP notifications from that provider anymore." + deprecated: true; }; } + // Deprecated: Remove SMTP Configuration + // + // Remove the SMTP configuration, be aware that the users will not get an E-Mail if no SMTP is set. + // + // Deprecated: please move to the new endpoint RemoveEmailProvider. rpc RemoveSMTPConfig(RemoveSMTPConfigRequest) returns (RemoveSMTPConfigResponse) { option (google.api.http) = { delete: "/smtp/{id}"; @@ -550,11 +585,15 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP"; - summary: "Remove SMTP Configuration"; - description: "Remove the SMTP configuration, be aware that the users will not get an E-Mail if no SMTP is set." + deprecated: true; }; } + // Deprecated: Test SMTP Provider + // + // Test an SMTP provider identified by its ID. After testing the provider, the users will receive information about the test results. + // + // Deprecated: please move to the new endpoint TestEmailProviderSMTPById. rpc TestSMTPConfigById(TestSMTPConfigByIdRequest) returns (TestSMTPConfigByIdResponse) { option (google.api.http) = { post: "/smtp/{id}/_test"; @@ -567,11 +606,15 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP Provider"; - summary: "Test SMTP Provider "; - description: "Test an SMTP provider identified by its ID. After testing the provider, the users will receive information about the test results." + deprecated: true; }; } + // Deprecated: Test SMTP Provider + // + // Test an SMTP provider. After testing the provider, the users will receive information about the test results. + // + // Deprecated: please move to the new endpoint TestEmailProviderSMTP. rpc TestSMTPConfig(TestSMTPConfigRequest) returns (TestSMTPConfigResponse) { option (google.api.http) = { post: "/smtp/_test"; @@ -584,11 +627,15 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP Provider"; - summary: "Test SMTP Provider"; - description: "Test an SMTP provider. After testing the provider, the users will receive information about the test results." + deprecated: true; }; } + // Deprecated: List SMTP Configs + // + // Returns a list of SMTP configurations. + // + // Deprecated: please move to the new endpoint ListEmailProviders. rpc ListSMTPConfigs(ListSMTPConfigsRequest) returns (ListSMTPConfigsResponse) { option (google.api.http) = { post: "/smtp/_search" @@ -601,8 +648,225 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "SMTP Configs"; - summary: "List SMTP Configs"; - description: "Returns a list of SMTP configurations." + deprecated: true; + }; + } + + rpc ListEmailProviders(ListEmailProvidersRequest) returns (ListEmailProvidersResponse) { + option (google.api.http) = { + post: "/email/_search" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.read"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email providers"; + summary: "List Email providers"; + description: "Returns a list of Email providers." + }; + } + + rpc GetEmailProvider(GetEmailProviderRequest) returns (GetEmailProviderResponse) { + option (google.api.http) = { + get: "/email"; + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.read"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email"; + summary: "Get active Email provider"; + description: "Returns the active Email provider from the system. This is used to send E-Mails to the users." + }; + } + + rpc GetEmailProviderById(GetEmailProviderByIdRequest) returns (GetEmailProviderByIdResponse) { + option (google.api.http) = { + get: "/email/{id}"; + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.read"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email"; + summary: "Get Email provider by its id"; + description: "Get a specific Email provider by its ID."; + }; + } + + rpc AddEmailProviderSMTP(AddEmailProviderSMTPRequest) returns (AddEmailProviderSMTPResponse) { + option (google.api.http) = { + post: "/email/smtp"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email"; + summary: "Add SMTP Email provider"; + description: "Add a new SMTP Email provider if nothing is set yet." + }; + } + + rpc UpdateEmailProviderSMTP(UpdateEmailProviderSMTPRequest) returns (UpdateEmailProviderSMTPResponse) { + option (google.api.http) = { + put: "/email/smtp/{id}"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email"; + summary: "Update SMTP Email provider"; + description: "Update the SMTP Email provider, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP." + }; + } + + rpc AddEmailProviderHTTP(AddEmailProviderHTTPRequest) returns (AddEmailProviderHTTPResponse) { + option (google.api.http) = { + post: "/email/http"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email"; + summary: "Add HTTP Email provider"; + description: "Add a new HTTP Email provider if nothing is set yet." + }; + } + + rpc UpdateEmailProviderHTTP(UpdateEmailProviderHTTPRequest) returns (UpdateEmailProviderHTTPResponse) { + option (google.api.http) = { + put: "/email/http/{id}"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email"; + summary: "Update HTTP Email provider"; + description: "Update the HTTP Email provider, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured HTTP." + }; + } + + rpc UpdateEmailProviderSMTPPassword(UpdateEmailProviderSMTPPasswordRequest) returns (UpdateEmailProviderSMTPPasswordResponse) { + option (google.api.http) = { + put: "/email/smtp/{id}/password"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "SMTP"; + summary: "Update SMTP Password"; + description: "Update the SMTP password that is used for the host, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP." + }; + } + + rpc ActivateEmailProvider(ActivateEmailProviderRequest) returns (ActivateEmailProviderResponse) { + option (google.api.http) = { + post: "/email/{id}/_activate"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email Provider"; + summary: "Activate Email Provider"; + description: "Activate an Email provider." + }; + } + + rpc DeactivateEmailProvider(DeactivateEmailProviderRequest) returns (DeactivateEmailProviderResponse) { + option (google.api.http) = { + post: "/email/{id}/_deactivate"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email Provider"; + summary: "Deactivate Email Provider"; + description: "Deactivate an Email provider. After deactivating the provider, the users will not be able to receive Email notifications from that provider anymore." + }; + } + + rpc RemoveEmailProvider(RemoveEmailProviderRequest) returns (RemoveEmailProviderResponse) { + option (google.api.http) = { + delete: "/email/{id}"; + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Email"; + summary: "Remove Email provider"; + description: "Remove the Email provider, be aware that the users will not get an E-Mail if no provider is set." + }; + } + + rpc TestEmailProviderSMTPById(TestEmailProviderSMTPByIdRequest) returns (TestEmailProviderSMTPByIdResponse) { + option (google.api.http) = { + post: "/email/smtp/{id}/_test"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "SMTP Email Provider"; + summary: "Test SMTP Email Provider"; + description: "Test an SMTP Email provider identified by its ID. After testing the provider, the users will receive information about the test results." + }; + } + + rpc TestEmailProviderSMTP(TestEmailProviderSMTPRequest) returns (TestEmailProviderSMTPResponse) { + option (google.api.http) = { + post: "/email/smtp/_test"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "SMTP EMailProvider"; + summary: "Test SMTP Email Provider"; + description: "Test an SMTP Email provider. After testing the provider, the users will receive information about the test results." }; } @@ -690,39 +954,39 @@ service AdminService { }; } - rpc AddSMSProviderHTTP(AddSMSProviderHTTPRequest) returns (AddSMSProviderHTTPResponse) { - option (google.api.http) = { - post: "/sms/http"; - body: "*" - }; + rpc AddSMSProviderHTTP(AddSMSProviderHTTPRequest) returns (AddSMSProviderHTTPResponse) { + option (google.api.http) = { + post: "/sms/http"; + body: "*" + }; - option (zitadel.v1.auth_option) = { - permission: "iam.write"; - }; + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; - option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - tags: "SMS Provider"; - summary: "Add HTTP SMS Provider"; - description: "Configure a new SMS provider of the type HTTP. A provider has to be activated to be able to send notifications." - }; - } + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "SMS Provider"; + summary: "Add HTTP SMS Provider"; + description: "Configure a new SMS provider of the type HTTP. A provider has to be activated to be able to send notifications." + }; + } - rpc UpdateSMSProviderHTTP(UpdateSMSProviderHTTPRequest) returns (UpdateSMSProviderHTTPResponse) { - option (google.api.http) = { - put: "/sms/http/{id}"; - body: "*" - }; + rpc UpdateSMSProviderHTTP(UpdateSMSProviderHTTPRequest) returns (UpdateSMSProviderHTTPResponse) { + option (google.api.http) = { + put: "/sms/http/{id}"; + body: "*" + }; - option (zitadel.v1.auth_option) = { - permission: "iam.write"; - }; + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; - option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - tags: "SMS Provider"; - summary: "Update HTTP SMS Provider"; - description: "Change the configuration of an SMS provider of the type HTTP. A provider has to be activated to be able to send notifications." - }; - } + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "SMS Provider"; + summary: "Update HTTP SMS Provider"; + description: "Change the configuration of an SMS provider of the type HTTP. A provider has to be activated to be able to send notifications." + }; + } rpc ActivateSMSProvider(ActivateSMSProviderRequest) returns (ActivateSMSProviderResponse) { option (google.api.http) = { @@ -4561,6 +4825,320 @@ message TestSMTPConfigRequest { // This is an empty response message TestSMTPConfigResponse {} +//This is an empty request +message GetEmailProviderRequest {} + +message GetEmailProviderResponse { + zitadel.settings.v1.EmailProvider config = 1; +} + +message GetEmailProviderByIdRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 100}]; +} + +message GetEmailProviderByIdResponse { + zitadel.settings.v1.EmailProvider config = 1; +} + +message ListEmailProvidersRequest { + zitadel.v1.ListQuery query = 1; +} + +message ListEmailProvidersResponse { + zitadel.v1.ListDetails details = 1; + repeated zitadel.settings.v1.EmailProvider result = 2; +} + +message AddEmailProviderSMTPRequest { + string sender_address = 1 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"noreply@m.zitadel.cloud\""; + min_length: 1; + max_length: 200; + } + ]; + string sender_name = 2 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"ZITADEL\""; + min_length: 1; + max_length: 200; + } + ]; + bool tls = 3; + string host = 4 [ + (validate.rules).string = {min_len: 1, max_len: 500}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"smtp.postmarkapp.com:587\""; + description: "Make sure to include the port."; + min_length: 1; + max_length: 500; + } + ]; + string user = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\""; + } + ]; + string password = 6 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"this-is-my-password\""; + } + ]; + string reply_to_address = 7 [ + (validate.rules).string = {min_len: 0, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"replyto@m.zitadel.cloud\""; + min_length: 0; + max_length: 200; + } + ]; + string description = 8 [ + (validate.rules).string = {min_len: 0, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"provider description\""; + min_length: 0; + max_length: 200; + } + ]; +} + +message AddEmailProviderSMTPResponse { + zitadel.v1.ObjectDetails details = 1; + string id = 2; +} + +message UpdateEmailProviderSMTPRequest { + string sender_address = 1 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"noreply@m.zitadel.cloud\""; + min_length: 1; + max_length: 200; + } + ]; + string sender_name = 2 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"ZITADEL\""; + min_length: 1; + max_length: 200; + } + ]; + bool tls = 3; + string host = 4 [ + (validate.rules).string = {min_len: 1, max_len: 500}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"smtp.postmarkapp.com:587\""; + description: "Make sure to include the port."; + min_length: 1; + max_length: 500; + } + ]; + string user = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\""; + } + ]; + string reply_to_address = 6 [ + (validate.rules).string = {min_len: 0, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"replyto@m.zitadel.cloud\""; + min_length: 0; + max_length: 200; + } + ]; + string password = 7 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"this-is-my-password\""; + } + ]; + string description = 8 [ + (validate.rules).string = {min_len: 0, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"provider description\""; + min_length: 0; + max_length: 200; + } + ]; + string id = 9 [(validate.rules).string = {min_len: 1, max_len: 100}]; +} + +message UpdateEmailProviderSMTPResponse { + zitadel.v1.ObjectDetails details = 1; +} + +message UpdateEmailProviderSMTPPasswordRequest { + string password = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"this-is-my-updated-password\""; + } + ]; + string id = 2 [(validate.rules).string = {min_len: 1, max_len: 100}]; +} + +message UpdateEmailProviderSMTPPasswordResponse { + zitadel.v1.ObjectDetails details = 1; +} + + +message AddEmailProviderHTTPRequest { + string endpoint = 1 [ + (validate.rules).string = {min_len: 1, max_len: 2048}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"http://relay.example.com/provider\""; + min_length: 1; + max_length: 2048; + } + ]; + string description = 2 [ + (validate.rules).string = {min_len: 0, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"provider description\""; + min_length: 0; + max_length: 200; + } + ]; +} + +message AddEmailProviderHTTPResponse { + zitadel.v1.ObjectDetails details = 1; + string id = 2; +} + +message UpdateEmailProviderHTTPRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 100}]; + string endpoint = 2 [ + (validate.rules).string = {min_len: 1, max_len: 2048}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"http://relay.example.com/provider\""; + min_length: 1; + max_length: 2048; + } + ]; + string description = 3 [ + (validate.rules).string = {min_len: 0, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"provider description\""; + min_length: 0; + max_length: 200; + } + ]; +} + +message UpdateEmailProviderHTTPResponse { + zitadel.v1.ObjectDetails details = 1; +} + +message ActivateEmailProviderRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; +} + +message ActivateEmailProviderResponse { + zitadel.v1.ObjectDetails details = 1; +} + +message DeactivateEmailProviderRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; +} + +message DeactivateEmailProviderResponse { + zitadel.v1.ObjectDetails details = 1; +} + +message RemoveEmailProviderRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 100}]; +} + +message RemoveEmailProviderResponse { + zitadel.v1.ObjectDetails details = 1; +} + +message TestEmailProviderSMTPByIdRequest { + string id = 1 [(validate.rules).string = {min_len: 1, max_len: 100}]; + string receiver_address = 2 [ + (validate.rules).string = {min_len: 1, max_len: 200, email: true}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"noreply@m.zitadel.cloud\""; + min_length: 1; + max_length: 200; + } + ]; +} + +// This is an empty response +message TestEmailProviderSMTPByIdResponse {} + +message TestEmailProviderSMTPRequest { + string sender_address = 1 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"noreply@m.zitadel.cloud\""; + min_length: 1; + max_length: 200; + } + ]; + string sender_name = 2 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"ZITADEL\""; + min_length: 1; + max_length: 200; + } + ]; + bool tls = 3; + string host = 4 [ + (validate.rules).string = {min_len: 1, max_len: 500}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"smtp.postmarkapp.com:587\""; + description: "Make sure to include the port."; + min_length: 1; + max_length: 500; + } + ]; + string user = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\""; + } + ]; + string password = 6 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"this-is-my-password\""; + } + ]; + string receiver_address = 7 [ + (validate.rules).string = {min_len: 1, max_len: 200, email: true}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"noreply@m.zitadel.cloud\""; + min_length: 1; + max_length: 200; + } + ]; + string id = 8 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Zitadel SMTP provider id in case you are not sending the password and want to reuse the stored password"; + example: "\"267191369515139464\""; + } + ]; +} + +// This is an empty response +message TestEmailProviderSMTPResponse {} + message ListSMSProvidersRequest { //list limitations and ordering zitadel.v1.ListQuery query = 1; diff --git a/proto/zitadel/settings.proto b/proto/zitadel/settings.proto index 7b4f656bf2..a2a6806c65 100644 --- a/proto/zitadel/settings.proto +++ b/proto/zitadel/settings.proto @@ -95,6 +95,57 @@ message SMTPConfig { string id = 10; } +message EmailProvider { + zitadel.v1.ObjectDetails details = 1; + string id = 2; + EmailProviderState state = 3; + string description = 6; + + oneof config { + EmailProviderSMTP smtp = 4; + EmailProviderHTTP http = 5; + } +} + +enum EmailProviderState { + EMAIL_PROVIDER_STATE_UNSPECIFIED = 0; + EMAIL_PROVIDER_ACTIVE = 1; + EMAIL_PROVIDER_INACTIVE = 2; +} + +message EmailProviderSMTP { + string sender_address = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"noreply@m.zitadel.cloud\""; + } + ]; + string sender_name = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"ZITADEL\""; + } + ]; + bool tls = 3; + string host = 4 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"smtp.postmarkapp.com:587\""; + } + ]; + string user = 5 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\""; + } + ]; + string reply_to_address = 6 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"replyto@m.zitadel.cloud\""; + } + ]; +} + +message EmailProviderHTTP { + string endpoint = 1; +} + message SMSProvider { zitadel.v1.ObjectDetails details = 1; string id = 2;