RELEASE NOTE
管理者と メンバーの ロール組織の 設定は 管理者だけが 変更。 権限を 確かめる 仕組みも 設計から 解説
これまでの OpenGreenTrack では、組織の全員が同じ操作をできました。10 月 23〜24 日の変更で、組織の設定を変えられる人を「管理者」に絞りました。日々の入力や算定は、これまでどおり全員が行えます。この記事では使い方に加えて、ロールの考え方と、権限を確かめる仕組みの設計を説明します。
この版のポイント
- ロールは「管理者」と「メンバー」の 2 つ。組織の設定(企業情報・算定年度・招待・メンバー)は管理者だけが変更
- データの入力・算定・拠点や排出係数の管理・レポートの出力は、これまでどおり全員が使える
- 権限は画面ではなく、サーバーとデータベースで確かめる。画面の出し分けは使いやすさのため
- 最後の管理者の降格・削除・退会は、データベースのトリガーまで含めた 3 か所で止める
01CONCEPT
考え方: 「組織の 設定」と 「日々の 算定業務」を 分ける
権限を細かく分けすぎると、誰が何をできるのかが分かりにくくなります。算定を誰が担うかは組織によって違うため、データの入力・算定・マスタの編集は全員に任せ、取り返しのつきにくい「組織の設定」だけを管理者に絞りました。
日々の算定業務
- データ入力・算定
- 拠点の管理
- 排出係数・IDEA の取込
- 削減目標
- レポートの出力
組織の設定
- 企業情報の編集
- 算定年度の追加・削除
- メンバーの招待・削除
- ロールの変更
| 操作 | 管理者 | メンバー |
|---|---|---|
| 企業情報の編集 | ○ | 見るだけ |
| 算定年度の追加・削除 | ○ | 見るだけ |
| メンバーの招待・削除・ロールの変更 | ○ | × |
| データ入力・算定・拠点・排出係数・IDEA の取込・削減目標・レポート | ○ | ○ |
admin→管理者組織を作った人(初期セットアップ)。管理者が変更したときlogger→メンバー招待の既定値。新しくメンバーにするときに書く値viewer→メンバー以前からある値。新しくは書かない
判定は「role が admin か」の 1 か所だけ(isAdmin)。logger と viewer を個別に判定するコードは書かない
データベースには、以前から admin・logger・viewer の 3 つの値がありました。新しく member という値を足すと、既存の行を書き換えるデータの更新が必要になります。OpenGreenTrack はマイグレーションにデータの更新を書かない決まり(スキーマとデータを分ける)なので、値はそのまま残し、表示と判定のほうを「管理者かどうか」の 2 つにそろえました。
02HOW TO
ロールを 変える・ロールを 選んで 招待する
STEPSメンバーのロールを変える
- 管理者で「企業・メンバー設定」を開く
「設定 → 企業・メンバー設定 → メンバー管理」を開きます。
- ロールのプルダウンで選び直す
変えたいメンバーの行で「管理者」か「メンバー」を選びます。この時点ではまだ下書きです(冒頭の画面)。
- 「変更を保存」を押す
自分をメンバーに変える場合は、保存の前に確認が出ます。管理者が 0 人になる下書きは保存できません。
03DESIGN
設計:権限は どこで 確かめているか
画面でボタンを隠すだけでは、ブラウザから直接サーバーの処理を呼ばれたときに防げません。そこで、画面の出し分けは使いやすさのためと割り切り、実際の守りはサーバーとデータベースの 2 か所に置いています。
サーバーでは、操作のたびにデータベースからロールを読み直します(requireAdmin())。ログインしたときの情報を使い回さないので、管理者からメンバーに変えられた人は、次の操作からすぐに組織の設定を変えられなくなります。算定年度の追加やロールの変更のように、データベースの行単位の権限(RLS)を通らない管理用の接続で書く処理は、ここで止めるのが唯一の守りになります。
データベースの権限は、ロールを入れる前から「差し込み口」を用意していました。企業情報の更新と招待のポリシーは最初から current_user_is_admin() という関数を参照していて、その中身は「ログインしているか」を返すだけでした。今回はこの関数の中身を「ロールが admin か」に差し替えたので、ポリシーそのものは 1 行も書き換えずに管理者だけになりました。
-- 以前:ログインしていれば true(ロールを入れるまでの仮の中身)
-- 今回:ログイン中の利用者のロールが admin なら true
create or replace function current_user_is_admin()
returns boolean language sql stable security definer set search_path = ''
as $$
select exists (
select 1 from public.profiles p
where p.id = auth.uid() and p.role = 'admin'
)
$$;04DESIGN
設計:ロールを 変える 処理の 流れ
メンバー管理の「変更を保存」では、ロールの変更とメンバーの削除をまとめて受け取ります。サーバーで検証して順番を決め、1 件ずつデータベースの関数(RPC)で反映します。ロールを書き換えられるのはこの関数だけで、管理用の接続からしか呼べません。
- 1画面「変更を保存」管理者が 0 人になる下書きは保存させない。自分をメンバーにするときは確認を出す
- 2サーバー管理者か確かめるrequireAdmin() がデータベースからロールを読み直す
- 3サーバー変更を検証する同じ組織のメンバーか、同じ人への変更が重複していないか、自分を削除していないか
- 4サーバー反映の順番を決める管理者への昇格 → メンバーへの降格 → 削除
- 5データベース関数 change_member_role組織ごとのロックを取り、操作する人が今も管理者か・対象が同じ組織か・値が admin か logger かを確かめる
- 6データベーストリガー更新の前に、組織の管理者が 0 人にならないかを数える
- 7データベース更新と記録ロールを書き換え、監査ログ(member.role.update)を同じトランザクションで残す
順番の理由:「花子さんを管理者にして、自分(太郎)はメンバーに戻る」を 1 回で保存したとき
- 太郎をメンバーに → 管理者 0 人
- トリガーが拒否して止まる
- 花子を管理者に → 管理者 2 人
- 太郎をメンバーに → 管理者 1 人で完了
反映の順番を「管理者への昇格 → メンバーへの降格 → 削除」に固定しているのは、「別の人を管理者にして、自分はメンバーに戻る」を 1 回の保存で行えるようにするためです。先に自分を降格すると、その瞬間に管理者が 0 人になって止まってしまいます。
05DESIGN
設計:同時に 操作しても、 管理者は 0 人に ならない
管理者が 0 人になると、誰もロールを変えられず、画面からは元に戻せません。最後の管理者をメンバーに変える・削除する・最後の管理者が退会する、のどれもできないようにしたうえで、2 人の管理者がほぼ同時に互いを降格するような競合にも備えています。
管理者が 2 人(A と B)の組織で、A は「B をメンバーに」、B は「A をメンバーに」をほぼ同時に保存した
- A の処理:自分以外の管理者を数える → B がいる → 実行
- B の処理:同時に数える → A がいる → 実行
- どちらも成功して、管理者が 0 人になる
- A の処理がロックを取る。B の処理は待つ
- A の処理:B をメンバーにして確定、ロックを外す
- B の処理:自分がもう管理者ではないので拒否される
ロックは「組織の管理者の人数を数える」処理にだけ組織ごとに取るので、ほかの組織の操作は待たされない
このチェックはデータベースのトリガーに置いているので、画面やサーバーを通らない経路(アカウントの削除から連鎖する削除など)にも効きます。ただし、組織そのものを削除するときは残す管理者がいないので止めません。
06SECURITY
Next.js 16.4.2 への 更新ほか
- Next.js を 16.3.4 から 16.4.2 に上げ、critical と判定されていた脆弱性(16.0.0〜16.3.7 が対象)を解消しました。
next/ogでのリモートコード実行、SSG・ISR のキャッシュ汚染、画像最適化(/_next/image)での SSRF などが含まれます。 - CI の Secret Scan を、その実行で取り出したコミットの履歴だけを調べるように直しました。
07CHANGELOG



