/// // // M4 — API Rules for all user-scoped collections. // // CRITICAL SECURITY: without these rules, any logged-in user can read or // overwrite every other shop's records by passing a different `userId` in // the request body. The frontend cannot enforce this — it must be locked // down at the DB layer. // // This migration sets List/View/Create/Update/Delete rules on each // user-owned collection to: userId = @request.auth.id // // Notes: // - `users` admin-only access is NOT configured here (do that in the PB // admin UI under the users collection). This migration only touches the // per-shop data collections. // - `settings` is treated as user-scoped via its `userId` field (the // frontend stores a `businessSettings` record per user). // - Re-run is idempotent — applying the same rule string twice is safe. // const USER_SCOPED_COLLECTIONS = [ 'quotes', 'repairOrders', 'invoices', 'appointments', 'customers', 'services', 'settings', ]; const RULE = 'userId = @request.auth.id'; migrate( (db) => { for (const name of USER_SCOPED_COLLECTIONS) { const col = db.findCollectionByNameOrId(name); if (!col) { console.warn(`[M4] collection "${name}" not found — skipping (create it first in admin)`); continue; } col.listRule = RULE; col.viewRule = RULE; col.createRule = RULE; col.updateRule = RULE; col.deleteRule = RULE; db.save(col); console.log(`[M4] applied user-scoped rules to "${name}"`); } }, (db) => { // On rollback: clear the rules back to empty (full access as admin). // WARNING: this re-opens cross-tenant access — only run the down-migration // if you intend to reconfigure manually afterward. for (const name of USER_SCOPED_COLLECTIONS) { const col = db.findCollectionByNameOrId(name); if (!col) continue; col.listRule = ''; col.viewRule = ''; col.createRule = ''; col.updateRule = ''; col.deleteRule = ''; db.save(col); } } );