Skip to content

feat(admin): block existing accounts when adding a blacklisted domain - #8596

Merged
pandemicsyn merged 2 commits into
mainfrom
feat/auto-block-blacklisted-domains
Oct 22, 2026
Merged

pandemicsyn merged 2 commits into
mainfrom
feat/auto-block-blacklisted-domains

Conversation

@pandemicsyn

@pandemicsyn pandemicsyn commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adding a domain to the blacklisted domains list now blocks existing accounts on that domain automatically. Previously the list only affected new sign-ups and per-request checks, and an admin had to open the Backfills page and click "Block next 50 000" one or more times to mark existing accounts as blocked.

  • admin.blacklistDomains.set diffs the submitted list against the list the admin started from and runs the existing backfill batch for only the newly added domains.
  • A backfill failure does not fail the save. The list is already enforced by auth checks, so the failure is reported to Sentry and the response tells the admin to run the manual backfill instead.
  • The mutation returns { blocked, remaining, failed }, and the edit tab reports the result, including a pointer to /admin/backfills when more work remains.
  • The manual backfill UI and API route are unchanged.

Implementation notes

  • The backfill batch and its domain condition moved from the Next.js route module into apps/web/src/lib/abuse/block-blacklisted-domains.ts so the tRPC router can call them without importing a route file. The route now imports the shared module and keeps the same GET/POST behaviour.
  • Domains that were already on the list are skipped. Those users were either blocked when the domain was added or are left to the existing backfill button, which still covers cases like users whose email_domain was filled in later.
  • Admin accounts are excluded from the domain backfill and from its remaining count, so an automatic block cannot lock an admin out of the tools needed to undo it.
  • The backfill still does not do the device-session/refresh-token/native-key revocation that blockUser does, and does not match list entries that are subdomains of the stored registrable email_domain.
  • Domain lists are bound as array parameters (= ANY / LIKE ANY), so an imported list of tens of thousands of entries no longer risks PostgreSQL's bind-parameter limit.

Verification

  • NODE_ENV=test npx jest src/lib/abuse/block-blacklisted-domains.test.ts src/routers/admin/blacklist-domains-router.test.ts — 2 suites, 36 tests passed (includes new tests for auto-blocking, skipping already-listed domains, and keeping the save when the backfill fails).
  • pnpm --filter web run typecheck — passed (tsgo exit 0).
  • oxlint and oxfmt on the changed files — clean.

Saving the blacklisted domains list left existing accounts untouched until
an admin remembered to open the Backfills page and click "Block next 50 000"
one or more times.

Run the existing backfill batch from the save mutation for domains that were
newly added to the list. Domains that were already listed are skipped, since
they were either backfilled when they were added or are handled by the manual
backfill button.

The list write is authoritative and already enforced by auth checks, so a
backfill failure is reported to Sentry without failing the save. The mutation
now returns the number of accounts blocked, whether more remain, and whether
the automatic run failed, and the edit tab surfaces that result.

The backfill batch moves to lib/abuse so both the route and the router can
use it without importing a Next.js route module.
@kilo-code-bot

kilo-code-bot Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental hardening commit resolves all prior findings: admin accounts are excluded from both the domain backfill and the unblocked count, an unreadable previous config now fails safe instead of triggering a full-list backfill, and domain lists are bound as two array parameters so large imported lists no longer approach PostgreSQL's bind-parameter limit.

Assumptions: [INFERENCE] The sql.param(...)::text[] array binding follows the existing pattern in apps/web/src/lib/spend-alerts/sweep.ts:654; tests and typecheck were not executed in this read-only review.

Files Reviewed (6 files)
  • apps/web/src/app/admin/api/backfills/block-blacklisted-domains/route.ts
  • apps/web/src/app/admin/components/BlacklistedDomains.tsx
  • apps/web/src/lib/abuse/block-blacklisted-domains.test.ts
  • apps/web/src/lib/abuse/block-blacklisted-domains.ts
  • apps/web/src/routers/admin/blacklist-domains-router.test.ts
  • apps/web/src/routers/admin/blacklist-domains-router.ts
Previous Review Summary (commit 5cdd942)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 5cdd942)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/routers/admin/blacklist-domains-router.ts 78 Automatic backfill has no admin exemption, so saving a domain can permanently block the acting admin (blocked users fail admin auth and cannot unblock themselves).

SUGGESTION

File Line Issue
apps/web/src/routers/admin/blacklist-domains-router.ts 54 readConfig() swallows Redis read errors and returns an empty list, so a transient failure turns a one-domain save into a full-list backfill.
apps/web/src/routers/admin/blacklist-domains-router.ts 76 Unbounded addedDomains can exceed PostgreSQL's 65,535-parameter statement limit on bulk imports (~2 binds per domain); the suggested manual backfill fails the same way.
Files Reviewed (7 files)
  • apps/web/src/app/admin/api/backfills/block-blacklisted-domains/route.ts
  • apps/web/src/app/admin/components/BlacklistedDomains.tsx
  • apps/web/src/app/admin/components/BlockBlacklistedDomainsBackfill.tsx
  • apps/web/src/lib/abuse/block-blacklisted-domains.test.ts
  • apps/web/src/lib/abuse/block-blacklisted-domains.ts
  • apps/web/src/routers/admin/blacklist-domains-router.test.ts
  • apps/web/src/routers/admin/blacklist-domains-router.ts - 3 issues

Reviewed by deepseek-flash-latest · Input: 90.4K · Output: 10K · Cached: 481.9K

Review guidance: REVIEW.md from base branch main

Skip admin accounts in the domain backfill and its remaining count, so
saving a domain cannot lock out an admin who then cannot reach the admin
tools needed to unblock the account.

Treat a failed previous-config read as unknown instead of an empty list,
so a transient Redis error cannot turn a one-domain save into a backfill
of every listed domain.

Bind the domain lists as array parameters instead of one bind per entry,
so a bulk import does not exceed PostgreSQL's bind-parameter limit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants