feat: user v2alpha email API (#5708)

* chore(proto): update versions

* change protoc plugin

* some cleanups

* define api for setting emails in new api

* implement user.SetEmail

* move SetEmail buisiness logic into command

* resuse newCryptoCode

* command: add ChangeEmail unit tests

Not complete, was not able to mock the generator.

* Revert "resuse newCryptoCode"

This reverts commit c89e90ae35.

* undo change to crypto code generators

* command: use a generator so we can test properly

* command: reorganise ChangeEmail

improve test coverage

* implement VerifyEmail

including unit tests

* add URL template tests

* proto: change context to object

* remove old auth option

* remove old auth option

* fix linting errors

run gci on modified files

* add permission checks and fix some errors

* comments

* comments

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
This commit is contained in:
Silvan
2023-04-25 09:02:29 +02:00
committed by GitHub
parent 2a79e77c7b
commit 095ec21678
58 changed files with 3589 additions and 399 deletions

View File

@@ -182,6 +182,10 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-SWf3g", "reduce.wrong.event.type %s", user.HumanEmailCodeAddedType)
}
if e.CodeReturned {
return crdb.NewNoOpStatement(e), nil
}
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType,
@@ -232,7 +236,7 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St
e,
u.metricSuccessfulDeliveriesEmail,
u.metricFailedDeliveriesEmail,
).SendEmailVerificationCode(notifyUser, origin, code)
).SendEmailVerificationCode(notifyUser, origin, code, e.URLTemplate)
if err != nil {
return nil, err
}

View File

@@ -1,13 +1,25 @@
package types
import (
"strings"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendEmailVerificationCode(user *query.NotifyUser, origin, code string) error {
url := login.MailVerificationLink(origin, user.ID, code, user.ResourceOwner)
func (notify Notify) SendEmailVerificationCode(user *query.NotifyUser, origin, code string, urlTmpl string) error {
var url string
if urlTmpl == "" {
url = login.MailVerificationLink(origin, user.ID, code, user.ResourceOwner)
} else {
var buf strings.Builder
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {
return err
}
url = buf.String()
}
args := make(map[string]interface{})
args["Code"] = code
return notify(url, args, domain.VerifyEmailMessageType, true)

View File

@@ -0,0 +1,107 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
)
func TestNotify_SendEmailVerificationCode(t *testing.T) {
type res struct {
url string
args map[string]interface{}
messageType string
allowUnverifiedNotificationChannel bool
}
notify := func(dst *res) Notify {
return func(
url string,
args map[string]interface{},
messageType string,
allowUnverifiedNotificationChannel bool,
) error {
dst.url = url
dst.args = args
dst.messageType = messageType
dst.allowUnverifiedNotificationChannel = allowUnverifiedNotificationChannel
return nil
}
}
type args struct {
user *query.NotifyUser
origin string
code string
urlTmpl string
}
tests := []struct {
name string
args args
want *res
wantErr error
}{
{
name: "default URL",
args: args{
user: &query.NotifyUser{
ID: "user1",
ResourceOwner: "org1",
},
origin: "https://example.com",
code: "123",
urlTmpl: "",
},
want: &res{
url: "https://example.com/ui/login/mail/verification?userID=user1&code=123&orgID=org1",
args: map[string]interface{}{"Code": "123"},
messageType: domain.VerifyEmailMessageType,
allowUnverifiedNotificationChannel: true,
},
},
{
name: "template error",
args: args{
user: &query.NotifyUser{
ID: "user1",
ResourceOwner: "org1",
},
origin: "https://example.com",
code: "123",
urlTmpl: "{{",
},
want: &res{},
wantErr: caos_errs.ThrowInvalidArgument(nil, "USERv2-ooD8p", "Errors.User.Email.InvalidURLTemplate"),
},
{
name: "template success",
args: args{
user: &query.NotifyUser{
ID: "user1",
ResourceOwner: "org1",
},
origin: "https://example.com",
code: "123",
urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
},
want: &res{
url: "https://example.com/email/verify?userID=user1&code=123&orgID=org1",
args: map[string]interface{}{"Code": "123"},
messageType: domain.VerifyEmailMessageType,
allowUnverifiedNotificationChannel: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := new(res)
err := notify(got).SendEmailVerificationCode(tt.args.user, tt.args.origin, tt.args.code, tt.args.urlTmpl)
require.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, got)
})
}
}