mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:47:32 +00:00
perf(import): optimize search for domains claimed by other organizations (#8200)
# Which Problems Are Solved Improve the performance of human imports by optimizing the query that finds domains claimed by other organizations. # How the Problems Are Solved Use the fields search table introduced in https://github.com/zitadel/zitadel/pull/8191 by storing each organization domain as Object ID and the verified status as field value. # Additional Changes - Feature flag for this optimization # Additional Context - Performance improvements for import are evaluated and acted upon internally at the moment --------- Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
This commit is contained in:
@@ -13,6 +13,8 @@ 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/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -390,3 +392,65 @@ func (c *Commands) getOrgDomainWriteModel(ctx context.Context, orgID, domain str
|
||||
}
|
||||
return domainWriteModel, nil
|
||||
}
|
||||
|
||||
type OrgDomainVerified struct {
|
||||
OrgID string
|
||||
Domain string
|
||||
Verified bool
|
||||
}
|
||||
|
||||
func (c *Commands) searchOrgDomainVerifiedByDomain(ctx context.Context, domain string) (_ *OrgDomainVerified, err error) {
|
||||
if !authz.GetFeatures(ctx).ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeOrgDomainVerified) {
|
||||
return c.searchOrgDomainVerifiedByDomainOld(ctx, domain)
|
||||
}
|
||||
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
condition := map[eventstore.FieldType]any{
|
||||
eventstore.FieldTypeAggregateType: org.AggregateType,
|
||||
eventstore.FieldTypeObjectType: org.OrgDomainSearchType,
|
||||
eventstore.FieldTypeObjectID: domain,
|
||||
eventstore.FieldTypeObjectRevision: org.OrgDomainObjectRevision,
|
||||
eventstore.FieldTypeFieldName: org.OrgDomainVerifiedSearchField,
|
||||
}
|
||||
|
||||
results, err := c.eventstore.Search(ctx, condition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(results) == 0 {
|
||||
_ = projection.OrgDomainVerifiedFields.Trigger(ctx)
|
||||
results, err = c.eventstore.Search(ctx, condition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
orgDomain := new(OrgDomainVerified)
|
||||
for _, result := range results {
|
||||
orgDomain.OrgID = result.Aggregate.ID
|
||||
if err = result.Value.Unmarshal(&orgDomain.Verified); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return orgDomain, nil
|
||||
}
|
||||
|
||||
func (c *Commands) searchOrgDomainVerifiedByDomainOld(ctx context.Context, domain string) (_ *OrgDomainVerified, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel := NewOrgDomainVerifiedWriteModel(domain)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &OrgDomainVerified{
|
||||
OrgID: writeModel.ResourceOwner,
|
||||
Domain: writeModel.Domain,
|
||||
Verified: writeModel.Verified,
|
||||
}, nil
|
||||
}
|
||||
|
@@ -40,17 +40,8 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-38fnu", "Errors.Org.DomainPolicy.NotExisting")
|
||||
}
|
||||
if !domainPolicy.UserLoginMustBeDomain {
|
||||
index := strings.LastIndex(userName, "@")
|
||||
if index > 1 {
|
||||
domainCheck := NewOrgDomainVerifiedWriteModel(userName[index+1:])
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Di2ei", "Errors.User.DomainNotAllowedAsUsername")
|
||||
}
|
||||
}
|
||||
if err = c.userValidateDomain(ctx, orgID, userName, domainPolicy.UserLoginMustBeDomain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
|
||||
|
||||
|
@@ -362,7 +362,7 @@ func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQue
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) userValidateDomain(ctx context.Context, resourceOwner string, username string, mustBeDomain bool) error {
|
||||
func (c *Commands) userValidateDomain(ctx context.Context, resourceOwner string, username string, mustBeDomain bool) (err error) {
|
||||
if mustBeDomain {
|
||||
return nil
|
||||
}
|
||||
@@ -372,12 +372,12 @@ func (c *Commands) userValidateDomain(ctx context.Context, resourceOwner string,
|
||||
return nil
|
||||
}
|
||||
|
||||
domainCheck, err := c.orgDomainVerifiedWriteModel(ctx, username[index+1:])
|
||||
domainCheck, err := c.searchOrgDomainVerifiedByDomain(ctx, username[index+1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != resourceOwner {
|
||||
if domainCheck.Verified && domainCheck.OrgID != resourceOwner {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
|
||||
}
|
||||
|
||||
@@ -479,7 +479,7 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
|
||||
if orgID == "" {
|
||||
return nil, nil, nil, "", zerrors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.Org.Empty")
|
||||
}
|
||||
if err := human.Normalize(); err != nil {
|
||||
if err = human.Normalize(); err != nil {
|
||||
return nil, nil, nil, "", err
|
||||
}
|
||||
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, links, passwordless, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
|
||||
@@ -497,24 +497,17 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
|
||||
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
|
||||
}
|
||||
|
||||
//nolint:gocognit
|
||||
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, links []*domain.UserIDPLink, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, addedHuman *HumanWriteModel, err error) {
|
||||
if err := human.CheckDomainPolicy(domainPolicy); err != nil {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if err = human.CheckDomainPolicy(domainPolicy); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
human.Username = strings.TrimSpace(human.Username)
|
||||
human.EmailAddress = human.EmailAddress.Normalize()
|
||||
if !domainPolicy.UserLoginMustBeDomain {
|
||||
index := strings.LastIndex(human.Username, "@")
|
||||
if index > 1 {
|
||||
domainCheck := NewOrgDomainVerifiedWriteModel(human.Username[index+1:])
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
|
||||
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
|
||||
}
|
||||
}
|
||||
if err = c.userValidateDomain(ctx, orgID, human.Username, domainPolicy.UserLoginMustBeDomain); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if human.AggregateID == "" {
|
||||
|
@@ -434,15 +434,3 @@ func (c *Commands) userHumanWriteModel(ctx context.Context, userID string, profi
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) orgDomainVerifiedWriteModel(ctx context.Context, domain string) (writeModel *OrgDomainVerifiedWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewOrgDomainVerifiedWriteModel(domain)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
@@ -19,17 +18,8 @@ func (c *Commands) changeUsername(ctx context.Context, cmds []eventstore.Command
|
||||
if err != nil {
|
||||
return cmds, zerrors.ThrowPreconditionFailed(err, "COMMAND-79pv6e1q62", "Errors.Org.DomainPolicy.NotExisting")
|
||||
}
|
||||
if !domainPolicy.UserLoginMustBeDomain {
|
||||
index := strings.LastIndex(userName, "@")
|
||||
if index > 1 {
|
||||
domainCheck := NewOrgDomainVerifiedWriteModel(userName[index+1:])
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
|
||||
return cmds, err
|
||||
}
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
|
||||
return cmds, zerrors.ThrowInvalidArgument(nil, "COMMAND-Di2ei", "Errors.User.DomainNotAllowedAsUsername")
|
||||
}
|
||||
}
|
||||
if err = c.userValidateDomain(ctx, orgID, userName, domainPolicy.UserLoginMustBeDomain); err != nil {
|
||||
return cmds, err
|
||||
}
|
||||
return append(cmds,
|
||||
user.NewUsernameChangedEvent(ctx, &wm.Aggregate().Aggregate, wm.UserName, userName, domainPolicy.UserLoginMustBeDomain),
|
||||
|
Reference in New Issue
Block a user