diff --git a/cmd/setup/26.go b/cmd/setup/26.go new file mode 100644 index 000000000..860198a75 --- /dev/null +++ b/cmd/setup/26.go @@ -0,0 +1,27 @@ +package setup + +import ( + "context" + _ "embed" + + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/eventstore" +) + +var ( + //go:embed 26.sql + authUsers3 string +) + +type AuthUsers3 struct { + dbClient *database.DB +} + +func (mig *AuthUsers3) Execute(ctx context.Context, _ eventstore.Event) error { + _, err := mig.dbClient.ExecContext(ctx, authUsers3) + return err +} + +func (mig *AuthUsers3) String() string { + return "26_auth_users3" +} diff --git a/cmd/setup/26.sql b/cmd/setup/26.sql new file mode 100644 index 000000000..fa709d667 --- /dev/null +++ b/cmd/setup/26.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS auth.users3 ( + instance_id TEXT NOT NULL, + id TEXT NOT NULL, + resource_owner TEXT NOT NULL, + change_date TIMESTAMPTZ NULL, + password_set BOOL NULL, + password_change TIMESTAMPTZ NULL, + last_login TIMESTAMPTZ NULL, + init_required BOOL NULL, + mfa_init_skipped TIMESTAMPTZ NULL, + username_change_required BOOL NULL, + passwordless_init_required BOOL NULL, + password_init_required BOOL NULL, + + PRIMARY KEY (instance_id, id) +) diff --git a/cmd/setup/config.go b/cmd/setup/config.go index bb96e9912..c66edb4db 100644 --- a/cmd/setup/config.go +++ b/cmd/setup/config.go @@ -108,6 +108,7 @@ type Steps struct { s23CorrectGlobalUniqueConstraints *CorrectGlobalUniqueConstraints s24AddActorToAuthTokens *AddActorToAuthTokens s25User11AddLowerFieldsToVerifiedEmail *User11AddLowerFieldsToVerifiedEmail + s26AuthUsers3 *AuthUsers3 } func MustNewSteps(v *viper.Viper) *Steps { diff --git a/cmd/setup/setup.go b/cmd/setup/setup.go index 693ec81e3..96d0f05bc 100644 --- a/cmd/setup/setup.go +++ b/cmd/setup/setup.go @@ -138,6 +138,7 @@ func Setup(config *Config, steps *Steps, masterKey string) { steps.s23CorrectGlobalUniqueConstraints = &CorrectGlobalUniqueConstraints{dbClient: esPusherDBClient} steps.s24AddActorToAuthTokens = &AddActorToAuthTokens{dbClient: queryDBClient} steps.s25User11AddLowerFieldsToVerifiedEmail = &User11AddLowerFieldsToVerifiedEmail{dbClient: esPusherDBClient} + steps.s26AuthUsers3 = &AuthUsers3{dbClient: esPusherDBClient} err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil) logging.OnError(err).Fatal("unable to start projections") @@ -175,6 +176,7 @@ func Setup(config *Config, steps *Steps, masterKey string) { steps.s22ActiveInstancesIndex, steps.s23CorrectGlobalUniqueConstraints, steps.s24AddActorToAuthTokens, + steps.s26AuthUsers3, } { mustExecuteMigration(ctx, eventstoreClient, step, "migration failed") } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 80dc085e4..76102a916 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -1517,12 +1517,12 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve user_repo.HumanPasswordlessTokenCheckFailedType, user_repo.HumanU2FTokenCheckSucceededType, user_repo.HumanU2FTokenCheckFailedType: - eventData, err := user_view_model.UserSessionFromEvent(event) + userAgentID, err := user_view_model.UserAgentIDFromEvent(event) if err != nil { logging.WithFields("traceID", tracing.TraceIDFromCtx(ctx)).WithError(err).Debug("error getting event data") return user_view_model.UserSessionToModel(session), nil } - if eventData.UserAgentID != agentID { + if userAgentID != agentID { continue } case user_repo.UserRemovedType: diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index f1f762e52..324039a76 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -2,6 +2,7 @@ package eventstore import ( "context" + "database/sql" "encoding/json" "testing" "time" @@ -75,11 +76,11 @@ type mockUser struct { func (m *mockViewUserSession) UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) { return &user_view_model.UserSessionView{ - ExternalLoginVerification: m.ExternalLoginVerification, - PasswordlessVerification: m.PasswordlessVerification, - PasswordVerification: m.PasswordVerification, - SecondFactorVerification: m.SecondFactorVerification, - MultiFactorVerification: m.MultiFactorVerification, + ExternalLoginVerification: sql.NullTime{Time: m.ExternalLoginVerification}, + PasswordlessVerification: sql.NullTime{Time: m.PasswordlessVerification}, + PasswordVerification: sql.NullTime{Time: m.PasswordVerification}, + SecondFactorVerification: sql.NullTime{Time: m.SecondFactorVerification}, + MultiFactorVerification: sql.NullTime{Time: m.MultiFactorVerification}, }, nil } @@ -90,7 +91,7 @@ func (m *mockViewUserSession) UserSessionsByAgentID(string, string) ([]*user_vie ResourceOwner: user.ResourceOwner, State: int32(user.SessionState), UserID: user.UserID, - LoginName: user.LoginName, + LoginName: sql.NullString{String: user.LoginName}, } } return sessions, nil diff --git a/internal/auth/repository/eventsourcing/handler/refresh_token.go b/internal/auth/repository/eventsourcing/handler/refresh_token.go index 4dac7eab6..c63f58123 100644 --- a/internal/auth/repository/eventsourcing/handler/refresh_token.go +++ b/internal/auth/repository/eventsourcing/handler/refresh_token.go @@ -3,8 +3,6 @@ package handler import ( "context" - "github.com/zitadel/logging" - auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" @@ -98,50 +96,84 @@ func (t *RefreshToken) Reducers() []handler.AggregateReducer { } func (t *RefreshToken) Reduce(event eventstore.Event) (_ *handler.Statement, err error) { - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - switch event.Type() { - case user.HumanRefreshTokenAddedType: - token := new(view_model.RefreshTokenView) - err := token.AppendEvent(event) - if err != nil { - return err - } - - return t.view.PutRefreshToken(token) - case user.HumanRefreshTokenRenewedType: - e := new(user.HumanRefreshTokenRenewedEvent) - if err := event.Unmarshal(e); err != nil { - logging.WithError(err).Error("could not unmarshal event data") - return zerrors.ThrowInternal(nil, "MODEL-BHn75", "could not unmarshal data") - } - token, err := t.view.RefreshTokenByID(e.TokenID, event.Aggregate().InstanceID) - if err != nil { - return err - } - err = token.AppendEvent(event) - if err != nil { - return err - } - return t.view.PutRefreshToken(token) - case user.HumanRefreshTokenRemovedType: - e := new(user.HumanRefreshTokenRemovedEvent) - if err := event.Unmarshal(e); err != nil { - logging.WithError(err).Error("could not unmarshal event data") - return zerrors.ThrowInternal(nil, "MODEL-Bz653", "could not unmarshal data") - } - return t.view.DeleteRefreshToken(e.TokenID, event.Aggregate().InstanceID) - case user.UserLockedType, - user.UserDeactivatedType, - user.UserRemovedType: - - return t.view.DeleteUserRefreshTokens(event.Aggregate().ID, event.Aggregate().InstanceID) - case instance.InstanceRemovedEventType: - - return t.view.DeleteInstanceRefreshTokens(event.Aggregate().InstanceID) - case org.OrgRemovedEventType: - return t.view.DeleteOrgRefreshTokens(event) - default: - return nil + // in case anything needs to be change here check if appendEvent function needs the change as well + switch event.Type() { + case user.HumanRefreshTokenAddedType: + e, ok := event.(*user.HumanRefreshTokenAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-IoF6j", "reduce.wrong.event.type %s", user.HumanRefreshTokenAddedType) } - }), nil + columns := []handler.Column{ + handler.NewCol(view_model.RefreshTokenKeyClientID, e.ClientID), + handler.NewCol(view_model.RefreshTokenKeyUserAgentID, e.UserAgentID), + handler.NewCol(view_model.RefreshTokenKeyUserID, e.Aggregate().ID), + handler.NewCol(view_model.RefreshTokenKeyInstanceID, e.Aggregate().InstanceID), + handler.NewCol(view_model.RefreshTokenKeyTokenID, e.TokenID), + handler.NewCol(view_model.RefreshTokenKeyResourceOwner, e.Aggregate().ResourceOwner), + handler.NewCol(view_model.RefreshTokenKeyCreationDate, event.CreatedAt()), + handler.NewCol(view_model.RefreshTokenKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.RefreshTokenKeySequence, event.Sequence()), + handler.NewCol(view_model.RefreshTokenKeyAMR, e.AuthMethodsReferences), + handler.NewCol(view_model.RefreshTokenKeyAuthTime, e.AuthTime), + handler.NewCol(view_model.RefreshTokenKeyAudience, e.Audience), + handler.NewCol(view_model.RefreshTokenKeyExpiration, event.CreatedAt().Add(e.Expiration)), + handler.NewCol(view_model.RefreshTokenKeyIdleExpiration, event.CreatedAt().Add(e.IdleExpiration)), + handler.NewCol(view_model.RefreshTokenKeyScopes, e.Scopes), + handler.NewCol(view_model.RefreshTokenKeyToken, e.TokenID), + handler.NewCol(view_model.RefreshTokenKeyActor, view_model.TokenActor{TokenActor: e.Actor}), + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil + case user.HumanRefreshTokenRenewedType: + e, ok := event.(*user.HumanRefreshTokenRenewedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-AG43hq", "reduce.wrong.event.type %s", user.HumanRefreshTokenRenewedType) + } + return handler.NewUpdateStatement(event, + []handler.Column{ + handler.NewCol(view_model.RefreshTokenKeyIdleExpiration, event.CreatedAt().Add(e.IdleExpiration)), + handler.NewCol(view_model.RefreshTokenKeyToken, e.RefreshToken), + handler.NewCol(view_model.RefreshTokenKeyChangeDate, e.CreatedAt()), + handler.NewCol(view_model.RefreshTokenKeySequence, event.Sequence()), + }, + []handler.Condition{ + handler.NewCond(view_model.RefreshTokenKeyTokenID, e.TokenID), + handler.NewCond(view_model.RefreshTokenKeyInstanceID, e.Aggregate().InstanceID), + }, + ), nil + case user.HumanRefreshTokenRemovedType: + e, ok := event.(*user.HumanRefreshTokenRemovedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SFF3t", "reduce.wrong.event.type %s", user.HumanRefreshTokenRemovedType) + } + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.RefreshTokenKeyTokenID, e.TokenID), + }, + ), nil + case user.UserLockedType, + user.UserDeactivatedType, + user.UserRemovedType: + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.RefreshTokenKeyUserID, event.Aggregate().ID), + }, + ), nil + case instance.InstanceRemovedEventType: + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID), + }, + ), nil + case org.OrgRemovedEventType: + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.RefreshTokenKeyResourceOwner, event.Aggregate().ResourceOwner), + }, + ), nil + default: + return handler.NewNoOpStatement(event), nil + } } diff --git a/internal/auth/repository/eventsourcing/handler/token.go b/internal/auth/repository/eventsourcing/handler/token.go index 4b0a7194d..d915037cb 100644 --- a/internal/auth/repository/eventsourcing/handler/token.go +++ b/internal/auth/repository/eventsourcing/handler/token.go @@ -3,6 +3,7 @@ package handler import ( "context" + "github.com/muhlemmer/gu" "github.com/zitadel/logging" auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view" @@ -150,129 +151,189 @@ func (t *Token) Reducers() []handler.AggregateReducer { } func (t *Token) Reduce(event eventstore.Event) (_ *handler.Statement, err error) { //nolint:gocognit - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - switch event.Type() { - case user.UserTokenAddedType, - user.PersonalAccessTokenAddedType: - token := new(view_model.TokenView) - err := token.AppendEvent(event) - if err != nil { - return err - } - return t.view.PutToken(token) - case user.UserV1ProfileChangedType, - user.HumanProfileChangedType: - user := new(view_model.UserView) - err := user.AppendEvent(event) - if err != nil { - return err - } - tokens, err := t.view.TokensByUserID(event.Aggregate().ID, event.Aggregate().InstanceID) - if err != nil { - return err - } - for _, token := range tokens { - token.PreferredLanguage = user.PreferredLanguage - } - return t.view.PutTokens(tokens) - case user.UserV1SignedOutType, - user.HumanSignedOutType: - id, err := agentIDFromSession(event) - if err != nil { - return err - } - - return t.view.DeleteSessionTokens(id, event) - case user.UserLockedType, - user.UserDeactivatedType, - user.UserRemovedType: - - return t.view.DeleteUserTokens(event) - case user.UserTokenRemovedType, - user.PersonalAccessTokenRemovedType: - id, err := tokenIDFromRemovedEvent(event) - if err != nil { - return err - } - - return t.view.DeleteToken(id, event.Aggregate().InstanceID) - case user.HumanRefreshTokenRemovedType: - id, err := refreshTokenIDFromRemovedEvent(event) - if err != nil { - return err - } - - return t.view.DeleteTokensFromRefreshToken(id, event.Aggregate().InstanceID) - case project.ApplicationDeactivatedType, - project.ApplicationRemovedType: - application, err := applicationFromSession(event) - if err != nil { - return err - } - - return t.view.DeleteApplicationTokens(event, application.AppID) - case project.ProjectDeactivatedType, - project.ProjectRemovedType: - project, err := t.getProjectByID(context.Background(), event.Aggregate().ID, event.Aggregate().InstanceID) - if err != nil { - return err - } - applicationIDs := make([]string, 0, len(project.Applications)) - for _, app := range project.Applications { - if app.OIDCConfig != nil && app.OIDCConfig.ClientID != "" { - applicationIDs = append(applicationIDs, app.OIDCConfig.ClientID) - } - } - - return t.view.DeleteApplicationTokens(event, applicationIDs...) - case instance.InstanceRemovedEventType: - return t.view.DeleteInstanceTokens(event) - case org.OrgRemovedEventType: - // deletes all tokens including PATs, which is expected for now - // if there is an undo of the org deletion in the future, - // we will need to have a look on how to handle the deleted PATs - return t.view.DeleteOrgTokens(event) - default: - return nil + // in case anything needs to be change here check if appendEvent function needs the change as well + switch event.Type() { + case user.UserTokenAddedType: + e, ok := event.(*user.UserTokenAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-W4tnq", "reduce.wrong.event.type %s", user.UserTokenAddedType) } - }), nil + return handler.NewCreateStatement(event, + []handler.Column{ + handler.NewCol(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCol(view_model.TokenKeyUserID, event.Aggregate().ID), + handler.NewCol(view_model.TokenKeyResourceOwner, event.Aggregate().ResourceOwner), + handler.NewCol(view_model.TokenKeyID, e.TokenID), + handler.NewCol(view_model.TokenKeyCreationDate, event.CreatedAt()), + handler.NewCol(view_model.TokenKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.TokenKeySequence, event.Sequence()), + handler.NewCol(view_model.TokenKeyApplicationID, e.ApplicationID), + handler.NewCol(view_model.TokenKeyUserAgentID, e.UserAgentID), + handler.NewCol(view_model.TokenKeyAudience, e.Audience), + handler.NewCol(view_model.TokenKeyScopes, e.Scopes), + handler.NewCol(view_model.TokenKeyExpiration, e.Expiration), + handler.NewCol(view_model.TokenKeyPreferredLanguage, e.PreferredLanguage), + handler.NewCol(view_model.TokenKeyRefreshTokenID, e.RefreshTokenID), + handler.NewCol(view_model.TokenKeyActor, view_model.TokenActor{TokenActor: e.Actor}), + handler.NewCol(view_model.TokenKeyIsPat, false), + }, + ), nil + case user.PersonalAccessTokenAddedType: + e, ok := event.(*user.PersonalAccessTokenAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-zF3rb", "reduce.wrong.event.type %s", user.PersonalAccessTokenAddedType) + } + return handler.NewCreateStatement(event, + []handler.Column{ + handler.NewCol(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCol(view_model.TokenKeyUserID, event.Aggregate().ID), + handler.NewCol(view_model.TokenKeyResourceOwner, event.Aggregate().ResourceOwner), + handler.NewCol(view_model.TokenKeyID, e.TokenID), + handler.NewCol(view_model.TokenKeyCreationDate, event.CreatedAt()), + handler.NewCol(view_model.TokenKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.TokenKeySequence, event.Sequence()), + handler.NewCol(view_model.TokenKeyScopes, e.Scopes), + handler.NewCol(view_model.TokenKeyExpiration, e.Expiration), + handler.NewCol(view_model.TokenKeyIsPat, true), + }, + ), nil + case user.UserV1ProfileChangedType, + user.HumanProfileChangedType: + e, ok := event.(*user.HumanProfileChangedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-ASF2t", "reduce.wrong.event.type %s", user.HumanProfileChangedType) + } + if e.PreferredLanguage == nil { + return handler.NewNoOpStatement(event), nil + } + return handler.NewUpdateStatement(event, + []handler.Column{ + handler.NewCol(view_model.TokenKeyPreferredLanguage, gu.Value(e.PreferredLanguage).String()), + handler.NewCol(view_model.TokenKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.TokenKeySequence, event.Sequence()), + }, + []handler.Condition{ + handler.NewCond(view_model.TokenKeyInstanceID, e.Aggregate().InstanceID), + handler.NewCond(view_model.TokenKeyUserID, e.Aggregate().ID), + }, + ), nil + case user.UserV1SignedOutType, + user.HumanSignedOutType: + e, ok := event.(*user.HumanSignedOutEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-Wtn2q", "reduce.wrong.event.type %s", user.HumanSignedOutType) + } + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.TokenKeyUserID, event.Aggregate().ID), + handler.NewCond(view_model.TokenKeyUserAgentID, e.UserAgentID), + }, + ), nil + case user.UserLockedType, + user.UserDeactivatedType, + user.UserRemovedType: + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.TokenKeyUserID, event.Aggregate().ID), + }, + ), nil + case user.UserTokenRemovedType, + user.PersonalAccessTokenRemovedType: + var tokenID string + switch e := event.(type) { + case *user.UserTokenRemovedEvent: + tokenID = e.TokenID + case *user.PersonalAccessTokenRemovedEvent: + tokenID = e.TokenID + default: + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SF3ga", "reduce.wrong.event.type %s", user.UserTokenRemovedType) + } + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.TokenKeyID, tokenID), + }, + ), nil + case user.HumanRefreshTokenRemovedType: + e, ok := event.(*user.HumanRefreshTokenRemovedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-Sfe11", "reduce.wrong.event.type %s", user.HumanRefreshTokenRemovedType) + } + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.TokenKeyRefreshTokenID, e.TokenID), + }, + ), nil + case project.ApplicationDeactivatedType, + project.ApplicationRemovedType: + var applicationID string + switch e := event.(type) { + case *project.ApplicationDeactivatedEvent: + applicationID = e.AppID + case *project.ApplicationRemovedEvent: + applicationID = e.AppID + default: + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SF3fq", "reduce.wrong.event.type %v", []eventstore.EventType{project.ApplicationDeactivatedType, project.ApplicationRemovedType}) + } + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.TokenKeyApplicationID, applicationID), + }, + ), nil + case project.ProjectDeactivatedType, + project.ProjectRemovedType: + project, err := t.getProjectByID(context.Background(), event.Aggregate().ID, event.Aggregate().InstanceID) + if err != nil { + return nil, err + } + applicationIDs := make([]string, 0, len(project.Applications)) + for _, app := range project.Applications { + if app.OIDCConfig != nil && app.OIDCConfig.ClientID != "" { + applicationIDs = append(applicationIDs, app.OIDCConfig.ClientID) + } + } + if len(applicationIDs) == 0 { + return handler.NewNoOpStatement(event), nil + } + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewOneOfTextCond(view_model.TokenKeyApplicationID, applicationIDs), + }, + ), nil + case instance.InstanceRemovedEventType: + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + }, + ), nil + case org.OrgRemovedEventType: + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.TokenKeyResourceOwner, event.Aggregate().ResourceOwner), + }, + ), nil + default: + return handler.NewNoOpStatement(event), nil + } +} + +type userAgentIDPayload struct { + ID string `json:"userAgentID"` } func agentIDFromSession(event eventstore.Event) (string, error) { - session := make(map[string]interface{}) - if err := event.Unmarshal(&session); err != nil { + payload := new(userAgentIDPayload) + if err := event.Unmarshal(payload); err != nil { logging.WithError(err).Error("could not unmarshal event data") return "", zerrors.ThrowInternal(nil, "MODEL-sd325", "could not unmarshal data") } - agentID, _ := session["userAgentID"].(string) - return agentID, nil -} - -func applicationFromSession(event eventstore.Event) (*project_es_model.Application, error) { - application := new(project_es_model.Application) - if err := event.Unmarshal(application); err != nil { - logging.WithError(err).Error("could not unmarshal event data") - return nil, zerrors.ThrowInternal(nil, "MODEL-Hrw1q", "could not unmarshal data") - } - return application, nil -} - -func tokenIDFromRemovedEvent(event eventstore.Event) (string, error) { - removed := make(map[string]interface{}) - if err := event.Unmarshal(&removed); err != nil { - logging.WithError(err).Error("could not unmarshal event data") - return "", zerrors.ThrowInternal(nil, "MODEL-Sff32", "could not unmarshal data") - } - return removed["tokenId"].(string), nil -} - -func refreshTokenIDFromRemovedEvent(event eventstore.Event) (string, error) { - removed := make(map[string]interface{}) - if err := event.Unmarshal(&removed); err != nil { - logging.WithError(err).Error("could not unmarshal event data") - return "", zerrors.ThrowInternal(nil, "MODEL-Dfb3w", "could not unmarshal data") - } - return removed["tokenId"].(string), nil + return payload.ID, nil } func (t *Token) getProjectByID(ctx context.Context, projID, instanceID string) (*proj_model.Project, error) { diff --git a/internal/auth/repository/eventsourcing/handler/user.go b/internal/auth/repository/eventsourcing/handler/user.go index c93dc0647..2333d5242 100644 --- a/internal/auth/repository/eventsourcing/handler/user.go +++ b/internal/auth/repository/eventsourcing/handler/user.go @@ -4,25 +4,20 @@ import ( "context" "time" - "github.com/zitadel/zitadel/internal/api/authz" auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" - es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models" - org_model "github.com/zitadel/zitadel/internal/org/model" - org_es_model "github.com/zitadel/zitadel/internal/org/repository/eventsourcing/model" - org_view "github.com/zitadel/zitadel/internal/org/repository/view" query2 "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/org" user_repo "github.com/zitadel/zitadel/internal/repository/user" - usr_view "github.com/zitadel/zitadel/internal/user/repository/view" view_model "github.com/zitadel/zitadel/internal/user/repository/view/model" "github.com/zitadel/zitadel/internal/zerrors" ) const ( - userTable = "auth.users2" + userTable = "auth.users3" ) type User struct { @@ -58,26 +53,14 @@ func (u *User) Reducers() []handler.AggregateReducer { { Aggregate: user_repo.AggregateType, EventReducers: []handler.EventReducer{ - { - Event: user_repo.HumanOTPSMSAddedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.HumanOTPSMSRemovedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.HumanOTPEmailAddedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.HumanOTPEmailRemovedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.MachineAddedEventType, - Reduce: u.ProcessUser, - }, { Event: user_repo.HumanAddedType, Reduce: u.ProcessUser, @@ -94,62 +77,14 @@ func (u *User) Reducers() []handler.AggregateReducer { Event: user_repo.HumanRegisteredType, Reduce: u.ProcessUser, }, - { - Event: user_repo.UserV1ProfileChangedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserV1EmailChangedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserV1EmailVerifiedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserV1PhoneChangedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserV1PhoneVerifiedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.UserV1PhoneRemovedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.UserV1AddressChangedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserDeactivatedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserReactivatedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserLockedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserUnlockedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserV1MFAOTPAddedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.UserV1MFAOTPVerifiedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.UserV1MFAOTPRemovedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.UserV1MFAInitSkippedType, Reduce: u.ProcessUser, @@ -158,86 +93,22 @@ func (u *User) Reducers() []handler.AggregateReducer { Event: user_repo.UserV1PasswordChangedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.HumanProfileChangedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanEmailChangedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanEmailVerifiedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanAvatarAddedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanAvatarRemovedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanPhoneChangedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanPhoneVerifiedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.HumanPhoneRemovedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.HumanAddressChangedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanMFAOTPAddedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.HumanMFAOTPVerifiedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.HumanMFAOTPRemovedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanU2FTokenAddedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.HumanU2FTokenVerifiedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.HumanU2FTokenRemovedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanPasswordlessTokenAddedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanPasswordlessTokenVerifiedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.HumanPasswordlessTokenRemovedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.HumanMFAInitSkippedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.MachineChangedEventType, - Reduce: u.ProcessUser, - }, { Event: user_repo.HumanPasswordChangedType, Reduce: u.ProcessUser, @@ -266,14 +137,6 @@ func (u *User) Reducers() []handler.AggregateReducer { Event: user_repo.HumanPasswordlessInitCodeRequestedType, Reduce: u.ProcessUser, }, - { - Event: user_repo.UserDomainClaimedType, - Reduce: u.ProcessUser, - }, - { - Event: user_repo.UserUserNameChangedType, - Reduce: u.ProcessUser, - }, { Event: user_repo.UserRemovedType, Reduce: u.ProcessUser, @@ -283,30 +146,6 @@ func (u *User) Reducers() []handler.AggregateReducer { { Aggregate: org.AggregateType, EventReducers: []handler.EventReducer{ - { - Event: org.OrgDomainVerifiedEventType, - Reduce: u.ProcessOrg, - }, - { - Event: org.OrgDomainRemovedEventType, - Reduce: u.ProcessOrg, - }, - { - Event: org.DomainPolicyAddedEventType, - Reduce: u.ProcessOrg, - }, - { - Event: org.DomainPolicyChangedEventType, - Reduce: u.ProcessOrg, - }, - { - Event: org.DomainPolicyRemovedEventType, - Reduce: u.ProcessOrg, - }, - { - Event: org.OrgDomainPrimarySetEventType, - Reduce: u.ProcessOrg, - }, { Event: org.OrgRemovedEventType, Reduce: u.ProcessOrg, @@ -327,141 +166,124 @@ func (u *User) Reducers() []handler.AggregateReducer { //nolint:gocognit func (u *User) ProcessUser(event eventstore.Event) (_ *handler.Statement, err error) { - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - user := new(view_model.UserView) - switch event.Type() { - case user_repo.UserV1AddedType, - user_repo.MachineAddedEventType, - user_repo.HumanAddedType, - user_repo.UserV1RegisteredType, - user_repo.HumanRegisteredType: - err = user.AppendEvent(event) - if err != nil { - return err - } - err = u.fillLoginNames(user) - case user_repo.UserV1ProfileChangedType, - user_repo.UserV1EmailChangedType, - user_repo.UserV1EmailVerifiedType, - user_repo.UserV1PhoneChangedType, - user_repo.UserV1PhoneVerifiedType, - user_repo.UserV1PhoneRemovedType, - user_repo.UserV1AddressChangedType, - user_repo.UserDeactivatedType, - user_repo.UserReactivatedType, - user_repo.UserLockedType, - user_repo.UserUnlockedType, - user_repo.UserV1MFAOTPAddedType, - user_repo.UserV1MFAOTPVerifiedType, - user_repo.UserV1MFAOTPRemovedType, - user_repo.UserV1MFAInitSkippedType, - user_repo.UserV1PasswordChangedType, - user_repo.HumanProfileChangedType, - user_repo.HumanEmailChangedType, - user_repo.HumanEmailVerifiedType, - user_repo.HumanAvatarAddedType, - user_repo.HumanAvatarRemovedType, - user_repo.HumanPhoneChangedType, - user_repo.HumanPhoneVerifiedType, - user_repo.HumanPhoneRemovedType, - user_repo.HumanAddressChangedType, - user_repo.HumanMFAOTPAddedType, - user_repo.HumanMFAOTPVerifiedType, - user_repo.HumanMFAOTPRemovedType, - user_repo.HumanOTPSMSAddedType, - user_repo.HumanOTPSMSRemovedType, - user_repo.HumanOTPEmailAddedType, - user_repo.HumanOTPEmailRemovedType, - user_repo.HumanU2FTokenAddedType, - user_repo.HumanU2FTokenVerifiedType, - user_repo.HumanU2FTokenRemovedType, - user_repo.HumanPasswordlessTokenAddedType, - user_repo.HumanPasswordlessTokenVerifiedType, - user_repo.HumanPasswordlessTokenRemovedType, - user_repo.HumanMFAInitSkippedType, - user_repo.MachineChangedEventType, - user_repo.HumanPasswordChangedType, - user_repo.HumanInitialCodeAddedType, - user_repo.UserV1InitialCodeAddedType, - user_repo.UserV1InitializedCheckSucceededType, - user_repo.HumanInitializedCheckSucceededType, - user_repo.HumanPasswordlessInitCodeAddedType, - user_repo.HumanPasswordlessInitCodeRequestedType: - user, err = u.view.UserByID(event.Aggregate().ID, event.Aggregate().InstanceID) - if err != nil { - if !zerrors.IsNotFound(err) { - return err - } - user, err = u.userFromEventstore(event.Aggregate(), user.EventTypes()) - if err != nil { - return err - } - } - err = user.AppendEvent(event) - case user_repo.UserDomainClaimedType, - user_repo.UserUserNameChangedType: - user, err = u.view.UserByID(event.Aggregate().ID, event.Aggregate().InstanceID) - if err != nil { - if !zerrors.IsNotFound(err) { - return err - } - user, err = u.userFromEventstore(event.Aggregate(), user.EventTypes()) - if err != nil { - return err - } - } - err = user.AppendEvent(event) - if err != nil { - return err - } - err = u.fillLoginNames(user) - case user_repo.UserRemovedType: - return u.view.DeleteUser(event.Aggregate().ID, event.Aggregate().InstanceID, event) - default: - return nil + // in case anything needs to be change here check if appendEvent function needs the change as well + switch event.Type() { + case user_repo.UserV1AddedType, + user_repo.HumanAddedType: + e, ok := event.(*user_repo.HumanAddedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SDAGF", "reduce.wrong.event.type %s", user_repo.HumanAddedType) } - if err != nil { - return err + return u.setPasswordData(event, e.Secret, e.EncodedHash), nil + case user_repo.UserV1RegisteredType, + user_repo.HumanRegisteredType: + e, ok := event.(*user_repo.HumanRegisteredEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-AS1hz", "reduce.wrong.event.type %s", user_repo.HumanRegisteredType) } - return u.view.PutUser(user, event) - }), nil + return u.setPasswordData(event, e.Secret, e.EncodedHash), nil + case user_repo.UserV1PasswordChangedType, + user_repo.HumanPasswordChangedType: + e, ok := event.(*user_repo.HumanPasswordChangedEvent) + if !ok { + return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-Gd31w", "reduce.wrong.event.type %s", user_repo.HumanPasswordChangedType) + } + return u.setPasswordData(event, e.Secret, e.EncodedHash), nil + case user_repo.UserV1PhoneRemovedType, + user_repo.HumanPhoneRemovedType, + user_repo.UserV1MFAOTPVerifiedType, + user_repo.HumanMFAOTPVerifiedType, + user_repo.HumanOTPSMSRemovedType, + user_repo.HumanOTPEmailRemovedType, + user_repo.HumanU2FTokenVerifiedType: + return handler.NewUpdateStatement(event, + []handler.Column{ + handler.NewCol(view_model.UserKeyMFAInitSkipped, time.Time{}), + handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()), + }, + []handler.Condition{ + handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID), + }), nil + case user_repo.UserV1MFAInitSkippedType, + user_repo.HumanMFAInitSkippedType: + return handler.NewUpdateStatement(event, + []handler.Column{ + handler.NewCol(view_model.UserKeyMFAInitSkipped, event.CreatedAt()), + handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()), + }, + []handler.Condition{ + handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID), + }), nil + case user_repo.UserV1InitialCodeAddedType, + user_repo.HumanInitialCodeAddedType: + return handler.NewUpdateStatement(event, + []handler.Column{ + handler.NewCol(view_model.UserKeyInitRequired, true), + handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()), + }, + []handler.Condition{ + handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID), + }), nil + case user_repo.UserV1InitializedCheckSucceededType, + user_repo.HumanInitializedCheckSucceededType: + return handler.NewUpdateStatement(event, + []handler.Column{ + handler.NewCol(view_model.UserKeyInitRequired, false), + handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()), + }, + []handler.Condition{ + handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID), + }), nil + case user_repo.HumanPasswordlessInitCodeAddedType, + user_repo.HumanPasswordlessInitCodeRequestedType: + return handler.NewUpdateStatement(event, + []handler.Column{ + handler.NewCol(view_model.UserKeyPasswordlessInitRequired, true), + handler.NewCol(view_model.UserKeyPasswordInitRequired, false), + handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()), + }, + []handler.Condition{ + handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID), + handler.NewCond(view_model.UserKeyPasswordSet, false), + }), nil + case user_repo.UserRemovedType: + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID), + }), nil + default: + return handler.NewNoOpStatement(event), nil + } } -func (u *User) fillLoginNames(user *view_model.UserView) (err error) { - userLoginMustBeDomain, primaryDomain, domains, err := u.loginNameInformation(context.Background(), user.ResourceOwner, user.InstanceID) - if err != nil { - return err +func (u *User) setPasswordData(event eventstore.Event, secret *crypto.CryptoValue, hash string) *handler.Statement { + set := secret != nil || hash != "" + columns := []handler.Column{ + handler.NewCol(view_model.UserKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCol(view_model.UserKeyUserID, event.Aggregate().ID), + handler.NewCol(view_model.UserKeyResourceOwner, event.Aggregate().ResourceOwner), + handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.UserKeyPasswordSet, set), + handler.NewCol(view_model.UserKeyPasswordInitRequired, !set), + handler.NewCol(view_model.UserKeyPasswordChange, event.CreatedAt()), } - user.SetLoginNames(userLoginMustBeDomain, domains) - user.PreferredLoginName = user.GenerateLoginName(primaryDomain, userLoginMustBeDomain) - return nil + return handler.NewUpsertStatement(event, columns[0:2], columns) } func (u *User) ProcessOrg(event eventstore.Event) (_ *handler.Statement, err error) { - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - switch event.Type() { - case org.OrgDomainVerifiedEventType, - org.OrgDomainRemovedEventType, - org.DomainPolicyAddedEventType, - org.DomainPolicyChangedEventType, - org.DomainPolicyRemovedEventType: - return u.fillLoginNamesOnOrgUsers(event) - case org.OrgDomainPrimarySetEventType: - return u.fillPreferredLoginNamesOnOrgUsers(event) - case org.OrgRemovedEventType: - return u.view.UpdateOrgOwnerRemovedUsers(event) - default: - return nil - } - }), nil -} - -func (u *User) ProcessInstance(event eventstore.Event) (_ *handler.Statement, err error) { + // in case anything needs to be change here check if appendEvent function needs the change as well switch event.Type() { - case instance.InstanceRemovedEventType: - return handler.NewStatement(event, - func(ex handler.Executer, projectionName string) error { - return u.view.DeleteInstanceUsers(event) + case org.OrgRemovedEventType: + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserKeyResourceOwner, event.Aggregate().ID), }, ), nil default: @@ -469,97 +291,16 @@ func (u *User) ProcessInstance(event eventstore.Event) (_ *handler.Statement, er } } -func (u *User) fillLoginNamesOnOrgUsers(event eventstore.Event) error { - userLoginMustBeDomain, _, domains, err := u.loginNameInformation(context.Background(), event.Aggregate().ResourceOwner, event.Aggregate().InstanceID) - if err != nil { - return err +func (u *User) ProcessInstance(event eventstore.Event) (_ *handler.Statement, err error) { + // in case anything needs to be change here check if appendEvent function needs the change as well + switch event.Type() { + case instance.InstanceRemovedEventType: + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID), + }, + ), nil + default: + return handler.NewNoOpStatement(event), nil } - users, err := u.view.UsersByOrgID(event.Aggregate().ID, event.Aggregate().InstanceID) - if err != nil { - return err - } - for _, user := range users { - user.SetLoginNames(userLoginMustBeDomain, domains) - } - return u.view.PutUsers(users, event) -} - -func (u *User) fillPreferredLoginNamesOnOrgUsers(event eventstore.Event) error { - userLoginMustBeDomain, primaryDomain, _, err := u.loginNameInformation(context.Background(), event.Aggregate().ResourceOwner, event.Aggregate().InstanceID) - if err != nil { - return err - } - if !userLoginMustBeDomain { - return nil - } - users, err := u.view.UsersByOrgID(event.Aggregate().ID, event.Aggregate().InstanceID) - if err != nil { - return err - } - for _, user := range users { - user.PreferredLoginName = user.GenerateLoginName(primaryDomain, userLoginMustBeDomain) - } - return u.view.PutUsers(users, event) -} - -func (u *User) getOrgByID(ctx context.Context, orgID, instanceID string) (*org_model.Org, error) { - query, err := org_view.OrgByIDQuery(orgID, instanceID, 0) - if err != nil { - return nil, err - } - - esOrg := &org_es_model.Org{ - ObjectRoot: es_models.ObjectRoot{ - AggregateID: orgID, - }, - } - events, err := u.es.Filter(ctx, query) - if err != nil { - return nil, err - } - if err = esOrg.AppendEvents(events...); err != nil { - return nil, err - } - if esOrg.Sequence == 0 { - return nil, zerrors.ThrowNotFound(nil, "EVENT-3m9vs", "Errors.Org.NotFound") - } - - return org_es_model.OrgToModel(esOrg), nil -} - -func (u *User) loginNameInformation(ctx context.Context, orgID string, instanceID string) (userLoginMustBeDomain bool, primaryDomain string, domains []*org_model.OrgDomain, err error) { - org, err := u.getOrgByID(ctx, orgID, instanceID) - if err != nil { - return false, "", nil, err - } - primaryDomain, err = org.GetPrimaryDomain() - if err != nil { - return false, "", nil, err - } - if org.DomainPolicy != nil { - return org.DomainPolicy.UserLoginMustBeDomain, primaryDomain, org.Domains, nil - } - policy, err := u.queries.DefaultDomainPolicy(authz.WithInstanceID(ctx, org.InstanceID)) - if err != nil { - return false, "", nil, err - } - return policy.UserLoginMustBeDomain, primaryDomain, org.Domains, nil -} - -func (u *User) userFromEventstore(agg *eventstore.Aggregate, eventTypes []eventstore.EventType) (*view_model.UserView, error) { - query, err := usr_view.UserByIDQuery(agg.ID, agg.InstanceID, time.Time{}, eventTypes) - if err != nil { - return nil, err - } - events, err := u.es.Filter(context.Background(), query) - if err != nil { - return nil, err - } - user := &view_model.UserView{} - for _, e := range events { - if err = user.AppendEvent(e); err != nil { - return nil, err - } - } - return user, nil } diff --git a/internal/auth/repository/eventsourcing/handler/user_session.go b/internal/auth/repository/eventsourcing/handler/user_session.go index fc2285684..de97a2062 100644 --- a/internal/auth/repository/eventsourcing/handler/user_session.go +++ b/internal/auth/repository/eventsourcing/handler/user_session.go @@ -12,8 +12,8 @@ import ( "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/user" + es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model" view_model "github.com/zitadel/zitadel/internal/user/repository/view/model" - "github.com/zitadel/zitadel/internal/zerrors" ) const ( @@ -187,64 +187,160 @@ func (s *UserSession) Reducers() []handler.AggregateReducer { } } +func sessionColumns(event eventstore.Event, columns ...handler.Column) ([]handler.Column, error) { + userAgent, err := agentIDFromSession(event) + if err != nil { + return nil, err + } + return append([]handler.Column{ + handler.NewCol(view_model.UserSessionKeyUserAgentID, userAgent), + handler.NewCol(view_model.UserSessionKeyUserID, event.Aggregate().ID), + handler.NewCol(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCol(view_model.UserSessionKeyCreationDate, handler.OnlySetValueOnInsert(userSessionTable, event.CreatedAt())), + handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeyResourceOwner, event.Aggregate().ResourceOwner), + handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()), + }, columns...), nil +} + func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err error) { + // in case anything needs to be change here check if appendEvent function needs the change as well switch event.Type() { case user.UserV1PasswordCheckSucceededType, - user.UserV1PasswordCheckFailedType, - user.UserV1MFAOTPCheckSucceededType, - user.UserV1MFAOTPCheckFailedType, - user.UserV1SignedOutType, - user.HumanPasswordCheckSucceededType, - user.HumanPasswordCheckFailedType, - user.UserIDPLoginCheckSucceededType, - user.HumanMFAOTPCheckSucceededType, + user.HumanPasswordCheckSucceededType: + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive), + ) + if err != nil { + return nil, err + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil + case user.UserV1PasswordCheckFailedType, + user.HumanPasswordCheckFailedType: + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}), + ) + if err != nil { + return nil, err + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil + case user.UserV1MFAOTPCheckSucceededType, + user.HumanMFAOTPCheckSucceededType: + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeTOTP), + handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive), + ) + if err != nil { + return nil, err + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil + case user.UserV1MFAOTPCheckFailedType, user.HumanMFAOTPCheckFailedType, - user.HumanU2FTokenCheckSucceededType, - user.HumanU2FTokenCheckFailedType, - user.HumanPasswordlessTokenCheckSucceededType, - user.HumanPasswordlessTokenCheckFailedType, + user.HumanU2FTokenCheckFailedType: + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}), + ) + if err != nil { + return nil, err + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil + case user.UserV1SignedOutType, user.HumanSignedOutType: - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - var session *view_model.UserSessionView - eventData, err := view_model.UserSessionFromEvent(event) - if err != nil { - return err - } - session, err = u.view.UserSessionByIDs(eventData.UserAgentID, event.Aggregate().ID, event.Aggregate().InstanceID) - if err != nil { - if !zerrors.IsNotFound(err) { - return err - } - session = &view_model.UserSessionView{ - CreationDate: event.CreatedAt(), - ResourceOwner: event.Aggregate().ResourceOwner, - UserAgentID: eventData.UserAgentID, - UserID: event.Aggregate().ID, - State: int32(domain.UserSessionStateActive), - InstanceID: event.Aggregate().InstanceID, - } - } - return u.updateSession(session, event) - }), nil + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFALevelNotSetUp), + handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFALevelNotSetUp), + handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateTerminated), + ) + if err != nil { + return nil, err + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil + case user.UserIDPLoginCheckSucceededType: + data := new(es_model.AuthRequest) + err := data.SetData(event) + if err != nil { + return nil, err + } + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, data.SelectedIDPConfigID), + handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive), + ) + if err != nil { + return nil, err + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil + case user.HumanU2FTokenCheckSucceededType: + data := new(es_model.AuthRequest) + err := data.SetData(event) + if err != nil { + return nil, err + } + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeySecondFactorVerification, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFATypeU2F), + handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive), + ) + if err != nil { + return nil, err + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil + case user.HumanPasswordlessTokenCheckSucceededType: + data := new(es_model.AuthRequest) + err := data.SetData(event) + if err != nil { + return nil, err + } + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFATypeU2FUserVerification), + handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive), + ) + if err != nil { + return nil, err + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil + case user.HumanPasswordlessTokenCheckFailedType: + data := new(es_model.AuthRequest) + err := data.SetData(event) + if err != nil { + return nil, err + } + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}), + ) + if err != nil { + return nil, err + } + return handler.NewUpsertStatement(event, columns[0:3], columns), nil case user.UserLockedType, user.UserDeactivatedType: return handler.NewUpdateStatement(event, []handler.Column{ - handler.NewCol("passwordless_verification", time.Time{}), - handler.NewCol("password_verification", time.Time{}), - handler.NewCol("second_factor_verification", time.Time{}), - handler.NewCol("second_factor_verification_type", domain.MFALevelNotSetUp), - handler.NewCol("multi_factor_verification", time.Time{}), - handler.NewCol("multi_factor_verification_type", domain.MFALevelNotSetUp), - handler.NewCol("external_login_verification", time.Time{}), - handler.NewCol("state", domain.UserSessionStateTerminated), - handler.NewCol("change_date", event.CreatedAt()), - handler.NewCol("sequence", event.Sequence()), + handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFALevelNotSetUp), + handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFALevelNotSetUp), + handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateTerminated), + handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()), }, []handler.Condition{ - handler.NewCond("instance_id", event.Aggregate().InstanceID), - handler.NewCond("user_id", event.Aggregate().ID), - handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)), + handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID), + handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)), }, ), nil case user.UserV1PasswordChangedType, @@ -256,26 +352,26 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err return handler.NewMultiStatement(event, handler.AddUpdateStatement( []handler.Column{ - handler.NewCol("password_verification", event.CreatedAt()), - handler.NewCol("change_date", event.CreatedAt()), - handler.NewCol("sequence", event.Sequence()), + handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()), }, []handler.Condition{ - handler.NewCond("instance_id", event.Aggregate().InstanceID), - handler.NewCond("user_id", event.Aggregate().ID), - handler.NewCond("user_agent_id", userAgent), + handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID), + handler.NewCond(view_model.UserSessionKeyUserAgentID, userAgent), }), handler.AddUpdateStatement( []handler.Column{ - handler.NewCol("password_verification", time.Time{}), - handler.NewCol("change_date", event.CreatedAt()), - handler.NewCol("sequence", event.Sequence()), + handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()), }, []handler.Condition{ - handler.NewCond("instance_id", event.Aggregate().InstanceID), - handler.NewCond("user_id", event.Aggregate().ID), - handler.Not(handler.NewCond("user_agent_id", userAgent)), - handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)), + handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID), + handler.Not(handler.NewCond(view_model.UserSessionKeyUserAgentID, userAgent)), + handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)), }), ), nil case user.UserV1MFAOTPRemovedType, @@ -283,84 +379,77 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err user.HumanU2FTokenRemovedType: return handler.NewUpdateStatement(event, []handler.Column{ - handler.NewCol("second_factor_verification", time.Time{}), - handler.NewCol("change_date", event.CreatedAt()), - handler.NewCol("sequence", event.Sequence()), + handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()), }, []handler.Condition{ - handler.NewCond("instance_id", event.Aggregate().InstanceID), - handler.NewCond("user_id", event.Aggregate().ID), - handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)), + handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID), + handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)), }, ), nil case user.UserIDPLinkRemovedType, user.UserIDPLinkCascadeRemovedType: return handler.NewUpdateStatement(event, []handler.Column{ - handler.NewCol("external_login_verification", time.Time{}), - handler.NewCol("selected_idp_config_id", ""), - handler.NewCol("change_date", event.CreatedAt()), - handler.NewCol("sequence", event.Sequence()), + handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, ""), + handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()), }, []handler.Condition{ - handler.NewCond("instance_id", event.Aggregate().InstanceID), - handler.NewCond("user_id", event.Aggregate().ID), - handler.Not(handler.NewCond("selected_idp_config_id", "")), + handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID), + handler.Not(handler.NewCond(view_model.UserSessionKeySelectedIDPConfigID, "")), }, ), nil case user.HumanPasswordlessTokenRemovedType: return handler.NewUpdateStatement(event, []handler.Column{ - handler.NewCol("passwordless_verification", time.Time{}), - handler.NewCol("multi_factor_verification", time.Time{}), - handler.NewCol("change_date", event.CreatedAt()), - handler.NewCol("sequence", event.Sequence()), + handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}), + handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()), + handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()), }, []handler.Condition{ - handler.NewCond("instance_id", event.Aggregate().InstanceID), - handler.NewCond("user_id", event.Aggregate().ID), - handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)), + handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID), + handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)), }, ), nil case user.UserRemovedType: - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - return u.view.DeleteUserSessions(event.Aggregate().ID, event.Aggregate().InstanceID) - }), nil + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID), + }, + ), nil case user.HumanRegisteredType: - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - eventData, err := view_model.UserSessionFromEvent(event) - if err != nil { - return err - } - session := &view_model.UserSessionView{ - CreationDate: event.CreatedAt(), - ResourceOwner: event.Aggregate().ResourceOwner, - UserAgentID: eventData.UserAgentID, - UserID: event.Aggregate().ID, - State: int32(domain.UserSessionStateActive), - InstanceID: event.Aggregate().InstanceID, - PasswordVerification: event.CreatedAt(), - } - return u.updateSession(session, event) - }), nil + columns, err := sessionColumns(event, + handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive), + handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()), + ) + if err != nil { + return nil, err + } + return handler.NewCreateStatement(event, + columns, + ), nil case instance.InstanceRemovedEventType: - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - return u.view.DeleteInstanceUserSessions(event.Aggregate().InstanceID) - }), nil + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + }, + ), nil case org.OrgRemovedEventType: - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - return u.view.DeleteOrgUserSessions(event) - }), nil + return handler.NewDeleteStatement(event, + []handler.Condition{ + handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID), + handler.NewCond(view_model.UserSessionKeyResourceOwner, event.Aggregate().ResourceOwner), + }, + ), nil default: - return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { - return nil - }), nil + return handler.NewNoOpStatement(event), nil } } - -func (u *UserSession) updateSession(session *view_model.UserSessionView, event eventstore.Event) error { - if err := session.AppendEvent(event); err != nil { - return err - } - return u.view.PutUserSession(session) -} diff --git a/internal/auth/repository/eventsourcing/view/refresh_token.go b/internal/auth/repository/eventsourcing/view/refresh_token.go index b45f769cb..ce268ba13 100644 --- a/internal/auth/repository/eventsourcing/view/refresh_token.go +++ b/internal/auth/repository/eventsourcing/view/refresh_token.go @@ -4,13 +4,10 @@ import ( "context" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/query" user_model "github.com/zitadel/zitadel/internal/user/model" usr_view "github.com/zitadel/zitadel/internal/user/repository/view" "github.com/zitadel/zitadel/internal/user/repository/view/model" - "github.com/zitadel/zitadel/internal/zerrors" ) const ( @@ -29,54 +26,6 @@ func (v *View) SearchRefreshTokens(request *user_model.RefreshTokenSearchRequest return usr_view.SearchRefreshTokens(v.Db, refreshTokenTable, request) } -func (v *View) PutRefreshToken(token *model.RefreshTokenView) error { - return usr_view.PutRefreshToken(v.Db, refreshTokenTable, token) -} - -func (v *View) PutRefreshTokens(token []*model.RefreshTokenView) error { - return usr_view.PutRefreshTokens(v.Db, refreshTokenTable, token...) -} - -func (v *View) DeleteRefreshToken(tokenID, instanceID string) error { - err := usr_view.DeleteRefreshToken(v.Db, refreshTokenTable, tokenID, instanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteUserRefreshTokens(userID, instanceID string) error { - err := usr_view.DeleteUserRefreshTokens(v.Db, refreshTokenTable, userID, instanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteApplicationRefreshTokens(event *models.Event, ids ...string) error { - err := usr_view.DeleteApplicationTokens(v.Db, refreshTokenTable, event.InstanceID, ids) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteInstanceRefreshTokens(instanceID string) error { - err := usr_view.DeleteInstanceRefreshTokens(v.Db, refreshTokenTable, instanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteOrgRefreshTokens(event eventstore.Event) error { - err := usr_view.DeleteOrgRefreshTokens(v.Db, refreshTokenTable, event.Aggregate().InstanceID, event.Aggregate().ResourceOwner) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - func (v *View) GetLatestRefreshTokenSequence(ctx context.Context) (_ *query.CurrentState, err error) { q := &query.CurrentStateSearchQueries{ Queries: make([]query.SearchQuery, 2), diff --git a/internal/auth/repository/eventsourcing/view/token.go b/internal/auth/repository/eventsourcing/view/token.go index 0fe1d6d18..92713dc28 100644 --- a/internal/auth/repository/eventsourcing/view/token.go +++ b/internal/auth/repository/eventsourcing/view/token.go @@ -3,12 +3,10 @@ package view import ( "context" - "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/telemetry/tracing" usr_view "github.com/zitadel/zitadel/internal/user/repository/view" "github.com/zitadel/zitadel/internal/user/repository/view/model" - "github.com/zitadel/zitadel/internal/zerrors" ) const ( @@ -23,70 +21,6 @@ func (v *View) TokensByUserID(userID, instanceID string) ([]*model.TokenView, er return usr_view.TokensByUserID(v.Db, tokenTable, userID, instanceID) } -func (v *View) PutToken(token *model.TokenView) error { - return usr_view.PutToken(v.Db, tokenTable, token) -} - -func (v *View) PutTokens(token []*model.TokenView) error { - return usr_view.PutTokens(v.Db, tokenTable, token...) -} - -func (v *View) DeleteToken(tokenID, instanceID string) error { - err := usr_view.DeleteToken(v.Db, tokenTable, tokenID, instanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteSessionTokens(agentID string, event eventstore.Event) error { - err := usr_view.DeleteSessionTokens(v.Db, tokenTable, agentID, event.Aggregate().ID, event.Aggregate().InstanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteUserTokens(event eventstore.Event) error { - err := usr_view.DeleteUserTokens(v.Db, tokenTable, event.Aggregate().ID, event.Aggregate().InstanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteApplicationTokens(event eventstore.Event, ids ...string) error { - err := usr_view.DeleteApplicationTokens(v.Db, tokenTable, event.Aggregate().InstanceID, ids) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteTokensFromRefreshToken(refreshTokenID, instanceID string) error { - err := usr_view.DeleteTokensFromRefreshToken(v.Db, tokenTable, refreshTokenID, instanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteInstanceTokens(event eventstore.Event) error { - err := usr_view.DeleteInstanceTokens(v.Db, tokenTable, event.Aggregate().InstanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteOrgTokens(event eventstore.Event) error { - err := usr_view.DeleteOrgTokens(v.Db, tokenTable, event.Aggregate().InstanceID, event.Aggregate().ResourceOwner) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - func (v *View) GetLatestTokenSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/auth/repository/eventsourcing/view/user.go b/internal/auth/repository/eventsourcing/view/user.go index 3c1c3ddcc..e75846471 100644 --- a/internal/auth/repository/eventsourcing/view/user.go +++ b/internal/auth/repository/eventsourcing/view/user.go @@ -5,7 +5,6 @@ import ( "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/query" usr_model "github.com/zitadel/zitadel/internal/user/model" "github.com/zitadel/zitadel/internal/user/repository/view" @@ -14,7 +13,7 @@ import ( ) const ( - userTable = "auth.users2" + userTable = "auth.users3" ) func (v *View) UserByID(userID, instanceID string) (*model.UserView, error) { @@ -142,42 +141,6 @@ func (v *View) userByID(ctx context.Context, instanceID string, queries ...query return user, nil } -func (v *View) UsersByOrgID(orgID, instanceID string) ([]*model.UserView, error) { - return view.UsersByOrgID(v.Db, userTable, orgID, instanceID) -} - -func (v *View) PutUser(user *model.UserView, event eventstore.Event) error { - return view.PutUser(v.Db, userTable, user) -} - -func (v *View) PutUsers(users []*model.UserView, event eventstore.Event) error { - return view.PutUsers(v.Db, userTable, users...) -} - -func (v *View) DeleteUser(userID, instanceID string, event eventstore.Event) error { - err := view.DeleteUser(v.Db, userTable, userID, instanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteInstanceUsers(event eventstore.Event) error { - err := view.DeleteInstanceUsers(v.Db, userTable, event.Aggregate().InstanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) UpdateOrgOwnerRemovedUsers(event eventstore.Event) error { - err := view.UpdateOrgOwnerRemovedUsers(v.Db, userTable, event.Aggregate().InstanceID, event.Aggregate().ID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - func (v *View) GetLatestUserSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) { q := &query.CurrentStateSearchQueries{ Queries: make([]query.SearchQuery, 2), diff --git a/internal/auth/repository/eventsourcing/view/user_session.go b/internal/auth/repository/eventsourcing/view/user_session.go index 59d2a967a..893e5872a 100644 --- a/internal/auth/repository/eventsourcing/view/user_session.go +++ b/internal/auth/repository/eventsourcing/view/user_session.go @@ -3,11 +3,9 @@ package view import ( "context" - "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/user/repository/view" "github.com/zitadel/zitadel/internal/user/repository/view/model" - "github.com/zitadel/zitadel/internal/zerrors" ) const ( @@ -22,34 +20,6 @@ func (v *View) UserSessionsByAgentID(agentID, instanceID string) ([]*model.UserS return view.UserSessionsByAgentID(v.client, agentID, instanceID) } -func (v *View) PutUserSession(userSession *model.UserSessionView) error { - return view.PutUserSession(v.Db, userSessionTable, userSession) -} - -func (v *View) DeleteUserSessions(userID, instanceID string) error { - err := view.DeleteUserSessions(v.Db, userSessionTable, userID, instanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteInstanceUserSessions(instanceID string) error { - err := view.DeleteInstanceUserSessions(v.Db, userSessionTable, instanceID) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - -func (v *View) DeleteOrgUserSessions(event eventstore.Event) error { - err := view.DeleteOrgUserSessions(v.Db, userSessionTable, event.Aggregate().InstanceID, event.Aggregate().ResourceOwner) - if err != nil && !zerrors.IsNotFound(err) { - return err - } - return nil -} - func (v *View) GetLatestUserSessionSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) { q := &query.CurrentStateSearchQueries{ Queries: make([]query.SearchQuery, 2), diff --git a/internal/eventstore/handler/v2/statement.go b/internal/eventstore/handler/v2/statement.go index 802bacc06..2eb919308 100644 --- a/internal/eventstore/handler/v2/statement.go +++ b/internal/eventstore/handler/v2/statement.go @@ -565,6 +565,13 @@ func Not(condition Condition) Condition { } } +// NewOneOfTextCond returns a Condition that checks if the column that stores a text is one of the given values +func NewOneOfTextCond(column string, values []string) Condition { + return func(param string) (string, []any) { + return column + " = ANY(" + param + ")", []any{database.TextArray[string](values)} + } +} + type Executer interface { Exec(string, ...interface{}) (sql.Result, error) } diff --git a/internal/user/repository/view/model/refresh_token.go b/internal/user/repository/view/model/refresh_token.go index 06b446362..23766c7b3 100644 --- a/internal/user/repository/view/model/refresh_token.go +++ b/internal/user/repository/view/model/refresh_token.go @@ -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 diff --git a/internal/user/repository/view/model/token.go b/internal/user/repository/view/model/token.go index ebe156473..a780e7e16 100644 --- a/internal/user/repository/view/model/token.go +++ b/internal/user/repository/view/model/token.go @@ -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{}) +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.Log("EVEN-Dbffe").WithError(err).Error("could not unmarshal event data") - return nil, zerrors.ThrowInternal(nil, "MODEL-SDAfw", "could not unmarshal data") + logging.WithError(err).Error("could not unmarshal event data") + return "", zerrors.ThrowInternal(nil, "MODEL-SDAfw", "could not unmarshal data") } - return m, nil + return m.ID, nil } diff --git a/internal/user/repository/view/model/user.go b/internal/user/repository/view/model/user.go index 1beb4b323..62c64edc0 100644 --- a/internal/user/repository/view/model/user.go +++ b/internal/user/repository/view/model/user.go @@ -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 != "" diff --git a/internal/user/repository/view/model/user_session.go b/internal/user/repository/view/model/user_session.go index f4f4b9862..4208ea5b5 100644 --- a/internal/user/repository/view/model/user_session.go +++ b/internal/user/repository/view/model/user_session.go @@ -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) } diff --git a/internal/user/repository/view/model/user_session_test.go b/internal/user/repository/view/model/user_session_test.go index 8f3157f98..25acd489c 100644 --- a/internal/user/repository/view/model/user_session_test.go +++ b/internal/user/repository/view/model/user_session_test.go @@ -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 { diff --git a/internal/user/repository/view/refresh_token_view.go b/internal/user/repository/view/refresh_token_view.go index 642dfc101..bc8c8b79a 100644 --- a/internal/user/repository/view/refresh_token_view.go +++ b/internal/user/repository/view/refresh_token_view.go @@ -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) -} diff --git a/internal/user/repository/view/token_view.go b/internal/user/repository/view/token_view.go index 72a0d164a..9c681c1f6 100644 --- a/internal/user/repository/view/token_view.go +++ b/internal/user/repository/view/token_view.go @@ -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) -} diff --git a/internal/user/repository/view/user_by_id.sql b/internal/user/repository/view/user_by_id.sql new file mode 100644 index 000000000..fa85fc43f --- /dev/null +++ b/internal/user/repository/view/user_by_id.sql @@ -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; \ No newline at end of file diff --git a/internal/user/repository/view/user_session_view.go b/internal/user/repository/view/user_session_view.go index 3afd3c16b..2de623379 100644 --- a/internal/user/repository/view/user_session_view.go +++ b/internal/user/repository/view/user_session_view.go @@ -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) } diff --git a/internal/user/repository/view/user_view.go b/internal/user/repository/view/user_view.go index 0b0aeba47..98b23f466 100644 --- a/internal/user/repository/view/user_view.go +++ b/internal/user/repository/view/user_view.go @@ -1,98 +1,42 @@ package view import ( + "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") }