This commit is contained in:
hailin 2025-05-26 16:50:39 +08:00
parent f055fe609c
commit 30e1cde3b2
1 changed files with 157 additions and 117 deletions

View File

@ -1,183 +1,223 @@
DO $$ DO $$
BEGIN BEGIN
IF NOT EXISTS(SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'storage') THEN IF NOT EXISTS (
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'storage'
) THEN
CREATE SCHEMA storage; CREATE SCHEMA storage;
END IF; END IF;
END$$; END$$;
DO $$ DO $$
DECLARE DECLARE
install_roles text = COALESCE(current_setting('storage.install_roles', true), 'true'); install_roles text := COALESCE(current_setting('storage.install_roles', true), 'true');
anon_role text = COALESCE(current_setting('storage.anon_role', true), 'anon'); anon_role text := COALESCE(current_setting('storage.anon_role', true), 'anon');
authenticated_role text = COALESCE(current_setting('storage.authenticated_role', true), 'authenticated'); authenticated_role text := COALESCE(current_setting('storage.authenticated_role', true), 'authenticated');
service_role text = COALESCE(current_setting('storage.service_role', true), 'service_role'); service_role text := COALESCE(current_setting('storage.service_role', true), 'service_role');
BEGIN BEGIN
IF install_roles != 'true' THEN IF install_roles != 'true' THEN RETURN; END IF;
RETURN;
-- 角色存在判断后再建
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = anon_role) THEN
EXECUTE 'CREATE ROLE ' || quote_ident(anon_role) || ' NOLOGIN NOINHERIT';
END IF; END IF;
-- Install ROLES IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = authenticated_role) THEN
EXECUTE 'CREATE ROLE IF NOT EXISTS ' || anon_role || ' NOLOGIN NOINHERIT'; EXECUTE 'CREATE ROLE ' || quote_ident(authenticated_role) || ' NOLOGIN NOINHERIT';
EXECUTE 'CREATE ROLE IF NOT EXISTS ' || authenticated_role || ' NOLOGIN NOINHERIT'; END IF;
EXECUTE 'CREATE ROLE IF NOT EXISTS ' || service_role || ' NOLOGIN NOINHERIT bypassrls';
-- create user authenticator noinherit; IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = service_role) THEN
IF NOT EXISTS ( EXECUTE 'CREATE ROLE ' || quote_ident(service_role) || ' NOLOGIN NOINHERIT BYPASSRLS';
SELECT 1 END IF;
FROM pg_roles
WHERE rolname = 'authenticator'
) THEN
EXECUTE 'create user authenticator noinherit;';
END IF;
EXECUTE 'grant ' || anon_role || ' to authenticator'; IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticator') THEN
EXECUTE 'grant ' || authenticated_role || ' to authenticator'; CREATE USER authenticator NOINHERIT;
EXECUTE 'grant ' || service_role || ' to authenticator'; END IF;
grant postgres to authenticator;
EXECUTE 'grant usage on schema storage to postgres,' || anon_role || ',' || authenticated_role || ',' || service_role; -- 授权角色
EXECUTE 'GRANT ' || quote_ident(anon_role) || ' TO authenticator';
EXECUTE 'GRANT ' || quote_ident(authenticated_role) || ' TO authenticator';
EXECUTE 'GRANT ' || quote_ident(service_role) || ' TO authenticator';
EXECUTE 'alter default privileges in schema storage grant all on tables to postgres,' || anon_role || ',' || authenticated_role || ',' || service_role; -- 不再尝试 GRANT postgres TO authenticator禁止行为
EXECUTE 'alter default privileges in schema storage grant all on functions to postgres,' || anon_role || ',' || authenticated_role || ',' || service_role;
EXECUTE 'alter default privileges in schema storage grant all on sequences to postgres,' || anon_role || ',' || authenticated_role || ',' || service_role; -- schema usage 与默认权限
EXECUTE 'GRANT USAGE ON SCHEMA storage TO ' ||
quote_ident(anon_role) || ',' ||
quote_ident(authenticated_role) || ',' ||
quote_ident(service_role);
EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA storage GRANT ALL ON TABLES TO ' ||
quote_ident(anon_role) || ',' || quote_ident(authenticated_role) || ',' || quote_ident(service_role);
EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA storage GRANT ALL ON FUNCTIONS TO ' ||
quote_ident(anon_role) || ',' || quote_ident(authenticated_role) || ',' || quote_ident(service_role);
EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA storage GRANT ALL ON SEQUENCES TO ' ||
quote_ident(anon_role) || ',' || quote_ident(authenticated_role) || ',' || quote_ident(service_role);
END$$; END$$;
CREATE TABLE IF NOT EXISTS storage.migrations (
CREATE TABLE IF NOT EXISTS "storage"."migrations" (
id integer PRIMARY KEY, id integer PRIMARY KEY,
name varchar(100) UNIQUE NOT NULL, name varchar(100) UNIQUE NOT NULL,
hash varchar(40) NOT NULL, -- sha1 hex encoded hash of the file name and contents, to ensure it hasn't been altered since applying the migration hash varchar(40) NOT NULL,
executed_at timestamp DEFAULT current_timestamp executed_at timestamp DEFAULT current_timestamp
); );
CREATE TABLE IF NOT EXISTS "storage"."buckets" ( CREATE TABLE IF NOT EXISTS storage.buckets (
"id" text not NULL, id text NOT NULL,
"name" text NOT NULL, name text NOT NULL,
"owner" uuid, owner uuid,
"created_at" timestamptz DEFAULT now(), created_at timestamptz DEFAULT now(),
"updated_at" timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
PRIMARY KEY ("id") PRIMARY KEY (id)
); );
CREATE UNIQUE INDEX IF NOT EXISTS "bname" ON "storage"."buckets" USING BTREE ("name"); CREATE UNIQUE INDEX IF NOT EXISTS bname ON storage.buckets (name);
CREATE TABLE IF NOT EXISTS "storage"."objects" ( CREATE TABLE IF NOT EXISTS storage.objects (
"id" uuid NOT NULL DEFAULT gen_random_uuid(), id uuid NOT NULL DEFAULT gen_random_uuid(),
"bucket_id" text, bucket_id text,
"name" text, name text,
"owner" uuid, owner uuid,
"created_at" timestamptz DEFAULT now(), created_at timestamptz DEFAULT now(),
"updated_at" timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
"last_accessed_at" timestamptz DEFAULT now(), last_accessed_at timestamptz DEFAULT now(),
"metadata" jsonb, metadata jsonb,
CONSTRAINT "objects_bucketId_fkey" FOREIGN KEY ("bucket_id") REFERENCES "storage"."buckets"("id"), CONSTRAINT objects_bucketId_fkey FOREIGN KEY (bucket_id) REFERENCES storage.buckets(id),
PRIMARY KEY ("id") PRIMARY KEY (id)
); );
CREATE UNIQUE INDEX IF NOT EXISTS "bucketid_objname" ON "storage"."objects" USING BTREE ("bucket_id","name"); CREATE UNIQUE INDEX IF NOT EXISTS bucketid_objname ON storage.objects (bucket_id, name);
CREATE INDEX IF NOT EXISTS name_prefix_search ON storage.objects(name text_pattern_ops); CREATE INDEX IF NOT EXISTS name_prefix_search ON storage.objects (name text_pattern_ops);
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY; ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
drop function if exists storage.foldername; -- 安全函数定义
CREATE OR REPLACE FUNCTION storage.foldername(name text) CREATE OR REPLACE FUNCTION storage.foldername(name text)
RETURNS text[] RETURNS text[]
LANGUAGE plpgsql LANGUAGE plpgsql
AS $function$ AS $$
DECLARE DECLARE _parts text[];
_parts text[];
BEGIN BEGIN
select string_to_array(name, '/') into _parts; SELECT string_to_array(name, '/') INTO _parts;
return _parts[1:array_length(_parts,1)-1]; RETURN _parts[1:array_length(_parts,1)-1];
END END
$function$; $$;
drop function if exists storage.filename;
CREATE OR REPLACE FUNCTION storage.filename(name text) CREATE OR REPLACE FUNCTION storage.filename(name text)
RETURNS text RETURNS text
LANGUAGE plpgsql LANGUAGE plpgsql
AS $function$ AS $$
DECLARE DECLARE _parts text[];
_parts text[];
BEGIN BEGIN
select string_to_array(name, '/') into _parts; SELECT string_to_array(name, '/') INTO _parts;
return _parts[array_length(_parts,1)]; RETURN _parts[array_length(_parts,1)];
END END
$function$; $$;
drop function if exists storage.extension;
CREATE OR REPLACE FUNCTION storage.extension(name text) CREATE OR REPLACE FUNCTION storage.extension(name text)
RETURNS text RETURNS text
LANGUAGE plpgsql LANGUAGE plpgsql
AS $function$ AS $$
DECLARE DECLARE _parts text[]; _filename text;
_parts text[];
_filename text;
BEGIN BEGIN
select string_to_array(name, '/') into _parts; SELECT string_to_array(name, '/') INTO _parts;
select _parts[array_length(_parts,1)] into _filename; SELECT _parts[array_length(_parts,1)] INTO _filename;
-- @todo return the last part instead of 2 RETURN reverse(split_part(reverse(_filename), '.', 1));
return reverse(split_part(reverse(_filename), '.', 1));
END END
$function$; $$;
-- @todo can this query be optimised further? CREATE OR REPLACE FUNCTION storage.search(
drop function if exists storage.search; prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0
CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0) )
RETURNS TABLE ( RETURNS TABLE (
name text, name text,
id uuid, id uuid,
updated_at TIMESTAMPTZ, updated_at timestamptz,
created_at TIMESTAMPTZ, created_at timestamptz,
last_accessed_at TIMESTAMPTZ, last_accessed_at timestamptz,
metadata jsonb metadata jsonb
) )
LANGUAGE plpgsql LANGUAGE plpgsql
AS $function$ AS $$
BEGIN BEGIN
return query RETURN QUERY
with files_folders as ( WITH files_folders AS (
select ((string_to_array(objects.name, '/'))[levels]) as folder SELECT (string_to_array(objects.name, '/'))[levels] AS folder
from objects FROM storage.objects
where objects.name ilike prefix || '%' WHERE objects.name ILIKE prefix || '%'
and bucket_id = bucketname AND bucket_id = bucketname
GROUP by folder GROUP BY folder
limit limits LIMIT limits OFFSET offsets
offset offsets )
) SELECT
select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders files_folders.folder AS name,
left join objects objects.id,
on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname; objects.updated_at,
objects.created_at,
objects.last_accessed_at,
objects.metadata
FROM files_folders
LEFT JOIN storage.objects
ON prefix || files_folders.folder = objects.name
AND objects.bucket_id = bucketname;
END END
$function$; $$;
DO $$ DO $$
DECLARE DECLARE
install_roles text = COALESCE(current_setting('storage.install_roles', true), 'true'); install_roles text := COALESCE(current_setting('storage.install_roles', true), 'true');
super_user text = COALESCE(current_setting('storage.super_user', true), 'supabase_storage_admin'); super_user text := COALESCE(current_setting('storage.super_user', true), 'supabase_storage_admin');
BEGIN BEGIN
IF install_roles != 'true' THEN IF install_roles != 'true' THEN RETURN; END IF;
RETURN;
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = super_user) THEN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = super_user) THEN
EXECUTE 'CREATE USER ' || super_user || ' NOINHERIT CREATEROLE LOGIN NOREPLICATION'; EXECUTE 'CREATE ROLE ' || quote_ident(super_user) || ' NOINHERIT CREATEROLE LOGIN NOREPLICATION';
END IF; END IF;
-- Grant privileges to Super User EXECUTE 'GRANT ALL PRIVILEGES ON SCHEMA storage TO ' || quote_ident(super_user);
EXECUTE 'GRANT ALL PRIVILEGES ON SCHEMA storage TO ' || super_user; EXECUTE 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO ' || quote_ident(super_user);
EXECUTE 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA storage TO ' || super_user; EXECUTE 'GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO ' || quote_ident(super_user);
EXECUTE 'GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA storage TO ' || super_user;
IF super_user != 'postgres' THEN IF super_user != 'postgres' THEN
EXECUTE 'ALTER USER ' || super_user || ' SET search_path = "storage"'; EXECUTE 'ALTER ROLE ' || quote_ident(super_user) || ' SET search_path = "storage"';
END IF; END IF;
EXECUTE 'ALTER table "storage".objects owner to ' || super_user; EXECUTE 'ALTER TABLE storage.objects OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER table "storage".buckets owner to ' || super_user; EXECUTE 'ALTER TABLE storage.buckets OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER table "storage".migrations OWNER TO ' || super_user; EXECUTE 'ALTER TABLE storage.migrations OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER function "storage".foldername(text) owner to ' || super_user;
EXECUTE 'ALTER function "storage".filename(text) owner to ' || super_user; EXECUTE 'ALTER FUNCTION storage.foldername(text) OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER function "storage".extension(text) owner to ' || super_user; EXECUTE 'ALTER FUNCTION storage.filename(text) OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER function "storage".search(text,text,int,int,int) owner to ' || super_user; EXECUTE 'ALTER FUNCTION storage.extension(text) OWNER TO ' || quote_ident(super_user);
EXECUTE 'ALTER FUNCTION storage.search(text,text,int,int,int) OWNER TO ' || quote_ident(super_user);
END$$;
-- ✅ 单独插入 bucket允许重复执行
INSERT INTO storage.buckets (id, name, owner)
VALUES ('profile_images', 'profile_images', NULL)
ON CONFLICT (id) DO NOTHING;
-- ✅ 单独定义权限策略(重复执行不会报错)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_policies WHERE policyname = 'anon upload profile_images'
) THEN
EXECUTE $$CREATE POLICY "anon upload profile_images"
ON storage.objects
FOR INSERT
TO anon
WITH CHECK (bucket_id = 'profile_images')$$;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_policies WHERE policyname = 'anon read profile_images'
) THEN
EXECUTE $$CREATE POLICY "anon read profile_images"
ON storage.objects
FOR SELECT
TO anon
USING (bucket_id = 'profile_images')$$;
END IF;
END$$; END$$;