fix: correctly send links to login v2 in email notifications (#10711)

# Which Problems Are Solved

There were still some emails (passkey registration and domain claimed)
sent with links pointing to login v1 even when the login v2 was enabled
for the instance.
Also while looking into the issue, it was discovered that some links
pointing to login V2 were not correctly generated.


# How the Problems Are Solved

- Added default paths for passkey registration and domain claimed
notifications
- Fixed the existing paths to properly handle concatenation (resp. use
`url.ResolveReference`)
  - Change their go types (from string) to `*url.URL` 
  - Added a mapstructure hook for string to url
- Removed unnecessary `InstanceSetupFeatures` and corresponding
conversions
- Refactored the methods on the `login.DefaultPaths` struct and added an
interface to the `Commands` to only need to pass a single config (and
not every method)
- Added an `OriginURL` method to the `DomainCtx` to prevent going from
url to string and back
- Added the use of the templates in case of enabled login v2 for passkey
registration and domain claimed)

# Additional Changes

None

# Additional Context

closes #10643

---------

Co-authored-by: Max Peintner <[email protected]>
Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Max Peintner <[email protected]>
Co-authored-by: Gayathri Vijayan <[email protected]>
This commit is contained in:
Stefan Benz
2026-02-13 13:06:56 +00:00
committed by GitHub
co-authored by Max Peintner Livio Spring Livio Spring Max Peintner Gayathri Vijayan
parent 382aec2d61
commit 2a2d5392a3
48 changed files with 1323 additions and 450 deletions
@@ -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=
@@ -109,6 +109,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
requestId={requestId}
code={code}
codeId={codeId}
loginName={loginName ?? session?.factors?.user?.loginName ?? user?.preferredLoginName}
/>
)}
</div>
+17 -3
View File
@@ -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<Inputs>({
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);
}
+7 -1
View File
@@ -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 = {
+11 -4
View File
@@ -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:
+1
View File
@@ -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(","),
+2 -3
View File
@@ -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,
+3 -2
View File
@@ -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
+2
View File
@@ -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(","),
+2 -2
View File
@@ -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 {
+5 -3
View File
@@ -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"},
},
})
},
+3 -4
View File
@@ -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,
+1
View File
@@ -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(","),
+5 -3
View File
@@ -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"},
},
})
},
+2 -3
View File
@@ -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,
+9 -4
View File
@@ -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)
+108 -21
View File
@@ -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 (
+242 -17
View File
@@ -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)
})
}
}
+15 -12
View File
@@ -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 {
+37
View File
@@ -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
}
}
+2 -49
View File
@@ -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))
}
+2 -7
View File
@@ -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
}
}
-119
View File
@@ -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)
})
}
}
+96
View File
@@ -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)
}
+25 -2
View File
@@ -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, "[email protected]", "username", false),
user.NewDomainClaimedEvent(http.WithRequestedHost(context.Background(), "domain"), &user.NewAggregate("userID1", "org2").Aggregate, "[email protected]", "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) {
"[email protected]",
"[email protected]",
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) {
"[email protected]",
"[email protected]",
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 {
+2
View File
@@ -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
}
+3 -3
View File
@@ -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, ""))
}
}
+2 -2
View File
@@ -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
}
+241
View File
@@ -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 protected]",
},
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
},
res: res{
want: &domain.Email{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
EmailAddress: "[email protected]",
},
},
},
{
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 protected]",
true,
),
),
),
expectPush(
user.NewHumanEmailChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"[email protected]",
),
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 protected]",
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 protected]",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanEmailChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"[email protected]",
),
),
),
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 {
+34 -14
View File
@@ -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 {
+3 -3
View File
@@ -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
}
+56 -53
View File
@@ -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 protected]",
),
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 protected]",
),
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 protected]",
),
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,
+2 -2
View File
@@ -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
}
+90 -31
View File
@@ -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 protected]", "", 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,
"[email protected]",
),
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,
"[email protected]",
),
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)
+4 -4
View File
@@ -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,
+4
View File
@@ -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 {
+170 -22
View File
@@ -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 protected]",
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 protected]",
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 protected]",
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 {
+1 -1
View File
@@ -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)
+27 -20
View File
@@ -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)
+27
View File
@@ -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
}
}
@@ -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 {
@@ -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
@@ -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: &notificationChannels{
Chain: *senders.ChainChannels(channel),
EmailConfig: &email.Config{
@@ -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
},
}
}
+3 -1
View File
@@ -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,
) {
+23 -3
View File
@@ -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
}
-10
View File
@@ -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,
+3
View File
@@ -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(),