@intentsolutionsio/supabase-pack 1.0.0 → 1.0.3

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 (133) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +73 -47
  3. package/package.json +4 -4
  4. package/skills/supabase-advanced-troubleshooting/SKILL.md +404 -200
  5. package/skills/supabase-advanced-troubleshooting/references/errors.md +11 -0
  6. package/skills/supabase-advanced-troubleshooting/references/evidence-collection-framework.md +34 -0
  7. package/skills/supabase-advanced-troubleshooting/references/examples.md +11 -0
  8. package/skills/supabase-advanced-troubleshooting/references/rls-edge-functions-realtime.md +363 -0
  9. package/skills/supabase-advanced-troubleshooting/references/systematic-isolation.md +56 -0
  10. package/skills/supabase-advanced-troubleshooting/references/timing-analysis.md +35 -0
  11. package/skills/supabase-architecture-variants/SKILL.md +395 -216
  12. package/skills/supabase-architecture-variants/references/errors.md +11 -0
  13. package/skills/supabase-architecture-variants/references/examples.md +12 -0
  14. package/skills/supabase-architecture-variants/references/serverless-and-multi-tenant.md +251 -0
  15. package/skills/supabase-architecture-variants/references/variant-a-monolith-(simple).md +44 -0
  16. package/skills/supabase-architecture-variants/references/variant-b-service-layer-(moderate).md +72 -0
  17. package/skills/supabase-architecture-variants/references/variant-c-microservice-(complex).md +81 -0
  18. package/skills/supabase-auth-storage-realtime-core/SKILL.md +471 -37
  19. package/skills/supabase-ci-integration/SKILL.md +315 -67
  20. package/skills/supabase-ci-integration/references/errors.md +10 -0
  21. package/skills/supabase-ci-integration/references/examples.md +36 -0
  22. package/skills/supabase-ci-integration/references/implementation.md +54 -0
  23. package/skills/supabase-common-errors/SKILL.md +320 -62
  24. package/skills/supabase-common-errors/references/errors.md +53 -0
  25. package/skills/supabase-common-errors/references/examples.md +23 -0
  26. package/skills/supabase-cost-tuning/SKILL.md +365 -131
  27. package/skills/supabase-cost-tuning/references/cost-estimation.md +34 -0
  28. package/skills/supabase-cost-tuning/references/cost-reduction-strategies.md +40 -0
  29. package/skills/supabase-cost-tuning/references/errors.md +11 -0
  30. package/skills/supabase-cost-tuning/references/examples.md +15 -0
  31. package/skills/supabase-data-handling/SKILL.md +378 -145
  32. package/skills/supabase-data-handling/references/errors.md +11 -0
  33. package/skills/supabase-data-handling/references/examples.md +27 -0
  34. package/skills/supabase-data-handling/references/implementation.md +223 -0
  35. package/skills/supabase-data-handling/references/retention-and-backup.md +221 -0
  36. package/skills/supabase-debug-bundle/SKILL.md +267 -73
  37. package/skills/supabase-debug-bundle/references/errors.md +12 -0
  38. package/skills/supabase-debug-bundle/references/examples.md +24 -0
  39. package/skills/supabase-debug-bundle/references/implementation.md +54 -0
  40. package/skills/supabase-deploy-integration/SKILL.md +258 -147
  41. package/skills/supabase-deploy-integration/references/errors.md +11 -0
  42. package/skills/supabase-deploy-integration/references/examples.md +21 -0
  43. package/skills/supabase-deploy-integration/references/google-cloud-run.md +36 -0
  44. package/skills/supabase-deploy-integration/references/vercel-deployment.md +35 -0
  45. package/skills/supabase-enterprise-rbac/SKILL.md +327 -160
  46. package/skills/supabase-enterprise-rbac/references/api-scoping-and-enforcement.md +255 -0
  47. package/skills/supabase-enterprise-rbac/references/errors.md +11 -0
  48. package/skills/supabase-enterprise-rbac/references/examples.md +12 -0
  49. package/skills/supabase-enterprise-rbac/references/role-implementation.md +33 -0
  50. package/skills/supabase-enterprise-rbac/references/sso-integration.md +35 -0
  51. package/skills/supabase-hello-world/SKILL.md +160 -54
  52. package/skills/supabase-incident-runbook/SKILL.md +453 -131
  53. package/skills/supabase-incident-runbook/references/errors.md +11 -0
  54. package/skills/supabase-incident-runbook/references/examples.md +10 -0
  55. package/skills/supabase-incident-runbook/references/immediate-actions-by-error-type.md +41 -0
  56. package/skills/supabase-install-auth/SKILL.md +186 -50
  57. package/skills/supabase-install-auth/references/examples.md +102 -0
  58. package/skills/supabase-known-pitfalls/SKILL.md +411 -241
  59. package/skills/supabase-known-pitfalls/references/errors.md +11 -0
  60. package/skills/supabase-known-pitfalls/references/examples.md +12 -0
  61. package/skills/supabase-load-scale/SKILL.md +346 -217
  62. package/skills/supabase-load-scale/references/capacity-planning.md +47 -0
  63. package/skills/supabase-load-scale/references/errors.md +11 -0
  64. package/skills/supabase-load-scale/references/examples.md +26 -0
  65. package/skills/supabase-load-scale/references/load-testing-with-k6.md +59 -0
  66. package/skills/supabase-load-scale/references/scaling-patterns.md +65 -0
  67. package/skills/supabase-load-scale/references/table-partitioning.md +263 -0
  68. package/skills/supabase-local-dev-loop/SKILL.md +272 -73
  69. package/skills/supabase-local-dev-loop/references/errors.md +11 -0
  70. package/skills/supabase-local-dev-loop/references/examples.md +21 -0
  71. package/skills/supabase-local-dev-loop/references/implementation.md +60 -0
  72. package/skills/supabase-migration-deep-dive/SKILL.md +338 -177
  73. package/skills/supabase-migration-deep-dive/references/backfill-versioning-rollback.md +258 -0
  74. package/skills/supabase-migration-deep-dive/references/errors.md +11 -0
  75. package/skills/supabase-migration-deep-dive/references/examples.md +12 -0
  76. package/skills/supabase-migration-deep-dive/references/implementation-plan.md +80 -0
  77. package/skills/supabase-migration-deep-dive/references/pre-migration-assessment.md +39 -0
  78. package/skills/supabase-multi-env-setup/SKILL.md +393 -152
  79. package/skills/supabase-multi-env-setup/references/configuration-structure.md +59 -0
  80. package/skills/supabase-multi-env-setup/references/errors.md +11 -0
  81. package/skills/supabase-multi-env-setup/references/examples.md +11 -0
  82. package/skills/supabase-observability/SKILL.md +318 -196
  83. package/skills/supabase-observability/references/alert-configuration.md +40 -0
  84. package/skills/supabase-observability/references/errors.md +11 -0
  85. package/skills/supabase-observability/references/examples.md +13 -0
  86. package/skills/supabase-observability/references/metrics-collection.md +65 -0
  87. package/skills/supabase-performance-tuning/SKILL.md +304 -160
  88. package/skills/supabase-performance-tuning/references/caching-strategy.md +49 -0
  89. package/skills/supabase-performance-tuning/references/errors.md +11 -0
  90. package/skills/supabase-performance-tuning/references/examples.md +13 -0
  91. package/skills/supabase-policy-guardrails/SKILL.md +248 -221
  92. package/skills/supabase-policy-guardrails/references/ci-cost-security.md +484 -0
  93. package/skills/supabase-policy-guardrails/references/errors.md +11 -0
  94. package/skills/supabase-policy-guardrails/references/eslint-rules.md +46 -0
  95. package/skills/supabase-policy-guardrails/references/examples.md +10 -0
  96. package/skills/supabase-prod-checklist/SKILL.md +474 -84
  97. package/skills/supabase-prod-checklist/references/errors.md +63 -0
  98. package/skills/supabase-prod-checklist/references/examples.md +153 -0
  99. package/skills/supabase-prod-checklist/references/implementation.md +113 -0
  100. package/skills/supabase-rate-limits/SKILL.md +311 -98
  101. package/skills/supabase-rate-limits/references/errors.md +11 -0
  102. package/skills/supabase-rate-limits/references/examples.md +46 -0
  103. package/skills/supabase-rate-limits/references/implementation.md +66 -0
  104. package/skills/supabase-reference-architecture/SKILL.md +249 -182
  105. package/skills/supabase-reference-architecture/references/errors.md +29 -0
  106. package/skills/supabase-reference-architecture/references/examples.md +116 -0
  107. package/skills/supabase-reference-architecture/references/key-components.md +244 -0
  108. package/skills/supabase-reference-architecture/references/project-structure.md +109 -0
  109. package/skills/supabase-reliability-patterns/SKILL.md +229 -234
  110. package/skills/supabase-reliability-patterns/references/circuit-breaker.md +36 -0
  111. package/skills/supabase-reliability-patterns/references/dead-letter-queue.md +48 -0
  112. package/skills/supabase-reliability-patterns/references/errors.md +11 -0
  113. package/skills/supabase-reliability-patterns/references/examples.md +11 -0
  114. package/skills/supabase-reliability-patterns/references/idempotency-keys.md +36 -0
  115. package/skills/supabase-reliability-patterns/references/offline-degradation-health-dualwrite.md +489 -0
  116. package/skills/supabase-schema-from-requirements/SKILL.md +373 -34
  117. package/skills/supabase-sdk-patterns/SKILL.md +388 -99
  118. package/skills/supabase-sdk-patterns/references/errors.md +11 -0
  119. package/skills/supabase-sdk-patterns/references/examples.md +45 -0
  120. package/skills/supabase-sdk-patterns/references/implementation.md +67 -0
  121. package/skills/supabase-security-basics/SKILL.md +282 -102
  122. package/skills/supabase-security-basics/references/errors.md +10 -0
  123. package/skills/supabase-security-basics/references/examples.md +70 -0
  124. package/skills/supabase-security-basics/references/implementation.md +39 -0
  125. package/skills/supabase-upgrade-migration/SKILL.md +248 -66
  126. package/skills/supabase-upgrade-migration/references/errors.md +10 -0
  127. package/skills/supabase-upgrade-migration/references/examples.md +51 -0
  128. package/skills/supabase-upgrade-migration/references/implementation.md +29 -0
  129. package/skills/supabase-webhooks-events/SKILL.md +412 -138
  130. package/skills/supabase-webhooks-events/references/errors.md +55 -0
  131. package/skills/supabase-webhooks-events/references/event-handler-pattern.md +106 -0
  132. package/skills/supabase-webhooks-events/references/examples.md +133 -0
  133. package/skills/supabase-webhooks-events/references/signature-verification.md +165 -0
