Files
zitadel/internal/command/instance_trusted_domain.go
Livio SpringandGitHub 71fab2e574 feat: allow adding trusted domains in instance setup (#11169)
# Which Problems Are Solved

When running Zitadel behind a reverse proxy and especially when the API
and the login UI don't run on the same domain, Zitadel needs to be
configured to trust the corresponding domains and use them in public
responses, like email links and more.
This can be done by adding a trusted domain. However it's currently only
possible through the API and not in the instance setup process.

# How the Problems Are Solved

Added a possibility to configure multiple trusted domains in the first
instance setup process.

# Additional Changes

None

# Additional Context

- closes #11153
2026-02-23 06:06:33 +00:00

98 lines
3.6 KiB
Go

package command
import (
"context"
"slices"
"strings"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors"
)
func (c *Commands) AddTrustedDomain(ctx context.Context, trustedDomain string) (details *domain.ObjectDetails, err error) {
trustedDomain, err = validateTrustedDomain(trustedDomain)
if err != nil {
return nil, err
}
model := NewInstanceTrustedDomainsWriteModel(ctx)
err = c.eventstore.FilterToQueryReducer(ctx, model)
if err != nil {
return nil, err
}
if slices.Contains(model.Domains, trustedDomain) {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-hg42a", "Errors.Instance.Domain.AlreadyExists")
}
err = c.pushAppendAndReduce(ctx, model, instance.NewTrustedDomainAddedEvent(ctx, InstanceAggregateFromWriteModel(&model.WriteModel), trustedDomain))
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&model.WriteModel), nil
}
// setupTrustedDomain is only used for the instance setup process where all events are prepared and pushed in a single transaction
func (c *Commands) setupTrustedDomain(a *instance.Aggregate, trustedDomain string) preparation.Validation {
return func() (_ preparation.CreateCommands, err error) {
trustedDomain, err = validateTrustedDomain(trustedDomain)
if err != nil {
return nil, err
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
model := NewInstanceTrustedDomainsWriteModel(ctx)
events, err := filter(ctx, model.Query())
if err != nil {
return nil, err
}
err = AppendAndReduce(model, events...)
if err != nil {
return nil, err
}
if slices.Contains(model.Domains, trustedDomain) {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-hg42a", "Errors.Instance.Domain.AlreadyExists")
}
return []eventstore.Command{instance.NewTrustedDomainAddedEvent(ctx, &a.Aggregate, trustedDomain)}, nil
}, nil
}
}
func validateTrustedDomain(trustedDomain string) (string, error) {
trustedDomain = strings.TrimSpace(trustedDomain)
if trustedDomain == "" || len(trustedDomain) > 253 {
return "", zerrors.ThrowInvalidArgument(nil, "COMMA-Stk21", "Errors.Invalid.Argument")
}
if !allowDomainRunes.MatchString(trustedDomain) {
return "", zerrors.ThrowInvalidArgument(nil, "COMMA-S3v3w", "Errors.Instance.Domain.InvalidCharacter")
}
return trustedDomain, nil
}
func (c *Commands) RemoveTrustedDomain(ctx context.Context, trustedDomain string, errorIfNotFound bool) (*domain.ObjectDetails, error) {
trustedDomain = strings.TrimSpace(trustedDomain)
if trustedDomain == "" || len(trustedDomain) > 253 {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-ajAzwu", "Errors.Invalid.Argument")
}
if !allowDomainRunes.MatchString(trustedDomain) {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-lfs3Te", "Errors.Instance.Domain.InvalidCharacter")
}
model := NewInstanceTrustedDomainsWriteModel(ctx)
err := c.eventstore.FilterToQueryReducer(ctx, model)
if err != nil {
return nil, err
}
if !slices.Contains(model.Domains, trustedDomain) {
if errorIfNotFound {
return nil, zerrors.ThrowNotFound(nil, "COMMA-de3z9", "Errors.Instance.Domain.NotFound")
}
return nil, nil
}
err = c.pushAppendAndReduce(ctx, model, instance.NewTrustedDomainRemovedEvent(ctx, InstanceAggregateFromWriteModel(&model.WriteModel), trustedDomain))
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&model.WriteModel), nil
}