fix: login (#242)

* password in init user only if needed

* reactivate user session

* set context AuthorizeClientIDSecret

* fix qr code for light

* fix copy

* check user and org active in auth

* add org view provider

* handle inactive projects

* translate error messages
This commit is contained in:
Livio Amstutz 2020-06-19 14:52:04 +02:00 committed by GitHub
parent fb89241984
commit e653eaab86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 327 additions and 83 deletions

View File

@ -116,7 +116,7 @@ SystemDefaults:
Notifications: Notifications:
DebugMode: $DEBUG_MODE DebugMode: $DEBUG_MODE
Endpoints: 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}}' PasswordReset: '$ZITADEL_ACCOUNTS/password/init?userID={{.UserID}}&code={{.Code}}'
VerifyEmail: '$ZITADEL_ACCOUNTS/mail/verification?userID={{.UserID}}&code={{.Code}}' VerifyEmail: '$ZITADEL_ACCOUNTS/mail/verification?userID={{.UserID}}&code={{.Code}}'
Providers: Providers:

View File

@ -12,10 +12,12 @@ import (
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/id" "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_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" 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 { type AuthRequestRepo struct {
@ -26,6 +28,7 @@ type AuthRequestRepo struct {
UserSessionViewProvider userSessionViewProvider UserSessionViewProvider userSessionViewProvider
UserViewProvider userViewProvider UserViewProvider userViewProvider
UserEventProvider userEventProvider UserEventProvider userEventProvider
OrgViewProvider orgViewProvider
IdGenerator id.Generator IdGenerator id.Generator
@ -36,17 +39,21 @@ type AuthRequestRepo struct {
} }
type userSessionViewProvider interface { type userSessionViewProvider interface {
UserSessionByIDs(string, string) (*view_model.UserSessionView, error) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error)
UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error)
} }
type userViewProvider interface { type userViewProvider interface {
UserByID(string) (*view_model.UserView, error) UserByID(string) (*user_view_model.UserView, error)
} }
type userEventProvider interface { type userEventProvider interface {
UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) 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 { func (repo *AuthRequestRepo) Health(ctx context.Context) error {
if err := repo.UserEvents.Health(ctx); err != nil { if err := repo.UserEvents.Health(ctx); err != nil {
return err return err
@ -124,7 +131,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID string)
if err != nil { if err != nil {
return err return err
} }
user, err := repo.View.UserByID(userID) user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, userID)
if err != nil { if err != nil {
return err 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) { func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthRequest, checkLoggedIn bool) ([]model.NextStep, error) {
if request == nil { 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) steps := make([]model.NextStep, 0)
if !checkLoggedIn && request.Prompt == model.PromptNone { if !checkLoggedIn && request.Prompt == model.PromptNone {
@ -186,7 +193,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR
} }
return steps, nil 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 { if err != nil {
return nil, err return nil, err
} }
@ -296,7 +303,7 @@ func userSessionsByUserAgentID(provider userSessionViewProvider, agentID string)
if err != nil { if err != nil {
return nil, err 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) { 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) { if !errors.IsNotFound(err) {
return nil, err return nil, err
} }
session = &view_model.UserSessionView{} session = &user_view_model.UserSessionView{}
} }
events, err := eventProvider.UserEventsByID(ctx, user.ID, session.Sequence) events, err := eventProvider.UserEventsByID(ctx, user.ID, session.Sequence)
if err != nil { if err != nil {
logging.Log("EVENT-Hse6s").WithError(err).Debug("error retrieving new events") 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 sessionCopy := *session
for _, event := range events { for _, event := range events {
@ -319,19 +326,44 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
es_model.UserPasswordCheckFailed, es_model.UserPasswordCheckFailed,
es_model.MfaOtpCheckSucceeded, es_model.MfaOtpCheckSucceeded,
es_model.MfaOtpCheckFailed, es_model.MfaOtpCheckFailed,
es_model.SignedOut: es_model.SignedOut,
eventData, err := view_model.UserSessionFromEvent(event) es_model.UserLocked,
es_model.UserDeactivated:
eventData, err := user_view_model.UserSessionFromEvent(event)
if err != nil { if err != nil {
logging.Log("EVENT-sdgT3").WithError(err).Debug("error getting event data") 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 { if eventData.UserAgentID != agentID {
continue continue
} }
case es_model.UserRemoved:
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dG2fe", "Errors.User.NotActive")
} }
sessionCopy.AppendEvent(event) 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) { 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) events, err := eventProvider.UserEventsByID(ctx, userID, user.Sequence)
if err != nil { if err != nil {
logging.Log("EVENT-dfg42").WithError(err).Debug("error retrieving new events") 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 userCopy := *user
for _, event := range events { for _, event := range events {
if err := userCopy.AppendEvent(event); err != nil { 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
} }

View File

@ -13,29 +13,31 @@ import (
"github.com/caos/zitadel/internal/auth_request/repository/cache" "github.com/caos/zitadel/internal/auth_request/repository/cache"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models" 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_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" 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{} 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") 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") return nil, errors.ThrowInternal(nil, "id", "internal error")
} }
type mockViewErrUserSession struct{} 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") 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") return nil, errors.ThrowInternal(nil, "id", "internal error")
} }
@ -50,17 +52,17 @@ type mockUser struct {
LoginName string LoginName string
} }
func (m *mockViewUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) { func (m *mockViewUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) {
return &view_model.UserSessionView{ return &user_view_model.UserSessionView{
PasswordVerification: m.PasswordVerification, PasswordVerification: m.PasswordVerification,
MfaSoftwareVerification: m.MfaSoftwareVerification, MfaSoftwareVerification: m.MfaSoftwareVerification,
}, nil }, nil
} }
func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) { func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error) {
sessions := make([]*view_model.UserSessionView, len(m.Users)) sessions := make([]*user_view_model.UserSessionView, len(m.Users))
for i, user := range m.Users { for i, user := range m.Users {
sessions[i] = &view_model.UserSessionView{ sessions[i] = &user_view_model.UserSessionView{
UserID: user.UserID, UserID: user.UserID,
LoginName: user.LoginName, LoginName: user.LoginName,
} }
@ -70,7 +72,7 @@ func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*view_model.UserS
type mockViewNoUser struct{} 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") return nil, errors.ThrowNotFound(nil, "id", "user not found")
} }
@ -102,8 +104,8 @@ type mockViewUser struct {
MfaInitSkipped time.Time MfaInitSkipped time.Time
} }
func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) { func (m *mockViewUser) UserByID(string) (*user_view_model.UserView, error) {
return &view_model.UserView{ return &user_view_model.UserView{
InitRequired: m.InitRequired, InitRequired: m.InitRequired,
PasswordSet: m.PasswordSet, PasswordSet: m.PasswordSet,
PasswordChangeRequired: m.PasswordChangeRequired, PasswordChangeRequired: m.PasswordChangeRequired,
@ -111,9 +113,26 @@ func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) {
OTPState: m.OTPState, OTPState: m.OTPState,
MfaMaxSetUp: m.MfaMaxSetUp, MfaMaxSetUp: m.MfaMaxSetUp,
MfaInitSkipped: m.MfaInitSkipped, MfaInitSkipped: m.MfaInitSkipped,
State: int32(user_model.USERSTATE_ACTIVE),
}, nil }, 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) { func TestAuthRequestRepo_nextSteps(t *testing.T) {
type fields struct { type fields struct {
UserEvents *user_event.UserEventstore UserEvents *user_event.UserEventstore
@ -122,6 +141,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userSessionViewProvider userSessionViewProvider userSessionViewProvider userSessionViewProvider
userViewProvider userViewProvider userViewProvider userViewProvider
userEventProvider userEventProvider userEventProvider userEventProvider
orgViewProvider orgViewProvider
PasswordCheckLifeTime time.Duration PasswordCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration MfaSoftwareCheckLifeTime time.Duration
@ -203,7 +223,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
nil, nil,
}, },
{ {
"user not not found, not found error", "user not found, not found error",
fields{ fields{
userViewProvider: &mockViewNoUser{}, userViewProvider: &mockViewNoUser{},
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
@ -212,6 +232,44 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
nil, nil,
errors.IsNotFound, 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", "usersession not found, new user session, password step",
fields{ fields{
@ -220,6 +278,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true, PasswordSet: true,
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
}, },
args{&model.AuthRequest{UserID: "UserID"}, false}, args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.PasswordStep{}}, []model.NextStep{&model.PasswordStep{}},
@ -231,6 +290,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userSessionViewProvider: &mockViewErrUserSession{}, userSessionViewProvider: &mockViewErrUserSession{},
userViewProvider: &mockViewUser{}, userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
}, },
args{&model.AuthRequest{UserID: "UserID"}, false}, args{&model.AuthRequest{UserID: "UserID"}, false},
nil, nil,
@ -245,6 +305,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true, PasswordSet: true,
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
}, },
args{&model.AuthRequest{UserID: "UserID"}, false}, args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.InitUserStep{ []model.NextStep{&model.InitUserStep{
@ -258,6 +319,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userSessionViewProvider: &mockViewUserSession{}, userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewUser{}, userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
}, },
args{&model.AuthRequest{UserID: "UserID"}, false}, args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.InitPasswordStep{}}, []model.NextStep{&model.InitPasswordStep{}},
@ -271,6 +333,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true, PasswordSet: true,
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
}, },
args{&model.AuthRequest{UserID: "UserID"}, false}, args{&model.AuthRequest{UserID: "UserID"}, false},
@ -289,6 +352,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware), MfaMaxSetUp: int32(model.MfaLevelSoftware),
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour,
}, },
@ -312,6 +376,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware), MfaMaxSetUp: int32(model.MfaLevelSoftware),
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour,
}, },
@ -331,6 +396,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware), MfaMaxSetUp: int32(model.MfaLevelSoftware),
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour,
}, },
@ -351,6 +417,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware), MfaMaxSetUp: int32(model.MfaLevelSoftware),
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour,
}, },
@ -371,6 +438,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware), MfaMaxSetUp: int32(model.MfaLevelSoftware),
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour,
}, },
@ -391,6 +459,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware), MfaMaxSetUp: int32(model.MfaLevelSoftware),
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour,
}, },
@ -408,6 +477,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
UserSessionViewProvider: tt.fields.userSessionViewProvider, UserSessionViewProvider: tt.fields.userSessionViewProvider,
UserViewProvider: tt.fields.userViewProvider, UserViewProvider: tt.fields.userViewProvider,
UserEventProvider: tt.fields.userEventProvider, UserEventProvider: tt.fields.userEventProvider,
OrgViewProvider: tt.fields.orgViewProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime, PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime, MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime, MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
@ -718,6 +788,24 @@ func Test_userSessionByIDs(t *testing.T) {
}, },
nil, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -763,6 +851,7 @@ func Test_userByID(t *testing.T) {
}, },
&user_model.UserView{ &user_model.UserView{
PasswordChangeRequired: true, PasswordChangeRequired: true,
State: user_model.USERSTATE_ACTIVE,
}, },
nil, nil,
}, },
@ -783,6 +872,7 @@ func Test_userByID(t *testing.T) {
}, },
&user_model.UserView{ &user_model.UserView{
PasswordChangeRequired: true, PasswordChangeRequired: true,
State: user_model.USERSTATE_ACTIVE,
}, },
nil, nil,
}, },
@ -807,7 +897,7 @@ func Test_userByID(t *testing.T) {
&user_model.UserView{ &user_model.UserView{
PasswordChangeRequired: false, PasswordChangeRequired: false,
ChangeDate: time.Now().UTC().Round(1 * time.Second), 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), PasswordChanged: time.Now().UTC().Round(1 * time.Second),
}, },
nil, nil,

