diff --git a/cmd/zitadel/system-defaults.yaml b/cmd/zitadel/system-defaults.yaml index 59101bf907..748955dadc 100644 --- a/cmd/zitadel/system-defaults.yaml +++ b/cmd/zitadel/system-defaults.yaml @@ -116,7 +116,7 @@ SystemDefaults: Notifications: DebugMode: $DEBUG_MODE Endpoints: - InitCode: '$ZITADEL_ACCOUNTS/user/init?userID={{.UserID}}&code={{.Code}}' + InitCode: '$ZITADEL_ACCOUNTS/user/init?userID={{.UserID}}&code={{.Code}}&passwordset={{.PasswordSet}}' PasswordReset: '$ZITADEL_ACCOUNTS/password/init?userID={{.UserID}}&code={{.Code}}' VerifyEmail: '$ZITADEL_ACCOUNTS/mail/verification?userID={{.UserID}}&code={{.Code}}' Providers: diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 97fb301a88..1ffa718b77 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -12,10 +12,12 @@ import ( "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/id" + org_model "github.com/caos/zitadel/internal/org/model" + org_view_model "github.com/caos/zitadel/internal/org/repository/view/model" user_model "github.com/caos/zitadel/internal/user/model" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" - view_model "github.com/caos/zitadel/internal/user/repository/view/model" + user_view_model "github.com/caos/zitadel/internal/user/repository/view/model" ) type AuthRequestRepo struct { @@ -26,6 +28,7 @@ type AuthRequestRepo struct { UserSessionViewProvider userSessionViewProvider UserViewProvider userViewProvider UserEventProvider userEventProvider + OrgViewProvider orgViewProvider IdGenerator id.Generator @@ -36,17 +39,21 @@ type AuthRequestRepo struct { } type userSessionViewProvider interface { - UserSessionByIDs(string, string) (*view_model.UserSessionView, error) - UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) + UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) + UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error) } type userViewProvider interface { - UserByID(string) (*view_model.UserView, error) + UserByID(string) (*user_view_model.UserView, error) } type userEventProvider interface { UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) } +type orgViewProvider interface { + OrgByID(string) (*org_view_model.OrgView, error) +} + func (repo *AuthRequestRepo) Health(ctx context.Context) error { if err := repo.UserEvents.Health(ctx); err != nil { return err @@ -124,7 +131,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID string) if err != nil { return err } - user, err := repo.View.UserByID(userID) + user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, userID) if err != nil { return err } @@ -169,7 +176,7 @@ func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id string, chec func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthRequest, checkLoggedIn bool) ([]model.NextStep, error) { if request == nil { - return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "request must not be nil") + return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "Errors.Internal") } steps := make([]model.NextStep, 0) if !checkLoggedIn && request.Prompt == model.PromptNone { @@ -186,7 +193,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR } return steps, nil } - user, err := userByID(ctx, repo.UserViewProvider, repo.UserEventProvider, request.UserID) + user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, request.UserID) if err != nil { return nil, err } @@ -296,7 +303,7 @@ func userSessionsByUserAgentID(provider userSessionViewProvider, agentID string) if err != nil { return nil, err } - return view_model.UserSessionsToModel(session), nil + return user_view_model.UserSessionsToModel(session), nil } func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eventProvider userEventProvider, agentID string, user *user_model.UserView) (*user_model.UserSessionView, error) { @@ -305,12 +312,12 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve if !errors.IsNotFound(err) { return nil, err } - session = &view_model.UserSessionView{} + session = &user_view_model.UserSessionView{} } events, err := eventProvider.UserEventsByID(ctx, user.ID, session.Sequence) if err != nil { logging.Log("EVENT-Hse6s").WithError(err).Debug("error retrieving new events") - return view_model.UserSessionToModel(session), nil + return user_view_model.UserSessionToModel(session), nil } sessionCopy := *session for _, event := range events { @@ -319,19 +326,44 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve es_model.UserPasswordCheckFailed, es_model.MfaOtpCheckSucceeded, es_model.MfaOtpCheckFailed, - es_model.SignedOut: - eventData, err := view_model.UserSessionFromEvent(event) + es_model.SignedOut, + es_model.UserLocked, + es_model.UserDeactivated: + eventData, err := user_view_model.UserSessionFromEvent(event) if err != nil { logging.Log("EVENT-sdgT3").WithError(err).Debug("error getting event data") - return view_model.UserSessionToModel(session), nil + return user_view_model.UserSessionToModel(session), nil } if eventData.UserAgentID != agentID { continue } + case es_model.UserRemoved: + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dG2fe", "Errors.User.NotActive") } sessionCopy.AppendEvent(event) } - return view_model.UserSessionToModel(&sessionCopy), nil + return user_view_model.UserSessionToModel(&sessionCopy), nil +} + +func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, orgViewProvider orgViewProvider, userID string) (*user_model.UserView, error) { + user, err := userByID(ctx, userViewProvider, userEventProvider, userID) + if err != nil { + return nil, err + } + if user.State == user_model.USERSTATE_LOCKED || user.State == user_model.USERSTATE_SUSPEND { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-FJ262", "Errors.User.Locked") + } + if !(user.State == user_model.USERSTATE_ACTIVE || user.State == user_model.USERSTATE_INITIAL) { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-FJ262", "Errors.User.NotActive") + } + org, err := orgViewProvider.OrgByID(user.ResourceOwner) + if err != nil { + return nil, err + } + if org.State != int32(org_model.ORGSTATE_ACTIVE) { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Zws3s", "Errors.User.NotActive") + } + return user, nil } func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider userEventProvider, userID string) (*user_model.UserView, error) { @@ -342,13 +374,13 @@ func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider events, err := eventProvider.UserEventsByID(ctx, userID, user.Sequence) if err != nil { logging.Log("EVENT-dfg42").WithError(err).Debug("error retrieving new events") - return view_model.UserToModel(user), nil + return user_view_model.UserToModel(user), nil } userCopy := *user for _, event := range events { if err := userCopy.AppendEvent(event); err != nil { - return view_model.UserToModel(user), nil + return user_view_model.UserToModel(user), nil } } - return view_model.UserToModel(&userCopy), nil + return user_view_model.UserToModel(&userCopy), nil } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index c9d7d6764e..72ed9a4789 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -13,29 +13,31 @@ import ( "github.com/caos/zitadel/internal/auth_request/repository/cache" "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/models" + org_model "github.com/caos/zitadel/internal/org/model" + org_view_model "github.com/caos/zitadel/internal/org/repository/view/model" user_model "github.com/caos/zitadel/internal/user/model" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" - view_model "github.com/caos/zitadel/internal/user/repository/view/model" + user_view_model "github.com/caos/zitadel/internal/user/repository/view/model" ) type mockViewNoUserSession struct{} -func (m *mockViewNoUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) { +func (m *mockViewNoUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) { return nil, errors.ThrowNotFound(nil, "id", "user session not found") } -func (m *mockViewNoUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) { +func (m *mockViewNoUserSession) UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error) { return nil, errors.ThrowInternal(nil, "id", "internal error") } type mockViewErrUserSession struct{} -func (m *mockViewErrUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) { +func (m *mockViewErrUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) { return nil, errors.ThrowInternal(nil, "id", "internal error") } -func (m *mockViewErrUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) { +func (m *mockViewErrUserSession) UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error) { return nil, errors.ThrowInternal(nil, "id", "internal error") } @@ -50,17 +52,17 @@ type mockUser struct { LoginName string } -func (m *mockViewUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) { - return &view_model.UserSessionView{ +func (m *mockViewUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) { + return &user_view_model.UserSessionView{ PasswordVerification: m.PasswordVerification, MfaSoftwareVerification: m.MfaSoftwareVerification, }, nil } -func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) { - sessions := make([]*view_model.UserSessionView, len(m.Users)) +func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error) { + sessions := make([]*user_view_model.UserSessionView, len(m.Users)) for i, user := range m.Users { - sessions[i] = &view_model.UserSessionView{ + sessions[i] = &user_view_model.UserSessionView{ UserID: user.UserID, LoginName: user.LoginName, } @@ -70,7 +72,7 @@ func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*view_model.UserS type mockViewNoUser struct{} -func (m *mockViewNoUser) UserByID(string) (*view_model.UserView, error) { +func (m *mockViewNoUser) UserByID(string) (*user_view_model.UserView, error) { return nil, errors.ThrowNotFound(nil, "id", "user not found") } @@ -102,8 +104,8 @@ type mockViewUser struct { MfaInitSkipped time.Time } -func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) { - return &view_model.UserView{ +func (m *mockViewUser) UserByID(string) (*user_view_model.UserView, error) { + return &user_view_model.UserView{ InitRequired: m.InitRequired, PasswordSet: m.PasswordSet, PasswordChangeRequired: m.PasswordChangeRequired, @@ -111,9 +113,26 @@ func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) { OTPState: m.OTPState, MfaMaxSetUp: m.MfaMaxSetUp, MfaInitSkipped: m.MfaInitSkipped, + State: int32(user_model.USERSTATE_ACTIVE), }, nil } +type mockViewOrg struct { + State org_model.OrgState +} + +func (m *mockViewOrg) OrgByID(string) (*org_view_model.OrgView, error) { + return &org_view_model.OrgView{ + State: int32(m.State), + }, nil +} + +type mockViewErrOrg struct{} + +func (m *mockViewErrOrg) OrgByID(string) (*org_view_model.OrgView, error) { + return nil, errors.ThrowInternal(nil, "id", "internal error") +} + func TestAuthRequestRepo_nextSteps(t *testing.T) { type fields struct { UserEvents *user_event.UserEventstore @@ -122,6 +141,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userSessionViewProvider userSessionViewProvider userViewProvider userViewProvider userEventProvider userEventProvider + orgViewProvider orgViewProvider PasswordCheckLifeTime time.Duration MfaInitSkippedLifeTime time.Duration MfaSoftwareCheckLifeTime time.Duration @@ -203,7 +223,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { nil, }, { - "user not not found, not found error", + "user not found, not found error", fields{ userViewProvider: &mockViewNoUser{}, userEventProvider: &mockEventUser{}, @@ -212,6 +232,44 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { nil, errors.IsNotFound, }, + { + "user not active, precondition failed error", + fields{ + userViewProvider: &mockViewUser{}, + userEventProvider: &mockEventUser{ + &es_models.Event{ + AggregateType: user_es_model.UserAggregate, + Type: user_es_model.UserDeactivated, + }, + }, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, + }, + args{&model.AuthRequest{UserID: "UserID"}, false}, + nil, + errors.IsPreconditionFailed, + }, + { + "org error, internal error", + fields{ + userViewProvider: &mockViewUser{}, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewErrOrg{}, + }, + args{&model.AuthRequest{UserID: "UserID"}, false}, + nil, + errors.IsInternal, + }, + { + "org not active, precondition failed error", + fields{ + userViewProvider: &mockViewUser{}, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_INACTIVE}, + }, + args{&model.AuthRequest{UserID: "UserID"}, false}, + nil, + errors.IsPreconditionFailed, + }, { "usersession not found, new user session, password step", fields{ @@ -220,6 +278,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordSet: true, }, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, }, args{&model.AuthRequest{UserID: "UserID"}, false}, []model.NextStep{&model.PasswordStep{}}, @@ -231,6 +290,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userSessionViewProvider: &mockViewErrUserSession{}, userViewProvider: &mockViewUser{}, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, }, args{&model.AuthRequest{UserID: "UserID"}, false}, nil, @@ -245,6 +305,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordSet: true, }, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, }, args{&model.AuthRequest{UserID: "UserID"}, false}, []model.NextStep{&model.InitUserStep{ @@ -258,6 +319,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userSessionViewProvider: &mockViewUserSession{}, userViewProvider: &mockViewUser{}, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, }, args{&model.AuthRequest{UserID: "UserID"}, false}, []model.NextStep{&model.InitPasswordStep{}}, @@ -271,6 +333,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordSet: true, }, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, PasswordCheckLifeTime: 10 * 24 * time.Hour, }, args{&model.AuthRequest{UserID: "UserID"}, false}, @@ -289,6 +352,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MfaMaxSetUp: int32(model.MfaLevelSoftware), }, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, PasswordCheckLifeTime: 10 * 24 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour, }, @@ -312,6 +376,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MfaMaxSetUp: int32(model.MfaLevelSoftware), }, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, PasswordCheckLifeTime: 10 * 24 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour, }, @@ -331,6 +396,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MfaMaxSetUp: int32(model.MfaLevelSoftware), }, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, PasswordCheckLifeTime: 10 * 24 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour, }, @@ -351,6 +417,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MfaMaxSetUp: int32(model.MfaLevelSoftware), }, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, PasswordCheckLifeTime: 10 * 24 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour, }, @@ -371,6 +438,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MfaMaxSetUp: int32(model.MfaLevelSoftware), }, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, PasswordCheckLifeTime: 10 * 24 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour, }, @@ -391,6 +459,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MfaMaxSetUp: int32(model.MfaLevelSoftware), }, userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE}, PasswordCheckLifeTime: 10 * 24 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour, }, @@ -408,6 +477,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { UserSessionViewProvider: tt.fields.userSessionViewProvider, UserViewProvider: tt.fields.userViewProvider, UserEventProvider: tt.fields.userEventProvider, + OrgViewProvider: tt.fields.orgViewProvider, PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime, MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime, MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime, @@ -718,6 +788,24 @@ func Test_userSessionByIDs(t *testing.T) { }, nil, }, + { + "new user events (user deleted), precondition failed error", + args{ + userProvider: &mockViewUserSession{ + PasswordVerification: time.Now().UTC().Round(1 * time.Second), + }, + agentID: "agentID", + user: &user_model.UserView{ID: "id"}, + eventProvider: &mockEventUser{ + &es_models.Event{ + AggregateType: user_es_model.UserAggregate, + Type: user_es_model.UserRemoved, + }, + }, + }, + nil, + errors.IsPreconditionFailed, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -763,6 +851,7 @@ func Test_userByID(t *testing.T) { }, &user_model.UserView{ PasswordChangeRequired: true, + State: user_model.USERSTATE_ACTIVE, }, nil, }, @@ -783,6 +872,7 @@ func Test_userByID(t *testing.T) { }, &user_model.UserView{ PasswordChangeRequired: true, + State: user_model.USERSTATE_ACTIVE, }, nil, }, @@ -807,7 +897,7 @@ func Test_userByID(t *testing.T) { &user_model.UserView{ PasswordChangeRequired: false, ChangeDate: time.Now().UTC().Round(1 * time.Second), - State: user_model.USERSTATE_INITIAL, + State: user_model.USERSTATE_ACTIVE, PasswordChanged: time.Now().UTC().Round(1 * time.Second), }, nil, diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index a637e597f9..f44ee2c835 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -38,7 +38,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev return []spooler.Handler{ &User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}, orgEvents: repos.OrgEvents}, &UserSession{handler: handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount}, userEvents: repos.UserEvents}, - &Token{handler: handler{view, bulkLimit, configs.cycleDuration("Token"), errorCount}}, + &Token{handler: handler{view, bulkLimit, configs.cycleDuration("Token"), errorCount}, ProjectEvents: repos.ProjectEvents}, &Key{handler: handler{view, bulkLimit, configs.cycleDuration("Key"), errorCount}}, &Application{handler: handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount}}, &Org{handler: handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount}}, diff --git a/internal/auth/repository/eventsourcing/handler/token.go b/internal/auth/repository/eventsourcing/handler/token.go index 41ba431198..2bb193c7fd 100644 --- a/internal/auth/repository/eventsourcing/handler/token.go +++ b/internal/auth/repository/eventsourcing/handler/token.go @@ -1,21 +1,24 @@ package handler import ( + "context" "encoding/json" "time" - caos_errs "github.com/caos/zitadel/internal/errors" - es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" - "github.com/caos/logging" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" + es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/spooler" - "github.com/caos/zitadel/internal/user/repository/eventsourcing" + proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing" + project_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" + user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" ) type Token struct { handler + ProjectEvents *proj_event.ProjectEventstore } const ( @@ -33,21 +36,44 @@ func (u *Token) EventQuery() (*models.SearchQuery, error) { if err != nil { return nil, err } - return eventsourcing.UserQuery(sequence), nil + if err != nil { + return nil, err + } + return es_models.NewSearchQuery(). + AggregateTypeFilter(user_es_model.UserAggregate, project_es_model.ProjectAggregate). + LatestSequenceFilter(sequence), nil } func (u *Token) Process(event *models.Event) (err error) { switch event.Type { - case es_model.SignedOut: + case user_es_model.SignedOut: id, err := agentIDFromSession(event) if err != nil { return err } - err = u.view.DeleteSessionTokens(id, event.AggregateID, event.Sequence) + return u.view.DeleteSessionTokens(id, event.AggregateID, event.Sequence) + case user_es_model.UserLocked, + user_es_model.UserDeactivated, + user_es_model.UserRemoved: + return u.view.DeleteUserTokens(event.AggregateID, event.Sequence) + case project_es_model.ApplicationDeactivated, + project_es_model.ApplicationRemoved: + application, err := applicationFromSession(event) if err != nil { return err } - return u.view.ProcessedTokenSequence(event.Sequence) + return u.view.DeleteApplicationTokens(event.Sequence, application.AppID) + case project_es_model.ProjectDeactivated, + project_es_model.ProjectRemoved: + project, err := u.ProjectEvents.ProjectByID(context.Background(), event.AggregateID) + if err != nil { + return err + } + applicationsIDs := make([]string, 0, len(project.Applications)) + for _, app := range project.Applications { + applicationsIDs = append(applicationsIDs, app.AppID) + } + return u.view.DeleteApplicationTokens(event.Sequence, applicationsIDs...) default: return u.view.ProcessedTokenSequence(event.Sequence) } @@ -67,3 +93,12 @@ func agentIDFromSession(event *models.Event) (string, error) { } return session["userAgentID"].(string), nil } + +func applicationFromSession(event *models.Event) (*project_es_model.Application, error) { + application := new(project_es_model.Application) + if err := json.Unmarshal(event.Data, &application); err != nil { + logging.Log("EVEN-GRE2q").WithError(err).Error("could not unmarshal event data") + return nil, caos_errs.ThrowInternal(nil, "MODEL-Hrw1q", "could not unmarshal data") + } + return application, nil +} diff --git a/internal/auth/repository/eventsourcing/handler/user_session.go b/internal/auth/repository/eventsourcing/handler/user_session.go index b8b79bf957..e8bca1cd57 100644 --- a/internal/auth/repository/eventsourcing/handler/user_session.go +++ b/internal/auth/repository/eventsourcing/handler/user_session.go @@ -67,7 +67,9 @@ func (u *UserSession) Process(event *models.Event) (err error) { return u.updateSession(session, event) case es_model.UserPasswordChanged, es_model.MfaOtpRemoved, - es_model.UserProfileChanged: + es_model.UserProfileChanged, + es_model.UserLocked, + es_model.UserDeactivated: sessions, err := u.view.UserSessionsByUserID(event.AggregateID) if err != nil { return err @@ -78,6 +80,8 @@ func (u *UserSession) Process(event *models.Event) (err error) { } } return nil + case es_model.UserRemoved: + return u.view.DeleteUserSessions(event.AggregateID, event.Sequence) default: return u.view.ProcessedUserSessionSequence(event.Sequence) } @@ -89,7 +93,6 @@ func (u *UserSession) OnError(event *models.Event, err error) error { } func (u *UserSession) updateSession(session *view_model.UserSessionView, event *models.Event) error { - session.Sequence = event.Sequence session.AppendEvent(event) if err := u.fillUserInfo(session, event.AggregateID); err != nil { return err diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index bb5b606b36..56963a1999 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -138,6 +138,7 @@ func Start(conf Config, authZ auth.Config, systemDefaults sd.SystemDefaults, aut UserSessionViewProvider: view, UserViewProvider: view, UserEventProvider: user, + OrgViewProvider: view, IdGenerator: idGenerator, PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration, MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration, diff --git a/internal/auth/repository/eventsourcing/view/token.go b/internal/auth/repository/eventsourcing/view/token.go index 36602d4c98..3e37c2ed55 100644 --- a/internal/auth/repository/eventsourcing/view/token.go +++ b/internal/auth/repository/eventsourcing/view/token.go @@ -59,7 +59,23 @@ func (v *View) DeleteToken(tokenID string, eventSequence uint64) error { } func (v *View) DeleteSessionTokens(agentID, userID string, eventSequence uint64) error { - err := view.DeleteTokens(v.Db, tokenTable, agentID, userID) + err := view.DeleteSessionTokens(v.Db, tokenTable, agentID, userID) + if err != nil { + return nil + } + return v.ProcessedTokenSequence(eventSequence) +} + +func (v *View) DeleteUserTokens(userID string, eventSequence uint64) error { + err := view.DeleteUserTokens(v.Db, tokenTable, userID) + if err != nil { + return nil + } + return v.ProcessedTokenSequence(eventSequence) +} + +func (v *View) DeleteApplicationTokens(eventSequence uint64, ids ...string) error { + err := view.DeleteApplicationTokens(v.Db, tokenTable, ids) if err != nil { return nil } diff --git a/internal/auth/repository/eventsourcing/view/user_session.go b/internal/auth/repository/eventsourcing/view/user_session.go index 6882c68464..fd02f989c4 100644 --- a/internal/auth/repository/eventsourcing/view/user_session.go +++ b/internal/auth/repository/eventsourcing/view/user_session.go @@ -30,8 +30,8 @@ func (v *View) PutUserSession(userSession *model.UserSessionView) error { return v.ProcessedUserSessionSequence(userSession.Sequence) } -func (v *View) DeleteUserSession(sessionID string, eventSequence uint64) error { - err := view.DeleteUserSession(v.Db, userSessionTable, sessionID) +func (v *View) DeleteUserSessions(userID string, eventSequence uint64) error { + err := view.DeleteUserSessions(v.Db, userSessionTable, userID) if err != nil { return nil } diff --git a/internal/authz/repository/eventsourcing/view/token.go b/internal/authz/repository/eventsourcing/view/token.go index 3bbc834b4e..230d46d3a1 100644 --- a/internal/authz/repository/eventsourcing/view/token.go +++ b/internal/authz/repository/eventsourcing/view/token.go @@ -35,7 +35,7 @@ func (v *View) DeleteToken(tokenID string, eventSequence uint64) error { } func (v *View) DeleteSessionTokens(agentID, userID string, eventSequence uint64) error { - err := view.DeleteTokens(v.Db, tokenTable, agentID, userID) + err := view.DeleteSessionTokens(v.Db, tokenTable, agentID, userID) if err != nil { return nil } diff --git a/internal/login/handler/init_user_handler.go b/internal/login/handler/init_user_handler.go index a56aa814a4..007e81d0ed 100644 --- a/internal/login/handler/init_user_handler.go +++ b/internal/login/handler/init_user_handler.go @@ -4,11 +4,13 @@ import ( "github.com/caos/zitadel/internal/auth_request/model" caos_errs "github.com/caos/zitadel/internal/errors" "net/http" + "strconv" ) const ( - queryInitUserCode = "code" - queryInitUserUserID = "userID" + queryInitUserCode = "code" + queryInitUserUserID = "userID" + queryInitUserPassword = "passwordset" tmplInitUser = "inituser" tmplInitUserDone = "inituserdone" @@ -19,19 +21,22 @@ type initUserFormData struct { Password string `schema:"password"` PasswordConfirm string `schema:"passwordconfirm"` UserID string `schema:"userID"` + PasswordSet bool `schema:"passwordSet"` Resend bool `schema:"resend"` } type initUserData struct { baseData - Code string - UserID string + Code string + UserID string + PasswordSet bool } func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) { userID := r.FormValue(queryInitUserUserID) code := r.FormValue(queryInitUserCode) - l.renderInitUser(w, r, nil, userID, code, nil) + passwordSet, _ := strconv.ParseBool(r.FormValue(queryInitUserPassword)) + l.renderInitUser(w, r, nil, userID, code, passwordSet, nil) } func (l *Login) handleInitUserCheck(w http.ResponseWriter, r *http.Request) { @@ -43,7 +48,7 @@ func (l *Login) handleInitUserCheck(w http.ResponseWriter, r *http.Request) { } if data.Resend { - l.resendUserInit(w, r, authReq, data.UserID) + l.resendUserInit(w, r, authReq, data.UserID, data.PasswordSet) return } l.checkUserInitCode(w, r, authReq, data, nil) @@ -52,7 +57,7 @@ func (l *Login) handleInitUserCheck(w http.ResponseWriter, r *http.Request) { func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *initUserFormData, err error) { if data.Password != data.PasswordConfirm { err := caos_errs.ThrowInvalidArgument(nil, "VIEW-fsdfd", "Errors.User.Password.ConfirmationWrong") - l.renderInitUser(w, r, nil, data.UserID, data.Code, err) + l.renderInitUser(w, r, authReq, data.UserID, data.Code, data.PasswordSet, err) return } userOrgID := login @@ -61,22 +66,22 @@ func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authRe } err = l.authRepo.VerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, data.Code, data.Password) if err != nil { - l.renderInitUser(w, r, nil, data.UserID, "", err) + l.renderInitUser(w, r, nil, data.UserID, "", data.PasswordSet, err) return } l.renderInitUserDone(w, r, nil) } -func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID string) { +func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID string, showPassword bool) { userOrgID := login if authReq != nil { userOrgID = authReq.UserOrgID } err := l.authRepo.ResendInitVerificationMail(setContext(r.Context(), userOrgID), userID) - l.renderInitUser(w, r, authReq, userID, "", err) + l.renderInitUser(w, r, authReq, userID, "", showPassword, err) } -func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID, code string, err error) { +func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID, code string, passwordSet bool, err error) { var errType, errMessage string if err != nil { errMessage = l.getErrorMessage(r, err) @@ -85,9 +90,10 @@ func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq * userID = authReq.UserID } data := initUserData{ - baseData: l.getBaseData(r, nil, "Init User", errType, errMessage), - UserID: userID, - Code: code, + baseData: l.getBaseData(r, nil, "Init User", errType, errMessage), + UserID: userID, + Code: code, + PasswordSet: passwordSet, } l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitUser], data, nil) } diff --git a/internal/login/handler/renderer.go b/internal/login/handler/renderer.go index cc3b3e233b..3fd87ea5dc 100644 --- a/internal/login/handler/renderer.go +++ b/internal/login/handler/renderer.go @@ -168,7 +168,7 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq * case *model.MfaPromptStep: l.renderMfaPrompt(w, r, authReq, step, err) case *model.InitUserStep: - l.renderInitUser(w, r, authReq, "", "", nil) + l.renderInitUser(w, r, authReq, "", "", step.PasswordSet, nil) default: l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible")) } diff --git a/internal/login/static/i18n/de.yaml b/internal/login/static/i18n/de.yaml index a05bb6af62..feee5bcdf4 100644 --- a/internal/login/static/i18n/de.yaml +++ b/internal/login/static/i18n/de.yaml @@ -153,5 +153,7 @@ Errors: NotExisting: Multifaktor OTP (OneTimePassword) existiert nicht InvalidCode: Code ist ungültig NotReady: Multifaktor OTP (OneTimePassword) ist nicht bereit + Locked: Benutzer ist gesperrt + NotActive: Benutzer ist nicht aktiv optional: (optional) diff --git a/internal/login/static/i18n/en.yaml b/internal/login/static/i18n/en.yaml index 621a1217c3..b12597d742 100644 --- a/internal/login/static/i18n/en.yaml +++ b/internal/login/static/i18n/en.yaml @@ -154,6 +154,8 @@ Errors: NotExisting: Multifactor OTP (OneTimePassword) doesn't exist InvalidCode: Invalid code NotReady: Multifactor OTP (OneTimePassword) isn't ready + Locked: User is locked + NotActive: User is not active optional: (optional) diff --git a/internal/login/static/resources/scripts/copy_to_clipboard.js b/internal/login/static/resources/scripts/copy_to_clipboard.js index 55e4609551..bbc879a204 100644 --- a/internal/login/static/resources/scripts/copy_to_clipboard.js +++ b/internal/login/static/resources/scripts/copy_to_clipboard.js @@ -3,4 +3,4 @@ const copyToClipboard = str => { } let copyButton = document.getElementsByClassName("copy")[0]; -copyButton.addEventListener("click", copyToClipboard(copyButton.getAttribute("data-copy").value)); +copyButton.addEventListener("click", copyToClipboard(copyButton.getAttribute("data-copy"))); diff --git a/internal/login/static/resources/themes/scss/light.scss b/internal/login/static/resources/themes/scss/light.scss index dbe3b7a11b..3c75ea60dc 100644 --- a/internal/login/static/resources/themes/scss/light.scss +++ b/internal/login/static/resources/themes/scss/light.scss @@ -68,12 +68,12 @@ html { } #qrcode { - svg rect[style*="fill:white"] { - fill: $backgroundColorLight !important; + svg rect.color { + fill: $fontColorLight; } - svg rect[style*="fill:black"] { - fill: $fontColorLight !important; + svg rect.bg-color { + fill: $backgroundColorLight; } } diff --git a/internal/login/static/templates/init_user.html b/internal/login/static/templates/init_user.html index b383dae57d..aad9bbd444 100644 --- a/internal/login/static/templates/init_user.html +++ b/internal/login/static/templates/init_user.html @@ -10,12 +10,14 @@ +