mirror of
https://github.com/zitadel/zitadel.git
synced 2025-11-03 12:32:52 +00:00
fix: configure default url templates (#10416)
# Which Problems Are Solved Emails are still send only with URLs to login v1. # How the Problems Are Solved Add configuration for URLs as URL templates, so that links can point at Login v2. # Additional Changes None # Additional Context Closes #10236 --------- Co-authored-by: Marco A. <marco@zitadel.com>
This commit is contained in:
@@ -571,7 +571,11 @@ Login:
|
||||
MaxAge: 12h # ZITADEL_LOGIN_CACHE_MAXAGE
|
||||
# 168h is 7 days, one week
|
||||
SharedMaxAge: 168h # ZITADEL_LOGIN_CACHE_SHAREDMAXAGE
|
||||
DefaultOTPEmailURLV2: "/otp/verify?loginName={{.LoginName}}&code={{.Code}}" # ZITADEL_LOGIN_CACHE_DEFAULTOTPEMAILURLV2
|
||||
DefaultPaths:
|
||||
BasePath: "/ui/v2/login"
|
||||
PasswordSetPath: "/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}"
|
||||
EmailCodePath: "/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}"
|
||||
OTPEmailPath: "/otp/verify?code={{.Code}}&userID={{.UserID}}&sessionId={{.SessionID}}"
|
||||
|
||||
Console:
|
||||
ShortCache:
|
||||
|
||||
@@ -197,6 +197,8 @@ func projections(
|
||||
config.OIDC.DefaultRefreshTokenExpiration,
|
||||
config.OIDC.DefaultRefreshTokenIdleExpiration,
|
||||
config.DefaultInstance.SecretGenerators,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
logging.OnError(err).Fatal("unable to start commands")
|
||||
|
||||
@@ -219,7 +221,7 @@ func projections(
|
||||
commands,
|
||||
queries,
|
||||
es,
|
||||
config.Login.DefaultOTPEmailURLV2,
|
||||
config.Login.DefaultPaths.OTPEmailPath,
|
||||
config.SystemDefaults.Notifications.FileSystemPath,
|
||||
keys.User,
|
||||
keys.SMTP,
|
||||
|
||||
@@ -92,6 +92,8 @@ func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error
|
||||
0,
|
||||
0,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -58,6 +58,8 @@ func (mig *externalConfigChange) Execute(ctx context.Context, _ eventstore.Event
|
||||
0,
|
||||
0,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -527,6 +527,9 @@ func startCommandsQueries(
|
||||
config.OIDC.DefaultRefreshTokenExpiration,
|
||||
config.OIDC.DefaultRefreshTokenIdleExpiration,
|
||||
config.DefaultInstance.SecretGenerators,
|
||||
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
logging.OnError(err).Fatal("unable to start commands")
|
||||
|
||||
@@ -549,7 +552,7 @@ func startCommandsQueries(
|
||||
commands,
|
||||
queries,
|
||||
eventstoreClient,
|
||||
config.Login.DefaultOTPEmailURLV2,
|
||||
config.Login.DefaultPaths.OTPEmailPath,
|
||||
config.SystemDefaults.Notifications.FileSystemPath,
|
||||
keys.User,
|
||||
keys.SMTP,
|
||||
|
||||
@@ -256,6 +256,8 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
||||
config.OIDC.DefaultRefreshTokenExpiration,
|
||||
config.OIDC.DefaultRefreshTokenIdleExpiration,
|
||||
config.DefaultInstance.SecretGenerators,
|
||||
config.Login.DefaultEmailCodeURLTemplate,
|
||||
config.Login.DefaultPasswordSetURLTemplate,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot start commands: %w", err)
|
||||
@@ -301,7 +303,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
||||
commands,
|
||||
queries,
|
||||
eventstoreClient,
|
||||
config.Login.DefaultOTPEmailURLV2,
|
||||
config.Login.DefaultPaths.OTPEmailPath,
|
||||
config.SystemDefaults.Notifications.FileSystemPath,
|
||||
keys.User,
|
||||
keys.SMTP,
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
)
|
||||
|
||||
type MockContextInstanceOpts func(i *instance)
|
||||
@@ -14,6 +16,12 @@ func WithMockDefaultLanguage(lang language.Tag) MockContextInstanceOpts {
|
||||
}
|
||||
}
|
||||
|
||||
func WithMockFeatures(features feature.Features) MockContextInstanceOpts {
|
||||
return func(i *instance) {
|
||||
i.features = features
|
||||
}
|
||||
}
|
||||
|
||||
func NewMockContext(instanceID, orgID, userID string, opts ...MockContextInstanceOpts) context.Context {
|
||||
ctx := context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID})
|
||||
|
||||
|
||||
@@ -52,7 +52,43 @@ type Config struct {
|
||||
AssetCache middleware.CacheConfig
|
||||
|
||||
// LoginV2
|
||||
DefaultOTPEmailURLV2 string
|
||||
DefaultPaths *DefaultPaths
|
||||
}
|
||||
|
||||
type DefaultPaths struct {
|
||||
BasePath string
|
||||
PasswordSetPath string
|
||||
EmailCodePath string
|
||||
OTPEmailPath string
|
||||
}
|
||||
|
||||
func (c *Config) defaultBaseURL(ctx context.Context) string {
|
||||
loginV2 := authz.GetInstance(ctx).Features().LoginV2
|
||||
if loginV2.Required {
|
||||
// use the origin as default
|
||||
baseURI := http_utils.DomainContext(ctx).Origin()
|
||||
// use custom base URI if defined
|
||||
if loginV2.BaseURI != nil && loginV2.BaseURI.String() != "" {
|
||||
baseURI = loginV2.BaseURI.String()
|
||||
}
|
||||
return baseURI + c.DefaultPaths.BasePath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Config) DefaultEmailCodeURLTemplate(ctx context.Context) string {
|
||||
basePath := c.defaultBaseURL(ctx)
|
||||
if basePath == "" {
|
||||
return ""
|
||||
}
|
||||
return basePath + c.DefaultPaths.EmailCodePath
|
||||
}
|
||||
func (c *Config) DefaultPasswordSetURLTemplate(ctx context.Context) string {
|
||||
basePath := c.defaultBaseURL(ctx)
|
||||
if basePath == "" {
|
||||
return ""
|
||||
}
|
||||
return c.defaultBaseURL(ctx) + c.DefaultPaths.PasswordSetPath
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
178
internal/api/ui/login/login_test.go
Normal file
178
internal/api/ui/login/login_test.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
)
|
||||
|
||||
func TestConfig_defaultBaseURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
config := &Config{
|
||||
DefaultPaths: &DefaultPaths{BasePath: "/basepath"},
|
||||
}
|
||||
|
||||
baseCustomURI, err := url.Parse("https://custom")
|
||||
require.Nil(t, err)
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
inputCtx context.Context
|
||||
http.DomainCtx
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "LoginV2 not required",
|
||||
inputCtx: authz.NewMockContext("instance1", "org1", "user1"),
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "LoginV2 required, no custom BaseURI",
|
||||
inputCtx: http.WithDomainContext(
|
||||
authz.NewMockContext("instance1", "org1", "user1",
|
||||
authz.WithMockFeatures(feature.Features{LoginV2: feature.LoginV2{Required: true}}),
|
||||
),
|
||||
&http.DomainCtx{Protocol: "https", PublicHost: "origin"},
|
||||
),
|
||||
expected: "https://origin/basepath",
|
||||
},
|
||||
{
|
||||
name: "LoginV2 required, custom BaseURI",
|
||||
inputCtx: http.WithDomainContext(
|
||||
authz.NewMockContext("instance1", "org1", "user1",
|
||||
authz.WithMockFeatures(feature.Features{LoginV2: feature.LoginV2{Required: true, BaseURI: baseCustomURI}}),
|
||||
),
|
||||
&http.DomainCtx{Protocol: "https", PublicHost: "origin"},
|
||||
),
|
||||
|
||||
expected: "https://custom/basepath",
|
||||
},
|
||||
{
|
||||
name: "LoginV2 required, custom BaseURI empty string",
|
||||
inputCtx: http.WithDomainContext(
|
||||
authz.NewMockContext("instance1", "org1", "user1",
|
||||
authz.WithMockFeatures(feature.Features{LoginV2: feature.LoginV2{Required: true, BaseURI: &url.URL{}}}),
|
||||
),
|
||||
&http.DomainCtx{Protocol: "https", PublicHost: "origin"},
|
||||
),
|
||||
expected: "https://origin/basepath",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := config.defaultBaseURL(tc.inputCtx)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_DefaultEmailCodeURLTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputCtx context.Context
|
||||
expectedEmailURLTemplate string
|
||||
}{
|
||||
{
|
||||
testName: "when base path is empty should return empty email url template",
|
||||
inputCtx: http.WithDomainContext(
|
||||
authz.NewMockContext("instance1", "org1", "user1",
|
||||
authz.WithMockFeatures(feature.Features{LoginV2: feature.LoginV2{Required: false, BaseURI: &url.URL{}}}),
|
||||
),
|
||||
&http.DomainCtx{Protocol: "https", PublicHost: "origin"},
|
||||
),
|
||||
expectedEmailURLTemplate: "",
|
||||
},
|
||||
{
|
||||
testName: "when base path is not empty should return expected url template",
|
||||
inputCtx: http.WithDomainContext(
|
||||
authz.NewMockContext("instance1", "org1", "user1",
|
||||
authz.WithMockFeatures(feature.Features{LoginV2: feature.LoginV2{Required: true, BaseURI: &url.URL{}}}),
|
||||
),
|
||||
&http.DomainCtx{Protocol: "https", PublicHost: "origin"},
|
||||
),
|
||||
expectedEmailURLTemplate: "https://origin/basepath/email-code-path",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
c := &Config{
|
||||
DefaultPaths: &DefaultPaths{
|
||||
BasePath: "/basepath",
|
||||
EmailCodePath: "/email-code-path"},
|
||||
}
|
||||
|
||||
// Test
|
||||
res := c.DefaultEmailCodeURLTemplate(tc.inputCtx)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedEmailURLTemplate, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_DefaultPasswordSetURLTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputCtx context.Context
|
||||
expectedEmailURLTemplate string
|
||||
}{
|
||||
{
|
||||
testName: "when base path is empty should return empty email url template",
|
||||
inputCtx: http.WithDomainContext(
|
||||
authz.NewMockContext("instance1", "org1", "user1",
|
||||
authz.WithMockFeatures(feature.Features{LoginV2: feature.LoginV2{Required: false, BaseURI: &url.URL{}}}),
|
||||
),
|
||||
&http.DomainCtx{Protocol: "https", PublicHost: "origin"},
|
||||
),
|
||||
expectedEmailURLTemplate: "",
|
||||
},
|
||||
{
|
||||
testName: "when base path is not empty should return expected url template",
|
||||
inputCtx: http.WithDomainContext(
|
||||
authz.NewMockContext("instance1", "org1", "user1",
|
||||
authz.WithMockFeatures(feature.Features{LoginV2: feature.LoginV2{Required: true, BaseURI: &url.URL{}}}),
|
||||
),
|
||||
&http.DomainCtx{Protocol: "https", PublicHost: "origin"},
|
||||
),
|
||||
expectedEmailURLTemplate: "https://origin/basepath/password-set-path",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
c := &Config{
|
||||
DefaultPaths: &DefaultPaths{
|
||||
BasePath: "/basepath",
|
||||
PasswordSetPath: "/password-set-path",
|
||||
},
|
||||
}
|
||||
|
||||
// Test
|
||||
res := c.DefaultPasswordSetURLTemplate(tc.inputCtx)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedEmailURLTemplate, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,9 @@ type Commands struct {
|
||||
// These instance's milestones never need to be invalidated,
|
||||
// so the query and cache overhead can completely eliminated.
|
||||
milestonesCompleted sync.Map
|
||||
|
||||
defaultEmailCodeURLTemplate func(ctx context.Context) string
|
||||
defaultPasswordSetURLTemplate func(ctx context.Context) string
|
||||
}
|
||||
|
||||
func StartCommands(
|
||||
@@ -120,6 +123,8 @@ func StartCommands(
|
||||
defaultRefreshTokenLifetime,
|
||||
defaultRefreshTokenIdleLifetime time.Duration,
|
||||
defaultSecretGenerators *SecretGenerators,
|
||||
defaultEmailCodeURLTemplate func(ctx context.Context) string,
|
||||
defaultPasswordSetURLTemplate func(ctx context.Context) string,
|
||||
) (repo *Commands, err error) {
|
||||
if externalDomain == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Df21s", "no external domain specified")
|
||||
@@ -199,8 +204,10 @@ func StartCommands(
|
||||
Issuer: defaults.Multifactors.OTP.Issuer,
|
||||
},
|
||||
},
|
||||
GenerateDomain: domain.NewGeneratedInstanceDomain,
|
||||
caches: caches,
|
||||
GenerateDomain: domain.NewGeneratedInstanceDomain,
|
||||
caches: caches,
|
||||
defaultEmailCodeURLTemplate: defaultEmailCodeURLTemplate,
|
||||
defaultPasswordSetURLTemplate: defaultPasswordSetURLTemplate,
|
||||
}
|
||||
|
||||
if defaultSecretGenerators != nil && defaultSecretGenerators.ClientSecret != nil {
|
||||
|
||||
@@ -304,6 +304,11 @@ func (c *Commands) addHumanCommandEmail(ctx context.Context, filter preparation.
|
||||
if human.Email.ReturnCode {
|
||||
human.EmailCode = &emailCode.Plain
|
||||
}
|
||||
|
||||
if human.Email.URLTemplate == "" {
|
||||
human.Email.URLTemplate = c.defaultEmailCodeURLTemplate(ctx)
|
||||
}
|
||||
|
||||
return append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &a.Aggregate, emailCode.Crypted, emailCode.Expiry, human.Email.URLTemplate, human.Email.ReturnCode, human.AuthRequestID)), nil
|
||||
}
|
||||
return cmds, nil
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
@@ -44,6 +45,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
|
||||
newCode encrypedCodeFunc
|
||||
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
|
||||
defaultSecretGenerators *SecretGenerators
|
||||
defaultEmailCodeURLTemplate func(ctx context.Context) string
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -539,10 +541,11 @@ func TestCommandSide_AddHuman(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
newCode: mockEncryptedCode("userinit", time.Hour),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
newCode: mockEncryptedCode("userinit", time.Hour),
|
||||
defaultEmailCodeURLTemplate: func(_ context.Context) string { return "" },
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -680,16 +683,17 @@ func TestCommandSide_AddHuman(t *testing.T) {
|
||||
Crypted: []byte("emailCode"),
|
||||
},
|
||||
1*time.Hour,
|
||||
"",
|
||||
"http://example.com/{{.user}}/email/{{.code}}",
|
||||
true,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||
defaultEmailCodeURLTemplate: func(_ context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -1477,12 +1481,11 @@ func TestCommandSide_AddHuman(t *testing.T) {
|
||||
newEncryptedCode: tt.fields.newCode,
|
||||
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
|
||||
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
|
||||
defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
|
||||
}
|
||||
err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail)
|
||||
if tt.res.err == nil {
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
} else if !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
return
|
||||
|
||||
@@ -154,6 +154,11 @@ func (c *Commands) changeUserEmailWithGeneratorEvents(ctx context.Context, userI
|
||||
if err = cmd.Change(ctx, domain.EmailAddress(email)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if urlTmpl == "" {
|
||||
urlTmpl = c.defaultEmailCodeURLTemplate(ctx)
|
||||
}
|
||||
|
||||
if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -171,6 +176,11 @@ func (c *Commands) sendUserEmailCodeWithGeneratorEvents(ctx context.Context, use
|
||||
if existingCheck && cmd.model.Code == nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "EMAIL-5w5ilin4yt", "Errors.User.Code.Empty")
|
||||
}
|
||||
|
||||
if urlTmpl == "" {
|
||||
urlTmpl = c.defaultEmailCodeURLTemplate(ctx)
|
||||
}
|
||||
|
||||
if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1160,8 +1160,9 @@ func TestCommands_ChangeUserEmailVerified(t *testing.T) {
|
||||
|
||||
func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
eventstore *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
defaultEmailCodeURLTemplate func(ctx context.Context) string
|
||||
}
|
||||
type args struct {
|
||||
userID string
|
||||
@@ -1320,11 +1321,13 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"", false, "",
|
||||
"http://example.com/{{.user}}/email/{{.code}}",
|
||||
false, "",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
userID: "user1",
|
||||
@@ -1376,11 +1379,12 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"", true, "",
|
||||
"http://example.com/{{.user}}/email/{{.code2}}", true, "",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code2}}" },
|
||||
},
|
||||
args: args{
|
||||
userID: "user1",
|
||||
@@ -1458,8 +1462,9 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
eventstore: tt.fields.eventstore,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
|
||||
}
|
||||
got, err := c.changeUserEmailWithGenerator(context.Background(), tt.args.userID, tt.args.email, GetMockSecretGenerator(t), tt.args.returnCode, tt.args.urlTmpl)
|
||||
require.ErrorIs(t, tt.wantErr, err)
|
||||
@@ -1470,8 +1475,9 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
|
||||
|
||||
func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
eventstore *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
defaultEmailCodeURLTemplate func(ctx context.Context) string
|
||||
}
|
||||
type args struct {
|
||||
userID string
|
||||
@@ -1573,11 +1579,12 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"", false, "",
|
||||
"http://example.com/{{.user}}/email/{{.code}}", false, "",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
userID: "user1",
|
||||
@@ -1624,7 +1631,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"", false, "",
|
||||
"http://example.com/{{.user}}/email/{{.code2}}", false, "",
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1638,11 +1645,12 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"", false, "",
|
||||
"http://example.com/{{.user}}/email/{{.code2}}", false, "",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code2}}" },
|
||||
},
|
||||
args: args{
|
||||
userID: "user1",
|
||||
@@ -1722,11 +1730,12 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
time.Hour*1,
|
||||
"", true, "",
|
||||
"http://example.com/{{.user}}/email/{{.code}}", true, "",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
userID: "user1",
|
||||
@@ -1800,8 +1809,9 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
eventstore: tt.fields.eventstore,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
|
||||
}
|
||||
got, err := c.sendUserEmailCodeWithGenerator(context.Background(), tt.args.userID, GetMockSecretGenerator(t), tt.args.returnCode, tt.args.urlTmpl, tt.args.checkExisting)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
|
||||
@@ -411,6 +411,10 @@ func (c *Commands) changeUserEmail(ctx context.Context, cmds []eventstore.Comman
|
||||
if err != nil {
|
||||
return cmds, code, err
|
||||
}
|
||||
if email.URLTemplate == "" {
|
||||
email.URLTemplate = c.defaultEmailCodeURLTemplate(ctx)
|
||||
}
|
||||
|
||||
cmds = append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, email.URLTemplate, email.ReturnCode, ""))
|
||||
if email.ReturnCode {
|
||||
code = &cryptoCode.Plain
|
||||
|
||||
@@ -43,6 +43,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
|
||||
checkPermission domain.PermissionCheck
|
||||
defaultSecretGenerators *SecretGenerators
|
||||
defaultEmailCodeURLTemplate func(ctx context.Context) string
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -500,15 +501,16 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
Crypted: []byte("emailverify"),
|
||||
},
|
||||
1*time.Hour,
|
||||
"",
|
||||
"http://example.com/{{.user}}/email/{{.code}}",
|
||||
false,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
newCode: mockEncryptedCode("emailverify", time.Hour),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
newCode: mockEncryptedCode("emailverify", time.Hour),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -647,16 +649,17 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
Crypted: []byte("emailCode"),
|
||||
},
|
||||
1*time.Hour,
|
||||
"",
|
||||
"http://example.com/{{.user}}/email/{{.code}}",
|
||||
true,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -1519,7 +1522,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
),
|
||||
expectPush(
|
||||
newRegisterHumanEvent("email@test.ch", "", false, true, "", language.English),
|
||||
user.NewHumanEmailCodeAddedEvent(
|
||||
user.NewHumanEmailCodeAddedEventV2(
|
||||
context.Background(),
|
||||
&userAgg.Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
@@ -1529,6 +1532,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
Crypted: []byte("mailVerify"),
|
||||
},
|
||||
time.Hour,
|
||||
"http://example.com/{{.user}}/email/{{.code}}",
|
||||
false,
|
||||
"authRequestID",
|
||||
),
|
||||
user.NewUserIDPLinkAddedEvent(
|
||||
@@ -1540,9 +1545,10 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
newCode: mockEncryptedCode("mailVerify", time.Hour),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
newCode: mockEncryptedCode("mailVerify", time.Hour),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -2162,6 +2168,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
|
||||
CryptoMFA: cryptoAlg,
|
||||
},
|
||||
},
|
||||
defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
|
||||
}
|
||||
err := r.AddUserHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail, tt.args.codeAlg)
|
||||
if tt.res.err == nil {
|
||||
@@ -2199,6 +2206,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
||||
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
|
||||
checkPermission domain.PermissionCheck
|
||||
defaultSecretGenerators *SecretGenerators
|
||||
defaultEmailCodeURLTemplate func(ctx context.Context) string
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -2555,14 +2563,15 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
||||
Crypted: []byte("emailCode"),
|
||||
},
|
||||
time.Hour,
|
||||
"",
|
||||
"http://example.com/{{.user}}/email/{{.code}}",
|
||||
false,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -2735,14 +2744,15 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
||||
Crypted: []byte("emailCode"),
|
||||
},
|
||||
time.Hour,
|
||||
"",
|
||||
"http://example.com/{{.user}}/email/{{.code}}",
|
||||
true,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
newCode: mockEncryptedCode("emailCode", time.Hour),
|
||||
defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -3747,6 +3757,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
|
||||
userEncryption: tt.args.codeAlg,
|
||||
defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
|
||||
}
|
||||
err := r.ChangeUserHuman(tt.args.ctx, tt.args.human, tt.args.codeAlg)
|
||||
if tt.res.err == nil {
|
||||
|
||||
@@ -62,6 +62,9 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if urlTmpl == "" {
|
||||
urlTmpl = c.defaultPasswordSetURLTemplate(ctx)
|
||||
}
|
||||
cmd := user.NewHumanPasswordCodeAddedEventV2(ctx, UserAggregateFromWriteModelCtx(ctx, &model.WriteModel), passwordCode.CryptedCode(), passwordCode.CodeExpiry(), notificationType, urlTmpl, returnCode, generatorID)
|
||||
|
||||
if returnCode {
|
||||
|
||||
@@ -5,11 +5,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -339,12 +338,13 @@ func TestCommands_requestPasswordReset(t *testing.T) {
|
||||
},
|
||||
}
|
||||
type fields struct {
|
||||
checkPermission domain.PermissionCheck
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
userEncryption crypto.EncryptionAlgorithm
|
||||
newCode encrypedCodeFunc
|
||||
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
|
||||
defaultSecretGenerators *SecretGenerators
|
||||
checkPermission domain.PermissionCheck
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
userEncryption crypto.EncryptionAlgorithm
|
||||
newCode encrypedCodeFunc
|
||||
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
|
||||
defaultSecretGenerators *SecretGenerators
|
||||
defaultPasswordSetURLTemplate func(ctx context.Context) string
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -460,14 +460,15 @@ func TestCommands_requestPasswordReset(t *testing.T) {
|
||||
},
|
||||
10*time.Minute,
|
||||
domain.NotificationTypeEmail,
|
||||
"",
|
||||
"http://example.com/{{.user}}/password/{{.code}}",
|
||||
false,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
newCode: mockEncryptedCode("code", 10*time.Minute),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
newCode: mockEncryptedCode("code", 10*time.Minute),
|
||||
defaultPasswordSetURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/password/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -686,14 +687,15 @@ func TestCommands_requestPasswordReset(t *testing.T) {
|
||||
},
|
||||
10*time.Minute,
|
||||
domain.NotificationTypeEmail,
|
||||
"",
|
||||
"http://example.com/{{.user}}/password/{{.code}}",
|
||||
true,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
newCode: mockEncryptedCode("code", 10*time.Minute),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
newCode: mockEncryptedCode("code", 10*time.Minute),
|
||||
defaultPasswordSetURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/password/{{.code}}" },
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -711,12 +713,13 @@ func TestCommands_requestPasswordReset(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
userEncryption: tt.fields.userEncryption,
|
||||
newEncryptedCode: tt.fields.newCode,
|
||||
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
|
||||
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
userEncryption: tt.fields.userEncryption,
|
||||
newEncryptedCode: tt.fields.newCode,
|
||||
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
|
||||
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
|
||||
defaultPasswordSetURLTemplate: tt.fields.defaultPasswordSetURLTemplate,
|
||||
}
|
||||
|
||||
got, gotPlainCode, err := c.requestPasswordReset(tt.args.ctx, tt.args.userID, tt.args.returnCode, tt.args.urlTmpl, tt.args.notificationType)
|
||||
|
||||
Reference in New Issue
Block a user