diff --git a/packages/database/src/migrations/0016_role_permissions_per_property.sql b/packages/database/src/migrations/0016_role_permissions_per_property.sql new file mode 100644 index 0000000..37744ae --- /dev/null +++ b/packages/database/src/migrations/0016_role_permissions_per_property.sql @@ -0,0 +1,8 @@ +-- role_permissions must be unique per property. The old unique index +-- (role_id, permission_key) made Cloud tenant bootstrap silently skip +-- permission grants once any other property had seeded the same system role. + +DROP INDEX IF EXISTS role_permissions_role_perm_unique; + +CREATE UNIQUE INDEX IF NOT EXISTS role_permissions_role_perm_unique + ON role_permissions (property_id, role_id, permission_key); diff --git a/packages/database/src/push-schema.ts b/packages/database/src/push-schema.ts index 6878971..83d6f35 100644 --- a/packages/database/src/push-schema.ts +++ b/packages/database/src/push-schema.ts @@ -1020,7 +1020,7 @@ async function main() { permission_key varchar(100) NOT NULL, created_at timestamptz NOT NULL DEFAULT now() )`, - `CREATE UNIQUE INDEX IF NOT EXISTS role_permissions_role_perm_unique ON role_permissions (role_id, permission_key)`, + `CREATE UNIQUE INDEX IF NOT EXISTS role_permissions_role_perm_unique ON role_permissions (property_id, role_id, permission_key)`, `CREATE TABLE IF NOT EXISTS user_roles ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), property_id uuid NOT NULL REFERENCES properties(id), diff --git a/packages/database/src/schema/rbac.ts b/packages/database/src/schema/rbac.ts index 0cff1a4..da6e920 100644 --- a/packages/database/src/schema/rbac.ts +++ b/packages/database/src/schema/rbac.ts @@ -93,7 +93,12 @@ export const rolePermissions = pgTable( createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => ({ - rolePermUnique: uniqueIndex('role_permissions_role_perm_unique').on(t.roleId, t.permissionKey), + // Grants are property-scoped even when role_id points at a global system role. + rolePermUnique: uniqueIndex('role_permissions_role_perm_unique').on( + t.propertyId, + t.roleId, + t.permissionKey, + ), }), );