feat: label policy (#1708)

* feat: label policy proto extension

* feat: label policy and activate event

* feat: label policy asset events

* feat: label policy asset commands

* feat: add storage key

* feat: storage key validation

* feat: label policy asset tests

* feat: label policy query side

* feat: avatar

* feat: avatar event

* feat: human avatar

* feat: avatar read side

* feat: font on iam label policy

* feat: label policy font

* feat: possiblity to create bucket on put file

* uplaoder

* login policy logo

* set bucket prefix

* feat: avatar upload

* feat: avatar upload

* feat: use assets on command side

* feat: fix human avatar removed event

* feat: remove human avatar

* feat: mock asset storage

* feat: remove human avatar

* fix(operator): add configuration of asset storage to zitadel operator

* feat(console): private labeling policy (#1697)

* private labeling component, routing, preview

* font, colors, upload, i18n

* show logo

* fix: uniqueness (#1710)

* fix: uniqueconstraint to lower

* feat: change org

* feat: org change test

* feat: change org

* fix: tests

* fix: handle domain claims correctly

* feat: update org

Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>

* fix: handle domain claimed event correctly for service users (#1711)

* fix: handle domain claimed event correctly on user view

* fix: ignore domain claimed events for email notifications

* fix: change org

* handle org changed in read models correctly

* fix: change org in user grant handler

Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>

* fix: correct value (#1695)

* docs(api): correct link (#1712)

* upload service

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
Co-authored-by: Florian Forster <florian@caos.ch>

* feat: fix tests,

* feat: remove assets from label policy

* fix npm, set environment

* lint ts

* remove stylelinting

* fix(operator): add mapping for console with changed unit tests

* fix(operator): add secrets as env variables to pod

* feat: remove human avatar

* fix(operator): add secrets as env variables to pod

* feat: map label policy

* feat: labelpolicy, admin, mgmt, adv settings (#1715)

* fetch label policy, mgmt, admin service

* feat: advanced beh, links, add, update

* lint ts

* feat: watermark

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: custom css

* css

* css

* css

* css

* css

* getobject

* feat: dynamic handler

* feat: varibale css

* content info

* css overwrite

* feat: variablen css

* feat: generate css file

* feat: dark mode

* feat: dark mode

* fix logo css

* feat: upload logos

* dark mode with cookie

* feat: handle images in login

* avatar css and begin font

* feat: avatar

* feat: user avatar

* caching of static assets in login

* add avatar.js to main.html

* feat: header dont show logo if no url

* feat: label policy colors

* feat: mock asset storage

* feat: mock asset storage

* feat: fix tests

* feat: user avatar

* feat: header logo

* avatar

* avatar

* make it compatible with go 1.15

* feat: remove unused logos

* fix handler

* fix: styling error handling

* fonts

* fix: download func

* switch to mux

* fix: change upload api to assets

* fix build

* fix: download avatar

* fix: download logos

* fix: my avatar

* font

* fix: remove error msg popup possibility

* fix: docs

* fix: svalidate colors

* rem msg popup from frontend

* fix: email with private labeling

* fix: tests

* fix: email templates

* fix: change migration version

* fix: fix duplicate imports

* fix(console): assets, service url, upload, policy current and preview  (#1781)

* upload endpoint, layout

* fetch current, preview, fix upload

* cleanup private labeling

* fix linting

* begin generated asset handler

* generate asset api in dockerfile

* features for label policy

* features for label policy

* features

* flag for asset generator

* change asset generator flag

* fix label policy view in grpc

* fix: layout, activate policy (#1786)

* theme switcher up on top

* change layout

* activate policy

* feat(console): label policy back color, layout (#1788)

* theme switcher up on top

* change layout

* activate policy

* fix overwrite value fc

* reset policy, reset service

* autosave policy, preview desc, layout impv

* layout, i18n

* background colors, inject material styles

* load images

* clean, lint

* fix layout

* set custom hex

* fix content size conversion

* remove font format in generated css

* fix features for assets

* fix(console): label policy colors, image downloads, preview (#1804)

* load images

* colors, images binding

* lint

* refresh emitter

* lint

* propagate font colors

* upload error handling

* label policy feature check

* add blob in csp for console

* log

* fix: feature edits for label policy, refresh state on upload (#1807)

* show error on load image, stop spinner

* fix merge

* fix migration versions

* fix assets

* fix csp

* fix background color

* scss

* fix build

* lint scss

* fix statik for console

* fix features check for label policy

* cleanup

* lint

* public links

* fix notifications

* public links

* feat: merge main

* feat: fix translation files

* fix migration

* set api domain

* fix logo in email

* font face in email

* font face in email

* validate assets on upload

* cleanup

* add missing translations

* add missing translations

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Stefan Benz <stefan@caos.ch>
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
Fabi
2021-06-04 14:53:51 +02:00
committed by GitHub
parent c0d9d86b09
commit 73d37459bb
257 changed files with 18248 additions and 7178 deletions

View File

@@ -12,13 +12,18 @@ import (
)
type Config struct {
APIDomain string
Repository eventsourcing.Config
}
func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults, command *command.Commands) {
func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults, command *command.Commands, hasStatics bool) {
statikFS, err := fs.NewWithNamespace("notification")
logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start listener")
_, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command)
apiDomain := config.APIDomain
if !hasStatics {
apiDomain = ""
}
_, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, apiDomain)
logging.Log("MAIN-9uBxp").OnError(err).Panic("unable to start app")
}

View File

@@ -34,7 +34,7 @@ func (h *handler) Eventstore() v1.Eventstore {
return h.es
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) []query.Handler {
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem, apiDomain string) []query.Handler {
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey)
if err != nil {
logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto")
@@ -51,6 +51,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
aesCrypto,
i18n,
dir,
apiDomain,
),
}
}

View File

@@ -7,12 +7,15 @@ import (
"time"
"github.com/caos/logging"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
@@ -23,7 +26,6 @@ import (
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/user/repository/view"
"github.com/caos/zitadel/internal/user/repository/view/model"
"golang.org/x/text/language"
)
const (
@@ -50,6 +52,7 @@ type Notification struct {
i18n *i18n.Translator
statikDir http.FileSystem
subscription *v1.Subscription
apiDomain string
}
func newNotification(
@@ -59,6 +62,7 @@ func newNotification(
aesCrypto crypto.EncryptionAlgorithm,
translator *i18n.Translator,
statikDir http.FileSystem,
apiDomain string,
) *Notification {
h := &Notification{
handler: handler,
@@ -67,6 +71,7 @@ func newNotification(
i18n: translator,
statikDir: statikDir,
AesCrypto: aesCrypto,
apiDomain: apiDomain,
}
h.subscribe()
@@ -142,12 +147,13 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
return err
}
colors, err := n.getLabelPolicy(context.Background())
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
return err
}
template, err := n.getMailTemplate(context.Background())
template, err := n.getMailTemplate(ctx)
if err != nil {
return err
}
@@ -157,16 +163,16 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
return err
}
text, err := n.getMailText(context.Background(), mailTextTypeInitCode, user.PreferredLanguage)
text, err := n.getMailText(ctx, mailTextTypeInitCode, user.PreferredLanguage)
if err != nil {
return err
}
err = types.SendUserInitCode(string(template.Template), text, user, initCode, n.systemDefaults, n.AesCrypto, colors)
err = types.SendUserInitCode(string(template.Template), text, user, initCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
if err != nil {
return err
}
return n.command.HumanInitCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID)
return n.command.HumanInitCodeSent(ctx, event.ResourceOwner, event.AggregateID)
}
func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
@@ -180,13 +186,13 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
if err != nil || alreadyHandled {
return err
}
colors, err := n.getLabelPolicy(context.Background())
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
return err
}
template, err := n.getMailTemplate(context.Background())
template, err := n.getMailTemplate(ctx)
if err != nil {
return err
}
@@ -196,15 +202,15 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
return err
}
text, err := n.getMailText(context.Background(), mailTextTypePasswordReset, user.PreferredLanguage)
text, err := n.getMailText(ctx, mailTextTypePasswordReset, user.PreferredLanguage)
if err != nil {
return err
}
err = types.SendPasswordCode(string(template.Template), text, user, pwCode, n.systemDefaults, n.AesCrypto, colors)
err = types.SendPasswordCode(string(template.Template), text, user, pwCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
if err != nil {
return err
}
return n.command.PasswordCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID)
return n.command.PasswordCodeSent(ctx, event.ResourceOwner, event.AggregateID)
}
func (n *Notification) handleEmailVerificationCode(event *models.Event) (err error) {
@@ -219,12 +225,13 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
return nil
}
colors, err := n.getLabelPolicy(context.Background())
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
return err
}
template, err := n.getMailTemplate(context.Background())
template, err := n.getMailTemplate(ctx)
if err != nil {
return err
}
@@ -234,16 +241,16 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
return err
}
text, err := n.getMailText(context.Background(), mailTextTypeVerifyEmail, user.PreferredLanguage)
text, err := n.getMailText(ctx, mailTextTypeVerifyEmail, user.PreferredLanguage)
if err != nil {
return err
}
err = types.SendEmailVerificationCode(string(template.Template), text, user, emailCode, n.systemDefaults, n.AesCrypto, colors)
err = types.SendEmailVerificationCode(string(template.Template), text, user, emailCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
if err != nil {
return err
}
return n.command.HumanEmailVerificationCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID)
return n.command.HumanEmailVerificationCodeSent(ctx, event.ResourceOwner, event.AggregateID)
}
func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err error) {
@@ -285,25 +292,26 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) {
if user.LastEmail == "" {
return nil
}
colors, err := n.getLabelPolicy(context.Background())
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
return err
}
template, err := n.getMailTemplate(context.Background())
template, err := n.getMailTemplate(ctx)
if err != nil {
return err
}
text, err := n.getMailText(context.Background(), mailTextTypeDomainClaimed, user.PreferredLanguage)
text, err := n.getMailText(ctx, mailTextTypeDomainClaimed, user.PreferredLanguage)
if err != nil {
return err
}
err = types.SendDomainClaimed(string(template.Template), text, user, data["userName"], n.systemDefaults, colors)
err = types.SendDomainClaimed(string(template.Template), text, user, data["userName"], n.systemDefaults, colors, n.apiDomain)
if err != nil {
return err
}
return n.command.UserDomainClaimedSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID)
return n.command.UserDomainClaimedSent(ctx, event.ResourceOwner, event.AggregateID)
}
func (n *Notification) checkIfCodeAlreadyHandledOrExpired(event *models.Event, expiry time.Duration, eventTypes ...models.EventType) (bool, error) {
@@ -353,10 +361,10 @@ func getSetNotifyContextData(orgID string) context.Context {
// Read organization specific colors
func (n *Notification) getLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
// read from Org
policy, err := n.view.LabelPolicyByAggregateID(authz.GetCtxData(ctx).OrgID, labelPolicyTableOrg)
policy, err := n.view.LabelPolicyByAggregateIDAndState(authz.GetCtxData(ctx).OrgID, labelPolicyTableOrg, int32(domain.LabelPolicyStateActive))
if errors.IsNotFound(err) {
// read from default
policy, err = n.view.LabelPolicyByAggregateID(n.systemDefaults.IamID, labelPolicyTableDef)
policy, err = n.view.LabelPolicyByAggregateIDAndState(n.systemDefaults.IamID, labelPolicyTableDef, int32(domain.LabelPolicyStateActive))
if err != nil {
return nil, err
}

View File

@@ -26,7 +26,7 @@ type EsRepository struct {
spooler *es_spol.Spooler
}
func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands) (*EsRepository, error) {
func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands, apiDomain string) (*EsRepository, error) {
es, err := v1.Start(conf.Eventstore)
if err != nil {
return nil, err
@@ -45,7 +45,7 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, c
if err != nil {
return nil, err
}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, command, systemDefaults, translator, dir)
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, command, systemDefaults, translator, dir, apiDomain)
return &EsRepository{
spool,

View File

@@ -20,12 +20,12 @@ type SpoolerConfig struct {
Handlers handler.Configs
}
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) *spooler.Spooler {
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem, apiDomain string) *spooler.Spooler {
spoolerConfig := spooler.Config{
Eventstore: es,
Locker: &locker{dbClient: sql},
ConcurrentWorkers: c.ConcurrentWorkers,
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, systemDefaults, i18n, dir),
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, systemDefaults, i18n, dir, apiDomain),
}
spool := spoolerConfig.New()
spool.Start()

View File

@@ -5,6 +5,6 @@ import (
"github.com/caos/zitadel/internal/iam/repository/view/model"
)
func (v *View) LabelPolicyByAggregateID(aggregateID string, labelPolicyTableVar string) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTableVar, aggregateID)
func (v *View) LabelPolicyByAggregateIDAndState(aggregateID, labelPolicyTableVar string, state int32) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateIDAndState(v.Db, labelPolicyTableVar, aggregateID, state)
}

