Storage: Buckets and Policies
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Supabase Storage คือ object storage แบบ S3-compatible จัดเป็น bucket และ access ของทุก object ถูกบังคับด้วย Row Level Security policy ธรรมดาบน table Postgres — mechanism เดียวกับที่คุณรู้จักแล้วจาก module RLS ไม่ใช่ระบบสิทธิ์แยกต่างหากอีกชุด
Bucket: container แบบ public และ private
หัวข้อที่มีชื่อว่า “Bucket: container แบบ public และ private”bucket คือ container ระดับบนสุดสำหรับไฟล์ คล้าย S3 bucket เช่นคุณอาจมี bucket avatars สำหรับรูปโปรไฟล์ และ bucket public-assets สำหรับรูป marketing bucket หนึ่งตั้งเป็น public ได้ หมายความว่าใครก็ตามที่มี URL ของ object อ่านได้เลยโดยไม่ต้อง authenticate หรือตั้งเป็น private หมายความว่า access ถูกเช็คทุก request
การ upload, download และอ่านไฟล์จาก supabase-js ทำงานผ่านชื่อ bucket ทั้งหมด
// Upload a file into a path inside the bucketconst { data, error } = await supabase.storage .from('avatars') .upload('user123/photo.png', file);
// Download the raw file bytesconst { data: fileData } = await supabase.storage .from('avatars') .download('user123/photo.png');
// Get a public URL — only meaningful for a public bucketconst { data: publicUrlData } = supabase.storage .from('avatars') .getPublicUrl('user123/photo.png');
// Get a time-limited signed URL — for a private bucketconst { data: signedUrlData } = await supabase.storage .from('avatars') .createSignedUrl('user123/photo.png', 60);getPublicUrl แค่สร้าง URL string ขึ้นมาเฉย ๆ ไม่ได้เช็คอะไรเลย ดังนั้นเรียกกับ object ใน private bucket ก็ยังได้ URL กลับมา แต่ URL นั้นจะถูก reject อยู่ดี createSignedUrl คือตัวที่ต้องใช้จริงสำหรับ private content โดยจะสร้าง URL ที่ valid ตามจำนวนวินาทีที่คุณส่งเข้าไป (60 ในตัวอย่างข้างบน) โดยฝัง signed token ที่ให้ access ชั่วคราวกับ object ตัวนั้นตัวเดียว
สิ่งที่บังคับ access จริง ๆ คือ Row Level Security
หัวข้อที่มีชื่อว่า “สิ่งที่บังคับ access จริง ๆ คือ Row Level Security”นี่คือข้อเท็จจริงที่ควรจำไว้ Storage ไม่มีภาษาสิทธิ์เฉพาะของตัวเองเลย metadata ของทุก object ที่ upload ไป — bucket, path, owner, content type — ถูกเก็บเป็น row จริงใน table Postgres ชื่อ storage.objects access control ก็คือแค่ Row Level Security policy บน table นั้น เขียนด้วย syntax using กับ with check เดียวกันเป๊ะกับที่คุณใช้กับ table อื่นในคอร์สนี้
pattern ที่พบบ่อยคือให้ user จัดการได้แค่ไฟล์ใต้ folder prefix ของตัวเอง โดย key ด้วย auth.uid() ของเขา
create policy "Users can upload their own avatar"on storage.objectsfor insertto authenticatedwith check ( bucket_id = 'avatars' and (storage.foldername(name))[1] = auth.uid()::text);
create policy "Users can read their own avatar"on storage.objectsfor selectto authenticatedusing ( bucket_id = 'avatars' and (storage.foldername(name))[1] = auth.uid()::text);ด้วย policy แบบนี้ ไฟล์ที่ upload ไปที่ avatars/user123/photo.png จะเขียนได้และอ่านได้เฉพาะ user ที่ id เป็น user123 เท่านั้น — เพราะ name (path เต็มของ object) ถูกเช็คกับ auth.uid() ทุก request เหมือนกับที่ RLS บน table อื่นกรอง row ตาม user ที่ request เข้ามาเป๊ะ ๆ
flowchart LR
client["Client upload/download request"] --> api["Storage API"]
api --> objects[("storage.objects metadata row: bucket_id, name, owner")]
objects --> rls{"RLS policy: using / with check against auth.uid()"}
rls -->|allowed| data["Underlying object data returned or written"]
rls -->|denied| reject["403 rejected"]