Supabaseで「友達限定」とか「招待制」のアプリ作る時って、他の人のデータが絶対に見えたらあかんやん?そんな時にめっちゃ大事なんがRLS(Row Level Security)やで。
今回は「友人の推しだけが届く」みたいな招待制アプリで、どうやってRLSを設定するか教えるわ。とりあえずコピペで動くプロンプトからどうぞ!
一番雑な投げ方
とりあえず動かしたいだけなら、これ投げてみ?
Supabaseのテーブル `foodies` に `user_id` カラムがあるとするわ。
ログインしてるユーザー(`auth.uid()`)が、友達関係にあるユーザーの `foodies` だけ見れるRLSポリシー作って。
友達関係は `friends` テーブルにあって、`user_id`と`friend_id`のカラムがあると思っといて。
自分の `foodies` も見れるようにしてな。
これだけでもSQLの骨格は出てくるはずやで。
もうちょい具体的に投げるパターン
もうちょっと細かく指定したい時は、こんな感じで投げてみてな。
1. 自分の投稿と、フォローしてる友達の投稿だけ見たい時
「うちらの投稿と、フォローしてる友達の投稿だけ見たいねん」ってパターン。
ALTER TABLE foodies ENABLE ROW LEVEL SECURITY;
CREATE POLICY "show_own_and_friends_foodies" ON foodies
FOR SELECT
USING (
foodies.user_id = auth.uid() OR
EXISTS (
SELECT 1 FROM friends
WHERE
friends.user_id = auth.uid()
AND friends.friend_id = foodies.user_id
)
);
2. 友達関係そのものも、自分に関連するレコードだけ見たい時
friends テーブル自体も、自分に関連するレコードだけ見せるポリシーにしとくと安心やで。
ALTER TABLE friends ENABLE ROW LEVEL SECURITY;
CREATE POLICY "show_own_friends_relations" ON friends
FOR SELECT
USING (
friends.user_id = auth.uid() OR
friends.friend_id = auth.uid()
);
3. 書き込み(INSERT/UPDATE/DELETE)も制限したい時
SELECTだけじゃなくて、データを作る、更新する、消す時も制限したいやん?例えば「自分の投稿しか編集できひん」とかね。
ALTER TABLE foodies ENABLE ROW LEVEL SECURITY;
-- 自分の投稿だけ編集・削除・追加できるポリシー
CREATE POLICY "manage_own_foodies" ON foodies
FOR ALL
USING (foodies.user_id = auth.uid())
WITH CHECK (foodies.user_id = auth.uid());
FOR ALLにしとけば、SELECT以外の操作も全部このポリシーが適用されるから便利やで。
WITH CHECKは書き込み時に適用される条件や。
実践例 / 実録
うちらが作った招待制レストラン推薦アプリ「くぐる」やったら、まさに「友人の推しだけが届く」を実現するためにRLSをがっつり使ってるで。
例えば、foodiesっていうおすすめ情報が入ったテーブルと、friendsっていう友達関係を管理するテーブルがあったとするやん?
foodiesテーブルにはこんな感じで情報が入ってる。
id(UUID)user_id(UUID, 投稿者のID)place_name(text, お店の名前)description(text, おすすめコメント)created_at(timestamp with time zone)
friendsテーブルにはこんな感じ。
user_id(UUID, フォローしてる人のID)friend_id(UUID, フォローされてる人のID)created_at(timestamp with time zone)
この状態で「ログインしてるユーザーがフォローしてる友達のfoodiesと、自分のfoodiesだけが見える」ようにするには、さっきのポリシーがまさに効いてくるんや。
実際にSupabaseのSQLエディタで流すならこんな感じやで。
-- まず、foodiesテーブルでRLSを有効にするのを忘れんといてな!
ALTER TABLE public.foodies ENABLE ROW LEVEL SECURITY;
-- 次に、友達のfoodiesを見せるポリシーを作るで
CREATE POLICY "Allow authenticated users to view their own and friends' foodies" ON public.foodies
FOR SELECT
TO authenticated -- 認証済みユーザーに適用
USING (
-- 自分の投稿はいつでも見えるように
foodies.user_id = auth.uid() OR
-- 自分がフォローしてる友達の投稿も見れるように
EXISTS (
SELECT 1 FROM public.friends
WHERE
friends.user_id = auth.uid() AND
friends.friend_id = foodies.user_id
)
);
-- おまけ:friendsテーブルもRLSを有効にして、自分に関連する友達関係だけ見せるポリシーも作っとくと安心やで
ALTER TABLE public.friends ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow authenticated users to view their own friend relations" ON public.friends
FOR SELECT
TO authenticated
USING (
friends.user_id = auth.uid() OR
friends.friend_id = auth.uid()
);
これでログインしてるユーザー(auth.uid()で取れるよ!)が、自分と友達のデータだけを見れるようになるんや。他の人の投稿は、どんなに頑張ってもSQLで叩いても見えへんから、セキュリティはバッチリやな。
つまずきポイント
RLS、便利やけどたまに「あれ?なんで動かんの?」ってなる時があるんよな。
1. 「RLSが有効になってへんやん!」
一番よくあるのがこれ。テーブル作ってポリシー作っただけで満足してへん?ALTER TABLE your_table_name ENABLE ROW LEVEL SECURITY; これをテーブルごとに実行せんと、ポリシーは全く意味あらへんからな!
2. 「SELECTはできるのに、INSERT/UPDATE/DELETEがエラーになる!」
RLSポリシーって、デフォルトやとFOR SELECTだけになってるんよ。だからデータを読み込む時は大丈夫やけど、新しくデータを入れたり、更新したり、消したりする時はまた別のポリシーがいることが多いで。
例えば、自分の投稿だけUPDATEできるようにしたいなら、FOR UPDATEのポリシーも作らなあかんねん。
CREATE POLICY "allow_own_update" ON public.foodies
FOR UPDATE
USING (foodies.user_id = auth.uid())
WITH CHECK (foodies.user_id = auth.uid());
FOR ALLって書けば、SELECT、INSERT、UPDATE、DELETE全部に適用されるから、書き込み系の制限が全部同じでええならこれ使うと楽やで。
3. USINGとWITH CHECKの違い、よくわからん!
USING: これは「どの行を見たり、触ったりできるか」を決める条件や。SELECTの時に使うのはもちろん、INSERT、UPDATE、DELETEの時も、対象の行を特定するために使われる。WITH CHECK: これは主にINSERTやUPDATEの時に「新しい行や更新後の行が、このポリシーの条件を満たしてるか」をチェックする条件や。例えば「user_idが自分のもの」っていうポリシーがあって、他人のuser_idでINSERTしようとしたり、自分のuser_idを他人のものにUPDATEしようとしたりすると、WITH CHECKで弾かれるんや。USINGだけでも書き込み制限できる場合が多いけど、WITH CHECKはより安全を期すために使うイメージやな。
慣れるまでは「FOR ALLでUSINGとWITH CHECKを同じ条件にしといたら大体大丈夫」って覚えとくのもアリやで。これでセキュアな招待制アプリ、サクッと作っちゃってな!