View File

@ -38,7 +38,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev
return []spooler.Handler{ return []spooler.Handler{
&User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}, orgEvents: repos.OrgEvents}, &User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}, orgEvents: repos.OrgEvents},
&UserSession{handler: handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount}, userEvents: repos.UserEvents}, &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}}, &Key{handler: handler{view, bulkLimit, configs.cycleDuration("Key"), errorCount}},
&Application{handler: handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount}}, &Application{handler: handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount}},
&Org{handler: handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount}}, &Org{handler: handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount}},

View File

@ -1,21 +1,24 @@
package handler package handler
import ( import (
"context"
"encoding/json" "encoding/json"
"time" "time"
caos_errs "github.com/caos/zitadel/internal/errors"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"github.com/caos/logging" "github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models" "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/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 { type Token struct {
handler handler
ProjectEvents *proj_event.ProjectEventstore
} }
const ( const (
@ -33,21 +36,44 @@ func (u *Token) EventQuery() (*models.SearchQuery, error) {
if err != nil { if err != nil {
return nil, err 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) { func (u *Token) Process(event *models.Event) (err error) {
switch event.Type { switch event.Type {
case es_model.SignedOut: case user_es_model.SignedOut:
id, err := agentIDFromSession(event) id, err := agentIDFromSession(event)
if err != nil { if err != nil {
return err 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 { if err != nil {
return err 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: default:
return u.view.ProcessedTokenSequence(event.Sequence) return u.view.ProcessedTokenSequence(event.Sequence)
} }
@ -67,3 +93,12 @@ func agentIDFromSession(event *models.Event) (string, error) {
} }
return session["userAgentID"].(string), nil 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
}

View File

@ -67,7 +67,9 @@ func (u *UserSession) Process(event *models.Event) (err error) {
return u.updateSession(session, event) return u.updateSession(session, event)
case es_model.UserPasswordChanged, case es_model.UserPasswordChanged,
es_model.MfaOtpRemoved, es_model.MfaOtpRemoved,
es_model.UserProfileChanged: es_model.UserProfileChanged,
es_model.UserLocked,
es_model.UserDeactivated:
sessions, err := u.view.UserSessionsByUserID(event.AggregateID) sessions, err := u.view.UserSessionsByUserID(event.AggregateID)
if err != nil { if err != nil {
return err return err
@ -78,6 +80,8 @@ func (u *UserSession) Process(event *models.Event) (err error) {
} }
} }
return nil return nil
case es_model.UserRemoved:
return u.view.DeleteUserSessions(event.AggregateID, event.Sequence)
default: default:
return u.view.ProcessedUserSessionSequence(event.Sequence) 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 { func (u *UserSession) updateSession(session *view_model.UserSessionView, event *models.Event) error {
session.Sequence = event.Sequence
session.AppendEvent(event) session.AppendEvent(event)
if err := u.fillUserInfo(session, event.AggregateID); err != nil { if err := u.fillUserInfo(session, event.AggregateID); err != nil {
return err return err

View File

@ -138,6 +138,7 @@ func Start(conf Config, authZ auth.Config, systemDefaults sd.SystemDefaults, aut
UserSessionViewProvider: view, UserSessionViewProvider: view,
UserViewProvider: view, UserViewProvider: view,
UserEventProvider: user, UserEventProvider: user,
OrgViewProvider: view,
IdGenerator: idGenerator, IdGenerator: idGenerator,
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration, PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration, MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration,

View File

@ -59,7 +59,23 @@ func (v *View) DeleteToken(tokenID string, eventSequence uint64) error {
} }
func (v *View) DeleteSessionTokens(agentID, userID 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 { if err != nil {
return nil return nil
} }

View File

@ -30,8 +30,8 @@ func (v *View) PutUserSession(userSession *model.UserSessionView) error {
return v.ProcessedUserSessionSequence(userSession.Sequence) return v.ProcessedUserSessionSequence(userSession.Sequence)
} }
func (v *View) DeleteUserSession(sessionID string, eventSequence uint64) error { func (v *View) DeleteUserSessions(userID string, eventSequence uint64) error {
err := view.DeleteUserSession(v.Db, userSessionTable, sessionID) err := view.DeleteUserSessions(v.Db, userSessionTable, userID)
if err != nil { if err != nil {
return nil return nil
} }

View File

@ -35,7 +35,7 @@ func (v *View) DeleteToken(tokenID string, eventSequence uint64) error {
} }
func (v *View) DeleteSessionTokens(agentID, userID 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 { if err != nil {
return nil return nil
} }

View File

@ -4,11 +4,13 @@ import (
"github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/auth_request/model"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"net/http" "net/http"
"strconv"
) )
const ( const (
queryInitUserCode = "code" queryInitUserCode = "code"
queryInitUserUserID = "userID" queryInitUserUserID = "userID"
queryInitUserPassword = "passwordset"
tmplInitUser = "inituser" tmplInitUser = "inituser"
tmplInitUserDone = "inituserdone" tmplInitUserDone = "inituserdone"
@ -19,19 +21,22 @@ type initUserFormData struct {
Password string `schema:"password"` Password string `schema:"password"`
PasswordConfirm string `schema:"passwordconfirm"` PasswordConfirm string `schema:"passwordconfirm"`
UserID string `schema:"userID"` UserID string `schema:"userID"`
PasswordSet bool `schema:"passwordSet"`
Resend bool `schema:"resend"` Resend bool `schema:"resend"`
} }
type initUserData struct { type initUserData struct {
baseData baseData
Code string Code string
UserID string UserID string
PasswordSet bool
} }
func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) { func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) {
userID := r.FormValue(queryInitUserUserID) userID := r.FormValue(queryInitUserUserID)
code := r.FormValue(queryInitUserCode) 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) { 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 { if data.Resend {
l.resendUserInit(w, r, authReq, data.UserID) l.resendUserInit(w, r, authReq, data.UserID, data.PasswordSet)
return return
} }
l.checkUserInitCode(w, r, authReq, data, nil) 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) { func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *initUserFormData, err error) {
if data.Password != data.PasswordConfirm { if data.Password != data.PasswordConfirm {
err := caos_errs.ThrowInvalidArgument(nil, "VIEW-fsdfd", "Errors.User.Password.ConfirmationWrong") 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 return
} }
userOrgID := login 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) err = l.authRepo.VerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, data.Code, data.Password)
if err != nil { if err != nil {
l.renderInitUser(w, r, nil, data.UserID, "", err) l.renderInitUser(w, r, nil, data.UserID, "", data.PasswordSet, err)
return return
} }
l.renderInitUserDone(w, r, nil) 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 userOrgID := login
if authReq != nil { if authReq != nil {
userOrgID = authReq.UserOrgID userOrgID = authReq.UserOrgID
} }
err := l.authRepo.ResendInitVerificationMail(setContext(r.Context(), userOrgID), userID) 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 var errType, errMessage string
if err != nil { if err != nil {
errMessage = l.getErrorMessage(r, err) errMessage = l.getErrorMessage(r, err)
@ -85,9 +90,10 @@ func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq *
userID = authReq.UserID userID = authReq.UserID
} }
data := initUserData{ data := initUserData{
baseData: l.getBaseData(r, nil, "Init User", errType, errMessage), baseData: l.getBaseData(r, nil, "Init User", errType, errMessage),
UserID: userID, UserID: userID,
Code: code, Code: code,
PasswordSet: passwordSet,
} }
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitUser], data, nil) l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitUser], data, nil)
} }

View File

@ -168,7 +168,7 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
case *model.MfaPromptStep: case *model.MfaPromptStep:
l.renderMfaPrompt(w, r, authReq, step, err) l.renderMfaPrompt(w, r, authReq, step, err)
case *model.InitUserStep: case *model.InitUserStep:
l.renderInitUser(w, r, authReq, "", "", nil) l.renderInitUser(w, r, authReq, "", "", step.PasswordSet, nil)
default: default:
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible")) l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible"))
} }

View File

@ -153,5 +153,7 @@ Errors:
NotExisting: Multifaktor OTP (OneTimePassword) existiert nicht NotExisting: Multifaktor OTP (OneTimePassword) existiert nicht
InvalidCode: Code ist ungültig InvalidCode: Code ist ungültig
NotReady: Multifaktor OTP (OneTimePassword) ist nicht bereit NotReady: Multifaktor OTP (OneTimePassword) ist nicht bereit
Locked: Benutzer ist gesperrt
NotActive: Benutzer ist nicht aktiv
optional: (optional) optional: (optional)

View File

@ -154,6 +154,8 @@ Errors:
NotExisting: Multifactor OTP (OneTimePassword) doesn't exist NotExisting: Multifactor OTP (OneTimePassword) doesn't exist
InvalidCode: Invalid code InvalidCode: Invalid code
NotReady: Multifactor OTP (OneTimePassword) isn't ready NotReady: Multifactor OTP (OneTimePassword) isn't ready
Locked: User is locked
NotActive: User is not active
optional: (optional) optional: (optional)

View File

@ -3,4 +3,4 @@ const copyToClipboard = str => {
} }
let copyButton = document.getElementsByClassName("copy")[0]; let copyButton = document.getElementsByClassName("copy")[0];
copyButton.addEventListener("click", copyToClipboard(copyButton.getAttribute("data-copy").value)); copyButton.addEventListener("click", copyToClipboard(copyButton.getAttribute("data-copy")));

View File

@ -68,12 +68,12 @@ html {
} }
#qrcode { #qrcode {
svg rect[style*="fill:white"] { svg rect.color {
fill: $backgroundColorLight !important; fill: $fontColorLight;
} }
svg rect[style*="fill:black"] { svg rect.bg-color {
fill: $fontColorLight !important; fill: $backgroundColorLight;
} }
} }

