@wondev/dotenv-example 1.0.1 → 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/AGENTS.md +38 -0
- package/CLAUDE.md +1 -40
- package/bin/index.js +64 -55
- package/package.json +8 -2
- package/prompts/20260102_165844.md +3 -0
- package/prompts/20260102_170026.md +3 -0
- package/prompts/20260102_170120.md +4 -0
- package/prompts/20260102_170500.md +3 -0
- package/prompts/20260102_170839.md +3 -0
- package/prompts/20260102_171046.md +3 -0
- package/prompts/20260102_171147.md +38 -0
- package/prompts/20260102_171336.md +76 -0
- package/prompts/20260102_171546.md +40 -0
- package/.claude/README.md +0 -60
- package/.claude/commands/business_logic.md +0 -143
- package/.claude/commands/generate-prd.md +0 -175
- package/.claude/commands/gotobackend.md +0 -569
- package/.claude/commands/playwrightMCP_install.md +0 -113
- package/.claude/commands/setting_dev.md +0 -731
- package/.claude/commands/tech-lead.md +0 -404
- package/.claude/commands/user-flow.md +0 -839
- package/.claude/settings.local.json +0 -9
- package/.cursor/README.md +0 -10
- package/.cursor/mcp.json +0 -31
- package/.cursor/rules/common/cursor-rules.mdc +0 -53
- package/.cursor/rules/common/git-convention.mdc +0 -86
- package/.cursor/rules/common/self-improve.mdc +0 -72
- package/.cursor/rules/common/tdd.mdc +0 -81
- package/.cursor/rules/common/vibe-coding.mdc +0 -114
- package/.cursor/rules/supabase/supabase-bootstrap-auth.mdc +0 -236
- package/.cursor/rules/supabase/supabase-create-db-functions.mdc +0 -136
- package/.cursor/rules/supabase/supabase-create-migration.mdc +0 -50
- package/.cursor/rules/supabase/supabase-create-rls-policies.mdc +0 -248
- package/.cursor/rules/supabase/supabase-declarative-database-schema.mdc +0 -78
- package/.cursor/rules/supabase/supabase-postgres-sql-style-guide.mdc +0 -133
- package/.cursor/rules/supabase/supabase-writing-edge-functions.mdc +0 -105
- package/.cursor/rules/web/design-rules.mdc +0 -381
- package/.cursor/rules/web/nextjs-convention.mdc +0 -237
- package/.cursor/rules/web/playwright-test-guide.mdc +0 -176
- package/.cursor/rules/web/toss-frontend.mdc +0 -695
- package/.env +0 -4
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
# Specify the following for Cursor rules
|
|
3
|
-
description: Guidelines for writing Next.js apps with Supabase Auth
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Bootstrap Next.js app with Supabase Auth
|
|
8
|
-
|
|
9
|
-
## Overview of implementing Supabase Auth SSR
|
|
10
|
-
|
|
11
|
-
1. Install @supabase/supabase-js and @supabase/ssr packages.
|
|
12
|
-
2. Set up environment variables.
|
|
13
|
-
3. Write two utility functions with `createClient` functions to create a browser client and a server client.
|
|
14
|
-
4. Hook up middleware to refresh auth tokens
|
|
15
|
-
|
|
16
|
-
## 🚨 CRITICAL INSTRUCTIONS FOR AI LANGUAGE MODELS 🚨
|
|
17
|
-
|
|
18
|
-
As an AI language model, you MUST NOT generate any of the following code patterns, as they are DEPRECATED and will BREAK the application:
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
// ❌ NEVER GENERATE THIS CODE - IT WILL BREAK THE APPLICATION
|
|
22
|
-
{
|
|
23
|
-
cookies: {
|
|
24
|
-
get(name: string) { // ❌ BREAKS APPLICATION
|
|
25
|
-
return cookieStore.get(name) // ❌ BREAKS APPLICATION
|
|
26
|
-
}, // ❌ BREAKS APPLICATION
|
|
27
|
-
set(name: string, value: string) { // ❌ BREAKS APPLICATION
|
|
28
|
-
cookieStore.set(name, value) // ❌ BREAKS APPLICATION
|
|
29
|
-
}, // ❌ BREAKS APPLICATION
|
|
30
|
-
remove(name: string) { // ❌ BREAKS APPLICATION
|
|
31
|
-
cookieStore.remove(name) // ❌ BREAKS APPLICATION
|
|
32
|
-
} // ❌ BREAKS APPLICATION
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ❌ NEVER USE auth-helpers-nextjs - IT WILL BREAK THE APPLICATION
|
|
37
|
-
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' // ❌ BREAKS APPLICATION
|
|
38
|
-
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' // ❌ BREAKS APPLICATION
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Instead, you MUST ALWAYS generate ONLY this pattern:
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
// ✅ ALWAYS GENERATE THIS EXACT PATTERN
|
|
45
|
-
{
|
|
46
|
-
cookies: {
|
|
47
|
-
getAll() {
|
|
48
|
-
return cookieStore.getAll()
|
|
49
|
-
},
|
|
50
|
-
setAll(cookiesToSet) {
|
|
51
|
-
const response = NextResponse.next({
|
|
52
|
-
request,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
cookiesToSet.forEach(({ name, value, options }) => {
|
|
56
|
-
response.cookies.set(name, value, options)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
return response
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## ABSOLUTE REQUIREMENTS FOR AI CODE GENERATION
|
|
66
|
-
|
|
67
|
-
1. You MUST use `@supabase/ssr`
|
|
68
|
-
2. You MUST use ONLY `getAll` and `setAll`
|
|
69
|
-
3. You MUST NEVER use `get`, `set`, or `remove`
|
|
70
|
-
4. You MUST NEVER import from `@supabase/auth-helpers-nextjs`
|
|
71
|
-
|
|
72
|
-
## CORRECT BROWSER CLIENT IMPLEMENTATION
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
import { createBrowserClient } from '@supabase/ssr';
|
|
76
|
-
|
|
77
|
-
export function createClient() {
|
|
78
|
-
return createBrowserClient(
|
|
79
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
80
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## CORRECT SERVER CLIENT IMPLEMENTATION
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
import { createServerClient } from '@supabase/ssr';
|
|
89
|
-
import { cookies } from 'next/headers';
|
|
90
|
-
|
|
91
|
-
export async function createClient() {
|
|
92
|
-
const cookieStore = await cookies();
|
|
93
|
-
|
|
94
|
-
return createServerClient(
|
|
95
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
96
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
97
|
-
{
|
|
98
|
-
cookies: {
|
|
99
|
-
getAll() {
|
|
100
|
-
return cookieStore.getAll();
|
|
101
|
-
},
|
|
102
|
-
setAll(cookiesToSet) {
|
|
103
|
-
try {
|
|
104
|
-
cookiesToSet.forEach(({ name, value, options }) =>
|
|
105
|
-
cookieStore.set(name, value, options),
|
|
106
|
-
);
|
|
107
|
-
} catch {
|
|
108
|
-
// The `setAll` method was called from a Server Component.
|
|
109
|
-
// This can be ignored if you have middleware refreshing
|
|
110
|
-
// user sessions.
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
## CORRECT MIDDLEWARE IMPLEMENTATION
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
import { createServerClient } from '@supabase/ssr';
|
|
123
|
-
import { NextResponse, type NextRequest } from 'next/server';
|
|
124
|
-
|
|
125
|
-
export async function middleware(request: NextRequest) {
|
|
126
|
-
let supabaseResponse = NextResponse.next({
|
|
127
|
-
request,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const supabase = createServerClient(
|
|
131
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
132
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
133
|
-
{
|
|
134
|
-
cookies: {
|
|
135
|
-
getAll() {
|
|
136
|
-
return request.cookies.getAll();
|
|
137
|
-
},
|
|
138
|
-
setAll(cookiesToSet) {
|
|
139
|
-
cookiesToSet.forEach(({ name, value, options }) =>
|
|
140
|
-
request.cookies.set(name, value),
|
|
141
|
-
);
|
|
142
|
-
supabaseResponse = NextResponse.next({
|
|
143
|
-
request,
|
|
144
|
-
});
|
|
145
|
-
cookiesToSet.forEach(({ name, value, options }) =>
|
|
146
|
-
supabaseResponse.cookies.set(name, value, options),
|
|
147
|
-
);
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
// Do not run code between createServerClient and
|
|
154
|
-
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
|
|
155
|
-
// issues with users being randomly logged out.
|
|
156
|
-
|
|
157
|
-
// IMPORTANT: DO NOT REMOVE auth.getUser()
|
|
158
|
-
|
|
159
|
-
const {
|
|
160
|
-
data: { user },
|
|
161
|
-
} = await supabase.auth.getUser();
|
|
162
|
-
|
|
163
|
-
if (
|
|
164
|
-
!user &&
|
|
165
|
-
!request.nextUrl.pathname.startsWith('/login') &&
|
|
166
|
-
!request.nextUrl.pathname.startsWith('/auth')
|
|
167
|
-
) {
|
|
168
|
-
// no user, potentially respond by redirecting the user to the login page
|
|
169
|
-
const url = request.nextUrl.clone();
|
|
170
|
-
url.pathname = '/login';
|
|
171
|
-
return NextResponse.redirect(url);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// IMPORTANT: You *must* return the supabaseResponse object as it is.
|
|
175
|
-
// If you're creating a new response object with NextResponse.next() make sure to:
|
|
176
|
-
// 1. Pass the request in it, like so:
|
|
177
|
-
// const myNewResponse = NextResponse.next({ request })
|
|
178
|
-
// 2. Copy over the cookies, like so:
|
|
179
|
-
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
|
|
180
|
-
// 3. Change the myNewResponse object to fit your needs, but avoid changing
|
|
181
|
-
// the cookies!
|
|
182
|
-
// 4. Finally:
|
|
183
|
-
// return myNewResponse
|
|
184
|
-
// If this is not done, you may be causing the browser and server to go out
|
|
185
|
-
// of sync and terminate the user's session prematurely!
|
|
186
|
-
|
|
187
|
-
return supabaseResponse;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export const config = {
|
|
191
|
-
matcher: [
|
|
192
|
-
/*
|
|
193
|
-
* Match all request paths except for the ones starting with:
|
|
194
|
-
* - _next/static (static files)
|
|
195
|
-
* - _next/image (image optimization files)
|
|
196
|
-
* - favicon.ico (favicon file)
|
|
197
|
-
* Feel free to modify this pattern to include more paths.
|
|
198
|
-
*/
|
|
199
|
-
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
|
|
200
|
-
],
|
|
201
|
-
};
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
## AI MODEL VERIFICATION STEPS
|
|
205
|
-
|
|
206
|
-
Before generating any code, you MUST verify:
|
|
207
|
-
|
|
208
|
-
1. Are you using ONLY `getAll` and `setAll`? If not, STOP and FIX.
|
|
209
|
-
2. Are you importing from `@supabase/ssr`? If not, STOP and FIX.
|
|
210
|
-
3. Do you see ANY instance of `get`, `set`, or `remove`? If yes, STOP and FIX.
|
|
211
|
-
4. Are you importing from `auth-helpers-nextjs`? If yes, STOP and FIX.
|
|
212
|
-
|
|
213
|
-
## CONSEQUENCES OF INCORRECT IMPLEMENTATION
|
|
214
|
-
|
|
215
|
-
If you generate code using:
|
|
216
|
-
|
|
217
|
-
- Individual cookie methods (`get`/`set`/`remove`)
|
|
218
|
-
- `auth-helpers-nextjs` package
|
|
219
|
-
|
|
220
|
-
The implementation will:
|
|
221
|
-
|
|
222
|
-
1. Break in production
|
|
223
|
-
2. Fail to maintain session state
|
|
224
|
-
3. Cause authentication loops
|
|
225
|
-
4. Result in security vulnerabilities
|
|
226
|
-
|
|
227
|
-
## AI MODEL RESPONSE TEMPLATE
|
|
228
|
-
|
|
229
|
-
When asked about Supabase Auth SSR implementation, you MUST:
|
|
230
|
-
|
|
231
|
-
1. ONLY use code from this guide
|
|
232
|
-
2. NEVER suggest deprecated approaches
|
|
233
|
-
3. ALWAYS use the exact cookie handling shown above
|
|
234
|
-
4. VERIFY your response against the patterns shown here
|
|
235
|
-
|
|
236
|
-
Remember: There are NO EXCEPTIONS to these rules.
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
# Specify the following for Cursor rules
|
|
3
|
-
description: Guidelines for writing Supabase database functions
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Database: Create functions
|
|
8
|
-
|
|
9
|
-
You're a Supabase Postgres expert in writing database functions. Generate **high-quality PostgreSQL functions** that adhere to the following best practices:
|
|
10
|
-
|
|
11
|
-
## General Guidelines
|
|
12
|
-
|
|
13
|
-
1. **Default to `SECURITY INVOKER`:**
|
|
14
|
-
|
|
15
|
-
- Functions should run with the permissions of the user invoking the function, ensuring safer access control.
|
|
16
|
-
- Use `SECURITY DEFINER` only when explicitly required and explain the rationale.
|
|
17
|
-
|
|
18
|
-
2. **Set the `search_path` Configuration Parameter:**
|
|
19
|
-
|
|
20
|
-
- Always set `search_path` to an empty string (`set search_path = '';`).
|
|
21
|
-
- This avoids unexpected behavior and security risks caused by resolving object references in untrusted or unintended schemas.
|
|
22
|
-
- Use fully qualified names (e.g., `schema_name.table_name`) for all database objects referenced within the function.
|
|
23
|
-
|
|
24
|
-
3. **Adhere to SQL Standards and Validation:**
|
|
25
|
-
- Ensure all queries within the function are valid PostgreSQL SQL queries and compatible with the specified context (ie. Supabase).
|
|
26
|
-
|
|
27
|
-
## Best Practices
|
|
28
|
-
|
|
29
|
-
1. **Minimize Side Effects:**
|
|
30
|
-
|
|
31
|
-
- Prefer functions that return results over those that modify data unless they serve a specific purpose (e.g., triggers).
|
|
32
|
-
|
|
33
|
-
2. **Use Explicit Typing:**
|
|
34
|
-
|
|
35
|
-
- Clearly specify input and output types, avoiding ambiguous or loosely typed parameters.
|
|
36
|
-
|
|
37
|
-
3. **Default to Immutable or Stable Functions:**
|
|
38
|
-
|
|
39
|
-
- Where possible, declare functions as `IMMUTABLE` or `STABLE` to allow better optimization by PostgreSQL. Use `VOLATILE` only if the function modifies data or has side effects.
|
|
40
|
-
|
|
41
|
-
4. **Triggers (if Applicable):**
|
|
42
|
-
- If the function is used as a trigger, include a valid `CREATE TRIGGER` statement that attaches the function to the desired table and event (e.g., `BEFORE INSERT`).
|
|
43
|
-
|
|
44
|
-
## Example Templates
|
|
45
|
-
|
|
46
|
-
### Simple Function with `SECURITY INVOKER`
|
|
47
|
-
|
|
48
|
-
```sql
|
|
49
|
-
create or replace function my_schema.hello_world()
|
|
50
|
-
returns text
|
|
51
|
-
language plpgsql
|
|
52
|
-
security invoker
|
|
53
|
-
set search_path = ''
|
|
54
|
-
as $$
|
|
55
|
-
begin
|
|
56
|
-
return 'hello world';
|
|
57
|
-
end;
|
|
58
|
-
$$;
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Function with Parameters and Fully Qualified Object Names
|
|
62
|
-
|
|
63
|
-
```sql
|
|
64
|
-
create or replace function public.calculate_total_price(order_id bigint)
|
|
65
|
-
returns numeric
|
|
66
|
-
language plpgsql
|
|
67
|
-
security invoker
|
|
68
|
-
set search_path = ''
|
|
69
|
-
as $$
|
|
70
|
-
declare
|
|
71
|
-
total numeric;
|
|
72
|
-
begin
|
|
73
|
-
select sum(price * quantity)
|
|
74
|
-
into total
|
|
75
|
-
from public.order_items
|
|
76
|
-
where order_id = calculate_total_price.order_id;
|
|
77
|
-
|
|
78
|
-
return total;
|
|
79
|
-
end;
|
|
80
|
-
$$;
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Function as a Trigger
|
|
84
|
-
|
|
85
|
-
```sql
|
|
86
|
-
create or replace function my_schema.update_updated_at()
|
|
87
|
-
returns trigger
|
|
88
|
-
language plpgsql
|
|
89
|
-
security invoker
|
|
90
|
-
set search_path = ''
|
|
91
|
-
as $$
|
|
92
|
-
begin
|
|
93
|
-
-- Update the "updated_at" column on row modification
|
|
94
|
-
new.updated_at := now();
|
|
95
|
-
return new;
|
|
96
|
-
end;
|
|
97
|
-
$$;
|
|
98
|
-
|
|
99
|
-
create trigger update_updated_at_trigger
|
|
100
|
-
before update on my_schema.my_table
|
|
101
|
-
for each row
|
|
102
|
-
execute function my_schema.update_updated_at();
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Function with Error Handling
|
|
106
|
-
|
|
107
|
-
```sql
|
|
108
|
-
create or replace function my_schema.safe_divide(numerator numeric, denominator numeric)
|
|
109
|
-
returns numeric
|
|
110
|
-
language plpgsql
|
|
111
|
-
security invoker
|
|
112
|
-
set search_path = ''
|
|
113
|
-
as $$
|
|
114
|
-
begin
|
|
115
|
-
if denominator = 0 then
|
|
116
|
-
raise exception 'Division by zero is not allowed';
|
|
117
|
-
end if;
|
|
118
|
-
|
|
119
|
-
return numerator / denominator;
|
|
120
|
-
end;
|
|
121
|
-
$$;
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Immutable Function for Better Optimization
|
|
125
|
-
|
|
126
|
-
```sql
|
|
127
|
-
create or replace function my_schema.full_name(first_name text, last_name text)
|
|
128
|
-
returns text
|
|
129
|
-
language sql
|
|
130
|
-
security invoker
|
|
131
|
-
set search_path = ''
|
|
132
|
-
immutable
|
|
133
|
-
as $$
|
|
134
|
-
select first_name || ' ' || last_name;
|
|
135
|
-
$$;
|
|
136
|
-
```
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
# Specify the following for Cursor rules
|
|
3
|
-
description: Guidelines for writing Postgres migrations
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Database: Create migration
|
|
8
|
-
|
|
9
|
-
You are a Postgres Expert who loves creating secure database schemas.
|
|
10
|
-
|
|
11
|
-
This project uses the migrations provided by the Supabase CLI.
|
|
12
|
-
|
|
13
|
-
## Creating a migration file
|
|
14
|
-
|
|
15
|
-
Given the context of the user's message, create a database migration file inside the folder `supabase/migrations/`.
|
|
16
|
-
|
|
17
|
-
The file MUST following this naming convention:
|
|
18
|
-
|
|
19
|
-
The file MUST be named in the format `YYYYMMDDHHmmss_short_description.sql` with proper casing for months, minutes, and seconds in UTC time:
|
|
20
|
-
|
|
21
|
-
1. `YYYY` - Four digits for the year (e.g., `2024`).
|
|
22
|
-
2. `MM` - Two digits for the month (01 to 12).
|
|
23
|
-
3. `DD` - Two digits for the day of the month (01 to 31).
|
|
24
|
-
4. `HH` - Two digits for the hour in 24-hour format (00 to 23).
|
|
25
|
-
5. `mm` - Two digits for the minute (00 to 59).
|
|
26
|
-
6. `ss` - Two digits for the second (00 to 59).
|
|
27
|
-
7. Add an appropriate description for the migration.
|
|
28
|
-
|
|
29
|
-
For example:
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
20240906123045_create_profiles.sql
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## SQL Guidelines
|
|
36
|
-
|
|
37
|
-
Write Postgres-compatible SQL code for Supabase migration files that:
|
|
38
|
-
|
|
39
|
-
- Includes a header comment with metadata about the migration, such as the purpose, affected tables/columns, and any special considerations.
|
|
40
|
-
- Includes thorough comments explaining the purpose and expected behavior of each migration step.
|
|
41
|
-
- Write all SQL in lowercase.
|
|
42
|
-
- Add copious comments for any destructive SQL commands, including truncating, dropping, or column alterations.
|
|
43
|
-
- When creating a new table, you MUST enable Row Level Security (RLS) even if the table is intended for public access.
|
|
44
|
-
- When creating RLS Policies
|
|
45
|
-
- Ensure the policies cover all relevant access scenarios (e.g. select, insert, update, delete) based on the table's purpose and data sensitivity.
|
|
46
|
-
- If the table is intended for public access the policy can simply return `true`.
|
|
47
|
-
- RLS Policies should be granular: one policy for `select`, one for `insert` etc) and for each supabase role (`anon` and `authenticated`). DO NOT combine Policies even if the functionality is the same for both roles.
|
|
48
|
-
- Include comments explaining the rationale and intended behavior of each security policy
|
|
49
|
-
|
|
50
|
-
The generated SQL code should be production-ready, well-documented, and aligned with Supabase's best practices.
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Guidelines for writing Postgres Row Level Security policies
|
|
3
|
-
alwaysApply: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Database: Create RLS policies
|
|
7
|
-
|
|
8
|
-
You're a Supabase Postgres expert in writing row level security policies. Your purpose is to generate a policy with the constraints given by the user. You should first retrieve schema information to write policies for, usually the 'public' schema.
|
|
9
|
-
|
|
10
|
-
The output should use the following instructions:
|
|
11
|
-
|
|
12
|
-
- The generated SQL must be valid SQL.
|
|
13
|
-
- You can use only CREATE POLICY or ALTER POLICY queries, no other queries are allowed.
|
|
14
|
-
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
|
|
15
|
-
- You can add short explanations to your messages.
|
|
16
|
-
- The result should be a valid markdown. The SQL code should be wrapped in ``` (including sql language tag).
|
|
17
|
-
- Always use "auth.uid()" instead of "current_user".
|
|
18
|
-
- SELECT policies should always have USING but not WITH CHECK
|
|
19
|
-
- INSERT policies should always have WITH CHECK but not USING
|
|
20
|
-
- UPDATE policies should always have WITH CHECK and most often have USING
|
|
21
|
-
- DELETE policies should always have USING but not WITH CHECK
|
|
22
|
-
- Don't use `FOR ALL`. Instead separate into 4 separate policies for select, insert, update, and delete.
|
|
23
|
-
- The policy name should be short but detailed text explaining the policy, enclosed in double quotes.
|
|
24
|
-
- Always put explanations as separate text. Never use inline SQL comments.
|
|
25
|
-
- If the user asks for something that's not related to SQL policies, explain to the user
|
|
26
|
-
that you can only help with policies.
|
|
27
|
-
- Discourage `RESTRICTIVE` policies and encourage `PERMISSIVE` policies, and explain why.
|
|
28
|
-
|
|
29
|
-
The output should look like this:
|
|
30
|
-
|
|
31
|
-
```sql
|
|
32
|
-
CREATE POLICY "My descriptive policy." ON books FOR INSERT to authenticated USING ( (select auth.uid()) = author_id ) WITH ( true );
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Since you are running in a Supabase environment, take note of these Supabase-specific additions below.
|
|
36
|
-
|
|
37
|
-
## Authenticated and unauthenticated roles
|
|
38
|
-
|
|
39
|
-
Supabase maps every request to one of the roles:
|
|
40
|
-
|
|
41
|
-
- `anon`: an unauthenticated request (the user is not logged in)
|
|
42
|
-
- `authenticated`: an authenticated request (the user is logged in)
|
|
43
|
-
|
|
44
|
-
These are actually [Postgres Roles](mdc:docs/guides/database/postgres/roles). You can use these roles within your Policies using the `TO` clause:
|
|
45
|
-
|
|
46
|
-
```sql
|
|
47
|
-
create policy "Profiles are viewable by everyone"
|
|
48
|
-
on profiles
|
|
49
|
-
for select
|
|
50
|
-
to authenticated, anon
|
|
51
|
-
using ( true );
|
|
52
|
-
|
|
53
|
-
-- OR
|
|
54
|
-
|
|
55
|
-
create policy "Public profiles are viewable only by authenticated users"
|
|
56
|
-
on profiles
|
|
57
|
-
for select
|
|
58
|
-
to authenticated
|
|
59
|
-
using ( true );
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Note that `for ...` must be added after the table but before the roles. `to ...` must be added after `for ...`:
|
|
63
|
-
|
|
64
|
-
### Incorrect
|
|
65
|
-
|
|
66
|
-
```sql
|
|
67
|
-
create policy "Public profiles are viewable only by authenticated users"
|
|
68
|
-
on profiles
|
|
69
|
-
to authenticated
|
|
70
|
-
for select
|
|
71
|
-
using ( true );
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Correct
|
|
75
|
-
|
|
76
|
-
```sql
|
|
77
|
-
create policy "Public profiles are viewable only by authenticated users"
|
|
78
|
-
on profiles
|
|
79
|
-
for select
|
|
80
|
-
to authenticated
|
|
81
|
-
using ( true );
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Multiple operations
|
|
85
|
-
|
|
86
|
-
PostgreSQL policies do not support specifying multiple operations in a single FOR clause. You need to create separate policies for each operation.
|
|
87
|
-
|
|
88
|
-
### Incorrect
|
|
89
|
-
|
|
90
|
-
```sql
|
|
91
|
-
create policy "Profiles can be created and deleted by any user"
|
|
92
|
-
on profiles
|
|
93
|
-
for insert, delete -- cannot create a policy on multiple operators
|
|
94
|
-
to authenticated
|
|
95
|
-
with check ( true )
|
|
96
|
-
using ( true );
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Correct
|
|
100
|
-
|
|
101
|
-
```sql
|
|
102
|
-
create policy "Profiles can be created by any user"
|
|
103
|
-
on profiles
|
|
104
|
-
for insert
|
|
105
|
-
to authenticated
|
|
106
|
-
with check ( true );
|
|
107
|
-
|
|
108
|
-
create policy "Profiles can be deleted by any user"
|
|
109
|
-
on profiles
|
|
110
|
-
for delete
|
|
111
|
-
to authenticated
|
|
112
|
-
using ( true );
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Helper functions
|
|
116
|
-
|
|
117
|
-
Supabase provides some helper functions that make it easier to write Policies.
|
|
118
|
-
|
|
119
|
-
### `auth.uid()`
|
|
120
|
-
|
|
121
|
-
Returns the ID of the user making the request.
|
|
122
|
-
|
|
123
|
-
### `auth.jwt()`
|
|
124
|
-
|
|
125
|
-
Returns the JWT of the user making the request. Anything that you store in the user's `raw_app_meta_data` column or the `raw_user_meta_data` column will be accessible using this function. It's important to know the distinction between these two:
|
|
126
|
-
|
|
127
|
-
- `raw_user_meta_data` - can be updated by the authenticated user using the `supabase.auth.update()` function. It is not a good place to store authorization data.
|
|
128
|
-
- `raw_app_meta_data` - cannot be updated by the user, so it's a good place to store authorization data.
|
|
129
|
-
|
|
130
|
-
The `auth.jwt()` function is extremely versatile. For example, if you store some team data inside `app_metadata`, you can use it to determine whether a particular user belongs to a team. For example, if this was an array of IDs:
|
|
131
|
-
|
|
132
|
-
```sql
|
|
133
|
-
create policy "User is in team"
|
|
134
|
-
on my_table
|
|
135
|
-
to authenticated
|
|
136
|
-
using ( team_id in (select auth.jwt() -> 'app_metadata' -> 'teams'));
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### MFA
|
|
140
|
-
|
|
141
|
-
The `auth.jwt()` function can be used to check for [Multi-Factor Authentication](mdc:docs/guides/auth/auth-mfa#enforce-rules-for-mfa-logins). For example, you could restrict a user from updating their profile unless they have at least 2 levels of authentication (Assurance Level 2):
|
|
142
|
-
|
|
143
|
-
```sql
|
|
144
|
-
create policy "Restrict updates."
|
|
145
|
-
on profiles
|
|
146
|
-
as restrictive
|
|
147
|
-
for update
|
|
148
|
-
to authenticated using (
|
|
149
|
-
(select auth.jwt()->>'aal') = 'aal2'
|
|
150
|
-
);
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## RLS performance recommendations
|
|
154
|
-
|
|
155
|
-
Every authorization system has an impact on performance. While row level security is powerful, the performance impact is important to keep in mind. This is especially true for queries that scan every row in a table - like many `select` operations, including those using limit, offset, and ordering.
|
|
156
|
-
|
|
157
|
-
Based on a series of [tests](mdc:https:/github.com/GaryAustin1/RLS-Performance), we have a few recommendations for RLS:
|
|
158
|
-
|
|
159
|
-
### Add indexes
|
|
160
|
-
|
|
161
|
-
Make sure you've added [indexes](mdc:docs/guides/database/postgres/indexes) on any columns used within the Policies which are not already indexed (or primary keys). For a Policy like this:
|
|
162
|
-
|
|
163
|
-
```sql
|
|
164
|
-
create policy "Users can access their own records" on test_table
|
|
165
|
-
to authenticated
|
|
166
|
-
using ( (select auth.uid()) = user_id );
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
You can add an index like:
|
|
170
|
-
|
|
171
|
-
```sql
|
|
172
|
-
create index userid
|
|
173
|
-
on test_table
|
|
174
|
-
using btree (user_id);
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Call functions with `select`
|
|
178
|
-
|
|
179
|
-
You can use `select` statement to improve policies that use functions. For example, instead of this:
|
|
180
|
-
|
|
181
|
-
```sql
|
|
182
|
-
create policy "Users can access their own records" on test_table
|
|
183
|
-
to authenticated
|
|
184
|
-
using ( auth.uid() = user_id );
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
You can do:
|
|
188
|
-
|
|
189
|
-
```sql
|
|
190
|
-
create policy "Users can access their own records" on test_table
|
|
191
|
-
to authenticated
|
|
192
|
-
using ( (select auth.uid()) = user_id );
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
This method works well for JWT functions like `auth.uid()` and `auth.jwt()` as well as `security definer` Functions. Wrapping the function causes an `initPlan` to be run by the Postgres optimizer, which allows it to "cache" the results per-statement, rather than calling the function on each row.
|
|
196
|
-
|
|
197
|
-
Caution: You can only use this technique if the results of the query or function do not change based on the row data.
|
|
198
|
-
|
|
199
|
-
### Minimize joins
|
|
200
|
-
|
|
201
|
-
You can often rewrite your Policies to avoid joins between the source and the target table. Instead, try to organize your policy to fetch all the relevant data from the target table into an array or set, then you can use an `IN` or `ANY` operation in your filter.
|
|
202
|
-
|
|
203
|
-
For example, this is an example of a slow policy which joins the source `test_table` to the target `team_user`:
|
|
204
|
-
|
|
205
|
-
```sql
|
|
206
|
-
create policy "Users can access records belonging to their teams" on test_table
|
|
207
|
-
to authenticated
|
|
208
|
-
using (
|
|
209
|
-
(select auth.uid()) in (
|
|
210
|
-
select user_id
|
|
211
|
-
from team_user
|
|
212
|
-
where team_user.team_id = team_id -- joins to the source "test_table.team_id"
|
|
213
|
-
)
|
|
214
|
-
);
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
We can rewrite this to avoid this join, and instead select the filter criteria into a set:
|
|
218
|
-
|
|
219
|
-
```sql
|
|
220
|
-
create policy "Users can access records belonging to their teams" on test_table
|
|
221
|
-
to authenticated
|
|
222
|
-
using (
|
|
223
|
-
team_id in (
|
|
224
|
-
select team_id
|
|
225
|
-
from team_user
|
|
226
|
-
where user_id = (select auth.uid()) -- no join
|
|
227
|
-
)
|
|
228
|
-
);
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Specify roles in your policies
|
|
232
|
-
|
|
233
|
-
Always use the Role of inside your policies, specified by the `TO` operator. For example, instead of this query:
|
|
234
|
-
|
|
235
|
-
```sql
|
|
236
|
-
create policy "Users can access their own records" on rls_test
|
|
237
|
-
using ( auth.uid() = user_id );
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Use:
|
|
241
|
-
|
|
242
|
-
```sql
|
|
243
|
-
create policy "Users can access their own records" on rls_test
|
|
244
|
-
to authenticated
|
|
245
|
-
using ( (select auth.uid()) = user_id );
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
This prevents the policy `( (select auth.uid()) = user_id )` from running for any `anon` users, since the execution stops at the `to authenticated` step.
|