6.6 KiB
Entity Submission Flow - CRITICAL SECURITY PATTERN
The Sacred Flow
Every entity creation or edit MUST follow this exact flow:
1. User fills form
↓
2. Submission created → moderation queue
↓
3. Moderator reviews & approves
↓
4. Edge function writes to database (via service role)
↓
5. Versioning system captures change
↓
6. Public can view live entity
❌ NEVER DO THIS
// Direct database write - BYPASSES EVERYTHING!
await supabase.from('parks').insert(parkData);
await supabase.from('rides').update({ id: rideId }, rideData);
// Even if you're a moderator/admin:
if (isAdmin) {
await supabase.from('parks').insert(parkData); // STILL WRONG!
}
Why this is blocked:
- RLS policies explicitly deny INSERT/UPDATE to authenticated users
- Only the service role (edge functions) can write
- Edge functions are ONLY triggered by moderation approval
✅ ALWAYS DO THIS
// Import the submission helpers
import {
submitParkCreation,
submitParkUpdate,
submitRideCreation,
submitRideUpdate
} from '@/lib/entitySubmissionHelpers';
// For new entities:
const result = await submitParkCreation(parkData, user.id);
// For editing existing entities:
const result = await submitParkUpdate(parkId, parkData, user.id);
For React Components
Use the enforced wrappers:
import { enforceParkSubmissionFlow } from '@/lib/entityFormValidation';
// In your component:
const handleSubmit = enforceParkSubmissionFlow(isEditing, park?.id);
<ParkForm
isEditing={isEditing}
initialData={park}
onSubmit={async (data) => {
await handleSubmit(data, user!.id);
toast({ title: "Submitted for review" });
}}
/>
Why This Matters
Security
- Prevents spam and vandalism
- Rate-limits submissions via queue system
- Blocks malicious automated bots
Quality
- Human review catches errors, duplicates, inappropriate content
- Maintains high data quality standards
Accountability
- Full version history with user attribution
- Every change is tracked and auditable
- Can rollback any change
Compliance
- Complete audit trail for legal/regulatory requirements
- User attribution for all content
For Moderators/Admins
Even moderators must follow the flow:
- Create/edit submission through normal UI
- It goes to moderation queue
- Review your own submission
- Approve it
Why?
- Ensures versioning captures the change
- Creates audit trail
- Prevents accidental mistakes
Exception: Only emergency database fixes via SQL editor should bypass this. These should be:
- Extremely rare
- Documented in admin_audit_log
- Followed by manual version creation if needed
Database Protection
RLS Policies
The following tables have explicit denial policies:
parks- INSERT/UPDATE blocked for authenticated usersrides- INSERT/UPDATE blocked for authenticated userscompanies- INSERT/UPDATE blocked for authenticated usersride_models- INSERT/UPDATE blocked for authenticated users
Service Role Access
Only these edge functions can write (they use service role):
process-selective-approval- Applies approved submissions- Direct SQL migrations (admin only)
Versioning Triggers
All entity writes trigger auto_create_entity_version():
- Captures full entity state
- Records user who made change
- Creates diff of what changed
- Logs to audit trail if user attribution missing
Error Messages
If you see these errors, you're trying to bypass the flow:
Error: new row violates row-level security policy
→ You tried to INSERT/UPDATE directly. Use submission helpers.
Error: permission denied for table parks/rides/companies/ride_models
→ Same issue - use submission helpers.
Testing the Flow
Manual Test:
- Create a new park through the UI
- Check
content_submissionstable - should have new row - Check
submission_itemstable - should have park data - Approve via moderation queue
- Check
parkstable - entity should appear - Check
entity_versionstable - version should be created - Version should have correct
changed_byuser_id
Automated Test:
// Test that direct writes are blocked
try {
await supabase.from('parks').insert({ name: 'Test Park' });
throw new Error('Expected RLS to block this');
} catch (error) {
// Should get permission denied error
expect(error.message).toContain('row-level security');
}
// Test that submission flow works
const result = await submitParkCreation(parkData, userId);
expect(result.submitted).toBe(true);
Monitoring
Dashboard Widgets
Check the admin dashboard for:
- Suspicious Versions - Versions without user attribution
- Queue Backlog - Submissions waiting too long
- Flow Violations - Any attempts to bypass the flow
Audit Logs
-- Check for versions without user attribution
SELECT * FROM entity_versions
WHERE changed_by IS NULL
ORDER BY changed_at DESC;
-- Check for direct writes (should be none except migrations)
SELECT * FROM admin_audit_log
WHERE action = 'version_without_user'
ORDER BY created_at DESC;
Common Pitfalls
❌ "I'll just fix this one typo directly"
Even small fixes must go through the flow. Creates version history.
❌ "I'm an admin, I can bypass this"
No. Admins follow the flow. Prevents mistakes and maintains audit trail.
❌ "This is taking too long, I'll do it in SQL editor"
Only for emergencies. Document it. Create manual version if needed.
❌ "I'll bulk import via CSV"
Bulk imports should:
- Create submissions programmatically
- Auto-approve them if from trusted source
- Still trigger versioning
Questions?
Q: What if I need to bulk import 1000 parks?
A: Create submissions programmatically, set status='approved', let edge function process them.
Q: What if there's an emergency typo on homepage? A: Make the fix via submission → have another mod approve immediately. Takes <1 minute.
Q: What if the moderation queue is broken? A: Fix the queue. Don't bypass the flow. That creates worse problems.
Q: Can I use the service role key directly? A: Only in edge functions. Never in client-side code. Never for routine edits.
Related Files
src/lib/entitySubmissionHelpers.ts- Core submission functionssrc/lib/entityFormValidation.ts- Enforced wrapperssupabase/functions/process-selective-approval/index.ts- Approval processorsrc/components/admin/*Form.tsx- Form components using the flow
Update History
- 2025-01-XX: Added explicit RLS denial policies
- 2025-01-XX: Added versioning for photos table
- 2025-01-XX: Added user attribution to edge function
- 2025-01-XX: Created this documentation