@@ -1,54 +1,166 @@
1
1
  ---
2
2
  name: supabase-data-handling
3
- description: |
4
- Implement Supabase PII handling, data retention, and GDPR/CCPA compliance patterns.
5
- Use when handling sensitive data, implementing data redaction, configuring retention policies,
6
- or ensuring compliance with privacy regulations for Supabase integrations.
7
- Trigger with phrases like "supabase data", "supabase PII",
8
- "supabase GDPR", "supabase data retention", "supabase privacy", "supabase CCPA".
9
- allowed-tools: Read, Write, Edit
3
+ description: 'Implement GDPR/CCPA compliance with Supabase: RLS for data isolation,
4
+ user deletion
5
+
6
+ via auth.admin.deleteUser(), data export via SQL, PII column management,
7
+
8
+ backup/restore workflows, and retention policies.
9
+
10
+ Use when handling sensitive data, implementing right-to-deletion, configuring data
11
+ retention,
12
+
13
+ or auditing PII in Supabase database columns.
14
+
15
+ Trigger: "supabase GDPR", "supabase data handling", "supabase PII", "supabase compliance",
16
+
17
+ "supabase data retention", "supabase delete user", "supabase data export".
18
+
19
+ '
20
+ allowed-tools: Read, Write, Edit, Bash(npx supabase:*), Bash(supabase:*), Bash(psql:*),
21
+ Grep, Glob
10
22
  version: 1.0.0