View File

@ -10,12 +10,14 @@
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" /> <input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="userID" value="{{ .UserID }}" /> <input type="hidden" name="userID" value="{{ .UserID }}" />
<input type="hidden" name="passwordSet" value="{{ .PasswordSet }}" />
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<label class="label" for="code">{{t "InitUser.Code"}}</label> <label class="label" for="code">{{t "InitUser.Code"}}</label>
<input class="input" type="text" id="code" name="code" value="{{.Code}}" autocomplete="off" autofocus required> <input class="input" type="text" id="code" name="code" value="{{.Code}}" autocomplete="off" autofocus required>
</div> </div>
{{ if not .PasswordSet }}
<div class="field"> <div class="field">
<label class="label" for="password">{{t "InitUser.NewPassword"}}</label> <label class="label" for="password">{{t "InitUser.NewPassword"}}</label>
<input class="input" type="password" id="password" name="password" autocomplete="new-password" autofocus required> <input class="input" type="password" id="password" name="password" autocomplete="new-password" autofocus required>
@ -24,12 +26,13 @@
<label class="label" for="passwordconfirm">{{t "InitUser.NewPasswordConfirm"}}</label> <label class="label" for="passwordconfirm">{{t "InitUser.NewPasswordConfirm"}}</label>
<input class="input" type="password" id="passwordconfirm" name="passwordconfirm" autocomplete="new-password" autofocus required> <input class="input" type="password" id="passwordconfirm" name="passwordconfirm" autocomplete="new-password" autofocus required>
</div> </div>
{{ end }}
</div> </div>
{{ template "error-message" .}} {{ template "error-message" .}}
<div class="actions"> <div class="actions">
<button type="submit" name="resend" value="false" class="primary right" >{{t "Actions.Next"}}</buttontype="submit"> <button type="submit" name="resend" value="false" class="primary right" >{{t "Actions.Next"}}</button>
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend" }}</button> <button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend" }}</button>
</div> </div>
</form> </form>

