ThreadCount Community edition
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
-- The operations console's database role, and what it may see.
|
||||
--
|
||||
-- lib/legal.ts promises customers their records are "never shown across facilities". The console
|
||||
-- is the first thing in the product that legitimately reads every facility, and there is no
|
||||
-- row-level security here — so the promise is kept by a role that CANNOT read customer content,
|
||||
-- rather than by remembering not to. A future screen that queries the wrong table gets a
|
||||
-- permission error, not data.
|
||||
--
|
||||
-- The role itself is created by hand, as postgres, before this migration reaches the box:
|
||||
--
|
||||
-- CREATE ROLE ops_ro LOGIN PASSWORD '…';
|
||||
--
|
||||
-- The app role cannot CREATE ROLE, but it owns every table it created, so the grants below run
|
||||
-- fine under `prisma migrate deploy`. They are guarded: if the role does not exist yet, nothing
|
||||
-- happens and the deploy succeeds — the console cannot connect until it does, which is the safe
|
||||
-- failure. Create the role, then run the body of this block by hand.
|
||||
--
|
||||
-- Column lists are ALLOW lists. Anything not named is invisible to the console:
|
||||
-- Facility — the three coordinator contact columns, the logo bytes, the two slip footers and the
|
||||
-- free-text location are omitted. Contacts are revealed, when they are, through a
|
||||
-- separate narrow path with its own trail, never through this role.
|
||||
-- User, StaffAccount — no email, no name, no password hash, no TOTP secret.
|
||||
-- AuditEvent — at and op only; no userName, target, ip or userId.
|
||||
-- Content tables — id and facilityId only, which is enough to count per facility and nothing
|
||||
-- else. Staff also exposes `inactive` so "active staff" can be counted.
|
||||
-- Child tables with no facilityId (request lines, messages, receipts, kit-check answers…) — nothing.
|
||||
-- The console's own tables (Operator*, RevealGrant) — nothing; they are reached through the main
|
||||
-- client, as the console's records rather than a customer's.
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'ops_ro') THEN
|
||||
RAISE NOTICE 'ops_ro does not exist — grants skipped; create the role, then apply this block by hand';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
GRANT USAGE ON SCHEMA public TO ops_ro;
|
||||
|
||||
-- The facility, minus its contacts and its prose.
|
||||
GRANT SELECT (
|
||||
"id", "name", "timezone", "defaultEntitlement", "initialSets", "capSets", "defaultReorder",
|
||||
"exceptionHigh", "varianceReason", "glAccount", "journalDesc", "lastBackup", "barcodeLookup",
|
||||
"staffGroups", "nursingGroups", "kitGroups", "orderSeq", "catalogSeq", "requestSeq", "rev",
|
||||
"barcodeSeq", "slipOrg", "isDemo", "demoResetAt", "createdAt"
|
||||
) ON "Facility" TO ops_ro;
|
||||
|
||||
-- Accounts: enough to count and to date, never to identify.
|
||||
GRANT SELECT ("id", "facilityId", "role", "inactive", "createdAt", "totpEnabledAt") ON "User" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId", "staffId", "createdAt", "lastSeenAt") ON "StaffAccount" TO ops_ro;
|
||||
|
||||
-- The trail is already scrubbed to ids and op names; the console reads only when and what kind.
|
||||
GRANT SELECT ("id", "facilityId", "at", "op") ON "AuditEvent" TO ops_ro;
|
||||
|
||||
-- Content tables: count-only.
|
||||
GRANT SELECT ("id", "facilityId", "inactive") ON "Staff" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Issue" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "CatalogItem" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Order" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Request" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Pickup" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Stocktake" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "HandIn" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Photo" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Barcode" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Location" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "StockLevel" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "StockMove" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Department" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Supplier" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Approval" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "Alteration" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "CostChange" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "WaitlistEntry" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "KitCheck" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "DamageReport" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "RecordDispute" TO ops_ro;
|
||||
GRANT SELECT ("id", "facilityId") ON "LinenNotice" TO ops_ro;
|
||||
|
||||
-- The owner's own inbox: not facility data, and the one content table the console may read whole.
|
||||
GRANT SELECT ON "ContactMessage" TO ops_ro;
|
||||
|
||||
-- Applied-migrations, for the drift tile: what the database has against what the repo ships.
|
||||
GRANT SELECT ON "_prisma_migrations" TO ops_ro;
|
||||
END $$;
|
||||
Reference in New Issue
Block a user