11
23
  license: MIT
12
24
  author: Jeremy Longshore <jeremy@intentsolutions.io>
25
+ tags:
26
+ - saas
27
+ - supabase
28
+ - gdpr
29
+ - ccpa
30
+ - compliance
31
+ - data-handling
32
+ - privacy
33
+ compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
13
34
  ---
14
-
15
35
  # Supabase Data Handling
16
36
 
17
37
  ## Overview
18
- Handle sensitive data correctly when integrating with Supabase.
38
+
39
+ GDPR and CCPA compliance with Supabase requires a layered approach: Row Level Security (RLS) for tenant data isolation, `supabase.auth.admin.deleteUser()` for right-to-deletion requests, SQL-based data exports for subject access requests, PII detection across database columns, automated retention policies using `pg_cron`, and point-in-time recovery for backup/restore. This skill implements every compliance requirement using real Supabase SDK methods and PostgreSQL features.
40
+
41
+ **When to use:** Implementing GDPR right-to-deletion, responding to data subject access requests (DSARs), auditing PII in your database, configuring automated data retention, setting up tenant isolation with RLS, or planning backup/restore procedures.
19
42
 
20
43
  ## Prerequisites
21
- - Understanding of GDPR/CCPA requirements
22
- - Supabase SDK with data export capabilities
23
- - Database for audit logging
24
- - Scheduled job infrastructure for cleanup
25
44
 
