@jmruthers/pace-core 0.5.141 → 0.5.143
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/dist/{DataTable-EGIN2NKK.js → DataTable-SKCX4SCB.js} +6 -6
- package/dist/{UnifiedAuthProvider-XIQQ7LVU.js → UnifiedAuthProvider-BMJAP6Z7.js} +3 -3
- package/dist/{chunk-22WKWKRX.js → chunk-2AKRP5QZ.js} +4 -4
- package/dist/{chunk-4C7EXCAR.js → chunk-CRGFNQ2L.js} +4 -4
- package/dist/{chunk-WKTQM2IC.js → chunk-E6ZCVF4T.js} +4 -4
- package/dist/{chunk-INQLMHPF.js → chunk-ERGKJX4D.js} +2 -2
- package/dist/{chunk-6LAAY47Q.js → chunk-MSHEVJXS.js} +2 -2
- package/dist/{chunk-MA6EPSGZ.js → chunk-PKW27QVS.js} +2 -2
- package/dist/{chunk-T6JN6LH6.js → chunk-R53TUSFK.js} +3 -3
- package/dist/{chunk-PZV3XZKJ.js → chunk-SFVL7ZFI.js} +5 -5
- package/dist/{chunk-3R472UXR.js → chunk-VOJBGZYI.js} +3 -3
- package/dist/{chunk-ALUN6O3G.js → chunk-VP44VQJ6.js} +25 -14
- package/dist/chunk-VP44VQJ6.js.map +1 -0
- package/dist/{chunk-YCWDTTUK.js → chunk-WM26XK7I.js} +22 -8
- package/dist/chunk-WM26XK7I.js.map +1 -0
- package/dist/components.js +8 -8
- package/dist/hooks.js +7 -7
- package/dist/index.js +11 -11
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +7 -7
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +2 -2
- package/docs/rbac/README.md +11 -5
- package/docs/rbac/event-based-apps.md +872 -0
- package/package.json +1 -1
- package/src/components/NavigationMenu/NavigationMenu.tsx +32 -7
- package/src/services/EventService.ts +29 -8
- package/src/services/__tests__/EventService.test.ts +48 -8
- package/dist/chunk-ALUN6O3G.js.map +0 -1
- package/dist/chunk-YCWDTTUK.js.map +0 -1
- package/src/rbac/docs/event-based-apps.md +0 -285
- /package/dist/{DataTable-EGIN2NKK.js.map → DataTable-SKCX4SCB.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-XIQQ7LVU.js.map → UnifiedAuthProvider-BMJAP6Z7.js.map} +0 -0
- /package/dist/{chunk-22WKWKRX.js.map → chunk-2AKRP5QZ.js.map} +0 -0
- /package/dist/{chunk-4C7EXCAR.js.map → chunk-CRGFNQ2L.js.map} +0 -0
- /package/dist/{chunk-WKTQM2IC.js.map → chunk-E6ZCVF4T.js.map} +0 -0
- /package/dist/{chunk-INQLMHPF.js.map → chunk-ERGKJX4D.js.map} +0 -0
- /package/dist/{chunk-6LAAY47Q.js.map → chunk-MSHEVJXS.js.map} +0 -0
- /package/dist/{chunk-MA6EPSGZ.js.map → chunk-PKW27QVS.js.map} +0 -0
- /package/dist/{chunk-T6JN6LH6.js.map → chunk-R53TUSFK.js.map} +0 -0
- /package/dist/{chunk-PZV3XZKJ.js.map → chunk-SFVL7ZFI.js.map} +0 -0
- /package/dist/{chunk-3R472UXR.js.map → chunk-VOJBGZYI.js.map} +0 -0
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
---
|
|
2
|
+
lastUpdated: 2025-01-27T00:00:00+00:00
|
|
3
|
+
version: 0.5.76
|
|
4
|
+
reviewedBy: content-audit
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Event-Based Apps with RBAC
|
|
8
|
+
|
|
9
|
+
> **📚 RBAC System** | [← Back to Documentation](../../README.md) | [RBAC Overview](./README.md) | [Organisation-Based Quick Start](./quick-start.md)
|
|
10
|
+
|
|
11
|
+
> **⚠️ CRITICAL**: This guide is designed to be impossible to get wrong. Follow every step exactly as written, and your event-based RBAC system will work perfectly.
|
|
12
|
+
|
|
13
|
+
Build your first event-based RBAC-enabled application in under 10 minutes.
|
|
14
|
+
|
|
15
|
+
## 🎯 What We'll Build
|
|
16
|
+
|
|
17
|
+
A simple event management app that demonstrates:
|
|
18
|
+
- Event-based permission checking
|
|
19
|
+
- Automatic organisation resolution from event context
|
|
20
|
+
- Page-level access control for event-based apps
|
|
21
|
+
- Proper provider setup for event context
|
|
22
|
+
- Event selection and context management
|
|
23
|
+
|
|
24
|
+
## 🚨 Critical Rules (Follow These or It Won't Work)
|
|
25
|
+
|
|
26
|
+
1. **ALWAYS call `setupRBAC()` first** - This is MANDATORY and must be called before any RBAC features
|
|
27
|
+
2. **Never make direct database queries** to `rbac_apps`, `rbac_global_roles`, or other RBAC tables
|
|
28
|
+
3. **ALWAYS use `PagePermissionGuard`** for page-level permissions - This is the ONLY way to properly protect pages
|
|
29
|
+
4. **ALWAYS set up providers correctly** in the exact order shown below
|
|
30
|
+
5. **Use the exact app name** from your environment variable (must match database exactly, case-sensitive)
|
|
31
|
+
6. **Ensure database setup is complete** before starting the app - App must be registered with `requires_event = true`
|
|
32
|
+
7. **User must have organisation role** - Users need roles assigned in `rbac_organisation_roles` table
|
|
33
|
+
8. **Event must be selected** - `selectedEventId` must be set in UnifiedAuth context before permission checks
|
|
34
|
+
9. **Event must belong to organisation** - The event's `organisation_id` must match the user's organisation
|
|
35
|
+
|
|
36
|
+
## 🚀 Step-by-Step Implementation
|
|
37
|
+
|
|
38
|
+
### 1. Project Setup
|
|
39
|
+
|
|
40
|
+
If you don't have a project yet:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx create-react-app event-manager --template typescript
|
|
44
|
+
cd event-manager
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Install Dependencies
|
|
48
|
+
|
|
49
|
+
**CRITICAL**: Use the exact versions specified to avoid compatibility issues.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @jmruthers/pace-core@^0.4.1 @supabase/supabase-js@^2.38.0
|
|
53
|
+
npm install -D tailwindcss @types/react @types/react-dom
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Configure Tailwind CSS v4
|
|
57
|
+
|
|
58
|
+
Install Tailwind CSS v4:
|
|
59
|
+
```bash
|
|
60
|
+
npm install -D @tailwindcss/vite tailwindcss@^4.0.0
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Update `vite.config.ts`:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// vite.config.ts
|
|
67
|
+
import { defineConfig } from 'vite'
|
|
68
|
+
import react from '@vitejs/plugin-react'
|
|
69
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
70
|
+
|
|
71
|
+
export default defineConfig({
|
|
72
|
+
plugins: [
|
|
73
|
+
react(),
|
|
74
|
+
tailwindcss({
|
|
75
|
+
// CRITICAL: Include pace-core components for scanning
|
|
76
|
+
content: [
|
|
77
|
+
'./src/**/*.{js,ts,jsx,tsx}',
|
|
78
|
+
'./node_modules/@jmruthers/pace-core/**/*.{js,ts,jsx,tsx}'
|
|
79
|
+
]
|
|
80
|
+
})
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 4. Set Up Environment Variables
|
|
86
|
+
|
|
87
|
+
Create `.env.local`:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# .env.local
|
|
91
|
+
VITE_SUPABASE_URL=https://your-project.supabase.co
|
|
92
|
+
VITE_SUPABASE_ANON_KEY=your-anon-key-here
|
|
93
|
+
VITE_APP_NAME=pace-trac
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**CRITICAL**:
|
|
97
|
+
- Use `VITE_` prefix for Vite projects
|
|
98
|
+
- Use `NEXT_PUBLIC_` prefix for Next.js projects
|
|
99
|
+
- The `VITE_APP_NAME` must match exactly what you have in the `rbac_apps` table
|
|
100
|
+
|
|
101
|
+
### 5. Database Setup (CRITICAL)
|
|
102
|
+
|
|
103
|
+
**CRITICAL**: Your app must be registered in the database as an event-based app before you can use RBAC.
|
|
104
|
+
|
|
105
|
+
#### 5.1 Register Your App as Event-Based
|
|
106
|
+
|
|
107
|
+
Run this SQL in your Supabase SQL editor:
|
|
108
|
+
|
|
109
|
+
```sql
|
|
110
|
+
-- Replace 'pace-trac' with your actual app name (must match VITE_APP_NAME)
|
|
111
|
+
-- CRITICAL: requires_event = true makes this an event-based app
|
|
112
|
+
INSERT INTO rbac_apps (name, display_name, requires_event, is_active)
|
|
113
|
+
VALUES ('pace-trac', 'PACE Trac', true, true)
|
|
114
|
+
ON CONFLICT (name) DO UPDATE SET
|
|
115
|
+
display_name = EXCLUDED.display_name,
|
|
116
|
+
requires_event = EXCLUDED.requires_event, -- CRITICAL: Must be true for event-based apps
|
|
117
|
+
is_active = EXCLUDED.is_active;
|
|
118
|
+
|
|
119
|
+
-- Verify the app is registered correctly
|
|
120
|
+
SELECT id, name, display_name, requires_event, is_active
|
|
121
|
+
FROM rbac_apps
|
|
122
|
+
WHERE name = 'pace-trac';
|
|
123
|
+
-- Should return: requires_event = true, is_active = true
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### 5.2 Create App Pages
|
|
127
|
+
|
|
128
|
+
```sql
|
|
129
|
+
-- Create pages for your event-based app (replace 'pace-trac' with your actual app name)
|
|
130
|
+
INSERT INTO rbac_app_pages (app_id, page_name, page_description)
|
|
131
|
+
SELECT
|
|
132
|
+
a.id,
|
|
133
|
+
unnest(ARRAY['dashboard', 'participants', 'settings', 'reports']),
|
|
134
|
+
unnest(ARRAY['Event Dashboard', 'Participant Management', 'Event Settings', 'Event Reports'])
|
|
135
|
+
FROM rbac_apps a
|
|
136
|
+
WHERE a.name = 'pace-trac';
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### 5.3 Set Up Page Permissions
|
|
140
|
+
|
|
141
|
+
**CRITICAL**: Page permissions use the format `{operation}:page.{pageName}` (e.g., `read:page.dashboard`). For event-based apps, permissions are checked against the organisation resolved from the event.
|
|
142
|
+
|
|
143
|
+
```sql
|
|
144
|
+
-- Set up basic permissions for each page (replace 'pace-trac' with your actual app name)
|
|
145
|
+
-- IMPORTANT: Replace the organisation_id UUID with your actual organisation ID
|
|
146
|
+
WITH app_pages AS (
|
|
147
|
+
SELECT ap.id as page_id, ap.page_name, a.id as app_id
|
|
148
|
+
FROM rbac_app_pages ap
|
|
149
|
+
JOIN rbac_apps a ON ap.app_id = a.id
|
|
150
|
+
WHERE a.name = 'pace-trac'
|
|
151
|
+
)
|
|
152
|
+
INSERT INTO rbac_page_permissions (app_page_id, operation, role_name, allowed, organisation_id)
|
|
153
|
+
SELECT
|
|
154
|
+
ap.page_id,
|
|
155
|
+
op.operation,
|
|
156
|
+
role.role_name,
|
|
157
|
+
CASE
|
|
158
|
+
WHEN role.role_name = 'org_admin' THEN true
|
|
159
|
+
WHEN role.role_name = 'leader' AND ap.page_name != 'reports' THEN true
|
|
160
|
+
WHEN role.role_name = 'member' AND ap.page_name IN ('dashboard', 'participants') THEN true
|
|
161
|
+
ELSE false
|
|
162
|
+
END,
|
|
163
|
+
'00000000-0000-0000-0000-000000000000'::uuid -- ⚠️ REPLACE THIS with your actual organisation ID
|
|
164
|
+
FROM app_pages ap
|
|
165
|
+
CROSS JOIN (SELECT unnest(ARRAY['read', 'create', 'update', 'delete']) as operation) op
|
|
166
|
+
CROSS JOIN (SELECT unnest(ARRAY['org_admin', 'leader', 'member']) as role_name) role;
|
|
167
|
+
|
|
168
|
+
-- Verify permissions were created correctly
|
|
169
|
+
SELECT
|
|
170
|
+
a.name as app_name,
|
|
171
|
+
ap.page_name,
|
|
172
|
+
pp.operation,
|
|
173
|
+
pp.role_name,
|
|
174
|
+
pp.allowed,
|
|
175
|
+
pp.organisation_id
|
|
176
|
+
FROM rbac_page_permissions pp
|
|
177
|
+
JOIN rbac_app_pages ap ON pp.app_page_id = ap.id
|
|
178
|
+
JOIN rbac_apps a ON ap.app_id = a.id
|
|
179
|
+
WHERE a.name = 'pace-trac'
|
|
180
|
+
ORDER BY ap.page_name, pp.operation, pp.role_name;
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### 5.4 Create a Test Event
|
|
184
|
+
|
|
185
|
+
**CRITICAL**: For event-based apps, you need at least one event that belongs to your organisation.
|
|
186
|
+
|
|
187
|
+
```sql
|
|
188
|
+
-- Create a test event (replace with your actual organisation_id)
|
|
189
|
+
INSERT INTO event (event_name, event_code, organisation_id, event_date, is_active)
|
|
190
|
+
VALUES (
|
|
191
|
+
'Test Event 2025',
|
|
192
|
+
'TEST2025',
|
|
193
|
+
'00000000-0000-0000-0000-000000000000'::uuid, -- ⚠️ REPLACE THIS with your actual organisation ID
|
|
194
|
+
CURRENT_DATE + INTERVAL '30 days',
|
|
195
|
+
true
|
|
196
|
+
)
|
|
197
|
+
RETURNING event_id, event_name, event_code, organisation_id;
|
|
198
|
+
-- Save the event_id - you'll need it for testing
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### 5.5 Assign User Roles
|
|
202
|
+
|
|
203
|
+
```sql
|
|
204
|
+
-- Grant a user the org_admin role (replace with actual user and organisation IDs)
|
|
205
|
+
INSERT INTO rbac_organisation_roles (user_id, organisation_id, role, status, granted_at)
|
|
206
|
+
VALUES (
|
|
207
|
+
'your-user-id'::uuid,
|
|
208
|
+
'your-organisation-id'::uuid,
|
|
209
|
+
'org_admin',
|
|
210
|
+
'active',
|
|
211
|
+
NOW()
|
|
212
|
+
)
|
|
213
|
+
ON CONFLICT (user_id, organisation_id) DO UPDATE SET
|
|
214
|
+
role = EXCLUDED.role,
|
|
215
|
+
status = EXCLUDED.status,
|
|
216
|
+
updated_at = NOW();
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 6. Create Supabase Client
|
|
220
|
+
|
|
221
|
+
Create `src/lib/supabase.ts`:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// src/lib/supabase.ts
|
|
225
|
+
import { createClient } from '@supabase/supabase-js'
|
|
226
|
+
|
|
227
|
+
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
|
228
|
+
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
|
|
229
|
+
|
|
230
|
+
if (!supabaseUrl || !supabaseAnonKey) {
|
|
231
|
+
throw new Error('Missing Supabase environment variables')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 7. Initialize RBAC (MANDATORY!)
|
|
238
|
+
|
|
239
|
+
**CRITICAL**: You MUST call `setupRBAC()` before using any RBAC features. This configures rate limiting (default: 1000 requests/minute) and enables security validation.
|
|
240
|
+
|
|
241
|
+
**Option 1: Setup in main entry point (Recommended)**
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// src/main.tsx
|
|
245
|
+
import React from 'react'
|
|
246
|
+
import ReactDOM from 'react-dom/client'
|
|
247
|
+
import { setupRBAC } from '@jmruthers/pace-core/rbac'
|
|
248
|
+
import { supabase } from './lib/supabase'
|
|
249
|
+
import App from './App'
|
|
250
|
+
|
|
251
|
+
// ⚠️ CRITICAL: Call setupRBAC BEFORE rendering App
|
|
252
|
+
setupRBAC(supabase)
|
|
253
|
+
|
|
254
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
255
|
+
<React.StrictMode>
|
|
256
|
+
<App />
|
|
257
|
+
</React.StrictMode>
|
|
258
|
+
)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Option 2: Setup in separate file (Alternative)**
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// src/lib/rbac-setup.ts
|
|
265
|
+
import { setupRBAC } from '@jmruthers/pace-core/rbac'
|
|
266
|
+
import { supabase } from './supabase'
|
|
267
|
+
|
|
268
|
+
// ⚠️ REQUIRED: Initialize RBAC before using any RBAC features
|
|
269
|
+
setupRBAC(supabase, {
|
|
270
|
+
// Optional: Configure rate limiting for high-traffic apps
|
|
271
|
+
security: {
|
|
272
|
+
maxPermissionChecksPerMinute: 2000 // Increase from default 1000 if needed
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// src/main.tsx
|
|
277
|
+
import './lib/rbac-setup' // Import BEFORE App
|
|
278
|
+
import App from './App'
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 8. App Setup (CRITICAL - Event-Based Specific!)
|
|
282
|
+
|
|
283
|
+
Create `src/App.tsx` with this EXACT structure for event-based apps:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// src/App.tsx
|
|
287
|
+
import React from 'react'
|
|
288
|
+
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
|
|
289
|
+
import {
|
|
290
|
+
UnifiedAuthProvider,
|
|
291
|
+
OrganisationProvider,
|
|
292
|
+
EventProvider
|
|
293
|
+
} from '@jmruthers/pace-core/providers'
|
|
294
|
+
import { supabase } from './lib/supabase'
|
|
295
|
+
import { Dashboard } from './pages/Dashboard'
|
|
296
|
+
import { Participants } from './pages/Participants'
|
|
297
|
+
import { Login } from './pages/Login'
|
|
298
|
+
|
|
299
|
+
// CRITICAL: App name for RBAC resolution
|
|
300
|
+
const APP_NAME = import.meta.env.VITE_APP_NAME
|
|
301
|
+
|
|
302
|
+
if (!APP_NAME) {
|
|
303
|
+
throw new Error('VITE_APP_NAME environment variable is required')
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function App() {
|
|
307
|
+
return (
|
|
308
|
+
<UnifiedAuthProvider
|
|
309
|
+
supabaseClient={supabase}
|
|
310
|
+
appName={APP_NAME}
|
|
311
|
+
>
|
|
312
|
+
<OrganisationProvider>
|
|
313
|
+
{/* CRITICAL: EventProvider is REQUIRED for event-based apps */}
|
|
314
|
+
<EventProvider
|
|
315
|
+
autoSelectNextEvent={true} // Automatically select next upcoming event
|
|
316
|
+
onEventChange={(event) => {
|
|
317
|
+
console.log('Event changed:', event?.event_name)
|
|
318
|
+
}}
|
|
319
|
+
>
|
|
320
|
+
<Router>
|
|
321
|
+
<Routes>
|
|
322
|
+
<Route path="/login" element={<Login />} />
|
|
323
|
+
<Route path="/" element={<Dashboard />} />
|
|
324
|
+
<Route path="/participants" element={<Participants />} />
|
|
325
|
+
</Routes>
|
|
326
|
+
</Router>
|
|
327
|
+
</EventProvider>
|
|
328
|
+
</OrganisationProvider>
|
|
329
|
+
</UnifiedAuthProvider>
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export default App
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**CRITICAL Differences for Event-Based Apps:**
|
|
337
|
+
- ✅ **EventProvider is REQUIRED** - Wraps your app content
|
|
338
|
+
- ✅ **OrganisationProvider still required** - Needed for organisation context
|
|
339
|
+
- ✅ **autoSelectNextEvent** - Automatically selects the next upcoming event
|
|
340
|
+
- ✅ **Event context is automatically available** - `PagePermissionGuard` will resolve organisation from event
|
|
341
|
+
|
|
342
|
+
### 9. Create Login Page
|
|
343
|
+
|
|
344
|
+
Create `src/pages/Login.tsx`:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
// src/pages/Login.tsx
|
|
348
|
+
import React, { useState } from 'react'
|
|
349
|
+
import { useNavigate } from 'react-router-dom'
|
|
350
|
+
import { supabase } from '../lib/supabase'
|
|
351
|
+
|
|
352
|
+
export function Login() {
|
|
353
|
+
const [email, setEmail] = useState('')
|
|
354
|
+
const [password, setPassword] = useState('')
|
|
355
|
+
const [loading, setLoading] = useState(false)
|
|
356
|
+
const navigate = useNavigate()
|
|
357
|
+
|
|
358
|
+
const handleLogin = async (e: React.FormEvent) => {
|
|
359
|
+
e.preventDefault()
|
|
360
|
+
setLoading(true)
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
364
|
+
email,
|
|
365
|
+
password
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
if (error) {
|
|
369
|
+
alert('Login failed: ' + error.message)
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (data.user) {
|
|
374
|
+
navigate('/')
|
|
375
|
+
}
|
|
376
|
+
} catch (error) {
|
|
377
|
+
alert('Login failed: ' + (error as Error).message)
|
|
378
|
+
} finally {
|
|
379
|
+
setLoading(false)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<div className="min-h-screen flex items-center justify-center bg-sec-50">
|
|
385
|
+
<div className="max-w-md w-full space-y-8">
|
|
386
|
+
<div>
|
|
387
|
+
<h2 className="mt-6 text-center text-3xl font-extrabold text-sec-900">
|
|
388
|
+
Sign in to your account
|
|
389
|
+
</h2>
|
|
390
|
+
</div>
|
|
391
|
+
<form className="mt-8 space-y-6" onSubmit={handleLogin}>
|
|
392
|
+
<div>
|
|
393
|
+
<label htmlFor="email" className="sr-only">
|
|
394
|
+
Email address
|
|
395
|
+
</label>
|
|
396
|
+
<input
|
|
397
|
+
id="email"
|
|
398
|
+
name="email"
|
|
399
|
+
type="email"
|
|
400
|
+
required
|
|
401
|
+
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-sec-300 placeholder-sec-500 text-sec-900 focus:outline-none focus:ring-sec-500 focus:border-sec-500 focus:z-10 sm:text-sm"
|
|
402
|
+
placeholder="Email address"
|
|
403
|
+
value={email}
|
|
404
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
405
|
+
/>
|
|
406
|
+
</div>
|
|
407
|
+
<div>
|
|
408
|
+
<label htmlFor="password" className="sr-only">
|
|
409
|
+
Password
|
|
410
|
+
</label>
|
|
411
|
+
<input
|
|
412
|
+
id="password"
|
|
413
|
+
name="password"
|
|
414
|
+
type="password"
|
|
415
|
+
required
|
|
416
|
+
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-sec-300 placeholder-sec-500 text-sec-900 focus:outline-none focus:ring-sec-500 focus:border-sec-500 focus:z-10 sm:text-sm"
|
|
417
|
+
placeholder="Password"
|
|
418
|
+
value={password}
|
|
419
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
420
|
+
/>
|
|
421
|
+
</div>
|
|
422
|
+
<div>
|
|
423
|
+
<button
|
|
424
|
+
type="submit"
|
|
425
|
+
disabled={loading}
|
|
426
|
+
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-main-50 bg-sec-600 hover:bg-sec-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sec-500 disabled:opacity-50"
|
|
427
|
+
>
|
|
428
|
+
{loading ? 'Signing in...' : 'Sign in'}
|
|
429
|
+
</button>
|
|
430
|
+
</div>
|
|
431
|
+
</form>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### 10. Create Dashboard Page (Event-Based)
|
|
439
|
+
|
|
440
|
+
Create `src/pages/Dashboard.tsx`:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
// src/pages/Dashboard.tsx
|
|
444
|
+
import React from 'react'
|
|
445
|
+
import { useUnifiedAuth } from '@jmruthers/pace-core/providers'
|
|
446
|
+
import { PagePermissionGuard } from '@jmruthers/pace-core/rbac'
|
|
447
|
+
import { Link } from 'react-router-dom'
|
|
448
|
+
import { supabase } from '../lib/supabase'
|
|
449
|
+
|
|
450
|
+
export function Dashboard() {
|
|
451
|
+
const { user, selectedEventId, selectedOrganisationId } = useUnifiedAuth()
|
|
452
|
+
|
|
453
|
+
if (!user) {
|
|
454
|
+
return <div>Please log in</div>
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
<div className="min-h-screen bg-sec-50">
|
|
459
|
+
<nav className="bg-white shadow">
|
|
460
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
461
|
+
<div className="flex justify-between h-16">
|
|
462
|
+
<div className="flex items-center">
|
|
463
|
+
<h1 className="text-xl font-semibold">Event Dashboard</h1>
|
|
464
|
+
</div>
|
|
465
|
+
<div className="flex items-center space-x-4">
|
|
466
|
+
<span className="text-sm text-sec-700">
|
|
467
|
+
{user.email} | Event: {selectedEventId || 'None selected'} | Org: {selectedOrganisationId}
|
|
468
|
+
</span>
|
|
469
|
+
<button
|
|
470
|
+
onClick={() => supabase.auth.signOut()}
|
|
471
|
+
className="text-sm text-sec-500 hover:text-sec-700"
|
|
472
|
+
>
|
|
473
|
+
Sign out
|
|
474
|
+
</button>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
</nav>
|
|
479
|
+
|
|
480
|
+
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
|
481
|
+
<div className="px-4 py-6 sm:px-0">
|
|
482
|
+
<div className="border-4 border-dashed border-sec-200 rounded-lg h-96 p-8">
|
|
483
|
+
<h2 className="text-2xl font-bold mb-4">Welcome to your Event Dashboard</h2>
|
|
484
|
+
|
|
485
|
+
{/* CRITICAL: PagePermissionGuard automatically resolves organisation from event */}
|
|
486
|
+
{/* For event-based apps, you only need eventId - organisationId is resolved automatically */}
|
|
487
|
+
<PagePermissionGuard
|
|
488
|
+
pageName="dashboard"
|
|
489
|
+
operation="read"
|
|
490
|
+
fallback={
|
|
491
|
+
<div>
|
|
492
|
+
<p>You don't have permission to view the dashboard</p>
|
|
493
|
+
<p className="text-sm text-sec-600 mt-2">
|
|
494
|
+
{!selectedEventId && 'Please select an event first'}
|
|
495
|
+
{selectedEventId && 'Your role does not have access to this page'}
|
|
496
|
+
</p>
|
|
497
|
+
</div>
|
|
498
|
+
}
|
|
499
|
+
>
|
|
500
|
+
<div className="space-y-4">
|
|
501
|
+
<p>You have access to the dashboard!</p>
|
|
502
|
+
<p className="text-sm text-sec-600">
|
|
503
|
+
Current Event ID: {selectedEventId || 'No event selected'}
|
|
504
|
+
</p>
|
|
505
|
+
<p className="text-sm text-sec-600">
|
|
506
|
+
Organisation ID (resolved from event): {selectedOrganisationId || 'Resolving...'}
|
|
507
|
+
</p>
|
|
508
|
+
|
|
509
|
+
<div className="space-x-4">
|
|
510
|
+
<Link
|
|
511
|
+
to="/participants"
|
|
512
|
+
className="bg-main-500 text-main-50 px-4 py-2 rounded hover:bg-main-600"
|
|
513
|
+
>
|
|
514
|
+
View Participants
|
|
515
|
+
</Link>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
</PagePermissionGuard>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
</main>
|
|
522
|
+
</div>
|
|
523
|
+
)
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### 11. Create Participants Page (Event-Based)
|
|
528
|
+
|
|
529
|
+
Create `src/pages/Participants.tsx`:
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
// src/pages/Participants.tsx
|
|
533
|
+
import React from 'react'
|
|
534
|
+
import { useUnifiedAuth } from '@jmruthers/pace-core/providers'
|
|
535
|
+
import { PagePermissionGuard } from '@jmruthers/pace-core/rbac'
|
|
536
|
+
import { Link } from 'react-router-dom'
|
|
537
|
+
import { supabase } from '../lib/supabase'
|
|
538
|
+
|
|
539
|
+
export function Participants() {
|
|
540
|
+
const { user, selectedEventId, selectedOrganisationId } = useUnifiedAuth()
|
|
541
|
+
|
|
542
|
+
if (!user) {
|
|
543
|
+
return <div>Please log in</div>
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return (
|
|
547
|
+
<div className="min-h-screen bg-sec-50">
|
|
548
|
+
<nav className="bg-white shadow">
|
|
549
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
550
|
+
<div className="flex justify-between h-16">
|
|
551
|
+
<div className="flex items-center">
|
|
552
|
+
<Link to="/" className="text-main-600 hover:text-main-800 mr-4">
|
|
553
|
+
← Back to Dashboard
|
|
554
|
+
</Link>
|
|
555
|
+
<h1 className="text-xl font-semibold">Participants</h1>
|
|
556
|
+
</div>
|
|
557
|
+
<div className="flex items-center space-x-4">
|
|
558
|
+
<span className="text-sm text-sec-700">
|
|
559
|
+
{user.email} | Event: {selectedEventId || 'None'} | Org: {selectedOrganisationId}
|
|
560
|
+
</span>
|
|
561
|
+
<button
|
|
562
|
+
onClick={() => supabase.auth.signOut()}
|
|
563
|
+
className="text-sm text-sec-500 hover:text-sec-700"
|
|
564
|
+
>
|
|
565
|
+
Sign out
|
|
566
|
+
</button>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
</nav>
|
|
571
|
+
|
|
572
|
+
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
|
573
|
+
<div className="px-4 py-6 sm:px-0">
|
|
574
|
+
<div className="border-4 border-dashed border-sec-200 rounded-lg h-96 p-8">
|
|
575
|
+
<h2 className="text-2xl font-bold mb-4">Participant Management</h2>
|
|
576
|
+
|
|
577
|
+
{/* CRITICAL: PagePermissionGuard automatically resolves organisation from event */}
|
|
578
|
+
<PagePermissionGuard
|
|
579
|
+
pageName="participants"
|
|
580
|
+
operation="read"
|
|
581
|
+
fallback={
|
|
582
|
+
<div>
|
|
583
|
+
<p>You don't have permission to view participants</p>
|
|
584
|
+
<p className="text-sm text-sec-600 mt-2">
|
|
585
|
+
{!selectedEventId && 'Please select an event first'}
|
|
586
|
+
{selectedEventId && 'Your role does not have access to this page'}
|
|
587
|
+
</p>
|
|
588
|
+
</div>
|
|
589
|
+
}
|
|
590
|
+
>
|
|
591
|
+
<div className="space-y-4">
|
|
592
|
+
<p>You have access to the participants page!</p>
|
|
593
|
+
<p>This means your event-based RBAC system is working correctly.</p>
|
|
594
|
+
|
|
595
|
+
<div className="bg-main-100 border border-main-400 text-main-700 px-4 py-3 rounded">
|
|
596
|
+
<strong>Success!</strong> Your event-based RBAC setup is working correctly.
|
|
597
|
+
<p className="text-sm mt-2">
|
|
598
|
+
Event ID: {selectedEventId || 'Not set'}
|
|
599
|
+
<br />
|
|
600
|
+
Organisation ID (auto-resolved): {selectedOrganisationId || 'Resolving...'}
|
|
601
|
+
</p>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
</PagePermissionGuard>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
</main>
|
|
608
|
+
</div>
|
|
609
|
+
)
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## 🧪 Test Your Setup
|
|
614
|
+
|
|
615
|
+
1. **Start your development server**:
|
|
616
|
+
```bash
|
|
617
|
+
npm run dev
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
2. **Navigate to your app** (usually `http://localhost:3000` or `http://localhost:5173`)
|
|
621
|
+
|
|
622
|
+
3. **You should see the login page**
|
|
623
|
+
|
|
624
|
+
4. **Log in with a user that has the `org_admin` role**
|
|
625
|
+
|
|
626
|
+
5. **You should be redirected to the dashboard**
|
|
627
|
+
- If you see "No event selected", the EventProvider should auto-select the next event
|
|
628
|
+
- If no events exist, create one in the database (see step 5.4)
|
|
629
|
+
|
|
630
|
+
6. **Click "View Participants" - you should see the participants page**
|
|
631
|
+
|
|
632
|
+
7. **If you see "You don't have permission" messages, check the troubleshooting section below**
|
|
633
|
+
|
|
634
|
+
## 🎉 Success Checklist
|
|
635
|
+
|
|
636
|
+
Your event-based RBAC setup is working correctly if:
|
|
637
|
+
|
|
638
|
+
**Setup Checklist:**
|
|
639
|
+
- [ ] `setupRBAC(supabase)` is called before rendering App
|
|
640
|
+
- [ ] App is registered in `rbac_apps` table with `requires_event = true` and `is_active = true`
|
|
641
|
+
- [ ] Pages exist in `rbac_app_pages` for your app
|
|
642
|
+
- [ ] Page permissions exist in `rbac_page_permissions` for your pages, roles, and organisation
|
|
643
|
+
- [ ] User has an active role in `rbac_organisation_roles` for the organisation
|
|
644
|
+
- [ ] At least one event exists in the `event` table for your organisation
|
|
645
|
+
- [ ] `VITE_APP_NAME` environment variable matches database `rbac_apps.name` exactly
|
|
646
|
+
- [ ] `EventProvider` wraps your app content
|
|
647
|
+
- [ ] `OrganisationProvider` is present (required even for event-based apps)
|
|
648
|
+
|
|
649
|
+
**Runtime Checklist:**
|
|
650
|
+
- [ ] You can log in successfully
|
|
651
|
+
- [ ] Browser console shows "RBAC system initialized successfully"
|
|
652
|
+
- [ ] An event is automatically selected (or you can select one manually)
|
|
653
|
+
- [ ] You see the dashboard without "Access Denied" messages
|
|
654
|
+
- [ ] `PagePermissionGuard` shows loading state, then renders content (not fallback)
|
|
655
|
+
- [ ] You can navigate to the participants page
|
|
656
|
+
- [ ] You see "Success! Your event-based RBAC setup is working correctly" on the participants page
|
|
657
|
+
- [ ] No 400/406 errors in the browser console
|
|
658
|
+
- [ ] No "App not found" errors in the console
|
|
659
|
+
- [ ] No "STRICT MODE VIOLATION" errors in the console
|
|
660
|
+
- [ ] No "Event context is required" errors
|
|
661
|
+
- [ ] Organisation ID is automatically resolved from event (visible in dashboard)
|
|
662
|
+
|
|
663
|
+
## 🚨 Troubleshooting
|
|
664
|
+
|
|
665
|
+
### Issue: "RBACNotInitializedError"
|
|
666
|
+
|
|
667
|
+
**Cause**: Forgot to call `setupRBAC()`.
|
|
668
|
+
|
|
669
|
+
**Solution**:
|
|
670
|
+
```typescript
|
|
671
|
+
import { setupRBAC } from '@jmruthers/pace-core/rbac'
|
|
672
|
+
setupRBAC(supabase) // Must be called BEFORE rendering app
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Issue: "Event context is required" or "No event selected"
|
|
676
|
+
|
|
677
|
+
**Cause**: Event is not selected in UnifiedAuth context.
|
|
678
|
+
|
|
679
|
+
**Solutions**:
|
|
680
|
+
|
|
681
|
+
1. **Check EventProvider is present**:
|
|
682
|
+
```tsx
|
|
683
|
+
<EventProvider autoSelectNextEvent={true}>
|
|
684
|
+
<YourApp />
|
|
685
|
+
</EventProvider>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
2. **Verify events exist for your organisation**:
|
|
689
|
+
```sql
|
|
690
|
+
SELECT event_id, event_name, event_code, organisation_id, event_date
|
|
691
|
+
FROM event
|
|
692
|
+
WHERE organisation_id = 'your-organisation-id'::uuid
|
|
693
|
+
AND is_active = true
|
|
694
|
+
ORDER BY event_date ASC;
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
3. **Manually select an event** (for testing):
|
|
698
|
+
```tsx
|
|
699
|
+
import { useUnifiedAuth } from '@jmruthers/pace-core/providers';
|
|
700
|
+
|
|
701
|
+
function TestComponent() {
|
|
702
|
+
const { setSelectedEventId } = useUnifiedAuth();
|
|
703
|
+
|
|
704
|
+
useEffect(() => {
|
|
705
|
+
// Set a test event ID
|
|
706
|
+
setSelectedEventId('your-event-id');
|
|
707
|
+
}, []);
|
|
708
|
+
|
|
709
|
+
return <div>...</div>;
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### Issue: "Could not resolve organization from event context"
|
|
714
|
+
|
|
715
|
+
**Cause**: Event doesn't have a valid `organisation_id` or event doesn't exist.
|
|
716
|
+
|
|
717
|
+
**Solution**:
|
|
718
|
+
```sql
|
|
719
|
+
-- Verify event has organisation_id
|
|
720
|
+
SELECT event_id, event_name, organisation_id
|
|
721
|
+
FROM event
|
|
722
|
+
WHERE event_id = 'your-event-id'::uuid;
|
|
723
|
+
|
|
724
|
+
-- If organisation_id is NULL, update it:
|
|
725
|
+
UPDATE event
|
|
726
|
+
SET organisation_id = 'your-organisation-id'::uuid
|
|
727
|
+
WHERE event_id = 'your-event-id'::uuid;
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Issue: "Access Denied" or "STRICT MODE VIOLATION" on all pages
|
|
731
|
+
|
|
732
|
+
**This is the most common issue. Check these in EXACT ORDER:**
|
|
733
|
+
|
|
734
|
+
1. **Verify setupRBAC was called**:
|
|
735
|
+
- Check browser console for "RBAC system initialized successfully"
|
|
736
|
+
- If missing, add `setupRBAC(supabase)` in your main.tsx before rendering
|
|
737
|
+
|
|
738
|
+
2. **Verify your app is registered as event-based**:
|
|
739
|
+
```sql
|
|
740
|
+
SELECT id, name, display_name, requires_event, is_active
|
|
741
|
+
FROM rbac_apps
|
|
742
|
+
WHERE name = 'pace-trac';
|
|
743
|
+
```
|
|
744
|
+
- Must return exactly 1 row
|
|
745
|
+
- `requires_event` must be `true` (not `false`)
|
|
746
|
+
- `is_active` must be `true`
|
|
747
|
+
- `name` must match your `VITE_APP_NAME` exactly (case-sensitive)
|
|
748
|
+
|
|
749
|
+
3. **Check your environment variable matches database**:
|
|
750
|
+
```typescript
|
|
751
|
+
console.log('App name from env:', import.meta.env.VITE_APP_NAME);
|
|
752
|
+
```
|
|
753
|
+
- Must match database `rbac_apps.name` exactly
|
|
754
|
+
|
|
755
|
+
4. **Verify pages exist for your app**:
|
|
756
|
+
```sql
|
|
757
|
+
SELECT ap.id, ap.page_name, ap.page_description
|
|
758
|
+
FROM rbac_app_pages ap
|
|
759
|
+
JOIN rbac_apps a ON ap.app_id = a.id
|
|
760
|
+
WHERE a.name = 'pace-trac'
|
|
761
|
+
ORDER BY ap.page_name;
|
|
762
|
+
```
|
|
763
|
+
- Must have pages for `dashboard`, `participants`, etc.
|
|
764
|
+
|
|
765
|
+
5. **Verify user has organisation role**:
|
|
766
|
+
```sql
|
|
767
|
+
SELECT ror.*, u.email
|
|
768
|
+
FROM rbac_organisation_roles ror
|
|
769
|
+
JOIN auth.users u ON ror.user_id = u.id
|
|
770
|
+
WHERE ror.user_id = 'your-user-id'::uuid
|
|
771
|
+
AND ror.status = 'active';
|
|
772
|
+
```
|
|
773
|
+
- User must have at least one active role
|
|
774
|
+
- Must be for the organisation that owns the event
|
|
775
|
+
|
|
776
|
+
6. **Verify event belongs to user's organisation**:
|
|
777
|
+
```sql
|
|
778
|
+
SELECT e.event_id, e.event_name, e.organisation_id, ror.organisation_id as user_org_id
|
|
779
|
+
FROM event e
|
|
780
|
+
JOIN rbac_organisation_roles ror ON e.organisation_id = ror.organisation_id
|
|
781
|
+
WHERE e.event_id = 'your-event-id'::uuid
|
|
782
|
+
AND ror.user_id = 'your-user-id'::uuid
|
|
783
|
+
AND ror.status = 'active';
|
|
784
|
+
```
|
|
785
|
+
- Event's `organisation_id` must match user's organisation role
|
|
786
|
+
|
|
787
|
+
7. **Check page permissions exist for user's role**:
|
|
788
|
+
```sql
|
|
789
|
+
SELECT
|
|
790
|
+
ap.page_name,
|
|
791
|
+
pp.operation,
|
|
792
|
+
pp.role_name,
|
|
793
|
+
pp.allowed,
|
|
794
|
+
pp.organisation_id
|
|
795
|
+
FROM rbac_page_permissions pp
|
|
796
|
+
JOIN rbac_app_pages ap ON pp.app_page_id = ap.id
|
|
797
|
+
JOIN rbac_apps a ON ap.app_id = a.id
|
|
798
|
+
WHERE a.name = 'pace-trac'
|
|
799
|
+
AND pp.role_name = 'org_admin' -- Replace with your user's role
|
|
800
|
+
AND pp.organisation_id = 'your-org-id'::uuid -- Replace with your org ID
|
|
801
|
+
AND pp.allowed = true;
|
|
802
|
+
```
|
|
803
|
+
- Must have permissions for the pages you're trying to access
|
|
804
|
+
- Must have `allowed = true` for the operation (read, create, etc.)
|
|
805
|
+
- `organisation_id` must match the organisation that owns the event
|
|
806
|
+
|
|
807
|
+
8. **Check browser console for specific errors**:
|
|
808
|
+
- Look for `[PagePermissionGuard]` error messages
|
|
809
|
+
- Look for `[useCan]` permission check logs
|
|
810
|
+
- Look for "Event context is required" errors
|
|
811
|
+
- Look for rate limit errors (if you see these, you may need to increase the limit)
|
|
812
|
+
|
|
813
|
+
### Issue: 400/406 Database Errors
|
|
814
|
+
|
|
815
|
+
**This means your app is making incorrect database queries. Check:**
|
|
816
|
+
|
|
817
|
+
1. **You're using the providers correctly** (Step 8)
|
|
818
|
+
2. **You're not making direct database queries to RBAC tables**
|
|
819
|
+
3. **You're using the PagePermissionGuard component** (not manual permission checks)
|
|
820
|
+
4. **EventProvider is wrapping your app content**
|
|
821
|
+
|
|
822
|
+
### Issue: "App not found or inactive"
|
|
823
|
+
|
|
824
|
+
**Check:**
|
|
825
|
+
|
|
826
|
+
1. **Your app name in the database matches your environment variable exactly**
|
|
827
|
+
2. **Your app is marked as active** (`is_active = true`)
|
|
828
|
+
3. **Your app is marked as event-based** (`requires_event = true`)
|
|
829
|
+
4. **You're using the correct app name in the UnifiedAuthProvider**
|
|
830
|
+
|
|
831
|
+
### Issue: Components not rendering
|
|
832
|
+
|
|
833
|
+
**Check:**
|
|
834
|
+
|
|
835
|
+
1. **You're using PagePermissionGuard (not manual permission checks)**
|
|
836
|
+
2. **You're providing the correct pageName and operation**
|
|
837
|
+
3. **The page exists in rbac_app_pages table**
|
|
838
|
+
4. **An event is selected** (`selectedEventId` is not null)
|
|
839
|
+
5. **EventProvider is wrapping your components**
|
|
840
|
+
|
|
841
|
+
## 🔑 Key Differences: Event-Based vs Organisation-Based Apps
|
|
842
|
+
|
|
843
|
+
| Aspect | Organisation-Based | Event-Based |
|
|
844
|
+
|--------|-------------------|-------------|
|
|
845
|
+
| **Database Config** | `requires_event = false` | `requires_event = true` |
|
|
846
|
+
| **Required Provider** | `OrganisationProvider` | `OrganisationProvider` + `EventProvider` |
|
|
847
|
+
| **Context Required** | `organisationId` | `eventId` (org auto-resolved) |
|
|
848
|
+
| **PagePermissionGuard** | Needs `organisationId` | Needs `eventId` (org auto-resolved) |
|
|
849
|
+
| **Scope Resolution** | Direct from context | Organisation resolved from event |
|
|
850
|
+
| **Use Case** | User management, org settings | Event registration, event management |
|
|
851
|
+
|
|
852
|
+
## 🚀 Next Steps
|
|
853
|
+
|
|
854
|
+
- **[RBAC Overview](./README.md)** - Complete RBAC system documentation
|
|
855
|
+
- **[API Reference](./api-reference.md)** - Complete API documentation
|
|
856
|
+
- **[Examples](./examples.md)** - More complex usage patterns
|
|
857
|
+
- **[Troubleshooting](./troubleshooting.md)** - Advanced troubleshooting
|
|
858
|
+
- **[Migration Guide](../migration/rbac-migration.md)** - If migrating from legacy RBAC
|
|
859
|
+
|
|
860
|
+
## 🆘 Still Having Issues?
|
|
861
|
+
|
|
862
|
+
If you're still having problems after following this guide exactly:
|
|
863
|
+
|
|
864
|
+
1. **Check the browser console** for specific error messages
|
|
865
|
+
2. **Verify your database setup** matches the SQL commands exactly
|
|
866
|
+
3. **Make sure your environment variables** are set correctly
|
|
867
|
+
4. **Check that your user has the correct roles** in the database
|
|
868
|
+
5. **Verify an event exists** and belongs to your organisation
|
|
869
|
+
6. **Ensure EventProvider is wrapping your app** content
|
|
870
|
+
|
|
871
|
+
This guide is designed to be foolproof - if you follow it exactly, your event-based RBAC system will work correctly.
|
|
872
|
+
|