snipe-auth-rbac 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipe-auth-rbac",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Two-layer RBAC (system + company) for React, Next.js, and any modern TS app — paired with the Python sibling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -509,4 +509,114 @@ DO $$ BEGIN
509
509
  END IF;
510
510
  END $$;
511
511
 
512
+ -- ─────────────────────────────────────────────────────────────────
513
+ -- 11. Table-level grants + Row-Level Security
514
+ --
515
+ -- The admin module under `snipe-auth-rbac/admin` reads + writes
516
+ -- rbac.* tables directly via PostgREST (`.from('roles').select()`)
517
+ -- so the matrix UI can render. That requires per-table grants —
518
+ -- USAGE on the schema isn't enough on its own.
519
+ --
520
+ -- Strategy:
521
+ -- * GRANT CRUD on every rbac.* table to `authenticated`.
522
+ -- * Layer RLS so reads are wide enough for the admin UI but
523
+ -- writes are super-admin only by default.
524
+ -- * service_role keeps full access (backend / migrations / admin
525
+ -- scripts use service_role).
526
+ --
527
+ -- Adopters who want a different access model (e.g. delegate role
528
+ -- editing to non-super admins) replace the WRITE policies after
529
+ -- this file applies.
530
+ -- ─────────────────────────────────────────────────────────────────
531
+
532
+ -- is_super_admin: convenience helper used by the policies below.
533
+ -- SECURITY DEFINER so a policy can call it without recursing into
534
+ -- RLS on the very tables it inspects.
535
+ CREATE OR REPLACE FUNCTION rbac.is_super_admin()
536
+ RETURNS boolean
537
+ LANGUAGE sql
538
+ STABLE
539
+ SECURITY DEFINER
540
+ SET search_path = rbac, public
541
+ AS $$
542
+ SELECT EXISTS (
543
+ SELECT 1
544
+ FROM rbac.user_system_roles usr
545
+ JOIN rbac.roles r ON r.id = usr.role_id
546
+ WHERE usr.user_id = auth.uid()
547
+ AND r.is_super
548
+ );
549
+ $$;
550
+
551
+ DO $$ BEGIN
552
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated') THEN
553
+ EXECUTE 'GRANT EXECUTE ON FUNCTION rbac.is_super_admin() TO authenticated';
554
+ EXECUTE 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA rbac TO authenticated';
555
+ END IF;
556
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN
557
+ EXECUTE 'GRANT ALL ON ALL TABLES IN SCHEMA rbac TO service_role';
558
+ EXECUTE 'GRANT ALL ON ALL SEQUENCES IN SCHEMA rbac TO service_role';
559
+ EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA rbac GRANT ALL ON TABLES TO service_role';
560
+ END IF;
561
+ END $$;
562
+
563
+ ALTER TABLE rbac.companies ENABLE ROW LEVEL SECURITY;
564
+ ALTER TABLE rbac.resources ENABLE ROW LEVEL SECURITY;
565
+ ALTER TABLE rbac.roles ENABLE ROW LEVEL SECURITY;
566
+ ALTER TABLE rbac.role_permissions ENABLE ROW LEVEL SECURITY;
567
+ ALTER TABLE rbac.user_system_roles ENABLE ROW LEVEL SECURITY;
568
+ ALTER TABLE rbac.user_company_roles ENABLE ROW LEVEL SECURITY;
569
+
570
+ -- READ — open to any authenticated user for static config tables;
571
+ -- own-row only on assignment tables (super-admin sees all).
572
+ DROP POLICY IF EXISTS rbac_companies_read ON rbac.companies;
573
+ CREATE POLICY rbac_companies_read ON rbac.companies FOR SELECT TO authenticated USING (true);
574
+
575
+ DROP POLICY IF EXISTS rbac_resources_read ON rbac.resources;
576
+ CREATE POLICY rbac_resources_read ON rbac.resources FOR SELECT TO authenticated USING (true);
577
+
578
+ DROP POLICY IF EXISTS rbac_roles_read ON rbac.roles;
579
+ CREATE POLICY rbac_roles_read ON rbac.roles FOR SELECT TO authenticated USING (true);
580
+
581
+ DROP POLICY IF EXISTS rbac_role_permissions_read ON rbac.role_permissions;
582
+ CREATE POLICY rbac_role_permissions_read ON rbac.role_permissions FOR SELECT TO authenticated USING (true);
583
+
584
+ DROP POLICY IF EXISTS rbac_user_system_roles_read ON rbac.user_system_roles;
585
+ CREATE POLICY rbac_user_system_roles_read ON rbac.user_system_roles FOR SELECT TO authenticated
586
+ USING (user_id = auth.uid() OR rbac.is_super_admin());
587
+
588
+ DROP POLICY IF EXISTS rbac_user_company_roles_read ON rbac.user_company_roles;
589
+ CREATE POLICY rbac_user_company_roles_read ON rbac.user_company_roles FOR SELECT TO authenticated
590
+ USING (user_id = auth.uid() OR rbac.is_super_admin());
591
+
592
+ -- WRITE — super-admin only across the board. Adopters override
593
+ -- after this file applies to delegate role editing further.
594
+ DROP POLICY IF EXISTS rbac_companies_write ON rbac.companies;
595
+ CREATE POLICY rbac_companies_write ON rbac.companies FOR ALL TO authenticated
596
+ USING (rbac.is_super_admin()) WITH CHECK (rbac.is_super_admin());
597
+
598
+ DROP POLICY IF EXISTS rbac_resources_write ON rbac.resources;
599
+ CREATE POLICY rbac_resources_write ON rbac.resources FOR ALL TO authenticated
600
+ USING (rbac.is_super_admin()) WITH CHECK (rbac.is_super_admin());
601
+
602
+ DROP POLICY IF EXISTS rbac_roles_write ON rbac.roles;
603
+ CREATE POLICY rbac_roles_write ON rbac.roles FOR ALL TO authenticated
604
+ USING (rbac.is_super_admin()) WITH CHECK (rbac.is_super_admin());
605
+
606
+ DROP POLICY IF EXISTS rbac_role_permissions_write ON rbac.role_permissions;
607
+ CREATE POLICY rbac_role_permissions_write ON rbac.role_permissions FOR ALL TO authenticated
608
+ USING (rbac.is_super_admin()) WITH CHECK (rbac.is_super_admin());
609
+
610
+ DROP POLICY IF EXISTS rbac_user_system_roles_write ON rbac.user_system_roles;
611
+ CREATE POLICY rbac_user_system_roles_write ON rbac.user_system_roles FOR ALL TO authenticated
612
+ USING (rbac.is_super_admin()) WITH CHECK (rbac.is_super_admin());
613
+
614
+ DROP POLICY IF EXISTS rbac_user_company_roles_write ON rbac.user_company_roles;
615
+ CREATE POLICY rbac_user_company_roles_write ON rbac.user_company_roles FOR ALL TO authenticated
616
+ USING (rbac.is_super_admin()) WITH CHECK (rbac.is_super_admin());
617
+
512
618
  COMMIT;
