mirror of
https://github.com/block/buzz.git
synced 2026-10-28 21:11:48 +00:00
82 lines
4.0 KiB
PL/PgSQL
82 lines
4.0 KiB
PL/PgSQL
ALTER TABLE relay_invites ADD COLUMN revoked_at TIMESTAMPTZ;
|
|
ALTER TABLE relay_invites ADD COLUMN legacy_admission BOOLEAN NOT NULL DEFAULT true;
|
|
|
|
-- Serialize invitation admission with restriction and ownership changes per tenant.
|
|
CREATE FUNCTION invite_admission_lock(community UUID) RETURNS VOID
|
|
LANGUAGE sql AS $$
|
|
SELECT pg_advisory_xact_lock(hashtextextended('invite-admission:' || community::text, 0));
|
|
$$;
|
|
|
|
-- Live revocation is application-owned: supported restriction and owner writers
|
|
-- take invite_admission_lock before row locks and revoke in the same commit.
|
|
-- Rollout gate: drain all old writer pods before lifting bans or reopening
|
|
-- invitation admission, then rerun backfill_invite_revocations() under the
|
|
-- schema-destruction maintenance lock to repair bans committed during rollout.
|
|
-- Keep active bans authoritative for every invite. Legacy rows are additionally
|
|
-- revoked for any retained restriction row on their canonical issuer or current
|
|
-- owner, even when the restriction was lifted or the invite was minted later.
|
|
-- Do not infer enforcement from moderation or action audit rows. Scope every
|
|
-- issuer, owner, and restriction lookup to the invitation's community.
|
|
-- Schema migrations hold the exclusive schema-destruction lock. Retain the
|
|
-- universal tenant fence too: acquire its shared lock, then read the current
|
|
-- generation before applying this narrowly scoped historical security repair.
|
|
-- A quiescing tenant may return to active, so skipping it could revive bearers.
|
|
CREATE FUNCTION backfill_invite_revocations() RETURNS BIGINT
|
|
LANGUAGE plpgsql AS $$
|
|
DECLARE
|
|
target RECORD;
|
|
affected BIGINT;
|
|
total BIGINT := 0;
|
|
current_generation BIGINT;
|
|
prior_community TEXT := current_setting('buzz.deletion_executor_community', true);
|
|
prior_generation TEXT := current_setting('buzz.deletion_fence_generation', true);
|
|
BEGIN
|
|
FOR target IN SELECT id FROM communities
|
|
WHERE EXISTS (SELECT 1 FROM relay_invites WHERE community_id = communities.id)
|
|
ORDER BY id
|
|
LOOP
|
|
PERFORM pg_advisory_xact_lock_shared(community_deletion_lock_key(target.id));
|
|
SELECT deletion_fence_generation INTO STRICT current_generation
|
|
FROM communities WHERE id = target.id;
|
|
PERFORM set_config('buzz.deletion_executor_community', target.id::text, true);
|
|
PERFORM set_config('buzz.deletion_fence_generation', current_generation::text, true);
|
|
UPDATE relay_invites i SET revoked_at = clock_timestamp()
|
|
WHERE i.community_id = target.id AND i.revoked_at IS NULL
|
|
AND (
|
|
EXISTS (
|
|
SELECT 1 FROM community_bans b
|
|
WHERE b.community_id = i.community_id
|
|
AND (
|
|
b.pubkey = CASE WHEN octet_length(i.created_by) = 64
|
|
AND i.created_by !~ '[^0-9a-f]'
|
|
THEN decode(i.created_by, 'hex') END
|
|
OR b.pubkey = (
|
|
SELECT u.agent_owner_pubkey
|
|
FROM users u
|
|
WHERE u.community_id = i.community_id
|
|
AND octet_length(i.created_by) = 64
|
|
AND i.created_by !~ '[^0-9a-f]'
|
|
AND encode(u.pubkey, 'hex') = i.created_by
|
|
)
|
|
)
|
|
AND (
|
|
i.legacy_admission
|
|
OR (b.banned AND (b.ban_expires_at IS NULL
|
|
OR b.ban_expires_at > clock_timestamp()))
|
|
)
|
|
)
|
|
OR (i.legacy_admission AND (
|
|
octet_length(i.created_by) <> 64 OR i.created_by ~ '[^0-9a-f]'
|
|
))
|
|
);
|
|
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
total := total + affected;
|
|
PERFORM set_config('buzz.deletion_executor_community', COALESCE(prior_community, ''), true);
|
|
PERFORM set_config('buzz.deletion_fence_generation', COALESCE(prior_generation, ''), true);
|
|
END LOOP;
|
|
RETURN total;
|
|
END
|
|
$$;
|
|
|
|
SELECT backfill_invite_revocations();
|