fix(login): improve auth handlers (#7969)

# Which Problems Are Solved

During the implementation of #7486 it was noticed, that projections in
the `auth` database schema could be blocked.
Investigations suggested, that this is due to the use of
[GORM](https://gorm.io/index.html) and it's inability to use an existing
(sql) transaction.
With the improved / simplified handling (see below) there should also be
a minimal improvement in performance, resp. reduced database update
statements.

# How the Problems Are Solved

The handlers in `auth` are exchanged to proper (sql) statements and gorm
usage is removed for any writing part.
To further improve / simplify the handling of the users, a new
`auth.users3` table is created, where only attributes are handled, which
are not yet available from the `projections.users`,
`projections.login_name` and `projections.user_auth_methods` do not
provide. This reduces the events handled in that specific handler by a
lot.

# Additional Changes

None

# Additional Context

relates to #7486
This commit is contained in:
Livio Spring
2024-05-22 17:26:02 +02:00
committed by GitHub
parent cca342187b
commit fb162a7d75
25 changed files with 987 additions and 1279 deletions

27
cmd/setup/26.go Normal file
View File

@@ -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"
}

16
cmd/setup/26.sql Normal file
View File

@@ -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)
)

View File

@@ -108,6 +108,7 @@ type Steps struct {
s23CorrectGlobalUniqueConstraints *CorrectGlobalUniqueConstraints s23CorrectGlobalUniqueConstraints *CorrectGlobalUniqueConstraints
s24AddActorToAuthTokens *AddActorToAuthTokens s24AddActorToAuthTokens *AddActorToAuthTokens
s25User11AddLowerFieldsToVerifiedEmail *User11AddLowerFieldsToVerifiedEmail s25User11AddLowerFieldsToVerifiedEmail *User11AddLowerFieldsToVerifiedEmail
s26AuthUsers3 *AuthUsers3
} }
func MustNewSteps(v *viper.Viper) *Steps { func MustNewSteps(v *viper.Viper) *Steps {

View File

@@ -138,6 +138,7 @@ func Setup(config *Config, steps *Steps, masterKey string) {
steps.s23CorrectGlobalUniqueConstraints = &CorrectGlobalUniqueConstraints{dbClient: esPusherDBClient} steps.s23CorrectGlobalUniqueConstraints = &CorrectGlobalUniqueConstraints{dbClient: esPusherDBClient}
steps.s24AddActorToAuthTokens = &AddActorToAuthTokens{dbClient: queryDBClient} steps.s24AddActorToAuthTokens = &AddActorToAuthTokens{dbClient: queryDBClient}
steps.s25User11AddLowerFieldsToVerifiedEmail = &User11AddLowerFieldsToVerifiedEmail{dbClient: esPusherDBClient} steps.s25User11AddLowerFieldsToVerifiedEmail = &User11AddLowerFieldsToVerifiedEmail{dbClient: esPusherDBClient}
steps.s26AuthUsers3 = &AuthUsers3{dbClient: esPusherDBClient}
err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil) err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil)
logging.OnError(err).Fatal("unable to start projections") logging.OnError(err).Fatal("unable to start projections")
@@ -175,6 +176,7 @@ func Setup(config *Config, steps *Steps, masterKey string) {
steps.s22ActiveInstancesIndex, steps.s22ActiveInstancesIndex,
steps.s23CorrectGlobalUniqueConstraints, steps.s23CorrectGlobalUniqueConstraints,
steps.s24AddActorToAuthTokens, steps.s24AddActorToAuthTokens,
steps.s26AuthUsers3,
} { } {
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed") mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
} }

View File

@@ -1517,12 +1517,12 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
user_repo.HumanPasswordlessTokenCheckFailedType, user_repo.HumanPasswordlessTokenCheckFailedType,
user_repo.HumanU2FTokenCheckSucceededType, user_repo.HumanU2FTokenCheckSucceededType,
user_repo.HumanU2FTokenCheckFailedType: user_repo.HumanU2FTokenCheckFailedType:
eventData, err := user_view_model.UserSessionFromEvent(event) userAgentID, err := user_view_model.UserAgentIDFromEvent(event)
if err != nil { if err != nil {
logging.WithFields("traceID", tracing.TraceIDFromCtx(ctx)).WithError(err).Debug("error getting event data") logging.WithFields("traceID", tracing.TraceIDFromCtx(ctx)).WithError(err).Debug("error getting event data")
return user_view_model.UserSessionToModel(session), nil return user_view_model.UserSessionToModel(session), nil
} }
if eventData.UserAgentID != agentID { if userAgentID != agentID {
continue continue
} }
case user_repo.UserRemovedType: case user_repo.UserRemovedType:

View File

@@ -2,6 +2,7 @@ package eventstore
import ( import (
"context" "context"
"database/sql"
"encoding/json" "encoding/json"
"testing" "testing"
"time" "time"
@@ -75,11 +76,11 @@ type mockUser struct {
func (m *mockViewUserSession) UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) { func (m *mockViewUserSession) UserSessionByIDs(string, string, string) (*user_view_model.UserSessionView, error) {
return &user_view_model.UserSessionView{ return &user_view_model.UserSessionView{
ExternalLoginVerification: m.ExternalLoginVerification, ExternalLoginVerification: sql.NullTime{Time: m.ExternalLoginVerification},
PasswordlessVerification: m.PasswordlessVerification, PasswordlessVerification: sql.NullTime{Time: m.PasswordlessVerification},
PasswordVerification: m.PasswordVerification, PasswordVerification: sql.NullTime{Time: m.PasswordVerification},
SecondFactorVerification: m.SecondFactorVerification, SecondFactorVerification: sql.NullTime{Time: m.SecondFactorVerification},
MultiFactorVerification: m.MultiFactorVerification, MultiFactorVerification: sql.NullTime{Time: m.MultiFactorVerification},
}, nil }, nil
} }
@@ -90,7 +91,7 @@ func (m *mockViewUserSession) UserSessionsByAgentID(string, string) ([]*user_vie
ResourceOwner: user.ResourceOwner, ResourceOwner: user.ResourceOwner,
State: int32(user.SessionState), State: int32(user.SessionState),
UserID: user.UserID, UserID: user.UserID,
LoginName: user.LoginName, LoginName: sql.NullString{String: user.LoginName},
} }
} }
return sessions, nil return sessions, nil

View File

@@ -3,8 +3,6 @@ package handler
import ( import (
"context" "context"
"github.com/zitadel/logging"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view" auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2" "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) { func (t *RefreshToken) Reduce(event eventstore.Event) (_ *handler.Statement, err error) {
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { // in case anything needs to be change here check if appendEvent function needs the change as well
switch event.Type() { switch event.Type() {
case user.HumanRefreshTokenAddedType: case user.HumanRefreshTokenAddedType:
token := new(view_model.RefreshTokenView) e, ok := event.(*user.HumanRefreshTokenAddedEvent)
err := token.AppendEvent(event) if !ok {
if err != nil { return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-IoF6j", "reduce.wrong.event.type %s", user.HumanRefreshTokenAddedType)
return err
} }
columns := []handler.Column{
return t.view.PutRefreshToken(token) 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: case user.HumanRefreshTokenRenewedType:
e := new(user.HumanRefreshTokenRenewedEvent) e, ok := event.(*user.HumanRefreshTokenRenewedEvent)
if err := event.Unmarshal(e); err != nil { if !ok {
logging.WithError(err).Error("could not unmarshal event data") return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-AG43hq", "reduce.wrong.event.type %s", user.HumanRefreshTokenRenewedType)
return zerrors.ThrowInternal(nil, "MODEL-BHn75", "could not unmarshal data")
} }
token, err := t.view.RefreshTokenByID(e.TokenID, event.Aggregate().InstanceID) return handler.NewUpdateStatement(event,
if err != nil { []handler.Column{
return err handler.NewCol(view_model.RefreshTokenKeyIdleExpiration, event.CreatedAt().Add(e.IdleExpiration)),
} handler.NewCol(view_model.RefreshTokenKeyToken, e.RefreshToken),
err = token.AppendEvent(event) handler.NewCol(view_model.RefreshTokenKeyChangeDate, e.CreatedAt()),
if err != nil { handler.NewCol(view_model.RefreshTokenKeySequence, event.Sequence()),
return err },
} []handler.Condition{
return t.view.PutRefreshToken(token) handler.NewCond(view_model.RefreshTokenKeyTokenID, e.TokenID),
handler.NewCond(view_model.RefreshTokenKeyInstanceID, e.Aggregate().InstanceID),
},
), nil
case user.HumanRefreshTokenRemovedType: case user.HumanRefreshTokenRemovedType:
e := new(user.HumanRefreshTokenRemovedEvent) e, ok := event.(*user.HumanRefreshTokenRemovedEvent)
if err := event.Unmarshal(e); err != nil { if !ok {
logging.WithError(err).Error("could not unmarshal event data") return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SFF3t", "reduce.wrong.event.type %s", user.HumanRefreshTokenRemovedType)
return zerrors.ThrowInternal(nil, "MODEL-Bz653", "could not unmarshal data")
} }
return t.view.DeleteRefreshToken(e.TokenID, event.Aggregate().InstanceID) 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, case user.UserLockedType,
user.UserDeactivatedType, user.UserDeactivatedType,
user.UserRemovedType: user.UserRemovedType:
return handler.NewDeleteStatement(event,
return t.view.DeleteUserRefreshTokens(event.Aggregate().ID, event.Aggregate().InstanceID) []handler.Condition{
handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.RefreshTokenKeyUserID, event.Aggregate().ID),
},
), nil
case instance.InstanceRemovedEventType: case instance.InstanceRemovedEventType:
return handler.NewDeleteStatement(event,
return t.view.DeleteInstanceRefreshTokens(event.Aggregate().InstanceID) []handler.Condition{
handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID),
},
), nil
case org.OrgRemovedEventType: case org.OrgRemovedEventType:
return t.view.DeleteOrgRefreshTokens(event) return handler.NewDeleteStatement(event,
[]handler.Condition{
handler.NewCond(view_model.RefreshTokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.RefreshTokenKeyResourceOwner, event.Aggregate().ResourceOwner),
},
), nil
default: default:
return nil return handler.NewNoOpStatement(event), nil
} }
}), nil
} }

