@jmruthers/pace-core 0.5.79 → 0.5.81

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.
Files changed (146) hide show
  1. package/dist/{DataTable-BCBW5SCL.js → DataTable-OBT663FS.js} +6 -6
  2. package/dist/{UnifiedAuthProvider-TSHK77PL.js → UnifiedAuthProvider-K2IZAY5F.js} +3 -3
  3. package/dist/{chunk-TI67X46U.js → chunk-3FV24IOD.js} +7 -7
  4. package/dist/{chunk-WYKXRCXB.js → chunk-5BN3YGNK.js} +157 -53
  5. package/dist/{chunk-WYKXRCXB.js.map → chunk-5BN3YGNK.js.map} +1 -1
  6. package/dist/{chunk-RMK6FOHF.js → chunk-CACHCRZS.js} +158 -79
  7. package/dist/{chunk-RMK6FOHF.js.map → chunk-CACHCRZS.js.map} +1 -1
  8. package/dist/{chunk-GXWREXH7.js → chunk-CBSD3BZ3.js} +2 -2
  9. package/dist/{chunk-CALYF6HH.js → chunk-I2VVV5PQ.js} +2 -2
  10. package/dist/{chunk-3WFKFBVQ.js → chunk-KUYWZVR2.js} +4 -4
  11. package/dist/{chunk-LVV6J6ZF.js → chunk-NTW3KGS4.js} +5 -5
  12. package/dist/{chunk-HQ7KTKC3.js → chunk-RIXPZJUB.js} +2 -2
  13. package/dist/{chunk-JYCP4L55.js → chunk-S3JKDMD5.js} +3 -3
  14. package/dist/{chunk-OBXLAL3J.js → chunk-V5SWX6KL.js} +4 -4
  15. package/dist/{chunk-IQFITAE3.js → chunk-YVUZWLQG.js} +3 -3
  16. package/dist/components.js +8 -8
  17. package/dist/hooks.js +7 -7
  18. package/dist/index.js +11 -11
  19. package/dist/providers.js +2 -2
  20. package/dist/rbac/index.js +7 -7
  21. package/dist/utils.js +1 -1
  22. package/docs/api/classes/ColumnFactory.md +1 -1
  23. package/docs/api/classes/ErrorBoundary.md +1 -1
  24. package/docs/api/classes/InvalidScopeError.md +1 -1
  25. package/docs/api/classes/MissingUserContextError.md +1 -1
  26. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  27. package/docs/api/classes/PermissionDeniedError.md +1 -1
  28. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  29. package/docs/api/classes/RBACAuditManager.md +1 -1
  30. package/docs/api/classes/RBACCache.md +1 -1
  31. package/docs/api/classes/RBACEngine.md +1 -1
  32. package/docs/api/classes/RBACError.md +1 -1
  33. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  34. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  35. package/docs/api/classes/StorageUtils.md +1 -1
  36. package/docs/api/enums/FileCategory.md +1 -1
  37. package/docs/api/interfaces/AggregateConfig.md +1 -1
  38. package/docs/api/interfaces/ButtonProps.md +1 -1
  39. package/docs/api/interfaces/CardProps.md +1 -1
  40. package/docs/api/interfaces/ColorPalette.md +1 -1
  41. package/docs/api/interfaces/ColorShade.md +1 -1
  42. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  43. package/docs/api/interfaces/DataRecord.md +1 -1
  44. package/docs/api/interfaces/DataTableAction.md +1 -1
  45. package/docs/api/interfaces/DataTableColumn.md +1 -1
  46. package/docs/api/interfaces/DataTableProps.md +1 -1
  47. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  48. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  49. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  50. package/docs/api/interfaces/EventLogoProps.md +1 -1
  51. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  52. package/docs/api/interfaces/FileMetadata.md +1 -1
  53. package/docs/api/interfaces/FileReference.md +1 -1
  54. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  55. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  56. package/docs/api/interfaces/FileUploadProps.md +1 -1
  57. package/docs/api/interfaces/FooterProps.md +1 -1
  58. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  59. package/docs/api/interfaces/InputProps.md +1 -1
  60. package/docs/api/interfaces/LabelProps.md +1 -1
  61. package/docs/api/interfaces/LoginFormProps.md +1 -1
  62. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  63. package/docs/api/interfaces/NavigationContextType.md +1 -1
  64. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  65. package/docs/api/interfaces/NavigationItem.md +1 -1
  66. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  67. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  68. package/docs/api/interfaces/Organisation.md +1 -1
  69. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  70. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  71. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  72. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  73. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  74. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  75. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  76. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  77. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  78. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  79. package/docs/api/interfaces/PaletteData.md +1 -1
  80. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  81. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  82. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  83. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  84. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  85. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  86. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  87. package/docs/api/interfaces/RBACConfig.md +1 -1
  88. package/docs/api/interfaces/RBACLogger.md +1 -1
  89. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  90. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  91. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  92. package/docs/api/interfaces/RouteConfig.md +1 -1
  93. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  94. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  95. package/docs/api/interfaces/StorageConfig.md +1 -1
  96. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  97. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  98. package/docs/api/interfaces/StorageListOptions.md +1 -1
  99. package/docs/api/interfaces/StorageListResult.md +1 -1
  100. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  101. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  102. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  103. package/docs/api/interfaces/StyleImport.md +1 -1
  104. package/docs/api/interfaces/SwitchProps.md +1 -1
  105. package/docs/api/interfaces/ToastActionElement.md +1 -1
  106. package/docs/api/interfaces/ToastProps.md +1 -1
  107. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  108. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  109. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  110. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  111. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  112. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  113. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  114. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  115. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  116. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  117. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  118. package/docs/api/interfaces/UserEventAccess.md +1 -1
  119. package/docs/api/interfaces/UserMenuProps.md +1 -1
  120. package/docs/api/interfaces/UserProfile.md +1 -1
  121. package/docs/api/modules.md +3 -3
  122. package/docs/architecture/rpc-function-standards.md +757 -0
  123. package/package.json +1 -1
  124. package/src/components/DataTable/__tests__/styles.test.ts +5 -5
  125. package/src/components/DataTable/components/DataTableCore.tsx +125 -18
  126. package/src/components/DataTable/components/PaginationControls.tsx +78 -77
  127. package/src/components/DataTable/components/UnifiedTableBody.tsx +12 -4
  128. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +29 -16
  129. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +6 -4
  130. package/src/components/DataTable/hooks/useTableColumns.ts +20 -19
  131. package/src/components/DataTable/hooks/useTableHandlers.ts +8 -3
  132. package/src/components/DataTable/styles.ts +1 -1
  133. package/src/providers/services/UnifiedAuthProvider.tsx +175 -54
  134. package/src/styles/core.css +1 -2
  135. package/src/styles/base.css +0 -208
  136. /package/dist/{DataTable-BCBW5SCL.js.map → DataTable-OBT663FS.js.map} +0 -0
  137. /package/dist/{UnifiedAuthProvider-TSHK77PL.js.map → UnifiedAuthProvider-K2IZAY5F.js.map} +0 -0
  138. /package/dist/{chunk-TI67X46U.js.map → chunk-3FV24IOD.js.map} +0 -0
  139. /package/dist/{chunk-GXWREXH7.js.map → chunk-CBSD3BZ3.js.map} +0 -0
  140. /package/dist/{chunk-CALYF6HH.js.map → chunk-I2VVV5PQ.js.map} +0 -0
  141. /package/dist/{chunk-3WFKFBVQ.js.map → chunk-KUYWZVR2.js.map} +0 -0
  142. /package/dist/{chunk-LVV6J6ZF.js.map → chunk-NTW3KGS4.js.map} +0 -0
  143. /package/dist/{chunk-HQ7KTKC3.js.map → chunk-RIXPZJUB.js.map} +0 -0
  144. /package/dist/{chunk-JYCP4L55.js.map → chunk-S3JKDMD5.js.map} +0 -0
  145. /package/dist/{chunk-OBXLAL3J.js.map → chunk-V5SWX6KL.js.map} +0 -0
  146. /package/dist/{chunk-IQFITAE3.js.map → chunk-YVUZWLQG.js.map} +0 -0
