feat: notification loginname (#381)

* feat: add login names to notify user

* feat: add login names to initial mail

* feat: add login names to initial mail
This commit is contained in:
Fabi
2020-07-07 19:31:51 +02:00
committed by GitHub
parent 5081ff21b0
commit 1c40d5645e
15 changed files with 320 additions and 93 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/view"
org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing"
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
"net/http"
"time"
@@ -29,6 +30,7 @@ type handler struct {
type EventstoreRepos struct {
UserEvents *usr_event.UserEventstore
OrgEvents *org_event.OrgEventstore
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, eventstore eventstore.Eventstore, repos EventstoreRepos, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) []spooler.Handler {
@@ -37,7 +39,10 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev
logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto")
}
return []spooler.Handler{
&NotifyUser{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}},
&NotifyUser{
handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount},
orgEvents: repos.OrgEvents,
},
&Notification{
handler: handler{view, bulkLimit, configs.cycleDuration("Notification"), errorCount},
eventstore: eventstore,

View File

@@ -1,6 +1,11 @@
package handler
import (
"context"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"time"
@@ -9,13 +14,13 @@ import (
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/user/repository/eventsourcing"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
type NotifyUser struct {
handler
eventstore eventstore.Eventstore
orgEvents *org_events.OrgEventstore
}
const (
@@ -33,35 +38,131 @@ func (p *NotifyUser) EventQuery() (*models.SearchQuery, error) {
if err != nil {
return nil, err
}
return eventsourcing.UserQuery(sequence), nil
return es_models.NewSearchQuery().
AggregateTypeFilter(es_model.UserAggregate, org_es_model.OrgAggregate).
LatestSequenceFilter(sequence), nil
}
func (p *NotifyUser) Reduce(event *models.Event) (err error) {
func (u *NotifyUser) Reduce(event *models.Event) (err error) {
switch event.AggregateType {
case es_model.UserAggregate:
return u.ProcessUser(event)
case org_es_model.OrgAggregate:
return u.ProcessOrg(event)
default:
return nil
}
}
func (u *NotifyUser) ProcessUser(event *models.Event) (err error) {
user := new(view_model.NotifyUser)
switch event.Type {
case es_model.UserAdded,
es_model.UserRegistered:
user.AppendEvent(event)
u.fillLoginNames(user)
case es_model.UserProfileChanged,
es_model.UserEmailChanged,
es_model.UserEmailVerified,
es_model.UserPhoneChanged,
es_model.UserPhoneVerified,
es_model.UserPhoneRemoved:
user, err = p.view.NotifyUserByID(event.AggregateID)
user, err = u.view.NotifyUserByID(event.AggregateID)
if err != nil {
return err
}
err = user.AppendEvent(event)
case es_model.UserRemoved:
err = p.view.DeleteNotifyUser(event.AggregateID, event.Sequence)
err = u.view.DeleteNotifyUser(event.AggregateID, event.Sequence)
default:
return p.view.ProcessedNotifyUserSequence(event.Sequence)
return u.view.ProcessedNotifyUserSequence(event.Sequence)
}
if err != nil {
return err
}
return p.view.PutNotifyUser(user)
return u.view.PutNotifyUser(user, user.Sequence)
}
func (u *NotifyUser) ProcessOrg(event *models.Event) (err error) {
switch event.Type {
case org_es_model.OrgDomainVerified,
org_es_model.OrgDomainRemoved,
org_es_model.OrgIamPolicyAdded,
org_es_model.OrgIamPolicyChanged,
org_es_model.OrgIamPolicyRemoved:
return u.fillLoginNamesOnOrgUsers(event)
case org_es_model.OrgDomainPrimarySet:
return u.fillPreferredLoginNamesOnOrgUsers(event)
default:
return u.view.ProcessedNotifyUserSequence(event.Sequence)
}
if err != nil {
return err
}
return nil
}
func (u *NotifyUser) fillLoginNamesOnOrgUsers(event *models.Event) error {
org, err := u.orgEvents.OrgByID(context.Background(), org_model.NewOrg(event.ResourceOwner))
if err != nil {
return err
}
policy, err := u.orgEvents.GetOrgIamPolicy(context.Background(), event.ResourceOwner)
if err != nil {
return err
}
users, err := u.view.NotifyUsersByOrgID(event.AggregateID)
if err != nil {
return err
}
for _, user := range users {
user.SetLoginNames(policy, org.Domains)
err := u.view.PutNotifyUser(user, 0)
if err != nil {
return err
}
}
return nil
}
func (u *NotifyUser) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error {
org, err := u.orgEvents.OrgByID(context.Background(), org_model.NewOrg(event.ResourceOwner))
if err != nil {
return err
}
policy, err := u.orgEvents.GetOrgIamPolicy(context.Background(), event.ResourceOwner)
if err != nil {
return err
}
if !policy.UserLoginMustBeDomain {
return nil
}
users, err := u.view.NotifyUsersByOrgID(event.AggregateID)
if err != nil {
return err
}
for _, user := range users {
user.PreferredLoginName = user.GenerateLoginName(org.GetPrimaryDomain().Domain, policy.UserLoginMustBeDomain)
err := u.view.PutNotifyUser(user, 0)
if err != nil {
return err
}
}
return nil
}
func (u *NotifyUser) fillLoginNames(user *view_model.NotifyUser) (err error) {
org, err := u.orgEvents.OrgByID(context.Background(), org_model.NewOrg(user.ResourceOwner))
if err != nil {
return err
}
policy, err := u.orgEvents.GetOrgIamPolicy(context.Background(), user.ResourceOwner)
if err != nil {
return err
}
user.SetLoginNames(policy, org.Domains)
user.PreferredLoginName = user.GenerateLoginName(org.GetPrimaryDomain().Domain, policy.UserLoginMustBeDomain)
return nil
}
func (p *NotifyUser) OnError(event *models.Event, err error) error {

View File

@@ -9,6 +9,7 @@ import (
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/handler"
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/spooler"
noti_view "github.com/caos/zitadel/internal/notification/repository/eventsourcing/view"
es_org "github.com/caos/zitadel/internal/org/repository/eventsourcing"
es_usr "github.com/caos/zitadel/internal/user/repository/eventsourcing"
"golang.org/x/text/language"
"net/http"
@@ -19,6 +20,7 @@ type Config struct {
Eventstore es_int.Config
View types.SQL
Spooler spooler.SpoolerConfig
Domain string
}
type EsRepository struct {
@@ -47,11 +49,13 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults) (
if err != nil {
return nil, err
}
org := es_org.StartOrg(es_org.OrgConfig{Eventstore: es, IAMDomain: conf.Domain}, systemDefaults)
i18n, err := i18n.NewTranslator(dir, i18n.TranslatorConfig{DefaultLanguage: conf.DefaultLanguage})
if err != nil {
return nil, err
}
eventstoreRepos := handler.EventstoreRepos{UserEvents: user}
eventstoreRepos := handler.EventstoreRepos{UserEvents: user, OrgEvents: org}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, eventstoreRepos, systemDefaults, i18n, dir)
return &EsRepository{

View File

@@ -8,7 +8,6 @@ import (
"github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/handler"
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/view"
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
"net/http"
)
@@ -19,10 +18,6 @@ type SpoolerConfig struct {
Handlers handler.Configs
}
type EventstoreRepos struct {
UserEvents *usr_event.UserEventstore
}
func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, sql *sql.DB, eventstoreRepos handler.EventstoreRepos, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) *spooler.Spooler {
spoolerConfig := spooler.Config{
Eventstore: es,

View File

@@ -14,12 +14,19 @@ func (v *View) NotifyUserByID(userID string) (*model.NotifyUser, error) {
return view.NotifyUserByID(v.Db, notifyUserTable, userID)
}
func (v *View) PutNotifyUser(user *model.NotifyUser) error {
func (v *View) PutNotifyUser(user *model.NotifyUser, sequence uint64) error {
err := view.PutNotifyUser(v.Db, notifyUserTable, user)
if err != nil {
return err
}
return v.ProcessedNotifyUserSequence(user.Sequence)
if sequence != 0 {
return v.ProcessedNotifyUserSequence(sequence)
}
return nil
}
func (v *View) NotifyUsersByOrgID(orgID string) ([]*model.NotifyUser, error) {
return view.NotifyUsersByOrgID(v.Db, notifyUserTable, orgID)
}
func (v *View) DeleteNotifyUser(userID string, eventSequence uint64) error {

View File

@@ -3,7 +3,7 @@ InitCode:
PreHeader: User initialisieren
Subject: User initialisieren
Greeting: Hallo {{.FirstName}} {{.LastName}},
Text: Dieser Benutzer wurde soeben im Zitadel erstellt. Du kannst den Button unten verwenden, um die Initialisierung abzuschliesen. (Code {{.Code}}) Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren.
Text: Dieser Benutzer wurde soeben im Zitadel erstellt. Mit dem Benutzernamen {{.PreferredLoginName}} kannst du dich anmelden. Nutze den untenstehenden Button, um die Initialisierung abzuschliesen. (Code {{.Code}}) Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren.
ButtonText: Initialisierung abschliessen
PasswordReset:
Title: Zitadel - Passwort zurücksetzen

View File

@@ -3,7 +3,7 @@ InitCode:
PreHeader: Initialize User
Subject: Initialize User
Greeting: Hello {{.FirstName}} {{.LastName}},
Text: This user was created in Zitadel. Please click the button below to finish the initialization process. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
Text: This user was created in Zitadel. Use the username {{.PreferredLoginName}} to login. Please click the button below to finish the initialization process. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
ButtonText: Finish initialization
PasswordReset:
Title: Zitadel - Reset password

View File

@@ -31,9 +31,10 @@ func SendUserInitCode(dir http.FileSystem, i18n *i18n.Translator, user *view_mod
return err
}
var args = map[string]interface{}{
"FirstName": user.FirstName,
"LastName": user.LastName,
"Code": codeString,
"FirstName": user.FirstName,
"LastName": user.LastName,
"Code": codeString,
"PreferredLoginName": user.PreferredLoginName,
}
systemDefaults.Notifications.TemplateData.InitCode.Translate(i18n, args, user.PreferredLanguage)
initCodeData := &InitCodeEmailData{TemplateData: systemDefaults.Notifications.TemplateData.InitCode, URL: url}