View File

@@ -3,6 +3,7 @@ package handler
import ( import (
"context" "context"
"github.com/muhlemmer/gu"
"github.com/zitadel/logging" "github.com/zitadel/logging"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view" auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view"
@@ -150,72 +151,144 @@ func (t *Token) Reducers() []handler.AggregateReducer {
} }
func (t *Token) Reduce(event eventstore.Event) (_ *handler.Statement, err error) { //nolint:gocognit func (t *Token) Reduce(event eventstore.Event) (_ *handler.Statement, err error) { //nolint:gocognit
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { // in case anything needs to be change here check if appendEvent function needs the change as well
switch event.Type() { switch event.Type() {
case user.UserTokenAddedType, case user.UserTokenAddedType:
user.PersonalAccessTokenAddedType: e, ok := event.(*user.UserTokenAddedEvent)
token := new(view_model.TokenView) if !ok {
err := token.AppendEvent(event) return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-W4tnq", "reduce.wrong.event.type %s", user.UserTokenAddedType)
if err != nil {
return err
} }
return t.view.PutToken(token) 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, case user.UserV1ProfileChangedType,
user.HumanProfileChangedType: user.HumanProfileChangedType:
user := new(view_model.UserView) e, ok := event.(*user.HumanProfileChangedEvent)
err := user.AppendEvent(event) if !ok {
if err != nil { return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-ASF2t", "reduce.wrong.event.type %s", user.HumanProfileChangedType)
return err
} }
tokens, err := t.view.TokensByUserID(event.Aggregate().ID, event.Aggregate().InstanceID) if e.PreferredLanguage == nil {
if err != nil { return handler.NewNoOpStatement(event), nil
return err
} }
for _, token := range tokens { return handler.NewUpdateStatement(event,
token.PreferredLanguage = user.PreferredLanguage []handler.Column{
} handler.NewCol(view_model.TokenKeyPreferredLanguage, gu.Value(e.PreferredLanguage).String()),
return t.view.PutTokens(tokens) 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, case user.UserV1SignedOutType,
user.HumanSignedOutType: user.HumanSignedOutType:
id, err := agentIDFromSession(event) e, ok := event.(*user.HumanSignedOutEvent)
if err != nil { if !ok {
return err return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-Wtn2q", "reduce.wrong.event.type %s", user.HumanSignedOutType)
} }
return handler.NewDeleteStatement(event,
return t.view.DeleteSessionTokens(id, 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, case user.UserLockedType,
user.UserDeactivatedType, user.UserDeactivatedType,
user.UserRemovedType: user.UserRemovedType:
return handler.NewDeleteStatement(event,
return t.view.DeleteUserTokens(event) []handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyUserID, event.Aggregate().ID),
},
), nil
case user.UserTokenRemovedType, case user.UserTokenRemovedType,
user.PersonalAccessTokenRemovedType: user.PersonalAccessTokenRemovedType:
id, err := tokenIDFromRemovedEvent(event) var tokenID string
if err != nil { switch e := event.(type) {
return err 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,
return t.view.DeleteToken(id, event.Aggregate().InstanceID) []handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyID, tokenID),
},
), nil
case user.HumanRefreshTokenRemovedType: case user.HumanRefreshTokenRemovedType:
id, err := refreshTokenIDFromRemovedEvent(event) e, ok := event.(*user.HumanRefreshTokenRemovedEvent)
if err != nil { if !ok {
return err return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-Sfe11", "reduce.wrong.event.type %s", user.HumanRefreshTokenRemovedType)
} }
return handler.NewDeleteStatement(event,
return t.view.DeleteTokensFromRefreshToken(id, event.Aggregate().InstanceID) []handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyRefreshTokenID, e.TokenID),
},
), nil
case project.ApplicationDeactivatedType, case project.ApplicationDeactivatedType,
project.ApplicationRemovedType: project.ApplicationRemovedType:
application, err := applicationFromSession(event) var applicationID string
if err != nil { switch e := event.(type) {
return err 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,
return t.view.DeleteApplicationTokens(event, application.AppID) []handler.Condition{
handler.NewCond(view_model.TokenKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.TokenKeyApplicationID, applicationID),
},
), nil
case project.ProjectDeactivatedType, case project.ProjectDeactivatedType,
project.ProjectRemovedType: project.ProjectRemovedType:
project, err := t.getProjectByID(context.Background(), event.Aggregate().ID, event.Aggregate().InstanceID) project, err := t.getProjectByID(context.Background(), event.Aggregate().ID, event.Aggregate().InstanceID)
if err != nil { if err != nil {
return err return nil, err
} }
applicationIDs := make([]string, 0, len(project.Applications)) applicationIDs := make([]string, 0, len(project.Applications))
for _, app := range project.Applications { for _, app := range project.Applications {
@@ -223,56 +296,44 @@ func (t *Token) Reduce(event eventstore.Event) (_ *handler.Statement, err error)
applicationIDs = append(applicationIDs, app.OIDCConfig.ClientID) applicationIDs = append(applicationIDs, app.OIDCConfig.ClientID)
} }
} }
if len(applicationIDs) == 0 {
return t.view.DeleteApplicationTokens(event, applicationIDs...) return handler.NewNoOpStatement(event), nil
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
} }
}), 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) { func agentIDFromSession(event eventstore.Event) (string, error) {
session := make(map[string]interface{}) payload := new(userAgentIDPayload)
if err := event.Unmarshal(&session); err != nil { if err := event.Unmarshal(payload); err != nil {
logging.WithError(err).Error("could not unmarshal event data") logging.WithError(err).Error("could not unmarshal event data")
return "", zerrors.ThrowInternal(nil, "MODEL-sd325", "could not unmarshal data") return "", zerrors.ThrowInternal(nil, "MODEL-sd325", "could not unmarshal data")
} }
agentID, _ := session["userAgentID"].(string) return payload.ID, nil
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
} }
func (t *Token) getProjectByID(ctx context.Context, projID, instanceID string) (*proj_model.Project, error) { func (t *Token) getProjectByID(ctx context.Context, projID, instanceID string) (*proj_model.Project, error) {

View File

@@ -4,25 +4,20 @@ import (
"context" "context"
"time" "time"
"github.com/zitadel/zitadel/internal/api/authz"
auth_view "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/view" 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"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2" "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" query2 "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/org"
user_repo "github.com/zitadel/zitadel/internal/repository/user" 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" view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
const ( const (
userTable = "auth.users2" userTable = "auth.users3"
) )
type User struct { type User struct {
@@ -58,26 +53,14 @@ func (u *User) Reducers() []handler.AggregateReducer {
{ {
Aggregate: user_repo.AggregateType, Aggregate: user_repo.AggregateType,
EventReducers: []handler.EventReducer{ EventReducers: []handler.EventReducer{
{
Event: user_repo.HumanOTPSMSAddedType,
Reduce: u.ProcessUser,
},
{ {
Event: user_repo.HumanOTPSMSRemovedType, Event: user_repo.HumanOTPSMSRemovedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
}, },
{
Event: user_repo.HumanOTPEmailAddedType,
Reduce: u.ProcessUser,
},
{ {
Event: user_repo.HumanOTPEmailRemovedType, Event: user_repo.HumanOTPEmailRemovedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
}, },
{
Event: user_repo.MachineAddedEventType,
Reduce: u.ProcessUser,
},
{ {
Event: user_repo.HumanAddedType, Event: user_repo.HumanAddedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
@@ -94,62 +77,14 @@ func (u *User) Reducers() []handler.AggregateReducer {
Event: user_repo.HumanRegisteredType, Event: user_repo.HumanRegisteredType,
Reduce: u.ProcessUser, 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, Event: user_repo.UserV1PhoneRemovedType,
Reduce: u.ProcessUser, 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, Event: user_repo.UserV1MFAOTPVerifiedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
}, },
{
Event: user_repo.UserV1MFAOTPRemovedType,
Reduce: u.ProcessUser,
},
{ {
Event: user_repo.UserV1MFAInitSkippedType, Event: user_repo.UserV1MFAInitSkippedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
@@ -158,86 +93,22 @@ func (u *User) Reducers() []handler.AggregateReducer {
Event: user_repo.UserV1PasswordChangedType, Event: user_repo.UserV1PasswordChangedType,
Reduce: u.ProcessUser, 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, Event: user_repo.HumanPhoneRemovedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
}, },
{
Event: user_repo.HumanAddressChangedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanMFAOTPAddedType,
Reduce: u.ProcessUser,
},
{ {
Event: user_repo.HumanMFAOTPVerifiedType, Event: user_repo.HumanMFAOTPVerifiedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
}, },
{
Event: user_repo.HumanMFAOTPRemovedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.HumanU2FTokenAddedType,
Reduce: u.ProcessUser,
},
{ {
Event: user_repo.HumanU2FTokenVerifiedType, Event: user_repo.HumanU2FTokenVerifiedType,
Reduce: u.ProcessUser, 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, Event: user_repo.HumanMFAInitSkippedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
}, },
{
Event: user_repo.MachineChangedEventType,
Reduce: u.ProcessUser,
},
{ {
Event: user_repo.HumanPasswordChangedType, Event: user_repo.HumanPasswordChangedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
@@ -266,14 +137,6 @@ func (u *User) Reducers() []handler.AggregateReducer {
Event: user_repo.HumanPasswordlessInitCodeRequestedType, Event: user_repo.HumanPasswordlessInitCodeRequestedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
}, },
{
Event: user_repo.UserDomainClaimedType,
Reduce: u.ProcessUser,
},
{
Event: user_repo.UserUserNameChangedType,
Reduce: u.ProcessUser,
},
{ {
Event: user_repo.UserRemovedType, Event: user_repo.UserRemovedType,
Reduce: u.ProcessUser, Reduce: u.ProcessUser,
@@ -283,30 +146,6 @@ func (u *User) Reducers() []handler.AggregateReducer {
{ {
Aggregate: org.AggregateType, Aggregate: org.AggregateType,
EventReducers: []handler.EventReducer{ 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, Event: org.OrgRemovedEventType,
Reduce: u.ProcessOrg, Reduce: u.ProcessOrg,
@@ -327,141 +166,124 @@ func (u *User) Reducers() []handler.AggregateReducer {
//nolint:gocognit //nolint:gocognit
func (u *User) ProcessUser(event eventstore.Event) (_ *handler.Statement, err error) { func (u *User) ProcessUser(event eventstore.Event) (_ *handler.Statement, err error) {
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { // in case anything needs to be change here check if appendEvent function needs the change as well
user := new(view_model.UserView)
switch event.Type() { switch event.Type() {
case user_repo.UserV1AddedType, case user_repo.UserV1AddedType,
user_repo.MachineAddedEventType, user_repo.HumanAddedType:
user_repo.HumanAddedType, e, ok := event.(*user_repo.HumanAddedEvent)
user_repo.UserV1RegisteredType, if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-SDAGF", "reduce.wrong.event.type %s", user_repo.HumanAddedType)
}
return u.setPasswordData(event, e.Secret, e.EncodedHash), nil
case user_repo.UserV1RegisteredType,
user_repo.HumanRegisteredType: user_repo.HumanRegisteredType:
err = user.AppendEvent(event) e, ok := event.(*user_repo.HumanRegisteredEvent)
if err != nil { if !ok {
return err return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-AS1hz", "reduce.wrong.event.type %s", user_repo.HumanRegisteredType)
} }
err = u.fillLoginNames(user) return u.setPasswordData(event, e.Secret, e.EncodedHash), nil
case user_repo.UserV1ProfileChangedType, case user_repo.UserV1PasswordChangedType,
user_repo.UserV1EmailChangedType, user_repo.HumanPasswordChangedType:
user_repo.UserV1EmailVerifiedType, e, ok := event.(*user_repo.HumanPasswordChangedEvent)
user_repo.UserV1PhoneChangedType, if !ok {
user_repo.UserV1PhoneVerifiedType, return nil, zerrors.ThrowInvalidArgumentf(nil, "MODEL-Gd31w", "reduce.wrong.event.type %s", user_repo.HumanPasswordChangedType)
user_repo.UserV1PhoneRemovedType, }
user_repo.UserV1AddressChangedType, return u.setPasswordData(event, e.Secret, e.EncodedHash), nil
user_repo.UserDeactivatedType, case user_repo.UserV1PhoneRemovedType,
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.HumanPhoneRemovedType,
user_repo.HumanAddressChangedType, user_repo.UserV1MFAOTPVerifiedType,
user_repo.HumanMFAOTPAddedType,
user_repo.HumanMFAOTPVerifiedType, user_repo.HumanMFAOTPVerifiedType,
user_repo.HumanMFAOTPRemovedType,
user_repo.HumanOTPSMSAddedType,
user_repo.HumanOTPSMSRemovedType, user_repo.HumanOTPSMSRemovedType,
user_repo.HumanOTPEmailAddedType,
user_repo.HumanOTPEmailRemovedType, user_repo.HumanOTPEmailRemovedType,
user_repo.HumanU2FTokenAddedType, user_repo.HumanU2FTokenVerifiedType:
user_repo.HumanU2FTokenVerifiedType, return handler.NewUpdateStatement(event,
user_repo.HumanU2FTokenRemovedType, []handler.Column{
user_repo.HumanPasswordlessTokenAddedType, handler.NewCol(view_model.UserKeyMFAInitSkipped, time.Time{}),
user_repo.HumanPasswordlessTokenVerifiedType, handler.NewCol(view_model.UserKeyChangeDate, event.CreatedAt()),
user_repo.HumanPasswordlessTokenRemovedType, },
user_repo.HumanMFAInitSkippedType, []handler.Condition{
user_repo.MachineChangedEventType, handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
user_repo.HumanPasswordChangedType, handler.NewCond(view_model.UserKeyUserID, event.Aggregate().ID),
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
}
if err != nil {
return err
}
return u.view.PutUser(user, event)
}), nil }), 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) { func (u *User) setPasswordData(event eventstore.Event, secret *crypto.CryptoValue, hash string) *handler.Statement {
userLoginMustBeDomain, primaryDomain, domains, err := u.loginNameInformation(context.Background(), user.ResourceOwner, user.InstanceID) set := secret != nil || hash != ""
if err != nil { columns := []handler.Column{
return err 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) return handler.NewUpsertStatement(event, columns[0:2], columns)
user.PreferredLoginName = user.GenerateLoginName(primaryDomain, userLoginMustBeDomain)
return nil
} }
func (u *User) ProcessOrg(event eventstore.Event) (_ *handler.Statement, err error) { func (u *User) ProcessOrg(event eventstore.Event) (_ *handler.Statement, err error) {
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { // in case anything needs to be change here check if appendEvent function needs the change as well
switch event.Type() { 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: case org.OrgRemovedEventType:
return u.view.UpdateOrgOwnerRemovedUsers(event) return handler.NewDeleteStatement(event,
default: []handler.Condition{
return nil handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
} handler.NewCond(view_model.UserKeyResourceOwner, event.Aggregate().ID),
}), nil
}
func (u *User) ProcessInstance(event eventstore.Event) (_ *handler.Statement, err error) {
switch event.Type() {
case instance.InstanceRemovedEventType:
return handler.NewStatement(event,
func(ex handler.Executer, projectionName string) error {
return u.view.DeleteInstanceUsers(event)
}, },
), nil ), nil
default: default:
@@ -469,97 +291,16 @@ func (u *User) ProcessInstance(event eventstore.Event) (_ *handler.Statement, er
} }
} }
func (u *User) fillLoginNamesOnOrgUsers(event eventstore.Event) error { func (u *User) ProcessInstance(event eventstore.Event) (_ *handler.Statement, err error) {
userLoginMustBeDomain, _, domains, err := u.loginNameInformation(context.Background(), event.Aggregate().ResourceOwner, event.Aggregate().InstanceID) // in case anything needs to be change here check if appendEvent function needs the change as well
if err != nil { switch event.Type() {
return err case instance.InstanceRemovedEventType:
} return handler.NewDeleteStatement(event,
users, err := u.view.UsersByOrgID(event.Aggregate().ID, event.Aggregate().InstanceID) []handler.Condition{
if err != nil { handler.NewCond(view_model.UserKeyInstanceID, event.Aggregate().InstanceID),
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,
}, },
), nil
default:
return handler.NewNoOpStatement(event), nil
} }
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
} }

View File

@@ -12,8 +12,8 @@ import (
"github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/user" "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" view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
) )
const ( 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) { 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() { switch event.Type() {
case user.UserV1PasswordCheckSucceededType, case user.UserV1PasswordCheckSucceededType,
user.UserV1PasswordCheckFailedType, user.HumanPasswordCheckSucceededType:
user.UserV1MFAOTPCheckSucceededType, columns, err := sessionColumns(event,
user.UserV1MFAOTPCheckFailedType, handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
user.UserV1SignedOutType, handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
user.HumanPasswordCheckSucceededType, )
user.HumanPasswordCheckFailedType, if err != nil {
user.UserIDPLoginCheckSucceededType, return nil, err
user.HumanMFAOTPCheckSucceededType, }
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.HumanMFAOTPCheckFailedType,
user.HumanU2FTokenCheckSucceededType, user.HumanU2FTokenCheckFailedType:
user.HumanU2FTokenCheckFailedType, columns, err := sessionColumns(event,
user.HumanPasswordlessTokenCheckSucceededType, handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
user.HumanPasswordlessTokenCheckFailedType, )
if err != nil {
return nil, err
}
return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserV1SignedOutType,
user.HumanSignedOutType: user.HumanSignedOutType:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { columns, err := sessionColumns(event,
var session *view_model.UserSessionView handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
eventData, err := view_model.UserSessionFromEvent(event) 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 { if err != nil {
return err return nil, err
} }
session, err = u.view.UserSessionByIDs(eventData.UserAgentID, event.Aggregate().ID, event.Aggregate().InstanceID) return handler.NewUpsertStatement(event, columns[0:3], columns), nil
case user.UserIDPLoginCheckSucceededType:
data := new(es_model.AuthRequest)
err := data.SetData(event)
if err != nil { if err != nil {
if !zerrors.IsNotFound(err) { return nil, err
return err
} }
session = &view_model.UserSessionView{ columns, err := sessionColumns(event,
CreationDate: event.CreatedAt(), handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, event.CreatedAt()),
ResourceOwner: event.Aggregate().ResourceOwner, handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, data.SelectedIDPConfigID),
UserAgentID: eventData.UserAgentID, handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
UserID: event.Aggregate().ID, )
State: int32(domain.UserSessionStateActive), if err != nil {
InstanceID: event.Aggregate().InstanceID, 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
} }
return u.updateSession(session, event) columns, err := sessionColumns(event,
}), nil 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, case user.UserLockedType,
user.UserDeactivatedType: user.UserDeactivatedType:
return handler.NewUpdateStatement(event, return handler.NewUpdateStatement(event,
[]handler.Column{ []handler.Column{
handler.NewCol("passwordless_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
handler.NewCol("password_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
handler.NewCol("second_factor_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
handler.NewCol("second_factor_verification_type", domain.MFALevelNotSetUp), handler.NewCol(view_model.UserSessionKeySecondFactorVerificationType, domain.MFALevelNotSetUp),
handler.NewCol("multi_factor_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
handler.NewCol("multi_factor_verification_type", domain.MFALevelNotSetUp), handler.NewCol(view_model.UserSessionKeyMultiFactorVerificationType, domain.MFALevelNotSetUp),
handler.NewCol("external_login_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, time.Time{}),
handler.NewCol("state", domain.UserSessionStateTerminated), handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateTerminated),
handler.NewCol("change_date", event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()), handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID), handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)), handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)),
}, },
), nil ), nil
case user.UserV1PasswordChangedType, case user.UserV1PasswordChangedType,
@@ -256,26 +352,26 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
return handler.NewMultiStatement(event, return handler.NewMultiStatement(event,
handler.AddUpdateStatement( handler.AddUpdateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol("password_verification", event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
handler.NewCol("change_date", event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()), handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID), handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.NewCond("user_agent_id", userAgent), handler.NewCond(view_model.UserSessionKeyUserAgentID, userAgent),
}), }),
handler.AddUpdateStatement( handler.AddUpdateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol("password_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeyPasswordVerification, time.Time{}),
handler.NewCol("change_date", event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()), handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID), handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond("user_agent_id", userAgent)), handler.Not(handler.NewCond(view_model.UserSessionKeyUserAgentID, userAgent)),
handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)), handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)),
}), }),
), nil ), nil
case user.UserV1MFAOTPRemovedType, case user.UserV1MFAOTPRemovedType,
@@ -283,84 +379,77 @@ func (u *UserSession) Reduce(event eventstore.Event) (_ *handler.Statement, err
user.HumanU2FTokenRemovedType: user.HumanU2FTokenRemovedType:
return handler.NewUpdateStatement(event, return handler.NewUpdateStatement(event,
[]handler.Column{ []handler.Column{
handler.NewCol("second_factor_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeySecondFactorVerification, time.Time{}),
handler.NewCol("change_date", event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()), handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID), handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)), handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)),
}, },
), nil ), nil
case user.UserIDPLinkRemovedType, case user.UserIDPLinkRemovedType,
user.UserIDPLinkCascadeRemovedType: user.UserIDPLinkCascadeRemovedType:
return handler.NewUpdateStatement(event, return handler.NewUpdateStatement(event,
[]handler.Column{ []handler.Column{
handler.NewCol("external_login_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeyExternalLoginVerification, time.Time{}),
handler.NewCol("selected_idp_config_id", ""), handler.NewCol(view_model.UserSessionKeySelectedIDPConfigID, ""),
handler.NewCol("change_date", event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()), handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID), handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond("selected_idp_config_id", "")), handler.Not(handler.NewCond(view_model.UserSessionKeySelectedIDPConfigID, "")),
}, },
), nil ), nil
case user.HumanPasswordlessTokenRemovedType: case user.HumanPasswordlessTokenRemovedType:
return handler.NewUpdateStatement(event, return handler.NewUpdateStatement(event,
[]handler.Column{ []handler.Column{
handler.NewCol("passwordless_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeyPasswordlessVerification, time.Time{}),
handler.NewCol("multi_factor_verification", time.Time{}), handler.NewCol(view_model.UserSessionKeyMultiFactorVerification, time.Time{}),
handler.NewCol("change_date", event.CreatedAt()), handler.NewCol(view_model.UserSessionKeyChangeDate, event.CreatedAt()),
handler.NewCol("sequence", event.Sequence()), handler.NewCol(view_model.UserSessionKeySequence, event.Sequence()),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond("instance_id", event.Aggregate().InstanceID), handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond("user_id", event.Aggregate().ID), handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
handler.Not(handler.NewCond("state", domain.UserSessionStateTerminated)), handler.Not(handler.NewCond(view_model.UserSessionKeyState, domain.UserSessionStateTerminated)),
}, },
), nil ), nil
case user.UserRemovedType: case user.UserRemovedType:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { return handler.NewDeleteStatement(event,
return u.view.DeleteUserSessions(event.Aggregate().ID, event.Aggregate().InstanceID) []handler.Condition{
}), nil handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyUserID, event.Aggregate().ID),
},
), nil
case user.HumanRegisteredType: case user.HumanRegisteredType:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { columns, err := sessionColumns(event,
eventData, err := view_model.UserSessionFromEvent(event) handler.NewCol(view_model.UserSessionKeyState, domain.UserSessionStateActive),
handler.NewCol(view_model.UserSessionKeyPasswordVerification, event.CreatedAt()),
)
if err != nil { if err != nil {
return err return nil, err
} }
session := &view_model.UserSessionView{ return handler.NewCreateStatement(event,
CreationDate: event.CreatedAt(), columns,
ResourceOwner: event.Aggregate().ResourceOwner, ), nil
UserAgentID: eventData.UserAgentID,
UserID: event.Aggregate().ID,
State: int32(domain.UserSessionStateActive),
InstanceID: event.Aggregate().InstanceID,
PasswordVerification: event.CreatedAt(),
}
return u.updateSession(session, event)
}), nil
case instance.InstanceRemovedEventType: case instance.InstanceRemovedEventType:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { return handler.NewDeleteStatement(event,
return u.view.DeleteInstanceUserSessions(event.Aggregate().InstanceID) []handler.Condition{
}), nil handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
},
), nil
case org.OrgRemovedEventType: case org.OrgRemovedEventType:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { return handler.NewDeleteStatement(event,
return u.view.DeleteOrgUserSessions(event) []handler.Condition{
}), nil handler.NewCond(view_model.UserSessionKeyInstanceID, event.Aggregate().InstanceID),
handler.NewCond(view_model.UserSessionKeyResourceOwner, event.Aggregate().ResourceOwner),
},
), nil
default: default:
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error { return handler.NewNoOpStatement(event), nil
return nil
}), 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)
}

