fix(notify): notify user in projection (#3889)

* start implement notify user in projection

* fix(stmt): add copy to multi stmt

* use projections for notify users

* feat: notifications from projections

* feat: notifications from projections

* cleanup

* pre-release

* fix tests

* fix types

* fix command

* fix queryNotifyUser

* fix: build version

* fix: HumanPasswordlessInitCodeSent

Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
This commit is contained in:
Livio Spring
2022-07-06 14:09:49 +02:00
committed by GitHub
parent d15a15c809
commit a1d404291d
46 changed files with 2018 additions and 1839 deletions

View File

@@ -1,38 +1,17 @@
package types
import (
"context"
"strings"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/domain"
"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/templates"
"github.com/zitadel/zitadel/internal/query"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
)
type DomainClaimedData struct {
templates.TemplateData
URL string
}
func SendDomainClaimed(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, username string, emailConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), colors *query.LabelPolicy, assetsPrefix string, origin string) error {
func (notify Notify) SendDomainClaimed(user *query.NotifyUser, origin, username string) error {
url := login.LoginLink(origin, user.ResourceOwner)
var args = mapNotifyUserToArgs(user)
args := make(map[string]interface{})
args["TempUsername"] = username
args["Domain"] = strings.Split(user.LastEmail, "@")[1]
domainClaimedData := &DomainClaimedData{
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.DomainClaimedMessageType, user.PreferredLanguage, colors),
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, domainClaimedData)
if err != nil {
return err
}
return generateEmail(ctx, user, domainClaimedData.Subject, template, emailConfig, getFileSystemProvider, getLogProvider, true)
return notify(url, args, domain.DomainClaimedMessageType, true)
}

View File

@@ -1,43 +1,14 @@
package types
import (
"context"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"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/templates"
"github.com/zitadel/zitadel/internal/query"
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
)
type EmailVerificationCodeData struct {
templates.TemplateData
URL string
}
func SendEmailVerificationCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.EmailCode, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string, origin string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
}
url := login.MailVerificationLink(origin, user.ID, codeString, user.ResourceOwner)
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString
emailCodeData := &EmailVerificationCodeData{
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.VerifyEmailMessageType, user.PreferredLanguage, colors),
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
if err != nil {
return err
}
return generateEmail(ctx, user, emailCodeData.Subject, template, smtpConfig, getFileSystemProvider, getLogProvider, true)
func (notify Notify) SendEmailVerificationCode(user *query.NotifyUser, origin, code string) error {
url := login.MailVerificationLink(origin, user.ID, code, user.ResourceOwner)
args := make(map[string]interface{})
args["Code"] = code
return notify(url, args, domain.VerifyEmailMessageType, true)
}

View File

@@ -1,49 +1,14 @@
package types
import (
"context"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"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/templates"
"github.com/zitadel/zitadel/internal/query"
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
)
type InitCodeEmailData struct {
templates.TemplateData
URL string
}
type UrlData struct {
UserID string
Code string
PasswordSet bool
OrgID string
}
func SendUserInitCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix, origin string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
}
url := login.InitUserLink(origin, user.ID, codeString, user.ResourceOwner, user.PasswordSet)
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString
initCodeData := &InitCodeEmailData{
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.InitCodeMessageType, user.PreferredLanguage, colors),
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, initCodeData)
if err != nil {
return err
}
return generateEmail(ctx, user, initCodeData.Subject, template, smtpConfig, getFileSystemProvider, getLogProvider, true)
func (notify Notify) SendUserInitCode(user *query.NotifyUser, origin, code string) error {
url := login.InitUserLink(origin, user.ID, code, user.ResourceOwner, user.PasswordSet)
args := make(map[string]interface{})
args["Code"] = code
return notify(url, args, domain.InitCodeMessageType, true)
}

View File

