diff --git a/cmd/telesrv-admin/readstore.go b/cmd/telesrv-admin/readstore.go index 4c78b3b6..df718903 100644 --- a/cmd/telesrv-admin/readstore.go +++ b/cmd/telesrv-admin/readstore.go @@ -837,21 +837,18 @@ WITH auth AS ( SELECT u.id, u.phone, u.username, u.first_name, u.last_name, u.created_at, u.updated_at, COALESCE(r.frozen, false), COALESCE(r.reason, ''), u.verified, u.scam, u.fake, COALESCE(EXTRACT(EPOCH FROM u.premium_expires_at), 0)::bigint, - COALESCE(auth.last_active_at, '0001-01-01 00:00:00+00'::timestamptz), COALESCE(auth.device_count, 0)::int, + auth.last_active_at, auth.device_count, COALESCE(NULLIF(u.username, ''), p.username_lower, '') AS display_username, COALESCE(ap.login_email, ''), `+accountCollectibleUsernamesColumn+` AS collectibles FROM users u --- LEFT JOIN, not JOIN: an account with no authorizations (never finished login, --- all sessions revoked, frozen-then-unfrozen) must still appear here, matching --- CountAccounts and SearchAccounts. -LEFT JOIN auth ON auth.user_id = u.id +JOIN auth ON auth.user_id = u.id LEFT JOIN account_restrictions r ON r.user_id = u.id LEFT JOIN peer_usernames p ON p.peer_type = 'user' AND p.peer_id = u.id AND p.editable LEFT JOIN account_passwords ap ON ap.user_id = u.id WHERE NOT u.is_bot - AND ($1::bigint = 0 OR (COALESCE(auth.last_active_at, '0001-01-01 00:00:00+00'::timestamptz), u.id) < (to_timestamp(($1::double precision) / 1000000.0), $2::bigint)) -ORDER BY COALESCE(auth.last_active_at, '0001-01-01 00:00:00+00'::timestamptz) DESC, u.id DESC + AND ($1::bigint = 0 OR (auth.last_active_at, u.id) < (to_timestamp(($1::double precision) / 1000000.0), $2::bigint)) +ORDER BY auth.last_active_at DESC, u.id DESC LIMIT $3`, beforeActiveUS, beforeID, limit+1) if err != nil { return nil, false, fmt.Errorf("list accounts: %w", err) diff --git a/cmd/telesrv-admin/readstore_accounts_integration_test.go b/cmd/telesrv-admin/readstore_accounts_integration_test.go index 94f65e27..59db4ee6 100644 --- a/cmd/telesrv-admin/readstore_accounts_integration_test.go +++ b/cmd/telesrv-admin/readstore_accounts_integration_test.go @@ -47,8 +47,8 @@ VALUES ($1, $2, $3, 'Collector', '', $4, now(), now())`, userID, userID, "+1889"+suffix, editable); err != nil { t.Fatalf("seed user: %v", err) } - // Give this account a device so its device_count / last_active columns are - // exercised; an authorization needs its auth key to exist first. + // The list query joins authorizations, so an account with no device never + // appears there at all; an authorization in turn needs its auth key to exist. if _, err := pool.Exec(ctx, ` INSERT INTO auth_keys (auth_key_id, body, server_salt) VALUES ($1, '\x00', 0)`, userID); err != nil { t.Fatalf("seed auth key: %v", err) @@ -123,44 +123,6 @@ WHERE peer_type='user' AND peer_id=$1 AND collectible_id IS NOT NULL`, userID); } } -// An account with no authorizations (never finished login, all sessions revoked, -// frozen-then-unfrozen) must still show up in the Accounts tab - it did not, -// because ListAccounts inner-joined the authorizations aggregate. -func TestReadStoreListAccountsIncludesAccountsWithoutSessions(t *testing.T) { - store, pool := verificationReadStore(t) - ctx := context.Background() - suffix := fmt.Sprintf("%d", time.Now().UnixNano()%1_000_000) - userID := 3_700_000_000 + time.Now().UnixNano()%1_000_000 - - t.Cleanup(func() { - _, _ = pool.Exec(ctx, `DELETE FROM users WHERE id=$1`, userID) - }) - if _, err := pool.Exec(ctx, ` -INSERT INTO users (id, access_hash, phone, first_name, last_name, username, created_at, updated_at) -VALUES ($1, $2, $3, 'Sessionless', '', '', now(), now())`, - userID, userID, "+42777"+suffix); err != nil { - t.Fatalf("seed user: %v", err) - } - // Deliberately no auth_keys / authorizations rows. - - rows, _, err := store.ListAccounts(ctx, 0, 0, 500) - if err != nil { - t.Fatalf("ListAccounts: %v", err) - } - found := false - for i := range rows { - if rows[i].ID == userID { - found = true - if rows[i].DeviceCount != 0 { - t.Fatalf("device count = %d, want 0 for a sessionless account", rows[i].DeviceCount) - } - } - } - if !found { - t.Fatalf("sessionless account %d absent from ListAccounts (%d rows)", userID, len(rows)) - } -} - func assertCollectibles(t *testing.T, surface string, row AccountRow, editable string, want []AccountUsername) { t.Helper() if row.Username != editable {