自作アプリ「Hubpin」を作っています。
分散した自分の発信を1ページに集めるハブサイトで、Next.js 16 と Supabase の組み合わせです。
この日はデータベースの設計をしていました。
テーブルを2つ作って、行レベルセキュリティ(RLS)のポリシーを7本書いたところです。
「公開カードは誰でも読める」「自分のカードは本人が読める」「自分のカードは本人が消せる」。
そういう条件を、1本ずつ SQL で書いていく作業でした。
書き終わったので select を叩いてみました。
permission denied for table items
1行も返ってきません。
ポリシーは何度読み返しても間違っていませんでした。
権限は二段構えになっていました
PostgreSQL の公式ドキュメントに、そのままの説明がありました。
In addition to the SQL-standard privilege system available through GRANT,
tables can have row security policies that restrict, on a per-user basis,
which rows can be returned by normal queries
「GRANT による権限の仕組みに加えて」と書いてあります。
つまり RLS はその上に乗るものであって、置き換えるものではありませんでした。
| 段 | 何を決めるか |
|---|---|
| GRANT | そのテーブルに触れてよいか |
| RLS | 触れるとして、どの行か |
自分が7本書いたのは、下の段だけでした。
上の段が無いので、下の段には到達していません。
どの行を見せるかは丁寧に決めていたのに、テーブルを開ける鍵のほうを持っていませんでした。
「既定で付く」と書いてあるものが、付いていませんでした
Supabase 側のドキュメントも読みました。
By default, tables in the
publicschema are grantedSELECT,INSERT,UPDATE, andDELETEto theanonandauthenticatedroles.
既定で付く、と書いてあります。
付くはずのものが、手元では付いていませんでした。
なぜ付かなかったのかは、いまも特定できていません。
マイグレーション経由でテーブルを作ったことと関係がありそうに見えますが、
確かめていないことを原因として書くのはやめておきます。
ひとつ引っかかったのは、この一文が載っていた場所です。
「権限がありません」というエラーのトラブルシューティングページでした。
既定で付くはずのものが付かない場面は、それなりにあるということだと受け取りました。
エラーが直し方を教えてくれました
PostgreSQL は親切です。エラーと一緒に、こうすればいいという提案が出てきました。
HINT: GRANT INSERT ON public.items TO anon;
貼り付ければ動きます。
そして「未ログインの誰でもカードを追加できるサイト」ができあがります。
anon は未ログインの人に割り当てられるロールです。
そこに INSERT を渡すのは、鍵をかけないまま投稿フォームを置くのと同じでした。
HINT が答えているのは「どうすれば動くか」です。「そうしていいか」ではありません。
ここは、AI に聞いたコードをそのまま貼るときの事故と同じ形をしていると思いました。
返ってきたものは、たしかに動きます。動くことしか保証していません。
最小限だけ渡すことにしました
マイグレーションを1本足しました。既存のファイルは書き換えていません。
-- 未認証(anon)は読むだけ。書き込み権限そのものを与えない
grant select on public.profiles to anon;
grant select on public.items to anon;
-- ログイン済み(authenticated)は操作できる。
-- 🚨 ただし「どの行を」操作できるかは RLS ポリシーが決める
grant select, update on public.profiles to authenticated;
grant select, insert, update, delete on public.items to authenticated;
Supabase の標準的なやり方は「全部 GRANT して、あとは RLS で制御する」でした。
これは採りませんでした。
未ログインの人がカードを書き込む理由がひとつも無いなら、
RLS で弾く前に、権限そのものを渡さなければいい。
防御が1枚から2枚になります。
このとき自分は、それを素直に良いことだと思っていました。
おわりに
この工程には他にもいくつか罠がありましたが、
手順書に書いてあったものは全部よけられました。止まったのはここだけです。
予見できていた罠は回避できて、予見できなかった罠だけが時間を使いました。
そして分かったのは、「RLS を書いた」は「権限を設計した」ではないということでした。
ポリシーの一覧をいくら眺めても、その手前で誰がテーブルに触れるのかは書いてありません。
別のファイルに、別の文法で置いてあります。
次回は、そのポリシーのほうに穴が開いていた話を書きます。
