feat: http provider signing key addition (#10641)

# Which Problems Are Solved

HTTP Request to HTTP providers for Email or SMS are not signed.

# How the Problems Are Solved

Add a Signing Key to the HTTP Provider resources, which is then used to
generate a header to sign the payload.

# Additional Changes

Additional tests for query side of the SMTP provider.

# Additional Context

Closes #10067

---------

Co-authored-by: Marco A. <marco@zitadel.com>
(cherry picked from commit 8909b9a2a6)
This commit is contained in:
Stefan Benz
2025-09-08 13:00:04 +02:00
committed by Livio Spring
parent d2d94ea088
commit 1a7cd6e1af
36 changed files with 2113 additions and 132 deletions

View File

@@ -149,6 +149,8 @@ func projections(
keys.OIDC, keys.OIDC,
keys.SAML, keys.SAML,
keys.Target, keys.Target,
keys.SMS,
keys.SMTP,
config.InternalAuthZ.RolePermissionMappings, config.InternalAuthZ.RolePermissionMappings,
sessionTokenVerifier, sessionTokenVerifier,
func(q *query.Queries) domain.PermissionCheck { func(q *query.Queries) domain.PermissionCheck {

27
cmd/setup/62.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 62.sql
addHTTPProviderSigningKey string
)
type HTTPProviderAddSigningKey struct {
dbClient *database.DB
}
func (mig *HTTPProviderAddSigningKey) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addHTTPProviderSigningKey)
return err
}
func (mig *HTTPProviderAddSigningKey) String() string {
return "62_http_provider_add_signing_key"
}

2
cmd/setup/62.sql Normal file
View File

@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS projections.sms_configs3_http ADD COLUMN IF NOT EXISTS signing_key TEXT NULL;
ALTER TABLE IF EXISTS projections.smtp_configs5_http ADD COLUMN IF NOT EXISTS signing_key TEXT NULL;

View File

@@ -158,6 +158,7 @@ type Steps struct {
s59SetupWebkeys *SetupWebkeys s59SetupWebkeys *SetupWebkeys
s60GenerateSystemID *GenerateSystemID s60GenerateSystemID *GenerateSystemID
s61IDPTemplate6SAMLSignatureAlgorithm *IDPTemplate6SAMLSignatureAlgorithm s61IDPTemplate6SAMLSignatureAlgorithm *IDPTemplate6SAMLSignatureAlgorithm
s62HTTPProviderAddSigningKey *HTTPProviderAddSigningKey
} }
func MustNewSteps(v *viper.Viper) *Steps { func MustNewSteps(v *viper.Viper) *Steps {

View File

@@ -219,6 +219,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s58ReplaceLoginNames3View = &ReplaceLoginNames3View{dbClient: dbClient} steps.s58ReplaceLoginNames3View = &ReplaceLoginNames3View{dbClient: dbClient}
steps.s60GenerateSystemID = &GenerateSystemID{eventstore: eventstoreClient} steps.s60GenerateSystemID = &GenerateSystemID{eventstore: eventstoreClient}
steps.s61IDPTemplate6SAMLSignatureAlgorithm = &IDPTemplate6SAMLSignatureAlgorithm{dbClient: dbClient} steps.s61IDPTemplate6SAMLSignatureAlgorithm = &IDPTemplate6SAMLSignatureAlgorithm{dbClient: dbClient}
steps.s62HTTPProviderAddSigningKey = &HTTPProviderAddSigningKey{dbClient: dbClient}
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil) err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil)
logging.OnError(err).Fatal("unable to start projections") logging.OnError(err).Fatal("unable to start projections")
@@ -268,6 +269,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s58ReplaceLoginNames3View, steps.s58ReplaceLoginNames3View,
steps.s60GenerateSystemID, steps.s60GenerateSystemID,
steps.s61IDPTemplate6SAMLSignatureAlgorithm, steps.s61IDPTemplate6SAMLSignatureAlgorithm,
steps.s62HTTPProviderAddSigningKey,
} { } {
setupErr = executeMigration(ctx, eventstoreClient, step, "migration failed") setupErr = executeMigration(ctx, eventstoreClient, step, "migration failed")
if setupErr != nil { if setupErr != nil {
@@ -468,6 +470,8 @@ func startCommandsQueries(
keys.OIDC, keys.OIDC,
keys.SAML, keys.SAML,
keys.Target, keys.Target,
keys.SMS,
keys.SMTP,
config.InternalAuthZ.RolePermissionMappings, config.InternalAuthZ.RolePermissionMappings,
sessionTokenVerifier, sessionTokenVerifier,
func(q *query.Queries) domain.PermissionCheck { func(q *query.Queries) domain.PermissionCheck {

View File

@@ -205,6 +205,8 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
keys.OIDC, keys.OIDC,
keys.SAML, keys.SAML,
keys.Target, keys.Target,
keys.SMS,
keys.SMTP,
config.InternalAuthZ.RolePermissionMappings, config.InternalAuthZ.RolePermissionMappings,
sessionTokenVerifier, sessionTokenVerifier,
func(q *query.Queries) domain.PermissionCheck { func(q *query.Queries) domain.PermissionCheck {

View File

@@ -57,6 +57,7 @@ func (s *Server) AddEmailProviderHTTP(ctx context.Context, req *admin_pb.AddEmai
return &admin_pb.AddEmailProviderHTTPResponse{ return &admin_pb.AddEmailProviderHTTPResponse{
Details: object.DomainToChangeDetailsPb(config.Details), Details: object.DomainToChangeDetailsPb(config.Details),
Id: config.ID, Id: config.ID,
SigningKey: config.SigningKey,
}, nil }, nil
} }
@@ -67,6 +68,7 @@ func (s *Server) UpdateEmailProviderHTTP(ctx context.Context, req *admin_pb.Upda
} }
return &admin_pb.UpdateEmailProviderHTTPResponse{ return &admin_pb.UpdateEmailProviderHTTPResponse{
Details: object.DomainToChangeDetailsPb(config.Details), Details: object.DomainToChangeDetailsPb(config.Details),
SigningKey: config.SigningKey,
}, nil }, nil
} }

View File

@@ -69,6 +69,7 @@ func httpToPb(http *query.HTTP) *settings_pb.EmailProvider_Http {
return &settings_pb.EmailProvider_Http{ return &settings_pb.EmailProvider_Http{
Http: &settings_pb.EmailProviderHTTP{ Http: &settings_pb.EmailProviderHTTP{
Endpoint: http.Endpoint, Endpoint: http.Endpoint,
SigningKey: http.SigningKey,
}, },
} }
} }
@@ -81,6 +82,7 @@ func smtpToPb(config *query.SMTP) *settings_pb.EmailProvider_Smtp {
User: config.User, User: config.User,
SenderAddress: config.SenderAddress, SenderAddress: config.SenderAddress,
SenderName: config.SenderName, SenderName: config.SenderName,
ReplyToAddress: config.ReplyToAddress,
}, },
} }
} }
@@ -123,11 +125,14 @@ func addEmailProviderHTTPToConfig(ctx context.Context, req *admin_pb.AddEmailPro
} }
func updateEmailProviderHTTPToConfig(ctx context.Context, req *admin_pb.UpdateEmailProviderHTTPRequest) *command.ChangeSMTPConfigHTTP { func updateEmailProviderHTTPToConfig(ctx context.Context, req *admin_pb.UpdateEmailProviderHTTPRequest) *command.ChangeSMTPConfigHTTP {
// TODO handle expiration, currently only immediate expiration is supported
expirationSigningKey := req.GetExpirationSigningKey() != nil
return &command.ChangeSMTPConfigHTTP{ return &command.ChangeSMTPConfigHTTP{
ResourceOwner: authz.GetInstance(ctx).InstanceID(), ResourceOwner: authz.GetInstance(ctx).InstanceID(),
ID: req.Id, ID: req.Id,
Description: req.Description, Description: req.Description,
Endpoint: req.Endpoint, Endpoint: req.Endpoint,
ExpirationSigningKey: expirationSigningKey,
} }
} }

View File