@@ -0,0 +1,757 @@
1
+ # RPC Function Standards
2
+
3
+ ## Overview
4
+
5
+ This document defines the standards for creating, naming, and structuring RPC (Remote Procedure Call) functions in the PACE Core database. These standards ensure consistency, security, and maintainability across all database functions.
6
+
7
+ ## Naming Convention
8
+
9
+ ### Pattern
10
+
11
+ RPC function names must follow: **`<family>_<domain>_<verb>`**
12
+
13
+ ### Family Prefixes
14
+
15
+ The family prefix categorizes the function's purpose:
16
+
17
+ - **`data_`** - Data access functions that retrieve or manipulate application data
18
+ - Examples: `data_user_events_get`, `data_cake_meals_get`, `data_user_organisations_get`
19
+
20
+ - **`rbac_`** - RBAC (Role-Based Access Control) functions for permission checking, role management, and security
21
+ - Examples: `rbac_check_permission_simplified`, `rbac_permissions_get`, `rbac_role_grant`
22
+
23
+ - **`util_`** - Utility functions for common operations, helpers, and shared logic
24
+ - Examples: `util_app_resolve`, `util_format_date`
25
+
26
+ - **`app_`** - Application-specific functions tied to a particular app module
27
+ - Examples: `app_cake_supply_calculate`, `app_cake_distribution_get`
28
+
29
+ ### Domain
30
+
31
+ The domain identifies the entity or context the function operates on:
32
+
33
+ - **User-related**: `user_events`, `user_organisations`, `user_roles`
34
+ - **App-specific**: `cake_meals`, `cake_distribution`, `cake_supply`
35
+ - **System**: `permissions`, `roles`, `session`, `audit`
36
+
37
+ ### Verb
38
+
39
+ The verb describes the action being performed:
40
+
41
+ - **`get`** - Retrieve a single record or collection
42
+ - **`list`** - Retrieve a collection (alternative to `get`)
43
+ - **`create`** - Create a new record
44
+ - **`update`** - Update an existing record
45
+ - **`delete`** - Delete a record
46
+ - **`check`** - Validate or check a condition (returns boolean)
47
+ - **`validate`** - Validate data or access (returns boolean or result)
48
+ - **`grant`** - Grant a permission or role
49
+ - **`revoke`** - Revoke a permission or role
50
+ - **`calculate`** - Perform calculations
51
+ - **`resolve`** - Resolve or transform data
52
+
53
+ ### Examples
54
+
55
+ ```sql
56
+ -- ✅ Good examples
57
+ data_user_events_get -- Get user events
58
+ data_cake_meals_get -- Get cake meals
59
+ rbac_check_permission_simplified -- Check permission
60
+ rbac_permissions_get -- Get permissions
61
+ rbac_role_grant -- Grant role
62
+ util_app_resolve -- Resolve app
63
+ data_cake_distribution_list -- List distribution data
64
+
65
+ -- ❌ Bad examples
66
+ getUserEvents -- Wrong: camelCase, missing prefix
67
+ cake_meals_get -- Wrong: missing family prefix
68
+ rbac_checkPermission -- Wrong: camelCase
69
+ get_cake_meals -- Wrong: wrong prefix order
70
+ ```
71
+
72
+ ## Function Structure
73
+
74
+ ### Standard Template
75
+
76
+ ```sql
77
+ CREATE OR REPLACE FUNCTION public.<family>_<domain>_<verb>(
78
+ p_param1 TYPE,
79
+ p_param2 TYPE DEFAULT NULL,
80
+ p_user_id UUID DEFAULT auth.uid(),
81
+ p_organisation_id UUID DEFAULT NULL
82
+ )
83
+ RETURNS TABLE(
84
+ column1 TYPE,
85
+ column2 TYPE
86
+ )
87
+ LANGUAGE plpgsql
88
+ SECURITY DEFINER
89
+ SET search_path TO 'public'
90
+ AS $$
91
+ DECLARE
92
+ v_local_variable TYPE;
93
+ v_is_super_admin BOOLEAN := false;
94
+ BEGIN
95
+ -- 1. Input validation
96
+ IF p_param1 IS NULL THEN
97
+ RETURN;
98
+ END IF;
99
+
100
+ -- 2. Super admin check (if applicable)
101
+ SELECT EXISTS(
102
+ SELECT 1 FROM rbac_global_roles
103
+ WHERE user_id = COALESCE(p_user_id, auth.uid())
104
+ AND role = 'super_admin'
105
+ AND valid_from <= NOW()
106
+ AND (valid_to IS NULL OR valid_to >= NOW())
107
+ ) INTO v_is_super_admin;
108
+
109
+ IF v_is_super_admin THEN
110
+ -- Super admin logic
111
+ RETURN QUERY SELECT ...;
112
+ RETURN;
113
+ END IF;
114
+
115
+ -- 3. Organisation context validation (if required)
116
+ IF p_organisation_id IS NULL THEN
117
+ RAISE EXCEPTION 'Organisation context is required';
118
+ RETURN;
119
+ END IF;
120
+
121
+ -- 4. Dynamic RBAC permission check (replaces manual organisation/event checks)
122
+ -- Use rbac_check_permission_simplified() for consistency with RLS policies
123
+ IF NOT rbac_check_permission_simplified(
124
+ COALESCE(p_user_id, auth.uid()),
125
+ 'read:resource', -- Format: operation:resource (e.g., 'read:meals', 'create:events')
126
+ p_organisation_id,
127
+ NULL, -- p_event_id if event-scoped, NULL if org-scoped
128
+ (SELECT id FROM rbac_apps WHERE name = 'CAKE' LIMIT 1), -- p_app_id
129
+ 'resource' -- p_page_id (page name from rbac_app_pages)
130
+ ) THEN
131
+ RETURN; -- No permission
132
+ END IF;
133
+
134
+ -- 5. Main logic (RLS is bypassed but we've checked permissions above)
135
+ RETURN QUERY SELECT ...;
136
+
137
+ EXCEPTION WHEN OTHERS THEN
138
+ RAISE WARNING 'Function failed: %', SQLERRM;
139
+ RETURN;
140
+ END;
141
+ $$;
142
+ ```
143
+
144
+ ### Key Requirements
145
+
146
+ 1. **Always use `SECURITY DEFINER`** - Functions must run with elevated privileges to bypass RLS. **However**, you must still implement security checks using `rbac_check_permission_simplified()` internally (see Dynamic RLS Integration section above).
147
+
148
+ 2. **Always set `SET search_path TO 'public'`** - Prevents SQL injection attacks
149
+
150
+ 3. **Parameter Naming**:
151
+ - Prefix all parameters with `p_` (e.g., `p_event_id`, `p_user_id`)
152
+ - Use `p_user_id UUID DEFAULT auth.uid()` for user context
153
+ - Use `p_organisation_id UUID DEFAULT NULL` for organisation context
154
+ - Use descriptive names
155
+
156
+ 4. **Variable Naming**:
157
+ - Prefix local variables with `v_` (e.g., `v_is_super_admin`, `v_event_exists`)
158
+ - Use descriptive names
159
+
160
+ 5. **Return Types**:
161
+ - Use `RETURNS TABLE(...)` for functions that return multiple rows
162
+ - Use `RETURNS BOOLEAN` for check/validation functions
163
+ - Use `RETURNS JSON` or `RETURNS JSONB` for complex data
164
+ - Use `RETURNS UUID` or `RETURNS TEXT` for single values
165
+
166
+ 6. **Error Handling**:
167
+ - Always include `EXCEPTION WHEN OTHERS` block
168
+ - Log errors with `RAISE WARNING` or `RAISE NOTICE`
169
+ - Return empty results on error (fail-secure)
170
+
171
+ 7. **Security Checks**:
172
+ - Always check super_admin status first (if applicable)
173
+ - Always use `rbac_check_permission_simplified()` for permission checks (ensures consistency with dynamic RLS)
174
+ - Always validate organisation membership (for non-super-admin)
175
+ - Always validate event access (for event-scoped functions)
176
+ - Never trust client-provided parameters without validation
177
+ - **Important**: Even though RLS is bypassed with `SECURITY DEFINER`, you must implement the same security checks that RLS policies would enforce
178
+
179
+ 8. **Comments**:
180
+ - Add header comment explaining function purpose
181
+ - Document parameters
182
+ - Document return value
183
+ - Document security implications
184
+
185
+ ## Dynamic RLS Integration
186
+
187
+ ### Overview
188
+
189
+ PACE Core uses **Dynamic RLS (Row Level Security)** with RBAC-aware policies. This means RLS policies use `rbac_check_permission_simplified()` to dynamically check permissions based on the current RBAC configuration, rather than hardcoded role checks.
190
+
191
+ ### RPC Functions vs RLS Policies
192
+
193
+ There are **two layers** of security in PACE Core:
194
+
195
+ 1. **RLS Policies** - For direct table access (client-side queries)
196
+ - Use `rbac_check_permission_simplified()` in policy USING clauses
197
+ - Enforce security at the database level for all direct queries
198
+ - Example: `CREATE POLICY ... USING (rbac_check_permission_simplified(...))`
199
+
200
+ 2. **RPC Functions** - For complex operations (server-side logic)
201
+ - Run with `SECURITY DEFINER` which **bypasses RLS**
202
+ - Must implement their own security checks
203
+ - Should use `rbac_check_permission_simplified()` internally for consistency
204
+
205
+ ### Why RPC Functions Bypass RLS
206
+
207
+ RPC functions use `SECURITY DEFINER` which bypasses RLS. This is **intentional and required** for several reasons:
208
+
209
+ #### Technical Reasons
210
+
211
+ 1. **Performance** - RPC functions need to query multiple tables efficiently without RLS overhead on every operation
212
+ 2. **Complex Logic** - Functions perform calculations, aggregations, and joins that RLS policies can't handle elegantly
213
+ 3. **Query Optimization** - Functions can optimize queries for specific use cases without RLS interference
214
+
215
+ #### Security Architecture Reasons
216
+
217
+ 4. **Centralized Security** - Functions implement security checks in one place rather than relying on multiple RLS policies
218
+ 5. **Explicit Control** - Security logic is visible in the function code, not hidden in policy definitions
219
+ 6. **Defense in Depth** - Even if RLS has bugs or is misconfigured, RPC function security checks still apply
220
+ 7. **RLS as Safety Net** - RLS policies provide defense-in-depth for any direct queries, but RPC functions don't rely on them
221
+
222
+ #### Why This Architecture is Better
223
+
224
+ **Problem with RLS-Only Approach:**
225
+ - RLS policies can have bugs (we saw this with `cake_diner` policy)
226
+ - RLS policies can be misconfigured or forgotten
227
+ - Multiple policies on multiple tables = multiple places for bugs
228
+ - Direct queries bypass security if RLS is broken
229
+
230
+ **Solution with RPC Functions:**
231
+ - All security logic in one place (the function)
232
+ - Explicit permission checks that are easy to audit
233
+ - RLS still exists as defense-in-depth (catches bugs)
234
+ - Can't bypass security even if RLS fails
235
+
236
+ **Important**: You must still implement security checks in the RPC function itself. Bypassing RLS doesn't mean bypassing security - it means implementing security explicitly rather than relying on RLS policies.
237
+
238
+ ### Using `rbac_check_permission_simplified()` in RPC Functions
239
+
240
+ RPC functions should use `rbac_check_permission_simplified()` for permission checks to ensure consistency with RLS policies:
241
+
242
+ ```sql
243
+ CREATE OR REPLACE FUNCTION data_cake_meals_get(
244
+ p_event_id TEXT,
245
+ p_user_id UUID DEFAULT auth.uid(),
246
+ p_organisation_id UUID DEFAULT NULL
247
+ )
248
+ RETURNS TABLE(...)
249
+ LANGUAGE plpgsql
250
+ SECURITY DEFINER
251
+ SET search_path TO 'public'
252
+ AS $$
253
+ BEGIN
254
+ -- Super admin check (always first)
255
+ IF EXISTS(SELECT 1 FROM rbac_global_roles WHERE user_id = p_user_id AND role = 'super_admin') THEN
256
+ RETURN QUERY SELECT * FROM cake_meal WHERE meal_event_id = p_event_id;
257
+ RETURN;
258
+ END IF;
259
+
260
+ -- Use rbac_check_permission_simplified for dynamic RBAC checking
261
+ IF NOT rbac_check_permission_simplified(
262
+ p_user_id,
263
+ 'read:meals',
264
+ p_organisation_id,
265
+ p_event_id,
266
+ (SELECT id FROM rbac_apps WHERE name = 'CAKE' LIMIT 1),
267
+ 'meals'
268
+ ) THEN
269
+ RETURN; -- No permission
270
+ END IF;
271
+
272
+ -- Return data (bypasses RLS due to SECURITY DEFINER)
273
+ RETURN QUERY
274
+ SELECT * FROM cake_meal
275
+ WHERE meal_event_id = p_event_id
276
+ AND organisation_id = p_organisation_id;
277
+ END;
278
+ $$;
279
+ ```
280
+
281
+ ### Key Points
282
+
283
+ 1. **Always check permissions** - Even though RLS is bypassed, you must check permissions
284
+ 2. **Use the same function** - Use `rbac_check_permission_simplified()` just like RLS policies do
285
+ 3. **Consistent behavior** - This ensures RPC functions and direct queries have the same security behavior
286
+ 4. **Performance** - `rbac_check_permission_simplified()` is optimized and cached
287
+
288
+ ### RPC-First Architecture (Recommended)
289
+
290
+ **Security Best Practice**: Use RPC functions as the **primary access method** for all database operations. Treat RLS as defense-in-depth, not a primary security control.
291
+
292
+ #### Why RPC Functions Should Be Preferred
293
+
294
+ 1. **Centralized Security Logic**
295
+ - All security checks in one place (the RPC function)
296
+ - Easier to audit and review
297
+ - Consistent behavior across all operations
298
+
299
+ 2. **Avoids RLS Bypass Issues**
300
+ - RLS policies can have bugs, be misconfigured, or be bypassed
301
+ - RPC functions with explicit permission checks are more reliable
302
+ - SECURITY DEFINER functions bypass RLS, but implement their own checks
303
+
304
+ 3. **Better Performance Control**
305
+ - Can optimize queries for specific use cases
306
+ - Can add caching at the function level
307
+ - Can pre-compute complex operations
308
+
309
+ 4. **Audit and Logging**
310
+ - All data access goes through known functions
311
+ - Easier to log and monitor
312
+ - Can add audit trails automatically
313
+
314
+ 5. **Business Logic Enforcement**
315
+ - Complex business rules enforced at database level
316
+ - Data validation and transformations in one place
317
+ - Prevents inconsistent logic across applications
318
+
319
+ #### Current Architecture (Allows Both)
320
+
321
+ Currently, PACE Core supports both RPC functions and direct queries:
322
+
323
+ **Use RPC Functions when:**
324
+ - Complex calculations or aggregations needed
325
+ - Multiple tables need to be joined with complex logic
326
+ - Performance is critical (pre-computed results)
327
+ - Business logic is too complex for RLS policies
328
+ - **Security-sensitive operations** (preferred for all operations)
329
+
330
+ **Use Direct Queries (with RLS) when:**
331
+ - Simple CRUD operations (only if RLS policies are properly configured)
332
+ - Standard filtering and sorting
333
+ - Client-side filtering is sufficient
334
+ - No complex business logic required
335
+ - ⚠️ **Not recommended for production** - Use RPC functions instead
336
+
337
+ #### Recommended Future Architecture: RPC-Only Access
338
+
339
+ For maximum security, consider migrating to an **RPC-only architecture**:
340
+
341
+ 1. **All table access through RPC functions**
342
+ - Create RPC functions for all data operations
343
+ - Ban direct table queries from application code
344
+ - Use RLS as pure defense-in-depth (should never be relied upon)
345
+
346
+ 2. **Benefits of RPC-Only:**
347
+ - **Single point of security enforcement** - All access controlled in functions
348
+ - **Easier auditing** - All access logged through RPC layer
349
+ - **Consistent behavior** - Same security checks for all operations
350
+ - **Easier maintenance** - Security logic in one place
351
+ - **Prevents bugs** - Can't accidentally bypass security by querying directly
352
+
353
+ 3. **RLS as Defense-in-Depth:**
354
+ - RLS policies still exist and provide protection
355
+ - But RPC functions bypass them (SECURITY DEFINER)
356
+ - RLS catches any bugs or unauthorized direct access attempts
357
+ - Acts as a safety net, not primary security
358
+
359
+ 4. **Migration Path:**
360
+ ```
361
+ Phase 1: Create RPC functions for all operations
362
+ Phase 2: Update applications to use RPC functions
363
+ Phase 3: Enable strict mode to log/block direct queries
364
+ Phase 4: Remove direct query access entirely
365
+ ```
366
+
367
+ ### Example: Consistent Security Between RLS and RPC
368
+
369
+ **RLS Policy (for direct queries):**
370
+ ```sql
371
+ CREATE POLICY "rbac_cake_meal_select" ON cake_meal
372
+ FOR SELECT TO authenticated
373
+ USING (
374
+ organisation_id IS NOT NULL
375
+ AND (
376
+ is_user_super_admin()
377
+ OR rbac_check_permission_simplified(
378
+ auth.uid(),
379
+ 'read:meals',
380
+ organisation_id,
381
+ meal_event_id,
382
+ (SELECT id FROM rbac_apps WHERE name = 'CAKE' LIMIT 1),
383
+ 'meals'
384
+ )
385
+ )
386
+ );
387
+ ```
388
+
389
+ **RPC Function (for complex operations):**
390
+ ```sql
391
+ CREATE OR REPLACE FUNCTION data_cake_meals_get(...)
392
+ ...
393
+ BEGIN
394
+ -- Same security check logic as RLS policy
395
+ IF NOT (
396
+ v_is_super_admin OR
397
+ rbac_check_permission_simplified(
398
+ p_user_id,
399
+ 'read:meals',
400
+ p_organisation_id,
401
+ p_event_id,
402
+ (SELECT id FROM rbac_apps WHERE name = 'CAKE' LIMIT 1),
403
+ 'meals'
404
+ )
405
+ ) THEN
406
+ RETURN;
407
+ END IF;
408
+ ...
409
+ END;
410
+ ```
411
+
412
+ Both use the **same permission checking function**, ensuring consistent security behavior.
413
+
414
+ ## Security Best Practices
415
+
416
+ ### 1. Input Validation
417
+
418
+ Always validate inputs:
419
+
420
+ ```sql
421
+ -- Check for NULL required parameters
422
+ IF p_event_id IS NULL THEN
423
+ RETURN;
424
+ END IF;
425
+
426
+ -- Check for invalid values
427
+ IF NOT EXISTS(SELECT 1 FROM event WHERE event_id = p_event_id) THEN
428
+ RETURN;
429
+ END IF;
430
+ ```
431
+
432
+ ### 2. Organisation Context
433
+
434
+ Always require organisation context for non-super-admin operations:
435
+
436
+ ```sql
437
+ -- Get organisation from context if not provided
438
+ IF p_organisation_id IS NULL THEN
439
+ p_organisation_id := current_setting('app.organisation_id', true)::uuid;
440
+ END IF;
441
+
442
+ IF p_organisation_id IS NULL THEN
443
+ RAISE EXCEPTION 'Organisation context is required';
444
+ END IF;
445
+ ```
446
+
447
+ ### 3. Super Admin Bypass
448
+
449
+ Always check for super_admin status first:
450
+
451
+ ```sql
452
+ DECLARE
453
+ v_is_super_admin BOOLEAN;
454
+ BEGIN
455
+ SELECT EXISTS(
456
+ SELECT 1 FROM rbac_global_roles
457
+ WHERE user_id = COALESCE(p_user_id, auth.uid())
458
+ AND role = 'super_admin'
459
+ AND valid_from <= NOW()
460
+ AND (valid_to IS NULL OR valid_to >= NOW())
461
+ ) INTO v_is_super_admin;
462
+
463
+ IF v_is_super_admin THEN
464
+ -- Super admin can access all data
465
+ RETURN QUERY SELECT * FROM table_name;
466
+ RETURN;
467
+ END IF;
468
+
469
+ -- Regular user logic with RBAC checks
470
+ ...
471
+ END;
472
+ ```
473
+
474
+ ### 4. Organisation Membership Validation
475
+
476
+ Always validate organisation membership:
477
+
478
+ ```sql
479
+ IF NOT EXISTS(
480
+ SELECT 1 FROM rbac_organisation_roles
481
+ WHERE user_id = COALESCE(p_user_id, auth.uid())
482
+ AND organisation_id = p_organisation_id
483
+ AND status = 'active'
484
+ AND revoked_at IS NULL
485
+ AND valid_from <= NOW()
486
+ AND (valid_to IS NULL OR valid_to >= NOW())
487
+ ) THEN
488
+ RETURN; -- No access
489
+ END IF;
490
+ ```
491
+
492
+ ### 5. Event Access Validation
493
+
494
+ For event-scoped functions:
495
+
496
+ ```sql
497
+ IF NOT EXISTS(
498
+ SELECT 1 FROM rbac_event_app_roles
499
+ WHERE user_id = COALESCE(p_user_id, auth.uid())
500
+ AND event_id = p_event_id
501
+ AND app_id = (SELECT id FROM rbac_apps WHERE name = 'CAKE' LIMIT 1)
502
+ AND status = 'active'
503
+ ) THEN
504
+ RETURN; -- No event access
505
+ END IF;
506
+ ```
507
+
508
+ ## Performance Considerations
509
+
510
+ ### 1. Use Indexes
511
+
512
+ Ensure queries use indexed columns:
513
+ - `organisation_id`
514
+ - `user_id`
515
+ - `event_id`
516
+ - Foreign key columns
517
+
518
+ ### 2. Limit Result Sets
519
+
520
+ Always use `LIMIT` when appropriate:
521
+
522
+ ```sql
523
+ RETURN QUERY
524
+ SELECT * FROM table_name
525
+ WHERE condition
526
+ ORDER BY column
527
+ LIMIT 1000; -- Prevent large result sets
528
+ ```
529
+
530
+ ### 3. Avoid N+1 Queries
531
+
532
+ Use CTEs (Common Table Expressions) or JOINs instead of subqueries:
533
+
534
+ ```sql
535
+ -- ❌ Bad: N+1 query pattern
536
+ FOR rec IN SELECT id FROM table1 LOOP
537
+ SELECT * INTO v_data FROM table2 WHERE table1_id = rec.id;
538
+ END LOOP;
539
+
540
+ -- ✅ Good: Single query with JOIN
541
+ RETURN QUERY
542
+ SELECT t1.*, t2.*
543
+ FROM table1 t1
544
+ JOIN table2 t2 ON t2.table1_id = t1.id;
545
+ ```
546
+
547
+ ### 4. Cache Expensive Checks
548
+
549
+ For functions called frequently, consider caching super_admin checks:
550
+
551
+ ```sql
552
+ -- Cache super_admin status in function
553
+ DECLARE
554
+ v_is_super_admin BOOLEAN;
555
+ BEGIN
556
+ -- Check once, use multiple times
557
+ SELECT EXISTS(...) INTO v_is_super_admin;
558
+
559
+ IF v_is_super_admin THEN
560
+ -- Fast path
561
+ ELSE
562
+ -- Regular path with RBAC
563
+ END IF;
564
+ END;
565
+ ```
566
+
567
+ ## Testing Requirements
568
+
569
+ ### Unit Tests
570
+
571
+ Functions should be tested with:
572
+
573
+ 1. **Null inputs** - Should handle gracefully
574
+ 2. **Invalid inputs** - Should return empty or error
575
+ 3. **Super admin access** - Should bypass checks
576
+ 4. **Organisation membership** - Should enforce correctly
577
+ 5. **Event access** - Should validate for event-scoped functions
578
+ 6. **Empty results** - Should return empty set, not error
579
+ 7. **Error conditions** - Should fail gracefully
580
+
581
+ ### Example Test
582
+
583
+ ```sql
584
+ -- Test super admin access
585
+ SELECT * FROM data_user_events_get('event-123', 'super-admin-user-id', NULL);
586
+ -- Should return all events
587
+
588
+ -- Test regular user with access
589
+ SELECT * FROM data_user_events_get('event-123', 'regular-user-id', 'org-123');
590
+ -- Should return events user has access to
591
+
592
+ -- Test user without access
593
+ SELECT * FROM data_user_events_get('event-123', 'unauthorized-user-id', 'org-123');
594
+ -- Should return empty
595
+
596
+ -- Test NULL input
597
+ SELECT * FROM data_user_events_get(NULL, 'user-id', 'org-123');
598
+ -- Should return empty gracefully
599
+ ```
600
+
601
+ ## Migration Guidelines
602
+
603
+ ### Creating New Functions
604
+
605
+ 1. **Create migration file**: `YYYYMMDDHHMMSS_descriptive_name.sql`
606
+ 2. **Add function**: Use standard template above
607
+ 3. **Add comments**: Document purpose, parameters, returns
608
+ 4. **Add grants**: `GRANT EXECUTE ON FUNCTION ... TO authenticated;`
609
+ 5. **Add tests**: Include test queries in migration
610
+ 6. **Update types**: Add function name to TypeScript types
611
+
612
+ ### Updating Existing Functions
613
+
614
+ 1. **Use `CREATE OR REPLACE FUNCTION`** - Ensures idempotency
615
+ 2. **Preserve backward compatibility** - Add new parameters with defaults
616
+ 3. **Version changes** - Update function name if breaking changes (e.g., `_v2`)
617
+ 4. **Document changes** - Add comments explaining changes
618
+
619
+ ### Deprecating Functions
620
+
621
+ 1. **Add deprecation notice**:
622
+ ```sql
623
+ COMMENT ON FUNCTION old_function IS 'DEPRECATED: Use new_function instead';
624
+ ```
625
+
626
+ 2. **Keep function** - Don't delete immediately, allow migration period
627
+
628
+ 3. **Update references** - Update all callers before removing
629
+
630
+ ## Common Patterns
631
+
632
+ ### Pattern 1: Simple Data Retrieval
633
+
634
+ ```sql
635
+ CREATE OR REPLACE FUNCTION data_entity_list(
636
+ p_organisation_id UUID,
637
+ p_user_id UUID DEFAULT auth.uid()
638
+ )
639
+ RETURNS TABLE(id UUID, name TEXT)
640
+ LANGUAGE plpgsql
641
+ SECURITY DEFINER
642
+ SET search_path TO 'public'
643
+ AS $$
644
+ BEGIN
645
+ -- Super admin check
646
+ IF EXISTS(SELECT 1 FROM rbac_global_roles WHERE user_id = p_user_id AND role = 'super_admin') THEN
647
+ RETURN QUERY SELECT id, name FROM entity;
648
+ RETURN;
649
+ END IF;
650
+
651
+ -- Organisation check
652
+ IF NOT EXISTS(SELECT 1 FROM rbac_organisation_roles WHERE user_id = p_user_id AND organisation_id = p_organisation_id) THEN
653
+ RETURN;
654
+ END IF;
655
+
656
+ -- Return data
657
+ RETURN QUERY
658
+ SELECT id, name
659
+ FROM entity
660
+ WHERE organisation_id = p_organisation_id;
661
+ END;
662
+ $$;
663
+ ```
664
+
665
+ ### Pattern 2: Permission Check
666
+
667
+ ```sql
668
+ CREATE OR REPLACE FUNCTION rbac_check_permission(
669
+ p_user_id UUID,
670
+ p_permission TEXT,
671
+ p_organisation_id UUID
672
+ )
673
+ RETURNS BOOLEAN
674
+ LANGUAGE plpgsql
675
+ SECURITY DEFINER
676
+ SET search_path TO 'public'
677
+ AS $$
678
+ DECLARE
679
+ v_has_permission BOOLEAN := false;
680
+ BEGIN
681
+ -- Super admin has all permissions
682
+ IF EXISTS(SELECT 1 FROM rbac_global_roles WHERE user_id = p_user_id AND role = 'super_admin') THEN
683
+ RETURN true;
684
+ END IF;
685
+
686
+ -- Check permission
687
+ SELECT EXISTS(
688
+ SELECT 1 FROM rbac_page_permissions pp
689
+ JOIN rbac_organisation_roles ror ON pp.organisation_id = ror.organisation_id
690
+ WHERE ror.user_id = p_user_id
691
+ AND ror.organisation_id = p_organisation_id
692
+ AND pp.operation = split_part(p_permission, ':', 1)
693
+ AND pp.allowed = true
694
+ ) INTO v_has_permission;
695
+
696
+ RETURN COALESCE(v_has_permission, false);
697
+ END;
698
+ $$;
699
+ ```
700
+
701
+ ### Pattern 3: Complex Calculation
702
+
703
+ ```sql
704
+ CREATE OR REPLACE FUNCTION app_cake_distribution_calculate(
705
+ p_event_id TEXT,
706
+ p_user_id UUID DEFAULT auth.uid(),
707
+ p_organisation_id UUID DEFAULT NULL
708
+ )
709
+ RETURNS TABLE(...)
710
+ LANGUAGE plpgsql
711
+ SECURITY DEFINER
712
+ SET search_path TO 'public'
713
+ AS $$
714
+ DECLARE
715
+ v_is_super_admin BOOLEAN;
716
+ BEGIN
717
+ -- Security checks
718
+ ...
719
+
720
+ -- Use CTEs for complex logic
721
+ WITH event_units AS (
722
+ SELECT unit_id FROM cake_unit WHERE unit_event_id = p_event_id
723
+ ),
724
+ unit_diners AS (
725
+ SELECT ... FROM cake_diner WHERE diner_unit_id IN (SELECT unit_id FROM event_units)
726
+ )
727
+ SELECT ...;
728
+ END;
729
+ $$;
730
+ ```
731
+
732
+ ## Summary Checklist
733
+
734
+ When creating or reviewing an RPC function, ensure:
735
+
736
+ - ✅ Follows naming convention: `<family>_<domain>_<verb>`
737
+ - ✅ Uses `SECURITY DEFINER`
738
+ - ✅ Sets `search_path TO 'public'`
739
+ - ✅ Validates all inputs
740
+ - ✅ Checks super_admin status (if applicable)
741
+ - ✅ Validates organisation membership (if applicable)
742
+ - ✅ Validates event access (if event-scoped)
743
+ - ✅ Uses proper error handling
744
+ - ✅ Returns empty on error (fail-secure)
745
+ - ✅ Has documentation comments
746
+ - ✅ Uses descriptive parameter names (`p_` prefix)
747
+ - ✅ Uses descriptive variable names (`v_` prefix)
748
+ - ✅ Has appropriate return type
749
+ - ✅ Includes `GRANT EXECUTE` statement
750
+ - ✅ Tested with various scenarios
751
+
752
+ ## References
753
+
754
+ - [RBAC System Documentation](../core-concepts/rbac-system.md)
755
+ - [Database Schema Requirements](./database-schema-requirements.md)
756
+ - [Security Architecture](./rbac-security-architecture.md)
757
+