mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:27:42 +00:00
feat: user service v2 create, update and remove (#6996)
* feat: user service v2 remove user * feat: user service v2 add user human * feat: user service v2 change user human * feat: user service v2 change user human unit tests * feat: user service v2 reactivate, deactivate, lock, unlock user * feat: user service v2 integration tests * fix: merge back origin/main * lint: linter corrections * fix: move permission check for isVerfied and password change * fix: add deprecated notices and other review comments * fix: consistent naming in proto * fix: errors package renaming * fix: remove / delete user renaming in integration test * fix: machine user status changes through user v2 api * fix: linting changes * fix: linting changes * fix: changes from review * fix: changes from review * fix: changes from review * fix: changes from review * fix: changes from review --------- Co-authored-by: Tim Möhlmann <tim+github@zitadel.com> Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -44,7 +44,7 @@ func (c *Commands) ChangeDefaultDomainPolicy(ctx context.Context, userLoginMustB
|
||||
}
|
||||
|
||||
func (c *Commands) getDefaultDomainPolicy(ctx context.Context) (*domain.DomainPolicy, error) {
|
||||
policyWriteModel, err := c.defaultDomainPolicyWriteModelByID(ctx)
|
||||
policyWriteModel, err := c.instanceDomainPolicyWriteModel(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func (c *Commands) getDefaultDomainPolicy(ctx context.Context) (*domain.DomainPo
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (c *Commands) defaultDomainPolicyWriteModelByID(ctx context.Context) (policy *InstanceDomainPolicyWriteModel, err error) {
|
||||
func (c *Commands) instanceDomainPolicyWriteModel(ctx context.Context) (policy *InstanceDomainPolicyWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
|
@@ -453,7 +453,7 @@ func (c *Commands) prepareRemoveOrg(a *org.Aggregate) preparation.Validation {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMA-aps2n", "Errors.Org.NotFound")
|
||||
}
|
||||
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, a.ID)
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, a.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -59,18 +59,19 @@ func (c *Commands) RemoveOrgDomainPolicy(ctx context.Context, orgID string) (*do
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
// Deprecated: Use commands.domainPolicyWriteModel directly, to remove the domain.DomainPolicy struct
|
||||
func (c *Commands) getOrgDomainPolicy(ctx context.Context, orgID string) (*domain.DomainPolicy, error) {
|
||||
policy, err := c.orgDomainPolicyWriteModelByID(ctx, orgID)
|
||||
policy, err := c.orgDomainPolicyWriteModel(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if policy.State == domain.PolicyStateActive {
|
||||
if policy.State.Exists() {
|
||||
return orgWriteModelToDomainPolicy(policy), nil
|
||||
}
|
||||
return c.getDefaultDomainPolicy(ctx)
|
||||
}
|
||||
|
||||
func (c *Commands) orgDomainPolicyWriteModelByID(ctx context.Context, orgID string) (policy *OrgDomainPolicyWriteModel, err error) {
|
||||
func (c *Commands) orgDomainPolicyWriteModel(ctx context.Context, orgID string) (policy *OrgDomainPolicyWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
|
@@ -26,7 +26,7 @@ type UniqueConstraintReadModel struct {
|
||||
}
|
||||
|
||||
type commandProvider interface {
|
||||
getOrgDomainPolicy(ctx context.Context, orgID string) (*domain.DomainPolicy, error)
|
||||
domainPolicyWriteModel(ctx context.Context, orgID string) (*PolicyDomainWriteModel, error)
|
||||
}
|
||||
|
||||
func NewUniqueConstraintReadModel(ctx context.Context, provider commandProvider) *UniqueConstraintReadModel {
|
||||
@@ -114,21 +114,21 @@ func (rm *UniqueConstraintReadModel) Reduce() error {
|
||||
case *project.RoleRemovedEvent:
|
||||
rm.removeUniqueConstraint(e.Aggregate().ID, e.Key, project.UniqueRoleType)
|
||||
case *user.HumanAddedEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-0k9Gs").WithError(err).Error("could not read policy for human added event unique constraint")
|
||||
continue
|
||||
}
|
||||
rm.addUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, policy.UserLoginMustBeDomain))
|
||||
case *user.HumanRegisteredEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-m9fod").WithError(err).Error("could not read policy for human registered event unique constraint")
|
||||
continue
|
||||
}
|
||||
rm.addUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, policy.UserLoginMustBeDomain))
|
||||
case *user.MachineAddedEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-2n8vs").WithError(err).Error("could not read policy for machine added event unique constraint")
|
||||
continue
|
||||
@@ -138,14 +138,14 @@ func (rm *UniqueConstraintReadModel) Reduce() error {
|
||||
rm.removeUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.UniqueUsername)
|
||||
rm.listRemoveUniqueConstraint(e.Aggregate().ID, user.UniqueUserIDPLinkType)
|
||||
case *user.UsernameChangedEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-5n8gk").WithError(err).Error("could not read policy for username changed event unique constraint")
|
||||
continue
|
||||
}
|
||||
rm.changeUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, policy.UserLoginMustBeDomain))
|
||||
case *user.DomainClaimedEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-xb8uf").WithError(err).Error("could not read policy for domain claimed event unique constraint")
|
||||
continue
|
||||
|
@@ -38,7 +38,7 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-6m9gs", "Errors.User.UsernameNotChanged")
|
||||
}
|
||||
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-38fnu", "Errors.Org.DomainPolicy.NotExisting")
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-m9od", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner)
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, existingUser.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotExisting")
|
||||
}
|
||||
@@ -330,7 +330,7 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events
|
||||
changedUserGrant := NewUserWriteModel(userID, existingUser.ResourceOwner)
|
||||
userAgg := UserAggregateFromWriteModel(&changedUserGrant.WriteModel)
|
||||
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner)
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, existingUser.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
// Deprecated: User commands.domainPolicyWriteModel directly, to remove use of eventstore.Filter function
|
||||
func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, orgID string) (*PolicyDomainWriteModel, error) {
|
||||
wm, err := orgDomainPolicy(ctx, filter, orgID)
|
||||
if err != nil {
|
||||
@@ -25,6 +26,25 @@ func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQuer
|
||||
return nil, zerrors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal")
|
||||
}
|
||||
|
||||
func (c *Commands) domainPolicyWriteModel(ctx context.Context, orgID string) (*PolicyDomainWriteModel, error) {
|
||||
wm, err := c.orgDomainPolicyWriteModel(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wm != nil && wm.State.Exists() {
|
||||
return &wm.PolicyDomainWriteModel, err
|
||||
}
|
||||
instanceWriteModel, err := c.instanceDomainPolicyWriteModel(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if instanceWriteModel != nil && instanceWriteModel.State.Exists() {
|
||||
return &instanceWriteModel.PolicyDomainWriteModel, err
|
||||
}
|
||||
return nil, zerrors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal")
|
||||
}
|
||||
|
||||
// Deprecated: Use commands.orgDomainPolicyWriteModel directly, to remove use of eventstore.Filter function
|
||||
func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer, orgID string) (*OrgDomainPolicyWriteModel, error) {
|
||||
policy := NewOrgDomainPolicyWriteModel(orgID)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
@@ -39,6 +59,7 @@ func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReduce
|
||||
return policy, err
|
||||
}
|
||||
|
||||
// Deprecated: Use commands.instanceDomainPolicyWriteModel directly, to remove use of eventstore.Filter function
|
||||
func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*InstanceDomainPolicyWriteModel, error) {
|
||||
policy := NewInstanceDomainPolicyWriteModel(ctx)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
|
@@ -127,6 +127,7 @@ func (m *AddMetadataEntry) Valid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: use commands.AddUserHuman
|
||||
func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *AddHuman, allowInitMail bool) (err error) {
|
||||
if resourceOwner == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-5Ky74", "Errors.Internal")
|
||||
@@ -180,7 +181,7 @@ func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = userValidateDomain(ctx, a, human.Username, domainPolicy.UserLoginMustBeDomain, filter); err != nil {
|
||||
if err = c.userValidateDomain(ctx, a.ResourceOwner, human.Username, domainPolicy.UserLoginMustBeDomain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -310,6 +311,7 @@ func (c *Commands) addHumanCommandPhone(ctx context.Context, filter preparation.
|
||||
return append(cmds, user.NewHumanPhoneCodeAddedEventV2(ctx, &a.Aggregate, phoneCode.Crypted, phoneCode.Expiry, human.Phone.ReturnCode)), nil
|
||||
}
|
||||
|
||||
// Deprecated: use commands.NewUserHumanWriteModel, to remove deprecated eventstore.Filter
|
||||
func (c *Commands) addHumanCommandCheckID(ctx context.Context, filter preparation.FilterToQueryReducer, human *AddHuman, orgID string) (err error) {
|
||||
if human.ID == "" {
|
||||
human.ID, err = c.idGenerator.Next()
|
||||
@@ -347,7 +349,7 @@ func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQue
|
||||
return nil
|
||||
}
|
||||
|
||||
func userValidateDomain(ctx context.Context, a *user.Aggregate, username string, mustBeDomain bool, filter preparation.FilterToQueryReducer) error {
|
||||
func (c *Commands) userValidateDomain(ctx context.Context, resourceOwner string, username string, mustBeDomain bool) error {
|
||||
if mustBeDomain {
|
||||
return nil
|
||||
}
|
||||
@@ -357,17 +359,12 @@ func userValidateDomain(ctx context.Context, a *user.Aggregate, username string,
|
||||
return nil
|
||||
}
|
||||
|
||||
domainCheck := NewOrgDomainVerifiedWriteModel(username[index+1:])
|
||||
events, err := filter(ctx, domainCheck.Query())
|
||||
domainCheck, err := c.orgDomainVerifiedWriteModel(ctx, username[index+1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainCheck.AppendEvents(events...)
|
||||
if err = domainCheck.Reduce(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != a.ResourceOwner {
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != resourceOwner {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
|
||||
}
|
||||
|
||||
@@ -411,6 +408,7 @@ func (h *AddHuman) shouldAddInitCode() bool {
|
||||
h.Password == ""
|
||||
}
|
||||
|
||||
// Deprecated: use commands.AddUserHuman
|
||||
func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, links []*domain.UserIDPLink, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator crypto.Generator) (_ *domain.Human, passwordlessCode *domain.PasswordlessInitCode, err error) {
|
||||
if orgID == "" {
|
||||
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing")
|
||||
@@ -459,6 +457,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
|
||||
return writeModelToHuman(addedHuman), passwordlessCode, nil
|
||||
}
|
||||
|
||||
// Deprecated: use commands.AddUserHuman
|
||||
func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgMemberRoles []string, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
|
||||
if orgID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
|
||||
|
@@ -80,9 +80,7 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne
|
||||
commands = append(commands, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
|
||||
}
|
||||
if password != "" {
|
||||
passwordWriteModel := NewHumanPasswordWriteModel(userID, existingCode.ResourceOwner)
|
||||
passwordWriteModel.UserState = domain.UserStateActive
|
||||
passwordCommand, err := c.setPasswordCommand(ctx, passwordWriteModel, password, false)
|
||||
passwordCommand, err := c.setPasswordCommand(ctx, userAgg, domain.UserStateActive, password, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -78,7 +78,7 @@ func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner st
|
||||
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org for loginname")
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-55M9f", "Errors.Org.NotFound")
|
||||
}
|
||||
orgPolicy, err := c.getOrgDomainPolicy(ctx, org.AggregateID)
|
||||
orgPolicy, err := c.domainPolicyWriteModel(ctx, org.AggregateID)
|
||||
if err != nil {
|
||||
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.DomainPolicy.NotFound")
|
||||
|
@@ -34,7 +34,7 @@ func (c *Commands) SetPassword(ctx context.Context, orgID, userID, password stri
|
||||
return c.setPassword(ctx, wm, password, oneTime)
|
||||
}
|
||||
|
||||
func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, password, userAgentID string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, password string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -61,8 +61,10 @@ func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID,
|
||||
return c.setPassword(ctx, wm, password, false)
|
||||
}
|
||||
|
||||
func (c *Commands) setPassword(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (objectDetails *domain.ObjectDetails, err error) {
|
||||
command, err := c.setPasswordCommand(ctx, wm, password, changeRequired)
|
||||
// setEncodedPassword add change event from already encoded password to HumanPasswordWriteModel and return the necessary object details for response
|
||||
func (c *Commands) setEncodedPassword(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (objectDetails *domain.ObjectDetails, err error) {
|
||||
agg := user.NewAggregate(wm.AggregateID, wm.ResourceOwner)
|
||||
command, err := c.setPasswordCommand(ctx, &agg.Aggregate, wm.UserState, password, changeRequired, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -73,20 +75,39 @@ func (c *Commands) setPassword(ctx context.Context, wm *HumanPasswordWriteModel,
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) setPasswordCommand(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (_ eventstore.Command, err error) {
|
||||
if err = c.canUpdatePassword(ctx, password, wm); err != nil {
|
||||
// setPassword add change event to HumanPasswordWriteModel and return the necessary object details for response
|
||||
func (c *Commands) setPassword(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (objectDetails *domain.ObjectDetails, err error) {
|
||||
agg := user.NewAggregate(wm.AggregateID, wm.ResourceOwner)
|
||||
command, err := c.setPasswordCommand(ctx, &agg.Aggregate, wm.UserState, password, changeRequired, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, span := tracing.NewNamedSpan(ctx, "passwap.Hash")
|
||||
encoded, err := c.userPasswordHasher.Hash(password)
|
||||
span.EndWithError(err)
|
||||
if err = convertPasswapErr(err); err != nil {
|
||||
err = c.pushAppendAndReduce(ctx, wm, command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user.NewHumanPasswordChangedEvent(ctx, UserAggregateFromWriteModel(&wm.WriteModel), encoded, changeRequired, ""), nil
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
func (c *Commands) setPasswordCommand(ctx context.Context, agg *eventstore.Aggregate, userState domain.UserState, password string, changeRequired, encoded bool) (_ eventstore.Command, err error) {
|
||||
if err = c.canUpdatePassword(ctx, password, agg.ResourceOwner, userState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !encoded {
|
||||
ctx, span := tracing.NewNamedSpan(ctx, "passwap.Hash")
|
||||
encodedPassword, err := c.userPasswordHasher.Hash(password)
|
||||
span.EndWithError(err)
|
||||
if err = convertPasswapErr(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user.NewHumanPasswordChangedEvent(ctx, agg, encodedPassword, changeRequired, ""), nil
|
||||
}
|
||||
return user.NewHumanPasswordChangedEvent(ctx, agg, password, changeRequired, ""), nil
|
||||
}
|
||||
|
||||
// ChangePassword change password of existing user
|
||||
func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -100,38 +121,39 @@ func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPasswor
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wm.EncodedHash == "" {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Fds3s", "Errors.User.Password.Empty")
|
||||
}
|
||||
if err = c.canUpdatePassword(ctx, newPassword, wm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, spanPasswap := tracing.NewNamedSpan(ctx, "passwap.VerifyAndUpdate")
|
||||
updated, err := c.userPasswordHasher.VerifyAndUpdate(wm.EncodedHash, oldPassword, newPassword)
|
||||
spanPasswap.EndWithError(err)
|
||||
if err = convertPasswapErr(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.pushAppendAndReduce(ctx, wm,
|
||||
user.NewHumanPasswordChangedEvent(ctx, UserAggregateFromWriteModel(&wm.WriteModel), updated, false, userAgentID))
|
||||
newPasswordHash, err := c.verifyAndUpdatePassword(ctx, wm.EncodedHash, oldPassword, newPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
return c.setEncodedPassword(ctx, wm, newPasswordHash, false)
|
||||
}
|
||||
|
||||
func (c *Commands) canUpdatePassword(ctx context.Context, newPassword string, wm *HumanPasswordWriteModel) (err error) {
|
||||
// verifyAndUpdatePassword verify if the old password is correct with the encoded hash and
|
||||
// returns the hash of the new password if so
|
||||
func (c *Commands) verifyAndUpdatePassword(ctx context.Context, encodedHash, oldPassword, newPassword string) (string, error) {
|
||||
if encodedHash == "" {
|
||||
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-Fds3s", "Errors.User.Password.NotSet")
|
||||
}
|
||||
|
||||
_, spanPasswap := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
||||
updated, err := c.userPasswordHasher.VerifyAndUpdate(encodedHash, oldPassword, newPassword)
|
||||
spanPasswap.EndWithError(err)
|
||||
return updated, convertPasswapErr(err)
|
||||
}
|
||||
|
||||
// canUpdatePassword checks uf the given password can be used to be the password of a user
|
||||
func (c *Commands) canUpdatePassword(ctx context.Context, newPassword string, resourceOwner string, state domain.UserState) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if wm.UserState == domain.UserStateUnspecified || wm.UserState == domain.UserStateDeleted {
|
||||
if !isUserStateExists(state) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-G8dh3", "Errors.User.Password.NotFound")
|
||||
}
|
||||
if wm.UserState == domain.UserStateInitial {
|
||||
if state == domain.UserStateInitial {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-M9dse", "Errors.User.NotInitialised")
|
||||
}
|
||||
policy, err := c.getOrgPasswordComplexityPolicy(ctx, wm.ResourceOwner)
|
||||
policy, err := c.getOrgPasswordComplexityPolicy(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,6 +164,7 @@ func (c *Commands) canUpdatePassword(ctx context.Context, newPassword string, wm
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestSetPassword generate and send out new code to change password for a specific user
|
||||
func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType, passwordVerificationCode crypto.Generator) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-M00oL", "Errors.User.UserIDMissing")
|
||||
@@ -151,7 +174,7 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted {
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Hj9ds", "Errors.User.NotFound")
|
||||
}
|
||||
if existingHuman.UserState == domain.UserStateInitial {
|
||||
@@ -173,6 +196,7 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
// PasswordCodeSent notification send with code to change password
|
||||
func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
if userID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-meEfe", "Errors.User.UserIDMissing")
|
||||
@@ -190,6 +214,7 @@ func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string) (
|
||||
return err
|
||||
}
|
||||
|
||||
// PasswordChangeSent notification sent that user changed his password
|
||||
func (c *Commands) PasswordChangeSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
if userID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-pqlm2n", "Errors.User.UserIDMissing")
|
||||
@@ -207,6 +232,7 @@ func (c *Commands) PasswordChangeSent(ctx context.Context, orgID, userID string)
|
||||
return err
|
||||
}
|
||||
|
||||
// HumanCheckPassword check password for user with additional informations from authRequest
|
||||
func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, password string, authRequest *domain.AuthRequest, lockoutPolicy *domain.LockoutPolicy) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -230,13 +256,13 @@ func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, passwo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wm.UserState == domain.UserStateUnspecified || wm.UserState == domain.UserStateDeleted {
|
||||
|
||||
if !isUserStateExists(wm.UserState) {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.NotFound")
|
||||
}
|
||||
if wm.UserState == domain.UserStateLocked {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-JLK35", "Errors.User.Locked")
|
||||
}
|
||||
|
||||
if wm.EncodedHash == "" {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3nJ4t", "Errors.User.Password.NotSet")
|
||||
}
|
||||
|
@@ -278,7 +278,6 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
code string
|
||||
resourceOwner string
|
||||
password string
|
||||
agentID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@@ -505,7 +504,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
userPasswordHasher: tt.fields.userPasswordHasher,
|
||||
userEncryption: tt.fields.userEncryption,
|
||||
}
|
||||
got, err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password, tt.args.agentID)
|
||||
got, err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -529,7 +528,6 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
||||
resourceOwner string
|
||||
oldPassword string
|
||||
newPassword string
|
||||
agentID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@@ -675,18 +673,6 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
||||
false,
|
||||
"")),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
1,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -766,7 +752,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
||||
eventstore: eventstoreExpect(t, tt.expect...),
|
||||
userPasswordHasher: tt.fields.userPasswordHasher,
|
||||
}
|
||||
got, err := r.ChangePassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.oldPassword, tt.args.newPassword, tt.args.agentID)
|
||||
got, err := r.ChangePassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.oldPassword, tt.args.newPassword)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@@ -4266,6 +4266,17 @@ func TestCommandSide_HumanSignOut(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newAddMachineEvent(userLoginMustBeDomain bool, accessTokenType domain.OIDCTokenType) *user.MachineAddedEvent {
|
||||
return user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"name",
|
||||
"description",
|
||||
userLoginMustBeDomain,
|
||||
accessTokenType,
|
||||
)
|
||||
}
|
||||
|
||||
func newAddHumanEvent(password string, changeRequired, userLoginMustBeDomain bool, phone string, preferredLanguage language.Tag) *user.HumanAddedEvent {
|
||||
event := user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
|
@@ -149,7 +149,7 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner,
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
orgPolicy, err := c.getOrgDomainPolicy(ctx, org.AggregateID)
|
||||
orgPolicy, err := c.domainPolicyWriteModel(ctx, org.AggregateID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
213
internal/command/user_v2.go
Normal file
213
internal/command/user_v2.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) LockUserV2(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-agz3eczifm", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-450yxuqrh1", "Errors.User.NotFound")
|
||||
}
|
||||
if !hasUserState(existingHuman.UserState, domain.UserStateActive, domain.UserStateInitial) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-lgws8wtsqf", "Errors.User.ShouldBeActiveOrInitial")
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, existingHuman, user.NewUserLockedEvent(ctx, &existingHuman.Aggregate().Aggregate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) UnlockUserV2(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-a9ld4xckax", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-x377t913pw", "Errors.User.NotFound")
|
||||
}
|
||||
if !hasUserState(existingHuman.UserState, domain.UserStateLocked) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-olb9vb0oca", "Errors.User.NotLocked")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, existingHuman, user.NewUserUnlockedEvent(ctx, &existingHuman.Aggregate().Aggregate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeactivateUserV2(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-78iiirat8y", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-5gp2p62iin", "Errors.User.NotFound")
|
||||
}
|
||||
if isUserStateInitial(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-gvx4kct9r2", "Errors.User.CantDeactivateInitial")
|
||||
}
|
||||
if isUserStateInactive(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-5gunjw0cd7", "Errors.User.AlreadyInactive")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, existingHuman, user.NewUserDeactivatedEvent(ctx, &existingHuman.Aggregate().Aggregate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ReactivateUserV2(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-0nx1ie38fw", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-9hy5kzbuk6", "Errors.User.NotFound")
|
||||
}
|
||||
if !isUserStateInactive(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-s5qqcz97hf", "Errors.User.NotInactive")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, existingHuman, user.NewUserReactivatedEvent(ctx, &existingHuman.Aggregate().Aggregate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateUser(ctx context.Context, resourceOwner, userID string) error {
|
||||
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
|
||||
return nil
|
||||
}
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionDeleteUser(ctx context.Context, resourceOwner, userID string) error {
|
||||
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
|
||||
return nil
|
||||
}
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserDelete, resourceOwner, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) userStateWriteModel(ctx context.Context, userID string) (writeModel *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewUserStateWriteModel(userID, "")
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveUserV2(ctx context.Context, userID string, cascadingUserMemberships []*CascadingMembership, cascadingGrantIDs ...string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-vaipl7s13l", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingUser, err := c.userRemoveWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingUser.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-bd4ir1mblj", "Errors.User.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionDeleteUser(ctx, existingUser.ResourceOwner, existingUser.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, existingUser.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-l40ykb3xh2", "Errors.Org.DomainPolicy.NotExisting")
|
||||
}
|
||||
var events []eventstore.Command
|
||||
events = append(events, user.NewUserRemovedEvent(ctx, &existingUser.Aggregate().Aggregate, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain))
|
||||
|
||||
for _, grantID := range cascadingGrantIDs {
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
if err != nil {
|
||||
logging.WithFields("usergrantid", grantID).WithError(err).Warn("could not cascade remove role on user grant")
|
||||
continue
|
||||
}
|
||||
events = append(events, removeEvent)
|
||||
}
|
||||
|
||||
if len(cascadingUserMemberships) > 0 {
|
||||
membershipEvents, err := c.removeUserMemberships(ctx, cascadingUserMemberships)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events = append(events, membershipEvents...)
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingUser, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingUser.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) userRemoveWriteModel(ctx context.Context, userID string) (writeModel *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewUserRemoveWriteModel(userID, "")
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
@@ -67,6 +67,14 @@ func (c *Commands) changeUserEmailWithCode(ctx context.Context, userID, resource
|
||||
// When the plain text code is returned, no notification e-mail will be send to the user.
|
||||
// urlTmpl allows changing the target URL that is used by the e-mail and should be a validated Go template, if used.
|
||||
func (c *Commands) changeUserEmailWithGenerator(ctx context.Context, userID, resourceOwner, email string, gen crypto.Generator, returnCode bool, urlTmpl string) (*domain.Email, error) {
|
||||
cmd, err := c.changeUserEmailWithGeneratorEvents(ctx, userID, resourceOwner, email, gen, returnCode, urlTmpl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmd.Push(ctx)
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserEmailWithGeneratorEvents(ctx context.Context, userID, resourceOwner, email string, gen crypto.Generator, returnCode bool, urlTmpl string) (*UserEmailEvents, error) {
|
||||
cmd, err := c.NewUserEmailEvents(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -82,7 +90,7 @@ func (c *Commands) changeUserEmailWithGenerator(ctx context.Context, userID, res
|
||||
if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmd.Push(ctx)
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (c *Commands) VerifyUserEmail(ctx context.Context, userID, resourceOwner, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) {
|
||||
@@ -167,18 +175,30 @@ func (c *UserEmailEvents) SetVerified(ctx context.Context) {
|
||||
// AddGeneratedCode generates a new encrypted code and sets it to the email address.
|
||||
// When returnCode a plain text of the code will be returned from Push.
|
||||
func (c *UserEmailEvents) AddGeneratedCode(ctx context.Context, gen crypto.Generator, urlTmpl string, returnCode bool) error {
|
||||
value, plain, err := crypto.NewCode(gen)
|
||||
cmd, code, err := generateCodeCommand(ctx, c.aggregate, gen, urlTmpl, returnCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.events = append(c.events, user.NewHumanEmailCodeAddedEventV2(ctx, c.aggregate, value, gen.Expiry(), urlTmpl, returnCode))
|
||||
c.events = append(c.events, cmd)
|
||||
if returnCode {
|
||||
c.plainCode = &plain
|
||||
c.plainCode = &code
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateCodeCommand(ctx context.Context, agg *eventstore.Aggregate, gen crypto.Generator, urlTmpl string, returnCode bool) (eventstore.Command, string, error) {
|
||||
value, plain, err := crypto.NewCode(gen)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
cmd := user.NewHumanEmailCodeAddedEventV2(ctx, agg, value, gen.Expiry(), urlTmpl, returnCode)
|
||||
if returnCode {
|
||||
return cmd, plain, nil
|
||||
}
|
||||
return cmd, "", nil
|
||||
}
|
||||
|
||||
func (c *UserEmailEvents) VerifyCode(ctx context.Context, code string, gen crypto.Generator) error {
|
||||
if code == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty")
|
||||
|
457
internal/command/user_v2_human.go
Normal file
457
internal/command/user_v2_human.go
Normal file
@@ -0,0 +1,457 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type ChangeHuman struct {
|
||||
ID string
|
||||
Username *string
|
||||
Profile *Profile
|
||||
Email *Email
|
||||
Phone *Phone
|
||||
|
||||
Password *Password
|
||||
|
||||
// Details are set after a successful execution of the command
|
||||
Details *domain.ObjectDetails
|
||||
|
||||
// EmailCode is set by the command
|
||||
EmailCode *string
|
||||
|
||||
// PhoneCode is set by the command
|
||||
PhoneCode *string
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
FirstName *string
|
||||
LastName *string
|
||||
NickName *string
|
||||
DisplayName *string
|
||||
PreferredLanguage *language.Tag
|
||||
Gender *domain.Gender
|
||||
}
|
||||
|
||||
type Password struct {
|
||||
// Either you have to have permission, a password code or the old password to change
|
||||
PasswordCode *string
|
||||
OldPassword *string
|
||||
Password *string
|
||||
EncodedPasswordHash *string
|
||||
|
||||
ChangeRequired bool
|
||||
}
|
||||
|
||||
func (h *ChangeHuman) Validate(hasher *crypto.PasswordHasher) (err error) {
|
||||
if h.Email != nil && h.Email.Address != "" {
|
||||
if err := h.Email.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if h.Phone != nil && h.Phone.Number != "" {
|
||||
if h.Phone.Number, err = h.Phone.Number.Normalize(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if h.Password != nil {
|
||||
if err := h.Password.Validate(hasher); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Password) Validate(hasher *crypto.PasswordHasher) error {
|
||||
if p.EncodedPasswordHash != nil {
|
||||
if !hasher.EncodingSupported(*p.EncodedPasswordHash) {
|
||||
return zerrors.ThrowInvalidArgument(nil, "USER-oz74onzvqr", "Errors.User.Password.NotSupported")
|
||||
}
|
||||
}
|
||||
if p.Password == nil && p.EncodedPasswordHash == nil {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-3klek4sbns", "Errors.User.Password.Empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ChangeHuman) Changed() bool {
|
||||
if h.Username != nil {
|
||||
return true
|
||||
}
|
||||
if h.Profile != nil {
|
||||
return true
|
||||
}
|
||||
if h.Email != nil {
|
||||
return true
|
||||
}
|
||||
if h.Phone != nil {
|
||||
return true
|
||||
}
|
||||
if h.Password != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human *AddHuman, allowInitMail bool, alg crypto.EncryptionAlgorithm) (err error) {
|
||||
if resourceOwner == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-095xh8fll1", "Errors.Internal")
|
||||
}
|
||||
|
||||
if err := human.Validate(c.userPasswordHasher); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if human.ID == "" {
|
||||
human.ID, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// only check if user is already existing
|
||||
existingHuman, err := c.userExistsWriteModel(
|
||||
ctx,
|
||||
human.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isUserStateExists(existingHuman.UserState) {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-7yiox1isql", "Errors.User.AlreadyExisting")
|
||||
}
|
||||
// check for permission to create user on resourceOwner
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, human.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
// add resourceowner for the events with the aggregate
|
||||
existingHuman.ResourceOwner = resourceOwner
|
||||
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.userValidateDomain(ctx, resourceOwner, human.Username, domainPolicy.UserLoginMustBeDomain); err != nil {
|
||||
return err
|
||||
}
|
||||
var createCmd humanCreationCommand
|
||||
if human.Register {
|
||||
createCmd = user.NewHumanRegisteredEvent(
|
||||
ctx,
|
||||
&existingHuman.Aggregate().Aggregate,
|
||||
human.Username,
|
||||
human.FirstName,
|
||||
human.LastName,
|
||||
human.NickName,
|
||||
human.DisplayName,
|
||||
human.PreferredLanguage,
|
||||
human.Gender,
|
||||
human.Email.Address,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
)
|
||||
} else {
|
||||
createCmd = user.NewHumanAddedEvent(
|
||||
ctx,
|
||||
&existingHuman.Aggregate().Aggregate,
|
||||
human.Username,
|
||||
human.FirstName,
|
||||
human.LastName,
|
||||
human.NickName,
|
||||
human.DisplayName,
|
||||
human.PreferredLanguage,
|
||||
human.Gender,
|
||||
human.Email.Address,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
)
|
||||
}
|
||||
|
||||
if human.Phone.Number != "" {
|
||||
createCmd.AddPhoneData(human.Phone.Number)
|
||||
}
|
||||
|
||||
// separated to change when old user logic is not used anymore
|
||||
filter := c.eventstore.Filter //nolint:staticcheck
|
||||
if err := addHumanCommandPassword(ctx, filter, createCmd, human, c.userPasswordHasher); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmds := make([]eventstore.Command, 0, 3)
|
||||
cmds = append(cmds, createCmd)
|
||||
|
||||
cmds, err = c.addHumanCommandEmail(ctx, filter, cmds, existingHuman.Aggregate(), human, alg, allowInitMail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmds, err = c.addHumanCommandPhone(ctx, filter, cmds, existingHuman.Aggregate(), human, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, metadataEntry := range human.Metadata {
|
||||
cmds = append(cmds, user.NewMetadataSetEvent(
|
||||
ctx,
|
||||
&existingHuman.Aggregate().Aggregate,
|
||||
metadataEntry.Key,
|
||||
metadataEntry.Value,
|
||||
))
|
||||
}
|
||||
for _, link := range human.Links {
|
||||
cmd, err := addLink(ctx, filter, existingHuman.Aggregate(), link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if len(cmds) == 0 {
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.pushAppendAndReduce(ctx, existingHuman, cmds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeUserHuman(ctx context.Context, human *ChangeHuman, alg crypto.EncryptionAlgorithm) (err error) {
|
||||
if err := human.Validate(c.userPasswordHasher); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingHuman, err := c.userHumanWriteModel(
|
||||
ctx,
|
||||
human.ID,
|
||||
human.Profile != nil,
|
||||
human.Email != nil,
|
||||
human.Phone != nil,
|
||||
human.Password != nil,
|
||||
false, // avatar not updateable
|
||||
false, // IDPLinks not updateable
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-ugjs0upun6", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
if human.Changed() {
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cmds := make([]eventstore.Command, 0)
|
||||
if human.Username != nil {
|
||||
cmds, err = c.changeUsername(ctx, cmds, existingHuman, *human.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if human.Profile != nil {
|
||||
cmds, err = changeUserProfile(ctx, cmds, existingHuman, human.Profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if human.Email != nil {
|
||||
cmds, human.EmailCode, err = c.changeUserEmail(ctx, cmds, existingHuman, human.Email, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if human.Phone != nil {
|
||||
cmds, human.PhoneCode, err = c.changeUserPhone(ctx, cmds, existingHuman, human.Phone, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if human.Password != nil {
|
||||
cmds, err = c.changeUserPassword(ctx, cmds, existingHuman, human.Password, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(cmds) == 0 {
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
err = c.pushAppendAndReduce(ctx, existingHuman, cmds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserEmail(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, email *Email, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, code *string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
if email.Address != "" && email.Address != wm.Email {
|
||||
cmds = append(cmds, user.NewHumanEmailChangedEvent(ctx, &wm.Aggregate().Aggregate, email.Address))
|
||||
|
||||
if email.Verified {
|
||||
return append(cmds, user.NewHumanEmailVerifiedEvent(ctx, &wm.Aggregate().Aggregate)), code, nil
|
||||
} else {
|
||||
cryptoCode, err := c.newEmailCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return cmds, code, err
|
||||
}
|
||||
cmds = append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, email.URLTemplate, email.ReturnCode))
|
||||
if email.ReturnCode {
|
||||
code = &cryptoCode.Plain
|
||||
}
|
||||
return cmds, code, nil
|
||||
}
|
||||
}
|
||||
// only create separate event of verified if email was not changed
|
||||
if email.Verified && wm.IsEmailVerified != email.Verified {
|
||||
return append(cmds, user.NewHumanEmailVerifiedEvent(ctx, &wm.Aggregate().Aggregate)), nil, nil
|
||||
}
|
||||
return cmds, code, nil
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserPhone(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, phone *Phone, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, code *string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
if phone.Number != "" && phone.Number != wm.Phone {
|
||||
cmds = append(cmds, user.NewHumanPhoneChangedEvent(ctx, &wm.Aggregate().Aggregate, phone.Number))
|
||||
|
||||
if phone.Verified {
|
||||
return append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &wm.Aggregate().Aggregate)), code, nil
|
||||
} else {
|
||||
cryptoCode, err := c.newPhoneCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return cmds, code, err
|
||||
}
|
||||
cmds = append(cmds, user.NewHumanPhoneCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, phone.ReturnCode))
|
||||
if phone.ReturnCode {
|
||||
code = &cryptoCode.Plain
|
||||
}
|
||||
return cmds, code, nil
|
||||
}
|
||||
}
|
||||
// only create separate event of verified if email was not changed
|
||||
if phone.Verified && wm.IsPhoneVerified != phone.Verified {
|
||||
return append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &wm.Aggregate().Aggregate)), code, nil
|
||||
}
|
||||
return cmds, code, nil
|
||||
}
|
||||
|
||||
func changeUserProfile(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, profile *Profile) ([]eventstore.Command, error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
cmd, err := wm.NewProfileChangedEvent(ctx, profile.FirstName, profile.LastName, profile.NickName, profile.DisplayName, profile.PreferredLanguage, profile.Gender)
|
||||
if cmd != nil {
|
||||
return append(cmds, cmd), err
|
||||
}
|
||||
return cmds, err
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, password *Password, alg crypto.EncryptionAlgorithm) ([]eventstore.Command, error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
// Either have a code to set the password
|
||||
if password.PasswordCode != nil {
|
||||
if err := crypto.VerifyCodeWithAlgorithm(wm.PasswordCodeCreationDate, wm.PasswordCodeExpiry, wm.PasswordCode, *password.PasswordCode, alg); err != nil {
|
||||
return cmds, err
|
||||
}
|
||||
}
|
||||
var encodedPassword string
|
||||
// or have the old password to change it
|
||||
if password.OldPassword != nil {
|
||||
// newly encode old password if no new and already encoded password is set
|
||||
pw := *password.OldPassword
|
||||
if password.Password != nil {
|
||||
pw = *password.Password
|
||||
}
|
||||
alreadyEncodedPassword, err := c.verifyAndUpdatePassword(ctx, wm.PasswordEncodedHash, *password.OldPassword, pw)
|
||||
if err != nil {
|
||||
return cmds, err
|
||||
}
|
||||
encodedPassword = alreadyEncodedPassword
|
||||
}
|
||||
|
||||
// password already hashed in request
|
||||
if password.EncodedPasswordHash != nil {
|
||||
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, *password.EncodedPasswordHash, password.ChangeRequired, true)
|
||||
if cmd != nil {
|
||||
return append(cmds, cmd), err
|
||||
}
|
||||
return cmds, err
|
||||
}
|
||||
// password already hashed in verify
|
||||
if encodedPassword != "" {
|
||||
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, encodedPassword, password.ChangeRequired, true)
|
||||
if cmd != nil {
|
||||
return append(cmds, cmd), err
|
||||
}
|
||||
return cmds, err
|
||||
}
|
||||
// password still to be hashed
|
||||
if password.Password != nil {
|
||||
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, *password.Password, password.ChangeRequired, false)
|
||||
if cmd != nil {
|
||||
return append(cmds, cmd), err
|
||||
}
|
||||
return cmds, err
|
||||
}
|
||||
// no password changes necessary
|
||||
return cmds, nil
|
||||
}
|
||||
|
||||
func (c *Commands) userExistsWriteModel(ctx context.Context, userID string) (writeModel *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewUserExistsWriteModel(userID, "")
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) userHumanWriteModel(ctx context.Context, userID string, profileWM, emailWM, phoneWM, passwordWM, avatarWM, idpLinksWM bool) (writeModel *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewUserHumanWriteModel(userID, "", profileWM, emailWM, phoneWM, passwordWM, avatarWM, idpLinksWM)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
2568
internal/command/user_v2_human_test.go
Normal file
2568
internal/command/user_v2_human_test.go
Normal file
File diff suppressed because it is too large
Load Diff
558
internal/command/user_v2_model.go
Normal file
558
internal/command/user_v2_model.go
Normal file
@@ -0,0 +1,558 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type UserV2WriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
UserName string
|
||||
|
||||
MachineWriteModel bool
|
||||
Name string
|
||||
Description string
|
||||
AccessTokenType domain.OIDCTokenType
|
||||
|
||||
MachineSecretWriteModel bool
|
||||
ClientSecret *crypto.CryptoValue
|
||||
|
||||
ProfileWriteModel bool
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
DisplayName string
|
||||
PreferredLanguage language.Tag
|
||||
Gender domain.Gender
|
||||
|
||||
AvatarWriteModel bool
|
||||
Avatar string
|
||||
|
||||
HumanWriteModel bool
|
||||
InitCode *crypto.CryptoValue
|
||||
InitCodeCreationDate time.Time
|
||||
InitCodeExpiry time.Duration
|
||||
InitCheckFailedCount uint64
|
||||
|
||||
PasswordWriteModel bool
|
||||
PasswordEncodedHash string
|
||||
PasswordChangeRequired bool
|
||||
PasswordCode *crypto.CryptoValue
|
||||
PasswordCodeCreationDate time.Time
|
||||
PasswordCodeExpiry time.Duration
|
||||
PasswordCheckFailedCount uint64
|
||||
|
||||
EmailWriteModel bool
|
||||
Email domain.EmailAddress
|
||||
IsEmailVerified bool
|
||||
EmailCode *crypto.CryptoValue
|
||||
EmailCodeCreationDate time.Time
|
||||
EmailCodeExpiry time.Duration
|
||||
EmailCheckFailedCount uint64
|
||||
|
||||
PhoneWriteModel bool
|
||||
Phone domain.PhoneNumber
|
||||
IsPhoneVerified bool
|
||||
PhoneCode *crypto.CryptoValue
|
||||
PhoneCodeCreationDate time.Time
|
||||
PhoneCodeExpiry time.Duration
|
||||
PhoneCheckFailedCount uint64
|
||||
|
||||
StateWriteModel bool
|
||||
UserState domain.UserState
|
||||
|
||||
IDPLinkWriteModel bool
|
||||
IDPLinks []*domain.UserIDPLink
|
||||
}
|
||||
|
||||
func NewUserExistsWriteModel(userID, resourceOwner string) *UserV2WriteModel {
|
||||
return newUserV2WriteModel(userID, resourceOwner, WithHuman(), WithMachine())
|
||||
}
|
||||
|
||||
func NewUserStateWriteModel(userID, resourceOwner string) *UserV2WriteModel {
|
||||
return newUserV2WriteModel(userID, resourceOwner, WithHuman(), WithMachine(), WithState())
|
||||
}
|
||||
|
||||
func NewUserRemoveWriteModel(userID, resourceOwner string) *UserV2WriteModel {
|
||||
return newUserV2WriteModel(userID, resourceOwner, WithHuman(), WithMachine(), WithState(), WithIDPLinks())
|
||||
}
|
||||
|
||||
func NewUserHumanWriteModel(userID, resourceOwner string, profileWM, emailWM, phoneWM, passwordWM, avatarWM, idpLinks bool) *UserV2WriteModel {
|
||||
opts := []UserV2WMOption{WithHuman(), WithState()}
|
||||
if profileWM {
|
||||
opts = append(opts, WithProfile())
|
||||
}
|
||||
if emailWM {
|
||||
opts = append(opts, WithEmail())
|
||||
}
|
||||
if phoneWM {
|
||||
opts = append(opts, WithPhone())
|
||||
}
|
||||
if passwordWM {
|
||||
opts = append(opts, WithPassword())
|
||||
}
|
||||
if avatarWM {
|
||||
opts = append(opts, WithAvatar())
|
||||
}
|
||||
if idpLinks {
|
||||
opts = append(opts, WithIDPLinks())
|
||||
}
|
||||
return newUserV2WriteModel(userID, resourceOwner, opts...)
|
||||
}
|
||||
|
||||
func newUserV2WriteModel(userID, resourceOwner string, opts ...UserV2WMOption) *UserV2WriteModel {
|
||||
wm := &UserV2WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
|
||||
for _, optFunc := range opts {
|
||||
optFunc(wm)
|
||||
}
|
||||
return wm
|
||||
}
|
||||
|
||||
type UserV2WMOption func(o *UserV2WriteModel)
|
||||
|
||||
func WithHuman() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.HumanWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithMachine() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.MachineWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithProfile() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.ProfileWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithEmail() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.EmailWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithPhone() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.PhoneWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithPassword() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.PasswordWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithState() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.StateWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithAvatar() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.AvatarWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithIDPLinks() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.IDPLinkWriteModel = true
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanAddedEvent:
|
||||
wm.reduceHumanAddedEvent(e)
|
||||
case *user.HumanRegisteredEvent:
|
||||
wm.reduceHumanRegisteredEvent(e)
|
||||
|
||||
case *user.HumanInitialCodeAddedEvent:
|
||||
wm.UserState = domain.UserStateInitial
|
||||
wm.SetInitCode(e.Code, e.Expiry, e.CreationDate())
|
||||
case *user.HumanInitializedCheckSucceededEvent:
|
||||
wm.UserState = domain.UserStateActive
|
||||
wm.EmptyInitCode()
|
||||
case *user.HumanInitializedCheckFailedEvent:
|
||||
wm.InitCheckFailedCount += 1
|
||||
|
||||
case *user.UsernameChangedEvent:
|
||||
wm.UserName = e.UserName
|
||||
case *user.HumanProfileChangedEvent:
|
||||
wm.reduceHumanProfileChangedEvent(e)
|
||||
|
||||
case *user.MachineChangedEvent:
|
||||
if e.Name != nil {
|
||||
wm.Name = *e.Name
|
||||
}
|
||||
if e.Description != nil {
|
||||
wm.Description = *e.Description
|
||||
}
|
||||
if e.AccessTokenType != nil {
|
||||
wm.AccessTokenType = *e.AccessTokenType
|
||||
}
|
||||
|
||||
case *user.MachineAddedEvent:
|
||||
wm.UserName = e.UserName
|
||||
wm.Name = e.Name
|
||||
wm.Description = e.Description
|
||||
wm.AccessTokenType = e.AccessTokenType
|
||||
wm.UserState = domain.UserStateActive
|
||||
|
||||
case *user.HumanEmailChangedEvent:
|
||||
wm.Email = e.EmailAddress
|
||||
wm.IsEmailVerified = false
|
||||
wm.EmptyEmailCode()
|
||||
case *user.HumanEmailCodeAddedEvent:
|
||||
wm.IsEmailVerified = false
|
||||
wm.SetEMailCode(e.Code, e.Expiry, e.CreationDate())
|
||||
case *user.HumanEmailVerifiedEvent:
|
||||
wm.IsEmailVerified = true
|
||||
wm.EmptyEmailCode()
|
||||
case *user.HumanEmailVerificationFailedEvent:
|
||||
wm.EmailCheckFailedCount += 1
|
||||
|
||||
case *user.HumanPhoneChangedEvent:
|
||||
wm.IsPhoneVerified = false
|
||||
wm.Phone = e.PhoneNumber
|
||||
wm.EmptyPhoneCode()
|
||||
case *user.HumanPhoneCodeAddedEvent:
|
||||
wm.IsPhoneVerified = false
|
||||
wm.SetPhoneCode(e.Code, e.Expiry, e.CreationDate())
|
||||
case *user.HumanPhoneVerifiedEvent:
|
||||
wm.IsPhoneVerified = true
|
||||
wm.EmptyPhoneCode()
|
||||
case *user.HumanPhoneVerificationFailedEvent:
|
||||
wm.PhoneCheckFailedCount += 1
|
||||
case *user.HumanPhoneRemovedEvent:
|
||||
wm.EmptyPhoneCode()
|
||||
wm.Phone = ""
|
||||
wm.IsPhoneVerified = false
|
||||
|
||||
case *user.HumanAvatarAddedEvent:
|
||||
wm.Avatar = e.StoreKey
|
||||
case *user.HumanAvatarRemovedEvent:
|
||||
wm.Avatar = ""
|
||||
|
||||
case *user.UserLockedEvent:
|
||||
wm.UserState = domain.UserStateLocked
|
||||
case *user.UserUnlockedEvent:
|
||||
wm.PasswordCheckFailedCount = 0
|
||||
wm.UserState = domain.UserStateActive
|
||||
|
||||
case *user.UserDeactivatedEvent:
|
||||
wm.UserState = domain.UserStateInactive
|
||||
case *user.UserReactivatedEvent:
|
||||
wm.UserState = domain.UserStateActive
|
||||
|
||||
case *user.UserRemovedEvent:
|
||||
wm.UserState = domain.UserStateDeleted
|
||||
|
||||
case *user.HumanPasswordHashUpdatedEvent:
|
||||
wm.PasswordEncodedHash = e.EncodedHash
|
||||
case *user.HumanPasswordCheckFailedEvent:
|
||||
wm.PasswordCheckFailedCount += 1
|
||||
case *user.HumanPasswordCheckSucceededEvent:
|
||||
wm.PasswordCheckFailedCount = 0
|
||||
case *user.HumanPasswordChangedEvent:
|
||||
wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
||||
wm.PasswordChangeRequired = e.ChangeRequired
|
||||
wm.EmptyPasswordCode()
|
||||
case *user.HumanPasswordCodeAddedEvent:
|
||||
wm.SetPasswordCode(e.Code, e.Expiry, e.CreationDate())
|
||||
case *user.UserIDPLinkAddedEvent:
|
||||
wm.AddIDPLink(e.IDPConfigID, e.DisplayName, e.ExternalUserID)
|
||||
case *user.UserIDPLinkRemovedEvent:
|
||||
wm.RemoveIDPLink(e.IDPConfigID, e.ExternalUserID)
|
||||
case *user.UserIDPLinkCascadeRemovedEvent:
|
||||
wm.RemoveIDPLink(e.IDPConfigID, e.ExternalUserID)
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) AddIDPLink(configID, displayName, externalUserID string) {
|
||||
wm.IDPLinks = append(wm.IDPLinks, &domain.UserIDPLink{IDPConfigID: configID, DisplayName: displayName, ExternalUserID: externalUserID})
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) RemoveIDPLink(configID, externalUserID string) {
|
||||
idx, _ := wm.IDPLinkByID(configID, externalUserID)
|
||||
if idx < 0 {
|
||||
return
|
||||
}
|
||||
copy(wm.IDPLinks[idx:], wm.IDPLinks[idx+1:])
|
||||
wm.IDPLinks[len(wm.IDPLinks)-1] = nil
|
||||
wm.IDPLinks = wm.IDPLinks[:len(wm.IDPLinks)-1]
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) EmptyInitCode() {
|
||||
wm.InitCode = nil
|
||||
wm.InitCodeExpiry = 0
|
||||
wm.InitCodeCreationDate = time.Time{}
|
||||
wm.InitCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) SetInitCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
|
||||
wm.InitCode = code
|
||||
wm.InitCodeExpiry = expiry
|
||||
wm.InitCodeCreationDate = creationDate
|
||||
wm.InitCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) EmptyEmailCode() {
|
||||
wm.EmailCode = nil
|
||||
wm.EmailCodeExpiry = 0
|
||||
wm.EmailCodeCreationDate = time.Time{}
|
||||
wm.EmailCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) SetEMailCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
|
||||
wm.EmailCode = code
|
||||
wm.EmailCodeExpiry = expiry
|
||||
wm.EmailCodeCreationDate = creationDate
|
||||
wm.EmailCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) EmptyPhoneCode() {
|
||||
wm.PhoneCode = nil
|
||||
wm.PhoneCodeExpiry = 0
|
||||
wm.PhoneCodeCreationDate = time.Time{}
|
||||
wm.PhoneCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) SetPhoneCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
|
||||
wm.PhoneCode = code
|
||||
wm.PhoneCodeExpiry = expiry
|
||||
wm.PhoneCodeCreationDate = creationDate
|
||||
wm.PhoneCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) EmptyPasswordCode() {
|
||||
wm.PasswordCode = nil
|
||||
wm.PasswordCodeExpiry = 0
|
||||
wm.PasswordCodeCreationDate = time.Time{}
|
||||
}
|
||||
func (wm *UserV2WriteModel) SetPasswordCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
|
||||
wm.PasswordCode = code
|
||||
wm.PasswordCodeExpiry = expiry
|
||||
wm.PasswordCodeCreationDate = creationDate
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
// remove events are always processed
|
||||
// and username is based for machine and human
|
||||
eventTypes := []eventstore.EventType{
|
||||
user.UserRemovedType,
|
||||
user.UserUserNameChangedType,
|
||||
}
|
||||
|
||||
if wm.HumanWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1AddedType,
|
||||
user.HumanAddedType,
|
||||
user.UserV1RegisteredType,
|
||||
user.HumanRegisteredType,
|
||||
)
|
||||
}
|
||||
|
||||
if wm.MachineWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.MachineChangedEventType,
|
||||
user.MachineAddedEventType,
|
||||
)
|
||||
}
|
||||
|
||||
if wm.EmailWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1EmailChangedType,
|
||||
user.HumanEmailChangedType,
|
||||
user.UserV1EmailCodeAddedType,
|
||||
user.HumanEmailCodeAddedType,
|
||||
|
||||
user.UserV1EmailVerifiedType,
|
||||
user.HumanEmailVerifiedType,
|
||||
user.HumanEmailVerificationFailedType,
|
||||
user.UserV1EmailVerificationFailedType,
|
||||
)
|
||||
}
|
||||
if wm.PhoneWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1PhoneChangedType,
|
||||
user.HumanPhoneChangedType,
|
||||
user.UserV1PhoneCodeAddedType,
|
||||
user.HumanPhoneCodeAddedType,
|
||||
|
||||
user.UserV1PhoneVerifiedType,
|
||||
user.HumanPhoneVerifiedType,
|
||||
user.HumanPhoneVerificationFailedType,
|
||||
user.UserV1PhoneVerificationFailedType,
|
||||
|
||||
user.UserV1PhoneRemovedType,
|
||||
user.HumanPhoneRemovedType,
|
||||
)
|
||||
}
|
||||
if wm.ProfileWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1ProfileChangedType,
|
||||
user.HumanProfileChangedType,
|
||||
)
|
||||
}
|
||||
if wm.StateWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1InitialCodeAddedType,
|
||||
user.HumanInitialCodeAddedType,
|
||||
|
||||
user.UserV1InitializedCheckSucceededType,
|
||||
user.HumanInitializedCheckSucceededType,
|
||||
user.HumanInitializedCheckFailedType,
|
||||
user.UserV1InitializedCheckFailedType,
|
||||
|
||||
user.UserLockedType,
|
||||
user.UserUnlockedType,
|
||||
user.UserDeactivatedType,
|
||||
user.UserReactivatedType,
|
||||
)
|
||||
}
|
||||
if wm.AvatarWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.HumanAvatarAddedType,
|
||||
user.HumanAvatarRemovedType,
|
||||
)
|
||||
}
|
||||
if wm.PasswordWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.HumanPasswordHashUpdatedType,
|
||||
|
||||
user.HumanPasswordChangedType,
|
||||
user.UserV1PasswordChangedType,
|
||||
user.HumanPasswordCodeAddedType,
|
||||
user.UserV1PasswordCodeAddedType,
|
||||
|
||||
user.HumanPasswordCheckFailedType,
|
||||
user.UserV1PasswordCheckFailedType,
|
||||
user.HumanPasswordCheckSucceededType,
|
||||
user.UserV1PasswordCheckSucceededType,
|
||||
)
|
||||
}
|
||||
if wm.IDPLinkWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserIDPLinkAddedType,
|
||||
user.UserIDPLinkRemovedType,
|
||||
user.UserIDPLinkCascadeRemovedType,
|
||||
)
|
||||
}
|
||||
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(eventTypes...).
|
||||
Builder()
|
||||
if wm.ResourceOwner != "" {
|
||||
query.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) reduceHumanAddedEvent(e *user.HumanAddedEvent) {
|
||||
wm.UserName = e.UserName
|
||||
wm.FirstName = e.FirstName
|
||||
wm.LastName = e.LastName
|
||||
wm.NickName = e.NickName
|
||||
wm.DisplayName = e.DisplayName
|
||||
wm.PreferredLanguage = e.PreferredLanguage
|
||||
wm.Gender = e.Gender
|
||||
wm.Email = e.EmailAddress
|
||||
wm.Phone = e.PhoneNumber
|
||||
wm.UserState = domain.UserStateActive
|
||||
wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
||||
wm.PasswordChangeRequired = e.ChangeRequired
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) reduceHumanRegisteredEvent(e *user.HumanRegisteredEvent) {
|
||||
wm.UserName = e.UserName
|
||||
wm.FirstName = e.FirstName
|
||||
wm.LastName = e.LastName
|
||||
wm.NickName = e.NickName
|
||||
wm.DisplayName = e.DisplayName
|
||||
wm.PreferredLanguage = e.PreferredLanguage
|
||||
wm.Gender = e.Gender
|
||||
wm.Email = e.EmailAddress
|
||||
wm.Phone = e.PhoneNumber
|
||||
wm.UserState = domain.UserStateActive
|
||||
wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
||||
wm.PasswordChangeRequired = e.ChangeRequired
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) reduceHumanProfileChangedEvent(e *user.HumanProfileChangedEvent) {
|
||||
if e.FirstName != "" {
|
||||
wm.FirstName = e.FirstName
|
||||
}
|
||||
if e.LastName != "" {
|
||||
wm.LastName = e.LastName
|
||||
}
|
||||
if e.NickName != nil {
|
||||
wm.NickName = *e.NickName
|
||||
}
|
||||
if e.DisplayName != nil {
|
||||
wm.DisplayName = *e.DisplayName
|
||||
}
|
||||
if e.PreferredLanguage != nil {
|
||||
wm.PreferredLanguage = *e.PreferredLanguage
|
||||
}
|
||||
if e.Gender != nil {
|
||||
wm.Gender = *e.Gender
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) Aggregate() *user.Aggregate {
|
||||
return user.NewAggregate(wm.AggregateID, wm.ResourceOwner)
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) NewProfileChangedEvent(
|
||||
ctx context.Context,
|
||||
firstName,
|
||||
lastName,
|
||||
nickName,
|
||||
displayName *string,
|
||||
preferredLanguage *language.Tag,
|
||||
gender *domain.Gender,
|
||||
) (*user.HumanProfileChangedEvent, error) {
|
||||
changes := make([]user.ProfileChanges, 0)
|
||||
if firstName != nil && wm.FirstName != *firstName {
|
||||
changes = append(changes, user.ChangeFirstName(*firstName))
|
||||
}
|
||||
if lastName != nil && wm.LastName != *lastName {
|
||||
changes = append(changes, user.ChangeLastName(*lastName))
|
||||
}
|
||||
if nickName != nil && wm.NickName != *nickName {
|
||||
changes = append(changes, user.ChangeNickName(*nickName))
|
||||
}
|
||||
if displayName != nil && wm.DisplayName != *displayName {
|
||||
changes = append(changes, user.ChangeDisplayName(*displayName))
|
||||
}
|
||||
if preferredLanguage != nil && wm.PreferredLanguage != *preferredLanguage {
|
||||
changes = append(changes, user.ChangePreferredLanguage(*preferredLanguage))
|
||||
}
|
||||
if gender != nil && wm.Gender != *gender {
|
||||
changes = append(changes, user.ChangeGender(*gender))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return user.NewHumanProfileChangedEvent(ctx, &wm.Aggregate().Aggregate, changes)
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) IDPLinkByID(idpID, externalUserID string) (idx int, idp *domain.UserIDPLink) {
|
||||
for idx, idp = range wm.IDPLinks {
|
||||
if idp.IDPConfigID == idpID && idp.ExternalUserID == externalUserID {
|
||||
return idx, idp
|
||||
}
|
||||
}
|
||||
return -1, nil
|
||||
}
|
2386
internal/command/user_v2_model_test.go
Normal file
2386
internal/command/user_v2_model_test.go
Normal file
File diff suppressed because it is too large
Load Diff
1413
internal/command/user_v2_test.go
Normal file
1413
internal/command/user_v2_test.go
Normal file
File diff suppressed because it is too large
Load Diff
37
internal/command/user_v2_username.go
Normal file
37
internal/command/user_v2_username.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) changeUsername(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, userName string) ([]eventstore.Command, error) {
|
||||
if wm.UserName == userName {
|
||||
return cmds, nil
|
||||
}
|
||||
orgID := wm.ResourceOwner
|
||||
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, orgID)
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
return append(cmds,
|
||||
user.NewUsernameChangedEvent(ctx, &wm.Aggregate().Aggregate, wm.UserName, userName, domainPolicy.UserLoginMustBeDomain),
|
||||
), nil
|
||||
}
|
Reference in New Issue
Block a user