mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
fix: session termination after user deletion/deactivation (#11644)
# Which Problems Are Solved After a user is deactivated, their sessions are still valid. When the user is redirected to login page, they're stuck in a loop on a blank page on the `users/me` endpoint as the user's session is still valid. # How the Problems Are Solved By terminating a deactivated user's session. This is done by: - Adding a new reducer in session projection to delete sessions for `UserDeactivatedEvent`, `UserLockedEvent` and `UserRemovedEvent` events - Adding a new reducer in the relation session projection to delete sessions based on `UserDeactivatedEvent`, `UserLockedEvent` events # Additional Changes N/A # Additional Context - Closes https://github.com/zitadel/zitadel/issues/10307 - todo: add integration test cases for relational session reducers based on user deactivated, locked, deleted events
This commit is contained in:
@@ -473,6 +473,8 @@ func TestServer_SessionReduces(t *testing.T) {
|
||||
assert.Nil(collect, dbSession)
|
||||
}, retryDuration, tick, "session not found within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
// TODO (@grvijayan): add tests for session reducers based on user deactivated, locked, deleted events
|
||||
}
|
||||
|
||||
func assertSessionsEqual(t *assert.CollectT, expected, actual *domain.Session) {
|
||||
|
||||
@@ -169,6 +169,18 @@ func (p *sessionProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: user.HumanPasswordChangedType,
|
||||
Reduce: p.reducePasswordChanged,
|
||||
},
|
||||
{
|
||||
Event: user.UserDeactivatedType,
|
||||
Reduce: p.reduceUserStateNotActive,
|
||||
},
|
||||
{
|
||||
Event: user.UserLockedType,
|
||||
Reduce: p.reduceUserStateNotActive,
|
||||
},
|
||||
{
|
||||
Event: user.UserRemovedType,
|
||||
Reduce: p.reduceUserStateNotActive,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -465,3 +477,20 @@ func (p *sessionProjection) reducePasswordChanged(event eventstore.Event) (*hand
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *sessionProjection) reduceUserStateNotActive(event eventstore.Event) (_ *handler.Statement, err error) {
|
||||
switch t := event.(type) {
|
||||
case *user.UserDeactivatedEvent, *user.UserRemovedEvent, *user.UserLockedEvent:
|
||||
// ok
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-7zALpR", "reduce.wrong.event.type %v", t)
|
||||
}
|
||||
|
||||
return handler.NewDeleteStatement(
|
||||
event,
|
||||
[]handler.Condition{
|
||||
handler.NewCond(SessionColumnUserID, event.Aggregate().ID),
|
||||
handler.NewCond(SessionColumnInstanceID, event.Aggregate().InstanceID),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -117,6 +117,14 @@ func (p *sessionRelationalProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: user.HumanPasswordChangedType,
|
||||
Reduce: p.reducePasswordChanged,
|
||||
},
|
||||
{
|
||||
Event: user.UserDeactivatedType,
|
||||
Reduce: p.reduceUserStateNotActive,
|
||||
},
|
||||
{
|
||||
Event: user.UserLockedType,
|
||||
Reduce: p.reduceUserStateNotActive,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -573,3 +581,28 @@ func (p *sessionRelationalProjection) reducePasswordChanged(event eventstore.Eve
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *sessionRelationalProjection) reduceUserStateNotActive(event eventstore.Event) (*handler.Statement, error) {
|
||||
switch t := event.(type) {
|
||||
case *user.UserDeactivatedEvent, *user.UserLockedEvent:
|
||||
// ok
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-XBglbF", "reduce.wrong.event.type %v", t)
|
||||
}
|
||||
|
||||
return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-fcxV68", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
v3Tx := v3_sql.SQLTx(tx)
|
||||
|
||||
sessionRepo := repository.SessionRepository()
|
||||
condition := database.And(
|
||||
sessionRepo.InstanceIDCondition(event.Aggregate().InstanceID),
|
||||
sessionRepo.UserIDCondition(event.Aggregate().ID),
|
||||
)
|
||||
_, err := sessionRepo.Delete(ctx, v3Tx, condition)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
@@ -414,6 +414,84 @@ func TestSessionProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reduce UserDeactivated",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
user.UserDeactivatedType,
|
||||
user.AggregateType,
|
||||
[]byte(`{}`),
|
||||
), user.UserDeactivatedEventMapper),
|
||||
},
|
||||
reduce: (&sessionProjection{}).reduceUserStateNotActive,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("user"),
|
||||
sequence: 15,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.sessions8 WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []any{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reduce UserRemoved",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
user.UserRemovedType,
|
||||
user.AggregateType,
|
||||
[]byte(`{}`),
|
||||
), user.UserRemovedEventMapper),
|
||||
},
|
||||
reduce: (&sessionProjection{}).reduceUserStateNotActive,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("user"),
|
||||
sequence: 15,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.sessions8 WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []any{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reduce UserLocked",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
user.UserLockedType,
|
||||
user.AggregateType,
|
||||
[]byte(`{}`),
|
||||
), user.UserLockedEventMapper),
|
||||
},
|
||||
reduce: (&sessionProjection{}).reduceUserStateNotActive,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("user"),
|
||||
sequence: 15,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.sessions8 WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []any{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user