fix: use triggering origin for notification links (#6628)

* take baseurl if saved on event

* refactor: make es mocks reusable

* Revert "refactor: make es mocks reusable"

This reverts commit 434ce12a6acf639514308bc231e76ebb8676b643.

* make messages testable

* test asset url

* fmt

* fmt

* simplify notification.Start

* test url combinations

* support init code added

* support password changed

* support reset pw

* support user domain claimed

* support add pwless login

* support verify phone

* Revert "support verify phone"

This reverts commit e40503303e2fdda0c85985b3fe3160ce96d43cca.

* save trigger origin from ctx

* add ready for review check

* camel

* test email otp

* fix variable naming

* fix DefaultOTPEmailURLV2

* Revert "fix DefaultOTPEmailURLV2"

This reverts commit fa34d4d2a83fbfd8353759c9148af9165a9dd44c.

* fix email otp challenged test

* fix email otp challenged test

* pass origin in login and gateway requests

* take origin from header

* take x-forwarded if present

* Update internal/notification/handlers/queries.go

Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>

* Update internal/notification/handlers/commands.go

Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>

* move origin header to ctx if available

* generate

* cleanup

* use forwarded header

* support X-Forwarded-* headers

* standardize context handling

* fix linting

---------

Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
This commit is contained in:
Elio Bischof 2023-10-10 15:20:53 +02:00 committed by GitHub
parent 0180779d6d
commit 8f6cb47567
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 2405 additions and 508 deletions

View File

@ -101,7 +101,8 @@ Please make sure you cover your changes with tests before marking a Pull Request
- [ ] Integration tests against the gRPC server ensure that probable good and bad read and write permissions are tested.
- [ ] Integration tests against the gRPC server ensure that the API is easily usable despite eventual consistency.
- [ ] Integration tests against the gRPC server ensure that all probable login and registration flows are covered."
- [ ] Integration tests ensure that certain commands send expected notifications.
- [ ] Integration tests ensure that certain commands emit expected events that trigger notifications.
- [ ] Integration tests ensure that certain events trigger expected notifications.
## Contribute

View File

@ -235,7 +235,6 @@ func startZitadel(config *Config, masterKey string, server chan<- *Server) error
commands,
queries,
eventstoreClient,
assets.AssetAPIFromDomain(config.ExternalSecure, config.ExternalPort),
config.Login.DefaultOTPEmailURLV2,
config.SystemDefaults.Notifications.FileSystemPath,
keys.User,
@ -311,6 +310,8 @@ func startAPIs(
authZRepo,
queries,
}
// always set the origin in the context if available in the http headers, no matter for what protocol
router.Use(middleware.OriginHandler)
verifier := internal_authz.Start(repo, http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), config.SystemAPIUsers)
tlsConfig, err := config.TLS.Config()
if err != nil {
@ -444,7 +445,6 @@ func startAPIs(
if err := apis.RegisterService(ctx, oidc_v2.CreateServer(commands, queries, oidcProvider, config.ExternalSecure)); err != nil {
return err
}
// handle grpc at last to be able to handle the root, because grpc and gateway require a lot of different prefixes
apis.RouteGRPC()
return nil

View File

@ -55,12 +55,6 @@ func AssetAPI(externalSecure bool) func(context.Context) string {
}
}
func AssetAPIFromDomain(externalSecure bool, externalPort uint16) func(context.Context) string {
return func(ctx context.Context) string {
return http_util.BuildHTTP(authz.GetInstance(ctx).RequestedDomain(), externalPort, externalSecure) + HandlerPrefix
}
}
type Uploader interface {
UploadAsset(ctx context.Context, info string, asset *command.AssetUpload, commands *command.Commands) error
ObjectName(data authz.CtxData) (string, error)

View File

@ -133,7 +133,7 @@ func GetAllPermissionsFromCtx(ctx context.Context) []string {
func checkOrigin(ctx context.Context, origins []string) error {
origin := grpc.GetGatewayHeader(ctx, http_util.Origin)
if origin == "" {
origin = http_util.OriginFromCtx(ctx)
origin = http_util.OriginHeader(ctx)
if origin == "" {
return nil
}

View File

@ -45,6 +45,7 @@ type key int
const (
httpHeaders key = iota
remoteAddr
origin
)
func CopyHeadersToContext(h http.Handler) http.Handler {
@ -61,7 +62,7 @@ func HeadersFromCtx(ctx context.Context) (http.Header, bool) {
return headers, ok
}
func OriginFromCtx(ctx context.Context) string {
func OriginHeader(ctx context.Context) string {
headers, ok := ctx.Value(httpHeaders).(http.Header)
if !ok {
return ""
@ -69,6 +70,18 @@ func OriginFromCtx(ctx context.Context) string {
return headers.Get(Origin)
}
func ComposedOrigin(ctx context.Context) string {
o, ok := ctx.Value(origin).(string)
if !ok {
return ""
}
return o
}
func WithComposedOrigin(ctx context.Context, composed string) context.Context {
return context.WithValue(ctx, origin, composed)
}
func RemoteIPFromCtx(ctx context.Context) string {
ctxHeaders, ok := HeadersFromCtx(ctx)
if !ok {

View File

@ -0,0 +1,103 @@
package middleware
import (
"fmt"
"net/http"
"net/url"
"github.com/muhlemmer/httpforwarded"
"github.com/zitadel/logging"
http_util "github.com/zitadel/zitadel/internal/api/http"
)
func OriginHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := composeOrigin(r)
if !http_util.IsOrigin(origin) {
logging.Debugf("extracted origin is not valid: %s", origin)
next.ServeHTTP(w, r)
return
}
next.ServeHTTP(w, r.WithContext(http_util.WithComposedOrigin(r.Context(), origin)))
})
}
func composeOrigin(r *http.Request) string {
if origin, err := originFromForwardedHeader(r); err != nil {
logging.OnError(err).Debug("failed to build origin from forwarded header, trying x-forwarded-* headers")
} else {
return origin
}
if origin, err := originFromXForwardedHeaders(r); err != nil {
logging.OnError(err).Debug("failed to build origin from x-forwarded-* headers, using host header")
} else {
return origin
}
scheme := "https"
if r.TLS == nil {
scheme = "http"
}
return fmt.Sprintf("%s://%s", scheme, r.Host)
}
func originFromForwardedHeader(r *http.Request) (string, error) {
fwd, err := httpforwarded.ParseFromRequest(r)
if err != nil {
return "", err
}
var fwdProto, fwdHost, fwdPort string
if fwdProto = mostRecentValue(fwd, "proto"); fwdProto == "" {
return "", fmt.Errorf("no proto in forwarded header")
}
if fwdHost = mostRecentValue(fwd, "host"); fwdHost == "" {
return "", fmt.Errorf("no host in forwarded header")
}
fwdPort, foundFwdFor := extractPort(mostRecentValue(fwd, "for"))
if !foundFwdFor {
return "", fmt.Errorf("no for in forwarded header")
}
o := fmt.Sprintf("%s://%s", fwdProto, fwdHost)
if fwdPort != "" {
o += ":" + fwdPort
}
return o, nil
}
func originFromXForwardedHeaders(r *http.Request) (string, error) {
scheme := r.Header.Get("X-Forwarded-Proto")
if scheme == "" {
return "", fmt.Errorf("no X-Forwarded-Proto header")
}
host := r.Header.Get("X-Forwarded-Host")
if host == "" {
return "", fmt.Errorf("no X-Forwarded-Host header")
}
return fmt.Sprintf("%s://%s", scheme, host), nil
}
func extractPort(raw string) (string, bool) {
if u, ok := parseURL(raw); ok {
return u.Port(), ok
}
return "", false
}
func parseURL(raw string) (*url.URL, bool) {
if raw == "" {
return nil, false
}
u, err := url.Parse(raw)
return u, err == nil
}
func mostRecentValue(forwarded map[string][]string, key string) string {
if forwarded == nil {
return ""
}
values := forwarded[key]
if len(values) == 0 {
return ""
}
return values[len(values)-1]
}

View File

@ -0,0 +1,106 @@
package notification
import (
"context"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/handlers"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/notification/types"
"github.com/zitadel/zitadel/internal/telemetry/metrics"
)
var _ types.ChannelChains = (*channels)(nil)
type counters struct {
success deliveryMetrics
failed deliveryMetrics
}
type deliveryMetrics struct {
email string
sms string
json string
}
type channels struct {
q *handlers.NotificationQueries
counters counters
}
func newChannels(q *handlers.NotificationQueries) *channels {
c := &channels{
q: q,
counters: counters{
success: deliveryMetrics{
email: "successful_deliveries_email",
sms: "successful_deliveries_sms",
json: "successful_deliveries_json",
},
failed: deliveryMetrics{
email: "failed_deliveries_email",
sms: "failed_deliveries_sms",
json: "failed_deliveries_json",
},
},
}
registerCounter(c.counters.success.email, "Successfully delivered emails")
registerCounter(c.counters.failed.email, "Failed email deliveries")
registerCounter(c.counters.success.sms, "Successfully delivered SMS")
registerCounter(c.counters.failed.sms, "Failed SMS deliveries")
registerCounter(c.counters.success.json, "Successfully delivered JSON messages")
registerCounter(c.counters.failed.json, "Failed JSON message deliveries")
return c
}
func registerCounter(counter, desc string) {
err := metrics.RegisterCounter(counter, desc)
logging.WithFields("metric", counter).OnError(err).Panic("unable to register counter")
}
func (c *channels) Email(ctx context.Context) (*senders.Chain, *smtp.Config, error) {
smtpCfg, err := c.q.GetSMTPConfig(ctx)
if err != nil {
return nil, nil, err
}
chain, err := senders.EmailChannels(
ctx,
smtpCfg,
c.q.GetFileSystemProvider,
c.q.GetLogProvider,
c.counters.success.email,
c.counters.failed.email,
)
return chain, smtpCfg, err
}
func (c *channels) SMS(ctx context.Context) (*senders.Chain, *twilio.Config, error) {
twilioCfg, err := c.q.GetTwilioConfig(ctx)
if err != nil {
return nil, nil, err
}
chain, err := senders.SMSChannels(
ctx,
twilioCfg,
c.q.GetFileSystemProvider,
c.q.GetLogProvider,
c.counters.success.sms,
c.counters.failed.sms,
)
return chain, twilioCfg, err
}
func (c *channels) Webhook(ctx context.Context, cfg webhook.Config) (*senders.Chain, error) {
return senders.WebhookChannels(
ctx,
cfg,
c.q.GetFileSystemProvider,
c.q.GetLogProvider,
c.counters.success.json,
c.counters.failed.json,
)
}

View File

@ -5,8 +5,8 @@
package mock
import (
channels "github.com/zitadel/zitadel/internal/notification/channels"
gomock "github.com/golang/mock/gomock"
channels "github.com/zitadel/zitadel/internal/notification/channels"
reflect "reflect"
)

View File

@ -6,6 +6,7 @@ package mock
import (
gomock "github.com/golang/mock/gomock"
eventstore "github.com/zitadel/zitadel/internal/eventstore"
reflect "reflect"
)
@ -33,11 +34,12 @@ func (m *MockMessage) EXPECT() *MockMessageMockRecorder {
}
// GetContent mocks base method
func (m *MockMessage) GetContent() string {
func (m *MockMessage) GetContent() (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetContent")
ret0, _ := ret[0].(string)
return ret0
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetContent indicates an expected call of GetContent
@ -45,3 +47,17 @@ func (mr *MockMessageMockRecorder) GetContent() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContent", reflect.TypeOf((*MockMessage)(nil).GetContent))
}
// GetTriggeringEvent mocks base method
func (m *MockMessage) GetTriggeringEvent() eventstore.Event {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTriggeringEvent")
ret0, _ := ret[0].(eventstore.Event)
return ret0
}
// GetTriggeringEvent indicates an expected call of GetTriggeringEvent
func (mr *MockMessageMockRecorder) GetTriggeringEvent() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTriggeringEvent", reflect.TypeOf((*MockMessage)(nil).GetTriggeringEvent))
}

View File

@ -1,7 +1,6 @@
package smtp
import (
"context"
"crypto/tls"
"net"
"net/smtp"
@ -23,24 +22,18 @@ type Email struct {
replyToAddress string
}
func InitChannel(ctx context.Context, getSMTPConfig func(ctx context.Context) (*Config, error)) (*Email, error) {
smtpConfig, err := getSMTPConfig(ctx)
if err != nil {
return nil, err
}
client, err := smtpConfig.SMTP.connectToSMTP(smtpConfig.Tls)
func InitChannel(cfg *Config) (*Email, error) {
client, err := cfg.SMTP.connectToSMTP(cfg.Tls)
if err != nil {
logging.New().WithError(err).Error("could not connect to smtp")
return nil, err
}
logging.New().Debug("successfully initialized smtp email channel")
return &Email{
smtpClient: client,
senderName: smtpConfig.FromName,
senderAddress: smtpConfig.From,
replyToAddress: smtpConfig.ReplyToAddress,
senderName: cfg.FromName,
senderAddress: cfg.From,
replyToAddress: cfg.ReplyToAddress,
}, nil
}

View File

@ -0,0 +1,24 @@
package handlers
import (
"context"
"github.com/zitadel/zitadel/internal/repository/milestone"
"github.com/zitadel/zitadel/internal/repository/quota"
)
type Commands interface {
HumanInitCodeSent(ctx context.Context, orgID, userID string) error
HumanEmailVerificationCodeSent(ctx context.Context, orgID, userID string) error
PasswordCodeSent(ctx context.Context, orgID, userID string) error
HumanOTPSMSCodeSent(ctx context.Context, userID, resourceOwner string) error
HumanOTPEmailCodeSent(ctx context.Context, userID, resourceOwner string) error
OTPSMSSent(ctx context.Context, sessionID, resourceOwner string) error
OTPEmailSent(ctx context.Context, sessionID, resourceOwner string) error
UserDomainClaimedSent(ctx context.Context, orgID, userID string) error
HumanPasswordlessInitCodeSent(ctx context.Context, userID, resourceOwner, codeID string) error
PasswordChangeSent(ctx context.Context, orgID, userID string) error
HumanPhoneVerificationCodeSent(ctx context.Context, orgID, userID string) error
UsageNotificationSent(ctx context.Context, dueEvent *quota.NotificationDueEvent) error
MilestonePushed(ctx context.Context, msType milestone.Type, endpoints []string, primaryDomain string) error
}

View File

@ -0,0 +1,4 @@
package handlers
//go:generate mockgen -package mock -destination ./mock/queries.mock.go github.com/zitadel/zitadel/internal/notification/handlers Queries
//go:generate mockgen -package mock -destination ./mock/commands.mock.go github.com/zitadel/zitadel/internal/notification/handlers Commands

View File

@ -0,0 +1,218 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/zitadel/zitadel/internal/notification/handlers (interfaces: Commands)
// Package mock is a generated GoMock package.
package mock
import (
context "context"
gomock "github.com/golang/mock/gomock"
milestone "github.com/zitadel/zitadel/internal/repository/milestone"
quota "github.com/zitadel/zitadel/internal/repository/quota"
reflect "reflect"
)
// MockCommands is a mock of Commands interface
type MockCommands struct {
ctrl *gomock.Controller
recorder *MockCommandsMockRecorder
}
// MockCommandsMockRecorder is the mock recorder for MockCommands
type MockCommandsMockRecorder struct {
mock *MockCommands
}
// NewMockCommands creates a new mock instance
func NewMockCommands(ctrl *gomock.Controller) *MockCommands {
mock := &MockCommands{ctrl: ctrl}
mock.recorder = &MockCommandsMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockCommands) EXPECT() *MockCommandsMockRecorder {
return m.recorder
}
// HumanEmailVerificationCodeSent mocks base method
func (m *MockCommands) HumanEmailVerificationCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanEmailVerificationCodeSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// HumanEmailVerificationCodeSent indicates an expected call of HumanEmailVerificationCodeSent
func (mr *MockCommandsMockRecorder) HumanEmailVerificationCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanEmailVerificationCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanEmailVerificationCodeSent), arg0, arg1, arg2)
}
// HumanInitCodeSent mocks base method
func (m *MockCommands) HumanInitCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanInitCodeSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// HumanInitCodeSent indicates an expected call of HumanInitCodeSent
func (mr *MockCommandsMockRecorder) HumanInitCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanInitCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanInitCodeSent), arg0, arg1, arg2)
}
// HumanOTPEmailCodeSent mocks base method
func (m *MockCommands) HumanOTPEmailCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanOTPEmailCodeSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// HumanOTPEmailCodeSent indicates an expected call of HumanOTPEmailCodeSent
func (mr *MockCommandsMockRecorder) HumanOTPEmailCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanOTPEmailCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanOTPEmailCodeSent), arg0, arg1, arg2)
}
// HumanOTPSMSCodeSent mocks base method
func (m *MockCommands) HumanOTPSMSCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanOTPSMSCodeSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// HumanOTPSMSCodeSent indicates an expected call of HumanOTPSMSCodeSent
func (mr *MockCommandsMockRecorder) HumanOTPSMSCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanOTPSMSCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanOTPSMSCodeSent), arg0, arg1, arg2)
}
// HumanPasswordlessInitCodeSent mocks base method
func (m *MockCommands) HumanPasswordlessInitCodeSent(arg0 context.Context, arg1, arg2, arg3 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanPasswordlessInitCodeSent", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// HumanPasswordlessInitCodeSent indicates an expected call of HumanPasswordlessInitCodeSent
func (mr *MockCommandsMockRecorder) HumanPasswordlessInitCodeSent(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanPasswordlessInitCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanPasswordlessInitCodeSent), arg0, arg1, arg2, arg3)
}
// HumanPhoneVerificationCodeSent mocks base method
func (m *MockCommands) HumanPhoneVerificationCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanPhoneVerificationCodeSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// HumanPhoneVerificationCodeSent indicates an expected call of HumanPhoneVerificationCodeSent
func (mr *MockCommandsMockRecorder) HumanPhoneVerificationCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanPhoneVerificationCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanPhoneVerificationCodeSent), arg0, arg1, arg2)
}
// MilestonePushed mocks base method
func (m *MockCommands) MilestonePushed(arg0 context.Context, arg1 milestone.Type, arg2 []string, arg3 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MilestonePushed", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// MilestonePushed indicates an expected call of MilestonePushed
func (mr *MockCommandsMockRecorder) MilestonePushed(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MilestonePushed", reflect.TypeOf((*MockCommands)(nil).MilestonePushed), arg0, arg1, arg2, arg3)
}
// OTPEmailSent mocks base method
func (m *MockCommands) OTPEmailSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OTPEmailSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// OTPEmailSent indicates an expected call of OTPEmailSent
func (mr *MockCommandsMockRecorder) OTPEmailSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OTPEmailSent", reflect.TypeOf((*MockCommands)(nil).OTPEmailSent), arg0, arg1, arg2)
}
// OTPSMSSent mocks base method
func (m *MockCommands) OTPSMSSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OTPSMSSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// OTPSMSSent indicates an expected call of OTPSMSSent
func (mr *MockCommandsMockRecorder) OTPSMSSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OTPSMSSent", reflect.TypeOf((*MockCommands)(nil).OTPSMSSent), arg0, arg1, arg2)
}
// PasswordChangeSent mocks base method
func (m *MockCommands) PasswordChangeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PasswordChangeSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// PasswordChangeSent indicates an expected call of PasswordChangeSent
func (mr *MockCommandsMockRecorder) PasswordChangeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordChangeSent", reflect.TypeOf((*MockCommands)(nil).PasswordChangeSent), arg0, arg1, arg2)
}
// PasswordCodeSent mocks base method
func (m *MockCommands) PasswordCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PasswordCodeSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// PasswordCodeSent indicates an expected call of PasswordCodeSent
func (mr *MockCommandsMockRecorder) PasswordCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordCodeSent", reflect.TypeOf((*MockCommands)(nil).PasswordCodeSent), arg0, arg1, arg2)
}
// UsageNotificationSent mocks base method
func (m *MockCommands) UsageNotificationSent(arg0 context.Context, arg1 *quota.NotificationDueEvent) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UsageNotificationSent", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UsageNotificationSent indicates an expected call of UsageNotificationSent
func (mr *MockCommandsMockRecorder) UsageNotificationSent(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UsageNotificationSent", reflect.TypeOf((*MockCommands)(nil).UsageNotificationSent), arg0, arg1)
}
// UserDomainClaimedSent mocks base method
func (m *MockCommands) UserDomainClaimedSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UserDomainClaimedSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// UserDomainClaimedSent indicates an expected call of UserDomainClaimedSent
func (mr *MockCommandsMockRecorder) UserDomainClaimedSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserDomainClaimedSent", reflect.TypeOf((*MockCommands)(nil).UserDomainClaimedSent), arg0, arg1, arg2)
}