619
+
620
+ -- Tell PostgREST to refresh schema cache so the policies + grants
621
+ -- propagate without a manual reload.
622
+ NOTIFY pgrst, 'reload config';
@@ -1,32 +1,42 @@
1
1
  -- snipe-auth-rbac — optional default seed
2
2
  --
3
3
  -- Companion to 0001_initial.sql that seeds:
4
- -- * Two system roles (System Admin with is_super=true, System Support).
5
- -- * Four generic company-role templates (Owner / Manager / Member /
6
- -- Viewer) with sensible default_permissions patterns.
4
+ -- * Two system roles (System-Administrator with is_super=true,
5
+ -- System-Support).
6
+ -- * Four generic company-role templates (Inhaber / Verwalter /
7
+ -- Mitarbeiter / Leser) with sensible default_permissions
8
+ -- patterns.
7
9
  --
8
- -- The four templates use only the `default` action set — they don't
9
- -- reference specific resources or groups, since those are defined by
10
- -- the host. After registering host resources, run
11
- -- ``rbac.apply_template_defaults(role_id)`` to materialise the matrix.
10
+ -- Names are in German that's the package's primary target
11
+ -- audience (German property-management / SaaS). Adopters who
12
+ -- prefer English names skip this file and seed their own.
12
13
  --
