@jmruthers/pace-core 0.5.39 → 0.5.41

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 (101) hide show
  1. package/dist/rbac/cli/policy-manager.js +278 -0
  2. package/dist/rbac/cli/policy-manager.js.map +1 -0
  3. package/docs/api/classes/ErrorBoundary.md +1 -1
  4. package/docs/api/classes/InvalidScopeError.md +1 -1
  5. package/docs/api/classes/MissingUserContextError.md +1 -1
  6. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  7. package/docs/api/classes/PermissionDeniedError.md +1 -1
  8. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  9. package/docs/api/classes/RBACAuditManager.md +1 -1
  10. package/docs/api/classes/RBACCache.md +1 -1
  11. package/docs/api/classes/RBACEngine.md +1 -1
  12. package/docs/api/classes/RBACError.md +1 -1
  13. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  14. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  15. package/docs/api/interfaces/AggregateConfig.md +1 -1
  16. package/docs/api/interfaces/ButtonProps.md +1 -1
  17. package/docs/api/interfaces/CardProps.md +1 -1
  18. package/docs/api/interfaces/ColorPalette.md +1 -1
  19. package/docs/api/interfaces/ColorShade.md +1 -1
  20. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  21. package/docs/api/interfaces/DataTableAction.md +1 -1
  22. package/docs/api/interfaces/DataTableColumn.md +1 -1
  23. package/docs/api/interfaces/DataTableProps.md +1 -1
  24. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  25. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  26. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  27. package/docs/api/interfaces/EventContextType.md +1 -1
  28. package/docs/api/interfaces/EventLogoProps.md +1 -1
  29. package/docs/api/interfaces/EventProviderProps.md +1 -1
  30. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  31. package/docs/api/interfaces/FileUploadProps.md +1 -1
  32. package/docs/api/interfaces/FooterProps.md +1 -1
  33. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  34. package/docs/api/interfaces/InputProps.md +1 -1
  35. package/docs/api/interfaces/LabelProps.md +1 -1
  36. package/docs/api/interfaces/LoginFormProps.md +1 -1
  37. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  38. package/docs/api/interfaces/NavigationContextType.md +1 -1
  39. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  40. package/docs/api/interfaces/NavigationItem.md +1 -1
  41. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  42. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  43. package/docs/api/interfaces/Organisation.md +1 -1
  44. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  45. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  46. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  47. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  48. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  49. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  50. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  51. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  52. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  53. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  54. package/docs/api/interfaces/PaletteData.md +1 -1
  55. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  56. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  57. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  58. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  59. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  60. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  61. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  62. package/docs/api/interfaces/RBACConfig.md +1 -1
  63. package/docs/api/interfaces/RBACContextType.md +1 -1
  64. package/docs/api/interfaces/RBACLogger.md +1 -1
  65. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  66. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  67. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  68. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  69. package/docs/api/interfaces/RouteConfig.md +1 -1
  70. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  71. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  72. package/docs/api/interfaces/StorageConfig.md +1 -1
  73. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  74. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  75. package/docs/api/interfaces/StorageListOptions.md +1 -1
  76. package/docs/api/interfaces/StorageListResult.md +1 -1
  77. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  78. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  79. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  80. package/docs/api/interfaces/StyleImport.md +1 -1
  81. package/docs/api/interfaces/ToastActionElement.md +1 -1
  82. package/docs/api/interfaces/ToastProps.md +1 -1
  83. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  84. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  85. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  86. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  87. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  88. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  89. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  90. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  91. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  92. package/docs/api/interfaces/UserEventAccess.md +1 -1
  93. package/docs/api/interfaces/UserMenuProps.md +1 -1
  94. package/docs/api/interfaces/UserProfile.md +1 -1
  95. package/docs/api/modules.md +2 -2
  96. package/docs/implementation-guides/data-tables.md +189 -0
  97. package/docs/rbac/README-rbac-rls-integration.md +358 -0
  98. package/docs/rbac/examples/rbac-rls-integration-example.md +332 -0
  99. package/docs/rbac/rbac-rls-integration.md +377 -0
  100. package/package.json +19 -3
  101. package/src/rbac/cli/policy-manager.ts +443 -0
