mirror of
https://github.com/zitadel/zitadel.git
synced 2025-07-15 21:11:09 +00:00

# Which Problems Are Solved This pull request addresses a significant gap in the user service v2 API, which currently lacks methods for managing machine users. # How the Problems Are Solved This PR adds new API endpoints to the user service v2 to manage machine users including their secret, keys and personal access tokens. Additionally, there's now a CreateUser and UpdateUser endpoints which allow to create either a human or machine user and update them. The existing `CreateHumanUser` endpoint has been deprecated along the corresponding management service endpoints. For details check the additional context section. # Additional Context - Closes https://github.com/zitadel/zitadel/issues/9349 ## More details - API changes: https://github.com/zitadel/zitadel/pull/9680 - Implementation: https://github.com/zitadel/zitadel/pull/9763 - Tests: https://github.com/zitadel/zitadel/pull/9771 ## Follow-ups - Metadata: support managing user metadata using resource API https://github.com/zitadel/zitadel/pull/10005 - Machine token type: support managing the machine token type (migrate to new enum with zero value unspecified?) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Livio Spring <livio.a@gmail.com>
610 lines
17 KiB
Go
610 lines
17 KiB
Go
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
|
|
|
|
CreationDate time.Time
|
|
|
|
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
|
|
PasswordCodeGeneratorID string
|
|
PasswordCodeVerificationID string
|
|
|
|
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
|
|
|
|
MetadataWriteModel bool
|
|
Metadata map[string][]byte
|
|
}
|
|
|
|
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, metadataListWM 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())
|
|
}
|
|
if metadataListWM {
|
|
opts = append(opts, WithMetadata())
|
|
}
|
|
return newUserV2WriteModel(userID, resourceOwner, opts...)
|
|
}
|
|
|
|
func NewUserMachineWriteModel(userID, resourceOwner string, metadataListWM bool) *UserV2WriteModel {
|
|
opts := []UserV2WMOption{WithMachine(), WithState()}
|
|
if metadataListWM {
|
|
opts = append(opts, WithMetadata())
|
|
}
|
|
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 WithMetadata() UserV2WMOption {
|
|
return func(o *UserV2WriteModel) {
|
|
o.MetadataWriteModel = 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 = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
|
wm.PasswordChangeRequired = e.ChangeRequired
|
|
wm.EmptyPasswordCode()
|
|
case *user.HumanPasswordCodeAddedEvent:
|
|
wm.SetPasswordCode(e)
|
|
case *user.HumanPasswordCodeSentEvent:
|
|
wm.SetPasswordCodeSent(e)
|
|
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)
|
|
case *user.MetadataSetEvent:
|
|
if wm.Metadata == nil {
|
|
wm.Metadata = make(map[string][]byte)
|
|
}
|
|
|
|
wm.Metadata[e.Key] = e.Value
|
|
case *user.MetadataRemovedEvent:
|
|
wm.Metadata[e.Key] = nil
|
|
delete(wm.Metadata, e.Key)
|
|
case *user.MetadataRemovedAllEvent:
|
|
wm.Metadata = nil
|
|
}
|
|
}
|
|
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(e *user.HumanPasswordCodeAddedEvent) {
|
|
wm.PasswordCode = e.Code
|
|
wm.PasswordCodeExpiry = e.Expiry
|
|
wm.PasswordCodeCreationDate = e.CreationDate()
|
|
wm.PasswordCodeGeneratorID = e.GeneratorID
|
|
}
|
|
|
|
func (wm *UserV2WriteModel) SetPasswordCodeSent(e *user.HumanPasswordCodeSentEvent) {
|
|
wm.PasswordCodeGeneratorID = e.GeneratorInfo.GetID()
|
|
wm.PasswordCodeVerificationID = e.GeneratorInfo.GetVerificationID()
|
|
}
|
|
|
|
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,
|
|
)
|
|
}
|
|
|
|
if wm.MetadataWriteModel {
|
|
eventTypes = append(eventTypes,
|
|
user.MetadataSetType,
|
|
user.MetadataRemovedType,
|
|
user.MetadataRemovedAllType)
|
|
}
|
|
|
|
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 = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
|
wm.PasswordChangeRequired = e.ChangeRequired
|
|
wm.CreationDate = e.Creation
|
|
}
|
|
|
|
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 = crypto.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
|
|
}
|