diff --git a/apps/docs/content/self-hosting/deploy/docker-compose.yaml b/apps/docs/content/self-hosting/deploy/docker-compose.yaml
index bf233dcf5..3eb9df424 100644
--- a/apps/docs/content/self-hosting/deploy/docker-compose.yaml
+++ b/apps/docs/content/self-hosting/deploy/docker-compose.yaml
@@ -36,7 +36,7 @@ services:
# Activate the login v2 on an installation from scratch.
# To activate the login v2 on an existing installation, read the "What's next" section.
ZITADEL_DEFAULTINSTANCE_FEATURES_LOGINV2_REQUIRED: true # To use the login v1, set this to false.
- ZITADEL_DEFAULTINSTANCE_FEATURES_LOGINV2_BASEURI: http://localhost:3000/ui/v2/login
+ ZITADEL_DEFAULTINSTANCE_FEATURES_LOGINV2_BASEURI: http://localhost:3000/ui/v2/login/
# Configure the redirection paths to the login v2.
ZITADEL_OIDC_DEFAULTLOGINURLV2: http://localhost:3000/ui/v2/login/login?authRequest=
ZITADEL_OIDC_DEFAULTLOGOUTURLV2: http://localhost:3000/ui/v2/login/logout?post_logout_redirect=
diff --git a/apps/login/src/app/(login)/passkey/set/page.tsx b/apps/login/src/app/(login)/passkey/set/page.tsx
index 29ed8a720..31d707919 100644
--- a/apps/login/src/app/(login)/passkey/set/page.tsx
+++ b/apps/login/src/app/(login)/passkey/set/page.tsx
@@ -109,6 +109,7 @@ export default async function Page(props: { searchParams: Promise
)}
diff --git a/apps/login/src/components/register-passkey.tsx b/apps/login/src/components/register-passkey.tsx
index 1a619c51f..d0ac0113c 100644
--- a/apps/login/src/components/register-passkey.tsx
+++ b/apps/login/src/components/register-passkey.tsx
@@ -21,9 +21,19 @@ type Props = {
organization?: string;
code?: string;
codeId?: string;
+ loginName?: string;
};
-export function RegisterPasskey({ sessionId, userId, isPrompt, organization, requestId, code, codeId }: Props) {
+export function RegisterPasskey({
+ sessionId,
+ userId,
+ isPrompt,
+ organization,
+ requestId,
+ code,
+ codeId,
+ loginName: initialLoginName,
+}: Props) {
const { handleSubmit, formState } = useForm({
mode: "onChange",
});
@@ -155,7 +165,7 @@ export function RegisterPasskey({ sessionId, userId, isPrompt, organization, req
return;
}
- continueAndLogin();
+ continueAndLogin(verificationResponse.loginName);
}, [sessionId, userId, code]);
// Auto-submit when code is provided (similar to VerifyForm)
@@ -165,7 +175,7 @@ export function RegisterPasskey({ sessionId, userId, isPrompt, organization, req
}
}, [code, submitRegisterAndContinue]);
- function continueAndLogin() {
+ function continueAndLogin(loginName?: string) {
const params = new URLSearchParams();
if (organization) {
@@ -184,6 +194,10 @@ export function RegisterPasskey({ sessionId, userId, isPrompt, organization, req
params.set("userId", userId);
}
+ if (loginName ?? initialLoginName) {
+ params.set("loginName", loginName ?? initialLoginName ?? "");
+ }
+
router.push("/passkey?" + params);
}
diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts
index c99730d11..c3349127c 100644
--- a/apps/login/src/lib/server/passkeys.ts
+++ b/apps/login/src/lib/server/passkeys.ts
@@ -196,6 +196,7 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
}${os.name}${os.name ? ", " : ""}${browser.name}`;
}
+ let loginName: string | undefined;
let currentUserId: string;
if (command.sessionId) {
@@ -216,6 +217,7 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
}
currentUserId = userId;
+ loginName = session?.session?.factors?.user?.loginName;
} else {
// UserId-based flow
currentUserId = command.userId!;
@@ -226,9 +228,11 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
if (!userResponse || !userResponse.user) {
throw new Error("User not found");
}
+
+ loginName = userResponse.user.preferredLoginName;
}
- return zitadelVerifyPasskeyRegistration({
+ const response = await zitadelVerifyPasskeyRegistration({
serviceConfig,
request: create(VerifyPasskeyRegistrationRequestSchema, {
passkeyId: command.passkeyId,
@@ -237,6 +241,8 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
userId: currentUserId,
}),
});
+
+ return { ...response, loginName };
}
type SendPasskeyCommand = {
diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml
index ba89e4538..579e09e47 100644
--- a/cmd/defaults.yaml
+++ b/cmd/defaults.yaml
@@ -708,10 +708,17 @@ Login:
# 168h is 7 days, one week
SharedMaxAge: 168h # ZITADEL_LOGIN_CACHE_SHAREDMAXAGE
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}}"
+ BasePath: "/ui/v2/login/"
+ # PasswordSetPath is used to create the link for setting a password during user registration or password reset
+ PasswordSetPath: "password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}" # ZITADEL_LOGIN_DEFAULTPATHS_PASSWORDSETPATH
+ # EmailCodePath is used to create the link for verifying the user's email address
+ EmailCodePath: "verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}"
+ # OTPEmailPath is used to create the link for verifying the user's OTP during login
+ OTPEmailPath: "otp/email?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}"
+ # PasskeySetPath is used to create the link for adding a passkey to the user's account though the login UI
+ PasskeySetPath: "passkey/set?codeId={{.CodeID}}&code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}"
+ # DomainClaimedPath is used to create the link to the login UI after an organization has claimed a domain and the user's loginname was updated
+ DomainClaimedPath: "loginname?loginName={{.TempUsername}}&organization={{.OrgID}}"
Console:
ShortCache:
diff --git a/cmd/mirror/config.go b/cmd/mirror/config.go
index 495ec91e9..78560201a 100644
--- a/cmd/mirror/config.go
+++ b/cmd/mirror/config.go
@@ -94,6 +94,7 @@ func newConfig(v *viper.Viper, config any) error {
hooks.MapHTTPHeaderStringDecode,
hook.Base64ToBytesHookFunc(),
hook.TagToLanguageHookFunc(),
+ hook.StringToURLHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
diff --git a/cmd/mirror/projections.go b/cmd/mirror/projections.go
index 9b9ae9a4a..ab4393262 100644
--- a/cmd/mirror/projections.go
+++ b/cmd/mirror/projections.go
@@ -214,8 +214,7 @@ func projections(
config.OIDC.DefaultRefreshTokenExpiration,
config.OIDC.DefaultRefreshTokenIdleExpiration,
config.DefaultInstance.SecretGenerators,
- nil,
- nil,
+ config.Login.DefaultPaths,
)
logging.OnError(ctx, err).Fatal("unable to start commands")
@@ -239,7 +238,7 @@ func projections(
commands,
queries,
es,
- config.Login.DefaultPaths.OTPEmailPath,
+ config.Login.DefaultPaths.DefaultOTPEmailURLTemplate,
config.SystemDefaults.Notifications.FileSystemPath,
keys.User,
keys.SMTP,
diff --git a/cmd/setup/03.go b/cmd/setup/03.go
index 29c7408ac..92e8e34c8 100644
--- a/cmd/setup/03.go
+++ b/cmd/setup/03.go
@@ -9,6 +9,7 @@ import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
+ "github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/cache/connector"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
@@ -43,6 +44,7 @@ type FirstInstance struct {
externalSecure bool
externalPort uint16
domain string
+ defaultPaths *login.DefaultPaths
}
func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error {
@@ -92,8 +94,7 @@ func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error
0,
0,
nil,
- nil,
- nil,
+ mig.defaultPaths,
)
if err != nil {
return err
diff --git a/cmd/setup/config.go b/cmd/setup/config.go
index b2d1ba71b..aa1a1554b 100644
--- a/cmd/setup/config.go
+++ b/cmd/setup/config.go
@@ -85,6 +85,7 @@ func NewConfig(cmd *cobra.Command, v *viper.Viper) (*Config, instrumentation.Shu
hook.EnumHookFunc(authz.MemberTypeString),
hook.Base64ToBytesHookFunc(),
hook.TagToLanguageHookFunc(),
+ hook.StringToURLHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
@@ -205,6 +206,7 @@ func NewSteps(ctx context.Context, v *viper.Viper) (*Steps, error) {
viper.DecodeHook(mapstructure.ComposeDecodeHookFunc(
hook.Base64ToBytesHookFunc(),
hook.TagToLanguageHookFunc(),
+ hook.StringToURLHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
diff --git a/cmd/setup/config_change.go b/cmd/setup/config_change.go
index 8aeefa85b..8e0bebe48 100644
--- a/cmd/setup/config_change.go
+++ b/cmd/setup/config_change.go
@@ -3,6 +3,7 @@ package setup
import (
"context"
+ "github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/cache/connector"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
@@ -58,8 +59,7 @@ func (mig *externalConfigChange) Execute(ctx context.Context, _ eventstore.Event
0,
0,
nil,
- nil,
- nil,
+ &login.DefaultPaths{},
)
if err != nil {
diff --git a/cmd/setup/config_test.go b/cmd/setup/config_test.go
index faf5abafd..c14bd6f05 100644
--- a/cmd/setup/config_test.go
+++ b/cmd/setup/config_test.go
@@ -4,6 +4,7 @@ import (
"encoding/base64"
"fmt"
"net/http"
+ "net/url"
"strings"
"testing"
@@ -16,6 +17,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
+ "github.com/zitadel/zitadel/internal/feature"
)
func TestMustNewConfig(t *testing.T) {
@@ -48,12 +50,12 @@ Actions:
DenyList: []
`},
want: func(t *testing.T, config *Config) {
- assert.Equal(t, config.DefaultInstance.Features, &command.InstanceSetupFeatures{
+ assert.Equal(t, config.DefaultInstance.Features, &command.InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
- LoginV2: &command.InstanceSetupFeatureLoginV2{
+ LoginV2: &feature.LoginV2{
Required: true,
- BaseURI: gu.Ptr("http://zitadel:8080"),
+ BaseURI: &url.URL{Scheme: "http", Host: "zitadel:8080"},
},
})
},
diff --git a/cmd/setup/setup.go b/cmd/setup/setup.go
index c607923cf..e6b78189d 100644
--- a/cmd/setup/setup.go
+++ b/cmd/setup/setup.go
@@ -186,6 +186,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.FirstInstance.externalDomain = config.ExternalDomain
steps.FirstInstance.externalSecure = config.ExternalSecure
steps.FirstInstance.externalPort = config.ExternalPort
+ steps.FirstInstance.defaultPaths = config.Login.DefaultPaths
steps.s5LastFailed = &LastFailed{dbClient: dbClient.DB}
steps.s6OwnerRemoveColumns = &OwnerRemoveColumns{dbClient: dbClient.DB}
@@ -573,9 +574,7 @@ func startCommandsQueries(
config.OIDC.DefaultRefreshTokenExpiration,
config.OIDC.DefaultRefreshTokenIdleExpiration,
config.DefaultInstance.SecretGenerators,
-
- nil,
- nil,
+ config.Login.DefaultPaths,
)
logging.OnError(ctx, err).Fatal("unable to start commands")
@@ -599,7 +598,7 @@ func startCommandsQueries(
commands,
queries,
eventstoreClient,
- config.Login.DefaultPaths.OTPEmailPath,
+ config.Login.DefaultPaths.DefaultOTPEmailURLTemplate,
config.SystemDefaults.Notifications.FileSystemPath,
keys.User,
keys.SMTP,
diff --git a/cmd/start/config.go b/cmd/start/config.go
index 719961f11..054d28acc 100644
--- a/cmd/start/config.go
+++ b/cmd/start/config.go
@@ -151,6 +151,7 @@ func readConfig(v *viper.Viper) (*Config, error) {
hooks.SliceTypeStringDecode[*command.SetQuota],
hook.Base64ToBytesHookFunc(),
hook.TagToLanguageHookFunc(),
+ hook.StringToURLHookFunc(),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
diff --git a/cmd/start/config_test.go b/cmd/start/config_test.go
index 17dcd1ceb..91d8b7274 100644
--- a/cmd/start/config_test.go
+++ b/cmd/start/config_test.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net"
"net/http"
+ "net/url"
"strings"
"testing"
@@ -17,6 +18,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
+ "github.com/zitadel/zitadel/internal/feature"
)
func Test_readConfig(t *testing.T) {
@@ -84,12 +86,12 @@ Actions:
DenyList: []
`},
want: func(t *testing.T, config *Config) {
- assert.Equal(t, config.DefaultInstance.Features, &command.InstanceSetupFeatures{
+ assert.Equal(t, config.DefaultInstance.Features, &command.InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
- LoginV2: &command.InstanceSetupFeatureLoginV2{
+ LoginV2: &feature.LoginV2{
Required: true,
- BaseURI: gu.Ptr("http://zitadel:8080"),
+ BaseURI: &url.URL{Scheme: "http", Host: "zitadel:8080"},
},
})
},
diff --git a/cmd/start/start.go b/cmd/start/start.go
index 9f5aea9f3..74164cc5b 100644
--- a/cmd/start/start.go
+++ b/cmd/start/start.go
@@ -288,8 +288,7 @@ 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,
+ config.Login.DefaultPaths,
)
if err != nil {
return fmt.Errorf("cannot start commands: %w", err)
@@ -329,7 +328,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
commands,
queries,
eventstoreClient,
- config.Login.DefaultPaths.OTPEmailPath,
+ config.Login.DefaultPaths.DefaultOTPEmailURLTemplate,
config.SystemDefaults.Notifications.FileSystemPath,
keys.User,
keys.SMTP,
diff --git a/internal/api/http/request_context.go b/internal/api/http/request_context.go
index 1b93cce83..ad83b2829 100644
--- a/internal/api/http/request_context.go
+++ b/internal/api/http/request_context.go
@@ -61,11 +61,16 @@ func (r *DomainCtx) RequestedDomain() string {
// Origin returns the origin (protocol://hostname[:port]) for which the request was handled.
// The instance host is used if no public host was set.
func (r *DomainCtx) Origin() string {
- host := r.PublicHost
- if host == "" {
- host = r.InstanceHost
+ return fmt.Sprintf("%s://%s", r.Protocol, r.RequestedHost())
+}
+
+// OriginURL returns the origin (protocol://hostname[:port]) for which the request was handled as [*url.URL].
+// The instance host is used if no public host was set.
+func (r *DomainCtx) OriginURL() *url.URL {
+ return &url.URL{
+ Scheme: r.Protocol,
+ Host: r.RequestedHost(),
}
- return fmt.Sprintf("%s://%s", r.Protocol, host)
}
var _ slog.LogValuer = (*DomainCtx)(nil)
diff --git a/internal/api/ui/login/login.go b/internal/api/ui/login/login.go
index c18e6fb1d..a573e6fcb 100644
--- a/internal/api/ui/login/login.go
+++ b/internal/api/ui/login/login.go
@@ -4,6 +4,7 @@ import (
"context"
"net/http"
"net/url"
+ "slices"
"strings"
"time"
@@ -56,39 +57,125 @@ type Config struct {
}
type DefaultPaths struct {
- BasePath string
- PasswordSetPath string
- EmailCodePath string
- OTPEmailPath string
+ BasePath *url.URL
+ PasswordSetPath *url.URL
+ EmailCodePath *url.URL
+ OTPEmailPath *url.URL
+ PasskeySetPath *url.URL
+ DomainClaimedPath *url.URL
}
-func (c *Config) defaultBaseURL(ctx context.Context) string {
+func (c *DefaultPaths) defaultBaseURL(ctx context.Context) *url.URL {
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
+ // In case login v1 is still active, we don't want to return a base URL, as the templates will not be used
+ if !loginV2.Required {
+ return nil
}
- return ""
+ origin := http_utils.DomainContext(ctx).OriginURL()
+ if loginV2.BaseURI == nil || loginV2.BaseURI.String() == "" {
+ // In case the login v2 is enabled without a custom BaseURI,
+ // we use the request origin plus the default base path as the base URL for the templates.
+ if c.BasePath == nil {
+ return origin
+ }
+ return origin.ResolveReference(c.BasePath)
+ }
+ // In case a custom BaseURI is set for login v2, we use it as the base URL for the templates.
+ if loginV2.BaseURI.IsAbs() {
+ return loginV2.BaseURI
+ }
+ // If the custom BaseURI is a relative URL, we join the request origin with the custom BaseURI to form the base URL for the templates.
+ return origin.ResolveReference(loginV2.BaseURI)
}
-func (c *Config) DefaultEmailCodeURLTemplate(ctx context.Context) string {
- basePath := c.defaultBaseURL(ctx)
- if basePath == "" {
+// mergeURLs will merge two URLs, where the path is joined and query parameters are combined.
+// It uses (*url.URL).JoinPath on the base URL and the second URL's Path field to build the resulting path,
+// and then merges the query parameters from both URLs using [mergeQueries], preserving existing values and placeholders.
+// Fragments are not modified or combined by this function.
+// For example, merging "https://example.com/base" with a URL whose path is "/path/to/resource" will result in "https://example.com/base/path/to/resource".
+func mergeURLs(base, path *url.URL) string {
+ if base == nil && path == nil {
return ""
}
- return basePath + c.DefaultPaths.EmailCodePath
+ if base == nil {
+ return path.String()
+ }
+ if path == nil {
+ return base.String()
+ }
+ u := base.JoinPath(path.Path)
+ u.RawQuery = mergeQueries(u.Query(), path.Query())
+ return u.String()
}
-func (c *Config) DefaultPasswordSetURLTemplate(ctx context.Context) string {
+
+// mergeQueries will merge two sets of query parameters, preserving existing values and placeholders.
+// It takes two url.Values, where the first one is considered the base and the second one is merged into it.
+// The resulting query string will contain all unique key-value pairs from both sets of query parameters.
+// If a value contains placeholders in the format "{{placeholder}}", they are preserved in the final query string without being URL-encoded.
+func mergeQueries(base, path url.Values) string {
+ placeholders := map[string]string{}
+ for _, value := range base {
+ for _, v := range value {
+ if strings.Contains(v, "{{") && strings.Contains(v, "}}") {
+ placeholders[url.QueryEscape(v)] = v
+ }
+ }
+ }
+ for key, value := range path {
+ baseValues, ok := base[key]
+ for _, v := range value {
+ if !ok || !slices.Contains(baseValues, v) {
+ base.Add(key, v)
+ }
+ if strings.Contains(v, "{{") && strings.Contains(v, "}}") {
+ placeholders[url.QueryEscape(v)] = v
+ }
+ }
+ }
+ raw := base.Encode()
+ for esc, orig := range placeholders {
+ raw = strings.ReplaceAll(raw, esc, orig)
+ }
+ return raw
+}
+
+func (c *DefaultPaths) DefaultEmailCodeURLTemplate(ctx context.Context) string {
basePath := c.defaultBaseURL(ctx)
- if basePath == "" {
+ if basePath == nil {
return ""
}
- return c.defaultBaseURL(ctx) + c.DefaultPaths.PasswordSetPath
+ return mergeURLs(basePath, c.EmailCodePath)
+}
+
+func (c *DefaultPaths) DefaultPasswordSetURLTemplate(ctx context.Context) string {
+ basePath := c.defaultBaseURL(ctx)
+ if basePath == nil {
+ return ""
+ }
+ return mergeURLs(basePath, c.PasswordSetPath)
+}
+
+func (c *DefaultPaths) DefaultPasskeySetURLTemplate(ctx context.Context) string {
+ basePath := c.defaultBaseURL(ctx)
+ if basePath == nil {
+ return ""
+ }
+ return mergeURLs(basePath, c.PasskeySetPath)
+}
+
+func (c *DefaultPaths) DefaultDomainClaimedURLTemplate(ctx context.Context) string {
+ basePath := c.defaultBaseURL(ctx)
+ if basePath == nil {
+ return ""
+ }
+ return mergeURLs(basePath, c.DomainClaimedPath)
+}
+
+func (c *DefaultPaths) DefaultOTPEmailURLTemplate(origin *url.URL) string {
+ if c.BasePath == nil {
+ return mergeURLs(origin, c.OTPEmailPath)
+ }
+ return mergeURLs(origin.ResolveReference(c.BasePath), c.OTPEmailPath)
}
const (
diff --git a/internal/api/ui/login/login_test.go b/internal/api/ui/login/login_test.go
index 4cc4fa958..a545ad1f9 100644
--- a/internal/api/ui/login/login_test.go
+++ b/internal/api/ui/login/login_test.go
@@ -16,12 +16,10 @@ import (
func TestConfig_defaultBaseURL(t *testing.T) {
t.Parallel()
- config := &Config{
- DefaultPaths: &DefaultPaths{BasePath: "/basepath"},
- }
+ config := &DefaultPaths{BasePath: &url.URL{Path: "/basepath"}}
baseCustomURI, err := url.Parse("https://custom")
- require.Nil(t, err)
+ require.NoError(t, err)
tt := []struct {
name string
@@ -45,15 +43,14 @@ func TestConfig_defaultBaseURL(t *testing.T) {
expected: "https://origin/basepath",
},
{
- name: "LoginV2 required, custom BaseURI",
+ name: "LoginV2 required, absolute 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",
+ expected: "https://custom",
},
{
name: "LoginV2 required, custom BaseURI empty string",
@@ -65,13 +62,27 @@ func TestConfig_defaultBaseURL(t *testing.T) {
),
expected: "https://origin/basepath",
},
+ {
+ name: "LoginV2 required, relative custom BaseURI",
+ inputCtx: http.WithDomainContext(
+ authz.NewMockContext("instance1", "org1", "user1",
+ authz.WithMockFeatures(feature.Features{LoginV2: feature.LoginV2{Required: true, BaseURI: &url.URL{Path: "/custom"}}}),
+ ),
+ &http.DomainCtx{Protocol: "https", PublicHost: "origin"},
+ ),
+ expected: "https://origin/custom",
+ },
}
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)
+ if tc.expected == "" {
+ assert.Nil(t, result)
+ return
+ }
+ assert.Equal(t, tc.expected, result.String())
})
}
}
@@ -111,10 +122,9 @@ func TestConfig_DefaultEmailCodeURLTemplate(t *testing.T) {
t.Parallel()
// Given
- c := &Config{
- DefaultPaths: &DefaultPaths{
- BasePath: "/basepath",
- EmailCodePath: "/email-code-path"},
+ c := &DefaultPaths{
+ BasePath: &url.URL{Path: "/basepath"},
+ EmailCodePath: &url.URL{Path: "/email-code-path"},
}
// Test
@@ -161,11 +171,9 @@ func TestConfig_DefaultPasswordSetURLTemplate(t *testing.T) {
t.Parallel()
// Given
- c := &Config{
- DefaultPaths: &DefaultPaths{
- BasePath: "/basepath",
- PasswordSetPath: "/password-set-path",
- },
+ c := &DefaultPaths{
+ BasePath: &url.URL{Path: "/basepath"},
+ PasswordSetPath: &url.URL{Path: "/password-set-path"},
}
// Test
@@ -176,3 +184,220 @@ func TestConfig_DefaultPasswordSetURLTemplate(t *testing.T) {
})
}
}
+
+func TestConfig_DefaultPasskeySetURLTemplate(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/passkey-set-path",
+ },
+ }
+
+ for _, tc := range tt {
+ t.Run(tc.testName, func(t *testing.T) {
+ t.Parallel()
+
+ // Given
+ c := &DefaultPaths{
+ BasePath: &url.URL{Path: "/basepath"},
+ PasskeySetPath: &url.URL{Path: "/passkey-set-path"},
+ }
+
+ // Test
+ res := c.DefaultPasskeySetURLTemplate(tc.inputCtx)
+
+ // Verify
+ assert.Equal(t, tc.expectedEmailURLTemplate, res)
+ })
+ }
+}
+
+func TestConfig_DefaultDomainClaimedURLTemplate(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/loginname",
+ },
+ }
+
+ for _, tc := range tt {
+ t.Run(tc.testName, func(t *testing.T) {
+ t.Parallel()
+
+ // Given
+ c := &DefaultPaths{
+ BasePath: &url.URL{Path: "/basepath"},
+ DomainClaimedPath: &url.URL{Path: "/loginname"},
+ }
+
+ // Test
+ res := c.DefaultDomainClaimedURLTemplate(tc.inputCtx)
+
+ // Verify
+ assert.Equal(t, tc.expectedEmailURLTemplate, res)
+ })
+ }
+}
+
+func Test_mergeURLs(t *testing.T) {
+ type args struct {
+ base *url.URL
+ path *url.URL
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "both base and path are nil, return empty string",
+ args: args{
+ base: nil,
+ path: nil,
+ },
+ want: "",
+ },
+ {
+ name: "base is nil, path is set, return path",
+ args: args{
+ base: nil,
+ path: &url.URL{Path: "/path"},
+ },
+ want: "/path",
+ },
+ {
+ name: "base is set, path is nil, return base",
+ args: args{
+ base: &url.URL{Path: "/base"},
+ path: nil,
+ },
+ want: "/base",
+ },
+ {
+ name: "both base and path are set, return merged path",
+ args: args{
+ base: &url.URL{Path: "/base"},
+ path: &url.URL{Path: "/path"},
+ },
+ want: "/base/path",
+ },
+ {
+ name: "both base and path are set incl. placeholders, return merged path",
+ args: args{
+ base: &url.URL{Path: "/base", RawQuery: "query={{.Placeholder}}"},
+ path: &url.URL{Path: "/path", RawQuery: "query={{.Placeholder1}}&query2={{.Placeholder2}}"},
+ },
+ want: "/base/path?query={{.Placeholder}}&query={{.Placeholder1}}&query2={{.Placeholder2}}",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := mergeURLs(tt.args.base, tt.args.path)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func Test_mergeQueries(t *testing.T) {
+ type args struct {
+ base url.Values
+ path url.Values
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "both base and path are empty, return empty string",
+ args: args{
+ base: map[string][]string{},
+ path: map[string][]string{},
+ },
+ want: "",
+ },
+ {
+ name: "base is empty, path is set, return path",
+ args: args{
+ base: map[string][]string{},
+ path: map[string][]string{
+ "query": {"{{.Placeholder1}}", "{{.Placeholder2}}"},
+ },
+ },
+ want: "query={{.Placeholder1}}&query={{.Placeholder2}}",
+ },
+ {
+ name: "base is set, path is empty, return base",
+ args: args{
+ base: map[string][]string{
+ "query": {"{{.Placeholder}}"},
+ },
+ path: map[string][]string{},
+ },
+ want: "query={{.Placeholder}}",
+ },
+ {
+ name: "both base and path are set, return merged query",
+ args: args{
+ base: map[string][]string{
+ "query": {"{{.Placeholder}}"},
+ "query2": {"{{.Placeholder}}"},
+ },
+ path: map[string][]string{
+ "query": {"{{.Placeholder1}}"},
+ "query2": {"{{.Placeholder}}", "{{.Placeholder2}}"},
+ },
+ },
+ want: "query={{.Placeholder}}&query={{.Placeholder1}}&query2={{.Placeholder}}&query2={{.Placeholder2}}",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equalf(t, tt.want, mergeQueries(tt.args.base, tt.args.path), "mergeQueries(%v, %v)", tt.args.base, tt.args.path)
+ })
+ }
+}
diff --git a/internal/command/command.go b/internal/command/command.go
index 29ef5adc8..104589139 100644
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -98,11 +98,18 @@ type Commands struct {
caches *Caches
// Store instance IDs where all milestones are reached (except InstanceDeleted).
// These instance's milestones never need to be invalidated,
- // so the query and cache overhead can completely eliminated.
+ // so the query and cache overhead can be completely eliminated.
milestonesCompleted sync.Map
- defaultEmailCodeURLTemplate func(ctx context.Context) string
- defaultPasswordSetURLTemplate func(ctx context.Context) string
+ loginPaths LoginPaths
+}
+
+//go:generate mockgen -package command -destination ./mock_login_paths.go . LoginPaths
+type LoginPaths interface {
+ DefaultEmailCodeURLTemplate(ctx context.Context) string
+ DefaultPasswordSetURLTemplate(ctx context.Context) string
+ DefaultPasskeySetURLTemplate(ctx context.Context) string
+ DefaultDomainClaimedURLTemplate(ctx context.Context) string
}
func StartCommands(
@@ -120,12 +127,9 @@ func StartCommands(
httpClient *http.Client,
permissionCheck domain.PermissionCheck,
sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error),
- defaultAccessTokenLifetime,
- defaultRefreshTokenLifetime,
- defaultRefreshTokenIdleLifetime time.Duration,
+ defaultAccessTokenLifetime, defaultRefreshTokenLifetime, defaultRefreshTokenIdleLifetime time.Duration,
defaultSecretGenerators *SecretGenerators,
- defaultEmailCodeURLTemplate func(ctx context.Context) string,
- defaultPasswordSetURLTemplate func(ctx context.Context) string,
+ loginPaths LoginPaths,
) (repo *Commands, err error) {
if externalDomain == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Df21s", "no external domain specified")
@@ -211,10 +215,9 @@ func StartCommands(
WithHyphen: defaults.Multifactors.RecoveryCodes.WithHyphen,
},
},
- GenerateDomain: domain.NewGeneratedInstanceDomain,
- caches: caches,
- defaultEmailCodeURLTemplate: defaultEmailCodeURLTemplate,
- defaultPasswordSetURLTemplate: defaultPasswordSetURLTemplate,
+ GenerateDomain: domain.NewGeneratedInstanceDomain,
+ caches: caches,
+ loginPaths: loginPaths,
}
if defaultSecretGenerators != nil && defaultSecretGenerators.ClientSecret != nil {
diff --git a/internal/command/command_test.go b/internal/command/command_test.go
index 306f2abcf..850c25d53 100644
--- a/internal/command/command_test.go
+++ b/internal/command/command_test.go
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "go.uber.org/mock/gomock"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -144,3 +145,39 @@ func TestCommands_asyncPush(t *testing.T) {
})
}
}
+
+func expectLoginPathsNoCall(t *testing.T) LoginPaths {
+ return NewMockLoginPaths(gomock.NewController(t))
+}
+
+func expectLoginPathsDefaultEmailCodeURLTemplate(tmpl string) func(t *testing.T) LoginPaths {
+ return func(t *testing.T) LoginPaths {
+ m := NewMockLoginPaths(gomock.NewController(t))
+ m.EXPECT().DefaultEmailCodeURLTemplate(gomock.Any()).Return(tmpl)
+ return m
+ }
+}
+
+func expectLoginPathsDefaultDomainClaimedURLTemplate(tmpl string) func(t *testing.T) LoginPaths {
+ return func(t *testing.T) LoginPaths {
+ m := NewMockLoginPaths(gomock.NewController(t))
+ m.EXPECT().DefaultDomainClaimedURLTemplate(gomock.Any()).Return(tmpl)
+ return m
+ }
+}
+
+func expectLoginPathsDefaultPasswordSetURLTemplate(tmpl string) func(t *testing.T) LoginPaths {
+ return func(t *testing.T) LoginPaths {
+ m := NewMockLoginPaths(gomock.NewController(t))
+ m.EXPECT().DefaultPasswordSetURLTemplate(gomock.Any()).Return(tmpl)
+ return m
+ }
+}
+
+func expectLoginPathsDefaultPasskeySetURLTemplate(tmpl string) func(t *testing.T) LoginPaths {
+ return func(t *testing.T) LoginPaths {
+ m := NewMockLoginPaths(gomock.NewController(t))
+ m.EXPECT().DefaultPasskeySetURLTemplate(gomock.Any()).Return(tmpl)
+ return m
+ }
+}
diff --git a/internal/command/instance.go b/internal/command/instance.go
index 350a9875c..c9bd43291 100644
--- a/internal/command/instance.go
+++ b/internal/command/instance.go
@@ -2,7 +2,6 @@ package command
import (
"context"
- "net/url"
"strings"
"time"
@@ -15,7 +14,6 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
- "github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
@@ -124,57 +122,12 @@ type InstanceSetup struct {
SMTPConfiguration *SMTPConfiguration
OIDCSettings *OIDCSettings
Quotas *SetQuotas
- Features *InstanceSetupFeatures
+ Features *InstanceFeatures
Limits *SetLimits
Restrictions *SetRestrictions
RolePermissionMappings []authz.RoleMapping
}
-type InstanceSetupFeatures struct {
- LoginDefaultOrg *bool
- UserSchema *bool
- ImprovedPerformance []feature.ImprovedPerformanceType
- DebugOIDCParentError *bool
- OIDCSingleV1SessionTermination *bool
- LoginV2 *InstanceSetupFeatureLoginV2
- PermissionCheckV2 *bool
- ManagementConsoleUseV2UserApi *bool
- EnableRelationalTables *bool
-}
-
-type InstanceSetupFeatureLoginV2 struct {
- Required bool `json:"required,omitempty"`
- BaseURI *string `json:"base_uri,omitempty"`
-}
-
-func (f *InstanceSetupFeatures) ToInstanceFeatures() (_ *InstanceFeatures, err error) {
- if f == nil {
- return nil, nil
- }
- var loginV2 *feature.LoginV2
- if f.LoginV2 != nil {
- loginV2 = &feature.LoginV2{Required: f.LoginV2.Required}
- if f.LoginV2.BaseURI != nil {
- loginV2.BaseURI, err = url.Parse(*f.LoginV2.BaseURI)
- if err != nil {
- return nil, err
- }
- }
- }
-
- return &InstanceFeatures{
- LoginDefaultOrg: f.LoginDefaultOrg,
- UserSchema: f.UserSchema,
- ImprovedPerformance: f.ImprovedPerformance,
- DebugOIDCParentError: f.DebugOIDCParentError,
- OIDCSingleV1SessionTermination: f.OIDCSingleV1SessionTermination,
- LoginV2: loginV2,
- PermissionCheckV2: f.PermissionCheckV2,
- ManagementConsoleUseV2UserApi: f.ManagementConsoleUseV2UserApi,
- EnableRelationalTables: f.EnableRelationalTables,
- }, nil
-}
-
type SMTPConfiguration struct {
Description string
SMTP smtp.SMTP
@@ -468,7 +421,7 @@ func setupQuotas(commands *Commands, validations *[]preparation.Validation, setQ
return nil
}
-func setupFeatures(validations *[]preparation.Validation, features *InstanceSetupFeatures, instanceID string) {
+func setupFeatures(validations *[]preparation.Validation, features *InstanceFeatures, instanceID string) {
if features != nil {
*validations = append(*validations, prepareSetFeatures(instanceID, features))
}
diff --git a/internal/command/instance_features.go b/internal/command/instance_features.go
index 3100c49b1..806832606 100644
--- a/internal/command/instance_features.go
+++ b/internal/command/instance_features.go
@@ -56,16 +56,11 @@ func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures)
return pushedEventsToObjectDetails(events), nil
}
-func prepareSetFeatures(instanceID string, f *InstanceSetupFeatures) preparation.Validation {
+func prepareSetFeatures(instanceID string, f *InstanceFeatures) preparation.Validation {
return func() (preparation.CreateCommands, error) {
- features, err := f.ToInstanceFeatures()
- if err != nil {
- return nil, err
- }
-
return func(ctx context.Context, _ preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
wm := NewInstanceFeaturesWriteModel(instanceID)
- return wm.setCommands(ctx, features), nil
+ return wm.setCommands(ctx, f), nil
}, nil
}
}
diff --git a/internal/command/instance_test.go b/internal/command/instance_test.go
index c5796f948..ca355d6fd 100644
--- a/internal/command/instance_test.go
+++ b/internal/command/instance_test.go
@@ -3,15 +3,12 @@ package command
import (
"context"
"encoding/json"
- "net/url"
"slices"
"strings"
"testing"
"time"
- "github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"golang.org/x/text/language"
@@ -21,7 +18,6 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
- "github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/instance"
@@ -1769,118 +1765,3 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
})
}
}
-
-func TestInstanceSetupFeatures_ToInstanceFeatures(t *testing.T) {
- t.Parallel()
-
- type fields struct {
- LoginDefaultOrg *bool
- UserSchema *bool
- ImprovedPerformance []feature.ImprovedPerformanceType
- DebugOIDCParentError *bool
- OIDCSingleV1SessionTermination *bool
- LoginV2 *InstanceSetupFeatureLoginV2
- PermissionCheckV2 *bool
- ManagementConsoleUseV2UserApi *bool
- EnableRelationalTables *bool
- }
-
- correctlyParsedURI, err := url.Parse("https://example.com")
- require.NoError(t, err)
-
- tt := []struct {
- name string
- fields fields
- want *InstanceFeatures
- wantErr bool
- }{
- {
- name: "nil features returns nil",
- fields: fields{},
- want: &InstanceFeatures{},
- },
- {
- name: "all fields no login v2",
- fields: fields{
- LoginDefaultOrg: gu.Ptr(true),
- UserSchema: gu.Ptr(false),
- ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
- DebugOIDCParentError: gu.Ptr(true),
- OIDCSingleV1SessionTermination: gu.Ptr(false),
- PermissionCheckV2: gu.Ptr(true),
- ManagementConsoleUseV2UserApi: gu.Ptr(false),
- EnableRelationalTables: gu.Ptr(true),
- },
- want: &InstanceFeatures{
- LoginDefaultOrg: gu.Ptr(true),
- UserSchema: gu.Ptr(false),
- ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
- DebugOIDCParentError: gu.Ptr(true),
- OIDCSingleV1SessionTermination: gu.Ptr(false),
- LoginV2: nil,
- PermissionCheckV2: gu.Ptr(true),
- ManagementConsoleUseV2UserApi: gu.Ptr(false),
- EnableRelationalTables: gu.Ptr(true),
- },
- },
- {
- name: "with login v2 no base uri",
- fields: fields{
- LoginV2: &InstanceSetupFeatureLoginV2{
- Required: true,
- },
- },
- want: &InstanceFeatures{
- LoginV2: &feature.LoginV2{
- Required: true,
- },
- },
- },
- {
- name: "with login v2 valid base uri",
- fields: fields{
- LoginV2: &InstanceSetupFeatureLoginV2{
- Required: true,
- BaseURI: gu.Ptr("https://example.com"),
- },
- },
- want: &InstanceFeatures{
- LoginV2: &feature.LoginV2{
- Required: true,
- BaseURI: correctlyParsedURI,
- },
- },
- },
- {
- name: "with login v2 invalid base uri",
- fields: fields{
- LoginV2: &InstanceSetupFeatureLoginV2{
- Required: true,
- BaseURI: gu.Ptr("://invalid"),
- },
- },
- wantErr: true,
- },
- }
- for _, tc := range tt {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- f := &InstanceSetupFeatures{
- LoginDefaultOrg: tc.fields.LoginDefaultOrg,
- UserSchema: tc.fields.UserSchema,
- ImprovedPerformance: tc.fields.ImprovedPerformance,
- DebugOIDCParentError: tc.fields.DebugOIDCParentError,
- OIDCSingleV1SessionTermination: tc.fields.OIDCSingleV1SessionTermination,
- LoginV2: tc.fields.LoginV2,
- PermissionCheckV2: tc.fields.PermissionCheckV2,
- ManagementConsoleUseV2UserApi: tc.fields.ManagementConsoleUseV2UserApi,
- EnableRelationalTables: tc.fields.EnableRelationalTables,
- }
- got, err := f.ToInstanceFeatures()
-
- require.Equal(t, tc.wantErr, err != nil)
- assert.Equal(t, tc.want, got)
- })
- }
-}
diff --git a/internal/command/mock_login_paths.go b/internal/command/mock_login_paths.go
new file mode 100644
index 000000000..718eed270
--- /dev/null
+++ b/internal/command/mock_login_paths.go
@@ -0,0 +1,96 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/zitadel/zitadel/internal/command (interfaces: LoginPaths)
+//
+// Generated by this command:
+//
+// mockgen -package command -destination ./mock_login_paths.go . LoginPaths
+//
+
+// Package command is a generated GoMock package.
+package command
+
+import (
+ context "context"
+ reflect "reflect"
+
+ gomock "go.uber.org/mock/gomock"
+)
+
+// MockLoginPaths is a mock of LoginPaths interface.
+type MockLoginPaths struct {
+ ctrl *gomock.Controller
+ recorder *MockLoginPathsMockRecorder
+}
+
+// MockLoginPathsMockRecorder is the mock recorder for MockLoginPaths.
+type MockLoginPathsMockRecorder struct {
+ mock *MockLoginPaths
+}
+
+// NewMockLoginPaths creates a new mock instance.
+func NewMockLoginPaths(ctrl *gomock.Controller) *MockLoginPaths {
+ mock := &MockLoginPaths{ctrl: ctrl}
+ mock.recorder = &MockLoginPathsMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockLoginPaths) EXPECT() *MockLoginPathsMockRecorder {
+ return m.recorder
+}
+
+// DefaultDomainClaimedURLTemplate mocks base method.
+func (m *MockLoginPaths) DefaultDomainClaimedURLTemplate(arg0 context.Context) string {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DefaultDomainClaimedURLTemplate", arg0)
+ ret0, _ := ret[0].(string)
+ return ret0
+}
+
+// DefaultDomainClaimedURLTemplate indicates an expected call of DefaultDomainClaimedURLTemplate.
+func (mr *MockLoginPathsMockRecorder) DefaultDomainClaimedURLTemplate(arg0 any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultDomainClaimedURLTemplate", reflect.TypeOf((*MockLoginPaths)(nil).DefaultDomainClaimedURLTemplate), arg0)
+}
+
+// DefaultEmailCodeURLTemplate mocks base method.
+func (m *MockLoginPaths) DefaultEmailCodeURLTemplate(arg0 context.Context) string {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DefaultEmailCodeURLTemplate", arg0)
+ ret0, _ := ret[0].(string)
+ return ret0
+}
+
+// DefaultEmailCodeURLTemplate indicates an expected call of DefaultEmailCodeURLTemplate.
+func (mr *MockLoginPathsMockRecorder) DefaultEmailCodeURLTemplate(arg0 any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultEmailCodeURLTemplate", reflect.TypeOf((*MockLoginPaths)(nil).DefaultEmailCodeURLTemplate), arg0)
+}
+
+// DefaultPasskeySetURLTemplate mocks base method.
+func (m *MockLoginPaths) DefaultPasskeySetURLTemplate(arg0 context.Context) string {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DefaultPasskeySetURLTemplate", arg0)
+ ret0, _ := ret[0].(string)
+ return ret0
+}
+
+// DefaultPasskeySetURLTemplate indicates an expected call of DefaultPasskeySetURLTemplate.
+func (mr *MockLoginPathsMockRecorder) DefaultPasskeySetURLTemplate(arg0 any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultPasskeySetURLTemplate", reflect.TypeOf((*MockLoginPaths)(nil).DefaultPasskeySetURLTemplate), arg0)
+}
+
+// DefaultPasswordSetURLTemplate mocks base method.
+func (m *MockLoginPaths) DefaultPasswordSetURLTemplate(arg0 context.Context) string {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DefaultPasswordSetURLTemplate", arg0)
+ ret0, _ := ret[0].(string)
+ return ret0
+}
+
+// DefaultPasswordSetURLTemplate indicates an expected call of DefaultPasswordSetURLTemplate.
+func (mr *MockLoginPathsMockRecorder) DefaultPasswordSetURLTemplate(arg0 any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultPasswordSetURLTemplate", reflect.TypeOf((*MockLoginPaths)(nil).DefaultPasswordSetURLTemplate), arg0)
+}
diff --git a/internal/command/org_domain_test.go b/internal/command/org_domain_test.go
index 1c5ab30b1..a2cd5dc72 100644
--- a/internal/command/org_domain_test.go
+++ b/internal/command/org_domain_test.go
@@ -29,6 +29,7 @@ func TestAddDomain(t *testing.T) {
idGenerator id.Generator
filter preparation.FilterToQueryReducer
eventstore func(*testing.T) *eventstore.Eventstore
+ loginPaths func(*testing.T) LoginPaths
permissionCheck OrganizationPermissionCheck
}
@@ -45,6 +46,7 @@ func TestAddDomain(t *testing.T) {
a: agg,
domain: "",
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
want: Want{
ValidationErr: zerrors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument"),
@@ -69,6 +71,7 @@ func TestAddDomain(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
want: Want{
Commands: []eventstore.Command{
@@ -118,12 +121,13 @@ func TestAddDomain(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsDefaultDomainClaimedURLTemplate(""),
},
want: Want{
Commands: []eventstore.Command{
org.NewDomainAddedEvent(context.Background(), &agg.Aggregate, "domain"),
org.NewDomainVerifiedEvent(context.Background(), &agg.Aggregate, "domain"),
- user.NewDomainClaimedEvent(http.WithRequestedHost(context.Background(), "domain"), &user.NewAggregate("userID1", "org2").Aggregate, "newID@temporary.domain", "username", false),
+ user.NewDomainClaimedEvent(http.WithRequestedHost(context.Background(), "domain"), &user.NewAggregate("userID1", "org2").Aggregate, "newID@temporary.domain", "username", false, ""),
},
},
},
@@ -148,6 +152,7 @@ func TestAddDomain(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
want: Want{
CreateErr: zerrors.ThrowAlreadyExists(nil, "", ""),
@@ -161,6 +166,7 @@ func TestAddDomain(t *testing.T) {
claimedUserIDs: []string{"userID1"},
filter: nil,
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
permissionCheck: newMockOrganizationPermissionCheckNotAllowed(),
},
want: Want{
@@ -186,6 +192,7 @@ func TestAddDomain(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
permissionCheck: newMockOrganizationPermissionCheckAllowed(),
},
want: Want{
@@ -200,7 +207,7 @@ func TestAddDomain(t *testing.T) {
AssertValidation(
t,
http.WithRequestedHost(context.Background(), "domain"),
- (&Commands{idGenerator: tt.args.idGenerator, eventstore: tt.args.eventstore(t)}).prepareAddOrgDomain(tt.args.a, tt.args.domain, tt.args.claimedUserIDs, tt.args.permissionCheck),
+ (&Commands{idGenerator: tt.args.idGenerator, eventstore: tt.args.eventstore(t), loginPaths: tt.args.loginPaths(t)}).prepareAddOrgDomain(tt.args.a, tt.args.domain, tt.args.claimedUserIDs, tt.args.permissionCheck),
tt.args.filter,
tt.want,
)
@@ -879,6 +886,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
secretGenerator crypto.Generator
alg crypto.EncryptionAlgorithm
domainValidationFunc func(domain, token, verifier string, checkType http.CheckType) error
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
ctx context.Context
@@ -900,6 +908,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
name: "invalid domain, error",
fields: fields{
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -917,6 +926,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
name: "missing aggregateid, error",
fields: fields{
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -941,6 +951,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -981,6 +992,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1015,6 +1027,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1070,6 +1083,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
domainValidationFunc: invalidDomainVerification,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1125,6 +1139,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
domainValidationFunc: validDomainVerification,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1183,6 +1198,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
domainValidationFunc: validDomainVerification,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1264,12 +1280,14 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
"tempid@temporary.zitadel.ch",
"username@domain.ch",
false,
+ "",
),
),
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
domainValidationFunc: validDomainVerification,
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "tempid"),
+ loginPaths: expectLoginPathsDefaultDomainClaimedURLTemplate(""),
},
args: args{
ctx: context.Background(),
@@ -1351,12 +1369,14 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
"tempid@temporary.zitadel.ch",
"username@domain.ch",
true,
+ "",
),
),
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
domainValidationFunc: validDomainVerification,
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "tempid"),
+ loginPaths: expectLoginPathsDefaultDomainClaimedURLTemplate(""),
},
args: args{
ctx: context.Background(),
@@ -1415,6 +1435,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
domainValidationFunc: validDomainVerification,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1439,6 +1460,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
eventstore: expectEventstore(),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
domainValidationFunc: validDomainVerification,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1464,6 +1486,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
domainVerificationAlg: tt.fields.alg,
domainVerificationValidator: tt.fields.domainValidationFunc,
idGenerator: tt.fields.idGenerator,
+ loginPaths: tt.fields.loginPaths(t),
}
got, err := r.ValidateOrgDomain(http.WithRequestedHost(tt.args.ctx, "zitadel.ch"), tt.args.domain, tt.args.claimedUserIDs, tt.args.permissionCheck)
if tt.res.err == nil {
diff --git a/internal/command/user.go b/internal/command/user.go
index ada16c8ff..93ea98414 100644
--- a/internal/command/user.go
+++ b/internal/command/user.go
@@ -303,6 +303,7 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events
fmt.Sprintf("%s@temporary.%s", id, http_util.DomainContext(ctx).RequestedDomain()),
existingUser.UserName,
domainPolicy.UserLoginMustBeDomain || organizationScopedUsername,
+ c.loginPaths.DefaultDomainClaimedURLTemplate(ctx),
),
}, changedUserGrant, nil
}
@@ -338,6 +339,7 @@ func (c *Commands) prepareUserDomainClaimed(ctx context.Context, filter preparat
fmt.Sprintf("%s@temporary.%s", id, http_util.DomainContext(ctx).RequestedDomain()),
userWriteModel.UserName,
domainPolicy.UserLoginMustBeDomain || organizationScopedUsername,
+ c.loginPaths.DefaultDomainClaimedURLTemplate(ctx),
), nil
}
diff --git a/internal/command/user_human.go b/internal/command/user_human.go
index eb693ea57..061cff7e9 100644
--- a/internal/command/user_human.go
+++ b/internal/command/user_human.go
@@ -306,10 +306,10 @@ func (c *Commands) addHumanCommandEmail(ctx context.Context, filter preparation.
}
if human.Email.URLTemplate == "" {
- human.Email.URLTemplate = c.defaultEmailCodeURLTemplate(ctx)
+ human.Email.URLTemplate = c.loginPaths.DefaultEmailCodeURLTemplate(ctx)
}
- return append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &a.Aggregate, emailCode.Crypted, emailCode.Expiry, human.Email.URLTemplate, human.Email.ReturnCode, human.AuthRequestID)), nil
+ return append(cmds, user.NewHumanEmailCodeAddedEvent(ctx, &a.Aggregate, emailCode.Crypted, emailCode.Expiry, human.Email.URLTemplate, human.Email.ReturnCode, human.AuthRequestID)), nil
}
return cmds, nil
}
@@ -599,7 +599,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
if err != nil {
return nil, nil, nil, err
}
- events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, ""))
+ events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, c.loginPaths.DefaultEmailCodeURLTemplate(ctx), false, ""))
}
}
diff --git a/internal/command/user_human_email.go b/internal/command/user_human_email.go
index c954b3246..92fe8b95c 100644
--- a/internal/command/user_human_email.go
+++ b/internal/command/user_human_email.go
@@ -50,7 +50,7 @@ func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email, em
if err != nil {
return nil, err
}
- events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, ""))
+ events = append(events, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, c.loginPaths.DefaultEmailCodeURLTemplate(ctx), false, ""))
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
@@ -134,7 +134,7 @@ func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID,
if authRequestID == "" {
authRequestID = existingEmail.AuthRequestID
}
- pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, authRequestID))
+ pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailCodeAddedEvent(ctx, userAgg, emailCode.Code, emailCode.Expiry, c.loginPaths.DefaultEmailCodeURLTemplate(ctx), false, authRequestID))
if err != nil {
return nil, err
}
diff --git a/internal/command/user_human_email_test.go b/internal/command/user_human_email_test.go
index e18413594..545ce3e0a 100644
--- a/internal/command/user_human_email_test.go
+++ b/internal/command/user_human_email_test.go
@@ -20,6 +20,7 @@ import (
func TestCommandSide_ChangeHumanEmail(t *testing.T) {
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
ctx context.Context
@@ -41,6 +42,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
name: "invalid email, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -61,6 +63,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -104,6 +107,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -140,6 +144,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -185,6 +190,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -234,6 +240,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -283,6 +290,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -341,9 +349,75 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
},
time.Hour*1,
"",
+ false,
+ "",
),
),
),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate(""),
+ },
+ args: args{
+ ctx: context.Background(),
+ email: &domain.Email{
+ ObjectRoot: models.ObjectRoot{
+ AggregateID: "user1",
+ },
+ EmailAddress: "email-changed@test.ch",
+ },
+ resourceOwner: "org1",
+ secretGenerator: GetMockSecretGenerator(t),
+ },
+ res: res{
+ want: &domain.Email{
+ ObjectRoot: models.ObjectRoot{
+ AggregateID: "user1",
+ ResourceOwner: "org1",
+ },
+ EmailAddress: "email-changed@test.ch",
+ },
+ },
+ },
+ {
+ name: "email changed with code, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "username",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ ),
+ ),
+ expectPush(
+ user.NewHumanEmailChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "email-changed@test.ch",
+ ),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("a"),
+ },
+ time.Hour*1,
+ "http://example.com/{{.user}}/email/{{.code}}",
+ false,
+ "",
+ ),
+ ),
+ ),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
ctx: context.Background(),
@@ -371,6 +445,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
+ loginPaths: tt.fields.loginPaths(t),
}
got, err := r.ChangeHumanEmail(tt.args.ctx, tt.args.email, tt.args.secretGenerator)
if tt.res.err == nil {
@@ -389,6 +464,7 @@ func TestCommandSide_ChangeHumanEmail(t *testing.T) {
func TestCommandSide_VerifyHumanEmail(t *testing.T) {
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
+ loginPaths func(*testing.T) LoginPaths
userPasswordHasher *crypto.Hasher
}
type args struct {
@@ -414,6 +490,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
name: "userid missing, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -428,6 +505,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
name: "code missing, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -444,6 +522,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -476,6 +555,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -517,6 +597,8 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
},
time.Hour*1,
"",
+ false,
+ "",
),
),
),
@@ -526,6 +608,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -568,6 +651,8 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
},
time.Hour*1,
"",
+ false,
+ "",
),
),
),
@@ -577,6 +662,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -621,6 +707,8 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
},
time.Hour*1,
"",
+ false,
+ "",
),
),
),
@@ -649,6 +737,84 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
),
),
userPasswordHasher: mockPasswordHasher("x"),
+ loginPaths: expectLoginPathsNoCall,
+ },
+ args: args{
+ ctx: context.Background(),
+ userID: "user1",
+ code: "a",
+ resourceOwner: "org1",
+ optionalPassword: "password",
+ optionalUserAgentID: "userAgentID",
+ secretGenerator: GetMockSecretGenerator(t),
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "valid code (with password and user agent), url template, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "username",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ ),
+ eventFromEventPusherWithCreationDateNow(
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("a"),
+ },
+ time.Hour*1,
+ "http://example.com/{{.user}}/email/{{.code}}",
+ false,
+ "",
+ ),
+ ),
+ ),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ 1,
+ false,
+ false,
+ false,
+ false,
+ ),
+ ),
+ ),
+ expectPush(
+ user.NewHumanEmailVerifiedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ ),
+ user.NewHumanPasswordChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "$plain$x$password",
+ false,
+ "userAgentID",
+ ),
+ ),
+ ),
+ userPasswordHasher: mockPasswordHasher("x"),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -671,6 +837,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
+ loginPaths: tt.fields.loginPaths(t),
}
got, err := r.VerifyHumanEmail(
tt.args.ctx,
@@ -697,6 +864,7 @@ func TestCommandSide_VerifyHumanEmail(t *testing.T) {
func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
ctx context.Context
@@ -719,6 +887,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
name: "userid missing, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -734,6 +903,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -772,6 +942,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -808,6 +979,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -860,9 +1032,12 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
},
time.Hour*1,
"",
+ false,
+ "",
),
),
),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate(""),
},
args: args{
ctx: context.Background(),
@@ -917,10 +1092,75 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
Crypted: []byte("a"),
},
time.Hour*1,
+ "",
+ false,
"authRequestID",
),
),
),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate(""),
+ },
+ args: args{
+ ctx: context.Background(),
+ userID: "user1",
+ resourceOwner: "org1",
+ secretGenerator: GetMockSecretGenerator(t),
+ authRequestID: "authRequestID",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "new code with authRequestID, url template, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "username",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanEmailVerifiedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanEmailChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "email2@test.ch",
+ ),
+ ),
+ ),
+ expectPush(
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("a"),
+ },
+ time.Hour*1,
+ "http://example.com/{{.user}}/email/{{.code}}",
+ false,
+ "authRequestID",
+ ),
+ ),
+ ),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
ctx: context.Background(),
@@ -940,6 +1180,7 @@ func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
+ loginPaths: tt.fields.loginPaths(t),
}
got, err := r.CreateHumanEmailVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secretGenerator, tt.args.authRequestID)
if tt.res.err == nil {
diff --git a/internal/command/user_human_test.go b/internal/command/user_human_test.go
index 7b52ebc05..75b91225b 100644
--- a/internal/command/user_human_test.go
+++ b/internal/command/user_human_test.go
@@ -45,7 +45,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
newCode encrypedCodeFunc
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
defaultSecretGenerators *SecretGenerators
- defaultEmailCodeURLTemplate func(ctx context.Context) string
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
ctx context.Context
@@ -73,6 +73,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
name: "orgid missing, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -97,6 +98,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
name: "user invalid, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -123,6 +125,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -155,6 +158,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
expectFilter(),
expectFilter(),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -196,6 +200,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
expectFilter(),
expectFilter(),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -264,6 +269,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -333,6 +339,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -403,6 +410,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -473,6 +481,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -541,11 +550,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),
- defaultEmailCodeURLTemplate: func(_ context.Context) string { return "" },
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
+ userPasswordHasher: mockPasswordHasher("x"),
+ codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -600,7 +609,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "", AllowedLanguage),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -619,6 +628,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("emailCode", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -674,7 +684,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "", AllowedLanguage),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -689,11 +699,11 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
- 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}}" },
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
+ userPasswordHasher: mockPasswordHasher("x"),
+ codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ newCode: mockEncryptedCode("emailCode", time.Hour),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
ctx: context.Background(),
@@ -755,6 +765,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -819,6 +830,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -883,6 +895,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -947,6 +960,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1000,6 +1014,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1086,6 +1101,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1195,6 +1211,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phonecode", time.Hour),
defaultSecretGenerators: defaultGenerators,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1262,6 +1279,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1371,6 +1389,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1441,6 +1460,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1481,7 +1501,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
newEncryptedCode: tt.fields.newCode,
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
- defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
+ loginPaths: tt.fields.loginPaths(t),
}
err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail)
if tt.res.err == nil {
diff --git a/internal/command/user_v2_email.go b/internal/command/user_v2_email.go
index 59793e50b..1a05fe9d7 100644
--- a/internal/command/user_v2_email.go
+++ b/internal/command/user_v2_email.go
@@ -156,7 +156,7 @@ func (c *Commands) changeUserEmailWithGeneratorEvents(ctx context.Context, userI
}
if urlTmpl == "" {
- urlTmpl = c.defaultEmailCodeURLTemplate(ctx)
+ urlTmpl = c.loginPaths.DefaultEmailCodeURLTemplate(ctx)
}
if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
@@ -178,7 +178,7 @@ func (c *Commands) sendUserEmailCodeWithGeneratorEvents(ctx context.Context, use
}
if urlTmpl == "" {
- urlTmpl = c.defaultEmailCodeURLTemplate(ctx)
+ urlTmpl = c.loginPaths.DefaultEmailCodeURLTemplate(ctx)
}
if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
@@ -286,7 +286,7 @@ func generateCodeCommand(ctx context.Context, agg *eventstore.Aggregate, gen cry
return nil, "", err
}
- cmd := user.NewHumanEmailCodeAddedEventV2(ctx, agg, value, gen.Expiry(), urlTmpl, returnCode, "")
+ cmd := user.NewHumanEmailCodeAddedEvent(ctx, agg, value, gen.Expiry(), urlTmpl, returnCode, "")
if returnCode {
return cmd, plain, nil
}
diff --git a/internal/command/user_v2_email_test.go b/internal/command/user_v2_email_test.go
index 9bf690dc1..e4e77b072 100644
--- a/internal/command/user_v2_email_test.go
+++ b/internal/command/user_v2_email_test.go
@@ -439,7 +439,7 @@ func TestCommands_ResendUserEmailCode(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -556,7 +556,7 @@ func TestCommands_SendUserEmailCode(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -647,7 +647,7 @@ func TestCommands_ResendUserEmailCodeURLTemplate(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -778,7 +778,7 @@ func TestCommands_SendUserEmailCodeURLTemplate(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -858,7 +858,7 @@ func TestCommands_ResendUserEmailReturnCode(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -975,7 +975,7 @@ func TestCommands_SendUserEmailReturnCode(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1160,9 +1160,9 @@ func TestCommands_ChangeUserEmailVerified(t *testing.T) {
func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
- checkPermission domain.PermissionCheck
- defaultEmailCodeURLTemplate func(ctx context.Context) string
+ eventstore func(*testing.T) *eventstore.Eventstore
+ checkPermission domain.PermissionCheck
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
userID string
@@ -1180,7 +1180,8 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
{
name: "missing user",
fields: fields{
- eventstore: eventstoreExpect(t),
+ eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "",
@@ -1193,8 +1194,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
{
name: "missing permission",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1213,6 +1213,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
),
),
checkPermission: newMockPermissionCheckNotAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -1225,8 +1226,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
{
name: "missing email",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1245,6 +1245,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
),
),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -1257,8 +1258,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
{
name: "not changed",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1277,6 +1277,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
),
),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -1289,8 +1290,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
{
name: "email changed",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1312,7 +1312,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
&user.NewAggregate("user1", "org1").Aggregate,
"email-changed@test.ch",
),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1326,8 +1326,8 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
),
),
),
- checkPermission: newMockPermissionCheckAllowed(),
- defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
userID: "user1",
@@ -1347,8 +1347,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
{
name: "email changed, return code",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1370,7 +1369,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
&user.NewAggregate("user1", "org1").Aggregate,
"email-changed@test.ch",
),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1383,8 +1382,8 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
),
),
),
- checkPermission: newMockPermissionCheckAllowed(),
- defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code2}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code2}}"),
},
args: args{
userID: "user1",
@@ -1405,8 +1404,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
{
name: "email changed, URL template",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1428,7 +1426,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
&user.NewAggregate("user1", "org1").Aggregate,
"email-changed@test.ch",
),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1442,6 +1440,7 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
),
),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -1462,9 +1461,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,
- defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
+ eventstore: tt.fields.eventstore(t),
+ checkPermission: tt.fields.checkPermission,
+ loginPaths: tt.fields.loginPaths(t),
}
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)
@@ -1475,9 +1474,9 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
- checkPermission domain.PermissionCheck
- defaultEmailCodeURLTemplate func(ctx context.Context) string
+ eventstore *eventstore.Eventstore
+ checkPermission domain.PermissionCheck
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
userID string
@@ -1496,6 +1495,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
name: "missing user",
fields: fields{
eventstore: eventstoreExpect(t),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "",
@@ -1525,7 +1525,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1540,6 +1540,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
checkPermission: newMockPermissionCheckNotAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -1570,7 +1571,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
expectPush(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1583,8 +1584,8 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
),
- checkPermission: newMockPermissionCheckAllowed(),
- defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
userID: "user1",
@@ -1622,7 +1623,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1636,7 +1637,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
expectPush(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1649,8 +1650,8 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
),
- checkPermission: newMockPermissionCheckAllowed(),
- defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code2}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code2}}"),
},
args: args{
userID: "user1",
@@ -1690,6 +1691,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -1721,7 +1723,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
expectPush(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1734,8 +1736,8 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
),
- checkPermission: newMockPermissionCheckAllowed(),
- defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
userID: "user1",
@@ -1775,7 +1777,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
expectPush(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -1789,6 +1791,7 @@ func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
),
),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -1809,9 +1812,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,
- defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
+ eventstore: tt.fields.eventstore,
+ checkPermission: tt.fields.checkPermission,
+ loginPaths: tt.fields.loginPaths(t),
}
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)
@@ -1924,7 +1927,7 @@ func TestCommands_VerifyUserEmail(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -2039,7 +2042,7 @@ func TestCommands_verifyUserEmailWithGenerator(t *testing.T) {
),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -2086,7 +2089,7 @@ func TestCommands_verifyUserEmailWithGenerator(t *testing.T) {
),
),
eventFromEventPusherWithCreationDateNow(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
diff --git a/internal/command/user_v2_human.go b/internal/command/user_v2_human.go
index 578f17e09..60277a017 100644
--- a/internal/command/user_v2_human.go
+++ b/internal/command/user_v2_human.go
@@ -413,10 +413,10 @@ func (c *Commands) changeUserEmail(ctx context.Context, cmds []eventstore.Comman
return cmds, code, err
}
if email.URLTemplate == "" {
- email.URLTemplate = c.defaultEmailCodeURLTemplate(ctx)
+ email.URLTemplate = c.loginPaths.DefaultEmailCodeURLTemplate(ctx)
}
- cmds = append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, email.URLTemplate, email.ReturnCode, ""))
+ cmds = append(cmds, user.NewHumanEmailCodeAddedEvent(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, email.URLTemplate, email.ReturnCode, ""))
if email.ReturnCode {
code = &cryptoCode.Plain
}
diff --git a/internal/command/user_v2_human_test.go b/internal/command/user_v2_human_test.go
index 44884c26d..86a22cff4 100644
--- a/internal/command/user_v2_human_test.go
+++ b/internal/command/user_v2_human_test.go
@@ -43,7 +43,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
checkPermission domain.PermissionCheck
defaultSecretGenerators *SecretGenerators
- defaultEmailCodeURLTemplate func(ctx context.Context) string
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
ctx context.Context
@@ -79,6 +79,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
fields: fields{
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -104,6 +105,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
fields: fields{
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -131,6 +133,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -163,6 +166,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -205,6 +209,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -274,6 +279,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -310,6 +316,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckNotAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -376,6 +383,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -449,6 +457,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -492,7 +501,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilterOrganizationSettings("org1", false, false),
expectPush(
newAddHumanEvent("", false, true, "", language.English),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -507,10 +516,10 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
- 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}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
+ newCode: mockEncryptedCode("emailverify", time.Hour),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
ctx: context.Background(),
@@ -565,7 +574,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "", language.English),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -584,6 +593,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
newCode: mockEncryptedCode("emailCode", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -640,7 +650,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "", language.English),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -655,11 +665,11 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
- 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}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
+ userPasswordHasher: mockPasswordHasher("x"),
+ newCode: mockEncryptedCode("emailCode", time.Hour),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
ctx: context.Background(),
@@ -725,6 +735,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -790,6 +801,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -855,6 +867,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -909,6 +922,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -996,6 +1010,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1106,6 +1121,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
userPasswordHasher: mockPasswordHasher("x"),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phonecode", time.Hour),
defaultSecretGenerators: defaultGenerators,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1212,6 +1228,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
userPasswordHasher: mockPasswordHasher("x"),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phonecode", time.Hour),
defaultSecretGenerators: defaultGenerators,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1280,6 +1297,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1390,6 +1408,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
userPasswordHasher: mockPasswordHasher("x"),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1461,6 +1480,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1522,7 +1542,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
expectPush(
newRegisterHumanEvent("email@test.ch", "", false, true, "", language.English),
- user.NewHumanEmailCodeAddedEventV2(
+ user.NewHumanEmailCodeAddedEvent(
context.Background(),
&userAgg.Aggregate,
&crypto.CryptoValue{
@@ -1545,10 +1565,10 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
- 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}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
+ newCode: mockEncryptedCode("mailVerify", time.Hour),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
ctx: context.Background(),
@@ -1629,6 +1649,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("mailVerify", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1717,6 +1738,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1801,6 +1823,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1858,6 +1881,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -1938,6 +1962,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2028,6 +2053,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2122,6 +2148,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockEncryptedCode("userinit", time.Hour),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2168,7 +2195,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
CryptoMFA: cryptoAlg,
},
},
- defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
+ loginPaths: tt.fields.loginPaths(t),
}
err := r.AddUserHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail, tt.args.codeAlg)
if tt.res.err == nil {
@@ -2206,7 +2233,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
checkPermission domain.PermissionCheck
defaultSecretGenerators *SecretGenerators
- defaultEmailCodeURLTemplate func(ctx context.Context) string
+ loginPaths func(*testing.T) LoginPaths
tarpit Tarpit
}
type args struct {
@@ -2245,6 +2272,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2271,6 +2299,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckNotAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2293,6 +2322,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2339,6 +2369,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2387,6 +2418,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2415,6 +2447,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2443,6 +2476,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckNotAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2492,6 +2526,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2527,6 +2562,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2564,7 +2600,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
&userAgg.Aggregate,
"changed@example.com",
),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -2579,10 +2615,10 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
),
),
- checkPermission: newMockPermissionCheckAllowed(),
- newCode: mockEncryptedCode("emailCode", time.Hour),
- defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
- tarpit: expectTarpit(0),
+ checkPermission: newMockPermissionCheckAllowed(),
+ newCode: mockEncryptedCode("emailCode", time.Hour),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
+ tarpit: expectTarpit(0),
},
args: args{
ctx: context.Background(),
@@ -2614,6 +2650,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2644,6 +2681,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckNotAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2682,6 +2720,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2718,6 +2757,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2750,7 +2790,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
&userAgg.Aggregate,
"changed@test.com",
),
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -2765,10 +2805,10 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
),
),
- checkPermission: newMockPermissionCheckAllowed(),
- newCode: mockEncryptedCode("emailCode", time.Hour),
- defaultEmailCodeURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/email/{{.code}}" },
- tarpit: expectTarpit(0),
+ checkPermission: newMockPermissionCheckAllowed(),
+ newCode: mockEncryptedCode("emailCode", time.Hour),
+ loginPaths: expectLoginPathsDefaultEmailCodeURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
+ tarpit: expectTarpit(0),
},
args: args{
ctx: context.Background(),
@@ -2852,6 +2892,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2928,6 +2969,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2959,6 +3001,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckNotAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -2997,6 +3040,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3033,6 +3077,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3113,6 +3158,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("phoneCode", time.Hour),
defaultSecretGenerators: defaultGenerators,
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3141,6 +3187,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3175,6 +3222,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3224,6 +3272,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3249,6 +3298,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3284,6 +3334,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
userPasswordHasher: mockPasswordHasher("x"),
checkPermission: newMockPermissionCheckNotAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3339,6 +3390,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
userPasswordHasher: mockPasswordHasher("x"),
checkPermission: newMockPermissionCheckAllowed(),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3397,6 +3449,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3452,6 +3505,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(1),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3524,6 +3578,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3579,6 +3634,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3644,6 +3700,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3703,6 +3760,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3778,6 +3836,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
tarpit: expectTarpit(0),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -3810,7 +3869,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
checkPermission: tt.fields.checkPermission,
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
userEncryption: tt.args.codeAlg,
- defaultEmailCodeURLTemplate: tt.fields.defaultEmailCodeURLTemplate,
+ loginPaths: tt.fields.loginPaths(t),
tarpit: tt.fields.tarpit.tarpit,
}
err := r.ChangeUserHuman(tt.args.ctx, tt.args.human, tt.args.codeAlg)
diff --git a/internal/command/user_v2_model_test.go b/internal/command/user_v2_model_test.go
index ecaa2db40..602553cef 100644
--- a/internal/command/user_v2_model_test.go
+++ b/internal/command/user_v2_model_test.go
@@ -662,7 +662,7 @@ func TestCommandSide_userHumanWriteModel_email(t *testing.T) {
newRegisterHumanEvent("username", "$plain$x$password", true, true, "", language.English),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&userAgg.Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -724,7 +724,7 @@ func TestCommandSide_userHumanWriteModel_email(t *testing.T) {
newRegisterHumanEvent("username", "$plain$x$password", true, true, "", language.English),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&userAgg.Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -783,7 +783,7 @@ func TestCommandSide_userHumanWriteModel_email(t *testing.T) {
newRegisterHumanEvent("username", "$plain$x$password", true, true, "", language.English),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&userAgg.Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
@@ -851,7 +851,7 @@ func TestCommandSide_userHumanWriteModel_email(t *testing.T) {
newRegisterHumanEvent("username", "$plain$x$password", true, true, "", language.English),
),
eventFromEventPusher(
- user.NewHumanEmailCodeAddedEventV2(context.Background(),
+ user.NewHumanEmailCodeAddedEvent(context.Background(),
&userAgg.Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
diff --git a/internal/command/user_v2_passkey.go b/internal/command/user_v2_passkey.go
index a38604974..0f88b8837 100644
--- a/internal/command/user_v2_passkey.go
+++ b/internal/command/user_v2_passkey.go
@@ -143,6 +143,10 @@ func (c *Commands) addUserPasskeyCode(ctx context.Context, userID, resourceOwner
}
agg := UserAggregateFromWriteModel(&wm.WriteModel)
+ if urlTmpl == "" {
+ urlTmpl = c.loginPaths.DefaultPasskeySetURLTemplate(ctx)
+ }
+
cmd := user.NewHumanPasswordlessInitCodeRequestedEvent(ctx, agg, codeID, code.Crypted, code.Expiry, urlTmpl, returnCode)
err = c.pushAppendAndReduce(ctx, wm, cmd)
if err != nil {
diff --git a/internal/command/user_v2_passkey_test.go b/internal/command/user_v2_passkey_test.go
index 0d1009862..1609f1ab4 100644
--- a/internal/command/user_v2_passkey_test.go
+++ b/internal/command/user_v2_passkey_test.go
@@ -142,9 +142,9 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
ExternalSecure: true,
}
alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
- es := eventstoreExpect(t,
+ es := expectEventstore(
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
- )
+ )(t)
code, err := newEncryptedCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) //nolint:staticcheck
require.NoError(t, err)
userAgg := &user.NewAggregate("user1", "org1").Aggregate
@@ -238,9 +238,9 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
func TestCommands_verifyUserPasskeyCode(t *testing.T) {
ctx := http_util.WithRequestedHost(context.Background(), "example.com")
alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
- es := eventstoreExpect(t,
+ es := expectEventstore(
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
- )
+ )(t)
code, err := newEncryptedCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) //nolint:staticcheck
require.NoError(t, err)
userAgg := &user.NewAggregate("user1", "org1").Aggregate
@@ -436,14 +436,14 @@ func TestCommands_pushUserPasskey(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
- eventstore: eventstoreExpect(t, prep...),
+ eventstore: expectEventstore(prep...)(t),
webauthnConfig: webauthnConfig,
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
}
wm, userAgg, webAuthN, err := c.createUserPasskey(ctx, "user1", "org1", "rpID", domain.AuthenticatorAttachmentCrossPlattform)
require.NoError(t, err)
- c.eventstore = eventstoreExpect(t, tt.expectPush(webAuthN.Challenge))
+ c.eventstore = expectEventstore(tt.expectPush(webAuthN.Challenge))(t)
got, err := c.pushUserPasskey(ctx, wm, userAgg, webAuthN, tt.args.events...)
require.ErrorIs(t, err, tt.wantErr)
@@ -463,6 +463,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) {
newCode encrypedCodeFunc
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
userID string
@@ -481,6 +482,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) {
newCode: mockEncryptedCode("passkey1", time.Hour),
eventstore: expectEventstore(),
idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -519,6 +521,49 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsDefaultPasskeySetURLTemplate(""),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
+ },
+ args: args{
+ userID: "user1",
+ resourceOwner: "org1",
+ },
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ {
+ name: "success, url template",
+ fields: fields{
+ newCode: mockEncryptedCode("passkey1", time.Minute),
+ eventstore: expectEventstore(
+ expectFilter(eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ userAgg,
+ "username",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ )),
+ expectPush(
+ user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
+ userAgg,
+ "123", &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("passkey1"),
+ }, time.Minute, "http://example.com/{{.user}}/email/{{.code}}", false,
+ ),
+ ),
+ ),
+ loginPaths: expectLoginPathsDefaultPasskeySetURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
},
args: args{
@@ -536,6 +581,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) {
newEncryptedCode: tt.fields.newCode,
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
+ loginPaths: tt.fields.loginPaths(t),
}
got, err := c.AddUserPasskeyCode(context.Background(), tt.args.userID, tt.args.resourceOwner, alg)
require.ErrorIs(t, err, tt.wantErr)
@@ -550,7 +596,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
type fields struct {
newCode encrypedCodeFunc
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
}
type args struct {
@@ -569,7 +615,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
name: "template error",
fields: fields{
newCode: newEncryptedCode,
- eventstore: eventstoreExpect(t),
+ eventstore: expectEventstore(),
},
args: args{
userID: "user1",
@@ -582,7 +628,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
name: "id generator error",
fields: fields{
newCode: newEncryptedCode,
- eventstore: eventstoreExpect(t),
+ eventstore: expectEventstore(),
idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe),
},
args: args{
@@ -596,7 +642,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
name: "success",
fields: fields{
newCode: mockEncryptedCode("passkey1", time.Minute),
- eventstore: eventstoreExpect(t,
+ eventstore: expectEventstore(
expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
userAgg,
@@ -642,7 +688,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
newEncryptedCode: tt.fields.newCode,
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
}
got, err := c.AddUserPasskeyCodeURLTemplate(context.Background(), tt.args.userID, tt.args.resourceOwner, alg, tt.args.urlTmpl)
@@ -657,8 +703,9 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) {
userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
newCode encrypedCodeFunc
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
userID string
@@ -675,8 +722,9 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) {
name: "id generator error",
fields: fields{
newCode: newEncryptedCode,
- eventstore: eventstoreExpect(t),
+ eventstore: expectEventstore(),
idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -688,7 +736,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) {
name: "success",
fields: fields{
newCode: mockEncryptedCode("passkey1", time.Minute),
- eventstore: eventstoreExpect(t,
+ eventstore: expectEventstore(
expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
userAgg,
@@ -716,6 +764,53 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) {
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
+ loginPaths: expectLoginPathsDefaultPasskeySetURLTemplate(""),
+ },
+ args: args{
+ userID: "user1",
+ resourceOwner: "org1",
+ },
+ want: &domain.PasskeyCodeDetails{
+ ObjectDetails: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ CodeID: "123",
+ Code: "passkey1",
+ },
+ },
+ {
+ name: "success, url template",
+ fields: fields{
+ newCode: mockEncryptedCode("passkey1", time.Minute),
+ eventstore: expectEventstore(
+ expectFilter(eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ userAgg,
+ "username",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ )),
+ expectPush(
+ user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
+ userAgg,
+ "123", &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("passkey1"),
+ }, time.Minute, "http://example.com/{{.user}}/email/{{.code}}", true,
+ ),
+ ),
+ ),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
+ loginPaths: expectLoginPathsDefaultPasskeySetURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
userID: "user1",
@@ -734,8 +829,9 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
newEncryptedCode: tt.fields.newCode,
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
+ loginPaths: tt.fields.loginPaths(t),
}
got, err := c.AddUserPasskeyCodeReturn(context.Background(), tt.args.userID, tt.args.resourceOwner, alg)
if tt.wantErr != nil {
@@ -754,8 +850,9 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
newCode encrypedCodeFunc
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
userID string
@@ -772,8 +869,9 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
name: "id generator error",
fields: fields{
newCode: newEncryptedCode,
- eventstore: eventstoreExpect(t),
+ eventstore: expectEventstore(),
idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -785,8 +883,9 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
name: "crypto error",
fields: fields{
newCode: newEncryptedCode,
- eventstore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
+ eventstore: expectEventstore(expectFilterError(io.ErrClosedPipe)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -798,11 +897,12 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
name: "filter query error",
fields: fields{
newCode: newEncryptedCode,
- eventstore: eventstoreExpect(t,
+ eventstore: expectEventstore(
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
expectFilterError(io.ErrClosedPipe),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
userID: "user1",
@@ -814,7 +914,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
name: "push error",
fields: fields{
newCode: mockEncryptedCode("passkey1", time.Minute),
- eventstore: eventstoreExpect(t,
+ eventstore: expectEventstore(
expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
userAgg,
@@ -842,6 +942,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
+ loginPaths: expectLoginPathsDefaultPasskeySetURLTemplate(""),
},
args: args{
userID: "user1",
@@ -853,7 +954,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
name: "success",
fields: fields{
newCode: mockEncryptedCode("passkey1", time.Minute),
- eventstore: eventstoreExpect(t,
+ eventstore: expectEventstore(
expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
userAgg,
@@ -881,6 +982,52 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
+ loginPaths: expectLoginPathsDefaultPasskeySetURLTemplate(""),
+ },
+ args: args{
+ userID: "user1",
+ resourceOwner: "org1",
+ },
+ want: &domain.PasskeyCodeDetails{
+ ObjectDetails: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ CodeID: "123",
+ Code: "passkey1",
+ },
+ }, {
+ name: "success, url template",
+ fields: fields{
+ newCode: mockEncryptedCode("passkey1", time.Minute),
+ eventstore: expectEventstore(
+ expectFilter(eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ userAgg,
+ "username",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ )),
+ expectPush(
+ user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
+ userAgg,
+ "123", &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("passkey1"),
+ }, time.Minute, "http://example.com/{{.user}}/email/{{.code}}", false,
+ ),
+ ),
+ ),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
+ loginPaths: expectLoginPathsDefaultPasskeySetURLTemplate("http://example.com/{{.user}}/email/{{.code}}"),
},
args: args{
userID: "user1",
@@ -899,8 +1046,9 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
newEncryptedCode: tt.fields.newCode,
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
+ loginPaths: tt.fields.loginPaths(t),
}
got, err := c.addUserPasskeyCode(context.Background(), tt.args.userID, tt.args.resourceOwner, alg, "", false)
if tt.wantErr != nil {
diff --git a/internal/command/user_v2_password.go b/internal/command/user_v2_password.go
index 7961f740c..7b91871c0 100644
--- a/internal/command/user_v2_password.go
+++ b/internal/command/user_v2_password.go
@@ -63,7 +63,7 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu
return nil, nil, err
}
if urlTmpl == "" {
- urlTmpl = c.defaultPasswordSetURLTemplate(ctx)
+ urlTmpl = c.loginPaths.DefaultPasswordSetURLTemplate(ctx)
}
cmd := user.NewHumanPasswordCodeAddedEventV2(ctx, UserAggregateFromWriteModelCtx(ctx, &model.WriteModel), passwordCode.CryptedCode(), passwordCode.CodeExpiry(), notificationType, urlTmpl, returnCode, generatorID)
diff --git a/internal/command/user_v2_password_test.go b/internal/command/user_v2_password_test.go
index 23b1afa07..f2c8c3148 100644
--- a/internal/command/user_v2_password_test.go
+++ b/internal/command/user_v2_password_test.go
@@ -338,13 +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
- defaultPasswordSetURLTemplate func(ctx context.Context) string
+ checkPermission domain.PermissionCheck
+ eventstore func(t *testing.T) *eventstore.Eventstore
+ userEncryption crypto.EncryptionAlgorithm
+ newCode encrypedCodeFunc
+ newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
+ defaultSecretGenerators *SecretGenerators
+ loginPaths func(*testing.T) LoginPaths
}
type args struct {
ctx context.Context
@@ -368,6 +368,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
name: "missing userID",
fields: fields{
eventstore: expectEventstore(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -383,6 +384,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -408,6 +410,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
),
),
),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -430,6 +433,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
),
),
checkPermission: newMockPermissionCheckNotAllowed(),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -466,9 +470,9 @@ func TestCommands_requestPasswordReset(t *testing.T) {
),
),
),
- checkPermission: newMockPermissionCheckAllowed(),
- newCode: mockEncryptedCode("code", 10*time.Minute),
- defaultPasswordSetURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/password/{{.code}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ newCode: mockEncryptedCode("code", 10*time.Minute),
+ loginPaths: expectLoginPathsDefaultPasswordSetURLTemplate("http://example.com/{{.user}}/password/{{.code}}"),
},
args: args{
ctx: context.Background(),
@@ -510,6 +514,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
),
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("code", 10*time.Minute),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -583,6 +588,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
defaultSecretGenerators: defaultGenerators,
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", 10*time.Minute),
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -652,6 +658,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(),
newCode: mockEncryptedCode("code", 10*time.Minute),
defaultSecretGenerators: defaultGenerators,
+ loginPaths: expectLoginPathsNoCall,
},
args: args{
ctx: context.Background(),
@@ -693,9 +700,9 @@ func TestCommands_requestPasswordReset(t *testing.T) {
),
),
),
- checkPermission: newMockPermissionCheckAllowed(),
- newCode: mockEncryptedCode("code", 10*time.Minute),
- defaultPasswordSetURLTemplate: func(ctx context.Context) string { return "http://example.com/{{.user}}/password/{{.code}}" },
+ checkPermission: newMockPermissionCheckAllowed(),
+ newCode: mockEncryptedCode("code", 10*time.Minute),
+ loginPaths: expectLoginPathsDefaultPasswordSetURLTemplate("http://example.com/{{.user}}/password/{{.code}}"),
},
args: args{
ctx: context.Background(),
@@ -713,13 +720,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,
- defaultPasswordSetURLTemplate: tt.fields.defaultPasswordSetURLTemplate,
+ 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,
+ loginPaths: tt.fields.loginPaths(t),
}
got, gotPlainCode, err := c.requestPasswordReset(tt.args.ctx, tt.args.userID, tt.args.returnCode, tt.args.urlTmpl, tt.args.notificationType)
diff --git a/internal/config/hook/string_to_url.go b/internal/config/hook/string_to_url.go
new file mode 100644
index 000000000..842b08ef8
--- /dev/null
+++ b/internal/config/hook/string_to_url.go
@@ -0,0 +1,27 @@
+package hook
+
+import (
+ "net/url"
+ "reflect"
+
+ "github.com/mitchellh/mapstructure"
+)
+
+func StringToURLHookFunc() mapstructure.DecodeHookFuncType {
+ return func(
+ f reflect.Type,
+ t reflect.Type,
+ data interface{},
+ ) (interface{}, error) {
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+
+ if t != reflect.TypeOf(&url.URL{}) {
+ return data, nil
+ }
+
+ u, err := url.Parse(data.(string))
+ return u, err
+ }
+}
diff --git a/internal/notification/handlers/user_notifier.go b/internal/notification/handlers/user_notifier.go
index 6880d63bf..0c9b60974 100644
--- a/internal/notification/handlers/user_notifier.go
+++ b/internal/notification/handlers/user_notifier.go
@@ -2,6 +2,7 @@ package handlers
import (
"context"
+ "net/url"
"time"
http_util "github.com/zitadel/zitadel/internal/api/http"
@@ -89,7 +90,7 @@ const (
type userNotifier struct {
queries *NotificationQueries
- otpEmailTmpl string
+ otpEmailTmpl func(origin *url.URL) string
queue Queue
maxAttempts uint8
@@ -101,7 +102,7 @@ func NewUserNotifier(
commands Commands,
queries *NotificationQueries,
channels types.ChannelChains,
- otpEmailTmpl string,
+ otpEmailTmpl func(origin *url.URL) string,
workerConfig WorkerConfig,
queue Queue,
) *handler.Handler {
@@ -530,7 +531,7 @@ func (u *userNotifier) reduceSessionOTPEmailChallenged(event eventstore.Event) (
if err != nil {
return err
}
- origin := http_util.DomainContext(ctx).Origin()
+ origin := http_util.DomainContext(ctx).OriginURL()
args := otpArgs(ctx, e.Expiry)
args.SessionID = e.Aggregate().ID
@@ -539,7 +540,7 @@ func (u *userNotifier) reduceSessionOTPEmailChallenged(event eventstore.Event) (
Aggregate: e.Aggregate(),
UserID: s.UserFactor.UserID,
UserResourceOwner: s.UserFactor.ResourceOwner,
- TriggeredAtOrigin: origin,
+ TriggeredAtOrigin: origin.String(),
EventType: e.EventType,
NotificationType: domain.NotificationTypeEmail,
MessageType: domain.VerifyEmailOTPMessageType,
@@ -555,11 +556,11 @@ func (u *userNotifier) reduceSessionOTPEmailChallenged(event eventstore.Event) (
}), nil
}
-func (u *userNotifier) otpEmailTemplate(origin string, e *session.OTPEmailChallengedEvent) string {
+func (u *userNotifier) otpEmailTemplate(origin *url.URL, e *session.OTPEmailChallengedEvent) string {
if e.URLTmpl != "" {
return e.URLTmpl
}
- return origin + u.otpEmailTmpl
+ return u.otpEmailTmpl(origin)
}
func otpArgs(ctx context.Context, expiry time.Duration) *domain.NotificationArguments {
diff --git a/internal/notification/handlers/user_notifier_legacy.go b/internal/notification/handlers/user_notifier_legacy.go
index 146c60e10..7117e5698 100644
--- a/internal/notification/handlers/user_notifier_legacy.go
+++ b/internal/notification/handlers/user_notifier_legacy.go
@@ -3,6 +3,7 @@ package handlers
import (
"context"
"errors"
+ "net/url"
"strings"
"time"
@@ -25,7 +26,7 @@ type userNotifierLegacy struct {
commands Commands
queries *NotificationQueries
channels types.ChannelChains
- otpEmailTmpl string
+ otpEmailTmpl func(origin *url.URL) string
}
func NewUserNotifierLegacy(
@@ -34,7 +35,7 @@ func NewUserNotifierLegacy(
commands Commands,
queries *NotificationQueries,
channels types.ChannelChains,
- otpEmailTmpl string,
+ otpEmailTmpl func(origin *url.URL) string,
) *handler.Handler {
return handler.NewHandler(ctx, &config, &userNotifierLegacy{
commands: commands,
@@ -421,8 +422,8 @@ func (u *userNotifierLegacy) reduceOTPEmailCodeAdded(event eventstore.Event) (*h
if e.AuthRequestInfo != nil {
authRequestID = e.AuthRequestInfo.ID
}
- url := func(code, origin string, _ *query.NotifyUser) (string, error) {
- return login.OTPLink(origin, authRequestID, code, domain.MFATypeOTPEmail), nil
+ url := func(code string, origin *url.URL, _ *query.NotifyUser) (string, error) {
+ return login.OTPLink(origin.String(), authRequestID, code, domain.MFATypeOTPEmail), nil
}
return u.reduceOTPEmail(
e,
@@ -450,11 +451,11 @@ func (u *userNotifierLegacy) reduceSessionOTPEmailChallenged(event eventstore.Ev
if err != nil {
return nil, err
}
- url := func(code, origin string, user *query.NotifyUser) (string, error) {
+ link := func(code string, origin *url.URL, user *query.NotifyUser) (string, error) {
var buf strings.Builder
- urlTmpl := origin + u.otpEmailTmpl
- if e.URLTmpl != "" {
- urlTmpl = e.URLTmpl
+ urlTmpl := e.URLTmpl
+ if urlTmpl == "" {
+ urlTmpl = u.otpEmailTmpl(origin)
}
if err := domain.RenderOTPEmailURLTemplate(&buf, urlTmpl, code, user.ID, user.PreferredLoginName, user.DisplayName, e.Aggregate().ID, user.PreferredLanguage); err != nil {
return "", err
@@ -467,7 +468,7 @@ func (u *userNotifierLegacy) reduceSessionOTPEmailChallenged(event eventstore.Ev
e.Expiry,
s.UserFactor.UserID,
s.UserFactor.ResourceOwner,
- url,
+ link,
u.commands.OTPEmailSent,
user.HumanOTPEmailCodeAddedType,
user.HumanOTPEmailCodeSentType,
@@ -480,7 +481,7 @@ func (u *userNotifierLegacy) reduceOTPEmail(
expiry time.Duration,
userID,
resourceOwner string,
- urlTmpl func(code, origin string, user *query.NotifyUser) (string, error),
+ urlTmpl func(code string, origin *url.URL, user *query.NotifyUser) (string, error),
sentCommand func(ctx context.Context, userID string, resourceOwner string) (err error),
eventTypes ...eventstore.EventType,
) (*handler.Statement, error) {
@@ -518,12 +519,12 @@ func (u *userNotifierLegacy) reduceOTPEmail(
if err != nil {
return nil, err
}
- url, err := urlTmpl(plainCode, http_util.DomainContext(ctx).Origin(), notifyUser)
+ link, err := urlTmpl(plainCode, http_util.DomainContext(ctx).OriginURL(), notifyUser)
if err != nil {
return nil, err
}
notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, event.Type())
- err = notify.SendOTPEmailCode(ctx, url, plainCode, expiry)
+ err = notify.SendOTPEmailCode(ctx, link, plainCode, expiry)
if err != nil {
if errors.Is(err, &channels.CancelError{}) {
// if the notification was canceled, we don't want to return the error, so there is no retry
@@ -577,7 +578,7 @@ func (u *userNotifierLegacy) reduceDomainClaimed(event eventstore.Event) (*handl
return err
}
err = types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, event.Type()).
- SendDomainClaimed(ctx, notifyUser, e.UserName)
+ SendDomainClaimed(ctx, notifyUser, e.UserName, e.URLTemplate)
if err != nil {
if errors.Is(err, &channels.CancelError{}) {
// if the notification was canceled, we don't want to return the error, so there is no retry
diff --git a/internal/notification/handlers/user_notifier_legacy_test.go b/internal/notification/handlers/user_notifier_legacy_test.go
index 54a0e4d40..58cd08f54 100644
--- a/internal/notification/handlers/user_notifier_legacy_test.go
+++ b/internal/notification/handlers/user_notifier_legacy_test.go
@@ -3,6 +3,7 @@ package handlers
import (
"database/sql"
"fmt"
+ "net/url"
"testing"
"time"
@@ -1972,7 +1973,9 @@ func newUserNotifierLegacy(t *testing.T, ctrl *gomock.Controller, queries *mock.
smtpAlg,
f.SMSTokenCrypto,
),
- otpEmailTmpl: defaultOTPEmailTemplate,
+ otpEmailTmpl: func(origin *url.URL) string {
+ return origin.String() + defaultOTPEmailTemplate
+ },
channels: ¬ificationChannels{
Chain: *senders.ChainChannels(channel),
EmailConfig: &email.Config{
diff --git a/internal/notification/handlers/user_notifier_test.go b/internal/notification/handlers/user_notifier_test.go
index eae40472e..258c7839b 100644
--- a/internal/notification/handlers/user_notifier_test.go
+++ b/internal/notification/handlers/user_notifier_test.go
@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
+ "net/url"
"testing"
"time"
@@ -1936,7 +1937,9 @@ func newUserNotifier(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQu
smtpAlg,
f.SMSTokenCrypto,
),
- otpEmailTmpl: defaultOTPEmailTemplate,
+ otpEmailTmpl: func(origin *url.URL) string {
+ return origin.String() + defaultOTPEmailTemplate
+ },
}
}
diff --git a/internal/notification/projections.go b/internal/notification/projections.go
index 7ad8a23fb..73f6dab2a 100644
--- a/internal/notification/projections.go
+++ b/internal/notification/projections.go
@@ -3,6 +3,7 @@ package notification
import (
"context"
"fmt"
+ "net/url"
"github.com/zitadel/logging"
@@ -35,7 +36,8 @@ func Register(
commands *command.Commands,
queries *query.Queries,
es *eventstore.Eventstore,
- otpEmailTmpl, fileSystemPath string,
+ otpEmailTmpl func(origin *url.URL) string,
+ fileSystemPath string,
userEncryption, smtpEncryption, smsEncryption crypto.EncryptionAlgorithm,
queue *queue.Queue,
) {
diff --git a/internal/notification/types/domain_claimed.go b/internal/notification/types/domain_claimed.go
index 433728392..36c3c33ae 100644
--- a/internal/notification/types/domain_claimed.go
+++ b/internal/notification/types/domain_claimed.go
@@ -10,11 +10,31 @@ import (
"github.com/zitadel/zitadel/internal/query"
)
-func (notify Notify) SendDomainClaimed(ctx context.Context, user *query.NotifyUser, username string) error {
- url := login.LoginLink(http_utils.DomainContext(ctx).Origin(), user.ResourceOwner)
+func (notify Notify) SendDomainClaimed(ctx context.Context, user *query.NotifyUser, username, urlTemplate string) error {
index := strings.LastIndex(user.LastEmail, "@")
+ domainSuffix := user.LastEmail[index+1:]
+ var url string
+ if urlTemplate == "" {
+ url = login.LoginLink(http_utils.DomainContext(ctx).Origin(), user.ResourceOwner)
+ } else {
+ var buf strings.Builder
+ if err := domain.RenderURLTemplate(&buf, urlTemplate, &DomainClaimedData{
+ Domain: domainSuffix,
+ TempUsername: username,
+ OrgID: user.ResourceOwner,
+ }); err != nil {
+ return err
+ }
+ url = buf.String()
+ }
args := make(map[string]interface{})
args["TempUsername"] = username
- args["Domain"] = user.LastEmail[index+1:]
+ args["Domain"] = domainSuffix
return notify(url, args, domain.DomainClaimedMessageType, true)
}
+
+type DomainClaimedData struct {
+ TempUsername string
+ Domain string
+ OrgID string
+}
diff --git a/internal/repository/user/human_email.go b/internal/repository/user/human_email.go
index 34e490323..4caf56453 100644
--- a/internal/repository/user/human_email.go
+++ b/internal/repository/user/human_email.go
@@ -143,16 +143,6 @@ func (e *HumanEmailCodeAddedEvent) TriggerOrigin() string {
}
func NewHumanEmailCodeAddedEvent(
- ctx context.Context,
- aggregate *eventstore.Aggregate,
- code *crypto.CryptoValue,
- expiry time.Duration,
- authRequestID string,
-) *HumanEmailCodeAddedEvent {
- return NewHumanEmailCodeAddedEventV2(ctx, aggregate, code, expiry, "", false, authRequestID)
-}
-
-func NewHumanEmailCodeAddedEventV2(
ctx context.Context,
aggregate *eventstore.Aggregate,
code *crypto.CryptoValue,
diff --git a/internal/repository/user/user.go b/internal/repository/user/user.go
index 4f798fbea..68d74cb0a 100644
--- a/internal/repository/user/user.go
+++ b/internal/repository/user/user.go
@@ -372,6 +372,7 @@ type DomainClaimedEvent struct {
eventstore.BaseEvent `json:"-"`
UserName string `json:"userName"`
+ URLTemplate string `json:"urlTemplate,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
oldUserName string
orgScopedUsername bool
@@ -398,6 +399,7 @@ func NewDomainClaimedEvent(
userName,
oldUserName string,
orgScopedUsername bool,
+ urlTemplate string,
) *DomainClaimedEvent {
return &DomainClaimedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@@ -406,6 +408,7 @@ func NewDomainClaimedEvent(
UserDomainClaimedType,
),
UserName: userName,
+ URLTemplate: urlTemplate,
oldUserName: oldUserName,
orgScopedUsername: orgScopedUsername,
TriggeredAtOrigin: http.DomainContext(ctx).Origin(),