認証と権限 Auth & permissions

管理者ポータルは Supabase Auth を使った JWT ベース認証と、ロール/権限キーによる画面側フィルタリングで構成されています。

The admin portal uses Supabase Auth for JWT-based login, plus a role / permission-key system that filters the UI client-side.

ログインフロー Login flow

sequenceDiagram
  participant U as Admin
  participant FE as Admin Portal
  participant SB as Supabase Auth
  participant W as Workers BFF
/v1/admin/* participant DB as PostgreSQL U->>FE: email + password FE->>SB: signInWithPassword() (data-plane 例外: SDK 直叩き) SB-->>FE: session (JWT) Note over FE: AuthContext.signIn → verifyAdminRole()
RequireAuth ルートマウント時にも再検証 FE->>W: GET /v1/admin/admins/me (Bearer JWT) W->>DB: SELECT admins WHERE user_id = jwt.sub AND status='active' W-->>FE: 200 { admin } / 401・403・404 → signOut('not_admin') FE->>W: GET /v1/admin/admins?status=active (PermissionProvider) FE->>W: GET /v1/admin/role-permissions W->>DB: SELECT * FROM admin.role_permissions JOIN admin.roles DB-->>W: rows W-->>FE: admins[] + role_permissions マップ FE-->>U: Dashboard (nav は usePermission(key) で絞り込み) Note over FE,W: 以降のリクエストは全て BFF 経由
Authorization: Bearer {jwt} で JWT を渡す
service_role / DB 直叩きは UI バンドル外

関連コンポーネント Components involved

Security: service_role キーは強力です。**ポータル UI バンドルには絶対に含めない**。Workers (BFF) の Secret として保管し、特権操作 (Auth admin / 直接 SQL 等) は全て BFF サーバ側で行う。 The service_role key is powerful and **must never be bundled into the portal UI**. It lives only as a Workers (BFF) secret; all privileged operations (Auth admin actions, direct SQL, etc.) happen server-side inside the BFF.

ロール Roles

ロールは roles テーブルで定義され、各ロールに対する権限キーは role_permissions で管理されます。

Roles live in the roles table, with permission keys per role stored in role_permissions.

Role想定担当Intended ownerアクセス範囲Scope
Super Adminシステム管理者Platform admin全権限All permissions
Ops Manager運用マネージャーOps manager駐車場・ユーザー・売上・サポートParking, users, revenue, support
Content Managerコンテンツ担当Content team記事・広告・ゲーミ・カスタマイズArticles, ads, gamification, theming
Support AgentCS担当Customer supportサポートチケット・誤情報報告Support tickets + error reports

権限キーマトリクス Permission key matrix

31 個の権限キーが定義されており、Roles 画面でチェックボックスマトリクスとして一括管理します。変更は保存ボタン押下時にのみ DB 反映(オートセーブ禁止)。SSoT は web/portal/admin/src/pages/RolesPage.tsxallPermissions

31 permission keys, managed as a checkbox matrix on the Roles screen. Changes commit only on Save — never on autosave. The source of truth is allPermissions inside web/portal/admin/src/pages/RolesPage.tsx.

グループGroup キーKeys
メイン / Maindashboard.view
駐車場 / Parkingparking.view · parking.edit · tags.view · tags.edit · reviews.view · reviews.moderate
ユーザー / Usersusers.view · users.edit
オーナー / Ownersowners.view · owners.edit
運営 / Opsplans.view · plans.edit · support.view · support.respond · notifications.view · notifications.send · sales.view
コンテンツ / Contentarticles.view · articles.edit · articles.publish · ads.view · ads.edit · gamification.view · gamification.edit
管理者 / Adminsadmins.view · admins.edit · roles.view · roles.edit
システム / Systemsettings.view · settings.edit

RBAC ロード & 判定フロー RBAC load & evaluation flow

flowchart LR
  Start((App boot)) --> Auth{Supabase
session?} Auth -- no --> Login[/login/] Auth -- yes --> Verify[GET /v1/admin/admins/me] Verify -- 401/403/404 --> Logout[signOut 'not_admin'] Verify -- 200 --> Load[PermissionProvider] Load --> A1[bff.admin.admins.list] Load --> A2[fetchAllRolePermissions] A1 --> Match[email 一致で
自分の admin 行を特定] A2 --> Map[role_id → Set permission_key] Match --> Hold[PermissionContext] Map --> Hold Hold --> Hook[usePermission key] Hook -- has key --> OK[render] Hook -- missing & loaded --> Hide[UI hide / Forbidden] Hook -- loading or fail --> Open[fail-open: render
BFF が二次防御]

アクセス制御の実装レイヤ Enforcement layers

  1. クライアント: AuthContext が権限キーを保持、サイドバー / ボタンを hasPermission('parking.edit') で表示制御。
  2. Client: AuthContext exposes permission keys; the sidebar and action buttons gate on hasPermission('parking.edit').
  3. RLS ポリシー: Supabase の Row Level Security を各テーブルに設定し、テーブル単位でアクセスを制御。
  4. RLS policies: Supabase Row Level Security on each table restricts access server-side.
  5. service_role: 管理者アカウント作成や削除など、特権操作は **BFF サーバ側 (Workers) でのみ** service_role を使用する。フロントは BFF (POST /v1/admin/admins 等) を叩くだけで service_role キーは UI バンドルに入らない。
  6. service_role: Privileged flows (create / delete admin) use service_role **inside the BFF (Workers) only**. The portal calls BFF endpoints (POST /v1/admin/admins etc.); the service_role key is never bundled into the UI.
注意Caveat: 本ポータルは信頼できる運用者向けに設計されています。UI 非表示は UX 上のガードであり、厳密な権限分離が必要な場合は RLS ポリシーを強化してください。 The portal is built for trusted operators. UI hiding is a UX guard; harden the RLS policies if you need strict enforcement.

管理者アカウント作成フロー Admin account creation flow

sequenceDiagram
  participant SA as Super Admin
  participant FE as Admins page
  participant W as Workers BFF
POST /v1/admin/admins participant Auth as Supabase Auth (service_role) participant DB as admins table SA->>FE: Fill form + submit FE->>W: POST /v1/admin/admins
{ email, name, role_id } W->>W: Generate 12-char random password (server-side) W->>Auth: admin.createUser(email, password, email_confirm: true) Auth-->>W: user (id = user_id) W->>DB: INSERT into admin.admins (user_id, name, email, role_id, status='active') DB-->>W: row W-->>FE: { admin, initial_password } FE-->>SA: Display initial password (one-time, copy button) Note over W,Auth: service_role キーは BFF 内のみで保持
UI バンドルには含めない

初期パスワードは画面上でのみ表示され、閉じた後は再表示できません。 リセット時は resetAdminPassword() が新しいパスワードを生成 → Auth 更新 → 画面表示 の流れになります。

The initial password is shown once and never again. A reset flow regenerates a password, updates Auth, and surfaces the new value via resetAdminPassword().