mirror of
				https://github.com/zitadel/zitadel.git
				synced 2025-10-27 14:30:28 +00:00 
			
		
		
		
	fix(login): improve auth handlers (#7969)
# Which Problems Are Solved During the implementation of #7486 it was noticed, that projections in the `auth` database schema could be blocked. Investigations suggested, that this is due to the use of [GORM](https://gorm.io/index.html) and it's inability to use an existing (sql) transaction. With the improved / simplified handling (see below) there should also be a minimal improvement in performance, resp. reduced database update statements. # How the Problems Are Solved The handlers in `auth` are exchanged to proper (sql) statements and gorm usage is removed for any writing part. To further improve / simplify the handling of the users, a new `auth.users3` table is created, where only attributes are handled, which are not yet available from the `projections.users`, `projections.login_name` and `projections.user_auth_methods` do not provide. This reduces the events handled in that specific handler by a lot. # Additional Changes None # Additional Context relates to #7486
This commit is contained in:
		| @@ -13,13 +13,24 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	RefreshTokenKeyTokenID       = "id" | ||||
| 	RefreshTokenKeyUserID        = "user_id" | ||||
| 	RefreshTokenKeyApplicationID = "application_id" | ||||
| 	RefreshTokenKeyUserAgentID   = "user_agent_id" | ||||
| 	RefreshTokenKeyExpiration    = "expiration" | ||||
| 	RefreshTokenKeyResourceOwner = "resource_owner" | ||||
| 	RefreshTokenKeyInstanceID    = "instance_id" | ||||
| 	RefreshTokenKeyTokenID        = "id" | ||||
| 	RefreshTokenKeyUserID         = "user_id" | ||||
| 	RefreshTokenKeyApplicationID  = "application_id" | ||||
| 	RefreshTokenKeyUserAgentID    = "user_agent_id" | ||||
| 	RefreshTokenKeyExpiration     = "expiration" | ||||
| 	RefreshTokenKeyResourceOwner  = "resource_owner" | ||||
| 	RefreshTokenKeyInstanceID     = "instance_id" | ||||
| 	RefreshTokenKeyCreationDate   = "creation_date" | ||||
| 	RefreshTokenKeyChangeDate     = "change_date" | ||||
| 	RefreshTokenKeySequence       = "sequence" | ||||
| 	RefreshTokenKeyActor          = "actor" | ||||
| 	RefreshTokenKeyAMR            = "amr" | ||||
| 	RefreshTokenKeyAuthTime       = "auth_time" | ||||
| 	RefreshTokenKeyAudience       = "audience" | ||||
| 	RefreshTokenKeyClientID       = "client_id" | ||||
| 	RefreshTokenKeyIdleExpiration = "idle_expiration" | ||||
| 	RefreshTokenKeyScopes         = "scopes" | ||||
| 	RefreshTokenKeyToken          = "token" | ||||
| ) | ||||
|  | ||||
| type RefreshTokenView struct { | ||||
| @@ -72,6 +83,7 @@ func RefreshTokenViewToModel(token *RefreshTokenView) *usr_model.RefreshTokenVie | ||||
| } | ||||
|  | ||||
| func (t *RefreshTokenView) AppendEventIfMyRefreshToken(event eventstore.Event) (err error) { | ||||
| 	// in case anything needs to be change here check if the Reduce function needs the change as well | ||||
| 	view := new(RefreshTokenView) | ||||
| 	switch event.Type() { | ||||
| 	case user_repo.HumanRefreshTokenAddedType: | ||||
| @@ -101,6 +113,7 @@ func (t *RefreshTokenView) AppendEventIfMyRefreshToken(event eventstore.Event) ( | ||||
| } | ||||
|  | ||||
| func (t *RefreshTokenView) AppendEvent(event eventstore.Event) error { | ||||
| 	// in case anything needs to be change here check if the Reduce function needs the change as well | ||||
| 	t.ChangeDate = event.CreatedAt() | ||||
| 	t.Sequence = event.Sequence() | ||||
| 	switch event.Type() { | ||||
| @@ -123,7 +136,7 @@ func (t *RefreshTokenView) setRootData(event eventstore.Event) { | ||||
| func (t *RefreshTokenView) appendAddedEvent(event eventstore.Event) error { | ||||
| 	e := new(user_repo.HumanRefreshTokenAddedEvent) | ||||
| 	if err := event.Unmarshal(e); err != nil { | ||||
| 		logging.Log("EVEN-Dbb31").WithError(err).Error("could not unmarshal event data") | ||||
| 		logging.WithError(err).Error("could not unmarshal event data") | ||||
| 		return zerrors.ThrowInternal(err, "MODEL-Bbr42", "could not unmarshal event") | ||||
| 	} | ||||
| 	t.ID = e.TokenID | ||||
| @@ -144,7 +157,7 @@ func (t *RefreshTokenView) appendAddedEvent(event eventstore.Event) error { | ||||
| func (t *RefreshTokenView) appendRenewedEvent(event eventstore.Event) error { | ||||
| 	e := new(user_repo.HumanRefreshTokenRenewedEvent) | ||||
| 	if err := event.Unmarshal(e); err != nil { | ||||
| 		logging.Log("EVEN-Vbbn2").WithError(err).Error("could not unmarshal event data") | ||||
| 		logging.WithError(err).Error("could not unmarshal event data") | ||||
| 		return zerrors.ThrowInternal(err, "MODEL-Bbrn4", "could not unmarshal event") | ||||
| 	} | ||||
| 	t.ID = e.TokenID | ||||
|   | ||||
| @@ -16,14 +16,23 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	TokenKeyTokenID        = "id" | ||||
| 	TokenKeyUserID         = "user_id" | ||||
| 	TokenKeyRefreshTokenID = "refresh_token_id" | ||||
| 	TokenKeyApplicationID  = "application_id" | ||||
| 	TokenKeyUserAgentID    = "user_agent_id" | ||||
| 	TokenKeyExpiration     = "expiration" | ||||
| 	TokenKeyResourceOwner  = "resource_owner" | ||||
| 	TokenKeyInstanceID     = "instance_id" | ||||
| 	TokenKeyTokenID           = "id" | ||||
| 	TokenKeyUserID            = "user_id" | ||||
| 	TokenKeyRefreshTokenID    = "refresh_token_id" | ||||
| 	TokenKeyApplicationID     = "application_id" | ||||
| 	TokenKeyUserAgentID       = "user_agent_id" | ||||
| 	TokenKeyExpiration        = "expiration" | ||||
| 	TokenKeyResourceOwner     = "resource_owner" | ||||
| 	TokenKeyInstanceID        = "instance_id" | ||||
| 	TokenKeyCreationDate      = "creation_date" | ||||
| 	TokenKeyChangeDate        = "change_date" | ||||
| 	TokenKeySequence          = "sequence" | ||||
| 	TokenKeyActor             = "actor" | ||||
| 	TokenKeyID                = "id" | ||||
| 	TokenKeyAudience          = "audience" | ||||
| 	TokenKeyPreferredLanguage = "preferred_language" | ||||
| 	TokenKeyScopes            = "scopes" | ||||
| 	TokenKeyIsPat             = "is_pat" | ||||
| ) | ||||
|  | ||||
| type TokenView struct { | ||||
| @@ -100,6 +109,7 @@ func TokenViewToModel(token *TokenView) *usr_model.TokenView { | ||||
| } | ||||
|  | ||||
| func (t *TokenView) AppendEventIfMyToken(event eventstore.Event) (err error) { | ||||
| 	// in case anything needs to be change here check if the Reduce function needs the change as well | ||||
| 	view := new(TokenView) | ||||
| 	switch event.Type() { | ||||
| 	case user_repo.UserTokenAddedType, | ||||
| @@ -112,7 +122,7 @@ func (t *TokenView) AppendEventIfMyToken(event eventstore.Event) (err error) { | ||||
| 		return t.appendRefreshTokenRemoved(event) | ||||
| 	case user_repo.UserV1SignedOutType, | ||||
| 		user_repo.HumanSignedOutType: | ||||
| 		id, err := agentIDFromSession(event) | ||||
| 		id, err := UserAgentIDFromEvent(event) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -146,6 +156,7 @@ func (t *TokenView) AppendEventIfMyToken(event eventstore.Event) (err error) { | ||||
| } | ||||
|  | ||||
| func (t *TokenView) AppendEvent(event eventstore.Event) error { | ||||
| 	// in case anything needs to be change here check if the Reduce function needs the change as well | ||||
| 	t.ChangeDate = event.CreatedAt() | ||||
| 	t.Sequence = event.Sequence() | ||||
| 	switch event.Type() { | ||||
| @@ -170,49 +181,40 @@ func (t *TokenView) setRootData(event eventstore.Event) { | ||||
|  | ||||
| func (t *TokenView) setData(event eventstore.Event) error { | ||||
| 	if err := event.Unmarshal(t); err != nil { | ||||
| 		logging.Log("EVEN-3Gm9s").WithError(err).Error("could not unmarshal event data") | ||||
| 		logging.WithError(err).Error("could not unmarshal event data") | ||||
| 		return zerrors.ThrowInternal(err, "MODEL-5Gms9", "could not unmarshal event") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func agentIDFromSession(event eventstore.Event) (string, error) { | ||||
| 	session := make(map[string]interface{}) | ||||
| 	if err := event.Unmarshal(&session); err != nil { | ||||
| 		logging.Log("EVEN-Ghgt3").WithError(err).Error("could not unmarshal event data") | ||||
| 		return "", zerrors.ThrowInternal(nil, "MODEL-GBf32", "could not unmarshal data") | ||||
| 	} | ||||
| 	return session["userAgentID"].(string), nil | ||||
| } | ||||
|  | ||||
| func (t *TokenView) appendTokenRemoved(event eventstore.Event) error { | ||||
| 	token, err := eventToMap(event) | ||||
| 	tokenID, err := tokenIDFromEvent(event) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if token["tokenId"] == t.ID { | ||||
| 	if tokenID == t.ID { | ||||
| 		t.Deactivated = true | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (t *TokenView) appendRefreshTokenRemoved(event eventstore.Event) error { | ||||
| 	refreshToken, err := eventToMap(event) | ||||
| 	tokenID, err := tokenIDFromEvent(event) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if refreshToken["tokenId"] == t.RefreshTokenID { | ||||
| 	if tokenID == t.RefreshTokenID { | ||||
| 		t.Deactivated = true | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (t *TokenView) appendPATRemoved(event eventstore.Event) error { | ||||
| 	pat, err := eventToMap(event) | ||||
| 	tokenID, err := tokenIDFromEvent(event) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if pat["tokenId"] == t.ID && t.IsPAT { | ||||
| 	if tokenID == t.ID && t.IsPAT { | ||||
| 		t.Deactivated = true | ||||
| 	} | ||||
| 	return nil | ||||
| @@ -235,11 +237,15 @@ func (t *TokenView) GetRelevantEventTypes() []eventstore.EventType { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func eventToMap(event eventstore.Event) (map[string]interface{}, error) { | ||||
| 	m := make(map[string]interface{}) | ||||
| 	if err := event.Unmarshal(&m); err != nil { | ||||
| 		logging.Log("EVEN-Dbffe").WithError(err).Error("could not unmarshal event data") | ||||
| 		return nil, zerrors.ThrowInternal(nil, "MODEL-SDAfw", "could not unmarshal data") | ||||
| 	} | ||||
| 	return m, nil | ||||
| type tokenIDPayload struct { | ||||
| 	ID string `json:"tokenId"` | ||||
| } | ||||
|  | ||||
| func tokenIDFromEvent(event eventstore.Event) (string, error) { | ||||
| 	m := new(tokenIDPayload) | ||||
| 	if err := event.Unmarshal(&m); err != nil { | ||||
| 		logging.WithError(err).Error("could not unmarshal event data") | ||||
| 		return "", zerrors.ThrowInternal(nil, "MODEL-SDAfw", "could not unmarshal data") | ||||
| 	} | ||||
| 	return m.ID, nil | ||||
| } | ||||
|   | ||||
| @@ -18,27 +18,27 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	UserKeyUserID             = "id" | ||||
| 	UserKeyUserName           = "user_name" | ||||
| 	UserKeyFirstName          = "first_name" | ||||
| 	UserKeyLastName           = "last_name" | ||||
| 	UserKeyNickName           = "nick_name" | ||||
| 	UserKeyDisplayName        = "display_name" | ||||
| 	UserKeyEmail              = "email" | ||||
| 	UserKeyState              = "user_state" | ||||
| 	UserKeyResourceOwner      = "resource_owner" | ||||
| 	UserKeyLoginNames         = "login_names" | ||||
| 	UserKeyPreferredLoginName = "preferred_login_name" | ||||
| 	UserKeyType               = "user_type" | ||||
| 	UserKeyInstanceID         = "instance_id" | ||||
| 	UserKeyOwnerRemoved       = "owner_removed" | ||||
| ) | ||||
|  | ||||
| type userType string | ||||
|  | ||||
| const ( | ||||
| 	userTypeHuman   = "human" | ||||
| 	userTypeMachine = "machine" | ||||
| 	UserKeyUserID                   = "id" | ||||
| 	UserKeyUserName                 = "user_name" | ||||
| 	UserKeyFirstName                = "first_name" | ||||
| 	UserKeyLastName                 = "last_name" | ||||
| 	UserKeyNickName                 = "nick_name" | ||||
| 	UserKeyDisplayName              = "display_name" | ||||
| 	UserKeyEmail                    = "email" | ||||
| 	UserKeyState                    = "user_state" | ||||
| 	UserKeyResourceOwner            = "resource_owner" | ||||
| 	UserKeyLoginNames               = "login_names" | ||||
| 	UserKeyPreferredLoginName       = "preferred_login_name" | ||||
| 	UserKeyType                     = "user_type" | ||||
| 	UserKeyInstanceID               = "instance_id" | ||||
| 	UserKeyOwnerRemoved             = "owner_removed" | ||||
| 	UserKeyPasswordSet              = "password_set" | ||||
| 	UserKeyPasswordInitRequired     = "password_init_required" | ||||
| 	UserKeyPasswordChange           = "password_change" | ||||
| 	UserKeyInitRequired             = "init_required" | ||||
| 	UserKeyPasswordlessInitRequired = "passwordless_init_required" | ||||
| 	UserKeyMFAInitSkipped           = "mfa_init_skipped" | ||||
| 	UserKeyChangeDate               = "change_date" | ||||
| ) | ||||
|  | ||||
| type UserView struct { | ||||
| @@ -51,7 +51,6 @@ type UserView struct { | ||||
| 	LoginNames         database.TextArray[string] `json:"-" gorm:"column:login_names"` | ||||
| 	PreferredLoginName string                     `json:"-" gorm:"column:preferred_login_name"` | ||||
| 	Sequence           uint64                     `json:"-" gorm:"column:sequence"` | ||||
| 	Type               userType                   `json:"-" gorm:"column:user_type"` | ||||
| 	UserName           string                     `json:"userName" gorm:"column:user_name"` | ||||
| 	InstanceID         string                     `json:"instanceID" gorm:"column:instance_id;primary_key"` | ||||
| 	*MachineView | ||||
| @@ -236,13 +235,13 @@ func (u *UserView) SetLoginNames(userLoginMustBeDomain bool, domains []*org_mode | ||||
| } | ||||
|  | ||||
| func (u *UserView) AppendEvent(event eventstore.Event) (err error) { | ||||
| 	// in case anything needs to be change here check if the Reduce function needs the change as well | ||||
| 	u.ChangeDate = event.CreatedAt() | ||||
| 	u.Sequence = event.Sequence() | ||||
| 	switch event.Type() { | ||||
| 	case user.MachineAddedEventType: | ||||
| 		u.CreationDate = event.CreatedAt() | ||||
| 		u.setRootData(event) | ||||
| 		u.Type = userTypeMachine | ||||
| 		err = u.setData(event) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| @@ -253,7 +252,6 @@ func (u *UserView) AppendEvent(event eventstore.Event) (err error) { | ||||
| 		user.HumanAddedType: | ||||
| 		u.CreationDate = event.CreatedAt() | ||||
| 		u.setRootData(event) | ||||
| 		u.Type = userTypeHuman | ||||
| 		err = u.setData(event) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| @@ -396,7 +394,7 @@ func (u *UserView) setData(event eventstore.Event) error { | ||||
| func (u *UserView) setPasswordData(event eventstore.Event) error { | ||||
| 	password := new(es_model.Password) | ||||
| 	if err := event.Unmarshal(password); err != nil { | ||||
| 		logging.Log("MODEL-sdw4r").WithError(err).Error("could not unmarshal event data") | ||||
| 		logging.WithError(err).Error("could not unmarshal event data") | ||||
| 		return zerrors.ThrowInternal(nil, "MODEL-6jhsw", "could not unmarshal data") | ||||
| 	} | ||||
| 	u.PasswordSet = password.Secret != nil || password.EncodedHash != "" | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/zitadel/logging" | ||||
| @@ -14,12 +15,23 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	UserSessionKeyUserAgentID   = "user_agent_id" | ||||
| 	UserSessionKeyUserID        = "user_id" | ||||
| 	UserSessionKeyState         = "state" | ||||
| 	UserSessionKeyResourceOwner = "resource_owner" | ||||
| 	UserSessionKeyInstanceID    = "instance_id" | ||||
| 	UserSessionKeyOwnerRemoved  = "owner_removed" | ||||
| 	UserSessionKeyUserAgentID                  = "user_agent_id" | ||||
| 	UserSessionKeyUserID                       = "user_id" | ||||
| 	UserSessionKeyState                        = "state" | ||||
| 	UserSessionKeyResourceOwner                = "resource_owner" | ||||
| 	UserSessionKeyInstanceID                   = "instance_id" | ||||
| 	UserSessionKeyOwnerRemoved                 = "owner_removed" | ||||
| 	UserSessionKeyCreationDate                 = "creation_date" | ||||
| 	UserSessionKeyChangeDate                   = "change_date" | ||||
| 	UserSessionKeySequence                     = "sequence" | ||||
| 	UserSessionKeyPasswordVerification         = "password_verification" | ||||
| 	UserSessionKeySecondFactorVerification     = "second_factor_verification" | ||||
| 	UserSessionKeySecondFactorVerificationType = "second_factor_verification_type" | ||||
| 	UserSessionKeyMultiFactorVerification      = "multi_factor_verification" | ||||
| 	UserSessionKeyMultiFactorVerificationType  = "multi_factor_verification_type" | ||||
| 	UserSessionKeyPasswordlessVerification     = "passwordless_verification" | ||||
| 	UserSessionKeyExternalLoginVerification    = "external_login_verification" | ||||
| 	UserSessionKeySelectedIDPConfigID          = "selected_idp_config_id" | ||||
| ) | ||||
|  | ||||
| type UserSessionView struct { | ||||
| @@ -33,29 +45,33 @@ type UserSessionView struct { | ||||
| 	// are not projected in the user session handler anymore | ||||
| 	// and are therefore annotated with a `gorm:"-"`. | ||||
| 	// They will be read from the corresponding projection directly. | ||||
| 	UserName                     string    `json:"-" gorm:"-"` | ||||
| 	LoginName                    string    `json:"-" gorm:"-"` | ||||
| 	DisplayName                  string    `json:"-" gorm:"-"` | ||||
| 	AvatarKey                    string    `json:"-" gorm:"-"` | ||||
| 	SelectedIDPConfigID          string    `json:"selectedIDPConfigID" gorm:"column:selected_idp_config_id"` | ||||
| 	PasswordVerification         time.Time `json:"-" gorm:"column:password_verification"` | ||||
| 	PasswordlessVerification     time.Time `json:"-" gorm:"column:passwordless_verification"` | ||||
| 	ExternalLoginVerification    time.Time `json:"-" gorm:"column:external_login_verification"` | ||||
| 	SecondFactorVerification     time.Time `json:"-" gorm:"column:second_factor_verification"` | ||||
| 	SecondFactorVerificationType int32     `json:"-" gorm:"column:second_factor_verification_type"` | ||||
| 	MultiFactorVerification      time.Time `json:"-" gorm:"column:multi_factor_verification"` | ||||
| 	MultiFactorVerificationType  int32     `json:"-" gorm:"column:multi_factor_verification_type"` | ||||
| 	Sequence                     uint64    `json:"-" gorm:"column:sequence"` | ||||
| 	InstanceID                   string    `json:"instanceID" gorm:"column:instance_id;primary_key"` | ||||
| 	UserName                     sql.NullString `json:"-" gorm:"-"` | ||||
| 	LoginName                    sql.NullString `json:"-" gorm:"-"` | ||||
| 	DisplayName                  sql.NullString `json:"-" gorm:"-"` | ||||
| 	AvatarKey                    sql.NullString `json:"-" gorm:"-"` | ||||
| 	SelectedIDPConfigID          sql.NullString `json:"selectedIDPConfigID" gorm:"column:selected_idp_config_id"` | ||||
| 	PasswordVerification         sql.NullTime   `json:"-" gorm:"column:password_verification"` | ||||
| 	PasswordlessVerification     sql.NullTime   `json:"-" gorm:"column:passwordless_verification"` | ||||
| 	ExternalLoginVerification    sql.NullTime   `json:"-" gorm:"column:external_login_verification"` | ||||
| 	SecondFactorVerification     sql.NullTime   `json:"-" gorm:"column:second_factor_verification"` | ||||
| 	SecondFactorVerificationType sql.NullInt32  `json:"-" gorm:"column:second_factor_verification_type"` | ||||
| 	MultiFactorVerification      sql.NullTime   `json:"-" gorm:"column:multi_factor_verification"` | ||||
| 	MultiFactorVerificationType  sql.NullInt32  `json:"-" gorm:"column:multi_factor_verification_type"` | ||||
| 	Sequence                     uint64         `json:"-" gorm:"column:sequence"` | ||||
| 	InstanceID                   string         `json:"instanceID" gorm:"column:instance_id;primary_key"` | ||||
| } | ||||
|  | ||||
| func UserSessionFromEvent(event eventstore.Event) (*UserSessionView, error) { | ||||
| 	v := new(UserSessionView) | ||||
| 	if err := event.Unmarshal(v); err != nil { | ||||
| 		logging.Log("EVEN-lso9e").WithError(err).Error("could not unmarshal event data") | ||||
| 		return nil, zerrors.ThrowInternal(nil, "MODEL-sd325", "could not unmarshal data") | ||||
| type userAgentIDPayload struct { | ||||
| 	ID string `json:"userAgentID"` | ||||
| } | ||||
|  | ||||
| func UserAgentIDFromEvent(event eventstore.Event) (string, error) { | ||||
| 	payload := new(userAgentIDPayload) | ||||
| 	if err := event.Unmarshal(payload); err != nil { | ||||
| 		logging.WithError(err).Error("could not unmarshal event data") | ||||
| 		return "", zerrors.ThrowInternal(nil, "MODEL-HJwk9", "could not unmarshal data") | ||||
| 	} | ||||
| 	return v, nil | ||||
| 	return payload.ID, nil | ||||
| } | ||||
|  | ||||
| func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView { | ||||
| @@ -66,18 +82,18 @@ func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView { | ||||
| 		State:                        domain.UserSessionState(userSession.State), | ||||
| 		UserAgentID:                  userSession.UserAgentID, | ||||
| 		UserID:                       userSession.UserID, | ||||
| 		UserName:                     userSession.UserName, | ||||
| 		LoginName:                    userSession.LoginName, | ||||
| 		DisplayName:                  userSession.DisplayName, | ||||
| 		AvatarKey:                    userSession.AvatarKey, | ||||
| 		SelectedIDPConfigID:          userSession.SelectedIDPConfigID, | ||||
| 		PasswordVerification:         userSession.PasswordVerification, | ||||
| 		PasswordlessVerification:     userSession.PasswordlessVerification, | ||||
| 		ExternalLoginVerification:    userSession.ExternalLoginVerification, | ||||
| 		SecondFactorVerification:     userSession.SecondFactorVerification, | ||||
| 		SecondFactorVerificationType: domain.MFAType(userSession.SecondFactorVerificationType), | ||||
| 		MultiFactorVerification:      userSession.MultiFactorVerification, | ||||
| 		MultiFactorVerificationType:  domain.MFAType(userSession.MultiFactorVerificationType), | ||||
| 		UserName:                     userSession.UserName.String, | ||||
| 		LoginName:                    userSession.LoginName.String, | ||||
| 		DisplayName:                  userSession.DisplayName.String, | ||||
| 		AvatarKey:                    userSession.AvatarKey.String, | ||||
| 		SelectedIDPConfigID:          userSession.SelectedIDPConfigID.String, | ||||
| 		PasswordVerification:         userSession.PasswordVerification.Time, | ||||
| 		PasswordlessVerification:     userSession.PasswordlessVerification.Time, | ||||
| 		ExternalLoginVerification:    userSession.ExternalLoginVerification.Time, | ||||
| 		SecondFactorVerification:     userSession.SecondFactorVerification.Time, | ||||
| 		SecondFactorVerificationType: domain.MFAType(userSession.SecondFactorVerificationType.Int32), | ||||
| 		MultiFactorVerification:      userSession.MultiFactorVerification.Time, | ||||
| 		MultiFactorVerificationType:  domain.MFAType(userSession.MultiFactorVerificationType.Int32), | ||||
| 		Sequence:                     userSession.Sequence, | ||||
| 	} | ||||
| } | ||||
| @@ -91,12 +107,13 @@ func UserSessionsToModel(userSessions []*UserSessionView) []*model.UserSessionVi | ||||
| } | ||||
|  | ||||
| func (v *UserSessionView) AppendEvent(event eventstore.Event) error { | ||||
| 	// in case anything needs to be change here check if the Reduce function needs the change as well | ||||
| 	v.Sequence = event.Sequence() | ||||
| 	v.ChangeDate = event.CreatedAt() | ||||
| 	switch event.Type() { | ||||
| 	case user.UserV1PasswordCheckSucceededType, | ||||
| 		user.HumanPasswordCheckSucceededType: | ||||
| 		v.PasswordVerification = event.CreatedAt() | ||||
| 		v.PasswordVerification = sql.NullTime{Time: event.CreatedAt(), Valid: true} | ||||
| 		v.State = int32(domain.UserSessionStateActive) | ||||
| 	case user.UserIDPLoginCheckSucceededType: | ||||
| 		data := new(es_model.AuthRequest) | ||||
| @@ -104,21 +121,21 @@ func (v *UserSessionView) AppendEvent(event eventstore.Event) error { | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		v.ExternalLoginVerification = event.CreatedAt() | ||||
| 		v.SelectedIDPConfigID = data.SelectedIDPConfigID | ||||
| 		v.ExternalLoginVerification = sql.NullTime{Time: event.CreatedAt(), Valid: true} | ||||
| 		v.SelectedIDPConfigID = sql.NullString{String: data.SelectedIDPConfigID, Valid: true} | ||||
| 		v.State = int32(domain.UserSessionStateActive) | ||||
| 	case user.HumanPasswordlessTokenCheckSucceededType: | ||||
| 		v.PasswordlessVerification = event.CreatedAt() | ||||
| 		v.MultiFactorVerification = event.CreatedAt() | ||||
| 		v.MultiFactorVerificationType = int32(domain.MFATypeU2FUserVerification) | ||||
| 		v.PasswordlessVerification = sql.NullTime{Time: event.CreatedAt(), Valid: true} | ||||
| 		v.MultiFactorVerification = sql.NullTime{Time: event.CreatedAt(), Valid: true} | ||||
| 		v.MultiFactorVerificationType = sql.NullInt32{Int32: int32(domain.MFATypeU2FUserVerification)} | ||||
| 		v.State = int32(domain.UserSessionStateActive) | ||||
| 	case user.HumanPasswordlessTokenCheckFailedType, | ||||
| 		user.HumanPasswordlessTokenRemovedType: | ||||
| 		v.PasswordlessVerification = time.Time{} | ||||
| 		v.MultiFactorVerification = time.Time{} | ||||
| 		v.PasswordlessVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 		v.MultiFactorVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 	case user.UserV1PasswordCheckFailedType, | ||||
| 		user.HumanPasswordCheckFailedType: | ||||
| 		v.PasswordVerification = time.Time{} | ||||
| 		v.PasswordVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 	case user.UserV1PasswordChangedType, | ||||
| 		user.HumanPasswordChangedType: | ||||
| 		data := new(es_model.PasswordChange) | ||||
| @@ -127,7 +144,7 @@ func (v *UserSessionView) AppendEvent(event eventstore.Event) error { | ||||
| 			return err | ||||
| 		} | ||||
| 		if v.UserAgentID != data.UserAgentID { | ||||
| 			v.PasswordVerification = time.Time{} | ||||
| 			v.PasswordVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 		} | ||||
| 	case user.HumanMFAOTPVerifiedType: | ||||
| 		data := new(es_model.OTPVerified) | ||||
| @@ -167,7 +184,7 @@ func (v *UserSessionView) AppendEvent(event eventstore.Event) error { | ||||
| 		user.HumanU2FTokenRemovedType, | ||||
| 		user.HumanOTPSMSCheckFailedType, | ||||
| 		user.HumanOTPEmailCheckFailedType: | ||||
| 		v.SecondFactorVerification = time.Time{} | ||||
| 		v.SecondFactorVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 	case user.HumanU2FTokenVerifiedType: | ||||
| 		data := new(es_model.WebAuthNVerify) | ||||
| 		err := data.SetData(event) | ||||
| @@ -183,24 +200,24 @@ func (v *UserSessionView) AppendEvent(event eventstore.Event) error { | ||||
| 		user.HumanSignedOutType, | ||||
| 		user.UserLockedType, | ||||
| 		user.UserDeactivatedType: | ||||
| 		v.PasswordlessVerification = time.Time{} | ||||
| 		v.PasswordVerification = time.Time{} | ||||
| 		v.SecondFactorVerification = time.Time{} | ||||
| 		v.SecondFactorVerificationType = int32(domain.MFALevelNotSetUp) | ||||
| 		v.MultiFactorVerification = time.Time{} | ||||
| 		v.MultiFactorVerificationType = int32(domain.MFALevelNotSetUp) | ||||
| 		v.ExternalLoginVerification = time.Time{} | ||||
| 		v.PasswordlessVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 		v.PasswordVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 		v.SecondFactorVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 		v.SecondFactorVerificationType = sql.NullInt32{Int32: int32(domain.MFALevelNotSetUp)} | ||||
| 		v.MultiFactorVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 		v.MultiFactorVerificationType = sql.NullInt32{Int32: int32(domain.MFALevelNotSetUp)} | ||||
| 		v.ExternalLoginVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 		v.State = int32(domain.UserSessionStateTerminated) | ||||
| 	case user.UserIDPLinkRemovedType, user.UserIDPLinkCascadeRemovedType: | ||||
| 		v.ExternalLoginVerification = time.Time{} | ||||
| 		v.SelectedIDPConfigID = "" | ||||
| 		v.ExternalLoginVerification = sql.NullTime{Time: time.Time{}, Valid: true} | ||||
| 		v.SelectedIDPConfigID = sql.NullString{String: "", Valid: true} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (v *UserSessionView) setSecondFactorVerification(verificationTime time.Time, mfaType domain.MFAType) { | ||||
| 	v.SecondFactorVerification = verificationTime | ||||
| 	v.SecondFactorVerificationType = int32(mfaType) | ||||
| 	v.SecondFactorVerification = sql.NullTime{Time: verificationTime, Valid: true} | ||||
| 	v.SecondFactorVerificationType = sql.NullInt32{Int32: int32(mfaType)} | ||||
| 	v.State = int32(domain.UserSessionStateActive) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"encoding/json" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| @@ -33,7 +34,7 @@ func TestAppendEvent(t *testing.T) { | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.UserV1PasswordCheckSucceededType}, | ||||
| 				userView: &UserSessionView{}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: now()}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append human password check succeeded event", | ||||
| @@ -41,23 +42,23 @@ func TestAppendEvent(t *testing.T) { | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.HumanPasswordCheckSucceededType}, | ||||
| 				userView: &UserSessionView{}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: now()}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append user password check failed event", | ||||
| 			args: args{ | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.UserV1PasswordCheckFailedType}, | ||||
| 				userView: &UserSessionView{PasswordVerification: now()}, | ||||
| 				userView: &UserSessionView{PasswordVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: time.Time{}}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: sql.NullTime{Time: time.Time{}, Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append human password check failed event", | ||||
| 			args: args{ | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.HumanPasswordCheckFailedType}, | ||||
| 				userView: &UserSessionView{PasswordVerification: now()}, | ||||
| 				userView: &UserSessionView{PasswordVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: time.Time{}}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: sql.NullTime{Time: time.Time{}, Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append user password changed event", | ||||
| @@ -72,9 +73,9 @@ func TestAppendEvent(t *testing.T) { | ||||
| 						return d | ||||
| 					}(), | ||||
| 				}, | ||||
| 				userView: &UserSessionView{UserAgentID: "id", PasswordVerification: now()}, | ||||
| 				userView: &UserSessionView{UserAgentID: "id", PasswordVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), PasswordVerification: time.Time{}}, | ||||
| 			result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), PasswordVerification: sql.NullTime{Time: time.Time{}, Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append human password changed event", | ||||
| @@ -91,9 +92,9 @@ func TestAppendEvent(t *testing.T) { | ||||
| 						return d | ||||
| 					}(), | ||||
| 				}, | ||||
| 				userView: &UserSessionView{UserAgentID: "id", PasswordVerification: now()}, | ||||
| 				userView: &UserSessionView{UserAgentID: "id", PasswordVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), PasswordVerification: time.Time{}}, | ||||
| 			result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), PasswordVerification: sql.NullTime{Time: time.Time{}, Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append human password changed event same user agent", | ||||
| @@ -111,9 +112,9 @@ func TestAppendEvent(t *testing.T) { | ||||
| 						return d | ||||
| 					}(), | ||||
| 				}, | ||||
| 				userView: &UserSessionView{UserAgentID: "id", PasswordVerification: now()}, | ||||
| 				userView: &UserSessionView{UserAgentID: "id", PasswordVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), PasswordVerification: now()}, | ||||
| 			result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), PasswordVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append user otp verified event", | ||||
| @@ -142,7 +143,7 @@ func TestAppendEvent(t *testing.T) { | ||||
| 				}, | ||||
| 				userView: &UserSessionView{UserAgentID: "id"}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), SecondFactorVerification: now()}, | ||||
| 			result: &UserSessionView{UserAgentID: "id", ChangeDate: now(), SecondFactorVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append user otp check succeeded event", | ||||
| @@ -150,7 +151,7 @@ func TestAppendEvent(t *testing.T) { | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.UserV1MFAOTPCheckSucceededType}, | ||||
| 				userView: &UserSessionView{}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: now()}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append human otp check succeeded event", | ||||
| @@ -158,55 +159,77 @@ func TestAppendEvent(t *testing.T) { | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.HumanMFAOTPCheckSucceededType}, | ||||
| 				userView: &UserSessionView{}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: now()}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append user otp check failed event", | ||||
| 			args: args{ | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.UserV1MFAOTPCheckFailedType}, | ||||
| 				userView: &UserSessionView{SecondFactorVerification: now()}, | ||||
| 				userView: &UserSessionView{SecondFactorVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: time.Time{}}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: sql.NullTime{Time: time.Time{}, Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append human otp check failed event", | ||||
| 			args: args{ | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.HumanMFAOTPCheckFailedType}, | ||||
| 				userView: &UserSessionView{SecondFactorVerification: now()}, | ||||
| 				userView: &UserSessionView{SecondFactorVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: time.Time{}}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: sql.NullTime{Time: time.Time{}, Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append user otp removed event", | ||||
| 			args: args{ | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.UserV1MFAOTPRemovedType}, | ||||
| 				userView: &UserSessionView{SecondFactorVerification: now()}, | ||||
| 				userView: &UserSessionView{SecondFactorVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: time.Time{}}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: sql.NullTime{Time: time.Time{}, Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append human otp removed event", | ||||
| 			args: args{ | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.HumanMFAOTPRemovedType}, | ||||
| 				userView: &UserSessionView{SecondFactorVerification: now()}, | ||||
| 				userView: &UserSessionView{SecondFactorVerification: sql.NullTime{Time: now(), Valid: true}}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: time.Time{}}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), SecondFactorVerification: sql.NullTime{Time: time.Time{}, Valid: true}}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append user signed out event", | ||||
| 			args: args{ | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.UserV1SignedOutType}, | ||||
| 				userView: &UserSessionView{PasswordVerification: now(), SecondFactorVerification: now()}, | ||||
| 				event: &es_models.Event{CreationDate: now(), Typ: user.UserV1SignedOutType}, | ||||
| 				userView: &UserSessionView{ | ||||
| 					PasswordVerification:     sql.NullTime{Time: now(), Valid: true}, | ||||
| 					SecondFactorVerification: sql.NullTime{Time: now(), Valid: true}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ | ||||
| 				ChangeDate:                now(), | ||||
| 				PasswordVerification:      sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				SecondFactorVerification:  sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				ExternalLoginVerification: sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				PasswordlessVerification:  sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				MultiFactorVerification:   sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				State:                     1, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: time.Time{}, SecondFactorVerification: time.Time{}, State: 1}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "append human signed out event", | ||||
| 			args: args{ | ||||
| 				event:    &es_models.Event{CreationDate: now(), Typ: user.HumanSignedOutType}, | ||||
| 				userView: &UserSessionView{PasswordVerification: now(), SecondFactorVerification: now()}, | ||||
| 				event: &es_models.Event{CreationDate: now(), Typ: user.HumanSignedOutType}, | ||||
| 				userView: &UserSessionView{ | ||||
| 					PasswordVerification:     sql.NullTime{Time: now(), Valid: true}, | ||||
| 					SecondFactorVerification: sql.NullTime{Time: now(), Valid: true}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ | ||||
| 				ChangeDate:                now(), | ||||
| 				PasswordVerification:      sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				SecondFactorVerification:  sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				ExternalLoginVerification: sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				PasswordlessVerification:  sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				MultiFactorVerification:   sql.NullTime{Time: time.Time{}, Valid: true}, | ||||
| 				State:                     1, | ||||
| 			}, | ||||
| 			result: &UserSessionView{ChangeDate: now(), PasswordVerification: time.Time{}, SecondFactorVerification: time.Time{}, State: 1}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Livio Spring
					Livio Spring