View File

@@ -1,8 +1,9 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title>
</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -10,241 +11,362 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
#outlook a { padding:0; }
body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }
table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }
img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }
p { display:block;margin:13px 0; }
</style>
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,500,700" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Lato:300,400,500,700);
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
.mj-column-per-60 {
width: 60% !important;
max-width: 60%;
}
.mj-column-per-100 { width:100% !important; max-width: 100%; }
.mj-column-per-60 { width:60% !important; max-width: 60%; }
}
</style>
<style type="text/css">
@media only screen and (max-width:480px) {
table.mj-full-width-mobile {
width: 100% !important;
}
table.mj-full-width-mobile { width: 100% !important; }
td.mj-full-width-mobile { width: auto !important; }
}
td.mj-full-width-mobile {
width: auto !important;
}
}
</style>
<style type="text/css">
.shadow a {
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
</style>
<style type="text/css">.shadow a {
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
}</style>
{{if .FontURL}}
<style>
@font-face {
font-family: '{{.FontFamily}}';
font-style: normal;
font-display: swap;
src: url({{.FontURL}});
}
</style>
{{end}}
</head>
<body style="word-spacing:normal;">
<div style="">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#fafafa;background-color:#fafafa;width:100%;border-radius:16px;">
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;border-radius:16px;max-width:800px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;border-radius:16px;">
<tbody>
<div
style=""
>
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:{{.BackgroundColor}};background-color:{{.BackgroundColor}};width:100%;border-radius:16px;"
>
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;border-radius:16px;max-width:800px;">
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;border-radius:16px;"
>
<tbody>
<tr>
<td
style="direction:ltr;font-size:0px;padding:20px 0;padding-left:0;text-align:center;"
>
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="800px" ><![endif]-->
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"
>
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-left:0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="800px" ><![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:800px;">
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"
>
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:800px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<td
style="direction:ltr;font-size:0px;padding:0;text-align:center;"
>
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="width:800px;" ><![endif]-->
<div
class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0;line-height:0;text-align:left;display:inline-block;width:100%;direction:ltr;"
>
<!--[if mso | IE]><table border="0" cellpadding="0" cellspacing="0" role="presentation" ><tr><td style="vertical-align:top;width:800px;" ><![endif]-->
<div
class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"
>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"
>
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="width:800px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0;line-height:0;text-align:left;display:inline-block;width:100%;direction:ltr;">
<!--[if mso | IE]><table border="0" cellpadding="0" cellspacing="0" role="presentation" ><tr><td style="vertical-align:top;width:800px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
<tbody>
<td style="vertical-align:top;padding:0;">
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%"
>
<tbody>
<tr>
<td
align="center" style="font-size:0px;padding:50px 0 30px 0;word-break:break-word;"
>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;"
>
<tbody>
<tr>
<td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%">
<tbody>
<tr>
<td align="center" style="font-size:0px;padding:50px 0 30px 0;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:180px;">
<img height="auto" src="https://static.zitadel.ch/zitadel-logo-dark@3x.png" style="border:0;border-radius:8px;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="180" />
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<td style="width:180px;">
<img
height="auto" src="{{.LogoURL}}" style="border:0;border-radius:8px;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="180"
/>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
<!--[if mso | IE]></td></tr><tr><td class="" width="800px" ><![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:800px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:480px;" ><![endif]-->
<div class="mj-column-per-60 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
<tbody>
<tr>
<td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%">
<tbody>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif;font-size:24px;font-weight:500;line-height:1;text-align:center;color:#22292f;">{{.Greeting}}</div>
</td>
</tr>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif;font-size:16px;font-weight:light;line-height:1.5;text-align:center;color:#22292f;">{{.Text}}</div>
</td>
</tr>
<tr>
<td align="center" vertical-align="middle" class="shadow" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#5282C1" role="presentation" style="border:none;border-radius:6px;cursor:auto;mso-padding-alt:10px 25px;background:#5282C1;" valign="middle">
<a href="{{.URL}}" rel="noopener noreferrer" style="display:inline-block;background:#5282C1;color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;font-weight:500;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:6px;" target="_blank">
{{.ButtonText}}
</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:20px;padding-right:20px;padding-bottom:20px;padding-left:20px;word-break:break-word;">
<p style="border-top:solid 2px #dbdbdb;font-size:1px;margin:0px auto;width:100%;">
</p>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #dbdbdb;font-size:1px;margin:0px auto;width:440px;" role="presentation" width="440px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]-->
</td>
</tr>
<tr>
<td align="center" style="font-size:0px;padding:16px;word-break:break-word;">
<div style="font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif;font-size:13px;line-height:1;text-align:center;color:#9ca299;"><a href="http://www.caos.ch" style="color:#e91e63; text-decoration: none;" target="_blank"> CAOS AG </a> <span style="color: #000000; margin: 0 .5rem;">|</span> Teufener Strasse 19 <span style="color: #000000; margin: 0 .5rem;">|</span> CH-9000 St. Gallen</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</body>
</tbody>
</table>
</html>
<!--[if mso | IE]></td></tr><tr><td class="" width="800px" ><![endif]-->
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"
>
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:800px;">
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"
>
<tbody>
<tr>
<td
style="direction:ltr;font-size:0px;padding:0;text-align:center;"
>
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:480px;" ><![endif]-->
<div
class="mj-column-per-60 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"
>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"
>
<tbody>
<tr>
<td style="vertical-align:top;padding:0;">
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%"
>
<tbody>
<tr>
<td
align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"
>
<div
style="font-family:{{.FontFamily}};font-size:24px;font-weight:500;line-height:1;text-align:center;color:{{.FontColor}};"
>{{.Greeting}}</div>
</td>
</tr>
<tr>
<td
align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"
>
<div
style="font-family:{{.FontFamily}};font-size:16px;font-weight:light;line-height:1.5;text-align:center;color:{{.FontColor}};"
>{{.Text}}</div>
</td>
</tr>
<tr>
<td
align="center" vertical-align="middle" class="shadow" style="font-size:0px;padding:10px 25px;word-break:break-word;"
>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;"
>
<tr>
<td
align="center" bgcolor="{{.PrimaryColor}}" role="presentation" style="border:none;border-radius:6px;cursor:auto;mso-padding-alt:10px 25px;background:{{.PrimaryColor}};" valign="middle"
>
<a
href="{{.URL}}" rel="noopener noreferrer" style="display:inline-block;background:{{.PrimaryColor}};color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;font-weight:500;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:6px;" target="_blank"
>
{{.ButtonText}}
</a>
</td>
</tr>
</table>
</td>
</tr>
{{if .IncludeFooter}}
<tr>
<td
align="center" style="font-size:0px;padding:10px 25px;padding-top:20px;padding-right:20px;padding-bottom:20px;padding-left:20px;word-break:break-word;"
>
<p
style="border-top:solid 2px #dbdbdb;font-size:1px;margin:0px auto;width:100%;"
>
</p>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #dbdbdb;font-size:1px;margin:0px auto;width:440px;" role="presentation" width="440px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]-->
</td>
</tr>
<tr>
<td
align="center" style="font-size:0px;padding:16px;word-break:break-word;"
>
<div
style="font-family:{{.FontFamily}};font-size:13px;line-height:1;text-align:center;color:{{.FontColor}};"
>{{.FooterText}}</div>
</td>
</tr>
{{end}}
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -1,8 +1,8 @@
<mjml>
<mj-head>
<mj-attributes>
<mj-font name="Lato" href="http://fonts.googleapis.com/css?family=Lato:200,300,400,600" />
<mj-text align="center" color="#22292f" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif" />
<mj-font name="Lato" href="{{.FontURL}}" />
<mj-text align="center" color="{{.FontColor}}" font-family="{{.FontFamily}}" />
<mj-section padding="0" full-width="full-width" />
<mj-body width="800px" border-radius="16px"/>
<mj-image padding="0" />
@@ -16,30 +16,36 @@
</mj-style>
</mj-head>
<mj-body>
<mj-wrapper background-color="#fafafa" border-radius="16px">
<mj-wrapper background-color="{{.BackgroundColor}}" border-radius="16px">
{{if .IncludeLogo}}
<mj-section>
<mj-group>
<mj-column>
<mj-image src="https://static.zitadel.ch/zitadel-logo-dark@3x.png" align="center" width="180px" border-radius="8px" padding="50px 0 30px 0" />
<mj-image src="{{.LogoURL}}" align="center" width="180px" border-radius="8px" padding="50px 0 30px 0" />
</mj-column>
</mj-group>
</mj-section>
{{end}}
<mj-section>
<mj-column width="60%">
<mj-text font-size="24px" font-weight="500">{{.Greeting}}</mj-text>
<mj-text font-size="16px" line-height="1.5" font-weight="light">{{.Text}}</mj-text>
<mj-button css-class="shadow" border-radius="6px" href="{{.URL}}" rel="noopener noreferrer" background-color="#5282C1" font-size="14px" font-weight="500" >{{.ButtonText}}</mj-button>
<mj-divider
border-color="#dbdbdb"
border-width="2px"
border-style="solid"
padding-left="20px"
padding-right="20px"
padding-bottom="20px"
padding-top="20px"
></mj-divider>
<mj-text padding="16px" color="#9ca299">
<a href="http://www.caos.ch" style="color:#e91e63; text-decoration: none;" target="_blank"> CAOS AG </a> <span style="color: #000000; margin: 0 .5rem;">|</span> Teufener Strasse 19 <span style="color: #000000; margin: 0 .5rem;">|</span> CH-9000 St. Gallen </mj-text>
<mj-button css-class="shadow" border-radius="6px" href="{{.URL}}" rel="noopener noreferrer" background-color="{{.PrimaryColor}}" font-size="14px" font-weight="500" >{{.ButtonText}}</mj-button>
{{if .IncludeFooter}}
<mj-divider
border-color="#dbdbdb"
border-width="2px"
border-style="solid"
padding-left="20px"
padding-right="20px"
padding-bottom="20px"
padding-top="20px"
></mj-divider>
<mj-text padding="16px">
{{.FooterText}} </mj-text>
{{end}}
</mj-column>
</mj-section>
</mj-wrapper>

View File

@@ -1,21 +1,45 @@
package templates
import (
"fmt"
"html"
"strings"
"github.com/caos/zitadel/internal/i18n"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
const (
defaultFont = "http://fonts.googleapis.com/css?family=Lato:200,300,400,600"
defaultFontFamily = "-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif"
defaultLogo = "https://static.zitadel.ch/zitadel-logo-dark@3x.png"
defaultFontColor = "#22292f"
defaultBackgroundColor = "#fafafa"
defaultPrimaryColor = "#5282C1"
//defaultOrgName = "CAOS AG"
//defaultOrgURL = "http://www.caos.ch"
//defaultFooter1 = "Teufener Strasse 19"
//defaultFooter2 = "CH-9000 St. Gallen"
)
type TemplateData struct {
Title string
PreHeader string
Subject string
Greeting string
Text string
Href string
ButtonText string
PrimaryColor string
SecondaryColor string
Title string
PreHeader string
Subject string
Greeting string
Text string
Href string
ButtonText string
PrimaryColor string
BackgroundColor string
FontColor string
IncludeLogo bool
LogoURL string
FontURL string
FontFamily string
IncludeFooter bool
FooterText string
}
func (data *TemplateData) Translate(i18n *i18n.Translator, args map[string]interface{}, langs ...string) {
@@ -29,3 +53,46 @@ func (data *TemplateData) Translate(i18n *i18n.Translator, args map[string]inter
}
data.ButtonText = i18n.Localize(data.ButtonText, nil, langs...)
}
func GetTemplateData(apiDomain, href string, text *iam_model.MailTextView, policy *iam_model.LabelPolicyView) TemplateData {
templateData := TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: html.UnescapeString(text.Text),
Href: href,
ButtonText: text.ButtonText,
PrimaryColor: defaultPrimaryColor,
BackgroundColor: defaultBackgroundColor,
FontColor: defaultFontColor,
LogoURL: defaultLogo,
FontURL: defaultFont,
FontFamily: defaultFontFamily,
IncludeFooter: false,
}
if policy.PrimaryColor != "" {
templateData.PrimaryColor = policy.PrimaryColor
}
if policy.BackgroundColor != "" {
templateData.BackgroundColor = policy.BackgroundColor
}
if policy.FontColor != "" {
templateData.FontColor = policy.FontColor
}
if apiDomain == "" {
return templateData
}
if policy.LogoURL == "" {
templateData.IncludeLogo = false
} else {
templateData.IncludeLogo = true
templateData.LogoURL = fmt.Sprintf("%s/assets/v1/%s/%s", apiDomain, policy.AggregateID, policy.LogoURL)
}
if policy.FontURL != "" {
split := strings.Split(policy.FontURL, "/")
templateData.FontFamily = split[len(split)-1] + "," + defaultFontFamily
templateData.FontURL = fmt.Sprintf("%s/assets/v1/%s/%s", apiDomain, policy.AggregateID, policy.FontURL)
}
return templateData
}

View File

@@ -15,7 +15,7 @@ type DomainClaimedData struct {
URL string
}
func SendDomainClaimed(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView) error {
func SendDomainClaimed(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView, apiDomain string) error {
url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.DomainClaimed, &UrlData{UserID: user.ID})
if err != nil {
return err
@@ -33,18 +33,8 @@ func SendDomainClaimed(mailhtml string, text *iam_model.MailTextView, user *view
text.Text = html.UnescapeString(text.Text)
emailCodeData := &DomainClaimedData{
TemplateData: templates.TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: html.UnescapeString(text.Text),
Href: url,
ButtonText: text.ButtonText,
PrimaryColor: colors.PrimaryColor,
SecondaryColor: colors.SecondaryColor,
},
URL: url,
TemplateData: templates.GetTemplateData(apiDomain, url, text, colors),
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
if err != nil {

View File

@@ -16,7 +16,7 @@ type EmailVerificationCodeData struct {
URL string
}
func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView) error {
func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
@@ -36,18 +36,8 @@ func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, us
text.Text = html.UnescapeString(text.Text)
emailCodeData := &EmailVerificationCodeData{
TemplateData: templates.TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: html.UnescapeString(text.Text),
Href: url,
ButtonText: text.ButtonText,
PrimaryColor: colors.PrimaryColor,
SecondaryColor: colors.SecondaryColor,
},
URL: url,
TemplateData: templates.GetTemplateData(apiDomain, url, text, colors),
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)

View File

@@ -22,7 +22,7 @@ type UrlData struct {
PasswordSet bool
}
func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView) error {
func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
@@ -43,18 +43,8 @@ func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_
text.Text = html.UnescapeString(text.Text)
emailCodeData := &InitCodeEmailData{
TemplateData: templates.TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: html.UnescapeString(text.Text),
Href: url,
ButtonText: text.ButtonText,
PrimaryColor: colors.PrimaryColor,
SecondaryColor: colors.SecondaryColor,
},
URL: url,
TemplateData: templates.GetTemplateData(apiDomain, url, text, colors),
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
if err != nil {

View File

@@ -18,7 +18,7 @@ type PasswordCodeData struct {
URL string
}
func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView) error {
func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
@@ -38,20 +38,10 @@ func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_
text.Text = html.UnescapeString(text.Text)
emailCodeData := &PasswordCodeData{
TemplateData: templates.TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: html.UnescapeString(text.Text),
Href: url,
ButtonText: text.ButtonText,
PrimaryColor: colors.PrimaryColor,
SecondaryColor: colors.SecondaryColor,
},
FirstName: user.FirstName,
LastName: user.LastName,
URL: url,
TemplateData: templates.GetTemplateData(apiDomain, url, text, colors),
FirstName: user.FirstName,
LastName: user.LastName,
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
if err != nil {