153 lines
4.8 KiB
PL/PgSQL
153 lines
4.8 KiB
PL/PgSQL
BEGIN;
|
|
|
|
DROP POLICY IF EXISTS "devices_delete_own" ON public.devices;
|
|
|
|
CREATE OR REPLACE FUNCTION public.protect_device_security_fields()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SET search_path = ''
|
|
AS $$
|
|
BEGIN
|
|
IF current_user NOT IN ('postgres', 'service_role', 'supabase_admin') AND (
|
|
NEW.user_id IS DISTINCT FROM OLD.user_id
|
|
OR NEW.installation_id IS DISTINCT FROM OLD.installation_id
|
|
OR NEW.platform IS DISTINCT FROM OLD.platform
|
|
OR NEW.revoked_at IS DISTINCT FROM OLD.revoked_at
|
|
OR NEW.push_token IS DISTINCT FROM OLD.push_token
|
|
) THEN
|
|
RAISE EXCEPTION 'device_security_fields_are_server_managed' USING ERRCODE = '42501';
|
|
END IF;
|
|
IF OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NULL THEN
|
|
RAISE EXCEPTION 'device_revocation_is_permanent' USING ERRCODE = '42501';
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS reject_direct_device_push_token_update ON public.devices;
|
|
DROP FUNCTION IF EXISTS public.reject_direct_device_push_token();
|
|
CREATE TRIGGER protect_device_security_fields_update
|
|
BEFORE UPDATE ON public.devices
|
|
FOR EACH ROW EXECUTE FUNCTION public.protect_device_security_fields();
|
|
|
|
CREATE OR REPLACE FUNCTION public.revoke_device(target_device_id uuid)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
current_user_id uuid := auth.uid();
|
|
target_device public.devices;
|
|
BEGIN
|
|
IF current_user_id IS NULL THEN
|
|
RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501';
|
|
END IF;
|
|
IF target_device_id IS NULL THEN
|
|
RAISE EXCEPTION 'invalid_device' USING ERRCODE = '22023';
|
|
END IF;
|
|
|
|
PERFORM pg_advisory_xact_lock(hashtextextended(target_device_id::text, 73052));
|
|
SELECT * INTO target_device
|
|
FROM public.devices
|
|
WHERE id = target_device_id AND user_id = current_user_id
|
|
FOR UPDATE;
|
|
IF target_device.id IS NULL THEN
|
|
RAISE EXCEPTION 'device_not_found' USING ERRCODE = 'P0002';
|
|
END IF;
|
|
|
|
IF target_device.revoked_at IS NULL THEN
|
|
UPDATE public.devices
|
|
SET revoked_at = now(), push_token = NULL
|
|
WHERE id = target_device.id
|
|
RETURNING * INTO target_device;
|
|
END IF;
|
|
DELETE FROM public.push_tokens
|
|
WHERE device_id = target_device.id AND user_id = current_user_id;
|
|
|
|
RETURN to_jsonb(target_device);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION public.unregister_current_device(
|
|
current_installation_id uuid
|
|
)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
current_user_id uuid := auth.uid();
|
|
target_device_id uuid;
|
|
BEGIN
|
|
IF current_user_id IS NULL THEN
|
|
RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501';
|
|
END IF;
|
|
IF current_installation_id IS NULL THEN
|
|
RAISE EXCEPTION 'invalid_device' USING ERRCODE = '22023';
|
|
END IF;
|
|
|
|
SELECT id INTO target_device_id
|
|
FROM public.devices
|
|
WHERE user_id = current_user_id
|
|
AND installation_id = current_installation_id
|
|
AND revoked_at IS NULL
|
|
FOR UPDATE;
|
|
IF target_device_id IS NULL THEN
|
|
RETURN jsonb_build_object('removed', false);
|
|
END IF;
|
|
|
|
DELETE FROM public.push_tokens
|
|
WHERE device_id = target_device_id AND user_id = current_user_id;
|
|
DELETE FROM public.devices
|
|
WHERE id = target_device_id AND user_id = current_user_id AND revoked_at IS NULL;
|
|
RETURN jsonb_build_object('removed', true, 'device_id', target_device_id);
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION public.purge_revoked_device(
|
|
target_device_id uuid
|
|
)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
current_user_id uuid := auth.uid();
|
|
jwt_issued_at timestamptz;
|
|
removed_count integer;
|
|
BEGIN
|
|
IF current_user_id IS NULL THEN
|
|
RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501';
|
|
END IF;
|
|
BEGIN
|
|
jwt_issued_at := to_timestamp((auth.jwt()->>'iat')::double precision);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RAISE EXCEPTION 'recent_authentication_required' USING ERRCODE = '42501';
|
|
END;
|
|
IF jwt_issued_at IS NULL OR jwt_issued_at < now() - interval '10 minutes' THEN
|
|
RAISE EXCEPTION 'recent_authentication_required' USING ERRCODE = '42501';
|
|
END IF;
|
|
|
|
DELETE FROM public.devices
|
|
WHERE id = target_device_id
|
|
AND user_id = current_user_id
|
|
AND revoked_at IS NOT NULL;
|
|
GET DIAGNOSTICS removed_count = ROW_COUNT;
|
|
IF removed_count = 0 THEN
|
|
RAISE EXCEPTION 'revoked_device_not_found' USING ERRCODE = 'P0002';
|
|
END IF;
|
|
RETURN jsonb_build_object('removed', true, 'device_id', target_device_id);
|
|
END;
|
|
$$;
|
|
|
|
REVOKE ALL ON FUNCTION public.revoke_device(uuid) FROM PUBLIC, anon;
|
|
REVOKE ALL ON FUNCTION public.unregister_current_device(uuid) FROM PUBLIC, anon;
|
|
REVOKE ALL ON FUNCTION public.purge_revoked_device(uuid) FROM PUBLIC, anon;
|
|
GRANT EXECUTE ON FUNCTION public.revoke_device(uuid) TO authenticated;
|
|
GRANT EXECUTE ON FUNCTION public.unregister_current_device(uuid) TO authenticated;
|
|
GRANT EXECUTE ON FUNCTION public.purge_revoked_device(uuid) TO authenticated;
|
|
|
|
COMMIT;
|