26
- ## Data Classification
45
+ - `@supabase/supabase-js` v2+ with service role key for admin operations
46
+ - Supabase project on Pro plan (for `pg_cron` and point-in-time recovery)
47
+ - Understanding of GDPR Articles 15-17 (access, rectification, erasure)
48
+ - Database access via SQL Editor or `psql` for schema changes
49
+
50
+ ## Instructions
51
+
52
+ ### Step 1: RLS for Data Isolation and PII Column Management
53
+
54
+ Configure Row Level Security to ensure users can only access their own data, and identify which columns contain PII.
55
+
56
+ **Tenant isolation with RLS:**
57
+
58
+ ```sql
59
+ -- Enable RLS on all tables containing user data
60
+ ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
61
+ ALTER TABLE public.orders ENABLE ROW LEVEL SECURITY;
62
+ ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;
63
+
64
+ -- Users can only read their own profile
65
+ CREATE POLICY "users_read_own_profile" ON public.profiles
66
+ FOR SELECT USING (auth.uid() = id);
67
+
68
+ -- Users can update their own profile
69
+ CREATE POLICY "users_update_own_profile" ON public.profiles
70
+ FOR UPDATE USING (auth.uid() = id)
71
+ WITH CHECK (auth.uid() = id);
72
+
73
+ -- Users can only see their own orders
74
+ CREATE POLICY "users_read_own_orders" ON public.orders
75
+ FOR SELECT USING (auth.uid() = user_id);
76
+
77
+ -- Organization-scoped isolation (multi-tenant)
78
+ CREATE POLICY "org_members_read_documents" ON public.documents
79
+ FOR SELECT USING (
80
+ org_id IN (
81
+ SELECT org_id FROM public.org_members
82
+ WHERE user_id = auth.uid()
83
+ )
84
+ );
85
+ ```
27
86
 
28
- | Category | Examples | Handling |
29
- |----------|----------|----------|
30
- | PII | Email, name, phone | Encrypt, minimize |
31
- | Sensitive | API keys, tokens | Never log, rotate |
32
- | Business | Usage metrics | Aggregate when possible |
33
- | Public | Product names | Standard handling |
87
+ **PII column audit identify sensitive data across your schema:**
88
+
89
+ ```sql
90
+ -- Find columns likely containing PII based on naming patterns
91
+ SELECT table_schema, table_name, column_name, data_type
92
+ FROM information_schema.columns
93
+ WHERE table_schema = 'public'
94
+ AND (
95
+ column_name ILIKE '%email%'
96
+ OR column_name ILIKE '%phone%'
97
+ OR column_name ILIKE '%name%'
98
+ OR column_name ILIKE '%address%'
99
+ OR column_name ILIKE '%ssn%'
100
+ OR column_name ILIKE '%birth%'
101
+ OR column_name ILIKE '%ip%'
102
+ OR column_name ILIKE '%location%'
103
+ )
104
+ ORDER BY table_name, column_name;
105
+
106
+ -- Add comments to mark PII columns for documentation
107
+ COMMENT ON COLUMN public.profiles.email IS 'PII: email address — GDPR Art. 4(1)';
108
+ COMMENT ON COLUMN public.profiles.full_name IS 'PII: personal name — GDPR Art. 4(1)';
109
+ COMMENT ON COLUMN public.profiles.phone IS 'PII: phone number — GDPR Art. 4(1)';
110
+
111
+ -- Create a PII registry view
112
+ CREATE OR REPLACE VIEW pii_registry AS
113
+ SELECT c.table_name, c.column_name, c.data_type,
114
+ pg_catalog.col_description(
115
+ (quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass,
116
+ c.ordinal_position
117
+ ) AS pii_classification
118
+ FROM information_schema.columns c
119
+ WHERE c.table_schema = 'public'
120
+ AND pg_catalog.col_description(
121
+ (quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass,
122
+ c.ordinal_position
123
+ ) LIKE 'PII:%';
124
+ ```
34
125
 
35
- ## PII Detection
126
+ **PII detection from the SDK:**
36
127
 
37
128
  ```typescript
38
- const PII_PATTERNS = [
39
- { type: 'email', regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g },
40
- { type: 'phone', regex: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g },
41
- { type: 'ssn', regex: /\b\d{3}-\d{2}-\d{4}\b/g },
42
- { type: 'credit_card', regex: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g },
43
- ];
44
-
45
- function detectPII(text: string): { type: string; match: string }[] {
46
- const findings: { type: string; match: string }[] = [];
47
-
48
- for (const pattern of PII_PATTERNS) {
49
- const matches = text.matchAll(pattern.regex);
50
- for (const match of matches) {
51
- findings.push({ type: pattern.type, match: match[0] });
129
+ import { createClient } from '@supabase/supabase-js';
130
+
131
+ const supabase = createClient(
132
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
133
+ process.env.SUPABASE_SERVICE_ROLE_KEY!,
134
+ { auth: { autoRefreshToken: false, persistSession: false } }
135
+ );
136
+
137
+ // Scan a table for PII patterns in text columns
138
+ async function scanTableForPII(tableName: string, sampleSize = 100) {
139
+ const PII_PATTERNS = [
140
+ { type: 'email', regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g },
141
+ { type: 'phone', regex: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g },
142
+ { type: 'ssn', regex: /\b\d{3}-\d{2}-\d{4}\b/g },
143
+ { type: 'ip_address', regex: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g },
144
+ ];
145
+
146
+ const { data, error } = await supabase
147
+ .from(tableName)
148
+ .select('*')
149
+ .limit(sampleSize);
150
+
151
+ if (error) throw error;
152
+
153
+ const findings: { column: string; type: string; count: number }[] = [];
154
+
155
+ for (const row of data ?? []) {
156
+ for (const [column, value] of Object.entries(row)) {
157
+ if (typeof value !== 'string') continue;
158
+ for (const pattern of PII_PATTERNS) {
159
+ const matches = value.match(pattern.regex);
160
+ if (matches) {
161
+ findings.push({ column, type: pattern.type, count: matches.length });
162
+ }
163
+ }
52
164
  }
53
165
  }
54
166
 
@@ -56,165 +168,286 @@ function detectPII(text: string): { type: string; match: string }[] {
56
168
  }
57
169
  ```
