diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index e123caeed1..80041f18bc 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -572,6 +572,12 @@ Login: # 168h is 7 days, one week SharedMaxAge: 168h # ZITADEL_LOGIN_CACHE_SHAREDMAXAGE DefaultOTPEmailURLV2: "/otp/verify?loginName={{.LoginName}}&code={{.Code}}" # ZITADEL_LOGIN_CACHE_DEFAULTOTPEMAILURLV2 + DefaultBasePath: "/ui/v2/login" + DefaultPaths: + LoginPath: "/login" + PasswordSetPath: "/password/set" + EmailCodePath: "/verify" + OTPEmailPath: /otp/verify?loginName={{.LoginName}}&code={{.Code}} Console: ShortCache: diff --git a/cmd/mirror/projections.go b/cmd/mirror/projections.go index 0ff4356d6f..76ea4a92e5 100644 --- a/cmd/mirror/projections.go +++ b/cmd/mirror/projections.go @@ -197,6 +197,8 @@ func projections( config.OIDC.DefaultRefreshTokenExpiration, config.OIDC.DefaultRefreshTokenIdleExpiration, config.DefaultInstance.SecretGenerators, + nil, + nil, ) logging.OnError(err).Fatal("unable to start commands") diff --git a/cmd/setup/03.go b/cmd/setup/03.go index e8c51c79c6..29c7408ac2 100644 --- a/cmd/setup/03.go +++ b/cmd/setup/03.go @@ -92,6 +92,8 @@ func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error 0, 0, nil, + nil, + nil, ) if err != nil { return err diff --git a/cmd/setup/config_change.go b/cmd/setup/config_change.go index fb3ae08d52..8aeefa85b5 100644 --- a/cmd/setup/config_change.go +++ b/cmd/setup/config_change.go @@ -58,6 +58,8 @@ func (mig *externalConfigChange) Execute(ctx context.Context, _ eventstore.Event 0, 0, nil, + nil, + nil, ) if err != nil { diff --git a/cmd/setup/setup.go b/cmd/setup/setup.go index 15236a73e9..c4a9088979 100644 --- a/cmd/setup/setup.go +++ b/cmd/setup/setup.go @@ -527,6 +527,9 @@ func startCommandsQueries( config.OIDC.DefaultRefreshTokenExpiration, config.OIDC.DefaultRefreshTokenIdleExpiration, config.DefaultInstance.SecretGenerators, + + nil, + nil, ) logging.OnError(err).Fatal("unable to start commands") diff --git a/cmd/start/start.go b/cmd/start/start.go index 045bc99d54..4c397259a3 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -256,6 +256,8 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server config.OIDC.DefaultRefreshTokenExpiration, config.OIDC.DefaultRefreshTokenIdleExpiration, config.DefaultInstance.SecretGenerators, + config.Login.DefaultEmailCodeURLTemplate, + config.Login.DefaultPasswordSetURLTemplate, ) if err != nil { return fmt.Errorf("cannot start commands: %w", err) diff --git a/internal/api/ui/login/login.go b/internal/api/ui/login/login.go index 5ff27c14fc..d091d47033 100644 --- a/internal/api/ui/login/login.go +++ b/internal/api/ui/login/login.go @@ -53,6 +53,37 @@ type Config struct { // LoginV2 DefaultOTPEmailURLV2 string + + DefaultBasePath string + DefaultPaths *DefaultPaths +} + +type DefaultPaths struct { + LoginPath string + PasswordSetPath string + EmailCodePath string + OTPEmailPath string +} + +func (c *Config) defaultBaseURL(ctx context.Context) string { + loginV2 := authz.GetInstance(ctx).Features().LoginV2 + if loginV2.Required { + // use the origin as default + baseURI := http_utils.DomainContext(ctx).Origin() + // use custom base URI if defined + if loginV2.BaseURI != nil && loginV2.BaseURI.String() != "" { + baseURI = loginV2.BaseURI.String() + } + return baseURI + c.DefaultBasePath + } + return "" +} + +func (c *Config) DefaultEmailCodeURLTemplate(ctx context.Context) string { + return c.defaultBaseURL(ctx) + c.DefaultPaths.EmailCodePath +} +func (c *Config) DefaultPasswordSetURLTemplate(ctx context.Context) string { + return c.defaultBaseURL(ctx) + c.DefaultPaths.PasswordSetPath } const ( diff --git a/internal/command/command.go b/internal/command/command.go index 64b7b53b67..060892a646 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -99,6 +99,9 @@ type Commands struct { // These instance's milestones never need to be invalidated, // so the query and cache overhead can completely eliminated. milestonesCompleted sync.Map + + defaultEmailCodeURLTemplate func(ctx context.Context) string + defaultPasswordSetURLTemplate func(ctx context.Context) string } func StartCommands( @@ -120,6 +123,8 @@ func StartCommands( defaultRefreshTokenLifetime, defaultRefreshTokenIdleLifetime time.Duration, defaultSecretGenerators *SecretGenerators, + defaultEmailCodeURLTemplate func(ctx context.Context) string, + defaultPasswordSetURLTemplate func(ctx context.Context) string, ) (repo *Commands, err error) { if externalDomain == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Df21s", "no external domain specified") @@ -199,8 +204,10 @@ func StartCommands( Issuer: defaults.Multifactors.OTP.Issuer, }, }, - GenerateDomain: domain.NewGeneratedInstanceDomain, - caches: caches, + GenerateDomain: domain.NewGeneratedInstanceDomain, + caches: caches, + defaultEmailCodeURLTemplate: defaultEmailCodeURLTemplate, + defaultPasswordSetURLTemplate: defaultPasswordSetURLTemplate, } if defaultSecretGenerators != nil && defaultSecretGenerators.ClientSecret != nil { diff --git a/internal/command/user_human.go b/internal/command/user_human.go index e4a6148159..aed8bf45ae 100644 --- a/internal/command/user_human.go +++ b/internal/command/user_human.go @@ -304,6 +304,11 @@ func (c *Commands) addHumanCommandEmail(ctx context.Context, filter preparation. if human.Email.ReturnCode { human.EmailCode = &emailCode.Plain } + + if human.Email.URLTemplate == "" { + human.Email.URLTemplate = c.defaultEmailCodeURLTemplate(ctx) + } + return append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &a.Aggregate, emailCode.Crypted, emailCode.Expiry, human.Email.URLTemplate, human.Email.ReturnCode, human.AuthRequestID)), nil } return cmds, nil diff --git a/internal/command/user_v2_email.go b/internal/command/user_v2_email.go index 4aa75d0935..f372d5846a 100644 --- a/internal/command/user_v2_email.go +++ b/internal/command/user_v2_email.go @@ -154,6 +154,11 @@ func (c *Commands) changeUserEmailWithGeneratorEvents(ctx context.Context, userI if err = cmd.Change(ctx, domain.EmailAddress(email)); err != nil { return nil, err } + + if urlTmpl == "" { + urlTmpl = c.defaultEmailCodeURLTemplate(ctx) + } + if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil { return nil, err } @@ -171,6 +176,11 @@ func (c *Commands) sendUserEmailCodeWithGeneratorEvents(ctx context.Context, use if existingCheck && cmd.model.Code == nil { return nil, zerrors.ThrowPreconditionFailed(err, "EMAIL-5w5ilin4yt", "Errors.User.Code.Empty") } + + if urlTmpl == "" { + urlTmpl = c.defaultEmailCodeURLTemplate(ctx) + } + if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil { return nil, err } diff --git a/internal/command/user_v2_human.go b/internal/command/user_v2_human.go index 3bb54c6d07..572aa29622 100644 --- a/internal/command/user_v2_human.go +++ b/internal/command/user_v2_human.go @@ -411,6 +411,10 @@ func (c *Commands) changeUserEmail(ctx context.Context, cmds []eventstore.Comman if err != nil { return cmds, code, err } + if email.URLTemplate == "" { + email.URLTemplate = c.defaultEmailCodeURLTemplate(ctx) + } + cmds = append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, email.URLTemplate, email.ReturnCode, "")) if email.ReturnCode { code = &cryptoCode.Plain diff --git a/internal/command/user_v2_password.go b/internal/command/user_v2_password.go index faa1fe14a6..e04b6ed748 100644 --- a/internal/command/user_v2_password.go +++ b/internal/command/user_v2_password.go @@ -62,6 +62,9 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu if err != nil { return nil, nil, err } + if urlTmpl == "" { + urlTmpl = c.defaultPasswordSetURLTemplate(ctx) + } cmd := user.NewHumanPasswordCodeAddedEventV2(ctx, UserAggregateFromWriteModelCtx(ctx, &model.WriteModel), passwordCode.CryptedCode(), passwordCode.CodeExpiry(), notificationType, urlTmpl, returnCode, generatorID) if returnCode {