View File

@ -0,0 +1,226 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/zitadel/zitadel/internal/notification/handlers (interfaces: Queries)
// Package mock is a generated GoMock package.
package mock
import (
context "context"
gomock "github.com/golang/mock/gomock"
domain "github.com/zitadel/zitadel/internal/domain"
query "github.com/zitadel/zitadel/internal/query"
language "golang.org/x/text/language"
reflect "reflect"
)
// MockQueries is a mock of Queries interface
type MockQueries struct {
ctrl *gomock.Controller
recorder *MockQueriesMockRecorder
}
// MockQueriesMockRecorder is the mock recorder for MockQueries
type MockQueriesMockRecorder struct {
mock *MockQueries
}
// NewMockQueries creates a new mock instance
func NewMockQueries(ctrl *gomock.Controller) *MockQueries {
mock := &MockQueries{ctrl: ctrl}
mock.recorder = &MockQueriesMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockQueries) EXPECT() *MockQueriesMockRecorder {
return m.recorder
}
// ActiveLabelPolicyByOrg mocks base method
func (m *MockQueries) ActiveLabelPolicyByOrg(arg0 context.Context, arg1 string, arg2 bool) (*query.LabelPolicy, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ActiveLabelPolicyByOrg", arg0, arg1, arg2)
ret0, _ := ret[0].(*query.LabelPolicy)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ActiveLabelPolicyByOrg indicates an expected call of ActiveLabelPolicyByOrg
func (mr *MockQueriesMockRecorder) ActiveLabelPolicyByOrg(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActiveLabelPolicyByOrg", reflect.TypeOf((*MockQueries)(nil).ActiveLabelPolicyByOrg), arg0, arg1, arg2)
}
// CustomTextListByTemplate mocks base method
func (m *MockQueries) CustomTextListByTemplate(arg0 context.Context, arg1, arg2 string, arg3 bool) (*query.CustomTexts, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CustomTextListByTemplate", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(*query.CustomTexts)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CustomTextListByTemplate indicates an expected call of CustomTextListByTemplate
func (mr *MockQueriesMockRecorder) CustomTextListByTemplate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CustomTextListByTemplate", reflect.TypeOf((*MockQueries)(nil).CustomTextListByTemplate), arg0, arg1, arg2, arg3)
}
// GetDefaultLanguage mocks base method
func (m *MockQueries) GetDefaultLanguage(arg0 context.Context) language.Tag {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetDefaultLanguage", arg0)
ret0, _ := ret[0].(language.Tag)
return ret0
}
// GetDefaultLanguage indicates an expected call of GetDefaultLanguage
func (mr *MockQueriesMockRecorder) GetDefaultLanguage(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultLanguage", reflect.TypeOf((*MockQueries)(nil).GetDefaultLanguage), arg0)
}
// GetNotifyUserByID mocks base method
func (m *MockQueries) GetNotifyUserByID(arg0 context.Context, arg1 bool, arg2 string, arg3 bool, arg4 ...query.SearchQuery) (*query.NotifyUser, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1, arg2, arg3}
for _, a := range arg4 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetNotifyUserByID", varargs...)
ret0, _ := ret[0].(*query.NotifyUser)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetNotifyUserByID indicates an expected call of GetNotifyUserByID
func (mr *MockQueriesMockRecorder) GetNotifyUserByID(arg0, arg1, arg2, arg3 interface{}, arg4 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1, arg2, arg3}, arg4...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotifyUserByID", reflect.TypeOf((*MockQueries)(nil).GetNotifyUserByID), varargs...)
}
// MailTemplateByOrg mocks base method
func (m *MockQueries) MailTemplateByOrg(arg0 context.Context, arg1 string, arg2 bool) (*query.MailTemplate, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MailTemplateByOrg", arg0, arg1, arg2)
ret0, _ := ret[0].(*query.MailTemplate)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MailTemplateByOrg indicates an expected call of MailTemplateByOrg
func (mr *MockQueriesMockRecorder) MailTemplateByOrg(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailTemplateByOrg", reflect.TypeOf((*MockQueries)(nil).MailTemplateByOrg), arg0, arg1, arg2)
}
// NotificationPolicyByOrg mocks base method
func (m *MockQueries) NotificationPolicyByOrg(arg0 context.Context, arg1 bool, arg2 string, arg3 bool) (*query.NotificationPolicy, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NotificationPolicyByOrg", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(*query.NotificationPolicy)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// NotificationPolicyByOrg indicates an expected call of NotificationPolicyByOrg
func (mr *MockQueriesMockRecorder) NotificationPolicyByOrg(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotificationPolicyByOrg", reflect.TypeOf((*MockQueries)(nil).NotificationPolicyByOrg), arg0, arg1, arg2, arg3)
}
// NotificationProviderByIDAndType mocks base method
func (m *MockQueries) NotificationProviderByIDAndType(arg0 context.Context, arg1 string, arg2 domain.NotificationProviderType) (*query.DebugNotificationProvider, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NotificationProviderByIDAndType", arg0, arg1, arg2)
ret0, _ := ret[0].(*query.DebugNotificationProvider)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// NotificationProviderByIDAndType indicates an expected call of NotificationProviderByIDAndType
func (mr *MockQueriesMockRecorder) NotificationProviderByIDAndType(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotificationProviderByIDAndType", reflect.TypeOf((*MockQueries)(nil).NotificationProviderByIDAndType), arg0, arg1, arg2)
}
// SMSProviderConfig mocks base method
func (m *MockQueries) SMSProviderConfig(arg0 context.Context, arg1 ...query.SearchQuery) (*query.SMSConfig, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "SMSProviderConfig", varargs...)
ret0, _ := ret[0].(*query.SMSConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SMSProviderConfig indicates an expected call of SMSProviderConfig
func (mr *MockQueriesMockRecorder) SMSProviderConfig(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMSProviderConfig", reflect.TypeOf((*MockQueries)(nil).SMSProviderConfig), varargs...)
}
// SMTPConfigByAggregateID mocks base method
func (m *MockQueries) SMTPConfigByAggregateID(arg0 context.Context, arg1 string) (*query.SMTPConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SMTPConfigByAggregateID", arg0, arg1)
ret0, _ := ret[0].(*query.SMTPConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SMTPConfigByAggregateID indicates an expected call of SMTPConfigByAggregateID
func (mr *MockQueriesMockRecorder) SMTPConfigByAggregateID(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMTPConfigByAggregateID", reflect.TypeOf((*MockQueries)(nil).SMTPConfigByAggregateID), arg0, arg1)
}
// SearchInstanceDomains mocks base method
func (m *MockQueries) SearchInstanceDomains(arg0 context.Context, arg1 *query.InstanceDomainSearchQueries) (*query.InstanceDomains, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SearchInstanceDomains", arg0, arg1)
ret0, _ := ret[0].(*query.InstanceDomains)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SearchInstanceDomains indicates an expected call of SearchInstanceDomains
func (mr *MockQueriesMockRecorder) SearchInstanceDomains(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchInstanceDomains", reflect.TypeOf((*MockQueries)(nil).SearchInstanceDomains), arg0, arg1)
}
// SearchMilestones mocks base method
func (m *MockQueries) SearchMilestones(arg0 context.Context, arg1 []string, arg2 *query.MilestonesSearchQueries) (*query.Milestones, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SearchMilestones", arg0, arg1, arg2)
ret0, _ := ret[0].(*query.Milestones)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SearchMilestones indicates an expected call of SearchMilestones
func (mr *MockQueriesMockRecorder) SearchMilestones(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchMilestones", reflect.TypeOf((*MockQueries)(nil).SearchMilestones), arg0, arg1, arg2)
}
// SessionByID mocks base method
func (m *MockQueries) SessionByID(arg0 context.Context, arg1 bool, arg2, arg3 string) (*query.Session, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SessionByID", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(*query.Session)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SessionByID indicates an expected call of SessionByID
func (mr *MockQueriesMockRecorder) SessionByID(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SessionByID", reflect.TypeOf((*MockQueries)(nil).SessionByID), arg0, arg1, arg2, arg3)
}

View File

@ -2,27 +2,56 @@ package handlers
import (
"context"
"fmt"
"net/url"
"github.com/zitadel/zitadel/internal/api/authz"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query"
)
func (n *NotificationQueries) Origin(ctx context.Context) (context.Context, string, error) {
type OriginEvent interface {
eventstore.Event
TriggerOrigin() string
}
func (n *NotificationQueries) Origin(ctx context.Context, e eventstore.Event) (context.Context, error) {
originEvent, ok := e.(OriginEvent)
if !ok {
return ctx, errors.ThrowInternal(fmt.Errorf("event of type %T doesn't implement OriginEvent", e), "NOTIF-3m9fs", "Errors.Internal")
}
origin := originEvent.TriggerOrigin()
if origin != "" {
originURL, err := url.Parse(origin)
if err != nil {
return ctx, err
}
return enrichCtx(ctx, originURL.Hostname(), origin), nil
}
primary, err := query.NewInstanceDomainPrimarySearchQuery(true)
if err != nil {
return ctx, "", err
return ctx, err
}
domains, err := n.SearchInstanceDomains(ctx, &query.InstanceDomainSearchQueries{
Queries: []query.SearchQuery{primary},
})
if err != nil {
return ctx, "", err
return ctx, err
}
if len(domains.Domains) < 1 {
return ctx, "", errors.ThrowInternal(nil, "NOTIF-Ef3r1", "Errors.Notification.NoDomain")
return ctx, errors.ThrowInternal(nil, "NOTIF-Ef3r1", "Errors.Notification.NoDomain")
}
ctx = authz.WithRequestedDomain(ctx, domains.Domains[0].Domain)
return ctx, http_utils.BuildHTTP(domains.Domains[0].Domain, n.externalPort, n.externalSecure), nil
return enrichCtx(
ctx,
domains.Domains[0].Domain,
http_utils.BuildHTTP(domains.Domains[0].Domain, n.externalPort, n.externalSecure),
), nil
}
func enrichCtx(ctx context.Context, host, origin string) context.Context {
ctx = authz.WithRequestedDomain(ctx, host)
ctx = http_utils.WithComposedOrigin(ctx, origin)
return ctx
}

View File

@ -1,16 +1,34 @@
package handlers
import (
"context"
"net/http"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
_ "github.com/zitadel/zitadel/internal/notification/statik"
"github.com/zitadel/zitadel/internal/query"
)
type Queries interface {
ActiveLabelPolicyByOrg(ctx context.Context, orgID string, withOwnerRemoved bool) (*query.LabelPolicy, error)
MailTemplateByOrg(ctx context.Context, orgID string, withOwnerRemoved bool) (*query.MailTemplate, error)
GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, withOwnerRemoved bool, queries ...query.SearchQuery) (*query.NotifyUser, error)
CustomTextListByTemplate(ctx context.Context, aggregateID, template string, withOwnerRemoved bool) (*query.CustomTexts, error)
SearchInstanceDomains(ctx context.Context, queries *query.InstanceDomainSearchQueries) (*query.InstanceDomains, error)
SessionByID(ctx context.Context, shouldTriggerBulk bool, id, sessionToken string) (*query.Session, error)
NotificationPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (*query.NotificationPolicy, error)
SearchMilestones(ctx context.Context, instanceIDs []string, queries *query.MilestonesSearchQueries) (*query.Milestones, error)
NotificationProviderByIDAndType(ctx context.Context, aggID string, providerType domain.NotificationProviderType) (*query.DebugNotificationProvider, error)
SMSProviderConfig(ctx context.Context, queries ...query.SearchQuery) (*query.SMSConfig, error)
SMTPConfigByAggregateID(ctx context.Context, aggregateID string) (*query.SMTPConfig, error)
GetDefaultLanguage(ctx context.Context) language.Tag
}
type NotificationQueries struct {
*query.Queries
Queries
es *eventstore.Eventstore
externalDomain string
externalPort uint16
@ -23,7 +41,7 @@ type NotificationQueries struct {
}
func NewNotificationQueries(
baseQueries *query.Queries,
baseQueries Queries,
es *eventstore.Eventstore,
externalDomain string,
externalPort uint16,

View File

@ -22,10 +22,9 @@ const (
type quotaNotifier struct {
crdb.StatementHandler
commands *command.Commands
queries *NotificationQueries
metricSuccessfulDeliveriesJSON string
metricFailedDeliveriesJSON string
commands *command.Commands
queries *NotificationQueries
channels types.ChannelChains
}
func NewQuotaNotifier(
@ -33,8 +32,7 @@ func NewQuotaNotifier(
config crdb.StatementHandlerConfig,
commands *command.Commands,
queries *NotificationQueries,
metricSuccessfulDeliveriesJSON,
metricFailedDeliveriesJSON string,
channels types.ChannelChains,
) *quotaNotifier {
p := new(quotaNotifier)
config.ProjectionName = QuotaNotificationsProjectionTable
@ -42,8 +40,7 @@ func NewQuotaNotifier(
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
p.commands = commands
p.queries = queries
p.metricSuccessfulDeliveriesJSON = metricSuccessfulDeliveriesJSON
p.metricFailedDeliveriesJSON = metricFailedDeliveriesJSON
p.channels = channels
projection.NotificationsQuotaProjection = p
return p
}
@ -75,19 +72,7 @@ func (u *quotaNotifier) reduceNotificationDue(event eventstore.Event) (*handler.
if alreadyHandled {
return crdb.NewNoOpStatement(e), nil
}
err = types.SendJSON(
ctx,
webhook.Config{
CallURL: e.CallURL,
Method: http.MethodPost,
},
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
e,
e,
u.metricSuccessfulDeliveriesJSON,
u.metricFailedDeliveriesJSON,
).WithoutTemplate()
err = types.SendJSON(ctx, webhook.Config{CallURL: e.CallURL, Method: http.MethodPost}, u.channels, e, e).WithoutTemplate()
if err != nil {
return nil, err
}

View File

@ -38,11 +38,10 @@ type TelemetryPusherConfig struct {
type telemetryPusher struct {
crdb.StatementHandler
cfg TelemetryPusherConfig
commands *command.Commands
queries *NotificationQueries
metricSuccessfulDeliveriesJSON string
metricFailedDeliveriesJSON string
cfg TelemetryPusherConfig
commands *command.Commands
queries *NotificationQueries
channels types.ChannelChains
}
func NewTelemetryPusher(
@ -51,8 +50,7 @@ func NewTelemetryPusher(
handlerCfg crdb.StatementHandlerConfig,
commands *command.Commands,
queries *NotificationQueries,
metricSuccessfulDeliveriesJSON,
metricFailedDeliveriesJSON string,
channels types.ChannelChains,
) *telemetryPusher {
p := new(telemetryPusher)
handlerCfg.ProjectionName = TelemetryProjectionTable
@ -62,8 +60,7 @@ func NewTelemetryPusher(
p.StatementHandler = crdb.NewStatementHandler(ctx, handlerCfg)
p.commands = commands
p.queries = queries
p.metricSuccessfulDeliveriesJSON = metricSuccessfulDeliveriesJSON
p.metricFailedDeliveriesJSON = metricFailedDeliveriesJSON
p.channels = channels
projection.TelemetryPusherProjection = p
return p
}
@ -132,8 +129,7 @@ func (t *telemetryPusher) pushMilestone(ctx context.Context, event *pseudo.Sched
Method: http.MethodPost,
Headers: t.cfg.Headers,
},
t.queries.GetFileSystemProvider,
t.queries.GetLogProvider,
t.channels,
&struct {
InstanceID string `json:"instanceId"`
ExternalDomain string `json:"externalDomain"`
@ -148,8 +144,6 @@ func (t *telemetryPusher) pushMilestone(ctx context.Context, event *pseudo.Sched
ReachedDate: ms.ReachedDate,
},
event,
t.metricSuccessfulDeliveriesJSON,
t.metricFailedDeliveriesJSON,
).WithoutTemplate(); err != nil {
return err
}

View File

@ -5,9 +5,8 @@ import (
"strings"
"time"
"github.com/zitadel/zitadel/internal/api/authz"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
@ -27,27 +26,19 @@ const (
type userNotifier struct {
crdb.StatementHandler
commands *command.Commands
commands Commands
queries *NotificationQueries
assetsPrefix func(context.Context) string
channels types.ChannelChains
otpEmailTmpl string
metricSuccessfulDeliveriesEmail,
metricFailedDeliveriesEmail,
metricSuccessfulDeliveriesSMS,
metricFailedDeliveriesSMS string
}
func NewUserNotifier(
ctx context.Context,
config crdb.StatementHandlerConfig,
commands *command.Commands,
commands Commands,
queries *NotificationQueries,
assetsPrefix func(context.Context) string,
channels types.ChannelChains,
otpEmailTmpl string,
metricSuccessfulDeliveriesEmail,
metricFailedDeliveriesEmail,
metricSuccessfulDeliveriesSMS,
metricFailedDeliveriesSMS string,
) *userNotifier {
p := new(userNotifier)
config.ProjectionName = UserNotificationsProjectionTable
@ -55,12 +46,8 @@ func NewUserNotifier(
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
p.commands = commands
p.queries = queries
p.assetsPrefix = assetsPrefix
p.channels = channels
p.otpEmailTmpl = otpEmailTmpl
p.metricSuccessfulDeliveriesEmail = metricSuccessfulDeliveriesEmail
p.metricFailedDeliveriesEmail = metricFailedDeliveriesEmail
p.metricSuccessfulDeliveriesSMS = metricSuccessfulDeliveriesSMS
p.metricFailedDeliveriesSMS = metricFailedDeliveriesSMS
projection.NotificationsProjection = p
return p
}
@ -177,25 +164,12 @@ func (u *userNotifier) reduceInitCodeAdded(event eventstore.Event) (*handler.Sta
if err != nil {
return nil, err
}
ctx, origin, err := u.queries.Origin(ctx)
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return nil, err
}
err = types.SendEmail(
ctx,
string(template.Template),
translator,
notifyUser,
u.queries.GetSMTPConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
e,
u.metricSuccessfulDeliveriesEmail,
u.metricFailedDeliveriesEmail,
).SendUserInitCode(notifyUser, origin, code)
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e).
SendUserInitCode(ctx, notifyUser, code)
if err != nil {
return nil, err
}
@ -247,25 +221,12 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St
if err != nil {
return nil, err
}
ctx, origin, err := u.queries.Origin(ctx)
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return nil, err
}
err = types.SendEmail(
ctx,
string(template.Template),
translator,
notifyUser,
u.queries.GetSMTPConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
e,
u.metricSuccessfulDeliveriesEmail,
u.metricFailedDeliveriesEmail,
).SendEmailVerificationCode(notifyUser, origin, code, e.URLTemplate)
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e).
SendEmailVerificationCode(ctx, notifyUser, code, e.URLTemplate)
if err != nil {
return nil, err
}
@ -316,41 +277,15 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler
if err != nil {
return nil, err
}
ctx, origin, err := u.queries.Origin(ctx)
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return nil, err
}
notify := types.SendEmail(
ctx,
string(template.Template),
translator,
notifyUser,
u.queries.GetSMTPConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
e,
u.metricSuccessfulDeliveriesEmail,
u.metricFailedDeliveriesEmail,
)
notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e)
if e.NotificationType == domain.NotificationTypeSms {
notify = types.SendSMSTwilio(
ctx,
translator,
notifyUser,
u.queries.GetTwilioConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
e,
u.metricSuccessfulDeliveriesSMS,
u.metricFailedDeliveriesSMS,
)
notify = types.SendSMSTwilio(ctx, u.channels, translator, notifyUser, colors, e)
}
err = notify.SendPasswordCode(notifyUser, origin, code, e.URLTemplate)
err = notify.SendPasswordCode(ctx, notifyUser, code, e.URLTemplate)
if err != nil {
return nil, err
}
@ -437,25 +372,12 @@ func (u *userNotifier) reduceOTPSMS(
if err != nil {
return nil, err
}
ctx, origin, err := u.queries.Origin(ctx)
ctx, err = u.queries.Origin(ctx, event)
if err != nil {
return nil, err
}
notify := types.SendSMSTwilio(
ctx,
translator,
notifyUser,
u.queries.GetTwilioConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
event,
u.metricSuccessfulDeliveriesSMS,
u.metricFailedDeliveriesSMS,
)
err = notify.SendOTPSMSCode(authz.GetInstance(ctx).RequestedDomain(), origin, plainCode, expiry)
notify := types.SendSMSTwilio(ctx, u.channels, translator, notifyUser, colors, event)
err = notify.SendOTPSMSCode(ctx, plainCode, expiry)
if err != nil {
return nil, err
}
@ -568,30 +490,16 @@ func (u *userNotifier) reduceOTPEmail(
if err != nil {
return nil, err
}
ctx, origin, err := u.queries.Origin(ctx)
ctx, err = u.queries.Origin(ctx, event)
if err != nil {
return nil, err
}
url, err := urlTmpl(plainCode, origin, notifyUser)
url, err := urlTmpl(plainCode, http_util.ComposedOrigin(ctx), notifyUser)
if err != nil {
return nil, err
}
notify := types.SendEmail(
ctx,
string(template.Template),
translator,
notifyUser,
u.queries.GetSMTPConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
event,
u.metricSuccessfulDeliveriesEmail,
u.metricFailedDeliveriesEmail,
)
err = notify.SendOTPEmailCode(notifyUser, url, authz.GetInstance(ctx).RequestedDomain(), origin, plainCode, expiry)
notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, event)
err = notify.SendOTPEmailCode(ctx, url, plainCode, expiry)
if err != nil {
return nil, err
}
@ -634,25 +542,12 @@ func (u *userNotifier) reduceDomainClaimed(event eventstore.Event) (*handler.Sta
if err != nil {
return nil, err
}
ctx, origin, err := u.queries.Origin(ctx)
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return nil, err
}
err = types.SendEmail(
ctx,
string(template.Template),
translator,
notifyUser,
u.queries.GetSMTPConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
e,
u.metricSuccessfulDeliveriesEmail,
u.metricFailedDeliveriesEmail,
).SendDomainClaimed(notifyUser, origin, e.UserName)
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e).
SendDomainClaimed(ctx, notifyUser, e.UserName)
if err != nil {
return nil, err
}
@ -701,25 +596,12 @@ func (u *userNotifier) reducePasswordlessCodeRequested(event eventstore.Event) (
if err != nil {
return nil, err
}
ctx, origin, err := u.queries.Origin(ctx)
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return nil, err
}
err = types.SendEmail(
ctx,
string(template.Template),
translator,
notifyUser,
u.queries.GetSMTPConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
e,
u.metricSuccessfulDeliveriesEmail,
u.metricFailedDeliveriesEmail,
).SendPasswordlessRegistrationLink(notifyUser, origin, code, e.ID, e.URLTemplate)
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e).
SendPasswordlessRegistrationLink(ctx, notifyUser, code, e.ID, e.URLTemplate)
if err != nil {
return nil, err
}
@ -771,25 +653,12 @@ func (u *userNotifier) reducePasswordChanged(event eventstore.Event) (*handler.S
if err != nil {
return nil, err
}
ctx, origin, err := u.queries.Origin(ctx)
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return nil, err
}
err = types.SendEmail(
ctx,
string(template.Template),
translator,
notifyUser,
u.queries.GetSMTPConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
e,
u.metricSuccessfulDeliveriesEmail,
u.metricFailedDeliveriesEmail,
).SendPasswordChange(notifyUser, origin)
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e).
SendPasswordChange(ctx, notifyUser)
if err != nil {
return nil, err
}
@ -836,24 +705,12 @@ func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.St
if err != nil {
return nil, err
}
ctx, origin, err := u.queries.Origin(ctx)
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return nil, err
}
err = types.SendSMSTwilio(
ctx,
translator,
notifyUser,
u.queries.GetTwilioConfig,
u.queries.GetFileSystemProvider,
u.queries.GetLogProvider,
colors,
u.assetsPrefix(ctx),
e,
u.metricSuccessfulDeliveriesSMS,
u.metricFailedDeliveriesSMS,
).SendPhoneVerificationCode(notifyUser, origin, code, authz.GetInstance(ctx).RequestedDomain())
err = types.SendSMSTwilio(ctx, u.channels, translator, notifyUser, colors, e).
SendPhoneVerificationCode(ctx, code)
if err != nil {
return nil, err
}

File diff suppressed because it is too large Load Diff

View File

@ -13,16 +13,6 @@ import (
_ "github.com/zitadel/zitadel/internal/notification/statik"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/telemetry/metrics"
)
const (
metricSuccessfulDeliveriesEmail = "successful_deliveries_email"
metricFailedDeliveriesEmail = "failed_deliveries_email"
metricSuccessfulDeliveriesSMS = "successful_deliveries_sms"
metricFailedDeliveriesSMS = "failed_deliveries_sms"
metricSuccessfulDeliveriesJSON = "successful_deliveries_json"
metricFailedDeliveriesJSON = "failed_deliveries_json"
)
func Start(
@ -35,55 +25,17 @@ func Start(
commands *command.Commands,
queries *query.Queries,
es *eventstore.Eventstore,
assetsPrefix func(context.Context) string,
otpEmailTmpl string,
fileSystemPath string,
userEncryption, smtpEncryption, smsEncryption crypto.EncryptionAlgorithm,
) {
statikFS, err := statik_fs.NewWithNamespace("notification")
logging.OnError(err).Panic("unable to start listener")
err = metrics.RegisterCounter(metricSuccessfulDeliveriesEmail, "Successfully delivered emails")
logging.WithFields("metric", metricSuccessfulDeliveriesEmail).OnError(err).Panic("unable to register counter")
err = metrics.RegisterCounter(metricFailedDeliveriesEmail, "Failed email deliveries")
logging.WithFields("metric", metricFailedDeliveriesEmail).OnError(err).Panic("unable to register counter")
err = metrics.RegisterCounter(metricSuccessfulDeliveriesSMS, "Successfully delivered SMS")
logging.WithFields("metric", metricSuccessfulDeliveriesSMS).OnError(err).Panic("unable to register counter")
err = metrics.RegisterCounter(metricFailedDeliveriesSMS, "Failed SMS deliveries")
logging.WithFields("metric", metricFailedDeliveriesSMS).OnError(err).Panic("unable to register counter")
err = metrics.RegisterCounter(metricSuccessfulDeliveriesJSON, "Successfully delivered JSON messages")
logging.WithFields("metric", metricSuccessfulDeliveriesJSON).OnError(err).Panic("unable to register counter")
err = metrics.RegisterCounter(metricFailedDeliveriesJSON, "Failed JSON message deliveries")
logging.WithFields("metric", metricFailedDeliveriesJSON).OnError(err).Panic("unable to register counter")
q := handlers.NewNotificationQueries(queries, es, externalDomain, externalPort, externalSecure, fileSystemPath, userEncryption, smtpEncryption, smsEncryption, statikFS)
handlers.NewUserNotifier(
ctx,
projection.ApplyCustomConfig(userHandlerCustomConfig),
commands,
q,
assetsPrefix,
otpEmailTmpl,
metricSuccessfulDeliveriesEmail,
metricFailedDeliveriesEmail,
metricSuccessfulDeliveriesSMS,
metricFailedDeliveriesSMS,
).Start()
handlers.NewQuotaNotifier(
ctx,
projection.ApplyCustomConfig(quotaHandlerCustomConfig),
commands,
q,
metricSuccessfulDeliveriesJSON,
metricFailedDeliveriesJSON,
).Start()
c := newChannels(q)
handlers.NewUserNotifier(ctx, projection.ApplyCustomConfig(userHandlerCustomConfig), commands, q, c, otpEmailTmpl).Start()
handlers.NewQuotaNotifier(ctx, projection.ApplyCustomConfig(quotaHandlerCustomConfig), commands, q, c).Start()
if telemetryCfg.Enabled {
handlers.NewTelemetryPusher(
ctx,
telemetryCfg,
projection.ApplyCustomConfig(telemetryHandlerCustomConfig),
commands,
q,
metricSuccessfulDeliveriesJSON,
metricFailedDeliveriesJSON,
).Start()
handlers.NewTelemetryPusher(ctx, telemetryCfg, projection.ApplyCustomConfig(telemetryHandlerCustomConfig), commands, q, c).Start()
}
}

View File

@ -8,12 +8,12 @@ type Chain struct {
channels []channels.NotificationChannel
}
func chainChannels(channel ...channels.NotificationChannel) *Chain {
func ChainChannels(channel ...channels.NotificationChannel) *Chain {
return &Chain{channels: channel}
}
// HandleMessage returns a non nil error from a provider immediately if any occurs
// messages are sent to channels in the same order they were provided to chainChannels()
// messages are sent to channels in the same order they were provided to ChainChannels()
func (c *Chain) HandleMessage(message channels.Message) error {
for i := range c.channels {
if err := c.channels[i].HandleMessage(message); err != nil {

View File

@ -17,14 +17,14 @@ const smtpSpanName = "smtp.NotificationChannel"
func EmailChannels(
ctx context.Context,
emailConfig func(ctx context.Context) (*smtp.Config, error),
emailConfig *smtp.Config,
getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
getLogProvider func(ctx context.Context) (*log.Config, error),
successMetricName,
failureMetricName string,
) (chain *Chain, err error) {
channels := make([]channels.NotificationChannel, 0, 3)
p, err := smtp.InitChannel(ctx, emailConfig)
p, err := smtp.InitChannel(emailConfig)
logging.WithFields(
"instance", authz.GetInstance(ctx).InstanceID(),
).OnError(err).Debug("initializing SMTP channel failed")
@ -41,5 +41,5 @@ func EmailChannels(
)
}
channels = append(channels, debugChannels(ctx, getFileSystemProvider, getLogProvider)...)
return chainChannels(channels...), nil
return ChainChannels(channels...), nil
}

View File

@ -34,5 +34,5 @@ func SMSChannels(
)
}
channels = append(channels, debugChannels(ctx, getFileSystemProvider, getLogProvider)...)
return chainChannels(channels...), nil
return ChainChannels(channels...), nil
}

View File

@ -15,7 +15,7 @@ import (
const webhookSpanName = "webhook.NotificationChannel"
func JSONChannels(
func WebhookChannels(
ctx context.Context,
webhookConfig webhook.Config,
getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
@ -45,5 +45,5 @@ func JSONChannels(
)
}
channels = append(channels, debugChannels(ctx, getFileSystemProvider, getLogProvider)...)
return chainChannels(channels...), nil
return ChainChannels(channels...), nil
}

View File

@ -1,15 +1,17 @@
package types
import (
"context"
"strings"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendDomainClaimed(user *query.NotifyUser, origin, username string) error {
url := login.LoginLink(origin, user.ResourceOwner)
func (notify Notify) SendDomainClaimed(ctx context.Context, user *query.NotifyUser, username string) error {
url := login.LoginLink(http_utils.ComposedOrigin(ctx), user.ResourceOwner)
index := strings.LastIndex(user.LastEmail, "@")
args := make(map[string]interface{})
args["TempUsername"] = username

View File

@ -1,17 +1,19 @@
package types
import (
"context"
"strings"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendEmailVerificationCode(user *query.NotifyUser, origin, code string, urlTmpl string) error {
func (notify Notify) SendEmailVerificationCode(ctx context.Context, user *query.NotifyUser, code string, urlTmpl string) error {
var url string
if urlTmpl == "" {
url = login.MailVerificationLink(origin, user.ID, code, user.ResourceOwner)
url = login.MailVerificationLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner)
} else {
var buf strings.Builder
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {

View File

@ -1,11 +1,13 @@
package types
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
@ -78,7 +80,7 @@ func TestNotify_SendEmailVerificationCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, notify := mockNotify()
err := notify.SendEmailVerificationCode(tt.args.user, tt.args.origin, tt.args.code, tt.args.urlTmpl)
err := notify.SendEmailVerificationCode(http_utils.WithComposedOrigin(context.Background(), tt.args.origin), tt.args.user, tt.args.code, tt.args.urlTmpl)
require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, got)
})

View File

@ -1,13 +1,16 @@
package types
import (
"context"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendUserInitCode(user *query.NotifyUser, origin, code string) error {
url := login.InitUserLink(origin, user.ID, user.PreferredLoginName, code, user.ResourceOwner, user.PasswordSet)
func (notify Notify) SendUserInitCode(ctx context.Context, user *query.NotifyUser, code string) error {
url := login.InitUserLink(http_utils.ComposedOrigin(ctx), user.ID, user.PreferredLoginName, code, user.ResourceOwner, user.PasswordSet)
args := make(map[string]interface{})
args["Code"] = code
return notify(url, args, domain.InitCodeMessageType, true)

View File

@ -1,40 +0,0 @@
package types
import (
"context"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/channels/fs"
"github.com/zitadel/zitadel/internal/notification/channels/log"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/messages"
"github.com/zitadel/zitadel/internal/notification/senders"
)
func handleJSON(
ctx context.Context,
webhookConfig webhook.Config,
getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
getLogProvider func(ctx context.Context) (*log.Config, error),
serializable interface{},
triggeringEvent eventstore.Event,
successMetricName,
failureMetricName string,
) error {
message := &messages.JSON{
Serializable: serializable,
TriggeringEvent: triggeringEvent,
}
channelChain, err := senders.JSONChannels(
ctx,
webhookConfig,
getFileSystemProvider,
getLogProvider,
successMetricName,
failureMetricName,
)
if err != nil {
return err
}
return channelChain.HandleMessage(message)
}

View File

@ -5,11 +5,10 @@ import (
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/notification/channels/fs"
"github.com/zitadel/zitadel/internal/notification/channels/log"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/notification/templates"
"github.com/zitadel/zitadel/internal/query"
)
@ -21,19 +20,20 @@ type Notify func(
allowUnverifiedNotificationChannel bool,
) error
type ChannelChains interface {
Email(context.Context) (*senders.Chain, *smtp.Config, error)
SMS(context.Context) (*senders.Chain, *twilio.Config, error)
Webhook(context.Context, webhook.Config) (*senders.Chain, error)
}
func SendEmail(
ctx context.Context,
channels ChannelChains,
mailhtml string,
translator *i18n.Translator,
user *query.NotifyUser,
emailConfig func(ctx context.Context) (*smtp.Config, error),
getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
getLogProvider func(ctx context.Context) (*log.Config, error),
colors *query.LabelPolicy,
assetsPrefix string,
triggeringEvent eventstore.Event,
successMetricName,
failureMetricName string,
) Notify {
return func(
url string,
@ -42,39 +42,30 @@ func SendEmail(
allowUnverifiedNotificationChannel bool,
) error {
args = mapNotifyUserToArgs(user, args)
data := GetTemplateData(translator, args, assetsPrefix, url, messageType, user.PreferredLanguage.String(), colors)
data := GetTemplateData(ctx, translator, args, url, messageType, user.PreferredLanguage.String(), colors)
template, err := templates.GetParsedTemplate(mailhtml, data)
if err != nil {
return err
}
return generateEmail(
ctx,
channels,
user,
data.Subject,
template,
emailConfig,
getFileSystemProvider,
getLogProvider,
allowUnverifiedNotificationChannel,
triggeringEvent,
successMetricName,
failureMetricName,
)
}
}
func SendSMSTwilio(
ctx context.Context,
channels ChannelChains,
translator *i18n.Translator,
user *query.NotifyUser,
twilioConfig func(ctx context.Context) (*twilio.Config, error),
getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
getLogProvider func(ctx context.Context) (*log.Config, error),
colors *query.LabelPolicy,
assetsPrefix string,
triggeringEvent eventstore.Event,
successMetricName,
failureMetricName string,
) Notify {
return func(
url string,
@ -83,18 +74,14 @@ func SendSMSTwilio(
allowUnverifiedNotificationChannel bool,
) error {
args = mapNotifyUserToArgs(user, args)
data := GetTemplateData(translator, args, assetsPrefix, url, messageType, user.PreferredLanguage.String(), colors)
data := GetTemplateData(ctx, translator, args, url, messageType, user.PreferredLanguage.String(), colors)
return generateSms(
ctx,
channels,
user,
data.Text,
twilioConfig,
getFileSystemProvider,
getLogProvider,
allowUnverifiedNotificationChannel,
triggeringEvent,
successMetricName,
failureMetricName,
)
}
}
@ -102,23 +89,17 @@ func SendSMSTwilio(
func SendJSON(
ctx context.Context,
webhookConfig webhook.Config,
getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
getLogProvider func(ctx context.Context) (*log.Config, error),
channels ChannelChains,
serializable interface{},
triggeringEvent eventstore.Event,
successMetricName,
failureMetricName string,
) Notify {
return func(_ string, _ map[string]interface{}, _ string, _ bool) error {
return handleJSON(
return handleWebhook(
ctx,
webhookConfig,
getFileSystemProvider,
getLogProvider,
channels,
serializable,
triggeringEvent,
successMetricName,
failureMetricName,
)
}
}

View File

@ -1,27 +1,31 @@
package types
import (
"context"
"time"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendOTPSMSCode(requestedDomain, origin, code string, expiry time.Duration) error {
args := otpArgs(code, origin, requestedDomain, expiry)
func (notify Notify) SendOTPSMSCode(ctx context.Context, code string, expiry time.Duration) error {
args := otpArgs(ctx, code, expiry)
return notify("", args, domain.VerifySMSOTPMessageType, false)
}
func (notify Notify) SendOTPEmailCode(user *query.NotifyUser, url, requestedDomain, origin, code string, expiry time.Duration) error {
args := otpArgs(code, origin, requestedDomain, expiry)
func (notify Notify) SendOTPEmailCode(ctx context.Context, url, code string, expiry time.Duration) error {
args := otpArgs(ctx, code, expiry)
return notify(url, args, domain.VerifyEmailOTPMessageType, false)
}
func otpArgs(code, origin, requestedDomain string, expiry time.Duration) map[string]interface{} {
func otpArgs(ctx context.Context, code string, expiry time.Duration) map[string]interface{} {
args := make(map[string]interface{})
args["OTP"] = code
args["Origin"] = origin
args["Domain"] = requestedDomain
args["Origin"] = http_utils.ComposedOrigin(ctx)
args["Domain"] = authz.GetInstance(ctx).RequestedDomain()
args["Expiry"] = expiry
return args
}

View File

@ -1,13 +1,16 @@
package types
import (
"context"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/console"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendPasswordChange(user *query.NotifyUser, origin string) error {
url := console.LoginHintLink(origin, user.PreferredLoginName)
func (notify Notify) SendPasswordChange(ctx context.Context, user *query.NotifyUser) error {
url := console.LoginHintLink(http_utils.ComposedOrigin(ctx), user.PreferredLoginName)
args := make(map[string]interface{})
return notify(url, args, domain.PasswordChangeMessageType, true)
}

View File

@ -1,17 +1,19 @@
package types
import (
"context"
"strings"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendPasswordCode(user *query.NotifyUser, origin, code, urlTmpl string) error {
func (notify Notify) SendPasswordCode(ctx context.Context, user *query.NotifyUser, code, urlTmpl string) error {
var url string
if urlTmpl == "" {
url = login.InitPasswordLink(origin, user.ID, code, user.ResourceOwner)
url = login.InitPasswordLink(http_utils.ComposedOrigin(ctx), user.ID, code, user.ResourceOwner)
} else {
var buf strings.Builder
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {

View File

@ -1,17 +1,19 @@
package types
import (
"context"
"strings"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendPasswordlessRegistrationLink(user *query.NotifyUser, origin, code, codeID, urlTmpl string) error {
func (notify Notify) SendPasswordlessRegistrationLink(ctx context.Context, user *query.NotifyUser, code, codeID, urlTmpl string) error {
var url string
if urlTmpl == "" {
url = domain.PasswordlessInitCodeLink(origin+login.HandlerPrefix+login.EndpointPasswordlessRegistration, user.ID, user.ResourceOwner, codeID, code)
url = domain.PasswordlessInitCodeLink(http_utils.ComposedOrigin(ctx)+login.HandlerPrefix+login.EndpointPasswordlessRegistration, user.ID, user.ResourceOwner, codeID, code)
} else {
var buf strings.Builder
if err := domain.RenderPasskeyURLTemplate(&buf, urlTmpl, user.ID, user.ResourceOwner, codeID, code); err != nil {
@ -19,6 +21,5 @@ func (notify Notify) SendPasswordlessRegistrationLink(user *query.NotifyUser, or
}
url = buf.String()
}
return notify(url, nil, domain.PasswordlessRegistrationMessageType, true)
}

View File

@ -1,11 +1,13 @@
package types
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
@ -80,7 +82,7 @@ func TestNotify_SendPasswordlessRegistrationLink(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, notify := mockNotify()
err := notify.SendPasswordlessRegistrationLink(tt.args.user, tt.args.origin, tt.args.code, tt.args.codeID, tt.args.urlTmpl)
err := notify.SendPasswordlessRegistrationLink(http_utils.WithComposedOrigin(context.Background(), tt.args.origin), tt.args.user, tt.args.code, tt.args.codeID, tt.args.urlTmpl)
require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, got)
})

View File

@ -1,13 +1,15 @@
package types
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendPhoneVerificationCode(user *query.NotifyUser, origin, code, requestedDomain string) error {
func (notify Notify) SendPhoneVerificationCode(ctx context.Context, code string) error {
args := make(map[string]interface{})
args["Code"] = code
args["Domain"] = requestedDomain
args["Domain"] = authz.GetInstance(ctx).RequestedDomain()
return notify("", args, domain.VerifyPhoneMessageType, true)
}

View File

@ -1,15 +1,20 @@
package types
import (
"context"
"fmt"
"strings"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/assets"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/notification/templates"
"github.com/zitadel/zitadel/internal/query"
)
func GetTemplateData(translator *i18n.Translator, translateArgs map[string]interface{}, assetsPrefix, href, msgType, lang string, policy *query.LabelPolicy) templates.TemplateData {
func GetTemplateData(ctx context.Context, translator *i18n.Translator, translateArgs map[string]interface{}, href, msgType, lang string, policy *query.LabelPolicy) templates.TemplateData {
assetsPrefix := http_util.ComposedOrigin(ctx) + assets.HandlerPrefix
templateData := templates.TemplateData{
URL: href,
PrimaryColor: templates.DefaultPrimaryColor,
@ -28,9 +33,6 @@ func GetTemplateData(translator *i18n.Translator, translateArgs map[string]inter
if policy.Light.FontColor != "" {
templateData.FontColor = policy.Light.FontColor
}
if assetsPrefix == "" {
return templateData
}
if policy.Light.LogoURL != "" {
templateData.LogoURL = fmt.Sprintf("%s/%s/%s", assetsPrefix, policy.ID, policy.Light.LogoURL)
}

View File

@ -6,26 +6,18 @@ import (
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/channels/fs"
"github.com/zitadel/zitadel/internal/notification/channels/log"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/notification/messages"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/query"
)
func generateEmail(
ctx context.Context,
channels ChannelChains,
user *query.NotifyUser,
subject,
content string,
smtpConfig func(ctx context.Context) (*smtp.Config, error),
getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
getLogProvider func(ctx context.Context) (*log.Config, error),
lastEmail bool,
triggeringEvent eventstore.Event,
successMetricName,
failureMetricName string,
) error {
content = html.UnescapeString(content)
message := &messages.Email{
@ -37,23 +29,14 @@ func generateEmail(
if lastEmail {
message.Recipients = []string{user.LastEmail}
}
channelChain, err := senders.EmailChannels(
ctx,
smtpConfig,
getFileSystemProvider,
getLogProvider,
successMetricName,
failureMetricName,
)
emailChannels, _, err := channels.Email(ctx)
if err != nil {
return err
}
if channelChain.Len() == 0 {
if emailChannels.Len() == 0 {
return errors.ThrowPreconditionFailed(nil, "MAIL-83nof", "Errors.Notification.Channels.NotPresent")
}
return channelChain.HandleMessage(message)
return emailChannels.HandleMessage(message)
}
func mapNotifyUserToArgs(user *query.NotifyUser, args map[string]interface{}) map[string]interface{} {

View File

@ -4,31 +4,26 @@ import (
"context"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/channels/fs"
"github.com/zitadel/zitadel/internal/notification/channels/log"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/messages"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/query"
)
func generateSms(
ctx context.Context,
channels ChannelChains,
user *query.NotifyUser,
content string,
getTwilioProvider func(ctx context.Context) (*twilio.Config, error),
getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
getLogProvider func(ctx context.Context) (*log.Config, error),
lastPhone bool,
triggeringEvent eventstore.Event,
successMetricName,
failureMetricName string,
) error {
number := ""
twilioConfig, err := getTwilioProvider(ctx)
smsChannels, twilioConfig, err := channels.SMS(ctx)
logging.OnError(err).Error("could not create sms channel")
if smsChannels.Len() == 0 {
return errors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent")
}
if err == nil {
number = twilioConfig.SenderNumber
}
@ -41,19 +36,5 @@ func generateSms(
if lastPhone {
message.RecipientPhoneNumber = user.LastPhone
}
channelChain, err := senders.SMSChannels(
ctx,
twilioConfig,
getFileSystemProvider,
getLogProvider,
successMetricName,
failureMetricName,
)
logging.OnError(err).Error("could not create sms channel")
if channelChain.Len() == 0 {
return errors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent")
}
return channelChain.HandleMessage(message)
return smsChannels.HandleMessage(message)
}

View File

@ -0,0 +1,27 @@
package types
import (
"context"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/messages"
)
func handleWebhook(
ctx context.Context,
webhookConfig webhook.Config,
channels ChannelChains,
serializable interface{},
triggeringEvent eventstore.Event,
) error {
message := &messages.JSON{
Serializable: serializable,
TriggeringEvent: triggeringEvent,
}
webhookChannels, err := channels.Webhook(ctx, webhookConfig)
if err != nil {
return err
}
return webhookChannels.HandleMessage(message)
}

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"time"
"github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
@ -409,10 +410,11 @@ func NewOTPSMSCheckedEvent(
type OTPEmailChallengedEvent struct {
eventstore.BaseEvent `json:"-"`
Code *crypto.CryptoValue `json:"code"`
Expiry time.Duration `json:"expiry"`
ReturnCode bool `json:"returnCode,omitempty"`
URLTmpl string `json:"urlTmpl,omitempty"`
Code *crypto.CryptoValue `json:"code"`
Expiry time.Duration `json:"expiry"`
ReturnCode bool `json:"returnCode,omitempty"`
URLTmpl string `json:"urlTmpl,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
}
func (e *OTPEmailChallengedEvent) Data() interface{} {
@ -427,6 +429,10 @@ func (e *OTPEmailChallengedEvent) SetBaseEvent(base *eventstore.BaseEvent) {
e.BaseEvent = *base
}
func (e *OTPEmailChallengedEvent) TriggerOrigin() string {
return e.TriggeredAtOrigin
}
func NewOTPEmailChallengedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
@ -441,10 +447,11 @@ func NewOTPEmailChallengedEvent(
aggregate,
OTPEmailChallengedType,
),
Code: code,
Expiry: expiry,
ReturnCode: returnCode,
URLTmpl: urlTmpl,
Code: code,
Expiry: expiry,
ReturnCode: returnCode,
URLTmpl: urlTmpl,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
}
}

View File

@ -7,6 +7,7 @@ import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
@ -244,6 +245,7 @@ type HumanInitialCodeAddedEvent struct {
eventstore.BaseEvent `json:"-"`
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
}
func (e *HumanInitialCodeAddedEvent) Data() interface{} {
@ -254,6 +256,10 @@ func (e *HumanInitialCodeAddedEvent) UniqueConstraints() []*eventstore.EventUniq
return nil
}
func (e *HumanInitialCodeAddedEvent) TriggerOrigin() string {
return e.TriggeredAtOrigin
}
func NewHumanInitialCodeAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
@ -266,8 +272,9 @@ func NewHumanInitialCodeAddedEvent(
aggregate,
HumanInitialCodeAddedType,
),
Code: code,
Expiry: expiry,
Code: code,
Expiry: expiry,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
}
}

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"time"
"github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
@ -122,10 +123,11 @@ func HumanEmailVerificationFailedEventMapper(event *repository.Event) (eventstor
type HumanEmailCodeAddedEvent struct {
eventstore.BaseEvent `json:"-"`
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
}
func (e *HumanEmailCodeAddedEvent) Data() interface{} {
@ -136,6 +138,10 @@ func (e *HumanEmailCodeAddedEvent) UniqueConstraints() []*eventstore.EventUnique
return nil
}
func (e *HumanEmailCodeAddedEvent) TriggerOrigin() string {
return e.TriggeredAtOrigin
}
func NewHumanEmailCodeAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
@ -159,10 +165,11 @@ func NewHumanEmailCodeAddedEventV2(
aggregate,
HumanEmailCodeAddedType,
),
Code: code,
Expiry: expiry,
URLTemplate: urlTemplate,
CodeReturned: codeReturned,
Code: code,
Expiry: expiry,
URLTemplate: urlTemplate,
CodeReturned: codeReturned,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
}
}

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"time"
"github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
@ -317,11 +318,12 @@ func HumanPasswordlessInitCodeAddedEventMapper(event *repository.Event) (eventst
type HumanPasswordlessInitCodeRequestedEvent struct {
eventstore.BaseEvent `json:"-"`
ID string `json:"id"`
Code *crypto.CryptoValue `json:"code"`
Expiry time.Duration `json:"expiry"`
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
ID string `json:"id"`
Code *crypto.CryptoValue `json:"code"`
Expiry time.Duration `json:"expiry"`
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
}
func (e *HumanPasswordlessInitCodeRequestedEvent) Data() interface{} {
@ -332,6 +334,10 @@ func (e *HumanPasswordlessInitCodeRequestedEvent) UniqueConstraints() []*eventst
return nil
}
func (e *HumanPasswordlessInitCodeRequestedEvent) TriggerOrigin() string {
return e.TriggeredAtOrigin
}
func NewHumanPasswordlessInitCodeRequestedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
@ -347,11 +353,12 @@ func NewHumanPasswordlessInitCodeRequestedEvent(
aggregate,
HumanPasswordlessInitCodeRequestedType,
),
ID: id,
Code: code,
Expiry: expiry,
URLTemplate: urlTmpl,
CodeReturned: codeReturned,
ID: id,
Code: code,
Expiry: expiry,
URLTemplate: urlTmpl,
CodeReturned: codeReturned,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
}
}

View File

@ -5,11 +5,11 @@ import (
"encoding/json"
"time"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository"
)
@ -29,10 +29,11 @@ type HumanPasswordChangedEvent struct {
// New events only use EncodedHash. However, the secret field
// is preserved to handle events older than the switch to Passwap.
Secret *crypto.CryptoValue `json:"secret,omitempty"`
EncodedHash string `json:"encodedHash,omitempty"`
ChangeRequired bool `json:"changeRequired"`
UserAgentID string `json:"userAgentID,omitempty"`
Secret *crypto.CryptoValue `json:"secret,omitempty"`
EncodedHash string `json:"encodedHash,omitempty"`
ChangeRequired bool `json:"changeRequired"`
UserAgentID string `json:"userAgentID,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
}
func (e *HumanPasswordChangedEvent) Data() interface{} {
@ -43,6 +44,10 @@ func (e *HumanPasswordChangedEvent) UniqueConstraints() []*eventstore.EventUniqu
return nil
}
func (e *HumanPasswordChangedEvent) TriggerOrigin() string {
return e.TriggeredAtOrigin
}
func NewHumanPasswordChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
@ -56,9 +61,10 @@ func NewHumanPasswordChangedEvent(
aggregate,
HumanPasswordChangedType,
),
EncodedHash: encodeHash,
ChangeRequired: changeRequired,
UserAgentID: userAgentID,
EncodedHash: encodeHash,
ChangeRequired: changeRequired,
UserAgentID: userAgentID,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
}
}
@ -77,11 +83,12 @@ func HumanPasswordChangedEventMapper(event *repository.Event) (eventstore.Event,
type HumanPasswordCodeAddedEvent struct {
eventstore.BaseEvent `json:"-"`
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
NotificationType domain.NotificationType `json:"notificationType,omitempty"`
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
NotificationType domain.NotificationType `json:"notificationType,omitempty"`
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
}
func (e *HumanPasswordCodeAddedEvent) Data() interface{} {
@ -92,6 +99,10 @@ func (e *HumanPasswordCodeAddedEvent) UniqueConstraints() []*eventstore.EventUni
return nil
}
func (e *HumanPasswordCodeAddedEvent) TriggerOrigin() string {
return e.TriggeredAtOrigin
}
func NewHumanPasswordCodeAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
@ -117,11 +128,12 @@ func NewHumanPasswordCodeAddedEventV2(
aggregate,
HumanPasswordCodeAddedType,
),
Code: code,
Expiry: expiry,
NotificationType: notificationType,
URLTemplate: urlTemplate,
CodeReturned: codeReturned,
Code: code,
Expiry: expiry,
NotificationType: notificationType,
URLTemplate: urlTemplate,
CodeReturned: codeReturned,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
}
}

View File

@ -5,10 +5,10 @@ import (
"encoding/json"
"time"
"github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository"
)
@ -315,6 +315,7 @@ type DomainClaimedEvent struct {
eventstore.BaseEvent `json:"-"`
UserName string `json:"userName"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
oldUserName string
userLoginMustBeDomain bool
}
@ -330,6 +331,10 @@ func (e *DomainClaimedEvent) UniqueConstraints() []*eventstore.EventUniqueConstr
}
}
func (e *DomainClaimedEvent) TriggerOrigin() string {
return e.TriggeredAtOrigin
}
func NewDomainClaimedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
@ -346,6 +351,7 @@ func NewDomainClaimedEvent(
UserName: userName,
oldUserName: oldUserName,
userLoginMustBeDomain: userLoginMustBeDomain,
TriggeredAtOrigin: http.ComposedOrigin(ctx),
}
}