58
170
 
59
- ## Data Redaction
171
+ ### Step 2: User Deletion and Data Export
172
+
173
+ Implement GDPR Article 17 (right to erasure) with `auth.admin.deleteUser()` and Article 15 (right of access) with SQL-based data export.
174
+
175
+ **Right to deletion — complete user erasure:**
60
176
 
61
177
  ```typescript
62
- function redactPII(data: Record<string, any>): Record<string, any> {
63
- const sensitiveFields = ['email', 'phone', 'ssn', 'password', 'apiKey'];
64
- const redacted = { ...data };
178
+ import { createClient } from '@supabase/supabase-js';
179
+
180
+ const supabase = createClient(
181
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
182
+ process.env.SUPABASE_SERVICE_ROLE_KEY!,
183
+ { auth: { autoRefreshToken: false, persistSession: false } }
184
+ );
185
+
186
+ interface DeletionResult {
187
+ userId: string;
188
+ tablesProcessed: string[];
189
+ storageFilesDeleted: number;
190
+ authDeleted: boolean;
191
+ auditLogId: string;
192
+ completedAt: string;
193
+ }
65
194
 
66
- for (const field of sensitiveFields) {
67
- if (redacted[field]) {
68
- redacted[field] = '[REDACTED]';
195
+ async function deleteUserData(userId: string): Promise<DeletionResult> {
196
+ const tablesProcessed: string[] = [];
197
+ let storageFilesDeleted = 0;
198
+
199
+ // 1. Delete user data from application tables (cascade order)
200
+ const tablesToPurge = ['comments', 'orders', 'documents', 'profiles'];
201
+
202
+ for (const table of tablesToPurge) {
203
+ const { error } = await supabase
204
+ .from(table)
205
+ .delete()
206
+ .eq('user_id', userId);
207
+
208
+ if (error && !error.message.includes('does not exist')) {
209
+ console.error(`Failed to delete from ${table}:`, error.message);
210
+ } else {
211
+ tablesProcessed.push(table);
69
212
  }
70
213
  }
71
214
 
72
- return redacted;
73
- }
215
+ // 2. Delete user files from storage
216
+ const { data: buckets } = await supabase.storage.listBuckets();
217
+ for (const bucket of buckets ?? []) {
218
+ const { data: files } = await supabase.storage
219
+ .from(bucket.name)
220
+ .list(`users/${userId}`);
74
221
 
75
- // Use in logging
76
- console.log('Supabase request:', redactPII(requestData));
77
- ```
222
+ if (files && files.length > 0) {
223
+ const paths = files.map((f) => `users/${userId}/${f.name}`);
224
+ const { error } = await supabase.storage
225
+ .from(bucket.name)
226
+ .remove(paths);
78
227
 
79
- ## Data Retention Policy
228
+ if (!error) storageFilesDeleted += paths.length;
229
+ }
230
+ }
80
231
 
81
- ### Retention Periods
82
- | Data Type | Retention | Reason |
83
- |-----------|-----------|--------|
84
- | API logs | 30 days | Debugging |
85
- | Error logs | 90 days | Root cause analysis |
86
- | Audit logs | 7 years | Compliance |
87
- | PII | Until deletion request | GDPR/CCPA |
232
+ // 3. Delete the auth user (removes from auth.users)
233
+ const { error: authError } = await supabase.auth.admin.deleteUser(userId);
234
+ const authDeleted = !authError;
88
235
 
89
- ### Automatic Cleanup
236
+ if (authError) {
237
+ console.error('Auth deletion failed:', authError.message);
238
+ }
90
239
 