13
- -- Domain-specific templates (Property Manager, Tenant Manager,
14
- -- Sales, …) belong in the host's own seed migration where their
15
- -- group/resource defaults can reference real registered resources.
14
+ -- The four templates use only the `default` action set — they
15
+ -- don't reference specific resources or groups, since those are
16
+ -- defined by the host. After registering host resources, run
17
+ -- ``rbac.apply_template_defaults(role_id)`` to materialise the
18
+ -- matrix.
19
+ --
20
+ -- Domain-specific templates (Liegenschaftsverwalter,
21
+ -- Mieterverwalter, Vertrieb, Gutachter, Anwalt, Mieter) belong in
22
+ -- the host's own seed migration where their group/resource
23
+ -- defaults can reference real registered resources.
16
24
  --
17
25
  -- Idempotent: every INSERT uses ON CONFLICT DO NOTHING. Re-running
18
- -- the file leaves an existing deployment untouched.
26
+ -- the file leaves an existing deployment untouched. Note: this
27
+ -- means upgrading from v0.3.0 (English names) does NOT auto-rename
28
+ -- — see CHANGELOG for the rename snippet.
19
29
 
20
30
  BEGIN;
21
31
 
22
32
  -- System roles
23
33
  INSERT INTO rbac.roles (id, scope, company_id, name, description, is_system, is_super, default_permissions)
24
34
  VALUES
25
- (gen_random_uuid(), 'system', NULL, 'System Admin',
35
+ (gen_random_uuid(), 'system', NULL, 'System-Administrator',
26
36
  'Plattform-Vollzugriff. Setzt jede Berechtigungsprüfung außer Kraft.',
27
37
  true, true,
28
38
  '{"default": ["read", "write", "update", "delete"]}'::jsonb),
29
- (gen_random_uuid(), 'system', NULL, 'System Support',
39
+ (gen_random_uuid(), 'system', NULL, 'System-Support',
30
40
  'Lesezugriff auf systemweite Ressourcen für Support-Aufgaben.',
31
41
  true, false,
32
42
  '{"default": ["read"]}'::jsonb)
@@ -36,19 +46,19 @@ ON CONFLICT DO NOTHING;
36
46
  -- Generic shapes only; domain-specific defaults are the host's job.
37
47
  INSERT INTO rbac.roles (id, scope, company_id, name, description, is_system, is_super, default_permissions)
38
48
  VALUES
39
- (gen_random_uuid(), 'company', NULL, 'Owner',
40
- 'Vollzugriff innerhalb der eigenen Company.',
49
+ (gen_random_uuid(), 'company', NULL, 'Inhaber',
50
+ 'Vollzugriff innerhalb des eigenen Mandanten.',
41
51
  true, false,
42
52
  '{"default": ["read", "write", "update", "delete"]}'::jsonb),
43
- (gen_random_uuid(), 'company', NULL, 'Manager',
44
- 'Verwaltet Daten der Company, kann Rollen ändern. Kein Löschen.',
53
+ (gen_random_uuid(), 'company', NULL, 'Verwalter',
54
+ 'Verwaltet Daten des Mandanten, kann Rollen ändern. Kein Löschen.',
45
55
  true, false,
46
56
  '{"default": ["read", "write", "update"]}'::jsonb),
47
- (gen_random_uuid(), 'company', NULL, 'Member',
57
+ (gen_random_uuid(), 'company', NULL, 'Mitarbeiter',
48
58
  'Standard-Mitarbeiter mit Lese- und Schreibzugriff.',
49
59
  true, false,
50
60
  '{"default": ["read", "write"]}'::jsonb),
51
- (gen_random_uuid(), 'company', NULL, 'Viewer',
61
+ (gen_random_uuid(), 'company', NULL, 'Leser',
52
62
  'Nur Lesezugriff.',
53
63
  true, false,
54
64
  '{"default": ["read"]}'::jsonb)