fix: configure default url templates

This commit is contained in:
Stefan Benz
2025-08-07 18:52:41 +02:00
parent 5f7851768b
commit 37fdcf06ea
12 changed files with 79 additions and 2 deletions

View File

@@ -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:

View File

@@ -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")

View File

@@ -92,6 +92,8 @@ func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error
0,
0,
nil,
nil,
nil,
)
if err != nil {
return err

View File

@@ -58,6 +58,8 @@ func (mig *externalConfigChange) Execute(ctx context.Context, _ eventstore.Event
0,
0,
nil,
nil,
nil,
)
if err != nil {

View File

@@ -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")

View File

@@ -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)

View File

@@ -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 (

View File

@@ -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")
@@ -201,6 +206,8 @@ func StartCommands(
},
GenerateDomain: domain.NewGeneratedInstanceDomain,
caches: caches,
defaultEmailCodeURLTemplate: defaultEmailCodeURLTemplate,
defaultPasswordSetURLTemplate: defaultPasswordSetURLTemplate,
}
if defaultSecretGenerators != nil && defaultSecretGenerators.ClientSecret != nil {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 {