31 lines
1.3 KiB
MySQL
31 lines
1.3 KiB
MySQL
|
|
-- Migration 026 — per-project access grants (RBAC v2).
|
||
|
|
--
|
||
|
|
-- v1 auth is flat: any logged-in user can do everything. This adds per-project
|
||
|
|
-- scoping. A grant targets either a user or a group (polymorphic subject) and
|
||
|
|
-- carries a level: 'view' (read-only) or 'edit' (read-write). Admins bypass all
|
||
|
|
-- of this in code (authz.js) and need no rows here.
|
||
|
|
--
|
||
|
|
-- subject_id is intentionally NOT a foreign key — it points at either users.id
|
||
|
|
-- or groups.id depending on subject_type. Rows are cleaned up when the project
|
||
|
|
-- is deleted (FK cascade). A deleted user/group leaves an orphan row that
|
||
|
|
-- resolves to nobody (harmless); a later sweep can prune them if desired.
|
||
|
|
|
||
|
|
DO $$ BEGIN
|
||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'access_level') THEN
|
||
|
|
CREATE TYPE access_level AS ENUM ('view', 'edit');
|
||
|
|
END IF;
|
||
|
|
END $$;
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS project_access (
|
||
|
|
project_id UUID NOT NULL REFERENCES projects ON DELETE CASCADE,
|
||
|
|
subject_type TEXT NOT NULL CHECK (subject_type IN ('user', 'group')),
|
||
|
|
subject_id UUID NOT NULL,
|
||
|
|
level access_level NOT NULL DEFAULT 'view',
|
||
|
|
granted_by UUID REFERENCES users ON DELETE SET NULL,
|
||
|
|
granted_at TIMESTAMPTZ DEFAULT NOW(),
|
||
|
|
PRIMARY KEY (project_id, subject_type, subject_id)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_project_access_subject
|
||
|
|
ON project_access (subject_type, subject_id);
|