View File

@ -16,8 +16,9 @@ type InitCodeEmailData struct {
} }
type UrlData struct { type UrlData struct {
UserID string UserID string
Code string Code string
PasswordSet bool
} }
func SendUserInitCode(dir http.FileSystem, i18n *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error { func SendUserInitCode(dir http.FileSystem, i18n *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error {
@ -25,7 +26,7 @@ func SendUserInitCode(dir http.FileSystem, i18n *i18n.Translator, user *view_mod
if err != nil { if err != nil {
return err return err
} }
url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.InitCode, &UrlData{UserID: user.ID, Code: codeString}) url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.InitCode, &UrlData{UserID: user.ID, Code: codeString, PasswordSet: user.PasswordSet})
if err != nil { if err != nil {
return err return err
} }

View File

@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/lib/pq"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
token_model "github.com/caos/zitadel/internal/token/model" token_model "github.com/caos/zitadel/internal/token/model"
@ -39,10 +40,20 @@ func DeleteToken(db *gorm.DB, table, tokenID string) error {
return delete(db) return delete(db)
} }
func DeleteTokens(db *gorm.DB, table, agentID, userID string) error { func DeleteSessionTokens(db *gorm.DB, table, agentID, userID string) error {
delete := view.PrepareDeleteByKeys(table, delete := view.PrepareDeleteByKeys(table,
view.Key{Key: model.TokenSearchKey(token_model.TOKENSEARCHKEY_USER_AGENT_ID), Value: agentID}, view.Key{Key: model.TokenSearchKey(token_model.TOKENSEARCHKEY_USER_AGENT_ID), Value: agentID},
view.Key{Key: model.TokenSearchKey(token_model.TOKENSEARCHKEY_USER_ID), Value: userID}, view.Key{Key: model.TokenSearchKey(token_model.TOKENSEARCHKEY_USER_ID), Value: userID},
) )
return delete(db) return delete(db)
} }
func DeleteUserTokens(db *gorm.DB, table, userID string) error {
delete := view.PrepareDeleteByKey(table, model.TokenSearchKey(token_model.TOKENSEARCHKEY_USER_ID), userID)
return delete(db)
}
func DeleteApplicationTokens(db *gorm.DB, table string, appIDs []string) error {
delete := view.PrepareDeleteByKey(table, model.TokenSearchKey(token_model.TOKENSEARCHKEY_APPLICATION_ID), pq.StringArray(appIDs))
return delete(db)
}

View File

@ -21,6 +21,7 @@ type NotifyUser struct {
VerifiedEmail string VerifiedEmail string
LastPhone string LastPhone string
VerifiedPhone string VerifiedPhone string
PasswordSet bool
Sequence uint64 Sequence uint64
} }

View File

@ -30,6 +30,7 @@ type NotifyUser struct {
VerifiedEmail string `json:"-" gorm:"column:verified_email"` VerifiedEmail string `json:"-" gorm:"column:verified_email"`
LastPhone string `json:"phone" gorm:"column:last_phone"` LastPhone string `json:"phone" gorm:"column:last_phone"`
VerifiedPhone string `json:"-" gorm:"column:verified_phone"` VerifiedPhone string `json:"-" gorm:"column:verified_phone"`
PasswordSet bool `json:"-" gorm:"column:password_set"`
Sequence uint64 `json:"-" gorm:"column:sequence"` Sequence uint64 `json:"-" gorm:"column:sequence"`
} }
@ -50,6 +51,7 @@ func NotifyUserFromModel(user *model.NotifyUser) *NotifyUser {
VerifiedEmail: user.VerifiedEmail, VerifiedEmail: user.VerifiedEmail,
LastPhone: user.LastPhone, LastPhone: user.LastPhone,
VerifiedPhone: user.VerifiedPhone, VerifiedPhone: user.VerifiedPhone,
PasswordSet: user.PasswordSet,
Sequence: user.Sequence, Sequence: user.Sequence,
} }
} }
@ -71,6 +73,7 @@ func NotifyUserToModel(user *NotifyUser) *model.NotifyUser {
VerifiedEmail: user.VerifiedEmail, VerifiedEmail: user.VerifiedEmail,
LastPhone: user.LastPhone, LastPhone: user.LastPhone,
VerifiedPhone: user.VerifiedPhone, VerifiedPhone: user.VerifiedPhone,
PasswordSet: user.PasswordSet,
Sequence: user.Sequence, Sequence: user.Sequence,
} }
} }
@ -84,6 +87,10 @@ func (u *NotifyUser) AppendEvent(event *models.Event) (err error) {
u.CreationDate = event.CreationDate u.CreationDate = event.CreationDate
u.setRootData(event) u.setRootData(event)
err = u.setData(event) err = u.setData(event)
if err != nil {
return err
}
err = u.setPasswordData(event)
case es_model.UserProfileChanged: case es_model.UserProfileChanged:
err = u.setData(event) err = u.setData(event)
case es_model.UserEmailChanged: case es_model.UserEmailChanged:
@ -94,6 +101,8 @@ func (u *NotifyUser) AppendEvent(event *models.Event) (err error) {
err = u.setData(event) err = u.setData(event)
case es_model.UserPhoneVerified: case es_model.UserPhoneVerified:
u.VerifiedPhone = u.LastPhone u.VerifiedPhone = u.LastPhone
case es_model.UserPasswordChanged:
err = u.setPasswordData(event)
} }
return err return err
} }
@ -105,8 +114,18 @@ func (u *NotifyUser) setRootData(event *models.Event) {
func (u *NotifyUser) setData(event *models.Event) error { func (u *NotifyUser) setData(event *models.Event) error {
if err := json.Unmarshal(event.Data, u); err != nil { if err := json.Unmarshal(event.Data, u); err != nil {
logging.Log("EVEN-lso9e").WithError(err).Error("could not unmarshal event data") logging.Log("MODEL-lso9e").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(nil, "MODEL-8iows", "could not unmarshal data") return caos_errs.ThrowInternal(nil, "MODEL-8iows", "could not unmarshal data")
} }
return nil return nil
} }
func (u *NotifyUser) setPasswordData(event *models.Event) error {
password := new(es_model.Password)
if err := json.Unmarshal(event.Data, password); err != nil {
logging.Log("MODEL-dfhw6").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(nil, "MODEL-BHFD2", "could not unmarshal data")
}
u.PasswordSet = password.Secret != nil
return nil
}

View File

@ -76,20 +76,25 @@ func UserSessionsToModel(userSessions []*UserSessionView) []*model.UserSessionVi
} }
func (v *UserSessionView) AppendEvent(event *models.Event) { func (v *UserSessionView) AppendEvent(event *models.Event) {
v.Sequence = event.Sequence
v.ChangeDate = event.CreationDate v.ChangeDate = event.CreationDate
switch event.Type { switch event.Type {
case es_model.UserPasswordCheckSucceeded: case es_model.UserPasswordCheckSucceeded:
v.PasswordVerification = event.CreationDate v.PasswordVerification = event.CreationDate
v.State = int32(req_model.UserSessionStateActive)
case es_model.UserPasswordCheckFailed, case es_model.UserPasswordCheckFailed,
es_model.UserPasswordChanged: es_model.UserPasswordChanged:
v.PasswordVerification = time.Time{} v.PasswordVerification = time.Time{}
case es_model.MfaOtpCheckSucceeded: case es_model.MfaOtpCheckSucceeded:
v.MfaSoftwareVerification = event.CreationDate v.MfaSoftwareVerification = event.CreationDate
v.MfaSoftwareVerificationType = int32(req_model.MfaTypeOTP) v.MfaSoftwareVerificationType = int32(req_model.MfaTypeOTP)
v.State = int32(req_model.UserSessionStateActive)
case es_model.MfaOtpCheckFailed, case es_model.MfaOtpCheckFailed,
es_model.MfaOtpRemoved: es_model.MfaOtpRemoved:
v.MfaSoftwareVerification = time.Time{} v.MfaSoftwareVerification = time.Time{}
case es_model.SignedOut: case es_model.SignedOut,
es_model.UserLocked,
es_model.UserDeactivated:
v.PasswordVerification = time.Time{} v.PasswordVerification = time.Time{}
v.MfaSoftwareVerification = time.Time{} v.MfaSoftwareVerification = time.Time{}
v.State = int32(req_model.UserSessionStateTerminated) v.State = int32(req_model.UserSessionStateTerminated)

View File

@ -59,7 +59,7 @@ func PutUserSession(db *gorm.DB, table string, session *model.UserSessionView) e
return save(db, session) return save(db, session)
} }
func DeleteUserSession(db *gorm.DB, table, sessionID string) error { func DeleteUserSessions(db *gorm.DB, table, userID string) error {
delete := view.PrepareDeleteByKey(table, model.UserSessionSearchKey(usr_model.USERSESSIONSEARCHKEY_USER_ID), sessionID) delete := view.PrepareDeleteByKey(table, model.UserSessionSearchKey(usr_model.USERSESSIONSEARCHKEY_USER_ID), userID)
return delete(db) return delete(db)
} }

View File

@ -60,7 +60,7 @@ func PrepareSave(table string) func(db *gorm.DB, object interface{}) error {
} }
} }
func PrepareDeleteByKey(table string, key ColumnKey, id string) func(db *gorm.DB) error { func PrepareDeleteByKey(table string, key ColumnKey, id interface{}) func(db *gorm.DB) error {
return func(db *gorm.DB) error { return func(db *gorm.DB) error {
err := db.Table(table). err := db.Table(table).
Where(fmt.Sprintf("%s = ?", key.ToColumnName()), id). Where(fmt.Sprintf("%s = ?", key.ToColumnName()), id).

View File

@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE notification.notify_users ADD COLUMN password_set BOOLEAN;
COMMIT;

View File

@ -6,7 +6,10 @@ import (
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/pkg/op"
"github.com/caos/zitadel/internal/user/model" "github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/errors"
proj_model "github.com/caos/zitadel/internal/project/model"
user_model "github.com/caos/zitadel/internal/user/model"
) )
const ( const (
@ -15,6 +18,8 @@ const (
scopeEmail = "email" scopeEmail = "email"
scopePhone = "phone" scopePhone = "phone"
scopeAddress = "address" scopeAddress = "address"
oidcCtx = "oidc"
) )
func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (op.Client, error) { func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (op.Client, error) {
@ -22,10 +27,17 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (op.Clie
if err != nil { if err != nil {
return nil, err return nil, err
} }
if client.State != proj_model.APPSTATE_ACTIVE {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sdaGg", "client is not active")
}
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime) return ClientFromBusiness(client, o.defaultLoginURL, o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime)
} }
func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secret string) error { func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secret string) error {
ctx = auth.SetCtxData(ctx, auth.CtxData{
UserID: oidcCtx,
OrgID: oidcCtx,
})
return o.repo.AuthorizeOIDCApplication(ctx, id, secret) return o.repo.AuthorizeOIDCApplication(ctx, id, secret)
} }
@ -72,13 +84,13 @@ func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID string, sc
return userInfo, nil return userInfo, nil
} }
func getGender(gender model.Gender) string { func getGender(gender user_model.Gender) string {
switch gender { switch gender {
case model.GENDER_FEMALE: case user_model.GENDER_FEMALE:
return "female" return "female"
case model.GENDER_MALE: case user_model.GENDER_MALE:
return "male" return "male"
case model.GENDER_DIVERSE: case user_model.GENDER_DIVERSE:
return "diverse" return "diverse"
} }
return "" return ""