Compare commits

..

No commits in common. "236e9ed25a360b3e69576b32e685c9ad93d5f2e4" and "e19ad9960ebaf3e74f40453427c3a55aa7f55f57" have entirely different histories.

2 changed files with 6 additions and 47 deletions

View file

@ -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, 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(r.frozen, false), COALESCE(r.reason, ''), u.verified, u.scam, u.fake,
COALESCE(EXTRACT(EPOCH FROM u.premium_expires_at), 0)::bigint, 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(NULLIF(u.username, ''), p.username_lower, '') AS display_username,
COALESCE(ap.login_email, ''), COALESCE(ap.login_email, ''),
`+accountCollectibleUsernamesColumn+` AS collectibles `+accountCollectibleUsernamesColumn+` AS collectibles
FROM users u FROM users u
-- LEFT JOIN, not JOIN: an account with no authorizations (never finished login, JOIN auth ON auth.user_id = u.id
-- all sessions revoked, frozen-then-unfrozen) must still appear here, matching
-- CountAccounts and SearchAccounts.
LEFT JOIN auth ON auth.user_id = u.id
LEFT JOIN account_restrictions r ON r.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 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 LEFT JOIN account_passwords ap ON ap.user_id = u.id
WHERE NOT u.is_bot 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)) AND ($1::bigint = 0 OR (auth.last_active_at, 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 ORDER BY auth.last_active_at DESC, u.id DESC
LIMIT $3`, beforeActiveUS, beforeID, limit+1) LIMIT $3`, beforeActiveUS, beforeID, limit+1)
if err != nil { if err != nil {
return nil, false, fmt.Errorf("list accounts: %w", err) return nil, false, fmt.Errorf("list accounts: %w", err)

View file

@ -47,8 +47,8 @@ VALUES ($1, $2, $3, 'Collector', '', $4, now(), now())`,
userID, userID, "+1889"+suffix, editable); err != nil { userID, userID, "+1889"+suffix, editable); err != nil {
t.Fatalf("seed user: %v", err) t.Fatalf("seed user: %v", err)
} }
// Give this account a device so its device_count / last_active columns are // The list query joins authorizations, so an account with no device never
// exercised; an authorization needs its auth key to exist first. // appears there at all; an authorization in turn needs its auth key to exist.
if _, err := pool.Exec(ctx, ` if _, err := pool.Exec(ctx, `
INSERT INTO auth_keys (auth_key_id, body, server_salt) VALUES ($1, '\x00', 0)`, userID); err != nil { INSERT INTO auth_keys (auth_key_id, body, server_salt) VALUES ($1, '\x00', 0)`, userID); err != nil {
t.Fatalf("seed auth key: %v", err) 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) { func assertCollectibles(t *testing.T, surface string, row AccountRow, editable string, want []AccountUsername) {
t.Helper() t.Helper()
if row.Username != editable { if row.Username != editable {