zitadel/internal/notification/handlers/user_notifier.go
Livio Spring 7a3ae8f499
fix(notifications): bring back legacy notification handling (#9015)
# Which Problems Are Solved

There are some problems related to the use of CockroachDB with the new
notification handling (#8931).
See #9002 for details.

# How the Problems Are Solved

- Brought back the previous notification handler as legacy mode.
- Added a configuration to choose between legacy mode and new parallel
workers.
  - Enabled legacy mode by default to prevent issues.

# Additional Changes

None

# Additional Context

- closes https://github.com/zitadel/zitadel/issues/9002
- relates to #8931
2024-12-06 10:56:19 +01:00

792 lines
24 KiB
Go

package handlers
import (
"context"
"time"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/console"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/notification/types"
"github.com/zitadel/zitadel/internal/repository/session"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
func init() {
RegisterSentHandler(user.HumanInitialCodeAddedType,
func(ctx context.Context, commands Commands, id, orgID string, _ *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.HumanInitCodeSent(ctx, orgID, id)
},
)
RegisterSentHandler(user.HumanEmailCodeAddedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.HumanEmailVerificationCodeSent(ctx, orgID, id)
},
)
RegisterSentHandler(user.HumanPasswordCodeAddedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.PasswordCodeSent(ctx, orgID, id, generatorInfo)
},
)
RegisterSentHandler(user.HumanOTPSMSCodeAddedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.HumanOTPSMSCodeSent(ctx, id, orgID, generatorInfo)
},
)
RegisterSentHandler(session.OTPSMSChallengedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.OTPSMSSent(ctx, id, orgID, generatorInfo)
},
)
RegisterSentHandler(user.HumanOTPEmailCodeAddedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.HumanOTPEmailCodeSent(ctx, id, orgID)
},
)
RegisterSentHandler(session.OTPEmailChallengedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.OTPEmailSent(ctx, id, orgID)
},
)
RegisterSentHandler(user.UserDomainClaimedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.UserDomainClaimedSent(ctx, orgID, id)
},
)
RegisterSentHandler(user.HumanPasswordlessInitCodeRequestedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.HumanPasswordlessInitCodeSent(ctx, id, orgID, args["CodeID"].(string))
},
)
RegisterSentHandler(user.HumanPasswordChangedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.PasswordChangeSent(ctx, orgID, id)
},
)
RegisterSentHandler(user.HumanPhoneCodeAddedType,
func(ctx context.Context, commands Commands, id, orgID string, generatorInfo *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.HumanPhoneVerificationCodeSent(ctx, orgID, id, generatorInfo)
},
)
RegisterSentHandler(user.HumanInviteCodeAddedType,
func(ctx context.Context, commands Commands, id, orgID string, _ *senders.CodeGeneratorInfo, args map[string]any) error {
return commands.InviteCodeSent(ctx, orgID, id)
},
)
}
const (
UserNotificationsProjectionTable = "projections.notifications"
)
type userNotifier struct {
commands Commands
queries *NotificationQueries
otpEmailTmpl string
}
func NewUserNotifier(
ctx context.Context,
config handler.Config,
commands Commands,
queries *NotificationQueries,
channels types.ChannelChains,
otpEmailTmpl string,
legacyMode bool,
) *handler.Handler {
if legacyMode {
return NewUserNotifierLegacy(ctx, config, commands, queries, channels, otpEmailTmpl)
}
return handler.NewHandler(ctx, &config, &userNotifier{
commands: commands,
queries: queries,
otpEmailTmpl: otpEmailTmpl,
})
}
func (u *userNotifier) Name() string {
return UserNotificationsProjectionTable
}
func (u *userNotifier) Reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: user.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: user.UserV1InitialCodeAddedType,
Reduce: u.reduceInitCodeAdded,
},
{
Event: user.HumanInitialCodeAddedType,
Reduce: u.reduceInitCodeAdded,
},
{
Event: user.UserV1EmailCodeAddedType,
Reduce: u.reduceEmailCodeAdded,
},
{
Event: user.HumanEmailCodeAddedType,
Reduce: u.reduceEmailCodeAdded,
},
{
Event: user.UserV1PasswordCodeAddedType,
Reduce: u.reducePasswordCodeAdded,
},
{
Event: user.HumanPasswordCodeAddedType,
Reduce: u.reducePasswordCodeAdded,
},
{
Event: user.UserDomainClaimedType,
Reduce: u.reduceDomainClaimed,
},
{
Event: user.HumanPasswordlessInitCodeRequestedType,
Reduce: u.reducePasswordlessCodeRequested,
},
{
Event: user.UserV1PhoneCodeAddedType,
Reduce: u.reducePhoneCodeAdded,
},
{
Event: user.HumanPhoneCodeAddedType,
Reduce: u.reducePhoneCodeAdded,
},
{
Event: user.HumanPasswordChangedType,
Reduce: u.reducePasswordChanged,
},
{
Event: user.HumanOTPSMSCodeAddedType,
Reduce: u.reduceOTPSMSCodeAdded,
},
{
Event: user.HumanOTPEmailCodeAddedType,
Reduce: u.reduceOTPEmailCodeAdded,
},
{
Event: user.HumanInviteCodeAddedType,
Reduce: u.reduceInviteCodeAdded,
},
},
},
{
Aggregate: session.AggregateType,
EventReducers: []handler.EventReducer{
{
Event: session.OTPSMSChallengedType,
Reduce: u.reduceSessionOTPSMSChallenged,
},
{
Event: session.OTPEmailChallengedType,
Reduce: u.reduceSessionOTPEmailChallenged,
},
},
},
}
}
func (u *userNotifier) reduceInitCodeAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanInitialCodeAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-EFe2f", "reduce.wrong.event.type %s", user.HumanInitialCodeAddedType)
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1InitialCodeAddedType, user.UserV1InitialCodeSentType,
user.HumanInitialCodeAddedType, user.HumanInitialCodeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
origin := http_util.DomainContext(ctx).Origin()
return u.commands.RequestNotification(
ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
origin,
e.EventType,
domain.NotificationTypeEmail,
domain.InitCodeMessageType,
).
WithURLTemplate(login.InitUserLinkTemplate(origin, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.AuthRequestID)).
WithCode(e.Code, e.Expiry).
WithArgs(&domain.NotificationArguments{
AuthRequestID: e.AuthRequestID,
}).
WithUnverifiedChannel(),
)
}), nil
}
func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanEmailCodeAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-SWf3g", "reduce.wrong.event.type %s", user.HumanEmailCodeAddedType)
}
if e.CodeReturned {
return handler.NewNoOpStatement(e), nil
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType,
user.HumanEmailCodeAddedType, user.HumanEmailCodeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
origin := http_util.DomainContext(ctx).Origin()
return u.commands.RequestNotification(ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
origin,
e.EventType,
domain.NotificationTypeEmail,
domain.VerifyEmailMessageType,
).
WithURLTemplate(u.emailCodeTemplate(origin, e)).
WithCode(e.Code, e.Expiry).
WithArgs(&domain.NotificationArguments{
AuthRequestID: e.AuthRequestID,
}).
WithUnverifiedChannel(),
)
}), nil
}
func (u *userNotifier) emailCodeTemplate(origin string, e *user.HumanEmailCodeAddedEvent) string {
if e.URLTemplate != "" {
return e.URLTemplate
}
return login.MailVerificationLinkTemplate(origin, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.AuthRequestID)
}
func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanPasswordCodeAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Eeg3s", "reduce.wrong.event.type %s", user.HumanPasswordCodeAddedType)
}
if e.CodeReturned {
return handler.NewNoOpStatement(e), nil
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1PasswordCodeAddedType, user.UserV1PasswordCodeSentType,
user.HumanPasswordCodeAddedType, user.HumanPasswordCodeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
origin := http_util.DomainContext(ctx).Origin()
return u.commands.RequestNotification(ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
origin,
e.EventType,
e.NotificationType,
domain.PasswordResetMessageType,
).
WithURLTemplate(u.passwordCodeTemplate(origin, e)).
WithCode(e.Code, e.Expiry).
WithArgs(&domain.NotificationArguments{
AuthRequestID: e.AuthRequestID,
}).
WithUnverifiedChannel(),
)
}), nil
}
func (u *userNotifier) passwordCodeTemplate(origin string, e *user.HumanPasswordCodeAddedEvent) string {
if e.URLTemplate != "" {
return e.URLTemplate
}
return login.InitPasswordLinkTemplate(origin, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.AuthRequestID)
}
func (u *userNotifier) reduceOTPSMSCodeAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanOTPSMSCodeAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ASF3g", "reduce.wrong.event.type %s", user.HumanOTPSMSCodeAddedType)
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.HumanOTPSMSCodeAddedType,
user.HumanOTPSMSCodeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
return u.commands.RequestNotification(ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
http_util.DomainContext(ctx).Origin(),
e.EventType,
domain.NotificationTypeSms,
domain.VerifySMSOTPMessageType,
).
WithCode(e.Code, e.Expiry).
WithArgs(otpArgs(ctx, e.Expiry)).
WithOTP(),
)
}), nil
}
func (u *userNotifier) reduceSessionOTPSMSChallenged(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*session.OTPSMSChallengedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Sk32L", "reduce.wrong.event.type %s", session.OTPSMSChallengedType)
}
if e.CodeReturned {
return handler.NewNoOpStatement(e), nil
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
session.OTPSMSChallengedType,
session.OTPSMSSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "")
if err != nil {
return err
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
args := otpArgs(ctx, e.Expiry)
args.SessionID = e.Aggregate().ID
return u.commands.RequestNotification(ctx,
s.UserFactor.ResourceOwner,
command.NewNotificationRequest(
s.UserFactor.UserID,
s.UserFactor.ResourceOwner,
http_util.DomainContext(ctx).Origin(),
e.EventType,
domain.NotificationTypeSms,
domain.VerifySMSOTPMessageType,
).
WithAggregate(e.Aggregate().ID, e.Aggregate().ResourceOwner).
WithCode(e.Code, e.Expiry).
WithOTP().
WithArgs(args),
)
}), nil
}
func (u *userNotifier) reduceOTPEmailCodeAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanOTPEmailCodeAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-JL3hw", "reduce.wrong.event.type %s", user.HumanOTPEmailCodeAddedType)
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.HumanOTPEmailCodeAddedType,
user.HumanOTPEmailCodeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
origin := http_util.DomainContext(ctx).Origin()
var authRequestID string
if e.AuthRequestInfo != nil {
authRequestID = e.AuthRequestInfo.ID
}
args := otpArgs(ctx, e.Expiry)
args.AuthRequestID = authRequestID
return u.commands.RequestNotification(ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
origin,
e.EventType,
domain.NotificationTypeEmail,
domain.VerifyEmailOTPMessageType,
).
WithURLTemplate(login.OTPLinkTemplate(origin, authRequestID, domain.MFATypeOTPEmail)).
WithCode(e.Code, e.Expiry).
WithOTP().
WithArgs(args),
)
}), nil
}
func (u *userNotifier) reduceSessionOTPEmailChallenged(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*session.OTPEmailChallengedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-zbsgt", "reduce.wrong.event.type %s", session.OTPEmailChallengedType)
}
if e.ReturnCode {
return handler.NewNoOpStatement(e), nil
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
session.OTPEmailChallengedType,
session.OTPEmailSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "")
if err != nil {
return err
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
origin := http_util.DomainContext(ctx).Origin()
args := otpArgs(ctx, e.Expiry)
args.SessionID = e.Aggregate().ID
return u.commands.RequestNotification(ctx,
s.UserFactor.ResourceOwner,
command.NewNotificationRequest(
s.UserFactor.UserID,
s.UserFactor.ResourceOwner,
origin,
e.EventType,
domain.NotificationTypeEmail,
domain.VerifyEmailOTPMessageType,
).
WithAggregate(e.Aggregate().ID, e.Aggregate().ResourceOwner).
WithURLTemplate(u.otpEmailTemplate(origin, e)).
WithCode(e.Code, e.Expiry).
WithOTP().
WithArgs(args),
)
}), nil
}
func (u *userNotifier) otpEmailTemplate(origin string, e *session.OTPEmailChallengedEvent) string {
if e.URLTmpl != "" {
return e.URLTmpl
}
return origin + u.otpEmailTmpl
}
func otpArgs(ctx context.Context, expiry time.Duration) *domain.NotificationArguments {
domainCtx := http_util.DomainContext(ctx)
return &domain.NotificationArguments{
Origin: domainCtx.Origin(),
Domain: domainCtx.RequestedDomain(),
Expiry: expiry,
}
}
func (u *userNotifier) reduceDomainClaimed(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.DomainClaimedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Drh5w", "reduce.wrong.event.type %s", user.UserDomainClaimedType)
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil,
user.UserDomainClaimedType, user.UserDomainClaimedSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
origin := http_util.DomainContext(ctx).Origin()
return u.commands.RequestNotification(ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
origin,
e.EventType,
domain.NotificationTypeEmail,
domain.DomainClaimedMessageType,
).
WithURLTemplate(login.LoginLink(origin, e.Aggregate().ResourceOwner)).
WithUnverifiedChannel().
WithPreviousDomain().
WithArgs(&domain.NotificationArguments{
TempUsername: e.UserName,
}),
)
}), nil
}
func (u *userNotifier) reducePasswordlessCodeRequested(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanPasswordlessInitCodeRequestedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-EDtjd", "reduce.wrong.event.type %s", user.HumanPasswordlessInitCodeAddedType)
}
if e.CodeReturned {
return handler.NewNoOpStatement(e), nil
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, map[string]interface{}{"id": e.ID}, user.HumanPasswordlessInitCodeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
origin := http_util.DomainContext(ctx).Origin()
return u.commands.RequestNotification(ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
origin,
e.EventType,
domain.NotificationTypeEmail,
domain.PasswordlessRegistrationMessageType,
).
WithURLTemplate(u.passwordlessCodeTemplate(origin, e)).
WithCode(e.Code, e.Expiry).
WithArgs(&domain.NotificationArguments{
CodeID: e.ID,
}),
)
}), nil
}
func (u *userNotifier) passwordlessCodeTemplate(origin string, e *user.HumanPasswordlessInitCodeRequestedEvent) string {
if e.URLTemplate != "" {
return e.URLTemplate
}
return domain.PasswordlessInitCodeLinkTemplate(origin+login.HandlerPrefix+login.EndpointPasswordlessRegistration, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.ID)
}
func (u *userNotifier) reducePasswordChanged(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanPasswordChangedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Yko2z8", "reduce.wrong.event.type %s", user.HumanPasswordChangedType)
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil, user.HumanPasswordChangeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
notificationPolicy, err := u.queries.NotificationPolicyByOrg(ctx, true, e.Aggregate().ResourceOwner, false)
if err != nil && !zerrors.IsNotFound(err) {
return err
}
if !notificationPolicy.PasswordChange {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
origin := http_util.DomainContext(ctx).Origin()
return u.commands.RequestNotification(ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
origin,
e.EventType,
domain.NotificationTypeEmail,
domain.PasswordChangeMessageType,
).
WithURLTemplate(console.LoginHintLink(origin, "{{.PreferredLoginName}}")).
WithUnverifiedChannel(),
)
}), nil
}
func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanPhoneCodeAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-He83g", "reduce.wrong.event.type %s", user.HumanPhoneCodeAddedType)
}
if e.CodeReturned {
return handler.NewNoOpStatement(e), nil
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1PhoneCodeAddedType, user.UserV1PhoneCodeSentType,
user.HumanPhoneCodeAddedType, user.HumanPhoneCodeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
return u.commands.RequestNotification(ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
http_util.DomainContext(ctx).Origin(),
e.EventType,
domain.NotificationTypeSms,
domain.VerifyPhoneMessageType,
).
WithCode(e.Code, e.Expiry).
WithUnverifiedChannel().
WithArgs(&domain.NotificationArguments{
Domain: http_util.DomainContext(ctx).RequestedDomain(),
}),
)
}), nil
}
func (u *userNotifier) reduceInviteCodeAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanInviteCodeAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Eeg3s", "reduce.wrong.event.type %s", user.HumanInviteCodeAddedType)
}
if e.CodeReturned {
return handler.NewNoOpStatement(e), nil
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.HumanInviteCodeAddedType, user.HumanInviteCodeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
origin := http_util.DomainContext(ctx).Origin()
applicationName := e.ApplicationName
if applicationName == "" {
applicationName = "ZITADEL"
}
return u.commands.RequestNotification(ctx,
e.Aggregate().ResourceOwner,
command.NewNotificationRequest(
e.Aggregate().ID,
e.Aggregate().ResourceOwner,
origin,
e.EventType,
domain.NotificationTypeEmail,
domain.InviteUserMessageType,
).
WithURLTemplate(u.inviteCodeTemplate(origin, e)).
WithCode(e.Code, e.Expiry).
WithUnverifiedChannel().
WithArgs(&domain.NotificationArguments{
AuthRequestID: e.AuthRequestID,
ApplicationName: applicationName,
}),
)
}), nil
}
func (u *userNotifier) inviteCodeTemplate(origin string, e *user.HumanInviteCodeAddedEvent) string {
if e.URLTemplate != "" {
return e.URLTemplate
}
return login.InviteUserLinkTemplate(origin, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.AuthRequestID)
}
func (u *userNotifier) checkIfCodeAlreadyHandledOrExpired(ctx context.Context, event eventstore.Event, expiry time.Duration, data map[string]interface{}, eventTypes ...eventstore.EventType) (bool, error) {
if expiry > 0 && event.CreatedAt().Add(expiry).Before(time.Now().UTC()) {
return true, nil
}
return u.queries.IsAlreadyHandled(ctx, event, data, eventTypes...)
}