@@ -0,0 +1,554 @@
package admin
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object"
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
)
func Test_listEmailProvidersToModel(t *testing.T) {
type args struct {
req *admin_pb.ListEmailProvidersRequest
}
tests := []struct {
name string
args args
res *query.SMTPConfigsSearchQueries
}{
{
name: "all fields filled",
args: args{
req: &admin_pb.ListEmailProvidersRequest{
Query: &object_pb.ListQuery{
Offset: 100,
Limit: 100,
Asc: true,
},
},
},
res: &query.SMTPConfigsSearchQueries{
SearchRequest: query.SearchRequest{
Offset: 100,
Limit: 100,
Asc: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := listEmailProvidersToModel(tt.args.req)
require.NoError(t, err)
assert.Equal(t, tt.res, got)
})
}
}
func Test_emailProvidersToPb(t *testing.T) {
type args struct {
req []*query.SMTPConfig
}
tests := []struct {
name string
args args
res []*settings_pb.EmailProvider
}{
{
name: "all fields filled",
args: args{
req: []*query.SMTPConfig{
{
CreationDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ChangeDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ResourceOwner: "resourceowner",
AggregateID: "agg",
ID: "id",
Sequence: 1,
Description: "description",
SMTPConfig: &query.SMTP{
TLS: true,
SenderAddress: "sender",
SenderName: "sendername",
ReplyToAddress: "address",
Host: "host",
User: "user",
},
HTTPConfig: nil,
State: 1,
},
{
CreationDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ChangeDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ResourceOwner: "resourceowner",
AggregateID: "agg",
ID: "id",
Sequence: 1,
Description: "description",
SMTPConfig: nil,
HTTPConfig: &query.HTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
State: 1,
},
},
},
res: []*settings_pb.EmailProvider{
{
Details: &object_pb.ObjectDetails{
Sequence: 1,
CreationDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ChangeDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ResourceOwner: "resourceowner",
},
Id: "id",
State: 1,
Description: "description",
Config: &settings_pb.EmailProvider_Smtp{
Smtp: &settings_pb.EmailProviderSMTP{
SenderAddress: "sender",
SenderName: "sendername",
Tls: true,
Host: "host",
User: "user",
ReplyToAddress: "address",
},
},
},
{
Details: &object_pb.ObjectDetails{
Sequence: 1,
CreationDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ChangeDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ResourceOwner: "resourceowner",
},
Id: "id",
State: 1,
Description: "description",
Config: &settings_pb.EmailProvider_Http{
Http: &settings_pb.EmailProviderHTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := emailProvidersToPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_emailProviderToProviderPb(t *testing.T) {
type args struct {
req *query.SMTPConfig
}
tests := []struct {
name string
args args
res *settings_pb.EmailProvider
}{
{
name: "all fields filled, smtp",
args: args{
req: &query.SMTPConfig{
CreationDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ChangeDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ResourceOwner: "resourceowner",
AggregateID: "agg",
ID: "id",
Sequence: 1,
Description: "description",
SMTPConfig: &query.SMTP{
TLS: true,
SenderAddress: "sender",
SenderName: "sendername",
ReplyToAddress: "address",
Host: "host",
User: "user",
},
HTTPConfig: &query.HTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
State: 1,
},
},
res: &settings_pb.EmailProvider{
Details: &object_pb.ObjectDetails{
Sequence: 1,
CreationDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ChangeDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ResourceOwner: "resourceowner",
},
Id: "id",
State: 1,
Description: "description",
Config: &settings_pb.EmailProvider_Smtp{
Smtp: &settings_pb.EmailProviderSMTP{
SenderAddress: "sender",
SenderName: "sendername",
Tls: true,
Host: "host",
User: "user",
ReplyToAddress: "address",
},
},
},
},
{
name: "all fields filled, http",
args: args{
req: &query.SMTPConfig{
CreationDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ChangeDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ResourceOwner: "resourceowner",
AggregateID: "agg",
ID: "id",
Sequence: 1,
Description: "description",
HTTPConfig: &query.HTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
State: 1,
},
},
res: &settings_pb.EmailProvider{
Details: &object_pb.ObjectDetails{
Sequence: 1,
CreationDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ChangeDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ResourceOwner: "resourceowner",
},
Id: "id",
State: 1,
Description: "description",
Config: &settings_pb.EmailProvider_Http{
Http: &settings_pb.EmailProviderHTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := emailProviderToProviderPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_emailProviderStateToPb(t *testing.T) {
type args struct {
req domain.SMTPConfigState
}
tests := []struct {
name string
args args
res settings_pb.EmailProviderState
}{
{
name: "unspecified",
args: args{
req: domain.SMTPConfigStateUnspecified,
},
res: settings_pb.EmailProviderState_EMAIL_PROVIDER_STATE_UNSPECIFIED,
},
{
name: "removed",
args: args{
req: domain.SMTPConfigStateRemoved,
},
res: settings_pb.EmailProviderState_EMAIL_PROVIDER_STATE_UNSPECIFIED,
},
{
name: "active",
args: args{
req: domain.SMTPConfigStateActive,
},
res: settings_pb.EmailProviderState_EMAIL_PROVIDER_ACTIVE,
},
{
name: "inactive",
args: args{
req: domain.SMTPConfigStateInactive,
},
res: settings_pb.EmailProviderState_EMAIL_PROVIDER_INACTIVE,
},
{
name: "default",
args: args{
req: 100,
},
res: settings_pb.EmailProviderState_EMAIL_PROVIDER_STATE_UNSPECIFIED,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := emailProviderStateToPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_httpToPb(t *testing.T) {
type args struct {
req *query.HTTP
}
tests := []struct {
name string
args args
res *settings_pb.EmailProvider_Http
}{
{
name: "all fields filled",
args: args{
req: &query.HTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
},
res: &settings_pb.EmailProvider_Http{
Http: &settings_pb.EmailProviderHTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := httpToPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_smtpToPb(t *testing.T) {
type args struct {
req *query.SMTP
}
tests := []struct {
name string
args args
res *settings_pb.EmailProvider_Smtp
}{
{
name: "all fields filled",
args: args{
req: &query.SMTP{
SenderAddress: "sender",
SenderName: "sendername",
TLS: true,
Host: "host",
User: "user",
ReplyToAddress: "address",
},
},
res: &settings_pb.EmailProvider_Smtp{
Smtp: &settings_pb.EmailProviderSMTP{
SenderAddress: "sender",
SenderName: "sendername",
Tls: true,
Host: "host",
User: "user",
ReplyToAddress: "address",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := smtpToPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_addEmailProviderSMTPToConfig(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.AddEmailProviderSMTPRequest
}
tests := []struct {
name string
args args
res *command.AddSMTPConfig
}{
{
name: "all fields filled",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.AddEmailProviderSMTPRequest{
SenderAddress: "sender",
SenderName: "sendername",
Tls: true,
Host: "host",
User: "user",
Password: "password",
ReplyToAddress: "address",
Description: "description",
},
},
res: &command.AddSMTPConfig{
ResourceOwner: "instance",
Description: "description",
Host: "host",
User: "user",
Password: "password",
Tls: true,
From: "sender",
FromName: "sendername",
ReplyToAddress: "address",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := addEmailProviderSMTPToConfig(tt.args.ctx, tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_updateEmailProviderSMTPToConfig(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.UpdateEmailProviderSMTPRequest
}
tests := []struct {
name string
args args
res *command.ChangeSMTPConfig
}{
{
name: "all fields filled",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.UpdateEmailProviderSMTPRequest{
SenderAddress: "sender",
SenderName: "sendername",
Tls: true,
Host: "host",
User: "user",
ReplyToAddress: "address",
Password: "password",
Description: "description",
Id: "id",
},
},
res: &command.ChangeSMTPConfig{
ResourceOwner: "instance",
ID: "id",
Description: "description",
Host: "host",
User: "user",
Password: "password",
Tls: true,
From: "sender",
FromName: "sendername",
ReplyToAddress: "address",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := updateEmailProviderSMTPToConfig(tt.args.ctx, tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_addEmailProviderHTTPToConfig(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.AddEmailProviderHTTPRequest
}
tests := []struct {
name string
args args
res *command.AddSMTPConfigHTTP
}{
{
name: "all fields filled",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.AddEmailProviderHTTPRequest{
Endpoint: "endpoint",
Description: "description",
},
},
res: &command.AddSMTPConfigHTTP{
ResourceOwner: "instance",
ID: "",
Description: "description",
Endpoint: "endpoint",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := addEmailProviderHTTPToConfig(tt.args.ctx, tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_updateEmailProviderHTTPToConfig(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.UpdateEmailProviderHTTPRequest
}
tests := []struct {
name string
args args
res *command.ChangeSMTPConfigHTTP
}{
{
name: "all fields filled",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.UpdateEmailProviderHTTPRequest{
Id: "id",
Endpoint: "endpoint",
Description: "description",
ExpirationSigningKey: durationpb.New(time.Second),
},
},
res: &command.ChangeSMTPConfigHTTP{
ResourceOwner: "instance",
ID: "id",
Description: "description",
Endpoint: "endpoint",
ExpirationSigningKey: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := updateEmailProviderHTTPToConfig(tt.args.ctx, tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}

View File

@@ -73,6 +73,7 @@ func (s *Server) AddSMSProviderHTTP(ctx context.Context, req *admin_pb.AddSMSPro
return &admin_pb.AddSMSProviderHTTPResponse{ return &admin_pb.AddSMSProviderHTTPResponse{
Details: object.DomainToAddDetailsPb(smsConfig.Details), Details: object.DomainToAddDetailsPb(smsConfig.Details),
Id: smsConfig.ID, Id: smsConfig.ID,
SigningKey: smsConfig.SigningKey,
}, nil }, nil
} }
@@ -83,6 +84,7 @@ func (s *Server) UpdateSMSProviderHTTP(ctx context.Context, req *admin_pb.Update
} }
return &admin_pb.UpdateSMSProviderHTTPResponse{ return &admin_pb.UpdateSMSProviderHTTPResponse{
Details: object.DomainToChangeDetailsPb(smsConfig.Details), Details: object.DomainToChangeDetailsPb(smsConfig.Details),
SigningKey: smsConfig.SigningKey,
}, nil }, nil
} }

View File

@@ -57,6 +57,7 @@ func HTTPConfigToPb(http *query.HTTP) *settings_pb.SMSProvider_Http {
return &settings_pb.SMSProvider_Http{ return &settings_pb.SMSProvider_Http{
Http: &settings_pb.HTTPConfig{ Http: &settings_pb.HTTPConfig{
Endpoint: http.Endpoint, Endpoint: http.Endpoint,
SigningKey: http.SigningKey,
}, },
} }
} }
@@ -73,6 +74,8 @@ func TwilioConfigToPb(twilio *query.Twilio) *settings_pb.SMSProvider_Twilio {
func smsStateToPb(state domain.SMSConfigState) settings_pb.SMSProviderConfigState { func smsStateToPb(state domain.SMSConfigState) settings_pb.SMSProviderConfigState {
switch state { switch state {
case domain.SMSConfigStateUnspecified, domain.SMSConfigStateRemoved:
return settings_pb.SMSProviderConfigState_SMS_PROVIDER_CONFIG_INACTIVE
case domain.SMSConfigStateInactive: case domain.SMSConfigStateInactive:
return settings_pb.SMSProviderConfigState_SMS_PROVIDER_CONFIG_INACTIVE return settings_pb.SMSProviderConfigState_SMS_PROVIDER_CONFIG_INACTIVE
case domain.SMSConfigStateActive: case domain.SMSConfigStateActive:
@@ -113,10 +116,13 @@ func addSMSConfigHTTPToConfig(ctx context.Context, req *admin_pb.AddSMSProviderH
} }
func updateSMSConfigHTTPToConfig(ctx context.Context, req *admin_pb.UpdateSMSProviderHTTPRequest) *command.ChangeSMSHTTP { func updateSMSConfigHTTPToConfig(ctx context.Context, req *admin_pb.UpdateSMSProviderHTTPRequest) *command.ChangeSMSHTTP {
// TODO handle expiration, currently only immediate expiration is supported
expirationSigningKey := req.GetExpirationSigningKey() != nil
return &command.ChangeSMSHTTP{ return &command.ChangeSMSHTTP{
ResourceOwner: authz.GetInstance(ctx).InstanceID(), ResourceOwner: authz.GetInstance(ctx).InstanceID(),
ID: req.Id, ID: req.Id,
Description: gu.Ptr(req.Description), Description: gu.Ptr(req.Description),
Endpoint: gu.Ptr(req.Endpoint), Endpoint: gu.Ptr(req.Endpoint),
ExpirationSigningKey: expirationSigningKey,
} }
} }

View File

@@ -0,0 +1,520 @@
package admin
import (
"context"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object"
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
)
func Test_listSMSConfigsToModel(t *testing.T) {
type args struct {
req *admin_pb.ListSMSProvidersRequest
}
tests := []struct {
name string
args args
res *query.SMSConfigsSearchQueries
}{
{
name: "all fields filled",
args: args{
req: &admin_pb.ListSMSProvidersRequest{
Query: &object_pb.ListQuery{
Offset: 100,
Limit: 100,
Asc: true,
},
},
},
res: &query.SMSConfigsSearchQueries{
SearchRequest: query.SearchRequest{
Offset: 100,
Limit: 100,
Asc: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := listSMSConfigsToModel(tt.args.req)
require.NoError(t, err)
assert.Equal(t, tt.res, got)
})
}
}
func Test_SMSConfigsToPb(t *testing.T) {
type args struct {
req []*query.SMSConfig
}
tests := []struct {
name string
args args
res []*settings_pb.SMSProvider
}{
{
name: "all fields filled",
args: args{
req: []*query.SMSConfig{
{
CreationDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ChangeDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ResourceOwner: "resourceowner",
AggregateID: "agg",
ID: "id",
Sequence: 1,
Description: "description",
TwilioConfig: &query.Twilio{
SID: "sid",
Token: nil,
SenderNumber: "sender",
VerifyServiceSID: "verify",
},
State: 1,
},
{
CreationDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ChangeDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ResourceOwner: "resourceowner",
AggregateID: "agg",
ID: "id",
Sequence: 1,
Description: "description",
HTTPConfig: &query.HTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
State: 1,
},
},
},
res: []*settings_pb.SMSProvider{
{
Details: &object_pb.ObjectDetails{
Sequence: 1,
CreationDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ChangeDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ResourceOwner: "resourceowner",
},
Id: "id",
State: 1,
Description: "description",
Config: &settings_pb.SMSProvider_Twilio{
Twilio: &settings_pb.TwilioConfig{
Sid: "sid",
SenderNumber: "sender",
VerifyServiceSid: "verify",
},
},
},
{
Details: &object_pb.ObjectDetails{
Sequence: 1,
CreationDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ChangeDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ResourceOwner: "resourceowner",
},
Id: "id",
State: 1,
Description: "description",
Config: &settings_pb.SMSProvider_Http{
Http: &settings_pb.HTTPConfig{
Endpoint: "endpoint",
SigningKey: "key",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := SMSConfigsToPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_SMSConfigToProviderPb(t *testing.T) {
type args struct {
req *query.SMSConfig
}
tests := []struct {
name string
args args
res *settings_pb.SMSProvider
}{
{
name: "all fields filled, twilio",
args: args{
req: &query.SMSConfig{
CreationDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ChangeDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ResourceOwner: "resourceowner",
AggregateID: "agg",
ID: "id",
Sequence: 1,
Description: "description",
TwilioConfig: &query.Twilio{
SID: "sid",
Token: nil,
SenderNumber: "sender",
VerifyServiceSID: "verify",
},
State: 1,
},
},
res: &settings_pb.SMSProvider{
Details: &object_pb.ObjectDetails{
Sequence: 1,
CreationDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ChangeDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ResourceOwner: "resourceowner",
},
Id: "id",
State: 1,
Description: "description",
Config: &settings_pb.SMSProvider_Twilio{
Twilio: &settings_pb.TwilioConfig{
Sid: "sid",
SenderNumber: "sender",
VerifyServiceSid: "verify",
},
},
},
},
{
name: "all fields filled, http",
args: args{
req: &query.SMSConfig{
CreationDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ChangeDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
ResourceOwner: "resourceowner",
AggregateID: "agg",
ID: "id",
Sequence: 1,
Description: "description",
HTTPConfig: &query.HTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
State: 1,
},
},
res: &settings_pb.SMSProvider{
Details: &object_pb.ObjectDetails{
Sequence: 1,
CreationDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ChangeDate: timestamppb.New(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
ResourceOwner: "resourceowner",
},
Id: "id",
State: 1,
Description: "description",
Config: &settings_pb.SMSProvider_Http{
Http: &settings_pb.HTTPConfig{
Endpoint: "endpoint",
SigningKey: "key",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := SMSConfigToProviderPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_smsStateToPb(t *testing.T) {
type args struct {
req domain.SMSConfigState
}
tests := []struct {
name string
args args
res settings_pb.SMSProviderConfigState
}{
{
name: "unspecified",
args: args{
req: domain.SMSConfigStateUnspecified,
},
res: settings_pb.SMSProviderConfigState_SMS_PROVIDER_CONFIG_INACTIVE,
},
{
name: "removed",
args: args{
req: domain.SMSConfigStateRemoved,
},
res: settings_pb.SMSProviderConfigState_SMS_PROVIDER_CONFIG_INACTIVE,
},
{
name: "active",
args: args{
req: domain.SMSConfigStateActive,
},
res: settings_pb.SMSProviderConfigState_SMS_PROVIDER_CONFIG_ACTIVE,
},
{
name: "inactive",
args: args{
req: domain.SMSConfigStateInactive,
},
res: settings_pb.SMSProviderConfigState_SMS_PROVIDER_CONFIG_INACTIVE,
},
{
name: "default",
args: args{
req: 100,
},
res: settings_pb.SMSProviderConfigState_SMS_PROVIDER_CONFIG_INACTIVE,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := smsStateToPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_HTTPConfigToPb(t *testing.T) {
type args struct {
req *query.HTTP
}
tests := []struct {
name string
args args
res *settings_pb.SMSProvider_Http
}{
{
name: "all fields filled",
args: args{
req: &query.HTTP{
Endpoint: "endpoint",
SigningKey: "key",
},
},
res: &settings_pb.SMSProvider_Http{
Http: &settings_pb.HTTPConfig{
Endpoint: "endpoint",
SigningKey: "key",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := HTTPConfigToPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_TwilioConfigToPb(t *testing.T) {
type args struct {
req *query.Twilio
}
tests := []struct {
name string
args args
res *settings_pb.SMSProvider_Twilio
}{
{
name: "all fields filled",
args: args{
req: &query.Twilio{
SID: "sid",
SenderNumber: "sender",
VerifyServiceSID: "verify",
},
},
res: &settings_pb.SMSProvider_Twilio{
Twilio: &settings_pb.TwilioConfig{
Sid: "sid",
SenderNumber: "sender",
VerifyServiceSid: "verify",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := TwilioConfigToPb(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_addSMSConfigTwilioToConfig(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.AddSMSProviderTwilioRequest
}
tests := []struct {
name string
args args
res *command.AddTwilioConfig
}{
{
name: "all fields filled",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.AddSMSProviderTwilioRequest{
Sid: "sid",
Token: "token",
SenderNumber: "sender",
Description: "description",
VerifyServiceSid: "verify",
},
},
res: &command.AddTwilioConfig{
ResourceOwner: "instance",
ID: "",
Description: "description",
SID: "sid",
Token: "token",
SenderNumber: "sender",
VerifyServiceSID: "verify",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := addSMSConfigTwilioToConfig(tt.args.ctx, tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_updateSMSConfigTwilioToConfig(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.UpdateSMSProviderTwilioRequest
}
tests := []struct {
name string
args args
res *command.ChangeTwilioConfig
}{
{
name: "all fields filled",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.UpdateSMSProviderTwilioRequest{
Id: "id",
Sid: "sid",
SenderNumber: "sender",
Description: "description",
VerifyServiceSid: "verify",
},
},
res: &command.ChangeTwilioConfig{
ResourceOwner: "instance",
ID: "id",
Description: gu.Ptr("description"),
SID: gu.Ptr("sid"),
SenderNumber: gu.Ptr("sender"),
VerifyServiceSID: gu.Ptr("verify"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := updateSMSConfigTwilioToConfig(tt.args.ctx, tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_addSMSConfigHTTPToConfig(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.AddSMSProviderHTTPRequest
}
tests := []struct {
name string
args args
res *command.AddSMSHTTP
}{
{
name: "all fields filled",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.AddSMSProviderHTTPRequest{
Endpoint: "endpoint",
Description: "description",
},
},
res: &command.AddSMSHTTP{
ResourceOwner: "instance",
ID: "",
Description: "description",
Endpoint: "endpoint",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := addSMSConfigHTTPToConfig(tt.args.ctx, tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
func Test_updateSMSConfigHTTPToConfig(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.UpdateSMSProviderHTTPRequest
}
tests := []struct {
name string
args args
res *command.ChangeSMSHTTP
}{
{
name: "all fields filled",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.UpdateSMSProviderHTTPRequest{
Id: "id",
Endpoint: "endpoint",
Description: "description",
ExpirationSigningKey: durationpb.New(time.Second),
},
},
res: &command.ChangeSMSHTTP{
ResourceOwner: "instance",
ID: "id",
Description: gu.Ptr("description"),
Endpoint: gu.Ptr("endpoint"),
ExpirationSigningKey: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := updateSMSConfigHTTPToConfig(tt.args.ctx, tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}

View File

@@ -0,0 +1,63 @@
package object
import (
"testing"
"github.com/stretchr/testify/assert"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object"
)
func Test_ListQueryToModel(t *testing.T) {
type args struct {
req *object_pb.ListQuery
}
type res struct {
offset, limit uint64
asc bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "all fields filled",
args: args{
req: &object_pb.ListQuery{
Offset: 100,
Limit: 100,
Asc: true,
},
},
res: res{
offset: 100,
limit: 100,
asc: true,
},
},
{
name: "all fields empty",
args: args{
req: &object_pb.ListQuery{
Offset: 0,
Limit: 0,
Asc: false,
},
},
res: res{
offset: 0,
limit: 0,
asc: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
offset, limit, asc := ListQueryToModel(tt.args.req)
assert.Equal(t, tt.res.offset, offset)
assert.Equal(t, tt.res.limit, limit)
assert.Equal(t, tt.res.asc, asc)
})
}
}

View File

@@ -201,7 +201,12 @@ func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregat
return changeEvent, true, nil return changeEvent, true, nil
} }
func (wm *IAMSMTPConfigWriteModel) NewHTTPChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id, description, endpoint string) (*instance.SMTPConfigHTTPChangedEvent, bool, error) { func (wm *IAMSMTPConfigWriteModel) NewHTTPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id, description, endpoint string,
signingKey *crypto.CryptoValue,
) (*instance.SMTPConfigHTTPChangedEvent, bool, error) {
changes := make([]instance.SMTPConfigHTTPChanges, 0) changes := make([]instance.SMTPConfigHTTPChanges, 0)
var err error var err error
if wm.HTTPConfig == nil { if wm.HTTPConfig == nil {
@@ -217,6 +222,10 @@ func (wm *IAMSMTPConfigWriteModel) NewHTTPChangedEvent(ctx context.Context, aggr
if wm.HTTPConfig.Endpoint != endpoint { if wm.HTTPConfig.Endpoint != endpoint {
changes = append(changes, instance.ChangeSMTPConfigHTTPEndpoint(endpoint)) changes = append(changes, instance.ChangeSMTPConfigHTTPEndpoint(endpoint))
} }
// if signingkey is set, update it as it is encrypted
if signingKey != nil {
changes = append(changes, instance.ChangeSMTPConfigHTTPSigningKey(signingKey))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false, nil return nil, false, nil
} }
@@ -252,6 +261,7 @@ func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigHTTPAddedEvent(e *instance.SM
wm.Description = e.Description wm.Description = e.Description
wm.HTTPConfig = &HTTPConfig{ wm.HTTPConfig = &HTTPConfig{
Endpoint: e.Endpoint, Endpoint: e.Endpoint,
SigningKey: e.SigningKey,
} }
wm.State = domain.SMTPConfigStateInactive wm.State = domain.SMTPConfigStateInactive
// If ID has empty value we're dealing with the old and unique smtp settings // If ID has empty value we're dealing with the old and unique smtp settings
@@ -313,6 +323,9 @@ func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigHTTPChangedEvent(e *instance.
if e.Endpoint != nil { if e.Endpoint != nil {
wm.HTTPConfig.Endpoint = *e.Endpoint wm.HTTPConfig.Endpoint = *e.Endpoint
} }
if e.SigningKey != nil {
wm.HTTPConfig.SigningKey = e.SigningKey
}
// If ID has empty value we're dealing with the old and unique smtp settings // 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 // These would be the default values for ID and State

View File

@@ -157,6 +157,7 @@ type AddSMSHTTP struct {
Description string Description string
Endpoint string Endpoint string
SigningKey string
} }
func (c *Commands) AddSMSConfigHTTP(ctx context.Context, config *AddSMSHTTP) (err error) { func (c *Commands) AddSMSConfigHTTP(ctx context.Context, config *AddSMSHTTP) (err error) {
@@ -174,6 +175,12 @@ func (c *Commands) AddSMSConfigHTTP(ctx context.Context, config *AddSMSHTTP) (er
return err return err
} }
code, err := c.newSigningKey(ctx, c.eventstore.Filter, c.smsEncryption) //nolint
if err != nil {
return err
}
config.SigningKey = code.PlainCode()
err = c.pushAppendAndReduce(ctx, err = c.pushAppendAndReduce(ctx,
smsConfigWriteModel, smsConfigWriteModel,
instance.NewSMSConfigHTTPAddedEvent( instance.NewSMSConfigHTTPAddedEvent(
@@ -182,6 +189,7 @@ func (c *Commands) AddSMSConfigHTTP(ctx context.Context, config *AddSMSHTTP) (er
config.ID, config.ID,
config.Description, config.Description,
config.Endpoint, config.Endpoint,
code.Crypted,
), ),
) )
if err != nil { if err != nil {
@@ -198,6 +206,8 @@ type ChangeSMSHTTP struct {
Description *string Description *string
Endpoint *string Endpoint *string
ExpirationSigningKey bool
SigningKey *string
} }
func (c *Commands) ChangeSMSConfigHTTP(ctx context.Context, config *ChangeSMSHTTP) (err error) { func (c *Commands) ChangeSMSConfigHTTP(ctx context.Context, config *ChangeSMSHTTP) (err error) {
@@ -214,12 +224,24 @@ func (c *Commands) ChangeSMSConfigHTTP(ctx context.Context, config *ChangeSMSHTT
if !smsConfigWriteModel.State.Exists() || smsConfigWriteModel.HTTP == nil { if !smsConfigWriteModel.State.Exists() || smsConfigWriteModel.HTTP == nil {
return zerrors.ThrowNotFound(nil, "COMMAND-6NW4I5Kqzj", "Errors.SMSConfig.NotFound") return zerrors.ThrowNotFound(nil, "COMMAND-6NW4I5Kqzj", "Errors.SMSConfig.NotFound")
} }
var changedSigningKey *crypto.CryptoValue
if config.ExpirationSigningKey {
code, err := c.newSigningKey(ctx, c.eventstore.Filter, c.smtpEncryption) //nolint
if err != nil {
return err
}
changedSigningKey = code.Crypted
config.SigningKey = &code.Plain
}
changedEvent, hasChanged, err := smsConfigWriteModel.NewHTTPChangedEvent( changedEvent, hasChanged, err := smsConfigWriteModel.NewHTTPChangedEvent(
ctx, ctx,
InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel), InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
config.ID, config.ID,
config.Description, config.Description,
config.Endpoint) config.Endpoint,
changedSigningKey)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -28,6 +28,7 @@ type TwilioConfig struct {
type HTTPConfig struct { type HTTPConfig struct {
Endpoint string Endpoint string
SigningKey *crypto.CryptoValue
} }
func NewIAMSMSConfigWriteModel(instanceID, id string) *IAMSMSConfigWriteModel { func NewIAMSMSConfigWriteModel(instanceID, id string) *IAMSMSConfigWriteModel {
@@ -83,6 +84,7 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
} }
wm.HTTP = &HTTPConfig{ wm.HTTP = &HTTPConfig{
Endpoint: e.Endpoint, Endpoint: e.Endpoint,
SigningKey: e.SigningKey,
} }
wm.Description = e.Description wm.Description = e.Description
wm.State = domain.SMSConfigStateInactive wm.State = domain.SMSConfigStateInactive
@@ -96,6 +98,9 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
if e.Endpoint != nil { if e.Endpoint != nil {
wm.HTTP.Endpoint = *e.Endpoint wm.HTTP.Endpoint = *e.Endpoint
} }
if e.SigningKey != nil {
wm.HTTP.SigningKey = e.SigningKey
}
case *instance.SMSConfigTwilioActivatedEvent: case *instance.SMSConfigTwilioActivatedEvent:
if wm.ID != e.ID { if wm.ID != e.ID {
wm.State = domain.SMSConfigStateInactive wm.State = domain.SMSConfigStateInactive
@@ -189,7 +194,13 @@ func (wm *IAMSMSConfigWriteModel) NewTwilioChangedEvent(ctx context.Context, agg
return changeEvent, true, nil return changeEvent, true, nil
} }
func (wm *IAMSMSConfigWriteModel) NewHTTPChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id string, description, endpoint *string) (*instance.SMSConfigHTTPChangedEvent, bool, error) { func (wm *IAMSMSConfigWriteModel) NewHTTPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id string,
description, endpoint *string,
signingKey *crypto.CryptoValue,
) (*instance.SMSConfigHTTPChangedEvent, bool, error) {
changes := make([]instance.SMSConfigHTTPChanges, 0) changes := make([]instance.SMSConfigHTTPChanges, 0)
var err error var err error
@@ -203,6 +214,10 @@ func (wm *IAMSMSConfigWriteModel) NewHTTPChangedEvent(ctx context.Context, aggre
if endpoint != nil && wm.HTTP.Endpoint != *endpoint { if endpoint != nil && wm.HTTP.Endpoint != *endpoint {
changes = append(changes, instance.ChangeSMSConfigHTTPEndpoint(*endpoint)) changes = append(changes, instance.ChangeSMSConfigHTTPEndpoint(*endpoint))
} }
// if signingkey is set, update it as it is encrypted
if signingKey != nil {
changes = append(changes, instance.ChangeSMSConfigHTTPSigningKey(signingKey))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false, nil return nil, false, nil

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"testing" "testing"
"time"
"github.com/muhlemmer/gu" "github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -308,6 +309,8 @@ func TestCommandSide_AddSMSConfigHTTP(t *testing.T) {
type fields struct { type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
alg crypto.EncryptionAlgorithm alg crypto.EncryptionAlgorithm
} }
type args struct { type args struct {
@@ -351,10 +354,18 @@ func TestCommandSide_AddSMSConfigHTTP(t *testing.T) {
"providerid", "providerid",
"description", "description",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "providerid"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "providerid"),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour),
defaultSecretGenerators: &SecretGenerators{},
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@@ -376,6 +387,8 @@ func TestCommandSide_AddSMSConfigHTTP(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
smsEncryption: tt.fields.alg, smsEncryption: tt.fields.alg,
} }
err := r.AddSMSConfigHTTP(tt.args.ctx, tt.args.http) err := r.AddSMSConfigHTTP(tt.args.ctx, tt.args.http)
@@ -395,6 +408,8 @@ func TestCommandSide_AddSMSConfigHTTP(t *testing.T) {
func TestCommandSide_ChangeSMSConfigHTTP(t *testing.T) { func TestCommandSide_ChangeSMSConfigHTTP(t *testing.T) {
type fields struct { type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@@ -474,6 +489,12 @@ func TestCommandSide_ChangeSMSConfigHTTP(t *testing.T) {
"providerid", "providerid",
"description", "description",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
@@ -505,6 +526,12 @@ func TestCommandSide_ChangeSMSConfigHTTP(t *testing.T) {
"providerid", "providerid",
"description", "description",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
@@ -514,9 +541,17 @@ func TestCommandSide_ChangeSMSConfigHTTP(t *testing.T) {
"providerid", "providerid",
"endpoint2", "endpoint2",
"description2", "description2",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("87654321"),
},
), ),
), ),
), ),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("87654321", time.Hour),
defaultSecretGenerators: &SecretGenerators{},
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@@ -525,6 +560,7 @@ func TestCommandSide_ChangeSMSConfigHTTP(t *testing.T) {
ID: "providerid", ID: "providerid",
Description: gu.Ptr("description2"), Description: gu.Ptr("description2"),
Endpoint: gu.Ptr("endpoint2"), Endpoint: gu.Ptr("endpoint2"),
ExpirationSigningKey: true,
}, },
}, },
res: res{ res: res{
@@ -538,6 +574,8 @@ func TestCommandSide_ChangeSMSConfigHTTP(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
} }
err := r.ChangeSMSConfigHTTP(tt.args.ctx, tt.args.http) err := r.ChangeSMSConfigHTTP(tt.args.ctx, tt.args.http)
if tt.res.err == nil { if tt.res.err == nil {
@@ -707,6 +745,12 @@ func TestCommandSide_ActivateSMSConfig(t *testing.T) {
"providerid", "providerid",
"description", "description",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
@@ -917,6 +961,12 @@ func TestCommandSide_DeactivateSMSConfig(t *testing.T) {
"providerid", "providerid",
"description", "description",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
eventFromEventPusher( eventFromEventPusher(
@@ -1083,6 +1133,12 @@ func TestCommandSide_RemoveSMSConfig(t *testing.T) {
"providerid", "providerid",
"description", "description",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
@@ -1141,10 +1197,11 @@ func newSMSConfigTwilioChangedEvent(ctx context.Context, id, sid, senderName, de
return event return event
} }
func newSMSConfigHTTPChangedEvent(ctx context.Context, id, endpoint, description string) *instance.SMSConfigHTTPChangedEvent { func newSMSConfigHTTPChangedEvent(ctx context.Context, id, endpoint, description string, signingKey *crypto.CryptoValue) *instance.SMSConfigHTTPChangedEvent {
changes := []instance.SMSConfigHTTPChanges{ changes := []instance.SMSConfigHTTPChanges{
instance.ChangeSMSConfigHTTPEndpoint(endpoint), instance.ChangeSMSConfigHTTPEndpoint(endpoint),
instance.ChangeSMSConfigHTTPDescription(description), instance.ChangeSMSConfigHTTPDescription(description),
instance.ChangeSMSConfigHTTPSigningKey(signingKey),
} }
event, _ := instance.NewSMSConfigHTTPChangedEvent(ctx, event, _ := instance.NewSMSConfigHTTPChangedEvent(ctx,
&instance.NewAggregate("INSTANCE").Aggregate, &instance.NewAggregate("INSTANCE").Aggregate,

View File

@@ -230,6 +230,7 @@ type AddSMTPConfigHTTP struct {
Description string Description string
Endpoint string Endpoint string
SigningKey string
} }
func (c *Commands) AddSMTPConfigHTTP(ctx context.Context, config *AddSMTPConfigHTTP) (err error) { func (c *Commands) AddSMTPConfigHTTP(ctx context.Context, config *AddSMTPConfigHTTP) (err error) {
@@ -248,12 +249,19 @@ func (c *Commands) AddSMTPConfigHTTP(ctx context.Context, config *AddSMTPConfigH
return err return err
} }
code, err := c.newSigningKey(ctx, c.eventstore.Filter, c.smtpEncryption) //nolint
if err != nil {
return err
}
config.SigningKey = code.PlainCode()
err = c.pushAppendAndReduce(ctx, smtpConfigWriteModel, instance.NewSMTPConfigHTTPAddedEvent( err = c.pushAppendAndReduce(ctx, smtpConfigWriteModel, instance.NewSMTPConfigHTTPAddedEvent(
ctx, ctx,
InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel),
config.ID, config.ID,
config.Description, config.Description,
config.Endpoint, config.Endpoint,
code.Crypted,
)) ))
if err != nil { if err != nil {
return err return err
@@ -269,6 +277,8 @@ type ChangeSMTPConfigHTTP struct {
Description string Description string
Endpoint string Endpoint string
ExpirationSigningKey bool
SigningKey *string
} }
func (c *Commands) ChangeSMTPConfigHTTP(ctx context.Context, config *ChangeSMTPConfigHTTP) (err error) { func (c *Commands) ChangeSMTPConfigHTTP(ctx context.Context, config *ChangeSMTPConfigHTTP) (err error) {
@@ -288,12 +298,23 @@ func (c *Commands) ChangeSMTPConfigHTTP(ctx context.Context, config *ChangeSMTPC
return zerrors.ThrowNotFound(nil, "COMMAND-xIrdledqv4", "Errors.SMTPConfig.NotFound") return zerrors.ThrowNotFound(nil, "COMMAND-xIrdledqv4", "Errors.SMTPConfig.NotFound")
} }
var changedSigningKey *crypto.CryptoValue
if config.ExpirationSigningKey {
code, err := c.newSigningKey(ctx, c.eventstore.Filter, c.smtpEncryption) //nolint
if err != nil {
return err
}
changedSigningKey = code.Crypted
config.SigningKey = &code.Plain
}
changedEvent, hasChanged, err := smtpConfigWriteModel.NewHTTPChangedEvent( changedEvent, hasChanged, err := smtpConfigWriteModel.NewHTTPChangedEvent(
ctx, ctx,
InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel), InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel),
config.ID, config.ID,
config.Description, config.Description,
config.Endpoint, config.Endpoint,
changedSigningKey,
) )
if err != nil { if err != nil {
return err return err

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock" "go.uber.org/mock/gomock"
@@ -903,6 +904,8 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) {
func TestCommandSide_AddSMTPConfigHTTP(t *testing.T) { func TestCommandSide_AddSMTPConfigHTTP(t *testing.T) {
type fields struct { type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
idGenerator id.Generator idGenerator id.Generator
} }
type args struct { type args struct {
@@ -944,10 +947,18 @@ func TestCommandSide_AddSMTPConfigHTTP(t *testing.T) {
"configid", "configid",
"test", "test",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("12345678", time.Hour),
defaultSecretGenerators: &SecretGenerators{},
}, },
args: args{ args: args{
http: &AddSMTPConfigHTTP{ http: &AddSMTPConfigHTTP{
@@ -968,6 +979,8 @@ func TestCommandSide_AddSMTPConfigHTTP(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
} }
err := r.AddSMTPConfigHTTP(context.Background(), tt.args.http) err := r.AddSMTPConfigHTTP(context.Background(), tt.args.http)
if tt.res.err == nil { if tt.res.err == nil {
@@ -987,6 +1000,8 @@ func TestCommandSide_AddSMTPConfigHTTP(t *testing.T) {
func TestCommandSide_ChangeSMTPConfigHTTP(t *testing.T) { func TestCommandSide_ChangeSMTPConfigHTTP(t *testing.T) {
type fields struct { type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
} }
type args struct { type args struct {
http *ChangeSMTPConfigHTTP http *ChangeSMTPConfigHTTP
@@ -1063,6 +1078,12 @@ func TestCommandSide_ChangeSMTPConfigHTTP(t *testing.T) {
"ID", "ID",
"test", "test",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
@@ -1094,6 +1115,12 @@ func TestCommandSide_ChangeSMTPConfigHTTP(t *testing.T) {
"ID", "ID",
"", "",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
@@ -1103,9 +1130,17 @@ func TestCommandSide_ChangeSMTPConfigHTTP(t *testing.T) {
"ID", "ID",
"test", "test",
"endpoint2", "endpoint2",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("87654321"),
},
), ),
), ),
), ),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("87654321", time.Hour),
defaultSecretGenerators: &SecretGenerators{},
}, },
args: args{ args: args{
http: &ChangeSMTPConfigHTTP{ http: &ChangeSMTPConfigHTTP{
@@ -1113,6 +1148,7 @@ func TestCommandSide_ChangeSMTPConfigHTTP(t *testing.T) {
ID: "ID", ID: "ID",
Description: "test", Description: "test",
Endpoint: "endpoint2", Endpoint: "endpoint2",
ExpirationSigningKey: true,
}, },
}, },
res: res{ res: res{
@@ -1126,6 +1162,8 @@ func TestCommandSide_ChangeSMTPConfigHTTP(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
} }
err := r.ChangeSMTPConfigHTTP(context.Background(), tt.args.http) err := r.ChangeSMTPConfigHTTP(context.Background(), tt.args.http)
if tt.res.err == nil { if tt.res.err == nil {
@@ -1300,6 +1338,12 @@ func TestCommandSide_ActivateSMTPConfig(t *testing.T) {
"ID", "ID",
"test", "test",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
@@ -1511,6 +1555,12 @@ func TestCommandSide_DeactivateSMTPConfig(t *testing.T) {
"ID", "ID",
"test", "test",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
eventFromEventPusher( eventFromEventPusher(
@@ -1677,6 +1727,12 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) {
"ID", "ID",
"test", "test",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("12345678"),
},
), ),
), ),
), ),
@@ -2051,10 +2107,11 @@ func newSMTPConfigChangedEvent(ctx context.Context, id, description string, tls
return event return event
} }
func newSMTPConfigHTTPChangedEvent(ctx context.Context, id, description, endpoint string) *instance.SMTPConfigHTTPChangedEvent { func newSMTPConfigHTTPChangedEvent(ctx context.Context, id, description, endpoint string, signingKey *crypto.CryptoValue) *instance.SMTPConfigHTTPChangedEvent {
changes := []instance.SMTPConfigHTTPChanges{ changes := []instance.SMTPConfigHTTPChanges{
instance.ChangeSMTPConfigHTTPDescription(description), instance.ChangeSMTPConfigHTTPDescription(description),
instance.ChangeSMTPConfigHTTPEndpoint(endpoint), instance.ChangeSMTPConfigHTTPEndpoint(endpoint),
instance.ChangeSMTPConfigHTTPSigningKey(signingKey),
} }
event, _ := instance.NewSMTPConfigHTTPChangeEvent(ctx, event, _ := instance.NewSMTPConfigHTTPChangeEvent(ctx,
&instance.NewAggregate("INSTANCE").Aggregate, &instance.NewAggregate("INSTANCE").Aggregate,

View File

@@ -12,6 +12,11 @@ import (
"github.com/zitadel/zitadel/internal/notification/channels" "github.com/zitadel/zitadel/internal/notification/channels"
"github.com/zitadel/zitadel/internal/notification/messages" "github.com/zitadel/zitadel/internal/notification/messages"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
"github.com/zitadel/zitadel/pkg/actions"
)
const (
SigningHeader = "ZITADEL-Signature"
) )
func InitChannel(ctx context.Context, cfg Config) (channels.NotificationChannel, error) { func InitChannel(ctx context.Context, cfg Config) (channels.NotificationChannel, error) {
@@ -39,6 +44,10 @@ func InitChannel(ctx context.Context, cfg Config) (channels.NotificationChannel,
req.Header = cfg.Headers req.Header = cfg.Headers
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
if cfg.SigningKey != "" {
req.Header.Set(SigningHeader, actions.ComputeSignatureHeader(time.Now(), []byte(payload), cfg.SigningKey))
}
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return err return err

View File

@@ -9,6 +9,7 @@ type Config struct {
CallURL string CallURL string
Method string Method string
Headers http.Header Headers http.Header
SigningKey string
} }
func (w *Config) Validate() error { func (w *Config) Validate() error {

View File

@@ -52,6 +52,7 @@ func (n *NotificationQueries) GetActiveEmailConfig(ctx context.Context) (*email.
CallURL: config.HTTPConfig.Endpoint, CallURL: config.HTTPConfig.Endpoint,
Method: http.MethodPost, Method: http.MethodPost,
Headers: nil, Headers: nil,
SigningKey: config.HTTPConfig.SigningKey,
}, },
}, nil }, nil
} }

View File

@@ -48,6 +48,7 @@ func (n *NotificationQueries) GetActiveSMSConfig(ctx context.Context) (*sms.Conf
CallURL: config.HTTPConfig.Endpoint, CallURL: config.HTTPConfig.Endpoint,
Method: http.MethodPost, Method: http.MethodPost,
Headers: nil, Headers: nil,
SigningKey: config.HTTPConfig.SigningKey,
}, },
}, nil }, nil
} }

View File

@@ -37,6 +37,7 @@ const (
SMSHTTPColumnSMSID = "sms_id" SMSHTTPColumnSMSID = "sms_id"
SMSHTTPColumnInstanceID = "instance_id" SMSHTTPColumnInstanceID = "instance_id"
SMSHTTPColumnEndpoint = "endpoint" SMSHTTPColumnEndpoint = "endpoint"
SMSHTTPColumnSigningKey = "signing_key"
) )
type smsConfigProjection struct{} type smsConfigProjection struct{}
@@ -80,6 +81,7 @@ func (*smsConfigProjection) Init() *old_handler.Check {
handler.NewColumn(SMSHTTPColumnSMSID, handler.ColumnTypeText), handler.NewColumn(SMSHTTPColumnSMSID, handler.ColumnTypeText),
handler.NewColumn(SMSHTTPColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(SMSHTTPColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(SMSHTTPColumnEndpoint, handler.ColumnTypeText), handler.NewColumn(SMSHTTPColumnEndpoint, handler.ColumnTypeText),
handler.NewColumn(SMSHTTPColumnSigningKey, handler.ColumnTypeJSONB, handler.Nullable()),
}, },
handler.NewPrimaryKey(SMSHTTPColumnInstanceID, SMSHTTPColumnSMSID), handler.NewPrimaryKey(SMSHTTPColumnInstanceID, SMSHTTPColumnSMSID),
smsHTTPTableSuffix, smsHTTPTableSuffix,
@@ -286,6 +288,7 @@ func (p *smsConfigProjection) reduceSMSConfigHTTPAdded(event eventstore.Event) (
handler.NewCol(SMSHTTPColumnSMSID, e.ID), handler.NewCol(SMSHTTPColumnSMSID, e.ID),
handler.NewCol(SMSHTTPColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(SMSHTTPColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMSHTTPColumnEndpoint, e.Endpoint), handler.NewCol(SMSHTTPColumnEndpoint, e.Endpoint),
handler.NewCol(SMSHTTPColumnSigningKey, e.SigningKey),
}, },
handler.WithTableSuffix(smsHTTPTableSuffix), handler.WithTableSuffix(smsHTTPTableSuffix),
), ),
@@ -306,7 +309,6 @@ func (p *smsConfigProjection) reduceSMSConfigHTTPChanged(event eventstore.Event)
if e.Description != nil { if e.Description != nil {
columns = append(columns, handler.NewCol(SMSColumnDescription, *e.Description)) columns = append(columns, handler.NewCol(SMSColumnDescription, *e.Description))
} }
if len(columns) > 0 {
stmts = append(stmts, handler.AddUpdateStatement( stmts = append(stmts, handler.AddUpdateStatement(
columns, columns,
[]handler.Condition{ []handler.Condition{
@@ -314,13 +316,17 @@ func (p *smsConfigProjection) reduceSMSConfigHTTPChanged(event eventstore.Event)
handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID), handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID),
}, },
)) ))
}
httpColumns := make([]handler.Column, 0)
if e.SigningKey != nil {
httpColumns = append(httpColumns, handler.NewCol(SMSHTTPColumnSigningKey, e.SigningKey))
}
if e.Endpoint != nil { if e.Endpoint != nil {
httpColumns = append(httpColumns, handler.NewCol(SMSHTTPColumnEndpoint, *e.Endpoint))
}
if len(httpColumns) > 0 {
stmts = append(stmts, handler.AddUpdateStatement( stmts = append(stmts, handler.AddUpdateStatement(
[]handler.Column{ httpColumns,
handler.NewCol(SMSHTTPColumnEndpoint, *e.Endpoint),
},
[]handler.Condition{ []handler.Condition{
handler.NewCond(SMSHTTPColumnSMSID, e.ID), handler.NewCond(SMSHTTPColumnSMSID, e.ID),
handler.NewCond(SMSHTTPColumnInstanceID, e.Aggregate().InstanceID), handler.NewCond(SMSHTTPColumnInstanceID, e.Aggregate().InstanceID),

View File

@@ -302,7 +302,8 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"id": "id", "id": "id",
"description": "description", "description": "description",
"endpoint": "endpoint" "endpoint": "endpoint",
"signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }
}`), }`),
), eventstore.GenericEventMapper[instance.SMSConfigHTTPAddedEvent]), ), eventstore.GenericEventMapper[instance.SMSConfigHTTPAddedEvent]),
}, },
@@ -327,11 +328,12 @@ func TestSMSProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.sms_configs3_http (sms_id, instance_id, endpoint) VALUES ($1, $2, $3)", expectedStmt: "INSERT INTO projections.sms_configs3_http (sms_id, instance_id, endpoint, signing_key) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"id", "id",
"instance-id", "instance-id",
"endpoint", "endpoint",
anyArg{},
}, },
}, },
}, },
@@ -348,7 +350,8 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"id": "id", "id": "id",
"endpoint": "endpoint", "endpoint": "endpoint",
"description": "description" "description": "description",
"signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }
}`), }`),
), eventstore.GenericEventMapper[instance.SMSConfigHTTPChangedEvent]), ), eventstore.GenericEventMapper[instance.SMSConfigHTTPChangedEvent]),
}, },
@@ -369,8 +372,9 @@ func TestSMSProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.sms_configs3_http SET endpoint = $1 WHERE (sms_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.sms_configs3_http SET (signing_key, endpoint) = ($1, $2) WHERE (sms_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{},
"endpoint", "endpoint",
"id", "id",
"instance-id", "instance-id",
@@ -452,6 +456,46 @@ func TestSMSProjection_reduces(t *testing.T) {
}, },
}, },
}, },
{
name: "instance reduceSMSConfigHTTPChanged, only signing key",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigHTTPChangedEventType,
instance.AggregateType,
[]byte(`{
"id": "id",
"signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }
}`),
), eventstore.GenericEventMapper[instance.SMSConfigHTTPChangedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigHTTPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.sms_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.sms_configs3_http SET signing_key = $1 WHERE (sms_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
anyArg{},
"id",
"instance-id",
},
},
},
},
},
},
{ {
name: "instance reduceSMSConfigTwilioActivated", name: "instance reduceSMSConfigTwilioActivated",
args: args{ args: args{

View File

@@ -40,6 +40,7 @@ const (
SMTPConfigHTTPColumnInstanceID = "instance_id" SMTPConfigHTTPColumnInstanceID = "instance_id"
SMTPConfigHTTPColumnID = "id" SMTPConfigHTTPColumnID = "id"
SMTPConfigHTTPColumnEndpoint = "endpoint" SMTPConfigHTTPColumnEndpoint = "endpoint"
SMTPConfigHTTPColumnSigningKey = "signing_key"
) )
type smtpConfigProjection struct{} type smtpConfigProjection struct{}
@@ -86,6 +87,7 @@ func (*smtpConfigProjection) Init() *old_handler.Check {
handler.NewColumn(SMTPConfigHTTPColumnID, handler.ColumnTypeText), handler.NewColumn(SMTPConfigHTTPColumnID, handler.ColumnTypeText),
handler.NewColumn(SMTPConfigHTTPColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(SMTPConfigHTTPColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(SMTPConfigHTTPColumnEndpoint, handler.ColumnTypeText), handler.NewColumn(SMTPConfigHTTPColumnEndpoint, handler.ColumnTypeText),
handler.NewColumn(SMTPConfigHTTPColumnSigningKey, handler.ColumnTypeJSONB, handler.Nullable()),
}, },
handler.NewPrimaryKey(SMTPConfigHTTPColumnInstanceID, SMTPConfigHTTPColumnID), handler.NewPrimaryKey(SMTPConfigHTTPColumnInstanceID, SMTPConfigHTTPColumnID),
smtpConfigHTTPTableSuffix, smtpConfigHTTPTableSuffix,
@@ -211,6 +213,7 @@ func (p *smtpConfigProjection) reduceSMTPConfigHTTPAdded(event eventstore.Event)
handler.NewCol(SMTPConfigHTTPColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(SMTPConfigHTTPColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMTPConfigHTTPColumnID, getSMTPConfigID(e.ID, e.Aggregate())), handler.NewCol(SMTPConfigHTTPColumnID, getSMTPConfigID(e.ID, e.Aggregate())),
handler.NewCol(SMTPConfigHTTPColumnEndpoint, e.Endpoint), handler.NewCol(SMTPConfigHTTPColumnEndpoint, e.Endpoint),
handler.NewCol(SMTPConfigHTTPColumnSigningKey, e.SigningKey),
}, },
handler.WithTableSuffix(smtpConfigHTTPTableSuffix), handler.WithTableSuffix(smtpConfigHTTPTableSuffix),
), ),
@@ -231,7 +234,6 @@ func (p *smtpConfigProjection) reduceSMTPConfigHTTPChanged(event eventstore.Even
if e.Description != nil { if e.Description != nil {
columns = append(columns, handler.NewCol(SMTPConfigColumnDescription, *e.Description)) columns = append(columns, handler.NewCol(SMTPConfigColumnDescription, *e.Description))
} }
if len(columns) > 0 {
stmts = append(stmts, handler.AddUpdateStatement( stmts = append(stmts, handler.AddUpdateStatement(
columns, columns,
[]handler.Condition{ []handler.Condition{
@@ -239,12 +241,14 @@ func (p *smtpConfigProjection) reduceSMTPConfigHTTPChanged(event eventstore.Even
handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID), handler.NewCond(SMTPConfigColumnInstanceID, e.Aggregate().InstanceID),
}, },
)) ))
}
smtpColumns := make([]handler.Column, 0, 1) smtpColumns := make([]handler.Column, 0, 1)
if e.Endpoint != nil { if e.Endpoint != nil {
smtpColumns = append(smtpColumns, handler.NewCol(SMTPConfigHTTPColumnEndpoint, *e.Endpoint)) smtpColumns = append(smtpColumns, handler.NewCol(SMTPConfigHTTPColumnEndpoint, *e.Endpoint))
} }
if e.SigningKey != nil {
smtpColumns = append(smtpColumns, handler.NewCol(SMTPConfigHTTPColumnSigningKey, e.SigningKey))
}
if len(smtpColumns) > 0 { if len(smtpColumns) > 0 {
stmts = append(stmts, handler.AddUpdateStatement( stmts = append(stmts, handler.AddUpdateStatement(
smtpColumns, smtpColumns,

View File

@@ -225,7 +225,8 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
"aggregate_id": "agg-id", "aggregate_id": "agg-id",
"id": "config-id", "id": "config-id",
"description": "test", "description": "test",
"endpoint": "endpoint" "endpoint": "endpoint",
"signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }
}`, }`,
), ),
), eventstore.GenericEventMapper[instance.SMTPConfigHTTPChangedEvent]), ), eventstore.GenericEventMapper[instance.SMTPConfigHTTPChangedEvent]),
@@ -247,9 +248,10 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.smtp_configs5_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.smtp_configs5_http SET (endpoint, signing_key) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"endpoint", "endpoint",
anyArg{},
"config-id", "config-id",
"instance-id", "instance-id",
}, },
@@ -338,6 +340,49 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
}, },
}, {
name: "reduceSMTPConfigHTTPChanged, signing key",
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",
"signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }
}`,
),
), eventstore.GenericEventMapper[instance.SMTPConfigHTTPChangedEvent]),
},
reduce: (&smtpConfigProjection{}).reduceSMTPConfigHTTPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 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_configs5_http SET signing_key = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
anyArg{},
"config-id",
"instance-id",
},
},
},
},
},
}, },
{ {
name: "reduceSMTPConfigAdded (no id)", name: "reduceSMTPConfigAdded (no id)",
@@ -481,7 +526,8 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
"id": "config-id", "id": "config-id",
"description": "test", "description": "test",
"senderAddress": "sender", "senderAddress": "sender",
"endpoint": "endpoint" "endpoint": "endpoint",
"signingKey": { "cryptoType": 0, "algorithm": "RSA-265", "keyId": "key-id" }
}`), }`),
), eventstore.GenericEventMapper[instance.SMTPConfigHTTPAddedEvent]), ), eventstore.GenericEventMapper[instance.SMTPConfigHTTPAddedEvent]),
}, },
@@ -506,11 +552,12 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.smtp_configs5_http (instance_id, id, endpoint) VALUES ($1, $2, $3)", expectedStmt: "INSERT INTO projections.smtp_configs5_http (instance_id, id, endpoint, signing_key) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"instance-id", "instance-id",
"config-id", "config-id",
"endpoint", "endpoint",
anyArg{},
}, },
}, },
}, },

View File

@@ -32,6 +32,8 @@ type Queries struct {
keyEncryptionAlgorithm crypto.EncryptionAlgorithm keyEncryptionAlgorithm crypto.EncryptionAlgorithm
idpConfigEncryption crypto.EncryptionAlgorithm idpConfigEncryption crypto.EncryptionAlgorithm
targetEncryptionAlgorithm crypto.EncryptionAlgorithm targetEncryptionAlgorithm crypto.EncryptionAlgorithm
smtpEncryptionAlgorithm crypto.EncryptionAlgorithm
smsEncryptionAlgorithm crypto.EncryptionAlgorithm
sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error) sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error)
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
@@ -53,7 +55,7 @@ func StartQueries(
cacheConnectors connector.Connectors, cacheConnectors connector.Connectors,
projections projection.Config, projections projection.Config,
defaults sd.SystemDefaults, defaults sd.SystemDefaults,
idpConfigEncryption, otpEncryption, keyEncryptionAlgorithm, certEncryptionAlgorithm, targetEncryptionAlgorithm crypto.EncryptionAlgorithm, idpConfigEncryption, otpEncryption, keyEncryptionAlgorithm, certEncryptionAlgorithm, targetEncryptionAlgorithm, smsEncryptionAlgorithm, smtpEncryptionAlgorithm crypto.EncryptionAlgorithm,
zitadelRoles []authz.RoleMapping, zitadelRoles []authz.RoleMapping,
sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error), sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error),
permissionCheck func(q *Queries) domain.PermissionCheck, permissionCheck func(q *Queries) domain.PermissionCheck,
@@ -72,6 +74,8 @@ func StartQueries(
keyEncryptionAlgorithm: keyEncryptionAlgorithm, keyEncryptionAlgorithm: keyEncryptionAlgorithm,
idpConfigEncryption: idpConfigEncryption, idpConfigEncryption: idpConfigEncryption,
targetEncryptionAlgorithm: targetEncryptionAlgorithm, targetEncryptionAlgorithm: targetEncryptionAlgorithm,
smsEncryptionAlgorithm: smsEncryptionAlgorithm,
smtpEncryptionAlgorithm: smtpEncryptionAlgorithm,
sessionTokenVerifier: sessionTokenVerifier, sessionTokenVerifier: sessionTokenVerifier,
multifactors: domain.MultifactorConfigs{ multifactors: domain.MultifactorConfigs{
OTP: domain.OTPConfig{ OTP: domain.OTPConfig{

View File

@@ -35,6 +35,13 @@ type SMSConfig struct {
HTTPConfig *HTTP HTTPConfig *HTTP
} }
func (h *SMSConfig) decryptSigningKey(alg crypto.EncryptionAlgorithm) error {
if h.HTTPConfig == nil {
return nil
}
return h.HTTPConfig.decryptSigningKey(alg)
}
type Twilio struct { type Twilio struct {
SID string SID string
Token *crypto.CryptoValue Token *crypto.CryptoValue
@@ -44,6 +51,20 @@ type Twilio struct {
type HTTP struct { type HTTP struct {
Endpoint string Endpoint string
signingKey *crypto.CryptoValue
SigningKey string
}
func (h *HTTP) decryptSigningKey(alg crypto.EncryptionAlgorithm) error {
if h.signingKey == nil {
return nil
}
keyValue, err := crypto.DecryptString(h.signingKey, alg)
if err != nil {
return zerrors.ThrowInternal(err, "QUERY-bxovy3YXwy", "Errors.Internal")
}
h.SigningKey = keyValue
return nil
} }
type SMSConfigsSearchQueries struct { type SMSConfigsSearchQueries struct {
@@ -142,6 +163,10 @@ var (
name: projection.SMSHTTPColumnEndpoint, name: projection.SMSHTTPColumnEndpoint,
table: smsHTTPTable, table: smsHTTPTable,
} }
SMSHTTPColumnSigningKey = Column{
name: projection.SMSHTTPColumnSigningKey,
table: smsHTTPTable,
}
) )
func (q *Queries) SMSProviderConfigByID(ctx context.Context, id string) (config *SMSConfig, err error) { func (q *Queries) SMSProviderConfigByID(ctx context.Context, id string) (config *SMSConfig, err error) {
@@ -163,7 +188,13 @@ func (q *Queries) SMSProviderConfigByID(ctx context.Context, id string) (config
config, err = scan(row) config, err = scan(row)
return err return err
}, stmt, args...) }, stmt, args...)
return config, err if err != nil {
return nil, err
}
if err := config.decryptSigningKey(q.smsEncryptionAlgorithm); err != nil {
return nil, err
}
return config, nil
} }
func (q *Queries) SMSProviderConfigActive(ctx context.Context, instanceID string) (config *SMSConfig, err error) { func (q *Queries) SMSProviderConfigActive(ctx context.Context, instanceID string) (config *SMSConfig, err error) {
@@ -185,7 +216,13 @@ func (q *Queries) SMSProviderConfigActive(ctx context.Context, instanceID string
config, err = scan(row) config, err = scan(row)
return err return err
}, stmt, args...) }, stmt, args...)
return config, err if err != nil {
return nil, err
}
if err := config.decryptSigningKey(q.smsEncryptionAlgorithm); err != nil {
return nil, err
}
return config, nil
} }
func (q *Queries) SearchSMSConfigs(ctx context.Context, queries *SMSConfigsSearchQueries) (configs *SMSConfigs, err error) { func (q *Queries) SearchSMSConfigs(ctx context.Context, queries *SMSConfigsSearchQueries) (configs *SMSConfigs, err error) {
@@ -208,8 +245,13 @@ func (q *Queries) SearchSMSConfigs(ctx context.Context, queries *SMSConfigsSearc
if err != nil { if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-l4bxm", "Errors.Internal") return nil, zerrors.ThrowInternal(err, "QUERY-l4bxm", "Errors.Internal")
} }
for i := range configs.Configs {
if err := configs.Configs[i].decryptSigningKey(q.smsEncryptionAlgorithm); err != nil {
return nil, err
}
}
configs.State, err = q.latestState(ctx, smsConfigsTable) configs.State, err = q.latestState(ctx, smsConfigsTable)
return configs, err return configs, nil
} }
func NewSMSProviderStateQuery(state domain.SMSConfigState) (SearchQuery, error) { func NewSMSProviderStateQuery(state domain.SMSConfigState) (SearchQuery, error) {
@@ -235,6 +277,7 @@ func prepareSMSConfigQuery() (sq.SelectBuilder, func(*sql.Row) (*SMSConfig, erro
SMSHTTPColumnSMSID.identifier(), SMSHTTPColumnSMSID.identifier(),
SMSHTTPColumnEndpoint.identifier(), SMSHTTPColumnEndpoint.identifier(),
SMSHTTPColumnSigningKey.identifier(),
).From(smsConfigsTable.identifier()). ).From(smsConfigsTable.identifier()).
LeftJoin(join(SMSTwilioColumnSMSID, SMSColumnID)). LeftJoin(join(SMSTwilioColumnSMSID, SMSColumnID)).
LeftJoin(join(SMSHTTPColumnSMSID, SMSColumnID)). LeftJoin(join(SMSHTTPColumnSMSID, SMSColumnID)).
@@ -264,6 +307,7 @@ func prepareSMSConfigQuery() (sq.SelectBuilder, func(*sql.Row) (*SMSConfig, erro
&httpConfig.id, &httpConfig.id,
&httpConfig.endpoint, &httpConfig.endpoint,
&httpConfig.signingKey,
) )
if err != nil { if err != nil {
@@ -299,6 +343,7 @@ func prepareSMSConfigsQuery() (sq.SelectBuilder, func(*sql.Rows) (*SMSConfigs, e
SMSHTTPColumnSMSID.identifier(), SMSHTTPColumnSMSID.identifier(),
SMSHTTPColumnEndpoint.identifier(), SMSHTTPColumnEndpoint.identifier(),
SMSHTTPColumnSigningKey.identifier(),
countColumn.identifier(), countColumn.identifier(),
).From(smsConfigsTable.identifier()). ).From(smsConfigsTable.identifier()).
@@ -332,6 +377,7 @@ func prepareSMSConfigsQuery() (sq.SelectBuilder, func(*sql.Rows) (*SMSConfigs, e
&httpConfig.id, &httpConfig.id,
&httpConfig.endpoint, &httpConfig.endpoint,
&httpConfig.signingKey,
&configs.Count, &configs.Count,
) )
@@ -373,6 +419,7 @@ func (c sqlTwilioConfig) set(smsConfig *SMSConfig) {
type sqlHTTPConfig struct { type sqlHTTPConfig struct {
id sql.NullString id sql.NullString
endpoint sql.NullString endpoint sql.NullString
signingKey *crypto.CryptoValue
} }
func (c sqlHTTPConfig) setSMS(smsConfig *SMSConfig) { func (c sqlHTTPConfig) setSMS(smsConfig *SMSConfig) {
@@ -381,5 +428,6 @@ func (c sqlHTTPConfig) setSMS(smsConfig *SMSConfig) {
} }
smsConfig.HTTPConfig = &HTTP{ smsConfig.HTTPConfig = &HTTP{
Endpoint: c.endpoint.String, Endpoint: c.endpoint.String,
signingKey: c.signingKey,
} }
} }

View File

@@ -32,7 +32,8 @@ var (
// http config // http config
` projections.sms_configs3_http.sms_id,` + ` projections.sms_configs3_http.sms_id,` +
` projections.sms_configs3_http.endpoint` + ` projections.sms_configs3_http.endpoint,` +
` projections.sms_configs3_http.signing_key` +
` FROM projections.sms_configs3` + ` FROM projections.sms_configs3` +
` LEFT JOIN projections.sms_configs3_twilio ON projections.sms_configs3.id = projections.sms_configs3_twilio.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_twilio.instance_id` + ` LEFT JOIN projections.sms_configs3_twilio ON projections.sms_configs3.id = projections.sms_configs3_twilio.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_twilio.instance_id` +
` LEFT JOIN projections.sms_configs3_http ON projections.sms_configs3.id = projections.sms_configs3_http.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_http.instance_id`) ` LEFT JOIN projections.sms_configs3_http ON projections.sms_configs3.id = projections.sms_configs3_http.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_http.instance_id`)
@@ -55,6 +56,7 @@ var (
// http config // http config
` projections.sms_configs3_http.sms_id,` + ` projections.sms_configs3_http.sms_id,` +
` projections.sms_configs3_http.endpoint,` + ` projections.sms_configs3_http.endpoint,` +
` projections.sms_configs3_http.signing_key,` +
` COUNT(*) OVER ()` + ` COUNT(*) OVER ()` +
` FROM projections.sms_configs3` + ` FROM projections.sms_configs3` +
` LEFT JOIN projections.sms_configs3_twilio ON projections.sms_configs3.id = projections.sms_configs3_twilio.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_twilio.instance_id` + ` LEFT JOIN projections.sms_configs3_twilio ON projections.sms_configs3.id = projections.sms_configs3_twilio.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_twilio.instance_id` +
@@ -78,6 +80,7 @@ var (
// http config // http config
"sms_id", "sms_id",
"endpoint", "endpoint",
"signing_key",
} }
smsConfigsCols = append(smsConfigCols, "count") smsConfigsCols = append(smsConfigCols, "count")
) )
@@ -131,6 +134,7 @@ func Test_SMSConfigsPrepare(t *testing.T) {
// http config // http config
nil, nil,
nil, nil,
nil,
}, },
}, },
), ),
@@ -185,6 +189,12 @@ func Test_SMSConfigsPrepare(t *testing.T) {
// http config // http config
"sms-id", "sms-id",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
}, },
}, },
), ),
@@ -205,6 +215,12 @@ func Test_SMSConfigsPrepare(t *testing.T) {
Description: "description", Description: "description",
HTTPConfig: &HTTP{ HTTPConfig: &HTTP{
Endpoint: "endpoint", Endpoint: "endpoint",
signingKey: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
}, },
}, },
}, },
@@ -236,6 +252,7 @@ func Test_SMSConfigsPrepare(t *testing.T) {
// http config // http config
nil, nil,
nil, nil,
nil,
}, },
{ {
"sms-id2", "sms-id2",
@@ -255,6 +272,7 @@ func Test_SMSConfigsPrepare(t *testing.T) {
// http config // http config
nil, nil,
nil, nil,
nil,
}, },
{ {
"sms-id3", "sms-id3",
@@ -274,6 +292,12 @@ func Test_SMSConfigsPrepare(t *testing.T) {
// http config // http config
"sms-id3", "sms-id3",
"endpoint3", "endpoint3",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
}, },
}, },
), ),
@@ -326,6 +350,12 @@ func Test_SMSConfigsPrepare(t *testing.T) {
Description: "description", Description: "description",
HTTPConfig: &HTTP{ HTTPConfig: &HTTP{
Endpoint: "endpoint3", Endpoint: "endpoint3",
signingKey: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
}, },
}, },
}, },
@@ -410,6 +440,7 @@ func Test_SMSConfigPrepare(t *testing.T) {
// http config // http config
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@@ -455,6 +486,12 @@ func Test_SMSConfigPrepare(t *testing.T) {
// http config // http config
"sms-id", "sms-id",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
}, },
), ),
}, },
@@ -469,6 +506,12 @@ func Test_SMSConfigPrepare(t *testing.T) {
Description: "description", Description: "description",
HTTPConfig: &HTTP{ HTTPConfig: &HTTP{
Endpoint: "endpoint", Endpoint: "endpoint",
signingKey: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
}, },
}, },
}, },

View File

@@ -121,6 +121,10 @@ var (
name: projection.SMTPConfigHTTPColumnEndpoint, name: projection.SMTPConfigHTTPColumnEndpoint,
table: smtpConfigsHTTPTable, table: smtpConfigsHTTPTable,
} }
SMTPConfigHTTPColumnSigningKey = Column{
name: projection.SMTPConfigHTTPColumnSigningKey,
table: smtpConfigsHTTPTable,
}
) )
type SMTPConfig struct { type SMTPConfig struct {
@@ -138,6 +142,13 @@ type SMTPConfig struct {
State domain.SMTPConfigState State domain.SMTPConfigState
} }
func (h *SMTPConfig) decryptSigningKey(alg crypto.EncryptionAlgorithm) error {
if h.HTTPConfig == nil {
return nil
}
return h.HTTPConfig.decryptSigningKey(alg)
}
type SMTP struct { type SMTP struct {
TLS bool TLS bool
SenderAddress string SenderAddress string
@@ -166,7 +177,13 @@ func (q *Queries) SMTPConfigActive(ctx context.Context, resourceOwner string) (c
config, err = scan(row) config, err = scan(row)
return err return err
}, query, args...) }, query, args...)
return config, err if err != nil {
return nil, err
}
if err := config.decryptSigningKey(q.smtpEncryptionAlgorithm); err != nil {
return nil, err
}
return config, nil
} }
func (q *Queries) SMTPConfigByID(ctx context.Context, instanceID, id string) (config *SMTPConfig, err error) { func (q *Queries) SMTPConfigByID(ctx context.Context, instanceID, id string) (config *SMTPConfig, err error) {
@@ -186,12 +203,16 @@ func (q *Queries) SMTPConfigByID(ctx context.Context, instanceID, id string) (co
config, err = scan(row) config, err = scan(row)
return err return err
}, query, args...) }, query, args...)
if err != nil {
return nil, err
}
if err := config.decryptSigningKey(q.smtpEncryptionAlgorithm); err != nil {
return nil, err
}
return config, err return config, err
} }
func prepareSMTPConfigQuery() (sq.SelectBuilder, func(*sql.Row) (*SMTPConfig, error)) { func prepareSMTPConfigQuery() (sq.SelectBuilder, func(*sql.Row) (*SMTPConfig, error)) {
password := new(crypto.CryptoValue)
return sq.Select( return sq.Select(
SMTPConfigColumnCreationDate.identifier(), SMTPConfigColumnCreationDate.identifier(),
SMTPConfigColumnChangeDate.identifier(), SMTPConfigColumnChangeDate.identifier(),
@@ -211,7 +232,9 @@ func prepareSMTPConfigQuery() (sq.SelectBuilder, func(*sql.Row) (*SMTPConfig, er
SMTPConfigSMTPColumnPassword.identifier(), SMTPConfigSMTPColumnPassword.identifier(),
SMTPConfigHTTPColumnID.identifier(), SMTPConfigHTTPColumnID.identifier(),
SMTPConfigHTTPColumnEndpoint.identifier()). SMTPConfigHTTPColumnEndpoint.identifier(),
SMTPConfigHTTPColumnSigningKey.identifier(),
).
From(smtpConfigsTable.identifier()). From(smtpConfigsTable.identifier()).
LeftJoin(join(SMTPConfigSMTPColumnID, SMTPConfigColumnID)). LeftJoin(join(SMTPConfigSMTPColumnID, SMTPConfigColumnID)).
LeftJoin(join(SMTPConfigHTTPColumnID, SMTPConfigColumnID)). LeftJoin(join(SMTPConfigHTTPColumnID, SMTPConfigColumnID)).
@@ -237,9 +260,10 @@ func prepareSMTPConfigQuery() (sq.SelectBuilder, func(*sql.Row) (*SMTPConfig, er
&smtpConfig.replyToAddress, &smtpConfig.replyToAddress,
&smtpConfig.host, &smtpConfig.host,
&smtpConfig.user, &smtpConfig.user,
&password, &smtpConfig.password,
&httpConfig.id, &httpConfig.id,
&httpConfig.endpoint, &httpConfig.endpoint,
&httpConfig.signingKey,
) )
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
@@ -247,9 +271,10 @@ func prepareSMTPConfigQuery() (sq.SelectBuilder, func(*sql.Row) (*SMTPConfig, er
} }
return nil, zerrors.ThrowInternal(err, "QUERY-9k87F", "Errors.Internal") return nil, zerrors.ThrowInternal(err, "QUERY-9k87F", "Errors.Internal")
} }
smtpConfig.password = password
smtpConfig.set(config) smtpConfig.set(config)
httpConfig.setSMTP(config) httpConfig.setSMTP(config)
return config, nil return config, nil
} }
} }
@@ -275,6 +300,8 @@ func prepareSMTPConfigsQuery() (sq.SelectBuilder, func(*sql.Rows) (*SMTPConfigs,
SMTPConfigHTTPColumnID.identifier(), SMTPConfigHTTPColumnID.identifier(),
SMTPConfigHTTPColumnEndpoint.identifier(), SMTPConfigHTTPColumnEndpoint.identifier(),
SMTPConfigHTTPColumnSigningKey.identifier(),
countColumn.identifier(), countColumn.identifier(),
).From(smtpConfigsTable.identifier()). ).From(smtpConfigsTable.identifier()).
LeftJoin(join(SMTPConfigSMTPColumnID, SMTPConfigColumnID)). LeftJoin(join(SMTPConfigSMTPColumnID, SMTPConfigColumnID)).
@@ -284,7 +311,6 @@ func prepareSMTPConfigsQuery() (sq.SelectBuilder, func(*sql.Rows) (*SMTPConfigs,
configs := &SMTPConfigs{Configs: []*SMTPConfig{}} configs := &SMTPConfigs{Configs: []*SMTPConfig{}}
for rows.Next() { for rows.Next() {
config := new(SMTPConfig) config := new(SMTPConfig)
password := new(crypto.CryptoValue)
var ( var (
smtpConfig = sqlSmtpConfig{} smtpConfig = sqlSmtpConfig{}
httpConfig = sqlHTTPConfig{} httpConfig = sqlHTTPConfig{}
@@ -304,20 +330,19 @@ func prepareSMTPConfigsQuery() (sq.SelectBuilder, func(*sql.Rows) (*SMTPConfigs,
&smtpConfig.replyToAddress, &smtpConfig.replyToAddress,
&smtpConfig.host, &smtpConfig.host,
&smtpConfig.user, &smtpConfig.user,
&password, &smtpConfig.password,
&httpConfig.id, &httpConfig.id,
&httpConfig.endpoint, &httpConfig.endpoint,
&httpConfig.signingKey,
&configs.Count, &configs.Count,
) )
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, zerrors.ThrowNotFound(err, "QUERY-fwofw", "Errors.SMTPConfig.NotFound")
}
return nil, zerrors.ThrowInternal(err, "QUERY-9k87F", "Errors.Internal") return nil, zerrors.ThrowInternal(err, "QUERY-9k87F", "Errors.Internal")
} }
smtpConfig.password = password
smtpConfig.set(config) smtpConfig.set(config)
httpConfig.setSMTP(config) httpConfig.setSMTP(config)
configs.Configs = append(configs.Configs, config) configs.Configs = append(configs.Configs, config)
} }
return configs, nil return configs, nil
@@ -344,6 +369,11 @@ func (q *Queries) SearchSMTPConfigs(ctx context.Context, queries *SMTPConfigsSea
if err != nil { if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-tOpKN", "Errors.Internal") return nil, zerrors.ThrowInternal(err, "QUERY-tOpKN", "Errors.Internal")
} }
for i := range configs.Configs {
if err := configs.Configs[i].decryptSigningKey(q.smtpEncryptionAlgorithm); err != nil {
return nil, err
}
}
configs.State, err = q.latestState(ctx, smsConfigsTable) configs.State, err = q.latestState(ctx, smsConfigsTable)
return configs, err return configs, err
} }
@@ -380,5 +410,6 @@ func (c sqlHTTPConfig) setSMTP(smtpConfig *SMTPConfig) {
} }
smtpConfig.HTTPConfig = &HTTP{ smtpConfig.HTTPConfig = &HTTP{
Endpoint: c.endpoint.String, Endpoint: c.endpoint.String,
signingKey: c.signingKey,
} }
} }

View File

@@ -30,10 +30,34 @@ var (
` projections.smtp_configs5_smtp.username,` + ` projections.smtp_configs5_smtp.username,` +
` projections.smtp_configs5_smtp.password,` + ` projections.smtp_configs5_smtp.password,` +
` projections.smtp_configs5_http.id,` + ` projections.smtp_configs5_http.id,` +
` projections.smtp_configs5_http.endpoint` + ` projections.smtp_configs5_http.endpoint,` +
` projections.smtp_configs5_http.signing_key` +
` FROM projections.smtp_configs5` + ` FROM projections.smtp_configs5` +
` LEFT JOIN projections.smtp_configs5_smtp ON projections.smtp_configs5.id = projections.smtp_configs5_smtp.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_smtp.instance_id` + ` LEFT JOIN projections.smtp_configs5_smtp ON projections.smtp_configs5.id = projections.smtp_configs5_smtp.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_smtp.instance_id` +
` LEFT JOIN projections.smtp_configs5_http ON projections.smtp_configs5.id = projections.smtp_configs5_http.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_http.instance_id` ` LEFT JOIN projections.smtp_configs5_http ON projections.smtp_configs5.id = projections.smtp_configs5_http.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_http.instance_id`
prepareSMTPConfigsStmt = `SELECT projections.smtp_configs5.creation_date,` +
` projections.smtp_configs5.change_date,` +
` projections.smtp_configs5.resource_owner,` +
` projections.smtp_configs5.sequence,` +
` projections.smtp_configs5.id,` +
` projections.smtp_configs5.state,` +
` projections.smtp_configs5.description,` +
` projections.smtp_configs5_smtp.id,` +
` projections.smtp_configs5_smtp.tls,` +
` projections.smtp_configs5_smtp.sender_address,` +
` projections.smtp_configs5_smtp.sender_name,` +
` projections.smtp_configs5_smtp.reply_to_address,` +
` projections.smtp_configs5_smtp.host,` +
` projections.smtp_configs5_smtp.username,` +
` projections.smtp_configs5_smtp.password,` +
` projections.smtp_configs5_http.id,` +
` projections.smtp_configs5_http.endpoint,` +
` projections.smtp_configs5_http.signing_key,` +
` COUNT(*) OVER ()` +
` FROM projections.smtp_configs5` +
` LEFT JOIN projections.smtp_configs5_smtp ON projections.smtp_configs5.id = projections.smtp_configs5_smtp.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_smtp.instance_id` +
` LEFT JOIN projections.smtp_configs5_http ON projections.smtp_configs5.id = projections.smtp_configs5_http.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_http.instance_id`
prepareSMTPConfigCols = []string{ prepareSMTPConfigCols = []string{
"creation_date", "creation_date",
"change_date", "change_date",
@@ -52,10 +76,12 @@ var (
"smtp_password", "smtp_password",
"id", "id",
"endpoint", "endpoint",
"signing_key",
} }
prepareSMTPConfigsCols = append(prepareSMTPConfigCols, "count")
) )
func Test_SMTPConfigsPrepares(t *testing.T) { func Test_SMTPConfigPrepares(t *testing.T) {
type want struct { type want struct {
sqlExpectations sqlExpectation sqlExpectations sqlExpectation
err checkErr err checkErr
@@ -109,6 +135,7 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
&crypto.CryptoValue{}, &crypto.CryptoValue{},
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@@ -156,6 +183,12 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
nil, nil,
"2232323", "2232323",
"endpoint", "endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
}, },
), ),
}, },
@@ -166,6 +199,12 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
Sequence: 20211108, Sequence: 20211108,
HTTPConfig: &HTTP{ HTTPConfig: &HTTP{
Endpoint: "endpoint", Endpoint: "endpoint",
signingKey: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
}, },
ID: "2232323", ID: "2232323",
State: domain.SMTPConfigStateActive, State: domain.SMTPConfigStateActive,
@@ -197,6 +236,7 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
&crypto.CryptoValue{}, &crypto.CryptoValue{},
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@@ -244,6 +284,7 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
&crypto.CryptoValue{}, &crypto.CryptoValue{},
nil, nil,
nil, nil,
nil,
}, },
), ),
}, },
@@ -290,3 +331,247 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
}) })
} }
} }
func Test_SMTPConfigsPrepares(t *testing.T) {
type want struct {
sqlExpectations sqlExpectation
err checkErr
}
tests := []struct {
name string
prepare interface{}
want want
object interface{}
}{
{
name: "prepareSMTPConfigsQuery no result",
prepare: prepareSMTPConfigsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareSMTPConfigsStmt),
nil,
nil,
),
},
object: &SMTPConfigs{Configs: []*SMTPConfig{}},
},
{
name: "prepareSMTPConfigsQuery found",
prepare: prepareSMTPConfigsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareSMTPConfigsStmt),
prepareSMTPConfigsCols,
[][]driver.Value{
{
testNow,
testNow,
"ro",
uint64(20211108),
"2232323",
domain.SMTPConfigStateActive,
"test",
"2232323",
true,
"sender",
"name",
"reply-to",
"host",
"user",
&crypto.CryptoValue{},
nil,
nil,
nil,
},
},
),
},
object: &SMTPConfigs{
SearchResponse: SearchResponse{
Count: 1,
},
Configs: []*SMTPConfig{
{
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: "prepareSMTPConfigsQuery found, multi",
prepare: prepareSMTPConfigsQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(prepareSMTPConfigsStmt),
prepareSMTPConfigsCols,
[][]driver.Value{
{
testNow,
testNow,
"ro",
uint64(20211108),
"2232323",
domain.SMTPConfigStateActive,
"test",
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
"2232323",
"endpoint",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
},
{
testNow,
testNow,
"ro",
uint64(20211109),
"44442323",
domain.SMTPConfigStateInactive,
"test2",
"44442323",
true,
"sender2",
"name2",
"reply-to2",
"host2",
"user2",
&crypto.CryptoValue{},
nil,
nil,
nil,
},
{
testNow,
testNow,
"ro",
uint64(20211109),
"23234444",
domain.SMTPConfigStateInactive,
"test3",
"23234444",
true,
"sender3",
"name3",
"reply-to3",
"host3",
"user3",
&crypto.CryptoValue{},
nil,
nil,
nil,
},
},
),
},
object: &SMTPConfigs{
SearchResponse: SearchResponse{
Count: 3,
},
Configs: []*SMTPConfig{
{
CreationDate: testNow,
ChangeDate: testNow,
ResourceOwner: "ro",
Sequence: 20211108,
HTTPConfig: &HTTP{
Endpoint: "endpoint",
signingKey: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "alg",
KeyID: "encKey",
Crypted: []byte("crypted"),
},
},
ID: "2232323",
State: domain.SMTPConfigStateActive,
Description: "test",
},
{
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",
},
{
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",
},
},
},
},
{
name: "prepareSMTPConfigsQuery sql err",
prepare: prepareSMTPConfigsQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(prepareSMTPConfigsStmt),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
if !errors.Is(err, sql.ErrConnDone) {
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
}
return nil, true
},
},
object: (*SMTPConfigs)(nil),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err)
})
}
}

View File

@@ -186,6 +186,7 @@ type SMSConfigHTTPAddedEvent struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Endpoint string `json:"endpoint,omitempty"` Endpoint string `json:"endpoint,omitempty"`
SigningKey *crypto.CryptoValue `json:"signingKey"`
} }
func NewSMSConfigHTTPAddedEvent( func NewSMSConfigHTTPAddedEvent(
@@ -194,6 +195,7 @@ func NewSMSConfigHTTPAddedEvent(
id, id,
description, description,
endpoint string, endpoint string,
signingKey *crypto.CryptoValue,
) *SMSConfigHTTPAddedEvent { ) *SMSConfigHTTPAddedEvent {
return &SMSConfigHTTPAddedEvent{ return &SMSConfigHTTPAddedEvent{
BaseEvent: eventstore.NewBaseEventForPush( BaseEvent: eventstore.NewBaseEventForPush(
@@ -204,6 +206,7 @@ func NewSMSConfigHTTPAddedEvent(
ID: id, ID: id,
Description: description, Description: description,
Endpoint: endpoint, Endpoint: endpoint,
SigningKey: signingKey,
} }
} }
@@ -225,6 +228,7 @@ type SMSConfigHTTPChangedEvent struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
Endpoint *string `json:"endpoint,omitempty"` Endpoint *string `json:"endpoint,omitempty"`
SigningKey *crypto.CryptoValue `json:"signingKey,omitempty"`
} }
func NewSMSConfigHTTPChangedEvent( func NewSMSConfigHTTPChangedEvent(
@@ -262,6 +266,11 @@ func ChangeSMSConfigHTTPEndpoint(endpoint string) func(event *SMSConfigHTTPChang
e.Endpoint = &endpoint e.Endpoint = &endpoint
} }
} }
func ChangeSMSConfigHTTPSigningKey(signingKey *crypto.CryptoValue) func(event *SMSConfigHTTPChangedEvent) {
return func(e *SMSConfigHTTPChangedEvent) {
e.SigningKey = signingKey
}
}
func (e *SMSConfigHTTPChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) { func (e *SMSConfigHTTPChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event e.BaseEvent = event

View File

@@ -220,6 +220,7 @@ type SMTPConfigHTTPAddedEvent struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Endpoint string `json:"endpoint,omitempty"` Endpoint string `json:"endpoint,omitempty"`
SigningKey *crypto.CryptoValue `json:"signingKey"`
} }
func NewSMTPConfigHTTPAddedEvent( func NewSMTPConfigHTTPAddedEvent(
@@ -227,6 +228,7 @@ func NewSMTPConfigHTTPAddedEvent(
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
id, description string, id, description string,
endpoint string, endpoint string,
signingKey *crypto.CryptoValue,
) *SMTPConfigHTTPAddedEvent { ) *SMTPConfigHTTPAddedEvent {
return &SMTPConfigHTTPAddedEvent{ return &SMTPConfigHTTPAddedEvent{
BaseEvent: eventstore.NewBaseEventForPush( BaseEvent: eventstore.NewBaseEventForPush(
@@ -237,6 +239,7 @@ func NewSMTPConfigHTTPAddedEvent(
ID: id, ID: id,
Description: description, Description: description,
Endpoint: endpoint, Endpoint: endpoint,
SigningKey: signingKey,
} }
} }
@@ -257,6 +260,7 @@ type SMTPConfigHTTPChangedEvent struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
Endpoint *string `json:"endpoint,omitempty"` Endpoint *string `json:"endpoint,omitempty"`
SigningKey *crypto.CryptoValue `json:"signingKey,omitempty"`
} }
func (e *SMTPConfigHTTPChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) { func (e *SMTPConfigHTTPChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
@@ -314,6 +318,12 @@ func ChangeSMTPConfigHTTPEndpoint(endpoint string) func(event *SMTPConfigHTTPCha
} }
} }
func ChangeSMTPConfigHTTPSigningKey(signingKey *crypto.CryptoValue) func(event *SMTPConfigHTTPChangedEvent) {
return func(e *SMTPConfigHTTPChangedEvent) {
e.SigningKey = signingKey
}
}
type SMTPConfigActivatedEvent struct { type SMTPConfigActivatedEvent struct {
*eventstore.BaseEvent `json:"-"` *eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`

View File

@@ -5069,6 +5069,12 @@ message AddEmailProviderHTTPRequest {
message AddEmailProviderHTTPResponse { message AddEmailProviderHTTPResponse {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;
string id = 2; string id = 2;
// Key used to sign and check payload sent to the HTTP provider.
string signing_key = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"98KmsU67\""
}
];
} }
message UpdateEmailProviderHTTPRequest { message UpdateEmailProviderHTTPRequest {
@@ -5090,10 +5096,31 @@ message UpdateEmailProviderHTTPRequest {
max_length: 200; max_length: 200;
} }
]; ];
// Regenerate the key used for signing and checking the payload sent to the HTTP provider.
// Set the graceful period for the existing key. During that time, the previous
// signing key and the new one will be used to sign the request to allow you a smooth
// transition onf your API.
//
// Note that we currently only allow an immediate rotation ("0s") and will support
// longer expirations in the future.
optional google.protobuf.Duration expiration_signing_key = 4 [
(validate.rules).duration = {const: {seconds: 0, nanos: 0}},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"0s\""
minimum: 0
maximum: 0
}
];
} }
message UpdateEmailProviderHTTPResponse { message UpdateEmailProviderHTTPResponse {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;
// Key used to sign and check payload sent to the HTTP provider.
optional string signing_key = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"98KmsU67\""
}
];
} }
message ActivateEmailProviderRequest { message ActivateEmailProviderRequest {
@@ -5336,6 +5363,12 @@ message AddSMSProviderHTTPRequest {
message AddSMSProviderHTTPResponse { message AddSMSProviderHTTPResponse {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;
string id = 2; string id = 2;
// Key used to sign and check payload sent to the HTTP provider.
string signing_key = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"98KmsU67\""
}
];
} }
message UpdateSMSProviderHTTPRequest { message UpdateSMSProviderHTTPRequest {
@@ -5357,10 +5390,30 @@ message UpdateSMSProviderHTTPRequest {
max_length: 200; max_length: 200;
} }
]; ];
// Regenerate the key used for signing and checking the payload sent to the HTTP provider.
// Set the graceful period for the existing key. During that time, the previous
// signing key and the new one will be used to sign the request to allow you a smooth
// transition onf your API.
//
// Note that we currently only allow an immediate rotation ("0s") and will support
// longer expirations in the future.
optional google.protobuf.Duration expiration_signing_key = 4 [
(validate.rules).duration = {const: {seconds: 0, nanos: 0}},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"0s\""
minimum: 0
maximum: 0
}
];
} }
message UpdateSMSProviderHTTPResponse { message UpdateSMSProviderHTTPResponse {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;// Key used to sign and check payload sent to the HTTP provider.
optional string signing_key = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"98KmsU67\""
}
];
} }
message ActivateSMSProviderRequest { message ActivateSMSProviderRequest {

View File

@@ -144,6 +144,11 @@ message EmailProviderSMTP {
message EmailProviderHTTP { message EmailProviderHTTP {
string endpoint = 1; string endpoint = 1;
string signing_key = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"98KmsU67\""
}
];
} }
message SMSProvider { message SMSProvider {
@@ -166,6 +171,11 @@ message TwilioConfig {
message HTTPConfig { message HTTPConfig {
string endpoint = 1; string endpoint = 1;
string signing_key = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"98KmsU67\""
}
];
} }
enum SMSProviderConfigState { enum SMSProviderConfigState {