@luanpdd/kit-mcp 1.7.0 → 1.8.1
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/CHANGELOG.md +101 -0
- package/gates/agent-no-recursive-dispatch.md +48 -0
- package/gates/budget-description.md +68 -0
- package/gates/no-personal-uuid.md +72 -0
- package/gates/skill-must-include.md +69 -0
- package/gates/sync-idempotent.md +62 -0
- package/kit/agents/codebase-mapper.md +1 -1
- package/kit/agents/executor.md +17 -0
- package/kit/agents/planner.md +35 -0
- package/kit/agents/project-researcher.md +1 -1
- package/kit/agents/schema-checker.md +4 -4
- package/kit/agents/supabase-architect.md +153 -0
- package/kit/agents/supabase-auth-bootstrapper.md +298 -0
- package/kit/agents/supabase-edge-fn-writer.md +185 -0
- package/kit/agents/supabase-migration-writer.md +156 -0
- package/kit/agents/supabase-realtime-implementer.md +252 -0
- package/kit/agents/supabase-rls-writer.md +218 -0
- package/kit/agents/supabase-storage-implementer.md +240 -0
- package/kit/agents/user-profiler.md +1 -1
- package/kit/agents/verifier.md +1 -1
- package/kit/commands/depurar.md +17 -0
- package/kit/commands/fazer.md +15 -0
- package/kit/commands/supabase.md +148 -0
- package/kit/framework/workflows/discuss-phase.md +19 -0
- package/kit/framework/workflows/plan-phase.md +25 -0
- package/kit/skills/_shared-supabase/glossary.md +180 -0
- package/kit/skills/supabase-auth-ssr/SKILL.md +260 -0
- package/kit/skills/supabase-cron-queues/SKILL.md +266 -0
- package/kit/skills/supabase-database-functions/SKILL.md +247 -0
- package/kit/skills/supabase-declarative-schema/SKILL.md +183 -0
- package/kit/skills/supabase-edge-functions/SKILL.md +242 -0
- package/kit/skills/supabase-migrations/SKILL.md +175 -0
- package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -0
- package/kit/skills/supabase-postgres-style/SKILL.md +138 -0
- package/kit/skills/supabase-realtime/SKILL.md +236 -0
- package/kit/skills/supabase-rls-policies/SKILL.md +185 -0
- package/kit/skills/supabase-storage/SKILL.md +234 -0
- package/package.json +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Glossário Supabase — Termos, Comandos e Patterns Canônicos
|
|
2
|
+
|
|
3
|
+
> Arquivo de referência compartilhado pelas skills `supabase-*`. **NÃO é skill** — não tem `description:` triggerável; não aparece em `listKit`. Cross-referenciado pelas 11 skills via Markdown link relativo.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## (a) Termos PT-BR ↔ EN
|
|
8
|
+
|
|
9
|
+
### Authorization e Auth
|
|
10
|
+
|
|
11
|
+
| EN | PT-BR / Significado |
|
|
12
|
+
|---|---|
|
|
13
|
+
| **RLS** | Row Level Security — segurança em nível de linha. Filtra automaticamente quais linhas de uma tabela cada usuário vê/modifica baseado em policies. |
|
|
14
|
+
| **policy** | Política de RLS — regra `create policy ... on <table> for <op> ...`. Sempre granular por operação (SELECT/INSERT/UPDATE/DELETE). |
|
|
15
|
+
| **`auth.uid()`** | Função que retorna o UUID do usuário autenticado da sessão atual. **Sempre** usar em `(select auth.uid())` em policies. |
|
|
16
|
+
| **`auth.jwt()`** | Função que retorna o JWT decodificado da sessão. Acesso via `auth.jwt()->'app_metadata'` ou `auth.jwt()->>'aal'`. |
|
|
17
|
+
| **`app_metadata`** | Metadata controlado pelo backend (apenas service_role pode mutar). **Use para roles/permissions** em RLS. |
|
|
18
|
+
| **`user_metadata`** | Metadata controlado pelo cliente (`auth.updateUser({data: ...})`). **NUNCA** use em policy de autorização — privilege escalation. |
|
|
19
|
+
| **service_role** | Role do Postgres com bypass total de RLS. **NUNCA** expor ao cliente — vazamento = acesso total ao DB. |
|
|
20
|
+
| **anon** | Role para requests sem autenticação. RLS aplicado normalmente. |
|
|
21
|
+
| **authenticated** | Role para usuário autenticado. RLS aplicado normalmente. |
|
|
22
|
+
| **public** | Role default — equivale a anon + authenticated juntos. Evite — sempre use `to authenticated` ou `to anon` explícito. |
|
|
23
|
+
| **AAL** | Authentication Assurance Level. `aal1` = senha apenas; `aal2` = senha + 2FA. Verifica via `(auth.jwt()->>'aal')::text`. |
|
|
24
|
+
|
|
25
|
+
### Database e Schema
|
|
26
|
+
|
|
27
|
+
| EN | PT-BR / Significado |
|
|
28
|
+
|---|---|
|
|
29
|
+
| **`schemas/`** | Pasta `supabase/schemas/` — fonte da verdade declarative do schema. Editar aqui, depois `db diff` gera migration. |
|
|
30
|
+
| **`migrations/`** | Pasta `supabase/migrations/` — arquivos `YYYYMMDDHHmmss_<name>.sql` versionados em git. |
|
|
31
|
+
| **`db diff`** | `supabase db diff -f <name>` — gera migration a partir do diff entre schemas/ declarado e DB local atual. |
|
|
32
|
+
| **`db reset`** | `supabase db reset` — recria DB local do zero + reaplica todas as migrations + seeds. |
|
|
33
|
+
| **`search_path`** | Caminho de busca de schema do Postgres. **Sempre** `set search_path = ''` em funções. |
|
|
34
|
+
| **schema-qualified** | Referência a objeto com schema explícito: `public.tasks` em vez de só `tasks`. Obrigatório quando `search_path = ''`. |
|
|
35
|
+
| **`SECURITY INVOKER`** | Função executa com permissões de quem chamou. Default obrigatório. |
|
|
36
|
+
| **`SECURITY DEFINER`** | Função executa com permissões do owner. Apenas com justificativa documentada. |
|
|
37
|
+
| **`IMMUTABLE`** | Função sempre retorna o mesmo para os mesmos inputs (sem consultar DB). |
|
|
38
|
+
| **`STABLE`** | Função consulta DB mas não modifica — mesmo resultado dentro de uma transação. |
|
|
39
|
+
| **`VOLATILE`** | Default. Função pode retornar valores diferentes ou ter side effects. |
|
|
40
|
+
|
|
41
|
+
### Realtime
|
|
42
|
+
|
|
43
|
+
| EN | PT-BR / Significado |
|
|
44
|
+
|---|---|
|
|
45
|
+
| **broadcast** | Mensagens custom via WebSocket — tipo recomendado em 2026 (substitui `postgres_changes` em apps novos). |
|
|
46
|
+
| **postgres_changes** | Pattern legado de receber mudanças do DB via stream. Single-threaded — não escala. **Migrar para broadcast.** |
|
|
47
|
+
| **presence** | Tracking de "quem está online". Usar **com moderação** — só para presence real (online status, cursor de colaboração). |
|
|
48
|
+
| **channel** | Canal de comunicação — naming canônico `scope:entity:id` (ex: `room:123:messages`). |
|
|
49
|
+
| **private channel** | Canal autenticado — `private: true` + RLS sobre `realtime.messages`. **Default em produção.** |
|
|
50
|
+
| **`realtime.broadcast_changes`** | Função SQL para emitir broadcast de dentro do Postgres (de trigger). |
|
|
51
|
+
| **`realtime.send`** | Função SQL para emitir mensagem custom (não amarrada a tabela). |
|
|
52
|
+
|
|
53
|
+
### Edge Functions
|
|
54
|
+
|
|
55
|
+
| EN | PT-BR / Significado |
|
|
56
|
+
|---|---|
|
|
57
|
+
| **Edge Function** | Função serverless Deno hospedada por Supabase. Roda perto do usuário. |
|
|
58
|
+
| **`Deno.serve`** | Built-in para HTTP server em Edge Functions (NÃO usar `serve` de `deno.land/std`). |
|
|
59
|
+
| **`EdgeRuntime.waitUntil`** | Permite tarefa background continuar após response retornar. |
|
|
60
|
+
| **`npm:` / `jsr:`** | Specifiers de import obrigatórios (sem bare specifiers). Ex: `import x from "npm:hono@4.6.7"`. |
|
|
61
|
+
|
|
62
|
+
### Storage e Vector
|
|
63
|
+
|
|
64
|
+
| EN | PT-BR / Significado |
|
|
65
|
+
|---|---|
|
|
66
|
+
| **bucket** | Container de arquivos — público ou privado. Privado por default. |
|
|
67
|
+
| **signed URL** | URL temporária com expiration para download de arquivo privado. |
|
|
68
|
+
| **`storage.objects`** | Tabela onde Storage grava metadados — RLS aplicado aqui controla acesso. |
|
|
69
|
+
| **multi-tenant path isolation** | Pattern: prefixar path do arquivo com `auth.uid()` (`{user_id}/file.png`) para isolar por tenant via RLS. |
|
|
70
|
+
| **TUS** | Tus Resumable Upload protocol — upload em chunks resumable. |
|
|
71
|
+
| **pgvector** | Extensão Postgres para embeddings/similarity search. |
|
|
72
|
+
| **HNSW** | Hierarchical Navigable Small World — index para vector. **Recall melhor.** Default em 2026. |
|
|
73
|
+
| **IVFFlat** | Inverted File Flat — index alternativo. Mais rápido com volumes grandes mas recall menor. |
|
|
74
|
+
| **`<=>`** | Operador cosine distance em pgvector. |
|
|
75
|
+
| **`<#>`** | Operador inner product em pgvector. |
|
|
76
|
+
| **`<->`** | Operador L2 (euclidean) distance em pgvector. |
|
|
77
|
+
|
|
78
|
+
### Background Jobs
|
|
79
|
+
|
|
80
|
+
| EN | PT-BR / Significado |
|
|
81
|
+
|---|---|
|
|
82
|
+
| **`pg_cron`** | Extensão para jobs cron dentro do Postgres. Schedule SQL/funções. |
|
|
83
|
+
| **`pgmq`** | Postgres Message Queue — extensão de queues. Requer Postgres 15.6.1.143+. |
|
|
84
|
+
| **`pg_net`** | Extensão para requests HTTP de dentro do Postgres. v0.10.0+. |
|
|
85
|
+
|
|
86
|
+
### Branching
|
|
87
|
+
|
|
88
|
+
| EN | PT-BR / Significado |
|
|
89
|
+
|---|---|
|
|
90
|
+
| **branch database** | Cópia preview do DB de produção para feature branches. |
|
|
91
|
+
| **persistent branch** | Branch que sobrevive entre PRs (staging long-lived). |
|
|
92
|
+
| **preview branch** | Branch criado para PR específico — destruído ao merge. |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## (b) Comandos CLI canônicos
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Schema declarative
|
|
100
|
+
supabase stop # parar containers (necessário antes de db diff)
|
|
101
|
+
supabase db diff -f <name> # gera migration de schemas/ → migrations/
|
|
102
|
+
supabase db reset # reset local + reaplica migrations + seeds
|
|
103
|
+
supabase db push # aplica migrations não aplicadas no DB remote
|
|
104
|
+
supabase db pull # pulla mudanças remote → cria migration local
|
|
105
|
+
|
|
106
|
+
# Migrations
|
|
107
|
+
supabase migration new <name> # cria migration vazia com timestamp UTC
|
|
108
|
+
|
|
109
|
+
# Edge Functions
|
|
110
|
+
supabase functions new <name> # cria boilerplate de Edge Function
|
|
111
|
+
supabase functions deploy <name> # deploy para Supabase
|
|
112
|
+
supabase functions invoke <name> --body '{}' # invoca localmente
|
|
113
|
+
|
|
114
|
+
# Tipos
|
|
115
|
+
supabase gen types typescript --local > types/db.ts # gera tipos do schema local
|
|
116
|
+
supabase gen types typescript --linked > types/db.ts # gera tipos do remote linked
|
|
117
|
+
|
|
118
|
+
# Project lifecycle
|
|
119
|
+
supabase init # inicializa supabase/ no projeto
|
|
120
|
+
supabase start # sobe stack local (Postgres + Studio + Auth + ...)
|
|
121
|
+
supabase stop # derruba stack local
|
|
122
|
+
supabase status # status dos containers locais
|
|
123
|
+
supabase link --project-ref <ref> # linka projeto local com remote
|
|
124
|
+
|
|
125
|
+
# Branching
|
|
126
|
+
supabase branches create <name> # cria preview branch
|
|
127
|
+
supabase branches list # lista branches
|
|
128
|
+
supabase branches delete <name> # deleta branch (importante para custo!)
|
|
129
|
+
|
|
130
|
+
# Secrets
|
|
131
|
+
supabase secrets set --env-file .env.production # setar secrets em remote
|
|
132
|
+
supabase secrets list # listar (sem revelar valores)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## (c) Patterns canônicos consolidados
|
|
138
|
+
|
|
139
|
+
### Pattern: `(select auth.uid())` wrapper em RLS
|
|
140
|
+
- Sem `(select)`: degradação até **1000×** em queries com filtro RLS
|
|
141
|
+
- Detalhes: [supabase-rls-policies](../supabase-rls-policies/SKILL.md)
|
|
142
|
+
|
|
143
|
+
### Pattern: `set search_path = ''` em funções
|
|
144
|
+
- Sem isso: vulnerável a hijack de schema via `search_path` manipulation
|
|
145
|
+
- Detalhes: [supabase-database-functions](../supabase-database-functions/SKILL.md)
|
|
146
|
+
|
|
147
|
+
### Pattern: `getAll`/`setAll` cookies em SSR (Next.js)
|
|
148
|
+
- Pacote `@supabase/ssr` — **NUNCA** `@supabase/auth-helpers-nextjs` (deprecated)
|
|
149
|
+
- Detalhes: [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md)
|
|
150
|
+
|
|
151
|
+
### Pattern: `cron → pgmq → Edge Function` (background jobs)
|
|
152
|
+
- Schedule via `pg_cron` → enqueue em `pgmq` → consumir e disparar `pg_net.http_post()` para Edge Function
|
|
153
|
+
- Sem dep externa (Inngest/Trigger.dev) — tudo dentro de Supabase
|
|
154
|
+
- Detalhes: [supabase-cron-queues](../supabase-cron-queues/SKILL.md)
|
|
155
|
+
|
|
156
|
+
### Pattern: RAG with permissions (similarity + RLS)
|
|
157
|
+
- Embeddings em coluna vector + RLS policy filtrando por `user_id` ou `org_id`
|
|
158
|
+
- Sem RLS, qualquer cliente vê embeddings de todos os tenants
|
|
159
|
+
- Detalhes: [supabase-pgvector-rag](../supabase-pgvector-rag/SKILL.md)
|
|
160
|
+
|
|
161
|
+
### Pattern: multi-tenant path isolation em Storage
|
|
162
|
+
- Path do arquivo prefixado com `auth.uid()` ou `org_id`: `{user_id}/avatar.png`, `{org_id}/docs/file.pdf`
|
|
163
|
+
- RLS sobre `storage.objects` valida que o cliente acessa apenas o próprio prefixo
|
|
164
|
+
- Detalhes: [supabase-storage](../supabase-storage/SKILL.md)
|
|
165
|
+
|
|
166
|
+
### Pattern: declarative-first → diff → migration
|
|
167
|
+
- Editar schemas em `supabase/schemas/*.sql`
|
|
168
|
+
- Rodar `supabase stop && supabase db diff -f <name>` para gerar migration em `supabase/migrations/`
|
|
169
|
+
- Revisar migration manualmente antes de aplicar
|
|
170
|
+
- Detalhes: [supabase-declarative-schema](../supabase-declarative-schema/SKILL.md)
|
|
171
|
+
|
|
172
|
+
### Pattern: `private: true` em Realtime channels
|
|
173
|
+
- Default em produção (2026) — desabilita acesso anônimo
|
|
174
|
+
- Requer RLS sobre `realtime.messages` para SELECT (read) e INSERT (write)
|
|
175
|
+
- Detalhes: [supabase-realtime](../supabase-realtime/SKILL.md)
|
|
176
|
+
|
|
177
|
+
### Pattern: schema-qualified em Edge Functions chamando Supabase
|
|
178
|
+
- Function consulta `public.tasks` (não `tasks`) quando usar service-role client
|
|
179
|
+
- Combina com `set search_path = ''` em DB functions chamadas via RPC
|
|
180
|
+
- Detalhes: [supabase-edge-functions](../supabase-edge-functions/SKILL.md)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supabase-auth-ssr
|
|
3
|
+
description: Use ao bootstrap Next.js v16 + Supabase Auth — @supabase/ssr, getAll/setAll APENAS, NUNCA auth-helpers-nextjs, proxy completo com getUser e redirects.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supabase — Auth SSR (Next.js v16+)
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill quando bootstrap ou auditar autenticação Supabase em Next.js v16+ (App Router) com SSR. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "Next.js + Supabase auth"
|
|
13
|
+
- "@supabase/ssr"
|
|
14
|
+
- "createServerClient", "createBrowserClient"
|
|
15
|
+
- "middleware.ts auth", "proxy auth"
|
|
16
|
+
- "cookies getAll setAll"
|
|
17
|
+
- "Supabase auth Next.js v16"
|
|
18
|
+
|
|
19
|
+
## Regras absolutas
|
|
20
|
+
|
|
21
|
+
**WARNING — NEVER use auth-helpers-nextjs.** O pacote `@supabase/auth-helpers-nextjs` está **DEPRECATED** e **quebra em Next.js v16+** (cookies API mudou). **SEMPRE** use `@supabase/ssr`.
|
|
22
|
+
|
|
23
|
+
**Outras regras:**
|
|
24
|
+
|
|
25
|
+
- **Padrão exclusivo `getAll`/`setAll`** para cookies — **NUNCA** `get`/`set`/`remove` individuais. Os métodos individuais não funcionam corretamente com middleware/Server Actions em Next.js v16+.
|
|
26
|
+
- **Browser client e Server client são distintos:**
|
|
27
|
+
- Browser (`createBrowserClient`) → para Client Components ("use client")
|
|
28
|
+
- Server (`createServerClient`) → para Server Components, Route Handlers, Server Actions
|
|
29
|
+
- **Middleware (`middleware.ts`) obrigatório** para refresh de sessão SSR. Deve chamar `supabase.auth.getUser()` em cada request.
|
|
30
|
+
- **Auth method order** — após `createServerClient` mas **ANTES** de `getUser()`, NÃO chamar nada que produza response intermediário. Os cookies precisam fluir corretamente.
|
|
31
|
+
- **`NEXT_PUBLIC_*` apenas para anon key** (`NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`). **NUNCA** `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY` — service_role bypassa RLS e seria exposto ao cliente (anti-pitfall B6).
|
|
32
|
+
- **Single serverClient factory** — não criar múltiplos clients em layouts (race condition na refresh de token — B13).
|
|
33
|
+
|
|
34
|
+
## Patterns canônicos
|
|
35
|
+
|
|
36
|
+
### Browser client — Client Components
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// utils/supabase/client.ts — PT-BR: client para Client Components
|
|
40
|
+
import { createBrowserClient } from '@supabase/ssr'
|
|
41
|
+
|
|
42
|
+
export function createClient() {
|
|
43
|
+
return createBrowserClient(
|
|
44
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
45
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// PT-BR: uso em Client Component
|
|
52
|
+
'use client'
|
|
53
|
+
import { createClient } from '@/utils/supabase/client'
|
|
54
|
+
|
|
55
|
+
export function LogoutButton() {
|
|
56
|
+
const supabase = createClient()
|
|
57
|
+
return <button onClick={() => supabase.auth.signOut()}>Sair</button>
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Server client — Server Components / Server Actions
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// utils/supabase/server.ts — PT-BR: client para Server Components/Actions/Route Handlers
|
|
65
|
+
import { createServerClient } from '@supabase/ssr'
|
|
66
|
+
import { cookies } from 'next/headers'
|
|
67
|
+
|
|
68
|
+
export async function createClient() {
|
|
69
|
+
const cookieStore = await cookies()
|
|
70
|
+
|
|
71
|
+
return createServerClient(
|
|
72
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
73
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
74
|
+
{
|
|
75
|
+
cookies: {
|
|
76
|
+
getAll() {
|
|
77
|
+
return cookieStore.getAll()
|
|
78
|
+
},
|
|
79
|
+
setAll(cookiesToSet) {
|
|
80
|
+
try {
|
|
81
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
82
|
+
cookieStore.set(name, value, options)
|
|
83
|
+
)
|
|
84
|
+
} catch {
|
|
85
|
+
// PT-BR: ok ignorar — chamado em Server Component (sem permissão de set)
|
|
86
|
+
// se proxy faz refresh, sessão fica saudável mesmo sem set aqui
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// PT-BR: uso em Server Component
|
|
97
|
+
import { createClient } from '@/utils/supabase/server'
|
|
98
|
+
|
|
99
|
+
export default async function Dashboard() {
|
|
100
|
+
const supabase = await createClient()
|
|
101
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
102
|
+
if (!user) return <p>Não autenticado</p>
|
|
103
|
+
return <p>Olá, {user.email}</p>
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Middleware — refresh de sessão (obrigatório)
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// middleware.ts — PT-BR: proxy obrigatório para refresh de sessão SSR
|
|
111
|
+
import { createServerClient } from '@supabase/ssr'
|
|
112
|
+
import { NextResponse, type NextRequest } from 'next/server'
|
|
113
|
+
|
|
114
|
+
export async function middleware(request: NextRequest) {
|
|
115
|
+
let supabaseResponse = NextResponse.next({ request })
|
|
116
|
+
|
|
117
|
+
const supabase = createServerClient(
|
|
118
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
119
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
120
|
+
{
|
|
121
|
+
cookies: {
|
|
122
|
+
getAll() {
|
|
123
|
+
return request.cookies.getAll()
|
|
124
|
+
},
|
|
125
|
+
setAll(cookiesToSet) {
|
|
126
|
+
cookiesToSet.forEach(({ name, value }) =>
|
|
127
|
+
request.cookies.set(name, value)
|
|
128
|
+
)
|
|
129
|
+
supabaseResponse = NextResponse.next({ request })
|
|
130
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
131
|
+
supabaseResponse.cookies.set(name, value, options)
|
|
132
|
+
)
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
// PT-BR: ATENÇÃO — não execute código entre createServerClient e getUser()
|
|
139
|
+
// (qualquer cookie set/get fora desse path quebra refresh silencioso)
|
|
140
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
141
|
+
|
|
142
|
+
// PT-BR: redirect para /login se sem user
|
|
143
|
+
if (!user && !request.nextUrl.pathname.startsWith('/login')) {
|
|
144
|
+
const url = request.nextUrl.clone()
|
|
145
|
+
url.pathname = '/login'
|
|
146
|
+
return NextResponse.redirect(url)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// PT-BR: IMPORTANTE — sempre retornar supabaseResponse (cookies precisam fluir)
|
|
150
|
+
return supabaseResponse
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const config = {
|
|
154
|
+
matcher: [
|
|
155
|
+
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
|
|
156
|
+
],
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Login com email/senha (Server Action)
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// app/login/actions.ts
|
|
164
|
+
'use server'
|
|
165
|
+
import { createClient } from '@/utils/supabase/server'
|
|
166
|
+
import { redirect } from 'next/navigation'
|
|
167
|
+
|
|
168
|
+
export async function loginAction(formData: FormData) {
|
|
169
|
+
const supabase = await createClient()
|
|
170
|
+
const { error } = await supabase.auth.signInWithPassword({
|
|
171
|
+
email: formData.get('email') as string,
|
|
172
|
+
password: formData.get('password') as string,
|
|
173
|
+
})
|
|
174
|
+
if (error) return { error: error.message }
|
|
175
|
+
redirect('/dashboard')
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Anti-patterns
|
|
180
|
+
|
|
181
|
+
### Anti-pattern 1: Importar de `@supabase/auth-helpers-nextjs`
|
|
182
|
+
|
|
183
|
+
**Errado:**
|
|
184
|
+
```ts
|
|
185
|
+
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
|
|
186
|
+
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Por quê:** `@supabase/auth-helpers-nextjs` está **DEPRECATED**. Quebra em Next.js v16+ (cookies API mudou). Não recebe mais updates de segurança.
|
|
190
|
+
|
|
191
|
+
**Certo:**
|
|
192
|
+
```ts
|
|
193
|
+
import { createServerClient, createBrowserClient } from '@supabase/ssr'
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Anti-pattern 2: `cookies: { get, set, remove }` (individual)
|
|
197
|
+
|
|
198
|
+
**Errado:**
|
|
199
|
+
```ts
|
|
200
|
+
{
|
|
201
|
+
cookies: {
|
|
202
|
+
get(name: string) { return cookieStore.get(name) },
|
|
203
|
+
set(name: string, value: string) { cookieStore.set(name, value) },
|
|
204
|
+
remove(name: string) { cookieStore.remove(name) },
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Por quê:** cookie methods individuais quebram em middleware quando há múltiplos cookies sendo set/get em uma única request. `getAll`/`setAll` são chamados em batch e preservam ordem.
|
|
210
|
+
|
|
211
|
+
**Certo:** ver pattern "Server client" e "Middleware" acima.
|
|
212
|
+
|
|
213
|
+
### Anti-pattern 3: `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY`
|
|
214
|
+
|
|
215
|
+
**Errado:**
|
|
216
|
+
```bash
|
|
217
|
+
# .env.local
|
|
218
|
+
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJhbG...
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Por quê:** `NEXT_PUBLIC_*` é **público** no client bundle. `service_role` bypassa RLS — vazamento = banco totalmente exposto.
|
|
222
|
+
|
|
223
|
+
**Certo:**
|
|
224
|
+
```bash
|
|
225
|
+
# .env.local
|
|
226
|
+
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
|
227
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbG... # ok público
|
|
228
|
+
SUPABASE_SERVICE_ROLE_KEY=eyJhbG... # privado — sem NEXT_PUBLIC_
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Use `service_role` apenas em código server-side que NUNCA é embarcado no bundle client (Route Handlers, Server Actions com `'use server'`, Edge Functions).
|
|
232
|
+
|
|
233
|
+
### Anti-pattern 4: Múltiplos serverClient em layouts (race condition)
|
|
234
|
+
|
|
235
|
+
**Errado:**
|
|
236
|
+
```tsx
|
|
237
|
+
// app/layout.tsx
|
|
238
|
+
const supabase1 = await createClient()
|
|
239
|
+
const { user } = await supabase1.auth.getUser()
|
|
240
|
+
// ...
|
|
241
|
+
// app/(dashboard)/layout.tsx
|
|
242
|
+
const supabase2 = await createClient() // ⚠ outro client na mesma request
|
|
243
|
+
const { user } = await supabase2.auth.getUser()
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Por quê:** múltiplos `createServerClient` na mesma request podem corromper cookies de refresh de token. Issue [supabase/ssr#68](https://github.com/supabase/ssr/issues/68) — race condition documentada.
|
|
247
|
+
|
|
248
|
+
**Certo:** middleware faz o refresh **uma vez por request**. Layouts apenas leem o user via `getUser()` que retorna cached:
|
|
249
|
+
```tsx
|
|
250
|
+
// app/layout.tsx — middleware já fez o refresh
|
|
251
|
+
const supabase = await createClient()
|
|
252
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Ver também
|
|
256
|
+
|
|
257
|
+
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — RLS aplicado quando user autenticado consulta tabelas
|
|
258
|
+
- [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions usando service_role server-side
|
|
259
|
+
- [supabase-realtime](../supabase-realtime/SKILL.md) — Realtime exige usuário autenticado para canais privados
|
|
260
|
+
- [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN + comandos CLI
|