91
- ```typescript
92
- async function cleanupSupabaseData(retentionDays: number): Promise<void> {
93
- const cutoff = new Date();
94
- cutoff.setDate(cutoff.getDate() - retentionDays);
240
+ // 4. Create audit log entry (required — must survive deletion)
241
+ const { data: auditEntry } = await supabase
242
+ .from('gdpr_audit_log')
243
+ .insert({
244
+ action: 'USER_DELETION',
245
+ subject_id: userId,
246
+ tables_purged: tablesProcessed,
247
+ storage_files_deleted: storageFilesDeleted,
248
+ auth_deleted: authDeleted,
249
+ performed_by: 'system',
250
+ legal_basis: 'GDPR Article 17 — Right to Erasure',
251
+ })
252
+ .select('id')
253
+ .single();
95
254
 
96
- await db.supabaseLogs.deleteMany({
97
- createdAt: { $lt: cutoff },
98
- type: { $nin: ['audit', 'compliance'] },
99
- });
255
+ return {
256
+ userId,
257
+ tablesProcessed,
258
+ storageFilesDeleted,
259
+ authDeleted,
260
+ auditLogId: auditEntry?.id ?? 'unknown',
261
+ completedAt: new Date().toISOString(),
262
+ };
100
263
  }
101
264
 
102
- // Schedule daily cleanup
103
- cron.schedule('0 3 * * *', () => cleanupSupabaseData(30));
265
+ // GDPR audit log table (create this migration)
266
+ // CREATE TABLE gdpr_audit_log (
267
+ // id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
268
+ // action text NOT NULL,
269
+ // subject_id uuid NOT NULL,
270
+ // tables_purged text[] DEFAULT '{}',
271
+ // storage_files_deleted int DEFAULT 0,
272
+ // auth_deleted boolean DEFAULT false,
273
+ // performed_by text NOT NULL,
274
+ // legal_basis text,
275
+ // created_at timestamptz DEFAULT now()
276
+ // );
277
+ // -- Audit logs must NEVER be deleted (compliance requirement)
278
+ // ALTER TABLE gdpr_audit_log ENABLE ROW LEVEL SECURITY;
279
+ // CREATE POLICY "admin_only" ON gdpr_audit_log FOR ALL USING (false);
104
280
  ```
105
281
 
106
- ## GDPR/CCPA Compliance
107
-
108
- ### Data Subject Access Request (DSAR)
282
+ **Data subject access request (DSAR) — export all user data:**
109
283
 
110
284
  ```typescript
111
- async function exportUserData(userId: string): Promise<DataExport> {
112
- const supabaseData = await supabaseClient.getUserData(userId);
113
-
114
- return {
115
- source: 'Supabase',
116
- exportedAt: new Date().toISOString(),
117
- data: {
118
- profile: supabaseData.profile,
119
- activities: supabaseData.activities,
120
- // Include all user-related data
121
- },
122
- };
285
+ import { createClient } from '@supabase/supabase-js';
286
+
287
+ const supabase = createClient(
288
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
289
+ process.env.SUPABASE_SERVICE_ROLE_KEY!,
290
+ { auth: { autoRefreshToken: false, persistSession: false } }
291
+ );
292
+
293
+ interface DataExport {
294
+ exportedAt: string;
295
+ subjectId: string;
296
+ legalBasis: string;
297
+ data: Record<string, unknown[]>;
298
+ storageFiles: string[];
123
299
  }
124
- ```
125
300
 