View File

@@ -4,13 +4,10 @@ import (
"context" "context"
"github.com/zitadel/zitadel/internal/api/authz" "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" "github.com/zitadel/zitadel/internal/query"
user_model "github.com/zitadel/zitadel/internal/user/model" user_model "github.com/zitadel/zitadel/internal/user/model"
usr_view "github.com/zitadel/zitadel/internal/user/repository/view" usr_view "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model" "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
) )
const ( const (
@@ -29,54 +26,6 @@ func (v *View) SearchRefreshTokens(request *user_model.RefreshTokenSearchRequest
return usr_view.SearchRefreshTokens(v.Db, refreshTokenTable, request) 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) { func (v *View) GetLatestRefreshTokenSequence(ctx context.Context) (_ *query.CurrentState, err error) {
q := &query.CurrentStateSearchQueries{ q := &query.CurrentStateSearchQueries{
Queries: make([]query.SearchQuery, 2), Queries: make([]query.SearchQuery, 2),

View File

@@ -3,12 +3,10 @@ package view
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
usr_view "github.com/zitadel/zitadel/internal/user/repository/view" usr_view "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model" "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
) )
const ( const (
@@ -23,70 +21,6 @@ func (v *View) TokensByUserID(userID, instanceID string) ([]*model.TokenView, er
return usr_view.TokensByUserID(v.Db, tokenTable, userID, instanceID) 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) { func (v *View) GetLatestTokenSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()

View File

@@ -5,7 +5,6 @@ import (
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
usr_model "github.com/zitadel/zitadel/internal/user/model" usr_model "github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/user/repository/view" "github.com/zitadel/zitadel/internal/user/repository/view"
@@ -14,7 +13,7 @@ import (
) )
const ( const (
userTable = "auth.users2" userTable = "auth.users3"
) )
func (v *View) UserByID(userID, instanceID string) (*model.UserView, error) { 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 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) { func (v *View) GetLatestUserSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) {
q := &query.CurrentStateSearchQueries{ q := &query.CurrentStateSearchQueries{
Queries: make([]query.SearchQuery, 2), Queries: make([]query.SearchQuery, 2),

View File

@@ -3,11 +3,9 @@ package view
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/user/repository/view" "github.com/zitadel/zitadel/internal/user/repository/view"
"github.com/zitadel/zitadel/internal/user/repository/view/model" "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/zerrors"
) )
const ( const (
@@ -22,34 +20,6 @@ func (v *View) UserSessionsByAgentID(agentID, instanceID string) ([]*model.UserS
return view.UserSessionsByAgentID(v.client, agentID, instanceID) 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) { func (v *View) GetLatestUserSessionSequence(ctx context.Context, instanceID string) (_ *query.CurrentState, err error) {
q := &query.CurrentStateSearchQueries{ q := &query.CurrentStateSearchQueries{
Queries: make([]query.SearchQuery, 2), Queries: make([]query.SearchQuery, 2),

View File

@@ -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 { type Executer interface {
Exec(string, ...interface{}) (sql.Result, error) Exec(string, ...interface{}) (sql.Result, error)
} }

View File

@@ -20,6 +20,17 @@ const (
RefreshTokenKeyExpiration = "expiration" RefreshTokenKeyExpiration = "expiration"
RefreshTokenKeyResourceOwner = "resource_owner" RefreshTokenKeyResourceOwner = "resource_owner"
RefreshTokenKeyInstanceID = "instance_id" 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 { type RefreshTokenView struct {
@@ -72,6 +83,7 @@ func RefreshTokenViewToModel(token *RefreshTokenView) *usr_model.RefreshTokenVie
} }
func (t *RefreshTokenView) AppendEventIfMyRefreshToken(event eventstore.Event) (err error) { 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) view := new(RefreshTokenView)
switch event.Type() { switch event.Type() {
case user_repo.HumanRefreshTokenAddedType: case user_repo.HumanRefreshTokenAddedType:
@@ -101,6 +113,7 @@ func (t *RefreshTokenView) AppendEventIfMyRefreshToken(event eventstore.Event) (
} }
func (t *RefreshTokenView) AppendEvent(event eventstore.Event) error { 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.ChangeDate = event.CreatedAt()
t.Sequence = event.Sequence() t.Sequence = event.Sequence()
switch event.Type() { switch event.Type() {
@@ -123,7 +136,7 @@ func (t *RefreshTokenView) setRootData(event eventstore.Event) {
func (t *RefreshTokenView) appendAddedEvent(event eventstore.Event) error { func (t *RefreshTokenView) appendAddedEvent(event eventstore.Event) error {
e := new(user_repo.HumanRefreshTokenAddedEvent) e := new(user_repo.HumanRefreshTokenAddedEvent)
if err := event.Unmarshal(e); err != nil { 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") return zerrors.ThrowInternal(err, "MODEL-Bbr42", "could not unmarshal event")
} }
t.ID = e.TokenID t.ID = e.TokenID
@@ -144,7 +157,7 @@ func (t *RefreshTokenView) appendAddedEvent(event eventstore.Event) error {
func (t *RefreshTokenView) appendRenewedEvent(event eventstore.Event) error { func (t *RefreshTokenView) appendRenewedEvent(event eventstore.Event) error {
e := new(user_repo.HumanRefreshTokenRenewedEvent) e := new(user_repo.HumanRefreshTokenRenewedEvent)
if err := event.Unmarshal(e); err != nil { 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") return zerrors.ThrowInternal(err, "MODEL-Bbrn4", "could not unmarshal event")
} }
t.ID = e.TokenID t.ID = e.TokenID

View File

@@ -24,6 +24,15 @@ const (
TokenKeyExpiration = "expiration" TokenKeyExpiration = "expiration"
TokenKeyResourceOwner = "resource_owner" TokenKeyResourceOwner = "resource_owner"
TokenKeyInstanceID = "instance_id" 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 { type TokenView struct {
@@ -100,6 +109,7 @@ func TokenViewToModel(token *TokenView) *usr_model.TokenView {
} }
func (t *TokenView) AppendEventIfMyToken(event eventstore.Event) (err error) { 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) view := new(TokenView)
switch event.Type() { switch event.Type() {
case user_repo.UserTokenAddedType, case user_repo.UserTokenAddedType,
@@ -112,7 +122,7 @@ func (t *TokenView) AppendEventIfMyToken(event eventstore.Event) (err error) {
return t.appendRefreshTokenRemoved(event) return t.appendRefreshTokenRemoved(event)
case user_repo.UserV1SignedOutType, case user_repo.UserV1SignedOutType,
user_repo.HumanSignedOutType: user_repo.HumanSignedOutType:
id, err := agentIDFromSession(event) id, err := UserAgentIDFromEvent(event)
if err != nil { if err != nil {
return err return err
} }
@@ -146,6 +156,7 @@ func (t *TokenView) AppendEventIfMyToken(event eventstore.Event) (err error) {
} }
func (t *TokenView) AppendEvent(event eventstore.Event) 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.ChangeDate = event.CreatedAt()
t.Sequence = event.Sequence() t.Sequence = event.Sequence()
switch event.Type() { switch event.Type() {
@@ -170,49 +181,40 @@ func (t *TokenView) setRootData(event eventstore.Event) {
func (t *TokenView) setData(event eventstore.Event) error { func (t *TokenView) setData(event eventstore.Event) error {
if err := event.Unmarshal(t); err != nil { 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 zerrors.ThrowInternal(err, "MODEL-5Gms9", "could not unmarshal event")
} }
return nil 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 { func (t *TokenView) appendTokenRemoved(event eventstore.Event) error {
token, err := eventToMap(event) tokenID, err := tokenIDFromEvent(event)
if err != nil { if err != nil {
return err return err
} }
if token["tokenId"] == t.ID { if tokenID == t.ID {
t.Deactivated = true t.Deactivated = true
} }
return nil return nil
} }
func (t *TokenView) appendRefreshTokenRemoved(event eventstore.Event) error { func (t *TokenView) appendRefreshTokenRemoved(event eventstore.Event) error {
refreshToken, err := eventToMap(event) tokenID, err := tokenIDFromEvent(event)
if err != nil { if err != nil {
return err return err
} }
if refreshToken["tokenId"] == t.RefreshTokenID { if tokenID == t.RefreshTokenID {
t.Deactivated = true t.Deactivated = true
} }
return nil return nil
} }
func (t *TokenView) appendPATRemoved(event eventstore.Event) error { func (t *TokenView) appendPATRemoved(event eventstore.Event) error {
pat, err := eventToMap(event) tokenID, err := tokenIDFromEvent(event)
if err != nil { if err != nil {
return err return err
} }
if pat["tokenId"] == t.ID && t.IsPAT { if tokenID == t.ID && t.IsPAT {
t.Deactivated = true t.Deactivated = true
} }
return nil return nil
@@ -235,11 +237,15 @@ func (t *TokenView) GetRelevantEventTypes() []eventstore.EventType {
} }
} }
func eventToMap(event eventstore.Event) (map[string]interface{}, error) { type tokenIDPayload struct {
m := make(map[string]interface{}) ID string `json:"tokenId"`
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") func tokenIDFromEvent(event eventstore.Event) (string, error) {
} m := new(tokenIDPayload)
return m, nil if err := event.Unmarshal(&m); err != nil {
logging.WithError(err).Error("could not unmarshal event data")
return "", zerrors.ThrowInternal(nil, "MODEL-SDAfw", "could not unmarshal data")
}
return m.ID, nil
} }

View File

@@ -32,13 +32,13 @@ const (
UserKeyType = "user_type" UserKeyType = "user_type"
UserKeyInstanceID = "instance_id" UserKeyInstanceID = "instance_id"
UserKeyOwnerRemoved = "owner_removed" UserKeyOwnerRemoved = "owner_removed"
) UserKeyPasswordSet = "password_set"
UserKeyPasswordInitRequired = "password_init_required"
type userType string UserKeyPasswordChange = "password_change"
UserKeyInitRequired = "init_required"
const ( UserKeyPasswordlessInitRequired = "passwordless_init_required"
userTypeHuman = "human" UserKeyMFAInitSkipped = "mfa_init_skipped"
userTypeMachine = "machine" UserKeyChangeDate = "change_date"
) )
type UserView struct { type UserView struct {
@@ -51,7 +51,6 @@ type UserView struct {
LoginNames database.TextArray[string] `json:"-" gorm:"column:login_names"` LoginNames database.TextArray[string] `json:"-" gorm:"column:login_names"`
PreferredLoginName string `json:"-" gorm:"column:preferred_login_name"` PreferredLoginName string `json:"-" gorm:"column:preferred_login_name"`
Sequence uint64 `json:"-" gorm:"column:sequence"` Sequence uint64 `json:"-" gorm:"column:sequence"`
Type userType `json:"-" gorm:"column:user_type"`
UserName string `json:"userName" gorm:"column:user_name"` UserName string `json:"userName" gorm:"column:user_name"`
InstanceID string `json:"instanceID" gorm:"column:instance_id;primary_key"` InstanceID string `json:"instanceID" gorm:"column:instance_id;primary_key"`
*MachineView *MachineView
@@ -236,13 +235,13 @@ func (u *UserView) SetLoginNames(userLoginMustBeDomain bool, domains []*org_mode
} }
func (u *UserView) AppendEvent(event eventstore.Event) (err error) { 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.ChangeDate = event.CreatedAt()
u.Sequence = event.Sequence() u.Sequence = event.Sequence()
switch event.Type() { switch event.Type() {
case user.MachineAddedEventType: case user.MachineAddedEventType:
u.CreationDate = event.CreatedAt() u.CreationDate = event.CreatedAt()
u.setRootData(event) u.setRootData(event)
u.Type = userTypeMachine
err = u.setData(event) err = u.setData(event)
if err != nil { if err != nil {
return err return err
@@ -253,7 +252,6 @@ func (u *UserView) AppendEvent(event eventstore.Event) (err error) {
user.HumanAddedType: user.HumanAddedType:
u.CreationDate = event.CreatedAt() u.CreationDate = event.CreatedAt()
u.setRootData(event) u.setRootData(event)
u.Type = userTypeHuman
err = u.setData(event) err = u.setData(event)
if err != nil { if err != nil {
return err return err
@@ -396,7 +394,7 @@ func (u *UserView) setData(event eventstore.Event) error {
func (u *UserView) setPasswordData(event eventstore.Event) error { func (u *UserView) setPasswordData(event eventstore.Event) error {
password := new(es_model.Password) password := new(es_model.Password)
if err := event.Unmarshal(password); err != nil { 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") return zerrors.ThrowInternal(nil, "MODEL-6jhsw", "could not unmarshal data")
} }
u.PasswordSet = password.Secret != nil || password.EncodedHash != "" u.PasswordSet = password.Secret != nil || password.EncodedHash != ""

View File

@@ -1,6 +1,7 @@
package model package model
import ( import (
"database/sql"
"time" "time"
"github.com/zitadel/logging" "github.com/zitadel/logging"
@@ -20,6 +21,17 @@ const (
UserSessionKeyResourceOwner = "resource_owner" UserSessionKeyResourceOwner = "resource_owner"
UserSessionKeyInstanceID = "instance_id" UserSessionKeyInstanceID = "instance_id"
UserSessionKeyOwnerRemoved = "owner_removed" 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 { type UserSessionView struct {
@@ -33,29 +45,33 @@ type UserSessionView struct {
// are not projected in the user session handler anymore // are not projected in the user session handler anymore
// and are therefore annotated with a `gorm:"-"`. // and are therefore annotated with a `gorm:"-"`.
// They will be read from the corresponding projection directly. // They will be read from the corresponding projection directly.
UserName string `json:"-" gorm:"-"` UserName sql.NullString `json:"-" gorm:"-"`
LoginName string `json:"-" gorm:"-"` LoginName sql.NullString `json:"-" gorm:"-"`
DisplayName string `json:"-" gorm:"-"` DisplayName sql.NullString `json:"-" gorm:"-"`
AvatarKey string `json:"-" gorm:"-"` AvatarKey sql.NullString `json:"-" gorm:"-"`
SelectedIDPConfigID string `json:"selectedIDPConfigID" gorm:"column:selected_idp_config_id"` SelectedIDPConfigID sql.NullString `json:"selectedIDPConfigID" gorm:"column:selected_idp_config_id"`
PasswordVerification time.Time `json:"-" gorm:"column:password_verification"` PasswordVerification sql.NullTime `json:"-" gorm:"column:password_verification"`
PasswordlessVerification time.Time `json:"-" gorm:"column:passwordless_verification"` PasswordlessVerification sql.NullTime `json:"-" gorm:"column:passwordless_verification"`
ExternalLoginVerification time.Time `json:"-" gorm:"column:external_login_verification"` ExternalLoginVerification sql.NullTime `json:"-" gorm:"column:external_login_verification"`
SecondFactorVerification time.Time `json:"-" gorm:"column:second_factor_verification"` SecondFactorVerification sql.NullTime `json:"-" gorm:"column:second_factor_verification"`
SecondFactorVerificationType int32 `json:"-" gorm:"column:second_factor_verification_type"` SecondFactorVerificationType sql.NullInt32 `json:"-" gorm:"column:second_factor_verification_type"`
MultiFactorVerification time.Time `json:"-" gorm:"column:multi_factor_verification"` MultiFactorVerification sql.NullTime `json:"-" gorm:"column:multi_factor_verification"`
MultiFactorVerificationType int32 `json:"-" gorm:"column:multi_factor_verification_type"` MultiFactorVerificationType sql.NullInt32 `json:"-" gorm:"column:multi_factor_verification_type"`
Sequence uint64 `json:"-" gorm:"column:sequence"` Sequence uint64 `json:"-" gorm:"column:sequence"`
InstanceID string `json:"instanceID" gorm:"column:instance_id;primary_key"` InstanceID string `json:"instanceID" gorm:"column:instance_id;primary_key"`
} }
func UserSessionFromEvent(event eventstore.Event) (*UserSessionView, error) { type userAgentIDPayload struct {
v := new(UserSessionView) ID string `json:"userAgentID"`
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") 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 { func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView {
@@ -66,18 +82,18 @@ func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView {
State: domain.UserSessionState(userSession.State), State: domain.UserSessionState(userSession.State),
UserAgentID: userSession.UserAgentID, UserAgentID: userSession.UserAgentID,
UserID: userSession.UserID, UserID: userSession.UserID,
UserName: userSession.UserName, UserName: userSession.UserName.String,
LoginName: userSession.LoginName, LoginName: userSession.LoginName.String,
DisplayName: userSession.DisplayName, DisplayName: userSession.DisplayName.String,
AvatarKey: userSession.AvatarKey, AvatarKey: userSession.AvatarKey.String,
SelectedIDPConfigID: userSession.SelectedIDPConfigID, SelectedIDPConfigID: userSession.SelectedIDPConfigID.String,
PasswordVerification: userSession.PasswordVerification, PasswordVerification: userSession.PasswordVerification.Time,
PasswordlessVerification: userSession.PasswordlessVerification, PasswordlessVerification: userSession.PasswordlessVerification.Time,
ExternalLoginVerification: userSession.ExternalLoginVerification, ExternalLoginVerification: userSession.ExternalLoginVerification.Time,
SecondFactorVerification: userSession.SecondFactorVerification, SecondFactorVerification: userSession.SecondFactorVerification.Time,
SecondFactorVerificationType: domain.MFAType(userSession.SecondFactorVerificationType), SecondFactorVerificationType: domain.MFAType(userSession.SecondFactorVerificationType.Int32),
MultiFactorVerification: userSession.MultiFactorVerification, MultiFactorVerification: userSession.MultiFactorVerification.Time,
MultiFactorVerificationType: domain.MFAType(userSession.MultiFactorVerificationType), MultiFactorVerificationType: domain.MFAType(userSession.MultiFactorVerificationType.Int32),
Sequence: userSession.Sequence, Sequence: userSession.Sequence,
} }
} }
@@ -91,12 +107,13 @@ func UserSessionsToModel(userSessions []*UserSessionView) []*model.UserSessionVi
} }
func (v *UserSessionView) AppendEvent(event eventstore.Event) error { 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.Sequence = event.Sequence()
v.ChangeDate = event.CreatedAt() v.ChangeDate = event.CreatedAt()
switch event.Type() { switch event.Type() {
case user.UserV1PasswordCheckSucceededType, case user.UserV1PasswordCheckSucceededType,
user.HumanPasswordCheckSucceededType: user.HumanPasswordCheckSucceededType:
v.PasswordVerification = event.CreatedAt() v.PasswordVerification = sql.NullTime{Time: event.CreatedAt(), Valid: true}
v.State = int32(domain.UserSessionStateActive) v.State = int32(domain.UserSessionStateActive)
case user.UserIDPLoginCheckSucceededType: case user.UserIDPLoginCheckSucceededType:
data := new(es_model.AuthRequest) data := new(es_model.AuthRequest)
@@ -104,21 +121,21 @@ func (v *UserSessionView) AppendEvent(event eventstore.Event) error {
if err != nil { if err != nil {
return err return err
} }
v.ExternalLoginVerification = event.CreatedAt() v.ExternalLoginVerification = sql.NullTime{Time: event.CreatedAt(), Valid: true}
v.SelectedIDPConfigID = data.SelectedIDPConfigID v.SelectedIDPConfigID = sql.NullString{String: data.SelectedIDPConfigID, Valid: true}
v.State = int32(domain.UserSessionStateActive) v.State = int32(domain.UserSessionStateActive)
case user.HumanPasswordlessTokenCheckSucceededType: case user.HumanPasswordlessTokenCheckSucceededType:
v.PasswordlessVerification = event.CreatedAt() v.PasswordlessVerification = sql.NullTime{Time: event.CreatedAt(), Valid: true}
v.MultiFactorVerification = event.CreatedAt() v.MultiFactorVerification = sql.NullTime{Time: event.CreatedAt(), Valid: true}
v.MultiFactorVerificationType = int32(domain.MFATypeU2FUserVerification) v.MultiFactorVerificationType = sql.NullInt32{Int32: int32(domain.MFATypeU2FUserVerification)}
v.State = int32(domain.UserSessionStateActive) v.State = int32(domain.UserSessionStateActive)
case user.HumanPasswordlessTokenCheckFailedType, case user.HumanPasswordlessTokenCheckFailedType,
user.HumanPasswordlessTokenRemovedType: user.HumanPasswordlessTokenRemovedType:
v.PasswordlessVerification = time.Time{} v.PasswordlessVerification = sql.NullTime{Time: time.Time{}, Valid: true}
v.MultiFactorVerification = time.Time{} v.MultiFactorVerification = sql.NullTime{Time: time.Time{}, Valid: true}
case user.UserV1PasswordCheckFailedType, case user.UserV1PasswordCheckFailedType,
user.HumanPasswordCheckFailedType: user.HumanPasswordCheckFailedType:
v.PasswordVerification = time.Time{} v.PasswordVerification = sql.NullTime{Time: time.Time{}, Valid: true}
case user.UserV1PasswordChangedType, case user.UserV1PasswordChangedType,
user.HumanPasswordChangedType: user.HumanPasswordChangedType:
data := new(es_model.PasswordChange) data := new(es_model.PasswordChange)
@@ -127,7 +144,7 @@ func (v *UserSessionView) AppendEvent(event eventstore.Event) error {
return err return err
} }
if v.UserAgentID != data.UserAgentID { if v.UserAgentID != data.UserAgentID {
v.PasswordVerification = time.Time{} v.PasswordVerification = sql.NullTime{Time: time.Time{}, Valid: true}
} }
case user.HumanMFAOTPVerifiedType: case user.HumanMFAOTPVerifiedType:
data := new(es_model.OTPVerified) data := new(es_model.OTPVerified)
@@ -167,7 +184,7 @@ func (v *UserSessionView) AppendEvent(event eventstore.Event) error {
user.HumanU2FTokenRemovedType, user.HumanU2FTokenRemovedType,
user.HumanOTPSMSCheckFailedType, user.HumanOTPSMSCheckFailedType,
user.HumanOTPEmailCheckFailedType: user.HumanOTPEmailCheckFailedType:
v.SecondFactorVerification = time.Time{} v.SecondFactorVerification = sql.NullTime{Time: time.Time{}, Valid: true}
case user.HumanU2FTokenVerifiedType: case user.HumanU2FTokenVerifiedType:
data := new(es_model.WebAuthNVerify) data := new(es_model.WebAuthNVerify)
err := data.SetData(event) err := data.SetData(event)
@@ -183,24 +200,24 @@ func (v *UserSessionView) AppendEvent(event eventstore.Event) error {
user.HumanSignedOutType, user.HumanSignedOutType,
user.UserLockedType, user.UserLockedType,
user.UserDeactivatedType: user.UserDeactivatedType:
v.PasswordlessVerification = time.Time{} v.PasswordlessVerification = sql.NullTime{Time: time.Time{}, Valid: true}
v.PasswordVerification = time.Time{} v.PasswordVerification = sql.NullTime{Time: time.Time{}, Valid: true}
v.SecondFactorVerification = time.Time{} v.SecondFactorVerification = sql.NullTime{Time: time.Time{}, Valid: true}
v.SecondFactorVerificationType = int32(domain.MFALevelNotSetUp) v.SecondFactorVerificationType = sql.NullInt32{Int32: int32(domain.MFALevelNotSetUp)}
v.MultiFactorVerification = time.Time{} v.MultiFactorVerification = sql.NullTime{Time: time.Time{}, Valid: true}
v.MultiFactorVerificationType = int32(domain.MFALevelNotSetUp) v.MultiFactorVerificationType = sql.NullInt32{Int32: int32(domain.MFALevelNotSetUp)}
v.ExternalLoginVerification = time.Time{} v.ExternalLoginVerification = sql.NullTime{Time: time.Time{}, Valid: true}
v.State = int32(domain.UserSessionStateTerminated) v.State = int32(domain.UserSessionStateTerminated)
case user.UserIDPLinkRemovedType, user.UserIDPLinkCascadeRemovedType: case user.UserIDPLinkRemovedType, user.UserIDPLinkCascadeRemovedType:
v.ExternalLoginVerification = time.Time{} v.ExternalLoginVerification = sql.NullTime{Time: time.Time{}, Valid: true}
v.SelectedIDPConfigID = "" v.SelectedIDPConfigID = sql.NullString{String: "", Valid: true}
} }
return nil return nil
} }
func (v *UserSessionView) setSecondFactorVerification(verificationTime time.Time, mfaType domain.MFAType) { func (v *UserSessionView) setSecondFactorVerification(verificationTime time.Time, mfaType domain.MFAType) {
v.SecondFactorVerification = verificationTime v.SecondFactorVerification = sql.NullTime{Time: verificationTime, Valid: true}
v.SecondFactorVerificationType = int32(mfaType) v.SecondFactorVerificationType = sql.NullInt32{Int32: int32(mfaType)}
v.State = int32(domain.UserSessionStateActive) v.State = int32(domain.UserSessionStateActive)
} }

View File

@@ -1,6 +1,7 @@
package model package model
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"testing" "testing"
"time" "time"
@@ -33,7 +34,7 @@ func TestAppendEvent(t *testing.T) {
event: &es_models.Event{CreationDate: now(), Typ: user.UserV1PasswordCheckSucceededType}, event: &es_models.Event{CreationDate: now(), Typ: user.UserV1PasswordCheckSucceededType},
userView: &UserSessionView{}, 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", 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}, event: &es_models.Event{CreationDate: now(), Typ: user.HumanPasswordCheckSucceededType},
userView: &UserSessionView{}, 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", name: "append user password check failed event",
args: args{ args: args{
event: &es_models.Event{CreationDate: now(), Typ: user.UserV1PasswordCheckFailedType}, 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", name: "append human password check failed event",
args: args{ args: args{
event: &es_models.Event{CreationDate: now(), Typ: user.HumanPasswordCheckFailedType}, 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", name: "append user password changed event",
@@ -72,9 +73,9 @@ func TestAppendEvent(t *testing.T) {
return d 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", name: "append human password changed event",
@@ -91,9 +92,9 @@ func TestAppendEvent(t *testing.T) {
return d 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", name: "append human password changed event same user agent",
@@ -111,9 +112,9 @@ func TestAppendEvent(t *testing.T) {
return d 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", name: "append user otp verified event",
@@ -142,7 +143,7 @@ func TestAppendEvent(t *testing.T) {
}, },
userView: &UserSessionView{UserAgentID: "id"}, 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", 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}, event: &es_models.Event{CreationDate: now(), Typ: user.UserV1MFAOTPCheckSucceededType},
userView: &UserSessionView{}, 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", 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}, event: &es_models.Event{CreationDate: now(), Typ: user.HumanMFAOTPCheckSucceededType},
userView: &UserSessionView{}, 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", name: "append user otp check failed event",
args: args{ args: args{
event: &es_models.Event{CreationDate: now(), Typ: user.UserV1MFAOTPCheckFailedType}, 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", name: "append human otp check failed event",
args: args{ args: args{
event: &es_models.Event{CreationDate: now(), Typ: user.HumanMFAOTPCheckFailedType}, 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", name: "append user otp removed event",
args: args{ args: args{
event: &es_models.Event{CreationDate: now(), Typ: user.UserV1MFAOTPRemovedType}, 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", name: "append human otp removed event",
args: args{ args: args{
event: &es_models.Event{CreationDate: now(), Typ: user.HumanMFAOTPRemovedType}, 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", name: "append user signed out event",
args: args{ args: args{
event: &es_models.Event{CreationDate: now(), Typ: user.UserV1SignedOutType}, event: &es_models.Event{CreationDate: now(), Typ: user.UserV1SignedOutType},
userView: &UserSessionView{PasswordVerification: now(), SecondFactorVerification: now()}, 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", name: "append human signed out event",
args: args{ args: args{
event: &es_models.Event{CreationDate: now(), Typ: user.HumanSignedOutType}, event: &es_models.Event{CreationDate: now(), Typ: user.HumanSignedOutType},
userView: &UserSessionView{PasswordVerification: now(), SecondFactorVerification: now()}, 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 { for _, tt := range tests {

View File

@@ -42,74 +42,9 @@ func RefreshTokensByUserID(db *gorm.DB, table, userID, instanceID string) ([]*us
return tokens, err 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) { func SearchRefreshTokens(db *gorm.DB, table string, req *model.RefreshTokenSearchRequest) ([]*usr_model.RefreshTokenView, uint64, error) {
tokens := make([]*usr_model.RefreshTokenView, 0) tokens := make([]*usr_model.RefreshTokenView, 0)
query := repository.PrepareSearchQuery(table, usr_model.RefreshTokenSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries}) query := repository.PrepareSearchQuery(table, usr_model.RefreshTokenSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &tokens) count, err := query(db, &tokens)
return tokens, count, err 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)
}

View File

@@ -47,74 +47,3 @@ func TokensByUserID(db *gorm.DB, table, userID, instanceID string) ([]*usr_model
_, err := query(db, &tokens) _, err := query(db, &tokens)
return tokens, err 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)
}

View File

@@ -0,0 +1,93 @@
WITH auth_methods AS (
SELECT
user_id
, method_type
, token_id
, state
, instance_id
, name
FROM
projections.user_auth_methods4
WHERE
instance_id = $1
AND user_id = $2
),
verified_auth_methods AS (
SELECT
method_type
FROM
auth_methods
WHERE state = 2
)
SELECT
u.id
, u.creation_date
, LEAST(u.change_date, au.change_date) AS change_date
, u.resource_owner
, u.state AS user_state
, au.password_set
, h.password_change_required
, au.password_change
, au.last_login
, u.username AS user_name
, (SELECT array_agg(ll.login_name) login_names FROM projections.login_names3 ll
WHERE u.instance_id = ll.instance_id AND u.id = ll.user_id
GROUP BY ll.user_id, ll.instance_id) AS login_names
, l.login_name
, h.first_name
, h.last_name
, h.nick_name
, h.display_name
, h.preferred_language
, h.gender
, h.email
, h.is_email_verified
, h.phone
, h.is_phone_verified
, (SELECT COALESCE((SELECT state FROM auth_methods WHERE method_type = 1), 0)) AS otp_state
, CASE
WHEN EXISTS (SELECT true FROM verified_auth_methods WHERE method_type = 3) THEN 2
WHEN EXISTS (SELECT true FROM verified_auth_methods WHERE method_type = 2) THEN 1
ELSE 0
END AS mfa_max_set_up
, au.mfa_init_skipped
, u.sequence
, au.init_required
, au.username_change_required
, m.name AS machine_name
, m.description AS machine_description
, u.type AS user_type
, (SELECT
JSONB_AGG(json_build_object('webAuthNTokenId', token_id, 'webAuthNTokenName', name, 'state', state))
FROM auth_methods
WHERE method_type = 2
) AS u2f_tokens
, (SELECT
JSONB_AGG(json_build_object('webAuthNTokenId', token_id, 'webAuthNTokenName', name, 'state', state))
FROM auth_methods
WHERE method_type = 3
) AS passwordless_tokens
, h.avatar_key
, au.passwordless_init_required
, au.password_init_required
, u.instance_id
, (SELECT EXISTS (SELECT true FROM verified_auth_methods WHERE method_type = 6)) AS otp_sms_added
, (SELECT EXISTS (SELECT true FROM verified_auth_methods WHERE method_type = 7)) AS otp_email_added
FROM projections.users12 u
LEFT JOIN projections.users12_humans h
ON u.instance_id = h.instance_id
AND u.id = h.user_id
LEFT JOIN projections.login_names3 l
ON u.instance_id = l.instance_id
AND u.id = l.user_id
AND l.is_primary = true
LEFT JOIN projections.users12_machines m
ON u.instance_id = m.instance_id
AND u.id = m.user_id
LEFT JOIN auth.users3 au
ON u.instance_id = au.instance_id
AND u.id = au.id
WHERE
u.instance_id = $1
AND u.id = $2
LIMIT 1;

View File

@@ -5,12 +5,8 @@ import (
_ "embed" _ "embed"
"errors" "errors"
"github.com/jinzhu/gorm"
"github.com/zitadel/zitadel/internal/database" "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/user/repository/view/model"
"github.com/zitadel/zitadel/internal/view/repository"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@@ -46,38 +42,8 @@ func UserSessionsByAgentID(db *database.DB, agentID, instanceID string) (userSes
return userSessions, err 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) { func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
session := new(model.UserSessionView) session := new(model.UserSessionView)
var userName, loginName, displayName, avatarKey sql.NullString
err := row.Scan( err := row.Scan(
&session.CreationDate, &session.CreationDate,
&session.ChangeDate, &session.ChangeDate,
@@ -85,10 +51,10 @@ func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
&session.State, &session.State,
&session.UserAgentID, &session.UserAgentID,
&session.UserID, &session.UserID,
&userName, &session.UserName,
&loginName, &session.LoginName,
&displayName, &session.DisplayName,
&avatarKey, &session.AvatarKey,
&session.SelectedIDPConfigID, &session.SelectedIDPConfigID,
&session.PasswordVerification, &session.PasswordVerification,
&session.PasswordlessVerification, &session.PasswordlessVerification,
@@ -103,10 +69,6 @@ func scanUserSession(row *sql.Row) (*model.UserSessionView, error) {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, zerrors.ThrowNotFound(nil, "VIEW-NGBs1", "Errors.UserSession.NotFound") 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 return session, err
} }
@@ -114,7 +76,6 @@ func scanUserSessions(rows *sql.Rows) ([]*model.UserSessionView, error) {
sessions := make([]*model.UserSessionView, 0) sessions := make([]*model.UserSessionView, 0)
for rows.Next() { for rows.Next() {
session := new(model.UserSessionView) session := new(model.UserSessionView)
var userName, loginName, displayName, avatarKey sql.NullString
err := rows.Scan( err := rows.Scan(
&session.CreationDate, &session.CreationDate,
&session.ChangeDate, &session.ChangeDate,
@@ -122,10 +83,10 @@ func scanUserSessions(rows *sql.Rows) ([]*model.UserSessionView, error) {
&session.State, &session.State,
&session.UserAgentID, &session.UserAgentID,
&session.UserID, &session.UserID,
&userName, &session.UserName,
&loginName, &session.LoginName,
&displayName, &session.DisplayName,
&avatarKey, &session.AvatarKey,
&session.SelectedIDPConfigID, &session.SelectedIDPConfigID,
&session.PasswordVerification, &session.PasswordVerification,
&session.PasswordlessVerification, &session.PasswordlessVerification,
@@ -140,10 +101,6 @@ func scanUserSessions(rows *sql.Rows) ([]*model.UserSessionView, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
session.UserName = userName.String
session.LoginName = loginName.String
session.DisplayName = displayName.String
session.AvatarKey = avatarKey.String
sessions = append(sessions, session) sessions = append(sessions, session)
} }

View File

@@ -1,98 +1,42 @@
package view package view
import ( import (
"github.com/jinzhu/gorm" "context"
"database/sql"
_ "embed"
"errors"
"github.com/jinzhu/gorm"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/domain"
usr_model "github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/user/repository/view/model" "github.com/zitadel/zitadel/internal/user/repository/view/model"
"github.com/zitadel/zitadel/internal/view/repository"
"github.com/zitadel/zitadel/internal/zerrors" "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) { func UserByID(db *gorm.DB, table, userID, instanceID string) (*model.UserView, error) {
user := new(model.UserView) user := new(model.UserView)
userIDQuery := &model.UserSearchQuery{
Key: usr_model.UserSearchKeyUserID, query := db.Raw(userByIDQuery, instanceID, userID)
Method: domain.SearchMethodEquals,
Value: userID, tx := query.BeginTx(context.Background(), &sql.TxOptions{ReadOnly: true})
} defer func() {
instanceIDQuery := &model.UserSearchQuery{ if err := tx.Commit().Error; err != nil {
Key: usr_model.UserSearchKeyInstanceID, logging.OnError(err).Info("commit failed")
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")
} }
tx.RollbackUnlessCommitted()
}()
err := tx.Scan(user).Error
if err == nil {
user.SetEmptyUserType() user.SetEmptyUserType()
return user, err return user, nil
}
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,
} }
instanceIDQuery := &usr_model.UserSearchQuery{ if errors.Is(err, gorm.ErrRecordNotFound) {
Key: usr_model.UserSearchKeyInstanceID, return nil, zerrors.ThrowNotFound(err, "VIEW-hodc6", "object not found")
Method: domain.SearchMethodEquals,
Value: instanceID,
} }
ownerRemovedQuery := &usr_model.UserSearchQuery{ logging.WithFields("table ", table).WithError(err).Warn("get from cache error")
Key: usr_model.UserSearchOwnerRemoved, return nil, zerrors.ThrowInternal(err, "VIEW-qJBg9", "cache error")
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)
} }