ship-safe 1.0.1 → 3.0.0
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/README.md +281 -23
- package/ai-defense/cost-protection.md +292 -0
- package/ai-defense/llm-security-checklist.md +324 -0
- package/ai-defense/prompt-injection-patterns.js +283 -0
- package/cli/bin/ship-safe.js +44 -2
- package/cli/commands/fix.js +216 -0
- package/cli/commands/guard.js +297 -0
- package/cli/commands/mcp.js +303 -0
- package/cli/commands/scan.js +231 -39
- package/cli/utils/entropy.js +126 -0
- package/cli/utils/output.js +10 -1
- package/cli/utils/patterns.js +376 -24
- package/configs/firebase/firestore-rules.txt +215 -0
- package/configs/firebase/security-checklist.md +236 -0
- package/configs/firebase/storage-rules.txt +206 -0
- package/configs/ship-safeignore-template +50 -0
- package/configs/supabase/rls-templates.sql +242 -0
- package/configs/supabase/secure-client.ts +225 -0
- package/configs/supabase/security-checklist.md +278 -0
- package/package.json +11 -2
- package/snippets/README.md +89 -25
- package/snippets/api-security/api-security-checklist.md +412 -0
- package/snippets/api-security/cors-config.ts +322 -0
- package/snippets/api-security/input-validation.ts +430 -0
- package/snippets/auth/jwt-checklist.md +322 -0
- package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
- package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# .ship-safeignore
|
|
2
|
+
# =================
|
|
3
|
+
# Exclude paths from ship-safe secret scanning.
|
|
4
|
+
# Same syntax as .gitignore — one pattern per line, # for comments.
|
|
5
|
+
#
|
|
6
|
+
# Ship-safe already skips test files by default.
|
|
7
|
+
# Use this file to exclude additional paths that generate false positives.
|
|
8
|
+
#
|
|
9
|
+
# USAGE:
|
|
10
|
+
# Copy this file to your project root as .ship-safeignore
|
|
11
|
+
# Then run: npx ship-safe scan .
|
|
12
|
+
|
|
13
|
+
# ── Examples ──────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
# Exclude a specific file
|
|
16
|
+
# config/seed-data.js
|
|
17
|
+
|
|
18
|
+
# Exclude a directory
|
|
19
|
+
# scripts/fixtures/
|
|
20
|
+
|
|
21
|
+
# Exclude all files matching a pattern
|
|
22
|
+
# **/*.example.js
|
|
23
|
+
# **/*.sample.*
|
|
24
|
+
|
|
25
|
+
# Exclude documentation that contains example credentials
|
|
26
|
+
# docs/
|
|
27
|
+
# *.md
|
|
28
|
+
|
|
29
|
+
# Exclude generated files
|
|
30
|
+
# generated/
|
|
31
|
+
# prisma/migrations/
|
|
32
|
+
|
|
33
|
+
# Exclude vendor/third-party code not in node_modules
|
|
34
|
+
# vendor/
|
|
35
|
+
# third-party/
|
|
36
|
+
|
|
37
|
+
# ── Common false positive sources ─────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
# E2E test fixtures (ship-safe skips unit tests but not e2e by default)
|
|
40
|
+
# e2e/
|
|
41
|
+
# cypress/fixtures/
|
|
42
|
+
# playwright/
|
|
43
|
+
|
|
44
|
+
# Seed data with example values
|
|
45
|
+
# prisma/seed.ts
|
|
46
|
+
# database/seeds/
|
|
47
|
+
|
|
48
|
+
# Infrastructure-as-code with example configs
|
|
49
|
+
# terraform/examples/
|
|
50
|
+
# .terraform/
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- SUPABASE ROW LEVEL SECURITY (RLS) TEMPLATES
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
--
|
|
5
|
+
-- Copy-paste these policies to secure your Supabase tables.
|
|
6
|
+
--
|
|
7
|
+
-- WHY RLS MATTERS:
|
|
8
|
+
-- Without RLS, anyone with your anon key can read/write ALL data.
|
|
9
|
+
-- 83% of exposed Supabase databases have RLS misconfigurations.
|
|
10
|
+
-- Source: https://byteiota.com/supabase-security-flaw-170-apps-exposed-by-missing-rls/
|
|
11
|
+
--
|
|
12
|
+
-- HOW TO USE:
|
|
13
|
+
-- 1. Enable RLS on your table: ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
|
|
14
|
+
-- 2. Copy the relevant policy below
|
|
15
|
+
-- 3. Replace 'your_table' with your actual table name
|
|
16
|
+
-- 4. Test with different user contexts
|
|
17
|
+
--
|
|
18
|
+
-- =============================================================================
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
-- =============================================================================
|
|
22
|
+
-- STEP 1: ALWAYS ENABLE RLS FIRST
|
|
23
|
+
-- =============================================================================
|
|
24
|
+
|
|
25
|
+
-- Enable RLS on a table (REQUIRED before adding policies)
|
|
26
|
+
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
|
|
27
|
+
|
|
28
|
+
-- Force RLS for table owners too (recommended for security)
|
|
29
|
+
ALTER TABLE your_table FORCE ROW LEVEL SECURITY;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
-- =============================================================================
|
|
33
|
+
-- PATTERN 1: USER OWNS THEIR DATA
|
|
34
|
+
-- =============================================================================
|
|
35
|
+
-- Use when: Each user should only see/edit their own records
|
|
36
|
+
-- Example tables: profiles, settings, user_preferences
|
|
37
|
+
|
|
38
|
+
-- Users can only SELECT their own rows
|
|
39
|
+
CREATE POLICY "Users can view own data"
|
|
40
|
+
ON your_table
|
|
41
|
+
FOR SELECT
|
|
42
|
+
USING (auth.uid() = user_id);
|
|
43
|
+
|
|
44
|
+
-- Users can only INSERT rows with their own user_id
|
|
45
|
+
CREATE POLICY "Users can insert own data"
|
|
46
|
+
ON your_table
|
|
47
|
+
FOR INSERT
|
|
48
|
+
WITH CHECK (auth.uid() = user_id);
|
|
49
|
+
|
|
50
|
+
-- Users can only UPDATE their own rows
|
|
51
|
+
CREATE POLICY "Users can update own data"
|
|
52
|
+
ON your_table
|
|
53
|
+
FOR UPDATE
|
|
54
|
+
USING (auth.uid() = user_id)
|
|
55
|
+
WITH CHECK (auth.uid() = user_id);
|
|
56
|
+
|
|
57
|
+
-- Users can only DELETE their own rows
|
|
58
|
+
CREATE POLICY "Users can delete own data"
|
|
59
|
+
ON your_table
|
|
60
|
+
FOR DELETE
|
|
61
|
+
USING (auth.uid() = user_id);
|
|
62
|
+
|
|
63
|
+
-- COMBINED: All operations for own data (simpler but less granular)
|
|
64
|
+
CREATE POLICY "Users manage own data"
|
|
65
|
+
ON your_table
|
|
66
|
+
FOR ALL
|
|
67
|
+
USING (auth.uid() = user_id)
|
|
68
|
+
WITH CHECK (auth.uid() = user_id);
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
-- =============================================================================
|
|
72
|
+
-- PATTERN 2: ORGANIZATION/TEAM BASED ACCESS
|
|
73
|
+
-- =============================================================================
|
|
74
|
+
-- Use when: Users belong to orgs and should see all org data
|
|
75
|
+
-- Example tables: projects, documents, team_settings
|
|
76
|
+
|
|
77
|
+
-- First, create a helper function to get user's org
|
|
78
|
+
CREATE OR REPLACE FUNCTION get_user_org_id()
|
|
79
|
+
RETURNS UUID AS $$
|
|
80
|
+
SELECT org_id FROM profiles WHERE id = auth.uid()
|
|
81
|
+
$$ LANGUAGE SQL SECURITY DEFINER;
|
|
82
|
+
|
|
83
|
+
-- Users can view all data in their organization
|
|
84
|
+
CREATE POLICY "Org members can view org data"
|
|
85
|
+
ON your_table
|
|
86
|
+
FOR SELECT
|
|
87
|
+
USING (org_id = get_user_org_id());
|
|
88
|
+
|
|
89
|
+
-- Users can insert data into their organization
|
|
90
|
+
CREATE POLICY "Org members can insert org data"
|
|
91
|
+
ON your_table
|
|
92
|
+
FOR INSERT
|
|
93
|
+
WITH CHECK (org_id = get_user_org_id());
|
|
94
|
+
|
|
95
|
+
-- Only allow updates to org data
|
|
96
|
+
CREATE POLICY "Org members can update org data"
|
|
97
|
+
ON your_table
|
|
98
|
+
FOR UPDATE
|
|
99
|
+
USING (org_id = get_user_org_id())
|
|
100
|
+
WITH CHECK (org_id = get_user_org_id());
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
-- =============================================================================
|
|
104
|
+
-- PATTERN 3: ROLE-BASED ACCESS CONTROL (RBAC)
|
|
105
|
+
-- =============================================================================
|
|
106
|
+
-- Use when: Different users have different permission levels
|
|
107
|
+
-- Example: admin, editor, viewer roles
|
|
108
|
+
|
|
109
|
+
-- Helper function to check user role
|
|
110
|
+
CREATE OR REPLACE FUNCTION get_user_role()
|
|
111
|
+
RETURNS TEXT AS $$
|
|
112
|
+
SELECT role FROM profiles WHERE id = auth.uid()
|
|
113
|
+
$$ LANGUAGE SQL SECURITY DEFINER;
|
|
114
|
+
|
|
115
|
+
-- Anyone authenticated can view (public within app)
|
|
116
|
+
CREATE POLICY "Authenticated users can view"
|
|
117
|
+
ON your_table
|
|
118
|
+
FOR SELECT
|
|
119
|
+
USING (auth.role() = 'authenticated');
|
|
120
|
+
|
|
121
|
+
-- Only admins and editors can insert
|
|
122
|
+
CREATE POLICY "Admins and editors can insert"
|
|
123
|
+
ON your_table
|
|
124
|
+
FOR INSERT
|
|
125
|
+
WITH CHECK (get_user_role() IN ('admin', 'editor'));
|
|
126
|
+
|
|
127
|
+
-- Only admins can delete
|
|
128
|
+
CREATE POLICY "Only admins can delete"
|
|
129
|
+
ON your_table
|
|
130
|
+
FOR DELETE
|
|
131
|
+
USING (get_user_role() = 'admin');
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
-- =============================================================================
|
|
135
|
+
-- PATTERN 4: PUBLIC READ, AUTHENTICATED WRITE
|
|
136
|
+
-- =============================================================================
|
|
137
|
+
-- Use when: Content is public but only logged-in users can contribute
|
|
138
|
+
-- Example tables: blog_posts, comments, public_profiles
|
|
139
|
+
|
|
140
|
+
-- Anyone can read (including anonymous)
|
|
141
|
+
CREATE POLICY "Public read access"
|
|
142
|
+
ON your_table
|
|
143
|
+
FOR SELECT
|
|
144
|
+
USING (true);
|
|
145
|
+
|
|
146
|
+
-- Only authenticated users can insert
|
|
147
|
+
CREATE POLICY "Authenticated users can insert"
|
|
148
|
+
ON your_table
|
|
149
|
+
FOR INSERT
|
|
150
|
+
WITH CHECK (auth.role() = 'authenticated');
|
|
151
|
+
|
|
152
|
+
-- Users can only update their own posts
|
|
153
|
+
CREATE POLICY "Users can update own posts"
|
|
154
|
+
ON your_table
|
|
155
|
+
FOR UPDATE
|
|
156
|
+
USING (auth.uid() = author_id)
|
|
157
|
+
WITH CHECK (auth.uid() = author_id);
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
-- =============================================================================
|
|
161
|
+
-- PATTERN 5: PRIVATE BY DEFAULT (Explicit sharing)
|
|
162
|
+
-- =============================================================================
|
|
163
|
+
-- Use when: Data is private unless explicitly shared
|
|
164
|
+
-- Example tables: documents, files with sharing
|
|
165
|
+
|
|
166
|
+
-- Owner can always access
|
|
167
|
+
CREATE POLICY "Owner full access"
|
|
168
|
+
ON documents
|
|
169
|
+
FOR ALL
|
|
170
|
+
USING (auth.uid() = owner_id)
|
|
171
|
+
WITH CHECK (auth.uid() = owner_id);
|
|
172
|
+
|
|
173
|
+
-- Shared users can view (requires a shares table)
|
|
174
|
+
CREATE POLICY "Shared users can view"
|
|
175
|
+
ON documents
|
|
176
|
+
FOR SELECT
|
|
177
|
+
USING (
|
|
178
|
+
auth.uid() = owner_id
|
|
179
|
+
OR
|
|
180
|
+
EXISTS (
|
|
181
|
+
SELECT 1 FROM document_shares
|
|
182
|
+
WHERE document_shares.document_id = documents.id
|
|
183
|
+
AND document_shares.user_id = auth.uid()
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
-- =============================================================================
|
|
189
|
+
-- PATTERN 6: TIME-BASED ACCESS
|
|
190
|
+
-- =============================================================================
|
|
191
|
+
-- Use when: Content has publish dates or expiration
|
|
192
|
+
-- Example tables: scheduled_posts, limited_offers
|
|
193
|
+
|
|
194
|
+
-- Only show published content
|
|
195
|
+
CREATE POLICY "Show only published content"
|
|
196
|
+
ON posts
|
|
197
|
+
FOR SELECT
|
|
198
|
+
USING (
|
|
199
|
+
published_at IS NOT NULL
|
|
200
|
+
AND published_at <= NOW()
|
|
201
|
+
AND (expires_at IS NULL OR expires_at > NOW())
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
-- Authors can always see their drafts
|
|
205
|
+
CREATE POLICY "Authors see own drafts"
|
|
206
|
+
ON posts
|
|
207
|
+
FOR SELECT
|
|
208
|
+
USING (auth.uid() = author_id);
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
-- =============================================================================
|
|
212
|
+
-- ANTI-PATTERNS: DON'T DO THIS
|
|
213
|
+
-- =============================================================================
|
|
214
|
+
|
|
215
|
+
-- BAD: Allows anyone to read everything
|
|
216
|
+
-- CREATE POLICY "bad_policy" ON users FOR SELECT USING (true);
|
|
217
|
+
|
|
218
|
+
-- BAD: No WITH CHECK means users could insert data for other users
|
|
219
|
+
-- CREATE POLICY "bad_insert" ON posts FOR INSERT WITH CHECK (true);
|
|
220
|
+
|
|
221
|
+
-- BAD: Missing USING clause on UPDATE allows updating any row
|
|
222
|
+
-- CREATE POLICY "bad_update" ON posts FOR UPDATE WITH CHECK (auth.uid() = user_id);
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
-- =============================================================================
|
|
226
|
+
-- TESTING YOUR POLICIES
|
|
227
|
+
-- =============================================================================
|
|
228
|
+
|
|
229
|
+
-- Test as a specific user (in SQL editor)
|
|
230
|
+
-- SET request.jwt.claim.sub = 'user-uuid-here';
|
|
231
|
+
-- SELECT * FROM your_table;
|
|
232
|
+
|
|
233
|
+
-- Check existing policies on a table
|
|
234
|
+
SELECT * FROM pg_policies WHERE tablename = 'your_table';
|
|
235
|
+
|
|
236
|
+
-- List all tables without RLS enabled (DANGER!)
|
|
237
|
+
SELECT schemaname, tablename
|
|
238
|
+
FROM pg_tables
|
|
239
|
+
WHERE schemaname = 'public'
|
|
240
|
+
AND tablename NOT IN (
|
|
241
|
+
SELECT tablename FROM pg_policies
|
|
242
|
+
);
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase Secure Client Configuration
|
|
3
|
+
* =====================================
|
|
4
|
+
*
|
|
5
|
+
* Copy this file to your project and customize for your needs.
|
|
6
|
+
*
|
|
7
|
+
* WHY THIS MATTERS:
|
|
8
|
+
* - Separates anon (public) and service_role (admin) clients
|
|
9
|
+
* - Adds type safety for environment variables
|
|
10
|
+
* - Includes helpers for common secure patterns
|
|
11
|
+
*
|
|
12
|
+
* USAGE:
|
|
13
|
+
* // In client components (React, Vue, etc.)
|
|
14
|
+
* import { supabase } from '@/lib/supabase';
|
|
15
|
+
*
|
|
16
|
+
* // In server-side code (API routes, server components)
|
|
17
|
+
* import { supabaseAdmin } from '@/lib/supabase';
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// ENVIRONMENT VARIABLE VALIDATION
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validates that required environment variables are set.
|
|
28
|
+
* Call this at app startup to fail fast if misconfigured.
|
|
29
|
+
*/
|
|
30
|
+
function validateEnv() {
|
|
31
|
+
const required = ['NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY'];
|
|
32
|
+
|
|
33
|
+
for (const key of required) {
|
|
34
|
+
if (!process.env[key]) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Missing required environment variable: ${key}\n` +
|
|
37
|
+
`Add it to your .env.local file.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Validate on module load (comment out if causing issues in edge runtime)
|
|
44
|
+
// validateEnv();
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// SUPABASE URL AND KEYS
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
51
|
+
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
|
52
|
+
|
|
53
|
+
// Service role key - ONLY available server-side
|
|
54
|
+
// This key bypasses RLS and should NEVER be exposed to the client
|
|
55
|
+
const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// CLIENT-SIDE SUPABASE CLIENT (uses anon key)
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Public Supabase client for use in browser/client components.
|
|
63
|
+
*
|
|
64
|
+
* SECURITY NOTES:
|
|
65
|
+
* - Uses the anon key (safe to expose)
|
|
66
|
+
* - All operations are subject to RLS policies
|
|
67
|
+
* - User must be authenticated for protected operations
|
|
68
|
+
*/
|
|
69
|
+
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
|
70
|
+
auth: {
|
|
71
|
+
// Persist sessions in localStorage
|
|
72
|
+
persistSession: true,
|
|
73
|
+
// Automatically refresh tokens
|
|
74
|
+
autoRefreshToken: true,
|
|
75
|
+
// Detect session from URL (for OAuth callbacks)
|
|
76
|
+
detectSessionInUrl: true,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// SERVER-SIDE SUPABASE CLIENT (uses service_role key)
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Admin Supabase client for use in server-side code ONLY.
|
|
86
|
+
*
|
|
87
|
+
* SECURITY NOTES:
|
|
88
|
+
* - Uses the service_role key (NEVER expose to client)
|
|
89
|
+
* - Bypasses ALL Row Level Security policies
|
|
90
|
+
* - Use only in: API routes, server actions, cron jobs
|
|
91
|
+
* - Always validate user permissions before using
|
|
92
|
+
*
|
|
93
|
+
* WHEN TO USE:
|
|
94
|
+
* - Admin operations (deleting users, bulk updates)
|
|
95
|
+
* - Background jobs that need full database access
|
|
96
|
+
* - Webhooks from external services
|
|
97
|
+
*
|
|
98
|
+
* WHEN NOT TO USE:
|
|
99
|
+
* - Any client-side code
|
|
100
|
+
* - Operations where user context matters
|
|
101
|
+
* - When RLS should apply
|
|
102
|
+
*/
|
|
103
|
+
export const supabaseAdmin: SupabaseClient | null = supabaseServiceRoleKey
|
|
104
|
+
? createClient(supabaseUrl, supabaseServiceRoleKey, {
|
|
105
|
+
auth: {
|
|
106
|
+
// Don't persist sessions for admin client
|
|
107
|
+
persistSession: false,
|
|
108
|
+
autoRefreshToken: false,
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
: null;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get admin client with error if not configured.
|
|
115
|
+
* Use this when service_role key is required.
|
|
116
|
+
*/
|
|
117
|
+
export function getSupabaseAdmin(): SupabaseClient {
|
|
118
|
+
if (!supabaseAdmin) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
'SUPABASE_SERVICE_ROLE_KEY is not configured.\n' +
|
|
121
|
+
'This operation requires admin privileges.'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return supabaseAdmin;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// HELPER: SERVER-SIDE CLIENT WITH USER CONTEXT
|
|
129
|
+
// =============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Creates a Supabase client that acts as a specific user.
|
|
133
|
+
* Useful for server-side operations that should respect RLS.
|
|
134
|
+
*
|
|
135
|
+
* @param accessToken - The user's JWT access token
|
|
136
|
+
* @returns Supabase client authenticated as the user
|
|
137
|
+
*
|
|
138
|
+
* USAGE:
|
|
139
|
+
* // In API route or server action
|
|
140
|
+
* const token = request.headers.get('Authorization')?.replace('Bearer ', '');
|
|
141
|
+
* const userClient = createUserClient(token);
|
|
142
|
+
* const { data } = await userClient.from('posts').select('*');
|
|
143
|
+
* // This respects RLS - user only sees their allowed data
|
|
144
|
+
*/
|
|
145
|
+
export function createUserClient(accessToken: string): SupabaseClient {
|
|
146
|
+
return createClient(supabaseUrl, supabaseAnonKey, {
|
|
147
|
+
global: {
|
|
148
|
+
headers: {
|
|
149
|
+
Authorization: `Bearer ${accessToken}`,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
auth: {
|
|
153
|
+
persistSession: false,
|
|
154
|
+
autoRefreshToken: false,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// =============================================================================
|
|
160
|
+
// HELPER: VALIDATE USER BEFORE ADMIN OPERATIONS
|
|
161
|
+
// =============================================================================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Validates that the current user has permission before admin operations.
|
|
165
|
+
* Always use this before using supabaseAdmin for user-triggered actions.
|
|
166
|
+
*
|
|
167
|
+
* @param userId - The user's ID to check
|
|
168
|
+
* @param requiredRole - The minimum role required (e.g., 'admin')
|
|
169
|
+
* @returns True if user has permission
|
|
170
|
+
*
|
|
171
|
+
* USAGE:
|
|
172
|
+
* const hasPermission = await validateUserPermission(userId, 'admin');
|
|
173
|
+
* if (!hasPermission) {
|
|
174
|
+
* return new Response('Forbidden', { status: 403 });
|
|
175
|
+
* }
|
|
176
|
+
* // Now safe to use supabaseAdmin
|
|
177
|
+
*/
|
|
178
|
+
export async function validateUserPermission(
|
|
179
|
+
userId: string,
|
|
180
|
+
requiredRole: string
|
|
181
|
+
): Promise<boolean> {
|
|
182
|
+
const admin = getSupabaseAdmin();
|
|
183
|
+
|
|
184
|
+
const { data: profile } = await admin
|
|
185
|
+
.from('profiles')
|
|
186
|
+
.select('role')
|
|
187
|
+
.eq('id', userId)
|
|
188
|
+
.single();
|
|
189
|
+
|
|
190
|
+
if (!profile) return false;
|
|
191
|
+
|
|
192
|
+
// Customize this based on your role hierarchy
|
|
193
|
+
const roleHierarchy: Record<string, number> = {
|
|
194
|
+
viewer: 0,
|
|
195
|
+
editor: 1,
|
|
196
|
+
admin: 2,
|
|
197
|
+
super_admin: 3,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const userRoleLevel = roleHierarchy[profile.role] ?? 0;
|
|
201
|
+
const requiredRoleLevel = roleHierarchy[requiredRole] ?? 999;
|
|
202
|
+
|
|
203
|
+
return userRoleLevel >= requiredRoleLevel;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// TYPE DEFINITIONS (customize for your database)
|
|
208
|
+
// =============================================================================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Generate types with:
|
|
212
|
+
* npx supabase gen types typescript --project-id your-project > types/database.ts
|
|
213
|
+
*
|
|
214
|
+
* Then import and use:
|
|
215
|
+
* import { Database } from '@/types/database';
|
|
216
|
+
* const supabase = createClient<Database>(url, key);
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
// Example type for user profiles
|
|
220
|
+
export interface Profile {
|
|
221
|
+
id: string;
|
|
222
|
+
email: string;
|
|
223
|
+
role: 'viewer' | 'editor' | 'admin';
|
|
224
|
+
created_at: string;
|
|
225
|
+
}
|