126
- ### Right to Deletion
301
+ async function exportUserData(userId: string): Promise<DataExport> {
302
+ const exportData: Record<string, unknown[]> = {};
127
303
 
128
- ```typescript
129
- async function deleteUserData(userId: string): Promise<DeletionResult> {
130
- // 1. Delete from Supabase
131
- await supabaseClient.deleteUser(userId);
304
+ // Export from each table containing user data
305
+ const tables = ['profiles', 'orders', 'documents', 'comments'];
132
306
 
133
- // 2. Delete local copies
134
- await db.supabaseUserCache.deleteMany({ userId });
307
+ for (const table of tables) {
308
+ const { data, error } = await supabase
309
+ .from(table)
310
+ .select('*')
311
+ .eq('user_id', userId);
135
312
 
136
- // 3. Audit log (required to keep)
137
- await auditLog.record({
138
- action: 'GDPR_DELETION',
139
- userId,
140
- service: 'supabase',
141
- timestamp: new Date(),
142
- });
313
+ if (!error && data) {
314
+ exportData[table] = data;
315
+ }
316
+ }
143
317
 
144
- return { success: true, deletedAt: new Date() };
145
- }
146
- ```
318
+ // List user files in storage
319
+ const storageFiles: string[] = [];
320
+ const { data: buckets } = await supabase.storage.listBuckets();
321
+ for (const bucket of buckets ?? []) {
322
+ const { data: files } = await supabase.storage
323
+ .from(bucket.name)
324
+ .list(`users/${userId}`);
147
325
 
148
- ## Data Minimization
326
+ for (const file of files ?? []) {
327
+ storageFiles.push(`${bucket.name}/users/${userId}/${file.name}`);
328
+ }
329
+ }
149
330
 
150
- ```typescript
151
- // Only request needed fields
152
- const user = await supabaseClient.getUser(userId, {
153
- fields: ['id', 'name'], // Not email, phone, address
154
- });
331
+ // Log the export for compliance
332
+ await supabase.from('gdpr_audit_log').insert({
333
+ action: 'DATA_EXPORT',
334
+ subject_id: userId,
335
+ performed_by: 'system',
336
+ legal_basis: 'GDPR Article 15 — Right of Access',
337
+ });
155
338
 
156
- // Don't store unnecessary data
157
- const cacheData = {
158
- id: user.id,
159
- name: user.name,
160
- // Omit sensitive fields
161
- };
339
+ return {
340
+ exportedAt: new Date().toISOString(),
341
+ subjectId: userId,
342
+ legalBasis: 'GDPR Article 15 — Right of Access',
343
+ data: exportData,
344
+ storageFiles,
345
+ };
346
+ }
162
347
  ```
163
348
 
164
- ## Instructions
165
-
166
- ### Step 1: Classify Data
167
- Categorize all Supabase data by sensitivity level.
349
+ ### Step 3: Retention Policies and Backup/Restore
168
350
 
169
- ### Step 2: Implement PII Detection
170
- Add regex patterns to detect sensitive data in logs.
351
+ See [retention policies and backup/restore](references/retention-and-backup.md) for `pg_cron` automated retention schedules (30/90/730-day tiers), SDK-based retention monitoring, `pg_dump`/`pg_restore` commands, and point-in-time recovery configuration.
171
352
 
172
- ### Step 3: Configure Redaction
173
- Apply redaction to sensitive fields before logging.
353
+ ## Output
174
354
 
175
- ### Step 4: Set Up Retention
176
- Configure automatic cleanup with appropriate retention periods.
355
+ After completing this skill, you will have:
177
356
 
178
- ## Output
179
- - Data classification documented
180
- - PII detection implemented
181
- - Redaction in logging active
182
- - Retention policy enforced
357
+ - **RLS tenant isolation** — row-level security policies ensuring users only access their own data
358
+ - **PII column registry** — documented and classified PII columns across all tables
359
+ - **PII scanner** — SDK-based pattern detection for emails, phones, SSNs, and IPs in text columns
360
+ - **User deletion pipeline** — complete `auth.admin.deleteUser()` flow with cascade table deletion, storage cleanup, and audit logging
361
+ - **Data export** — DSAR-compliant export of all user data from tables and storage
362
+ - **GDPR audit log** — immutable log of all deletion and export operations with legal basis
363
+ - **Automated retention** — `pg_cron` jobs for 30/90/730-day retention tiers
364
+ - **Backup/restore** — `pg_dump`/`pg_restore` commands and PITR configuration
183
365
 
184
366
  ## Error Handling
185
- | Issue | Cause | Solution |
367
+
368
+ | Error | Cause | Solution |
186
369
  |-------|-------|----------|
187
- | PII in logs | Missing redaction | Wrap logging with redact |
188
- | Deletion failed | Data locked | Check dependencies |
189
- | Export incomplete | Timeout | Increase batch size |
190
- | Audit gap | Missing entries | Review log pipeline |
370
+ | `auth.admin.deleteUser()` returns 404 | User already deleted or wrong ID | Check `auth.users` table; may have been deleted by another process |
371
+ | `violates foreign key constraint` during deletion | Child rows reference user | Delete in cascade order (comments → orders → profiles) or use `ON DELETE CASCADE` |
372
+ | `permission denied for function cron.schedule` | `pg_cron` not enabled or wrong plan | Enable `pg_cron` extension; requires Supabase Pro plan |
373
+ | `pg_dump: connection refused` | Using wrong port or pooler URL | Use direct connection (port 5432), not pooler (port 6543) for `pg_dump` |
374
+ | `RLS policy blocks admin operations` | Service role key not used | Use `createClient` with `SUPABASE_SERVICE_ROLE_KEY` to bypass RLS |
375
+ | Audit log entries missing | Table has RLS blocking inserts | Use `SECURITY DEFINER` function or service role for audit writes |
376
+ | Retention job not running | `pg_cron` job disabled or errored | Check `cron.job_run_details` for error messages |
191
377
 
192
378
  ## Examples
193
379
 
194
- ### Quick PII Scan
380
+ **Example 1 Handle a GDPR deletion request:**
381
+
195
382
  ```typescript
196
- const findings = detectPII(JSON.stringify(userData));
197
- if (findings.length > 0) {
198
- console.warn(`PII detected: ${findings.map(f => f.type).join(', ')}`);
383
+ import { createClient } from '@supabase/supabase-js';
384
+
385
+ const supabase = createClient(url, serviceRoleKey, {
386
+ auth: { autoRefreshToken: false, persistSession: false },
387
+ });
388
+
389
+ // API endpoint for GDPR deletion
390
+ async function handleDeletionRequest(userId: string) {
391
+ // Verify the request is legitimate (e.g., authenticated user or admin)
392
+ const result = await deleteUserData(userId);
393
+
394
+ console.log(`User ${userId} deleted:`, {
395
+ tables: result.tablesProcessed.join(', '),
396
+ files: result.storageFilesDeleted,
397
+ auth: result.authDeleted,
398
+ auditId: result.auditLogId,
399
+ });
400
+
401
+ // GDPR requires completion within 30 days
402
+ return { status: 'completed', auditId: result.auditLogId };
199
403
  }
200
404
  ```
201
405
 
202
- ### Redact Before Logging
203
- ```typescript
204
- const safeData = redactPII(apiResponse);
205
- logger.info('Supabase response:', safeData);
406
+ **Example 2 Quick PII audit:**
407
+
408
+ ```sql
409
+ -- Count rows with email-like patterns in unexpected columns
410
+ SELECT 'profiles' AS table_name, 'bio' AS column_name,
411
+ count(*) AS rows_with_email
412
+ FROM public.profiles
413
+ WHERE bio ~ '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
414
+ UNION ALL
415
+ SELECT 'orders', 'notes',
416
+ count(*)
417
+ FROM public.orders
418
+ WHERE notes ~ '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}';
206
419
  ```
207
420
 
208
- ### GDPR Data Export
421
+ **Example 3 Verify retention job execution:**
422
+
209
423
  ```typescript
210
- const userExport = await exportUserData('user-123');
211
- await sendToUser(userExport);
424
+ import { createClient } from '@supabase/supabase-js';
425
+
426
+ const supabase = createClient(url, serviceRoleKey, {
427
+ auth: { autoRefreshToken: false, persistSession: false },
428
+ });
429
+
430
+ async function checkRetentionJobs() {
431
+ const { data, error } = await supabase.rpc('get_cron_status');
432
+ if (error) throw error;
433
+
434
+ for (const job of data ?? []) {
435
+ console.log(`Job "${job.jobname}": last_run=${job.last_run}, status=${job.status}`);
436
+ }
437
+ }
212
438
  ```
213
439
 
214
440
  ## Resources
215
- - [GDPR Developer Guide](https://gdpr.eu/developers/)
441
+
442
+ - [Row Level Security — Supabase Docs](https://supabase.com/docs/guides/database/postgres/row-level-security)
443
+ - [Auth Admin deleteUser — Supabase Docs](https://supabase.com/docs/reference/javascript/auth-admin-deleteuser)
444
+ - [Database Backups — Supabase Docs](https://supabase.com/docs/guides/platform/backups)
445
+ - [pg_cron Extension — Supabase Docs](https://supabase.com/docs/guides/database/extensions/pg_cron)
446
+ - GDPR Developer Guide
216
447
  - [CCPA Compliance Guide](https://oag.ca.gov/privacy/ccpa)
217
- - [Supabase Privacy Guide](https://supabase.com/docs/privacy)
218
448
 
219
449
  ## Next Steps
220
- For enterprise access control, see `supabase-enterprise-rbac`.
450
+
451
+ - For enterprise role-based access control, see `supabase-enterprise-rbac`
452
+ - For security hardening and API key scoping, see `supabase-security-basics`
453
+ - For observability and audit trail monitoring, see `supabase-observability`
@@ -0,0 +1,11 @@
1
+ # Error Handling Reference
2
+
3
+ | Issue | Cause | Solution |
4
+ |-------|-------|----------|
5
+ | PII in logs | Missing redaction | Wrap logging with redact |
6
+ | Deletion failed | Data locked | Check dependencies |
7
+ | Export incomplete | Timeout | Increase batch size |
8
+ | Audit gap | Missing entries | Review log pipeline |
9
+
10
+ ---
11
+ *[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
@@ -0,0 +1,27 @@
1
+ ## Examples
2
+
3
+ ### Quick PII Scan
4
+
5
+ ```typescript
6
+ const findings = detectPII(JSON.stringify(userData));
7
+ if (findings.length > 0) {
8
+ console.warn(`PII detected: ${findings.map(f => f.type).join(', ')}`);
9
+ }
10
+ ```
11
+
12
+ ### Redact Before Logging
13
+
14
+ ```typescript
15
+ const safeData = redactPII(apiResponse);
16
+ logger.info('Supabase response:', safeData);
17
+ ```
18
+
19
+ ### GDPR Data Export
20
+
21
+ ```typescript
22
+ const userExport = await exportUserData('user-123');
23
+ await sendToUser(userExport);
24
+ ```
25
+
26
+ ---
27
+ *[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*