diff --git a/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.down.sql b/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.down.sql new file mode 100644 index 000000000000..b8e5be3ef56c --- /dev/null +++ b/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.down.sql @@ -0,0 +1,4 @@ +CREATE INDEX sessions_identity_id_nid_idx + ON sessions (identity_id, nid); + +DROP INDEX sessions_identity_id_nid_sorted_idx; diff --git a/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.mysql.down.sql b/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.mysql.down.sql new file mode 100644 index 000000000000..cfed7ce459f4 --- /dev/null +++ b/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.mysql.down.sql @@ -0,0 +1,5 @@ +CREATE INDEX sessions_identity_id_nid_idx + ON sessions (identity_id, nid); + +DROP INDEX sessions_identity_id_nid_sorted_idx + ON sessions; diff --git a/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.mysql.up.sql b/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.mysql.up.sql new file mode 100644 index 000000000000..a12ddfe87f32 --- /dev/null +++ b/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.mysql.up.sql @@ -0,0 +1,5 @@ +CREATE INDEX sessions_identity_id_nid_sorted_idx + ON sessions (identity_id, nid, authenticated_at); + +DROP INDEX sessions_identity_id_nid_idx + ON sessions; diff --git a/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.up.sql b/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.up.sql new file mode 100644 index 000000000000..1c4406cb049e --- /dev/null +++ b/persistence/sql/migrations/sql/20230619000000000001_sessions_add_sorted_indices.up.sql @@ -0,0 +1,4 @@ +CREATE INDEX sessions_identity_id_nid_sorted_idx + ON sessions (identity_id, nid, authenticated_at); + +DROP INDEX sessions_identity_id_nid_idx; diff --git a/persistence/sql/persister_session.go b/persistence/sql/persister_session.go index 080a807cc9f9..8a49c8810bba 100644 --- a/persistence/sql/persister_session.go +++ b/persistence/sql/persister_session.go @@ -118,7 +118,14 @@ func (p *Persister) ListSessions(ctx context.Context, active *bool, paginatorOpt } // ListSessionsByIdentity retrieves sessions for an identity from the store. -func (p *Persister) ListSessionsByIdentity(ctx context.Context, iID uuid.UUID, active *bool, page, perPage int, except uuid.UUID, expandables session.Expandables) (_ []session.Session, _ int64, err error) { +func (p *Persister) ListSessionsByIdentity( + ctx context.Context, + iID uuid.UUID, + active *bool, + page, perPage int, + except uuid.UUID, + expandables session.Expandables, +) (_ []session.Session, _ int64, err error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListSessionsByIdentity") defer otelx.End(span, &err) @@ -150,6 +157,8 @@ func (p *Persister) ListSessionsByIdentity(ctx context.Context, iID uuid.UUID, a } t = int64(total) + q.Order("authenticated_at DESC") + // Get the paginated list of matching items if err := q.Paginate(page, perPage).All(&s); err != nil { return sqlcon.HandleError(err) diff --git a/session/handler_test.go b/session/handler_test.go index 1b0b7c618bad..0cf4df728bfb 100644 --- a/session/handler_test.go +++ b/session/handler_test.go @@ -10,13 +10,13 @@ import ( "io" "net/http" "net/http/httptest" + "sort" "strconv" "strings" "testing" "time" "github.com/bxcodec/faker/v3" - "github.com/tidwall/gjson" "github.com/ory/kratos/identity" @@ -749,6 +749,7 @@ func TestHandlerAdminSessionManagement(t *testing.T) { t.Run(fmt.Sprintf("active=%#v", tc.activeOnly), func(t *testing.T) { sessions, _, _ := reg.SessionPersister().ListSessionsByIdentity(ctx, i.ID, nil, 1, 10, uuid.Nil, ExpandEverything) require.Equal(t, 5, len(sessions)) + assert.True(t, sort.IsSorted(sort.Reverse(byAuthenticatedAt(sessions)))) reqURL := ts.URL + "/admin/identities/" + i.ID.String() + "/sessions" if tc.activeOnly != "" { @@ -1022,3 +1023,11 @@ func TestHandlerRefreshSessionBySessionID(t *testing.T) { assert.NotEqual(t, gjson.GetBytes(body, "error.id").String(), "security_csrf_violation") }) } + +type byAuthenticatedAt []Session + +func (s byAuthenticatedAt) Len() int { return len(s) } +func (s byAuthenticatedAt) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byAuthenticatedAt) Less(i, j int) bool { + return s[i].AuthenticatedAt.Before(s[j].AuthenticatedAt) +}