@@ -0,0 +1,73 @@
package types
import (
"context"
"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/templates"
"github.com/zitadel/zitadel/internal/query"
)
type Notify func(
url string,
args map[string]interface{},
messageType string,
allowUnverifiedNotificationChannel bool,
) error
func SendEmail(
ctx context.Context,
mailhtml string,
translator *i18n.Translator,
user *query.NotifyUser,
emailConfig func(ctx context.Context) (*smtp.EmailConfig, error),
getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error),
getLogProvider func(ctx context.Context) (*log.LogConfig, error),
colors *query.LabelPolicy,
assetsPrefix string,
) Notify {
return func(
url string,
args map[string]interface{},
messageType string,
allowUnverifiedNotificationChannel bool,
) error {
args = mapNotifyUserToArgs(user, args)
data := GetTemplateData(translator, args, assetsPrefix, url, messageType, user.PreferredLanguage.String(), colors)
template, err := templates.GetParsedTemplate(mailhtml, data)
if err != nil {
return err
}
return generateEmail(ctx, user, data.Subject, template, emailConfig, getFileSystemProvider, getLogProvider, allowUnverifiedNotificationChannel)
}
}
func SendSMSTwilio(
ctx context.Context,
translator *i18n.Translator,
user *query.NotifyUser,
twilioConfig func(ctx context.Context) (*twilio.TwilioConfig, error),
getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error),
getLogProvider func(ctx context.Context) (*log.LogConfig, error),
colors *query.LabelPolicy,
assetsPrefix string,
) Notify {
return func(
url string,
args map[string]interface{},
messageType string,
allowUnverifiedNotificationChannel bool,
) error {
args = mapNotifyUserToArgs(user, args)
data := GetTemplateData(translator, args, assetsPrefix, url, messageType, user.PreferredLanguage.String(), colors)
return generateSms(ctx, user, data.Text, twilioConfig, getFileSystemProvider, getLogProvider, allowUnverifiedNotificationChannel)
}
}
func externalLink(origin string) string {
return origin + "/ui/login"
}

View File

@@ -1,51 +1,14 @@
package types
import (
"context"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"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/templates"
"github.com/zitadel/zitadel/internal/query"
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
)
type PasswordCodeData struct {
templates.TemplateData
FirstName string
LastName string
URL string
}
func SendPasswordCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PasswordCode, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getTwilioConfig func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string, origin string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
}
url := login.InitPasswordLink(origin, user.ID, codeString, user.ResourceOwner)
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString
passwordResetData := &PasswordCodeData{
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.PasswordResetMessageType, user.PreferredLanguage, colors),
FirstName: user.FirstName,
LastName: user.LastName,
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, passwordResetData)
if err != nil {
return err
}
if code.NotificationType == int32(domain.NotificationTypeSms) {
return generateSms(ctx, user, passwordResetData.Text, getTwilioConfig, getFileSystemProvider, getLogProvider, false)
}
return generateEmail(ctx, user, passwordResetData.Subject, template, smtpConfig, getFileSystemProvider, getLogProvider, true)
func (notify Notify) SendPasswordCode(user *query.NotifyUser, origin, code string) error {
url := login.InitPasswordLink(origin, user.ID, code, user.ResourceOwner)
args := make(map[string]interface{})
args["Code"] = code
return notify(url, args, domain.PasswordResetMessageType, true)
}

View File

@@ -1,42 +1,12 @@
package types
import (
"context"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"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/templates"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/repository/user"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
)
type PasswordlessRegistrationLinkData struct {
templates.TemplateData
URL string
}
func SendPasswordlessRegistrationLink(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *user.HumanPasswordlessInitCodeRequestedEvent, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string, origin string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
}
url := domain.PasswordlessInitCodeLink(origin+login.HandlerPrefix+login.EndpointPasswordlessRegistration, user.ID, user.ResourceOwner, code.ID, codeString)
var args = mapNotifyUserToArgs(user)
emailCodeData := &PasswordlessRegistrationLinkData{
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.PasswordlessRegistrationMessageType, user.PreferredLanguage, colors),
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
if err != nil {
return err
}
return generateEmail(ctx, user, emailCodeData.Subject, template, smtpConfig, getFileSystemProvider, getLogProvider, true)
func (notify Notify) SendPasswordlessRegistrationLink(user *query.NotifyUser, origin, code, codeID string) error {
url := domain.PasswordlessInitCodeLink(origin+login.HandlerPrefix+login.EndpointPasswordlessRegistration, user.ID, user.ResourceOwner, codeID, code)
return notify(url, nil, domain.PasswordlessRegistrationMessageType, true)
}

View File

