mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 18:17:35 +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 {
|
||||
|
@@ -42,74 +42,9 @@ func RefreshTokensByUserID(db *gorm.DB, table, userID, instanceID string) ([]*us
|
||||
return tokens, err
|
||||
}
|
||||
|
||||
func PutRefreshToken(db *gorm.DB, table string, token *usr_model.RefreshTokenView) error {
|
||||
save := repository.PrepareSaveOnConflict(table,
|
||||
[]string{"client_id", "user_agent_id", "user_id"},
|
||||
[]string{"id", "creation_date", "change_date", "token", "auth_time", "idle_expiration", "expiration", "sequence", "scopes", "audience", "amr"},
|
||||
)
|
||||
return save(db, token)
|
||||
}
|
||||
|
||||
func PutRefreshTokens(db *gorm.DB, table string, tokens ...*usr_model.RefreshTokenView) error {
|
||||
save := repository.PrepareBulkSave(table)
|
||||
t := make([]interface{}, len(tokens))
|
||||
for i, token := range tokens {
|
||||
t[i] = token
|
||||
}
|
||||
return save(db, t...)
|
||||
}
|
||||
|
||||
func SearchRefreshTokens(db *gorm.DB, table string, req *model.RefreshTokenSearchRequest) ([]*usr_model.RefreshTokenView, uint64, error) {
|
||||
tokens := make([]*usr_model.RefreshTokenView, 0)
|
||||
query := repository.PrepareSearchQuery(table, usr_model.RefreshTokenSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
|
||||
count, err := query(db, &tokens)
|
||||
return tokens, count, err
|
||||
}
|
||||
|
||||
func DeleteRefreshToken(db *gorm.DB, table, tokenID, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyRefreshTokenID), tokenID},
|
||||
repository.Key{usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyInstanceID), instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteSessionRefreshTokens(db *gorm.DB, table, agentID, userID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyUserAgentID), Value: agentID},
|
||||
repository.Key{Key: usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyUserID), Value: userID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteUserRefreshTokens(db *gorm.DB, table, userID, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyUserID), userID},
|
||||
repository.Key{usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyInstanceID), instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteApplicationRefreshTokens(db *gorm.DB, table string, instanceID string, appIDs []string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyInstanceID), Value: instanceID},
|
||||
repository.Key{Key: usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyApplicationID), Value: appIDs},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteOrgRefreshTokens(db *gorm.DB, table string, instanceID, orgID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyInstanceID), Value: instanceID},
|
||||
repository.Key{Key: usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyResourceOwner), Value: orgID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteInstanceRefreshTokens(db *gorm.DB, table string, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKey(table,
|
||||
usr_model.RefreshTokenSearchKey(model.RefreshTokenSearchKeyInstanceID),
|
||||
instanceID,
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
@@ -47,74 +47,3 @@ func TokensByUserID(db *gorm.DB, table, userID, instanceID string) ([]*usr_model
|
||||
_, err := query(db, &tokens)
|
||||
return tokens, err
|
||||
}
|
||||
|
||||
func PutToken(db *gorm.DB, table string, token *usr_model.TokenView) error {
|
||||
save := repository.PrepareSave(table)
|
||||
return save(db, token)
|
||||
}
|
||||
|
||||
func PutTokens(db *gorm.DB, table string, tokens ...*usr_model.TokenView) error {
|
||||
save := repository.PrepareBulkSave(table)
|
||||
t := make([]interface{}, len(tokens))
|
||||
for i, token := range tokens {
|
||||
t[i] = token
|
||||
}
|
||||
return save(db, t...)
|
||||
}
|
||||
|
||||
func DeleteToken(db *gorm.DB, table, tokenID, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyTokenID), Value: tokenID},
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyInstanceID), Value: instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteSessionTokens(db *gorm.DB, table, agentID, userID, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyUserAgentID), Value: agentID},
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyUserID), Value: userID},
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyInstanceID), Value: instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteUserTokens(db *gorm.DB, table, userID, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyUserID), Value: userID},
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyInstanceID), Value: instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteTokensFromRefreshToken(db *gorm.DB, table, refreshTokenID, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyRefreshTokenID), Value: refreshTokenID},
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyInstanceID), Value: instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteApplicationTokens(db *gorm.DB, table, instanceID string, appIDs []string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyApplicationID), Value: appIDs},
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyInstanceID), Value: instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteInstanceTokens(db *gorm.DB, table, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKey(table,
|
||||
usr_model.TokenSearchKey(model.TokenSearchKeyInstanceID),
|
||||
instanceID,
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteOrgTokens(db *gorm.DB, table, instanceID, orgID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyResourceOwner), Value: orgID},
|
||||
repository.Key{Key: usr_model.TokenSearchKey(model.TokenSearchKeyInstanceID), Value: instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
93
internal/user/repository/view/user_by_id.sql
Normal file
93
internal/user/repository/view/user_by_id.sql
Normal file
@@ -0,0 +1,93 @@
|
||||
WITH auth_methods AS (
|
||||
SELECT
|
||||
user_id
|
||||
, method_type
|
||||
, token_id
|
||||
, state
|
||||
, instance_id
|
||||
, name
|
||||
FROM
|
||||
projections.user_auth_methods4
|
||||
WHERE
|
||||
instance_id = $1
|
||||
AND user_id = $2
|
||||
),
|
||||
verified_auth_methods AS (
|
||||
SELECT
|
||||
method_type
|
||||
FROM
|
||||
auth_methods
|
||||
WHERE state = 2
|
||||
)
|
||||
SELECT
|
||||
u.id
|
||||
, u.creation_date
|
||||
, LEAST(u.change_date, au.change_date) AS change_date
|
||||
, u.resource_owner
|
||||
, u.state AS user_state
|
||||
, au.password_set
|
||||
, h.password_change_required
|
||||
, au.password_change
|
||||
, au.last_login
|
||||
, u.username AS user_name
|
||||
, (SELECT array_agg(ll.login_name) login_names FROM projections.login_names3 ll
|
||||
WHERE u.instance_id = ll.instance_id AND u.id = ll.user_id
|
||||
GROUP BY ll.user_id, ll.instance_id) AS login_names
|
||||
, l.login_name
|
||||
, h.first_name
|
||||
, h.last_name
|
||||
, h.nick_name
|
||||
, h.display_name
|
||||
, h.preferred_language
|
||||
, h.gender
|
||||
, h.email
|
||||
, h.is_email_verified
|
||||
, h.phone
|
||||
, h.is_phone_verified
|
||||
, (SELECT COALESCE((SELECT state FROM auth_methods WHERE method_type = 1), 0)) AS otp_state
|
||||
, CASE
|
||||
WHEN EXISTS (SELECT true FROM verified_auth_methods WHERE method_type = 3) THEN 2
|
||||
WHEN EXISTS (SELECT true FROM verified_auth_methods WHERE method_type = 2) THEN 1
|
||||
ELSE 0
|
||||
END AS mfa_max_set_up
|
||||
, au.mfa_init_skipped
|
||||
, u.sequence
|
||||
, au.init_required
|
||||
, au.username_change_required
|
||||
, m.name AS machine_name
|
||||
, m.description AS machine_description
|
||||
, u.type AS user_type
|
||||
, (SELECT
|
||||
JSONB_AGG(json_build_object('webAuthNTokenId', token_id, 'webAuthNTokenName', name, 'state', state))
|
||||
FROM auth_methods
|
||||
WHERE method_type = 2
|
||||
) AS u2f_tokens
|
||||
, (SELECT
|
||||
JSONB_AGG(json_build_object('webAuthNTokenId', token_id, 'webAuthNTokenName', name, 'state', state))
|
||||
FROM auth_methods
|
||||
WHERE method_type = 3
|
||||
) AS passwordless_tokens
|
||||
, h.avatar_key
|
||||
, au.passwordless_init_required
|
||||
, au.password_init_required
|
||||
, u.instance_id
|
||||
, (SELECT EXISTS (SELECT true FROM verified_auth_methods WHERE method_type = 6)) AS otp_sms_added
|
||||
, (SELECT EXISTS (SELECT true FROM verified_auth_methods WHERE method_type = 7)) AS otp_email_added
|
||||
FROM projections.users12 u
|
||||
LEFT JOIN projections.users12_humans h
|
||||
ON u.instance_id = h.instance_id
|
||||
AND u.id = h.user_id
|
||||
LEFT JOIN projections.login_names3 l
|
||||
ON u.instance_id = l.instance_id
|
||||
AND u.id = l.user_id
|
||||
AND l.is_primary = true
|
||||
LEFT JOIN projections.users12_machines m
|
||||
ON u.instance_id = m.instance_id
|
||||
AND u.id = m.user_id
|
||||
LEFT JOIN auth.users3 au
|
||||
ON u.instance_id = au.instance_id
|
||||
AND u.id = au.id
|
||||
WHERE
|
||||
u.instance_id = $1
|
||||
AND u.id = $2
|
||||
LIMIT 1;
|
@@ -5,12 +5,8 @@ import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
usr_model "github.com/zitadel/zitadel/internal/user/model"
|
||||
"github.com/zitadel/zitadel/internal/user/repository/view/model"
|
||||
"github.com/zitadel/zitadel/internal/view/repository"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
@@ -46,38 +42,8 @@ func UserSessionsByAgentID(db *database.DB, agentID, instanceID string) (userSes
|
||||
return userSessions, err
|
||||
}
|
||||
|
||||
func PutUserSession(db *gorm.DB, table string, session *model.UserSessionView) error {
|
||||
save := repository.PrepareSave(table)
|
||||
return save(db, session)
|
||||
}
|
||||
|
||||
func DeleteUserSessions(db *gorm.DB, table, userID, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: model.UserSessionSearchKey(usr_model.UserSessionSearchKeyUserID), Value: userID},
|
||||
repository.Key{Key: model.UserSessionSearchKey(usr_model.UserSessionSearchKeyInstanceID), Value: instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteInstanceUserSessions(db *gorm.DB, table, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKey(table,
|
||||
model.UserSessionSearchKey(usr_model.UserSessionSearchKeyInstanceID),
|
||||
instanceID,
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteOrgUserSessions(db *gorm.DB, table, instanceID, orgID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{Key: model.UserSessionSearchKey(usr_model.UserSessionSearchKeyResourceOwner), Value: orgID},
|
||||
repository.Key{Key: model.UserSessionSearchKey(usr_model.UserSessionSearchKeyInstanceID), Value: instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
|
||||
session := new(model.UserSessionView)
|
||||
var userName, loginName, displayName, avatarKey sql.NullString
|
||||
err := row.Scan(
|
||||
&session.CreationDate,
|
||||
&session.ChangeDate,
|
||||
@@ -85,10 +51,10 @@ func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
|
||||
&session.State,
|
||||
&session.UserAgentID,
|
||||
&session.UserID,
|
||||
&userName,
|
||||
&loginName,
|
||||
&displayName,
|
||||
&avatarKey,
|
||||
&session.UserName,
|
||||
&session.LoginName,
|
||||
&session.DisplayName,
|
||||
&session.AvatarKey,
|
||||
&session.SelectedIDPConfigID,
|
||||
&session.PasswordVerification,
|
||||
&session.PasswordlessVerification,
|
||||
@@ -103,10 +69,6 @@ func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "VIEW-NGBs1", "Errors.UserSession.NotFound")
|
||||
}
|
||||
session.UserName = userName.String
|
||||
session.LoginName = loginName.String
|
||||
session.DisplayName = displayName.String
|
||||
session.AvatarKey = avatarKey.String
|
||||
return session, err
|
||||
}
|
||||
|
||||
@@ -114,7 +76,6 @@ func scanUserSessions(rows *sql.Rows) ([]*model.UserSessionView, error) {
|
||||
sessions := make([]*model.UserSessionView, 0)
|
||||
for rows.Next() {
|
||||
session := new(model.UserSessionView)
|
||||
var userName, loginName, displayName, avatarKey sql.NullString
|
||||
err := rows.Scan(
|
||||
&session.CreationDate,
|
||||
&session.ChangeDate,
|
||||
@@ -122,10 +83,10 @@ func scanUserSessions(rows *sql.Rows) ([]*model.UserSessionView, error) {
|
||||
&session.State,
|
||||
&session.UserAgentID,
|
||||
&session.UserID,
|
||||
&userName,
|
||||
&loginName,
|
||||
&displayName,
|
||||
&avatarKey,
|
||||
&session.UserName,
|
||||
&session.LoginName,
|
||||
&session.DisplayName,
|
||||
&session.AvatarKey,
|
||||
&session.SelectedIDPConfigID,
|
||||
&session.PasswordVerification,
|
||||
&session.PasswordlessVerification,
|
||||
@@ -140,10 +101,6 @@ func scanUserSessions(rows *sql.Rows) ([]*model.UserSessionView, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session.UserName = userName.String
|
||||
session.LoginName = loginName.String
|
||||
session.DisplayName = displayName.String
|
||||
session.AvatarKey = avatarKey.String
|
||||
sessions = append(sessions, session)
|
||||
}
|
||||
|
||||
|
@@ -1,98 +1,42 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"errors"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
usr_model "github.com/zitadel/zitadel/internal/user/model"
|
||||
"github.com/zitadel/zitadel/internal/user/repository/view/model"
|
||||
"github.com/zitadel/zitadel/internal/view/repository"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
//go:embed user_by_id.sql
|
||||
var userByIDQuery string
|
||||
|
||||
func UserByID(db *gorm.DB, table, userID, instanceID string) (*model.UserView, error) {
|
||||
user := new(model.UserView)
|
||||
userIDQuery := &model.UserSearchQuery{
|
||||
Key: usr_model.UserSearchKeyUserID,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: userID,
|
||||
}
|
||||
instanceIDQuery := &model.UserSearchQuery{
|
||||
Key: usr_model.UserSearchKeyInstanceID,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: instanceID,
|
||||
}
|
||||
ownerRemovedQuery := &model.UserSearchQuery{
|
||||
Key: usr_model.UserSearchOwnerRemoved,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: false,
|
||||
}
|
||||
query := repository.PrepareGetByQuery(table, userIDQuery, instanceIDQuery, ownerRemovedQuery)
|
||||
err := query(db, user)
|
||||
if zerrors.IsNotFound(err) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "VIEW-sj8Sw", "Errors.User.NotFound")
|
||||
}
|
||||
user.SetEmptyUserType()
|
||||
return user, err
|
||||
}
|
||||
|
||||
func UsersByOrgID(db *gorm.DB, table, orgID, instanceID string) ([]*model.UserView, error) {
|
||||
users := make([]*model.UserView, 0)
|
||||
orgIDQuery := &usr_model.UserSearchQuery{
|
||||
Key: usr_model.UserSearchKeyResourceOwner,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: orgID,
|
||||
query := db.Raw(userByIDQuery, instanceID, userID)
|
||||
|
||||
tx := query.BeginTx(context.Background(), &sql.TxOptions{ReadOnly: true})
|
||||
defer func() {
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
logging.OnError(err).Info("commit failed")
|
||||
}
|
||||
tx.RollbackUnlessCommitted()
|
||||
}()
|
||||
|
||||
err := tx.Scan(user).Error
|
||||
if err == nil {
|
||||
user.SetEmptyUserType()
|
||||
return user, nil
|
||||
}
|
||||
instanceIDQuery := &usr_model.UserSearchQuery{
|
||||
Key: usr_model.UserSearchKeyInstanceID,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: instanceID,
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, zerrors.ThrowNotFound(err, "VIEW-hodc6", "object not found")
|
||||
}
|
||||
ownerRemovedQuery := &usr_model.UserSearchQuery{
|
||||
Key: usr_model.UserSearchOwnerRemoved,
|
||||
Method: domain.SearchMethodEquals,
|
||||
Value: false,
|
||||
}
|
||||
query := repository.PrepareSearchQuery(table, model.UserSearchRequest{
|
||||
Queries: []*usr_model.UserSearchQuery{orgIDQuery, instanceIDQuery, ownerRemovedQuery},
|
||||
})
|
||||
_, err := query(db, &users)
|
||||
return users, err
|
||||
}
|
||||
|
||||
func PutUsers(db *gorm.DB, table string, users ...*model.UserView) error {
|
||||
save := repository.PrepareBulkSave(table)
|
||||
u := make([]interface{}, len(users))
|
||||
for i, user := range users {
|
||||
u[i] = user
|
||||
}
|
||||
return save(db, u...)
|
||||
}
|
||||
|
||||
func PutUser(db *gorm.DB, table string, user *model.UserView) error {
|
||||
save := repository.PrepareSave(table)
|
||||
return save(db, user)
|
||||
}
|
||||
|
||||
func DeleteUser(db *gorm.DB, table, userID, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKeys(table,
|
||||
repository.Key{model.UserSearchKey(usr_model.UserSearchKeyUserID), userID},
|
||||
repository.Key{model.UserSearchKey(usr_model.UserSearchKeyInstanceID), instanceID},
|
||||
)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteInstanceUsers(db *gorm.DB, table, instanceID string) error {
|
||||
delete := repository.PrepareDeleteByKey(table, model.UserSearchKey(usr_model.UserSearchKeyInstanceID), instanceID)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func UpdateOrgOwnerRemovedUsers(db *gorm.DB, table, instanceID, aggID string) error {
|
||||
update := repository.PrepareUpdateByKeys(table,
|
||||
model.UserSearchKey(usr_model.UserSearchOwnerRemoved),
|
||||
true,
|
||||
repository.Key{Key: model.UserSearchKey(usr_model.UserSearchKeyInstanceID), Value: instanceID},
|
||||
repository.Key{Key: model.UserSearchKey(usr_model.UserSearchKeyResourceOwner), Value: aggID},
|
||||
)
|
||||
return update(db)
|
||||
logging.WithFields("table ", table).WithError(err).Warn("get from cache error")
|
||||
return nil, zerrors.ThrowInternal(err, "VIEW-qJBg9", "cache error")
|
||||
}
|
||||
|
Reference in New Issue
Block a user