| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- -- Durable, transactionally-enqueued API-key auth cache invalidation.
- -- cache_key is always SHA-256 hex; plaintext credentials never leave api_keys.
- CREATE TABLE IF NOT EXISTS auth_cache_invalidation_outbox (
- id BIGSERIAL PRIMARY KEY,
- cache_key CHAR(64) NOT NULL CHECK (cache_key ~ '^[0-9a-f]{64}$'),
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
- available_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
- delivery_stage SMALLINT NOT NULL DEFAULT 0 CHECK (delivery_stage IN (0, 1)),
- attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),
- last_error TEXT,
- claimed_at TIMESTAMPTZ,
- claimed_by TEXT
- );
- CREATE INDEX IF NOT EXISTS idx_auth_cache_invalidation_outbox_available
- ON auth_cache_invalidation_outbox (available_at, id)
- WHERE claimed_at IS NULL;
- CREATE INDEX IF NOT EXISTS idx_auth_cache_invalidation_outbox_lease
- ON auth_cache_invalidation_outbox (claimed_at)
- WHERE claimed_at IS NOT NULL;
- CREATE INDEX IF NOT EXISTS idx_auth_cache_invalidation_outbox_cache_key
- ON auth_cache_invalidation_outbox (cache_key);
- CREATE INDEX IF NOT EXISTS idx_auth_cache_invalidation_outbox_created_at
- ON auth_cache_invalidation_outbox (created_at);
- CREATE OR REPLACE FUNCTION enqueue_auth_cache_invalidation(raw_key TEXT)
- RETURNS VOID
- LANGUAGE plpgsql
- AS $$
- BEGIN
- IF raw_key IS NULL OR raw_key = '' THEN
- RETURN;
- END IF;
- INSERT INTO auth_cache_invalidation_outbox (cache_key)
- VALUES (encode(sha256(convert_to(raw_key, 'UTF8')), 'hex'));
- END;
- $$;
- CREATE OR REPLACE FUNCTION enqueue_api_key_auth_cache_invalidation()
- RETURNS TRIGGER
- LANGUAGE plpgsql
- AS $$
- BEGIN
- IF TG_OP = 'DELETE' THEN
- PERFORM enqueue_auth_cache_invalidation(OLD.key);
- RETURN OLD;
- END IF;
- IF OLD.key IS DISTINCT FROM NEW.key
- OR OLD.status IS DISTINCT FROM NEW.status
- OR OLD.deleted_at IS DISTINCT FROM NEW.deleted_at
- OR OLD.user_id IS DISTINCT FROM NEW.user_id
- OR OLD.group_id IS DISTINCT FROM NEW.group_id
- OR OLD.ip_whitelist IS DISTINCT FROM NEW.ip_whitelist
- OR OLD.ip_blacklist IS DISTINCT FROM NEW.ip_blacklist
- OR OLD.expires_at IS DISTINCT FROM NEW.expires_at THEN
- PERFORM enqueue_auth_cache_invalidation(OLD.key);
- IF NEW.deleted_at IS NULL AND NEW.key IS DISTINCT FROM OLD.key THEN
- PERFORM enqueue_auth_cache_invalidation(NEW.key);
- END IF;
- END IF;
- RETURN NEW;
- END;
- $$;
- DROP TRIGGER IF EXISTS trg_api_keys_auth_cache_invalidation ON api_keys;
- CREATE TRIGGER trg_api_keys_auth_cache_invalidation
- AFTER UPDATE OR DELETE ON api_keys
- FOR EACH ROW EXECUTE FUNCTION enqueue_api_key_auth_cache_invalidation();
- CREATE OR REPLACE FUNCTION enqueue_user_auth_cache_invalidation()
- RETURNS TRIGGER
- LANGUAGE plpgsql
- AS $$
- DECLARE
- target_user_id BIGINT;
- BEGIN
- target_user_id := OLD.id;
- IF TG_OP = 'UPDATE'
- AND OLD.status IS NOT DISTINCT FROM NEW.status
- AND OLD.role IS NOT DISTINCT FROM NEW.role
- AND OLD.deleted_at IS NOT DISTINCT FROM NEW.deleted_at THEN
- RETURN NEW;
- END IF;
- INSERT INTO auth_cache_invalidation_outbox (cache_key)
- SELECT encode(sha256(convert_to(k.key, 'UTF8')), 'hex')
- FROM api_keys AS k
- WHERE k.user_id = target_user_id
- AND k.deleted_at IS NULL
- AND k.key <> '';
- IF TG_OP = 'DELETE' THEN
- RETURN OLD;
- END IF;
- RETURN NEW;
- END;
- $$;
- DROP TRIGGER IF EXISTS trg_users_auth_cache_invalidation ON users;
- CREATE TRIGGER trg_users_auth_cache_invalidation
- AFTER UPDATE OR DELETE ON users
- FOR EACH ROW EXECUTE FUNCTION enqueue_user_auth_cache_invalidation();
- CREATE OR REPLACE FUNCTION enqueue_group_auth_cache_invalidation()
- RETURNS TRIGGER
- LANGUAGE plpgsql
- AS $$
- DECLARE
- target_group_id BIGINT;
- BEGIN
- target_group_id := OLD.id;
- IF TG_OP = 'UPDATE'
- AND OLD.status IS NOT DISTINCT FROM NEW.status
- AND OLD.is_exclusive IS NOT DISTINCT FROM NEW.is_exclusive
- AND OLD.deleted_at IS NOT DISTINCT FROM NEW.deleted_at THEN
- RETURN NEW;
- END IF;
- INSERT INTO auth_cache_invalidation_outbox (cache_key)
- SELECT encode(sha256(convert_to(k.key, 'UTF8')), 'hex')
- FROM api_keys AS k
- WHERE k.group_id = target_group_id
- AND k.deleted_at IS NULL
- AND k.key <> '';
- IF TG_OP = 'DELETE' THEN
- RETURN OLD;
- END IF;
- RETURN NEW;
- END;
- $$;
- DROP TRIGGER IF EXISTS trg_groups_auth_cache_invalidation ON groups;
- CREATE TRIGGER trg_groups_auth_cache_invalidation
- AFTER UPDATE OR DELETE ON groups
- FOR EACH ROW EXECUTE FUNCTION enqueue_group_auth_cache_invalidation();
- CREATE OR REPLACE FUNCTION enqueue_allowed_group_auth_cache_invalidation()
- RETURNS TRIGGER
- LANGUAGE plpgsql
- AS $$
- DECLARE
- target_user_id BIGINT;
- target_group_id BIGINT;
- BEGIN
- IF TG_OP = 'UPDATE'
- AND (OLD.user_id IS DISTINCT FROM NEW.user_id
- OR OLD.group_id IS DISTINCT FROM NEW.group_id) THEN
- IF EXISTS (
- SELECT 1 FROM groups g
- WHERE g.id = OLD.group_id AND g.is_exclusive = TRUE
- ) THEN
- INSERT INTO auth_cache_invalidation_outbox (cache_key)
- SELECT encode(sha256(convert_to(k.key, 'UTF8')), 'hex')
- FROM api_keys AS k
- WHERE k.user_id = OLD.user_id
- AND k.group_id = OLD.group_id
- AND k.deleted_at IS NULL
- AND k.key <> '';
- END IF;
- target_user_id := NEW.user_id;
- target_group_id := NEW.group_id;
- ELSIF TG_OP = 'UPDATE' THEN
- RETURN NEW;
- ELSIF TG_OP = 'INSERT' THEN
- target_user_id := NEW.user_id;
- target_group_id := NEW.group_id;
- ELSE
- target_user_id := OLD.user_id;
- target_group_id := OLD.group_id;
- END IF;
- IF EXISTS (
- SELECT 1 FROM groups g
- WHERE g.id = target_group_id AND g.is_exclusive = TRUE
- ) THEN
- INSERT INTO auth_cache_invalidation_outbox (cache_key)
- SELECT encode(sha256(convert_to(k.key, 'UTF8')), 'hex')
- FROM api_keys AS k
- WHERE k.user_id = target_user_id
- AND k.group_id = target_group_id
- AND k.deleted_at IS NULL
- AND k.key <> '';
- END IF;
- IF TG_OP = 'DELETE' THEN
- RETURN OLD;
- END IF;
- RETURN NEW;
- END;
- $$;
- DROP TRIGGER IF EXISTS trg_user_allowed_groups_auth_cache_invalidation ON user_allowed_groups;
- CREATE TRIGGER trg_user_allowed_groups_auth_cache_invalidation
- AFTER INSERT OR UPDATE OR DELETE ON user_allowed_groups
- FOR EACH ROW EXECUTE FUNCTION enqueue_allowed_group_auth_cache_invalidation();
- COMMENT ON TABLE auth_cache_invalidation_outbox IS
- 'Durable cross-instance auth cache invalidations; cache_key is SHA-256 hex, never plaintext API key';
|