Merge branch 'fix/admin-accounts-list-sessionless'
This commit is contained in:
commit
236e9ed25a
2 changed files with 47 additions and 6 deletions
|
|
@ -837,18 +837,21 @@ 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,
|
||||||
auth.last_active_at, auth.device_count,
|
COALESCE(auth.last_active_at, '0001-01-01 00:00:00+00'::timestamptz), COALESCE(auth.device_count, 0)::int,
|
||||||
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
|
||||||
JOIN auth ON auth.user_id = u.id
|
-- 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
|
||||||
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 (auth.last_active_at, u.id) < (to_timestamp(($1::double precision) / 1000000.0), $2::bigint))
|
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 auth.last_active_at DESC, u.id DESC
|
ORDER BY COALESCE(auth.last_active_at, '0001-01-01 00:00:00+00'::timestamptz) 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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
// The list query joins authorizations, so an account with no device never
|
// Give this account a device so its device_count / last_active columns are
|
||||||
// appears there at all; an authorization in turn needs its auth key to exist.
|
// exercised; an authorization needs its auth key to exist first.
|
||||||
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,6 +123,44 @@ 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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue