@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.
- package/LICENSE +1 -1
- package/README.md +73 -47
- package/package.json +4 -4
- package/skills/supabase-advanced-troubleshooting/SKILL.md +404 -200
- package/skills/supabase-advanced-troubleshooting/references/errors.md +11 -0
- package/skills/supabase-advanced-troubleshooting/references/evidence-collection-framework.md +34 -0
- package/skills/supabase-advanced-troubleshooting/references/examples.md +11 -0
- package/skills/supabase-advanced-troubleshooting/references/rls-edge-functions-realtime.md +363 -0
- package/skills/supabase-advanced-troubleshooting/references/systematic-isolation.md +56 -0
- package/skills/supabase-advanced-troubleshooting/references/timing-analysis.md +35 -0
- package/skills/supabase-architecture-variants/SKILL.md +395 -216
- package/skills/supabase-architecture-variants/references/errors.md +11 -0
- package/skills/supabase-architecture-variants/references/examples.md +12 -0
- package/skills/supabase-architecture-variants/references/serverless-and-multi-tenant.md +251 -0
- package/skills/supabase-architecture-variants/references/variant-a-monolith-(simple).md +44 -0
- package/skills/supabase-architecture-variants/references/variant-b-service-layer-(moderate).md +72 -0
- package/skills/supabase-architecture-variants/references/variant-c-microservice-(complex).md +81 -0
- package/skills/supabase-auth-storage-realtime-core/SKILL.md +471 -37
- package/skills/supabase-ci-integration/SKILL.md +315 -67
- package/skills/supabase-ci-integration/references/errors.md +10 -0
- package/skills/supabase-ci-integration/references/examples.md +36 -0
- package/skills/supabase-ci-integration/references/implementation.md +54 -0
- package/skills/supabase-common-errors/SKILL.md +320 -62
- package/skills/supabase-common-errors/references/errors.md +53 -0
- package/skills/supabase-common-errors/references/examples.md +23 -0
- package/skills/supabase-cost-tuning/SKILL.md +365 -131
- package/skills/supabase-cost-tuning/references/cost-estimation.md +34 -0
- package/skills/supabase-cost-tuning/references/cost-reduction-strategies.md +40 -0
- package/skills/supabase-cost-tuning/references/errors.md +11 -0
- package/skills/supabase-cost-tuning/references/examples.md +15 -0
- package/skills/supabase-data-handling/SKILL.md +378 -145
- package/skills/supabase-data-handling/references/errors.md +11 -0
- package/skills/supabase-data-handling/references/examples.md +27 -0
- package/skills/supabase-data-handling/references/implementation.md +223 -0
- package/skills/supabase-data-handling/references/retention-and-backup.md +221 -0
- package/skills/supabase-debug-bundle/SKILL.md +267 -73
- package/skills/supabase-debug-bundle/references/errors.md +12 -0
- package/skills/supabase-debug-bundle/references/examples.md +24 -0
- package/skills/supabase-debug-bundle/references/implementation.md +54 -0
- package/skills/supabase-deploy-integration/SKILL.md +258 -147
- package/skills/supabase-deploy-integration/references/errors.md +11 -0
- package/skills/supabase-deploy-integration/references/examples.md +21 -0
- package/skills/supabase-deploy-integration/references/google-cloud-run.md +36 -0
- package/skills/supabase-deploy-integration/references/vercel-deployment.md +35 -0
- package/skills/supabase-enterprise-rbac/SKILL.md +327 -160
- package/skills/supabase-enterprise-rbac/references/api-scoping-and-enforcement.md +255 -0
- package/skills/supabase-enterprise-rbac/references/errors.md +11 -0
- package/skills/supabase-enterprise-rbac/references/examples.md +12 -0
- package/skills/supabase-enterprise-rbac/references/role-implementation.md +33 -0
- package/skills/supabase-enterprise-rbac/references/sso-integration.md +35 -0
- package/skills/supabase-hello-world/SKILL.md +160 -54
- package/skills/supabase-incident-runbook/SKILL.md +453 -131
- package/skills/supabase-incident-runbook/references/errors.md +11 -0
- package/skills/supabase-incident-runbook/references/examples.md +10 -0
- package/skills/supabase-incident-runbook/references/immediate-actions-by-error-type.md +41 -0
- package/skills/supabase-install-auth/SKILL.md +186 -50
- package/skills/supabase-install-auth/references/examples.md +102 -0
- package/skills/supabase-known-pitfalls/SKILL.md +411 -241
- package/skills/supabase-known-pitfalls/references/errors.md +11 -0
- package/skills/supabase-known-pitfalls/references/examples.md +12 -0
- package/skills/supabase-load-scale/SKILL.md +346 -217
- package/skills/supabase-load-scale/references/capacity-planning.md +47 -0
- package/skills/supabase-load-scale/references/errors.md +11 -0
- package/skills/supabase-load-scale/references/examples.md +26 -0
- package/skills/supabase-load-scale/references/load-testing-with-k6.md +59 -0
- package/skills/supabase-load-scale/references/scaling-patterns.md +65 -0
- package/skills/supabase-load-scale/references/table-partitioning.md +263 -0
- package/skills/supabase-local-dev-loop/SKILL.md +272 -73
- package/skills/supabase-local-dev-loop/references/errors.md +11 -0
- package/skills/supabase-local-dev-loop/references/examples.md +21 -0
- package/skills/supabase-local-dev-loop/references/implementation.md +60 -0
- package/skills/supabase-migration-deep-dive/SKILL.md +338 -177
- package/skills/supabase-migration-deep-dive/references/backfill-versioning-rollback.md +258 -0
- package/skills/supabase-migration-deep-dive/references/errors.md +11 -0
- package/skills/supabase-migration-deep-dive/references/examples.md +12 -0
- package/skills/supabase-migration-deep-dive/references/implementation-plan.md +80 -0
- package/skills/supabase-migration-deep-dive/references/pre-migration-assessment.md +39 -0
- package/skills/supabase-multi-env-setup/SKILL.md +393 -152
- package/skills/supabase-multi-env-setup/references/configuration-structure.md +59 -0
- package/skills/supabase-multi-env-setup/references/errors.md +11 -0
- package/skills/supabase-multi-env-setup/references/examples.md +11 -0
- package/skills/supabase-observability/SKILL.md +318 -196
- package/skills/supabase-observability/references/alert-configuration.md +40 -0
- package/skills/supabase-observability/references/errors.md +11 -0
- package/skills/supabase-observability/references/examples.md +13 -0
- package/skills/supabase-observability/references/metrics-collection.md +65 -0
- package/skills/supabase-performance-tuning/SKILL.md +304 -160
- package/skills/supabase-performance-tuning/references/caching-strategy.md +49 -0
- package/skills/supabase-performance-tuning/references/errors.md +11 -0
- package/skills/supabase-performance-tuning/references/examples.md +13 -0
- package/skills/supabase-policy-guardrails/SKILL.md +248 -221
- package/skills/supabase-policy-guardrails/references/ci-cost-security.md +484 -0
- package/skills/supabase-policy-guardrails/references/errors.md +11 -0
- package/skills/supabase-policy-guardrails/references/eslint-rules.md +46 -0
- package/skills/supabase-policy-guardrails/references/examples.md +10 -0
- package/skills/supabase-prod-checklist/SKILL.md +474 -84
- package/skills/supabase-prod-checklist/references/errors.md +63 -0
- package/skills/supabase-prod-checklist/references/examples.md +153 -0
- package/skills/supabase-prod-checklist/references/implementation.md +113 -0
- package/skills/supabase-rate-limits/SKILL.md +311 -98
- package/skills/supabase-rate-limits/references/errors.md +11 -0
- package/skills/supabase-rate-limits/references/examples.md +46 -0
- package/skills/supabase-rate-limits/references/implementation.md +66 -0
- package/skills/supabase-reference-architecture/SKILL.md +249 -182
- package/skills/supabase-reference-architecture/references/errors.md +29 -0
- package/skills/supabase-reference-architecture/references/examples.md +116 -0
- package/skills/supabase-reference-architecture/references/key-components.md +244 -0
- package/skills/supabase-reference-architecture/references/project-structure.md +109 -0
- package/skills/supabase-reliability-patterns/SKILL.md +229 -234
- package/skills/supabase-reliability-patterns/references/circuit-breaker.md +36 -0
- package/skills/supabase-reliability-patterns/references/dead-letter-queue.md +48 -0
- package/skills/supabase-reliability-patterns/references/errors.md +11 -0
- package/skills/supabase-reliability-patterns/references/examples.md +11 -0
- package/skills/supabase-reliability-patterns/references/idempotency-keys.md +36 -0
- package/skills/supabase-reliability-patterns/references/offline-degradation-health-dualwrite.md +489 -0
- package/skills/supabase-schema-from-requirements/SKILL.md +373 -34
- package/skills/supabase-sdk-patterns/SKILL.md +388 -99
- package/skills/supabase-sdk-patterns/references/errors.md +11 -0
- package/skills/supabase-sdk-patterns/references/examples.md +45 -0
- package/skills/supabase-sdk-patterns/references/implementation.md +67 -0
- package/skills/supabase-security-basics/SKILL.md +282 -102
- package/skills/supabase-security-basics/references/errors.md +10 -0
- package/skills/supabase-security-basics/references/examples.md +70 -0
- package/skills/supabase-security-basics/references/implementation.md +39 -0
- package/skills/supabase-upgrade-migration/SKILL.md +248 -66
- package/skills/supabase-upgrade-migration/references/errors.md +10 -0
- package/skills/supabase-upgrade-migration/references/examples.md +51 -0
- package/skills/supabase-upgrade-migration/references/implementation.md +29 -0
- package/skills/supabase-webhooks-events/SKILL.md +412 -138
- package/skills/supabase-webhooks-events/references/errors.md +55 -0
- package/skills/supabase-webhooks-events/references/event-handler-pattern.md +106 -0
- package/skills/supabase-webhooks-events/references/examples.md +133 -0
- package/skills/supabase-webhooks-events/references/signature-verification.md +165 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# API Key Scoping and Role Enforcement
|
|
2
|
+
|
|
3
|
+
Enforce roles at the application layer to complement RLS, and scope API operations by role.
|
|
4
|
+
|
|
5
|
+
**Server-side role enforcement middleware:**
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { createClient } from '@supabase/supabase-js';
|
|
9
|
+
import type { NextRequest } from 'next/server';
|
|
10
|
+
|
|
11
|
+
// Create a per-request client with the user's JWT
|
|
12
|
+
function createRequestClient(request: NextRequest) {
|
|
13
|
+
const token = request.headers.get('Authorization')?.replace('Bearer ', '');
|
|
14
|
+
|
|
15
|
+
return createClient(
|
|
16
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
17
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
18
|
+
{
|
|
19
|
+
global: {
|
|
20
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Role enforcement for API routes
|
|
27
|
+
async function withRole(
|
|
28
|
+
request: NextRequest,
|
|
29
|
+
requiredRole: AppRole,
|
|
30
|
+
handler: (supabase: ReturnType<typeof createClient>, user: any) => Promise<Response>
|
|
31
|
+
) {
|
|
32
|
+
const supabase = createRequestClient(request);
|
|
33
|
+
|
|
34
|
+
const { data: { user }, error } = await supabase.auth.getUser();
|
|
35
|
+
if (error || !user) {
|
|
36
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const userRole = user.app_metadata?.role as AppRole;
|
|
40
|
+
if (!userRole || !hasRole(userRole, requiredRole)) {
|
|
41
|
+
return Response.json(
|
|
42
|
+
{ error: `Forbidden: requires "${requiredRole}" role` },
|
|
43
|
+
{ status: 403 }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return handler(supabase, user);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Usage in Next.js App Router
|
|
51
|
+
export async function DELETE(request: NextRequest) {
|
|
52
|
+
return withRole(request, 'admin', async (supabase, user) => {
|
|
53
|
+
const projectId = request.nextUrl.searchParams.get('id');
|
|
54
|
+
|
|
55
|
+
const { error } = await supabase
|
|
56
|
+
.from('projects')
|
|
57
|
+
.delete()
|
|
58
|
+
.eq('id', projectId);
|
|
59
|
+
|
|
60
|
+
if (error) return Response.json({ error: error.message }, { status: 400 });
|
|
61
|
+
return Response.json({ deleted: true });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Admin panel — manage user roles:**
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { createClient } from '@supabase/supabase-js';
|
|
70
|
+
|
|
71
|
+
const adminClient = createClient(
|
|
72
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
73
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY!,
|
|
74
|
+
{ auth: { autoRefreshToken: false, persistSession: false } }
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// List all users in an organization with their roles
|
|
78
|
+
async function listOrgMembers(orgId: string) {
|
|
79
|
+
const { data, error } = await adminClient
|
|
80
|
+
.from('team_members')
|
|
81
|
+
.select(`
|
|
82
|
+
user_id,
|
|
83
|
+
role,
|
|
84
|
+
created_at,
|
|
85
|
+
profiles!inner(email, full_name)
|
|
86
|
+
`)
|
|
87
|
+
.eq('org_id', orgId)
|
|
88
|
+
.order('created_at', { ascending: false });
|
|
89
|
+
|
|
90
|
+
if (error) throw error;
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Invite a user to an organization
|
|
95
|
+
async function inviteToOrg(
|
|
96
|
+
email: string,
|
|
97
|
+
orgId: string,
|
|
98
|
+
role: AppRole,
|
|
99
|
+
invitedBy: string
|
|
100
|
+
) {
|
|
101
|
+
// Create or get the user
|
|
102
|
+
const { data: existingUsers } = await adminClient
|
|
103
|
+
.from('profiles')
|
|
104
|
+
.select('id')
|
|
105
|
+
.eq('email', email)
|
|
106
|
+
.single();
|
|
107
|
+
|
|
108
|
+
const userId = existingUsers?.id;
|
|
109
|
+
if (!userId) {
|
|
110
|
+
// Send invite email via Supabase Auth
|
|
111
|
+
const { error } = await adminClient.auth.admin.inviteUserByEmail(email, {
|
|
112
|
+
data: { org_id: orgId, role },
|
|
113
|
+
});
|
|
114
|
+
if (error) throw error;
|
|
115
|
+
return { status: 'invited' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Add existing user to org
|
|
119
|
+
const { error } = await adminClient.from('team_members').insert({
|
|
120
|
+
org_id: orgId,
|
|
121
|
+
user_id: userId,
|
|
122
|
+
role,
|
|
123
|
+
invited_by: invitedBy,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (error) throw error;
|
|
127
|
+
|
|
128
|
+
// Update user's app_metadata with org and role
|
|
129
|
+
await setUserRole(userId, role, orgId);
|
|
130
|
+
|
|
131
|
+
return { status: 'added', userId };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Change a user's role (admin only)
|
|
135
|
+
async function changeUserRole(
|
|
136
|
+
orgId: string,
|
|
137
|
+
targetUserId: string,
|
|
138
|
+
newRole: AppRole
|
|
139
|
+
) {
|
|
140
|
+
// Update team_members table
|
|
141
|
+
const { error: dbError } = await adminClient
|
|
142
|
+
.from('team_members')
|
|
143
|
+
.update({ role: newRole })
|
|
144
|
+
.eq('org_id', orgId)
|
|
145
|
+
.eq('user_id', targetUserId);
|
|
146
|
+
|
|
147
|
+
if (dbError) throw dbError;
|
|
148
|
+
|
|
149
|
+
// Update JWT claims
|
|
150
|
+
await setUserRole(targetUserId, newRole, orgId);
|
|
151
|
+
|
|
152
|
+
console.log(`User ${targetUserId} role changed to "${newRole}" in org ${orgId}`);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Output
|
|
157
|
+
|
|
158
|
+
After completing this skill, you will have:
|
|
159
|
+
|
|
160
|
+
- **Role assignment via app_metadata** — `admin.updateUserById()` sets role claims on user JWTs
|
|
161
|
+
- **JWT claim extraction** — `get_user_role()` and `get_user_org_id()` SQL helper functions
|
|
162
|
+
- **Role-based RLS policies** — SELECT/INSERT/UPDATE/DELETE scoped by role hierarchy (admin > editor > member > viewer)
|
|
163
|
+
- **Organization-scoped access** — multi-tenant isolation via `org_id` in JWT claims and RLS policies
|
|
164
|
+
- **Application-layer enforcement** — `withRole()` middleware for API routes with proper 401/403 responses
|
|
165
|
+
- **Admin panel operations** — list members, invite users, change roles with both database and JWT updates
|
|
166
|
+
- **Role hierarchy checking** — `hasRole()` function supporting role escalation comparison
|
|
167
|
+
|
|
168
|
+
## Error Handling
|
|
169
|
+
|
|
170
|
+
| Error | Cause | Solution |
|
|
171
|
+
|-------|-------|----------|
|
|
172
|
+
| `app_metadata.role` is null in JWT | Role not set or user needs to re-login | Call `admin.updateUserById()` to set role; user must refresh their session |
|
|
173
|
+
| RLS policy returns empty results | JWT claims don't match policy conditions | Check `auth.jwt()` output in SQL Editor; verify `app_metadata` was set correctly |
|
|
174
|
+
| `permission denied for function` | Helper function not created or wrong schema | Create `get_user_role()` in the `public` schema with `SECURITY DEFINER` |
|
|
175
|
+
| User role changes not reflected | JWT cached with old claims | User must sign out and sign in again, or call `supabase.auth.refreshSession()` |
|
|
176
|
+
| `duplicate key value violates unique constraint` | User already in organization | Check `team_members` table for existing entry before inserting |
|
|
177
|
+
| `foreign key violation` on team_members | User or org doesn't exist | Verify both `user_id` and `org_id` exist before inserting membership |
|
|
178
|
+
| Role hierarchy bypass | Direct database access with service role | Service role bypasses RLS by design — restrict its use to server-side admin operations only |
|
|
179
|
+
|
|
180
|
+
## Examples
|
|
181
|
+
|
|
182
|
+
**Example 1 — Quick role check in a component:**
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { createClient } from '@supabase/supabase-js';
|
|
186
|
+
|
|
187
|
+
const supabase = createClient(url, anonKey);
|
|
188
|
+
|
|
189
|
+
async function canEditProject(): Promise<boolean> {
|
|
190
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
191
|
+
const role = user?.app_metadata?.role;
|
|
192
|
+
return role === 'admin' || role === 'editor';
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Example 2 — Verify RLS policies work correctly:**
|
|
197
|
+
|
|
198
|
+
```sql
|
|
199
|
+
-- Test as an editor in org-123
|
|
200
|
+
SET request.jwt.claims = '{"sub": "user-uuid", "role": "authenticated", "app_metadata": {"role": "editor", "org_id": "org-123"}}';
|
|
201
|
+
|
|
202
|
+
-- Should return only org-123 projects
|
|
203
|
+
SELECT * FROM projects;
|
|
204
|
+
|
|
205
|
+
-- Should succeed (editors can create)
|
|
206
|
+
INSERT INTO projects (org_id, name, created_by) VALUES ('org-123', 'Test', 'user-uuid');
|
|
207
|
+
|
|
208
|
+
-- Should fail (editors cannot delete)
|
|
209
|
+
DELETE FROM projects WHERE id = 'some-project-id';
|
|
210
|
+
|
|
211
|
+
RESET request.jwt.claims;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Example 3 — Onboard a new organization:**
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
async function onboardOrganization(orgName: string, adminEmail: string) {
|
|
218
|
+
// 1. Create the organization
|
|
219
|
+
const { data: org } = await adminClient
|
|
220
|
+
.from('organizations')
|
|
221
|
+
.insert({ name: orgName, slug: orgName.toLowerCase().replace(/\s+/g, '-') })
|
|
222
|
+
.select('id')
|
|
223
|
+
.single();
|
|
224
|
+
|
|
225
|
+
// 2. Assign the creator as admin
|
|
226
|
+
const { data: { users } } = await adminClient.auth.admin.listUsers();
|
|
227
|
+
const adminUser = users.find((u) => u.email === adminEmail);
|
|
228
|
+
|
|
229
|
+
if (adminUser && org) {
|
|
230
|
+
await setUserRole(adminUser.id, 'admin', org.id);
|
|
231
|
+
await adminClient.from('team_members').insert({
|
|
232
|
+
org_id: org.id,
|
|
233
|
+
user_id: adminUser.id,
|
|
234
|
+
role: 'admin',
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return org;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Resources
|
|
243
|
+
|
|
244
|
+
- [Custom Claims and RBAC — Supabase Docs](https://supabase.com/docs/guides/database/postgres/custom-claims-and-role-based-access-control-rbac)
|
|
245
|
+
- [Auth Admin updateUserById — Supabase Docs](https://supabase.com/docs/reference/javascript/auth-admin-updateuserbyid)
|
|
246
|
+
- [Row Level Security — Supabase Docs](https://supabase.com/docs/guides/database/postgres/row-level-security)
|
|
247
|
+
- [auth.jwt() Function — Supabase Docs](https://supabase.com/docs/guides/database/postgres/row-level-security#helper-functions)
|
|
248
|
+
- [Multi-tenancy Patterns — Supabase Docs](https://supabase.com/docs/guides/getting-started/architecture#multi-tenancy)
|
|
249
|
+
- [inviteUserByEmail — Supabase Docs](https://supabase.com/docs/reference/javascript/auth-admin-inviteuserbyemail)
|
|
250
|
+
|
|
251
|
+
## Next Steps
|
|
252
|
+
|
|
253
|
+
- For database migration patterns, see `supabase-migration-deep-dive`
|
|
254
|
+
- For security hardening and API key scoping, see `supabase-security-basics`
|
|
255
|
+
- For data handling and GDPR compliance, see `supabase-data-handling`
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Error Handling Reference
|
|
2
|
+
|
|
3
|
+
| Issue | Cause | Solution |
|
|
4
|
+
|-------|-------|----------|
|
|
5
|
+
| SSO login fails | Wrong callback URL | Verify IdP config |
|
|
6
|
+
| Permission denied | Missing role mapping | Update group mappings |
|
|
7
|
+
| Token expired | Short TTL | Refresh token logic |
|
|
8
|
+
| Audit gaps | Async logging failed | Check 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,12 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
### Quick Permission Check
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
if (!checkPermission(user.role, 'write')) {
|
|
7
|
+
throw new ForbiddenError('Write permission required');
|
|
8
|
+
}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Role Implementation
|
|
2
|
+
|
|
3
|
+
## Role Implementation
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
enum SupabaseRole {
|
|
7
|
+
Admin = 'admin',
|
|
8
|
+
Developer = 'developer',
|
|
9
|
+
Viewer = 'viewer',
|
|
10
|
+
Service = 'service',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface SupabasePermissions {
|
|
14
|
+
read: boolean;
|
|
15
|
+
write: boolean;
|
|
16
|
+
delete: boolean;
|
|
17
|
+
admin: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ROLE_PERMISSIONS: Record<SupabaseRole, SupabasePermissions> = {
|
|
21
|
+
admin: { read: true, write: true, delete: true, admin: true },
|
|
22
|
+
developer: { read: true, write: true, delete: false, admin: false },
|
|
23
|
+
viewer: { read: true, write: false, delete: false, admin: false },
|
|
24
|
+
service: { read: true, write: true, delete: false, admin: false },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function checkPermission(
|
|
28
|
+
role: SupabaseRole,
|
|
29
|
+
action: keyof SupabasePermissions
|
|
30
|
+
): boolean {
|
|
31
|
+
return ROLE_PERMISSIONS[role][action];
|
|
32
|
+
}
|
|
33
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Sso Integration
|
|
2
|
+
|
|
3
|
+
## SSO Integration
|
|
4
|
+
|
|
5
|
+
### SAML Configuration
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// Supabase SAML setup
|
|
9
|
+
const samlConfig = {
|
|
10
|
+
entryPoint: 'https://idp.company.com/saml/sso',
|
|
11
|
+
issuer: 'https://supabase.com/saml/metadata',
|
|
12
|
+
cert: process.env.SAML_CERT,
|
|
13
|
+
callbackUrl: 'https://app.yourcompany.com/auth/supabase/callback',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Map IdP groups to Supabase roles
|
|
17
|
+
const groupRoleMapping: Record<string, SupabaseRole> = {
|
|
18
|
+
'Engineering': SupabaseRole.Developer,
|
|
19
|
+
'Platform-Admins': SupabaseRole.Admin,
|
|
20
|
+
'Data-Team': SupabaseRole.Viewer,
|
|
21
|
+
};
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### OAuth2/OIDC Integration
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { OAuth2Client } from '@supabase/supabase-js';
|
|
28
|
+
|
|
29
|
+
const oauthClient = new OAuth2Client({
|
|
30
|
+
clientId: process.env.SUPABASE_OAUTH_CLIENT_ID!,
|
|
31
|
+
clientSecret: process.env.SUPABASE_OAUTH_CLIENT_SECRET!,
|
|
32
|
+
redirectUri: 'https://app.yourcompany.com/auth/supabase/callback',
|
|
33
|
+
scopes: read, write, realtime,
|
|
34
|
+
});
|
|
35
|
+
```
|
|
@@ -1,96 +1,202 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: supabase-hello-world
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
allowed-tools: Read, Write, Edit
|
|
3
|
+
description: "Run your first Supabase query \u2014 insert a row and read it back.\n\
|
|
4
|
+
Use when starting a new Supabase project, verifying your connection works,\nor learning\
|
|
5
|
+
\ the basic insert-then-select pattern with @supabase/supabase-js.\nTrigger with\
|
|
6
|
+
\ phrases like \"supabase hello world\", \"first supabase query\",\n\"supabase quick\
|
|
7
|
+
\ start\", \"test supabase connection\", \"supabase insert and select\".\n"
|
|
8
|
+
allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(npx:*), Bash(supabase:*)
|
|
10
9
|
version: 1.0.0
|
|
11
10
|
license: MIT
|
|
12
11
|
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
12
|
+
tags:
|
|
13
|
+
- saas
|
|
14
|
+
- supabase
|
|
15
|
+
- database
|
|
16
|
+
- getting-started
|
|
17
|
+
compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
|
|
13
18
|
---
|
|
14
|
-
|
|
15
|
-
# Supabase Hello World
|
|
19
|
+
# Supabase Hello World — First Query
|
|
16
20
|
|
|
17
21
|
## Overview
|
|
18
|
-
|
|
22
|
+
|
|
23
|
+
Execute your first real Supabase query: create a `todos` table in the dashboard, insert a row with the JS client, and read it back. This validates that your project URL, anon key, and Row Level Security are configured correctly before you build anything else.
|
|
19
24
|
|
|
20
25
|
## Prerequisites
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
26
|
+
|
|
27
|
+
- Completed `supabase-install-auth` setup (project URL + anon key in `.env`)
|
|
28
|
+
- `@supabase/supabase-js` v2+ installed (`npm install @supabase/supabase-js`)
|
|
29
|
+
- A Supabase project at [supabase.com/dashboard](https://supabase.com/dashboard)
|
|
24
30
|
|
|
25
31
|
## Instructions
|
|
26
32
|
|
|
27
|
-
### Step 1: Create
|
|
28
|
-
|
|
33
|
+
### Step 1: Create the `todos` Table
|
|
34
|
+
|
|
35
|
+
Open your Supabase dashboard SQL Editor and run:
|
|
36
|
+
|
|
37
|
+
```sql
|
|
38
|
+
-- Create a simple todos table
|
|
39
|
+
create table public.todos (
|
|
40
|
+
id bigint generated always as identity primary key,
|
|
41
|
+
task text not null,
|
|
42
|
+
is_complete boolean default false,
|
|
43
|
+
inserted_at timestamptz default now()
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- Enable Row Level Security (required for anon key access)
|
|
47
|
+
alter table public.todos enable row level security;
|
|
48
|
+
|
|
49
|
+
-- Allow anyone with the anon key to read and insert
|
|
50
|
+
-- (permissive for hello-world; lock down before production)
|
|
51
|
+
create policy "Allow public read" on public.todos
|
|
52
|
+
for select using (true);
|
|
53
|
+
|
|
54
|
+
create policy "Allow public insert" on public.todos
|
|
55
|
+
for insert with check (true);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Verify the table appears under **Table Editor** in the dashboard before continuing.
|
|
59
|
+
|
|
60
|
+
### Step 2: Insert a Row
|
|
29
61
|
|
|
30
|
-
### Step 2: Import and Initialize Client
|
|
31
62
|
```typescript
|
|
32
|
-
import {
|
|
63
|
+
import { createClient } from '@supabase/supabase-js'
|
|
64
|
+
|
|
65
|
+
const supabase = createClient(
|
|
66
|
+
process.env.SUPABASE_URL!,
|
|
67
|
+
process.env.SUPABASE_ANON_KEY!
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
// Insert a row and return it with .select()
|
|
71
|
+
const { data, error } = await supabase
|
|
72
|
+
.from('todos')
|
|
73
|
+
.insert({ task: 'Hello from Supabase!' })
|
|
74
|
+
.select()
|
|
75
|
+
|
|
76
|
+
if (error) {
|
|
77
|
+
console.error('Insert failed:', error.message)
|
|
78
|
+
// e.g. "new row violates row-level security policy"
|
|
79
|
+
process.exit(1)
|
|
80
|
+
}
|
|
33
81
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
82
|
+
console.log('Inserted:', data)
|
|
83
|
+
// [{ id: 1, task: "Hello from Supabase!", is_complete: false, inserted_at: "2026-03-22T..." }]
|
|
37
84
|
```
|
|
38
85
|
|
|
39
|
-
|
|
86
|
+
Key detail: `.insert()` alone returns `{ data: null }`. You must chain `.select()` to get the inserted row back.
|
|
87
|
+
|
|
88
|
+
### Step 3: Read It Back
|
|
89
|
+
|
|
40
90
|
```typescript
|
|
41
|
-
|
|
42
|
-
|
|
91
|
+
// Select all rows from todos
|
|
92
|
+
const { data: todos, error: selectError } = await supabase
|
|
93
|
+
.from('todos')
|
|
94
|
+
.select('*')
|
|
95
|
+
|
|
96
|
+
if (selectError) {
|
|
97
|
+
console.error('Select failed:', selectError.message)
|
|
98
|
+
process.exit(1)
|
|
43
99
|
}
|
|
44
100
|
|
|
45
|
-
|
|
101
|
+
console.log('Todos:', todos)
|
|
102
|
+
// [{ id: 1, task: "Hello from Supabase!", is_complete: false, inserted_at: "2026-03-22T..." }]
|
|
103
|
+
|
|
104
|
+
// Verify the round-trip
|
|
105
|
+
if (todos && todos.length > 0) {
|
|
106
|
+
console.log('Round-trip verified — row exists in database')
|
|
107
|
+
} else {
|
|
108
|
+
console.error('No rows returned. Check RLS policies.')
|
|
109
|
+
}
|
|
46
110
|
```
|
|
47
111
|
|
|
112
|
+
Open the **Table Editor** in the Supabase dashboard to visually confirm the row is there.
|
|
113
|
+
|
|
48
114
|
## Output
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```
|
|
115
|
+
|
|
116
|
+
- `todos` table created with RLS enabled
|
|
117
|
+
- One row inserted via the JS client
|
|
118
|
+
- Same row read back with `.select('*')`
|
|
119
|
+
- Dashboard confirms the data round-trip
|
|
55
120
|
|
|
56
121
|
## Error Handling
|
|
122
|
+
|
|
57
123
|
| Error | Cause | Solution |
|
|
58
124
|
|-------|-------|----------|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
125
|
+
| `relation "public.todos" does not exist` | Table not created | Run the Step 1 SQL in the dashboard SQL Editor |
|
|
126
|
+
| `new row violates row-level security policy` | RLS blocks the insert | Add the permissive insert policy from Step 1 |
|
|
127
|
+
| `Invalid API key` | Wrong anon key in `.env` | Copy from Settings > API in the dashboard |
|
|
128
|
+
| `FetchError: request to https://... failed` | Wrong project URL | Verify `SUPABASE_URL` matches dashboard URL |
|
|
129
|
+
| `data` is `null` after insert | Missing `.select()` chain | Add `.select()` after `.insert()` |
|
|
130
|
+
| Empty array returned from select | RLS blocks reads | Add the select policy from Step 1 |
|
|
63
131
|
|
|
64
132
|
## Examples
|
|
65
133
|
|
|
66
|
-
### TypeScript
|
|
67
|
-
```typescript
|
|
68
|
-
import { SupabaseClient } from '@supabase/supabase-js';
|
|
69
|
-
|
|
70
|
-
const client = new SupabaseClient({
|
|
71
|
-
apiKey: process.env.SUPABASE_API_KEY,
|
|
72
|
-
});
|
|
134
|
+
### TypeScript (Complete Script)
|
|
73
135
|
|
|
74
|
-
|
|
75
|
-
|
|
136
|
+
```typescript
|
|
137
|
+
import { createClient } from '@supabase/supabase-js'
|
|
138
|
+
|
|
139
|
+
const supabase = createClient(
|
|
140
|
+
process.env.SUPABASE_URL!,
|
|
141
|
+
process.env.SUPABASE_ANON_KEY!
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
async function helloSupabase() {
|
|
145
|
+
// Insert
|
|
146
|
+
const { data: inserted, error: insertErr } = await supabase
|
|
147
|
+
.from('todos')
|
|
148
|
+
.insert({ task: 'Hello from TypeScript!' })
|
|
149
|
+
.select()
|
|
150
|
+
.single()
|
|
151
|
+
|
|
152
|
+
if (insertErr) throw new Error(`Insert: ${insertErr.message}`)
|
|
153
|
+
console.log('Inserted:', inserted)
|
|
154
|
+
|
|
155
|
+
// Read back
|
|
156
|
+
const { data: rows, error: selectErr } = await supabase
|
|
157
|
+
.from('todos')
|
|
158
|
+
.select('*')
|
|
159
|
+
.order('inserted_at', { ascending: false })
|
|
160
|
+
.limit(5)
|
|
161
|
+
|
|
162
|
+
if (selectErr) throw new Error(`Select: ${selectErr.message}`)
|
|
163
|
+
console.log('Recent todos:', rows)
|
|
76
164
|
}
|
|
77
165
|
|
|
78
|
-
|
|
166
|
+
helloSupabase().catch(console.error)
|
|
79
167
|
```
|
|
80
168
|
|
|
81
|
-
### Python
|
|
82
|
-
```python
|
|
83
|
-
from supabase import SupabaseClient
|
|
84
|
-
|
|
85
|
-
client = SupabaseClient()
|
|
169
|
+
### Python
|
|
86
170
|
|
|
87
|
-
|
|
171
|
+
```python
|
|
172
|
+
from supabase import create_client
|
|
173
|
+
import os
|
|
174
|
+
|
|
175
|
+
supabase = create_client(
|
|
176
|
+
os.environ["SUPABASE_URL"],
|
|
177
|
+
os.environ["SUPABASE_ANON_KEY"]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Insert a row
|
|
181
|
+
result = supabase.table("todos").insert({"task": "Hello from Python!"}).execute()
|
|
182
|
+
print("Inserted:", result.data)
|
|
183
|
+
# [{"id": 2, "task": "Hello from Python!", "is_complete": False, ...}]
|
|
184
|
+
|
|
185
|
+
# Read it back
|
|
186
|
+
result = supabase.table("todos").select("*").execute()
|
|
187
|
+
print("All todos:", result.data)
|
|
88
188
|
```
|
|
89
189
|
|
|
190
|
+
Install the Python client with: `pip install supabase`
|
|
191
|
+
|
|
90
192
|
## Resources
|
|
91
|
-
|
|
92
|
-
- [Supabase
|
|
93
|
-
- [
|
|
193
|
+
|
|
194
|
+
- [Supabase Getting Started](https://supabase.com/docs/guides/getting-started)
|
|
195
|
+
- [JS Client — Insert](https://supabase.com/docs/reference/javascript/insert)
|
|
196
|
+
- [JS Client — Select](https://supabase.com/docs/reference/javascript/select)
|
|
197
|
+
- [Python Client](https://supabase.com/docs/reference/python/introduction)
|
|
198
|
+
- [Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security)
|
|
94
199
|
|
|
95
200
|
## Next Steps
|
|
96
|
-
|
|
201
|
+
|
|
202
|
+
Proceed to `supabase-local-dev-loop` for local development workflow with the Supabase CLI.
|