@@ -1,38 +1,12 @@
package types
import (
"context"
"fmt"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"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/twilio"
"github.com/zitadel/zitadel/internal/notification/templates"
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/query"
)
type PhoneVerificationCodeData struct {
UserID string
}
func SendPhoneVerificationCode(ctx context.Context, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PhoneCode, getTwilioConfig func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
}
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString
text := translator.Localize(fmt.Sprintf("%s.%s", domain.VerifyPhoneMessageType, domain.MessageText), args, user.PreferredLanguage)
codeData := &PhoneVerificationCodeData{UserID: user.ID}
template, err := templates.ParseTemplateText(text, codeData)
if err != nil {
return err
}
return generateSms(ctx, user, template, getTwilioConfig, getFileSystemProvider, getLogProvider, true)
func (notify Notify) SendPhoneVerificationCode(user *query.NotifyUser, origin, code string) error {
args := make(map[string]interface{})
args["Code"] = code
return notify("", args, domain.VerifyPhoneMessageType, true)
}

View File

@@ -11,7 +11,7 @@ import (
func GetTemplateData(translator *i18n.Translator, translateArgs map[string]interface{}, assetsPrefix, href, msgType, lang string, policy *query.LabelPolicy) templates.TemplateData {
templateData := templates.TemplateData{
Href: href,
URL: href,
PrimaryColor: templates.DefaultPrimaryColor,
BackgroundColor: templates.DefaultBackgroundColor,
FontColor: templates.DefaultFontColor,

View File

@@ -10,11 +10,10 @@ import (
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/notification/messages"
"github.com/zitadel/zitadel/internal/notification/senders"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/query"
)
func generateEmail(ctx context.Context, user *view_model.NotifyUser, subject, content string, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastEmail bool) error {
func generateEmail(ctx context.Context, user *query.NotifyUser, subject, content string, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastEmail bool) error {
content = html.UnescapeString(content)
message := &messages.Email{
Recipients: []string{user.VerifiedEmail},
@@ -36,20 +35,22 @@ func generateEmail(ctx context.Context, user *view_model.NotifyUser, subject, co
return channelChain.HandleMessage(message)
}
func mapNotifyUserToArgs(user *view_model.NotifyUser) map[string]interface{} {
return map[string]interface{}{
"UserName": user.UserName,
"FirstName": user.FirstName,
"LastName": user.LastName,
"NickName": user.NickName,
"DisplayName": user.DisplayName,
"LastEmail": user.LastEmail,
"VerifiedEmail": user.VerifiedEmail,
"LastPhone": user.LastPhone,
"VerifiedPhone": user.VerifiedPhone,
"PreferredLoginName": user.PreferredLoginName,
"LoginNames": user.LoginNames,
"ChangeDate": user.ChangeDate,
"CreationDate": user.CreationDate,
func mapNotifyUserToArgs(user *query.NotifyUser, args map[string]interface{}) map[string]interface{} {
if args == nil {
args = make(map[string]interface{})
}
args["UserName"] = user.Username
args["FirstName"] = user.FirstName
args["LastName"] = user.LastName
args["NickName"] = user.NickName
args["DisplayName"] = user.DisplayName
args["LastEmail"] = user.LastEmail
args["VerifiedEmail"] = user.VerifiedEmail
args["LastPhone"] = user.LastPhone
args["VerifiedPhone"] = user.VerifiedPhone
args["PreferredLoginName"] = user.PreferredLoginName
args["LoginNames"] = user.LoginNames
args["ChangeDate"] = user.ChangeDate
args["CreationDate"] = user.CreationDate
return args
}

View File

@@ -3,20 +3,22 @@ package types
import (
"context"
"github.com/zitadel/logging"
caos_errors "github.com/zitadel/zitadel/internal/errors"
"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"
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/query"
)
func generateSms(ctx context.Context, user *view_model.NotifyUser, content string, getTwilioProvider func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastPhone bool) error {
func generateSms(ctx context.Context, user *query.NotifyUser, content string, getTwilioProvider func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastPhone bool) error {
number := ""
twilio, err := getTwilioProvider(ctx)
twilioConfig, err := getTwilioProvider(ctx)
if err == nil {
number = twilio.SenderNumber
number = twilioConfig.SenderNumber
}
message := &messages.SMS{
SenderPhoneNumber: number,
@@ -27,7 +29,8 @@ func generateSms(ctx context.Context, user *view_model.NotifyUser, content strin
message.RecipientPhoneNumber = user.LastPhone
}
channelChain, err := senders.SMSChannels(ctx, twilio, getFileSystemProvider, getLogProvider)
channelChain, err := senders.SMSChannels(ctx, twilioConfig, getFileSystemProvider, getLogProvider)
logging.OnError(err).Error("could not create sms channel")
if channelChain.Len() == 0 {
return caos_errors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent")