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 # 168h is 7 days, one week
SharedMaxAge: 168h # ZITADEL_LOGIN_CACHE_SHAREDMAXAGE SharedMaxAge: 168h # ZITADEL_LOGIN_CACHE_SHAREDMAXAGE
DefaultOTPEmailURLV2: "/otp/verify?loginName={{.LoginName}}&code={{.Code}}" # ZITADEL_LOGIN_CACHE_DEFAULTOTPEMAILURLV2 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: Console:
ShortCache: ShortCache:

View File

@@ -197,6 +197,8 @@ func projections(
config.OIDC.DefaultRefreshTokenExpiration, config.OIDC.DefaultRefreshTokenExpiration,
config.OIDC.DefaultRefreshTokenIdleExpiration, config.OIDC.DefaultRefreshTokenIdleExpiration,
config.DefaultInstance.SecretGenerators, config.DefaultInstance.SecretGenerators,
nil,
nil,
) )
logging.OnError(err).Fatal("unable to start commands") 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,
0, 0,
nil, nil,
nil,
nil,
) )
if err != nil { if err != nil {
return err return err

View File

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

View File

@@ -527,6 +527,9 @@ func startCommandsQueries(
config.OIDC.DefaultRefreshTokenExpiration, config.OIDC.DefaultRefreshTokenExpiration,
config.OIDC.DefaultRefreshTokenIdleExpiration, config.OIDC.DefaultRefreshTokenIdleExpiration,
config.DefaultInstance.SecretGenerators, config.DefaultInstance.SecretGenerators,
nil,
nil,
) )
logging.OnError(err).Fatal("unable to start commands") 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.DefaultRefreshTokenExpiration,
config.OIDC.DefaultRefreshTokenIdleExpiration, config.OIDC.DefaultRefreshTokenIdleExpiration,
config.DefaultInstance.SecretGenerators, config.DefaultInstance.SecretGenerators,
config.Login.DefaultEmailCodeURLTemplate,
config.Login.DefaultPasswordSetURLTemplate,
) )
if err != nil { if err != nil {
return fmt.Errorf("cannot start commands: %w", err) return fmt.Errorf("cannot start commands: %w", err)

View File

@@ -53,6 +53,37 @@ type Config struct {
// LoginV2 // LoginV2
DefaultOTPEmailURLV2 string 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 ( const (

View File

@@ -99,6 +99,9 @@ type Commands struct {
// These instance's milestones never need to be invalidated, // 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 completely eliminated.
milestonesCompleted sync.Map milestonesCompleted sync.Map
defaultEmailCodeURLTemplate func(ctx context.Context) string
defaultPasswordSetURLTemplate func(ctx context.Context) string
} }
func StartCommands( func StartCommands(
@@ -120,6 +123,8 @@ func StartCommands(
defaultRefreshTokenLifetime, defaultRefreshTokenLifetime,
defaultRefreshTokenIdleLifetime time.Duration, defaultRefreshTokenIdleLifetime time.Duration,
defaultSecretGenerators *SecretGenerators, defaultSecretGenerators *SecretGenerators,
defaultEmailCodeURLTemplate func(ctx context.Context) string,
defaultPasswordSetURLTemplate func(ctx context.Context) string,
) (repo *Commands, err error) { ) (repo *Commands, err error) {
if externalDomain == "" { if externalDomain == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Df21s", "no external domain specified") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Df21s", "no external domain specified")
@@ -199,8 +204,10 @@ func StartCommands(
Issuer: defaults.Multifactors.OTP.Issuer, Issuer: defaults.Multifactors.OTP.Issuer,
}, },
}, },
GenerateDomain: domain.NewGeneratedInstanceDomain, GenerateDomain: domain.NewGeneratedInstanceDomain,
caches: caches, caches: caches,
defaultEmailCodeURLTemplate: defaultEmailCodeURLTemplate,
defaultPasswordSetURLTemplate: defaultPasswordSetURLTemplate,
} }
if defaultSecretGenerators != nil && defaultSecretGenerators.ClientSecret != nil { 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 { if human.Email.ReturnCode {
human.EmailCode = &emailCode.Plain 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 append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &a.Aggregate, emailCode.Crypted, emailCode.Expiry, human.Email.URLTemplate, human.Email.ReturnCode, human.AuthRequestID)), nil
} }
return cmds, 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 { if err = cmd.Change(ctx, domain.EmailAddress(email)); err != nil {
return nil, err return nil, err
} }
if urlTmpl == "" {
urlTmpl = c.defaultEmailCodeURLTemplate(ctx)
}
if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil { if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
return nil, err return nil, err
} }
@@ -171,6 +176,11 @@ func (c *Commands) sendUserEmailCodeWithGeneratorEvents(ctx context.Context, use
if existingCheck && cmd.model.Code == nil { if existingCheck && cmd.model.Code == nil {
return nil, zerrors.ThrowPreconditionFailed(err, "EMAIL-5w5ilin4yt", "Errors.User.Code.Empty") 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 { if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
return nil, err return nil, err
} }

View File

@@ -411,6 +411,10 @@ func (c *Commands) changeUserEmail(ctx context.Context, cmds []eventstore.Comman
if err != nil { if err != nil {
return cmds, code, err 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, "")) cmds = append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, email.URLTemplate, email.ReturnCode, ""))
if email.ReturnCode { if email.ReturnCode {
code = &cryptoCode.Plain code = &cryptoCode.Plain

View File

@@ -62,6 +62,9 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu
if err != nil { if err != nil {
return nil, nil, err 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) cmd := user.NewHumanPasswordCodeAddedEventV2(ctx, UserAggregateFromWriteModelCtx(ctx, &model.WriteModel), passwordCode.CryptedCode(), passwordCode.CodeExpiry(), notificationType, urlTmpl, returnCode, generatorID)
if returnCode { if returnCode {