@@ -0,0 +1,443 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * RBAC Policy Manager CLI Tool
5
+ *
6
+ * This tool provides command-line interface for managing RBAC-RLS integration policies.
7
+ * It allows developers and administrators to easily manage dynamic permission policies.
8
+ */
9
+
10
+ import { createClient } from '@supabase/supabase-js';
11
+ import { Command } from 'commander';
12
+ import chalk from 'chalk';
13
+ import ora from 'ora';
14
+ import Table from 'cli-table3';
15
+
16
+ interface PolicyConfig {
17
+ table_name: string;
18
+ page_name: string;
19
+ app_name: string;
20
+ organisation_column: string;
21
+ event_column?: string;
22
+ operations: string[];
23
+ is_active: boolean;
24
+ }
25
+
26
+ interface PolicyAudit {
27
+ table_name: string;
28
+ policy_name: string;
29
+ operation: string;
30
+ action: string;
31
+ changed_at: string;
32
+ success: boolean;
33
+ error_message?: string;
34
+ }
35
+
36
+ interface HealthCheck {
37
+ table_name: string;
38
+ policy_name: string;
39
+ operation: string;
40
+ is_healthy: boolean;
41
+ issues: string[];
42
+ }
43
+
44
+ class PolicyManager {
45
+ private supabase: any;
46
+
47
+ constructor(url: string, key: string) {
48
+ this.supabase = createClient(url, key);
49
+ }
50
+
51
+ /**
52
+ * List all registered tables
53
+ */
54
+ async listTables(): Promise<void> {
55
+ const spinner = ora('Fetching registered tables...').start();
56
+
57
+ try {
58
+ const { data, error } = await this.supabase
59
+ .from('rbac_policy_configs')
60
+ .select('*')
61
+ .eq('is_active', true)
62
+ .order('table_name');
63
+
64
+ if (error) throw error;
65
+
66
+ spinner.succeed('Fetched registered tables');
67
+
68
+ if (data.length === 0) {
69
+ console.log(chalk.yellow('No tables registered for RBAC policy management.'));
70
+ return;
71
+ }
72
+
73
+ const table = new Table({
74
+ head: ['Table', 'Page', 'App', 'Org Column', 'Event Column', 'Operations'],
75
+ colWidths: [20, 15, 10, 15, 15, 30]
76
+ });
77
+
78
+ data.forEach((config: PolicyConfig) => {
79
+ table.push([
80
+ config.table_name,
81
+ config.page_name,
82
+ config.app_name,
83
+ config.organisation_column,
84
+ config.event_column || 'N/A',
85
+ config.operations.join(', ')
86
+ ]);
87
+ });
88
+
89
+ console.log(table.toString());
90
+ } catch (error) {
91
+ spinner.fail('Failed to fetch tables');
92
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Register a new table for RBAC policy management
98
+ */
99
+ async registerTable(
100
+ tableName: string,
101
+ pageName: string,
102
+ appName: string = 'CAKE',
103
+ orgColumn: string = 'organisation_id',
104
+ eventColumn?: string,
105
+ operations: string[] = ['read', 'create', 'update', 'delete']
106
+ ): Promise<void> {
107
+ const spinner = ora(`Registering table ${tableName}...`).start();
108
+
109
+ try {
110
+ const { data, error } = await this.supabase
111
+ .rpc('register_rbac_table', {
112
+ p_table_name: tableName,
113
+ p_page_name: pageName,
114
+ p_app_name: appName,
115
+ p_organisation_column: orgColumn,
116
+ p_event_column: eventColumn,
117
+ p_operations: operations
118
+ });
119
+
120
+ if (error) throw error;
121
+
122
+ if (data) {
123
+ spinner.succeed(`Successfully registered table ${tableName}`);
124
+ } else {
125
+ spinner.fail(`Failed to register table ${tableName}`);
126
+ }
127
+ } catch (error) {
128
+ spinner.fail(`Failed to register table ${tableName}`);
129
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Update policies for a specific table
135
+ */
136
+ async updateTable(tableName: string, pageName: string, appName: string = 'CAKE'): Promise<void> {
137
+ const spinner = ora(`Updating policies for ${tableName}...`).start();
138
+
139
+ try {
140
+ const { data, error } = await this.supabase
141
+ .rpc('update_rbac_policies_for_table', {
142
+ p_table_name: tableName,
143
+ p_page_name: pageName,
144
+ p_app_name: appName
145
+ });
146
+
147
+ if (error) throw error;
148
+
149
+ if (data) {
150
+ spinner.succeed(`Successfully updated policies for ${tableName}`);
151
+ } else {
152
+ spinner.fail(`Failed to update policies for ${tableName}`);
153
+ }
154
+ } catch (error) {
155
+ spinner.fail(`Failed to update policies for ${tableName}`);
156
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Update all RBAC policies
162
+ */
163
+ async updateAll(): Promise<void> {
164
+ const spinner = ora('Updating all RBAC policies...').start();
165
+
166
+ try {
167
+ const { data, error } = await this.supabase
168
+ .rpc('update_all_rbac_policies');
169
+
170
+ if (error) throw error;
171
+
172
+ spinner.succeed('Updated all RBAC policies');
173
+
174
+ // Display results
175
+ const table = new Table({
176
+ head: ['Table', 'Page', 'App', 'Success', 'Error'],
177
+ colWidths: [20, 15, 10, 10, 30]
178
+ });
179
+
180
+ data.forEach((result: any) => {
181
+ table.push([
182
+ result.table_name,
183
+ result.page_name,
184
+ result.app_name,
185
+ result.success ? chalk.green('✓') : chalk.red('✗'),
186
+ result.error_message || ''
187
+ ]);
188
+ });
189
+
190
+ console.log(table.toString());
191
+ } catch (error) {
192
+ spinner.fail('Failed to update all policies');
193
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Check policy health
199
+ */
200
+ async checkHealth(): Promise<void> {
201
+ const spinner = ora('Checking policy health...').start();
202
+
203
+ try {
204
+ const { data, error } = await this.supabase
205
+ .rpc('check_rbac_policy_health');
206
+
207
+ if (error) throw error;
208
+
209
+ spinner.succeed('Policy health check complete');
210
+
211
+ if (data.length === 0) {
212
+ console.log(chalk.yellow('No policies found.'));
213
+ return;
214
+ }
215
+
216
+ const healthyPolicies = data.filter((h: HealthCheck) => h.is_healthy);
217
+ const unhealthyPolicies = data.filter((h: HealthCheck) => !h.is_healthy);
218
+
219
+ console.log(chalk.green(`✓ ${healthyPolicies.length} healthy policies`));
220
+ console.log(chalk.red(`✗ ${unhealthyPolicies.length} unhealthy policies`));
221
+
222
+ if (unhealthyPolicies.length > 0) {
223
+ console.log('\n' + chalk.red('Unhealthy Policies:'));
224
+ const table = new Table({
225
+ head: ['Table', 'Policy', 'Operation', 'Issues'],
226
+ colWidths: [20, 25, 10, 40]
227
+ });
228
+
229
+ unhealthyPolicies.forEach((policy: HealthCheck) => {
230
+ table.push([
231
+ policy.table_name,
232
+ policy.policy_name,
233
+ policy.operation,
234
+ policy.issues.join(', ')
235
+ ]);
236
+ });
237
+
238
+ console.log(table.toString());
239
+ }
240
+ } catch (error) {
241
+ spinner.fail('Failed to check policy health');
242
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Show policy audit log
248
+ */
249
+ async showAudit(limit: number = 20): Promise<void> {
250
+ const spinner = ora('Fetching audit log...').start();
251
+
252
+ try {
253
+ const { data, error } = await this.supabase
254
+ .from('rbac_policy_audit')
255
+ .select('*')
256
+ .order('changed_at', { ascending: false })
257
+ .limit(limit);
258
+
259
+ if (error) throw error;
260
+
261
+ spinner.succeed('Fetched audit log');
262
+
263
+ if (data.length === 0) {
264
+ console.log(chalk.yellow('No audit entries found.'));
265
+ return;
266
+ }
267
+
268
+ const table = new Table({
269
+ head: ['Table', 'Policy', 'Operation', 'Action', 'Success', 'Changed At'],
270
+ colWidths: [15, 20, 10, 10, 8, 20]
271
+ });
272
+
273
+ data.forEach((audit: PolicyAudit) => {
274
+ table.push([
275
+ audit.table_name,
276
+ audit.policy_name,
277
+ audit.operation,
278
+ audit.action,
279
+ audit.success ? chalk.green('✓') : chalk.red('✗'),
280
+ new Date(audit.changed_at).toLocaleString()
281
+ ]);
282
+ });
283
+
284
+ console.log(table.toString());
285
+ } catch (error) {
286
+ spinner.fail('Failed to fetch audit log');
287
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Test permission check
293
+ */
294
+ async testPermission(
295
+ operation: string,
296
+ pageName: string,
297
+ orgId: string,
298
+ eventId?: string,
299
+ appName: string = 'CAKE'
300
+ ): Promise<void> {
301
+ const spinner = ora('Testing permission check...').start();
302
+
303
+ try {
304
+ const { data, error } = await this.supabase
305
+ .rpc('check_rbac_permission_with_context', {
306
+ p_operation: operation,
307
+ p_page_name: pageName,
308
+ p_resource_organisation_id: orgId,
309
+ p_resource_event_id: eventId,
310
+ p_app_name: appName
311
+ });
312
+
313
+ if (error) throw error;
314
+
315
+ spinner.succeed('Permission check complete');
316
+
317
+ const result = data ? chalk.green('ALLOWED') : chalk.red('DENIED');
318
+ console.log(`Permission: ${operation} on ${pageName} = ${result}`);
319
+ } catch (error) {
320
+ spinner.fail('Failed to test permission');
321
+ console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));
322
+ }
323
+ }
324
+ }
325
+
326
+ // CLI Setup
327
+ const program = new Command();
328
+
329
+ program
330
+ .name('rbac-policy-manager')
331
+ .description('CLI tool for managing RBAC-RLS integration policies')
332
+ .version('1.0.0');
333
+
334
+ // Global options
335
+ program
336
+ .option('-u, --url <url>', 'Supabase URL', process.env.SUPABASE_URL)
337
+ .option('-k, --key <key>', 'Supabase service key', process.env.SUPABASE_SERVICE_ROLE_KEY)
338
+ .option('--verbose', 'Enable verbose output');
339
+
340
+ // List tables command
341
+ program
342
+ .command('list')
343
+ .description('List all registered tables')
344
+ .action(async (options) => {
345
+ const url = options.parent?.url || process.env.SUPABASE_URL;
346
+ const key = options.parent?.key || process.env.SUPABASE_SERVICE_ROLE_KEY;
347
+ const manager = new PolicyManager(url, key);
348
+ await manager.listTables();
349
+ });
350
+
351
+ // Register table command
352
+ program
353
+ .command('register <tableName> <pageName>')
354
+ .description('Register a table for RBAC policy management')
355
+ .option('-a, --app <appName>', 'App name', 'CAKE')
356
+ .option('-o, --org-column <column>', 'Organisation column name', 'organisation_id')
357
+ .option('-e, --event-column <column>', 'Event column name')
358
+ .option('--operations <operations>', 'Comma-separated operations', 'read,create,update,delete')
359
+ .action(async (tableName, pageName, options) => {
360
+ const operations = options.operations.split(',').map((op: string) => op.trim());
361
+ const url = options.parent?.url || process.env.SUPABASE_URL;
362
+ const key = options.parent?.key || process.env.SUPABASE_SERVICE_ROLE_KEY;
363
+ const manager = new PolicyManager(url, key);
364
+ await manager.registerTable(
365
+ tableName,
366
+ pageName,
367
+ options.app,
368
+ options.orgColumn,
369
+ options.eventColumn,
370
+ operations
371
+ );
372
+ });
373
+
374
+ // Update table command
375
+ program
376
+ .command('update <tableName> <pageName>')
377
+ .description('Update policies for a specific table')
378
+ .option('-a, --app <appName>', 'App name', 'CAKE')
379
+ .action(async (tableName, pageName, options) => {
380
+ const url = options.parent?.url || process.env.SUPABASE_URL;
381
+ const key = options.parent?.key || process.env.SUPABASE_SERVICE_ROLE_KEY;
382
+ const manager = new PolicyManager(url, key);
383
+ await manager.updateTable(tableName, pageName, options.app);
384
+ });
385
+
386
+ // Update all command
387
+ program
388
+ .command('update-all')
389
+ .description('Update all RBAC policies')
390
+ .action(async (options) => {
391
+ const url = options.url || process.env.SUPABASE_URL;
392
+ const key = options.key || process.env.SUPABASE_SERVICE_ROLE_KEY;
393
+ const manager = new PolicyManager(url, key);
394
+ await manager.updateAll();
395
+ });
396
+
397
+ // Health check command
398
+ program
399
+ .command('health')
400
+ .description('Check policy health')
401
+ .action(async (options) => {
402
+ const url = options.url || process.env.SUPABASE_URL;
403
+ const key = options.key || process.env.SUPABASE_SERVICE_ROLE_KEY;
404
+ const manager = new PolicyManager(url, key);
405
+ await manager.checkHealth();
406
+ });
407
+
408
+ // Audit command
409
+ program
410
+ .command('audit')
411
+ .description('Show policy audit log')
412
+ .option('-l, --limit <number>', 'Number of entries to show', '20')
413
+ .action(async (options) => {
414
+ const url = options.parent?.url || process.env.SUPABASE_URL;
415
+ const key = options.parent?.key || process.env.SUPABASE_SERVICE_ROLE_KEY;
416
+ const manager = new PolicyManager(url, key);
417
+ await manager.showAudit(parseInt(options.limit));
418
+ });
419
+
420
+ // Test permission command
421
+ program
422
+ .command('test <operation> <pageName> <orgId>')
423
+ .description('Test a permission check')
424
+ .option('-e, --event-id <eventId>', 'Event ID')
425
+ .option('-a, --app <appName>', 'App name', 'CAKE')
426
+ .action(async (operation, pageName, orgId, options) => {
427
+ const url = options.parent?.url || process.env.SUPABASE_URL;
428
+ const key = options.parent?.key || process.env.SUPABASE_SERVICE_ROLE_KEY;
429
+ const manager = new PolicyManager(url, key);
430
+ await manager.testPermission(operation, pageName, orgId, options.eventId, options.app);
431
+ });
432
+
433
+ // Parse command line arguments
434
+ program.parse(process.argv);
435
+
436
+ // Handle missing required options
437
+ if (!process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_ROLE_KEY) {
438
+ console.error(chalk.red('Error: Missing required environment variables'));
439
+ console.error('Please set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY');
440
+ process.exit(1);
441
+ }
442
+
443
+ export { PolicyManager };