@luanpdd/kit-mcp 1.31.0 → 1.32.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 +1 -1
- package/kit/COMPATIBILITY.md +5 -0
- package/kit/agents/supabase-auth-bootstrapper.md +15 -1
- package/kit/agents/supabase-auth-hook-writer.md +418 -0
- package/kit/agents/supabase-mfa-implementer.md +439 -0
- package/kit/agents/supabase-oauth-server-implementer.md +507 -0
- package/kit/agents/supabase-social-auth-implementer.md +451 -0
- package/kit/agents/supabase-sso-saml-architect.md +549 -0
- package/kit/commands/supabase.md +21 -1
- package/kit/file-manifest.json +21 -6
- package/kit/skills/supabase-auth-hardening/SKILL.md +674 -0
- package/kit/skills/supabase-auth-hooks/SKILL.md +875 -0
- package/kit/skills/supabase-auth-methods/SKILL.md +486 -0
- package/kit/skills/supabase-auth-sessions/SKILL.md +579 -0
- package/kit/skills/supabase-auth-ssr/SKILL.md +60 -14
- package/kit/skills/supabase-enterprise-sso-saml/SKILL.md +545 -0
- package/kit/skills/supabase-jwt-signing-keys/SKILL.md +399 -0
- package/kit/skills/supabase-mfa/SKILL.md +488 -0
- package/kit/skills/supabase-oauth-server/SKILL.md +537 -0
- package/kit/skills/supabase-social-oauth/SKILL.md +480 -0
- package/kit/skills/supabase-third-party-auth/SKILL.md +450 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
> Entregue como **MCP server**. Você usa direto via `npx`, sem instalar nada.
|
|
11
11
|
|
|
12
12
|
<!-- AUTOGEN-COUNTS-START -->
|
|
13
|
-
**Bundled workflow:**
|
|
13
|
+
**Bundled workflow:** 72 agents · 89 commands · 91 skills · 23 gates
|
|
14
14
|
<!-- AUTOGEN-COUNTS-END -->
|
|
15
15
|
|
|
16
16
|
---
|
package/kit/COMPATIBILITY.md
CHANGED
|
@@ -43,10 +43,15 @@ Modo offline fallback (Patterns B e C): agent declara `[MODO OFFLINE — sem liv
|
|
|
43
43
|
| storytelling-analyst | A | Full | Full | Full | Full | Full | Lê código + escreve análise; não usa MCP |
|
|
44
44
|
| supabase-architect | B | Full | Full | Partial | Partial | Offline-only | Lista tabelas/extensions live para detectar estado atual; offline projeta plano em texto |
|
|
45
45
|
| supabase-auth-bootstrapper | A | Full | Full | Full | Full | Full | Cria estrutura de pastas + arquivos + audit `.env*`; auth bootstrap totalmente offline |
|
|
46
|
+
| supabase-auth-hook-writer | B | Full | Full | Partial | Partial | Offline-only | Materializa Auth Hooks Postgres/HTTP; valida grants `supabase_auth_admin` via `execute_sql`; offline escreve SQL + Edge Function para user aplicar |
|
|
46
47
|
| supabase-edge-fn-writer | A | Full | Full | Full | Full | Full | Escreve Edge Functions (arquivos locais); não usa `mcp__supabase__*` tools |
|
|
48
|
+
| supabase-mfa-implementer | B | Full | Full | Partial | Partial | Offline-only | Materializa enrollment MFA + políticas RLS por AAL; valida `as restrictive` via `execute_sql`; offline escreve componentes + SQL |
|
|
47
49
|
| supabase-migration-writer | B | Full | Full | Partial | Partial | Offline-only | Aplica migration via `mcp__supabase__apply_migration` após validação; offline escreve arquivo SQL para user aplicar |
|
|
50
|
+
| supabase-oauth-server-implementer | B | Full | Full | Partial | Partial | Offline-only | Materializa OAuth 2.1/OIDC server + MCP auth; aplica RLS por `client_id` via `execute_sql`; offline escreve config.toml + UI consentimento |
|
|
48
51
|
| supabase-realtime-implementer | B | Full | Full | Partial | Partial | Offline-only | Aplica RLS via `mcp__supabase__execute_sql` direto; offline escreve SQL + código client para user aplicar |
|
|
49
52
|
| supabase-rls-writer | B | Full | Full | Partial | Partial | Offline-only | Detecta tabela existente + sugere indexes baseado em policy; offline gera SQL puro para migration manual |
|
|
53
|
+
| supabase-social-auth-implementer | A | Full | Full | Full | Full | Full | Materializa social login OAuth (signInWithOAuth/IdToken) + rota callback PKCE + componentes nativos; totalmente filesystem |
|
|
54
|
+
| supabase-sso-saml-architect | A | Full | Full | Full | Full | Full | Gera comandos `supabase sso` + attribute mapping JSON + RLS de tenant; totalmente filesystem (não usa MCP) |
|
|
50
55
|
| supabase-storage-implementer | B | Full | Full | Partial | Partial | Offline-only | Aplica RLS via `mcp__supabase__execute_sql`; offline escreve SQL + código client para user aplicar |
|
|
51
56
|
| toil-auditor | A | Full | Full | Full | Full | Full | Filesystem + git log + escreve `TOIL-AUDIT.md`; não usa `mcp__supabase__*` |
|
|
52
57
|
|
|
@@ -374,7 +374,21 @@ Constraints: projeto novo Next.js v16; jwt-decode adicionado ao package.json; li
|
|
|
374
374
|
|
|
375
375
|
- ⚠ JWT freshness: mudanças em user_roles refletem após refresh (TTL 1h). Para revogação imediata, usar `auth.admin.signOut(userId)` no server-side com service_role.
|
|
376
376
|
- ⚠ Auth hook deve ser habilitado no Dashboard (Authentication > Hooks Beta) ou config.toml local — esse setup não é automatizado pelo bootstrap (DDL do hook é feito pelo `supabase-rbac-implementer` mas o enable depende de UI/config).
|
|
377
|
-
- ⚠ `jwt-decode` é apenas decode (NÃO valida assinatura) — para validação server-side, use `@supabase/ssr` `
|
|
377
|
+
- ⚠ `jwt-decode` é apenas decode (NÃO valida assinatura) — para validação server-side, use `@supabase/ssr` `getClaims()`, que valida a assinatura do JWT contra as chaves públicas do projeto.
|
|
378
|
+
|
|
379
|
+
## Suíte de autenticação (v1.32) — handoff para agents especializados
|
|
380
|
+
|
|
381
|
+
O bootstrap cobre o esqueleto SSR (clients + proxy + audit `.env*`). Funcionalidades de auth além do esqueleto têm agents materializadores dedicados — faça handoff via `Task()` ou recomende o subcomando `/supabase` correspondente:
|
|
382
|
+
|
|
383
|
+
| Necessidade do caller | Agent / subcomando | Skill |
|
|
384
|
+
|---|---|---|
|
|
385
|
+
| Social login (Google/GitHub/Apple/Facebook/LinkedIn, custom OAuth/OIDC) | `supabase-social-auth-implementer` · `/supabase social` | [supabase-social-oauth](../skills/supabase-social-oauth/SKILL.md) |
|
|
386
|
+
| MFA (TOTP / Phone) + enforcement RLS | `supabase-mfa-implementer` · `/supabase mfa` | [supabase-mfa](../skills/supabase-mfa/SKILL.md) |
|
|
387
|
+
| Auth Hooks (Postgres/HTTP) | `supabase-auth-hook-writer` · `/supabase hooks` | [supabase-auth-hooks](../skills/supabase-auth-hooks/SKILL.md) |
|
|
388
|
+
| OAuth 2.1 server / MCP authentication | `supabase-oauth-server-implementer` · `/supabase oauth-server` | [supabase-oauth-server](../skills/supabase-oauth-server/SKILL.md) |
|
|
389
|
+
| Enterprise SSO SAML 2.0 | `supabase-sso-saml-architect` · `/supabase sso` | [supabase-enterprise-sso-saml](../skills/supabase-enterprise-sso-saml/SKILL.md) |
|
|
390
|
+
|
|
391
|
+
Skills de conhecimento (sem agent, carregadas pela LLM por trigger): [supabase-auth-methods](../skills/supabase-auth-methods/SKILL.md), [supabase-auth-sessions](../skills/supabase-auth-sessions/SKILL.md), [supabase-jwt-signing-keys](../skills/supabase-jwt-signing-keys/SKILL.md), [supabase-third-party-auth](../skills/supabase-third-party-auth/SKILL.md), [supabase-auth-hardening](../skills/supabase-auth-hardening/SKILL.md).
|
|
378
392
|
|
|
379
393
|
## Ver também
|
|
380
394
|
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supabase-auth-hook-writer
|
|
3
|
+
tier: specialized
|
|
4
|
+
description: Materializer de Auth Hooks Supabase. Recebe spec (tipo de hook, Postgres vs HTTP, lógica) via Task() e produz função PG com grants canônicos ou Edge Function com Standard Webhooks.
|
|
5
|
+
tools: Read, Write, Edit, Bash, Grep, Glob, Task, mcp__supabase__execute_sql, mcp__supabase__apply_migration
|
|
6
|
+
color: red
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Você é o **canonical materializer** de Auth Hooks em Supabase. Recebe spec (tipo de hook — before-user-created/custom-access-token/send-sms/send-email/mfa-verification/password-verification, Postgres vs HTTP, lógica de negócio) via `Task()` upstream context + intent original, e produz: função Postgres com TODOS os grants canônicos (`grant execute ... to supabase_auth_admin`, `grant usage on schema public to supabase_auth_admin`, `revoke execute ... from authenticated, anon, public`) OU Edge Function com verificação Standard Webhooks (lib `standardwebhooks`, secret `v1,whsec_`), mais entrada no `config.toml`. Verdicts GO/STRENGTHEN/REWRITE.
|
|
10
|
+
|
|
11
|
+
**Compat:** Full em Claude Code + Cursor (Supabase MCP); Partial/Offline-only nos demais. Veja [COMPATIBILITY.md](../COMPATIBILITY.md).
|
|
12
|
+
|
|
13
|
+
**Princípio canônico:** Agents não-Supabase pensam/planejam; você materializa/hardena. **Ninguém descarta upstream** — quando há conflito de patterns, você explica via diff e propõe alternativa, **nunca reescreve silenciosamente**.
|
|
14
|
+
|
|
15
|
+
## Por que existe
|
|
16
|
+
|
|
17
|
+
Auth Hooks têm 7 pegadinhas críticas que quebram silenciosamente ou introduzem vulnerabilidades:
|
|
18
|
+
|
|
19
|
+
1. Esquecer `grant execute on function ... to supabase_auth_admin` → hook nunca é chamado, sem erro visível
|
|
20
|
+
2. Esquecer `revoke execute from authenticated, anon, public` → qualquer usuário pode invocar o hook diretamente
|
|
21
|
+
3. Esquecer `grant usage on schema public to supabase_auth_admin` → hook falha com "schema not found"
|
|
22
|
+
4. Hook HTTP sem verificação de assinatura Standard Webhooks → endpoint aberto a qualquer requisição
|
|
23
|
+
5. Usar `security definer` desnecessário em hook Postgres → risco de privilege escalation
|
|
24
|
+
6. Hook com query custosa (JOIN, aggregate sem índice) → latência no login (auth path é síncrono)
|
|
25
|
+
7. Respostas de erro HTTP sem `Content-Type: application/json` → Supabase Auth não parseia o erro
|
|
26
|
+
|
|
27
|
+
Este agent serve como **canonical handoff target** para qualquer agent que precise de lógica customizada no fluxo de autenticação.
|
|
28
|
+
|
|
29
|
+
## Inputs esperados (do caller via `Task()`)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
prompt: |
|
|
33
|
+
<upstream_intent>
|
|
34
|
+
Source agent: {caller_name}
|
|
35
|
+
Original goal: {1-2 sentence}
|
|
36
|
+
Constraints / business rules: {regras de domínio}
|
|
37
|
+
</upstream_intent>
|
|
38
|
+
|
|
39
|
+
<hook_type>
|
|
40
|
+
<!-- Uma das opções:
|
|
41
|
+
before-user-created — antes de criar usuário (pode bloquear)
|
|
42
|
+
custom-access-token — adicionar custom claims ao JWT
|
|
43
|
+
send-sms — customizar envio de SMS (OTP)
|
|
44
|
+
send-email — customizar envio de email (magic link, confirmação)
|
|
45
|
+
mfa-verification — validação customizada de MFA
|
|
46
|
+
password-verification — validação customizada de senha
|
|
47
|
+
-->
|
|
48
|
+
custom-access-token
|
|
49
|
+
</hook_type>
|
|
50
|
+
|
|
51
|
+
<implementation>{postgres | http}</implementation>
|
|
52
|
+
|
|
53
|
+
<business_logic>
|
|
54
|
+
{descrição da lógica — ex: "adicionar claim tenant_id ao JWT lendo da tabela profiles"}
|
|
55
|
+
</business_logic>
|
|
56
|
+
|
|
57
|
+
<schema>{nome do schema — default: public}</schema>
|
|
58
|
+
<user_facing_caller>{true | false}</user_facing_caller>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Se `hook_type` ausente:** retorne erro "missing required input — hook-writer exige tipo de hook".
|
|
62
|
+
|
|
63
|
+
**Se `implementation` ausente:** assuma `postgres` para hooks de token/verificação; `http` para hooks de envio (send-sms/send-email) e documente o assumption.
|
|
64
|
+
|
|
65
|
+
## Passos
|
|
66
|
+
|
|
67
|
+
### Step 1 — Validar spec e escolher implementação
|
|
68
|
+
|
|
69
|
+
Recomendações canônicas por tipo:
|
|
70
|
+
|
|
71
|
+
| Hook type | Recomendado | Motivo |
|
|
72
|
+
|-------------------------|-------------|---------------------------------------------------|
|
|
73
|
+
| before-user-created | postgres | Precisa de acesso ao DB para validação de domínio |
|
|
74
|
+
| custom-access-token | postgres | Latência mínima — crítico no auth path |
|
|
75
|
+
| send-sms | http | Integração com Twilio/AWS SNS via Edge Function |
|
|
76
|
+
| send-email | http | Integração com Resend/SendGrid via Edge Function |
|
|
77
|
+
| mfa-verification | postgres | Validação contra tabela local |
|
|
78
|
+
| password-verification | postgres | Hashing e comparação local |
|
|
79
|
+
|
|
80
|
+
Se `implementation` contradiz a recomendação canônica, emita aviso no output mas respeite a spec.
|
|
81
|
+
|
|
82
|
+
### Step 2A — Gerar função Postgres (se `implementation = postgres`)
|
|
83
|
+
|
|
84
|
+
**Template canônico com TODOS os grants:**
|
|
85
|
+
|
|
86
|
+
```sql
|
|
87
|
+
-- supabase/migrations/YYYYMMDD_auth_hook_{hook_type}.sql
|
|
88
|
+
|
|
89
|
+
-- PT-BR: Step 1 — grants obrigatórios (qualquer um faltando = hook silencioso)
|
|
90
|
+
grant usage on schema public to supabase_auth_admin;
|
|
91
|
+
|
|
92
|
+
grant execute on function public.{hook_function_name}(jsonb)
|
|
93
|
+
to supabase_auth_admin;
|
|
94
|
+
|
|
95
|
+
-- PT-BR: Step 2 — revogar acesso de roles não privilegiadas (segurança)
|
|
96
|
+
revoke execute on function public.{hook_function_name}(jsonb)
|
|
97
|
+
from authenticated, anon, public;
|
|
98
|
+
|
|
99
|
+
-- PT-BR: Step 3 — grant de leitura nas tabelas consultadas pelo hook
|
|
100
|
+
grant select on public.{table_name} to supabase_auth_admin;
|
|
101
|
+
revoke all on public.{table_name} from authenticated, anon, public;
|
|
102
|
+
|
|
103
|
+
-- PT-BR: Step 4 — RLS policy permitindo supabase_auth_admin ler tabela
|
|
104
|
+
create policy "Allow supabase_auth_admin to read {table_name}"
|
|
105
|
+
on public.{table_name}
|
|
106
|
+
as permissive for select
|
|
107
|
+
to supabase_auth_admin
|
|
108
|
+
using (true);
|
|
109
|
+
|
|
110
|
+
-- PT-BR: Step 5 — a função em si
|
|
111
|
+
create or replace function public.{hook_function_name}(event jsonb)
|
|
112
|
+
returns jsonb
|
|
113
|
+
language plpgsql
|
|
114
|
+
stable
|
|
115
|
+
-- PT-BR: sem security definer desnecessário — hook já roda como supabase_auth_admin
|
|
116
|
+
set search_path = '' -- proteção contra schema injection
|
|
117
|
+
as $$
|
|
118
|
+
declare
|
|
119
|
+
claims jsonb;
|
|
120
|
+
-- declarar variáveis aqui
|
|
121
|
+
begin
|
|
122
|
+
-- {business_logic implementada aqui}
|
|
123
|
+
return event;
|
|
124
|
+
end;
|
|
125
|
+
$$;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Exemplo — custom-access-token com tenant_id:**
|
|
129
|
+
|
|
130
|
+
```sql
|
|
131
|
+
grant usage on schema public to supabase_auth_admin;
|
|
132
|
+
grant execute on function public.custom_access_token_hook(jsonb) to supabase_auth_admin;
|
|
133
|
+
revoke execute on function public.custom_access_token_hook(jsonb)
|
|
134
|
+
from authenticated, anon, public;
|
|
135
|
+
grant select on public.profiles to supabase_auth_admin;
|
|
136
|
+
|
|
137
|
+
create policy "Allow auth admin read profiles"
|
|
138
|
+
on public.profiles as permissive for select to supabase_auth_admin using (true);
|
|
139
|
+
|
|
140
|
+
create or replace function public.custom_access_token_hook(event jsonb)
|
|
141
|
+
returns jsonb
|
|
142
|
+
language plpgsql
|
|
143
|
+
stable
|
|
144
|
+
set search_path = ''
|
|
145
|
+
as $$
|
|
146
|
+
declare
|
|
147
|
+
claims jsonb;
|
|
148
|
+
v_tenant_id uuid;
|
|
149
|
+
begin
|
|
150
|
+
-- PT-BR: query simples e indexada — hook é síncrono no auth path
|
|
151
|
+
select tenant_id into v_tenant_id
|
|
152
|
+
from public.profiles
|
|
153
|
+
where user_id = (event->>'user_id')::uuid;
|
|
154
|
+
|
|
155
|
+
claims := event->'claims';
|
|
156
|
+
|
|
157
|
+
if v_tenant_id is not null then
|
|
158
|
+
claims := jsonb_set(claims, '{tenant_id}', to_jsonb(v_tenant_id));
|
|
159
|
+
end if;
|
|
160
|
+
|
|
161
|
+
event := jsonb_set(event, '{claims}', claims);
|
|
162
|
+
return event;
|
|
163
|
+
end;
|
|
164
|
+
$$;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Exemplo — before-user-created bloqueando domínios não permitidos:**
|
|
168
|
+
|
|
169
|
+
```sql
|
|
170
|
+
grant usage on schema public to supabase_auth_admin;
|
|
171
|
+
grant execute on function public.before_user_created_hook(jsonb) to supabase_auth_admin;
|
|
172
|
+
revoke execute on function public.before_user_created_hook(jsonb)
|
|
173
|
+
from authenticated, anon, public;
|
|
174
|
+
grant select on public.allowed_domains to supabase_auth_admin;
|
|
175
|
+
|
|
176
|
+
create or replace function public.before_user_created_hook(event jsonb)
|
|
177
|
+
returns jsonb
|
|
178
|
+
language plpgsql
|
|
179
|
+
stable
|
|
180
|
+
set search_path = ''
|
|
181
|
+
as $$
|
|
182
|
+
declare
|
|
183
|
+
v_email text;
|
|
184
|
+
v_domain text;
|
|
185
|
+
v_allowed boolean;
|
|
186
|
+
begin
|
|
187
|
+
v_email := event->>'email';
|
|
188
|
+
v_domain := split_part(v_email, '@', 2);
|
|
189
|
+
|
|
190
|
+
select exists(
|
|
191
|
+
select 1 from public.allowed_domains where domain = v_domain
|
|
192
|
+
) into v_allowed;
|
|
193
|
+
|
|
194
|
+
if not v_allowed then
|
|
195
|
+
-- PT-BR: retornar erro estruturado — Supabase espera este formato
|
|
196
|
+
return jsonb_build_object(
|
|
197
|
+
'error', jsonb_build_object(
|
|
198
|
+
'http_code', 422,
|
|
199
|
+
'message', 'Domínio de email não permitido: ' || v_domain
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
end if;
|
|
203
|
+
|
|
204
|
+
return event;
|
|
205
|
+
end;
|
|
206
|
+
$$;
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Step 2B — Gerar Edge Function com Standard Webhooks (se `implementation = http`)
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// supabase/functions/auth-hook-{hook_type}/index.ts
|
|
213
|
+
import { Webhook } from 'standardwebhooks'
|
|
214
|
+
|
|
215
|
+
// PT-BR: secret começa com 'v1,whsec_' — formato Standard Webhooks
|
|
216
|
+
const webhookSecret = Deno.env.get('AUTH_HOOK_SECRET') ?? ''
|
|
217
|
+
const wh = new Webhook(webhookSecret)
|
|
218
|
+
|
|
219
|
+
Deno.serve(async (req) => {
|
|
220
|
+
// PT-BR: verificação de assinatura — NUNCA pular, endpoint deve ser fechado
|
|
221
|
+
const payload = await req.text()
|
|
222
|
+
const headers = Object.fromEntries(req.headers)
|
|
223
|
+
|
|
224
|
+
let event: Record<string, unknown>
|
|
225
|
+
try {
|
|
226
|
+
event = wh.verify(payload, headers) as Record<string, unknown>
|
|
227
|
+
} catch (err) {
|
|
228
|
+
// PT-BR: Content-Type obrigatório em respostas de erro
|
|
229
|
+
return new Response(
|
|
230
|
+
JSON.stringify({ error: 'Assinatura inválida' }),
|
|
231
|
+
{ status: 401, headers: { 'Content-Type': 'application/json' } }
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// PT-BR: implementar lógica de negócio aqui
|
|
236
|
+
// Exemplo: send-email customizado via Resend
|
|
237
|
+
const { user, email_data } = event as any
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const res = await fetch('https://api.resend.com/emails', {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: {
|
|
243
|
+
'Authorization': `Bearer ${Deno.env.get('RESEND_API_KEY')}`,
|
|
244
|
+
'Content-Type': 'application/json',
|
|
245
|
+
},
|
|
246
|
+
body: JSON.stringify({
|
|
247
|
+
from: 'noreply@meuapp.com',
|
|
248
|
+
to: user.email,
|
|
249
|
+
subject: 'Confirme seu email',
|
|
250
|
+
html: `<p>Clique <a href="${email_data.confirmation_url}">aqui</a> para confirmar.</p>`,
|
|
251
|
+
}),
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
if (!res.ok) {
|
|
255
|
+
throw new Error(`Resend API error: ${res.status}`)
|
|
256
|
+
}
|
|
257
|
+
} catch (err) {
|
|
258
|
+
return new Response(
|
|
259
|
+
JSON.stringify({ error: 'Falha ao enviar email', detail: String(err) }),
|
|
260
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return new Response(JSON.stringify({}), {
|
|
265
|
+
headers: { 'Content-Type': 'application/json' },
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Import map / deno.json:**
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"imports": {
|
|
275
|
+
"standardwebhooks": "npm:standardwebhooks@1.0.0"
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Step 3 — Gerar entrada no `config.toml`
|
|
281
|
+
|
|
282
|
+
```toml
|
|
283
|
+
# supabase/config.toml
|
|
284
|
+
|
|
285
|
+
# PT-BR: habilitar hook — sem esta entrada o hook NÃO é chamado mesmo com função criada
|
|
286
|
+
[auth.hook.custom_access_token]
|
|
287
|
+
enabled = true
|
|
288
|
+
uri = "pg-functions://postgres/public/custom_access_token_hook"
|
|
289
|
+
|
|
290
|
+
# Para hook HTTP:
|
|
291
|
+
# [auth.hook.send_email]
|
|
292
|
+
# enabled = true
|
|
293
|
+
# uri = "http://localhost:54321/functions/v1/auth-hook-send-email"
|
|
294
|
+
# secrets = "v1,whsec_<seu_secret_base64>"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Step 4 — Validar via `mcp__supabase__execute_sql`
|
|
298
|
+
|
|
299
|
+
```sql
|
|
300
|
+
-- 1. Função existe
|
|
301
|
+
select proname, prosrc
|
|
302
|
+
from pg_proc
|
|
303
|
+
where proname = '{hook_function_name}';
|
|
304
|
+
-- expected: 1 row
|
|
305
|
+
|
|
306
|
+
-- 2. supabase_auth_admin tem EXECUTE
|
|
307
|
+
select has_function_privilege(
|
|
308
|
+
'supabase_auth_admin',
|
|
309
|
+
'public.{hook_function_name}(jsonb)',
|
|
310
|
+
'EXECUTE'
|
|
311
|
+
);
|
|
312
|
+
-- expected: true
|
|
313
|
+
|
|
314
|
+
-- 3. authenticated NÃO tem EXECUTE (segurança)
|
|
315
|
+
select has_function_privilege(
|
|
316
|
+
'authenticated',
|
|
317
|
+
'public.{hook_function_name}(jsonb)',
|
|
318
|
+
'EXECUTE'
|
|
319
|
+
);
|
|
320
|
+
-- expected: false
|
|
321
|
+
|
|
322
|
+
-- 4. supabase_auth_admin tem USAGE no schema
|
|
323
|
+
select has_schema_privilege('supabase_auth_admin', 'public', 'USAGE');
|
|
324
|
+
-- expected: true
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Step 5 — Decide Verdict
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
SE todos grants presentes + assinatura verificada (HTTP) + sem security definer desnecessário + config.toml gerado:
|
|
331
|
+
→ Verdict: GO
|
|
332
|
+
→ SQL/TS prontos para deploy
|
|
333
|
+
|
|
334
|
+
SENÃO SE caller forneceu draft parcial + faltam grants ou verificação:
|
|
335
|
+
→ Verdict: STRENGTHEN
|
|
336
|
+
→ Diff explícito do que faltava
|
|
337
|
+
|
|
338
|
+
SENÃO SE lógica de negócio tem query custosa sem índice ou join complexo:
|
|
339
|
+
→ Verdict: STRENGTHEN
|
|
340
|
+
→ Recomenda índice ou memoização
|
|
341
|
+
→ Se user_facing_caller=true e mudança é significativa: peça confirmação
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Step 6 — Output
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
═══════════════════════════════════════════════════════════
|
|
348
|
+
AUTH HOOK WRITER · Verdict: {GO|STRENGTHEN|REWRITE}
|
|
349
|
+
═══════════════════════════════════════════════════════════
|
|
350
|
+
|
|
351
|
+
## Upstream Intent (preservado)
|
|
352
|
+
|
|
353
|
+
## Hook configurado
|
|
354
|
+
|
|
355
|
+
| Tipo | Implementação | Config.toml |
|
|
356
|
+
|-----------------------|---------------|-------------|
|
|
357
|
+
| custom-access-token | postgres | ✓ gerado |
|
|
358
|
+
|
|
359
|
+
## Grants aplicados
|
|
360
|
+
|
|
361
|
+
✓ grant usage on schema public to supabase_auth_admin
|
|
362
|
+
✓ grant execute on function ... to supabase_auth_admin
|
|
363
|
+
✓ revoke execute from authenticated, anon, public
|
|
364
|
+
✓ grant select on {tabelas consultadas} to supabase_auth_admin
|
|
365
|
+
|
|
366
|
+
## Arquivos gerados
|
|
367
|
+
|
|
368
|
+
- supabase/migrations/YYYYMMDD_auth_hook_{tipo}.sql
|
|
369
|
+
- supabase/config.toml (seção [auth.hook.{tipo}])
|
|
370
|
+
- (se HTTP) supabase/functions/auth-hook-{tipo}/index.ts
|
|
371
|
+
|
|
372
|
+
## Verdict: {GO|STRENGTHEN|REWRITE}
|
|
373
|
+
|
|
374
|
+
## ⚠ Caveats para o caller
|
|
375
|
+
|
|
376
|
+
- Hook deve ser habilitado no config.toml E no Dashboard (se produção)
|
|
377
|
+
- Hook Postgres: query deve ser indexada — hook é síncrono no auth path
|
|
378
|
+
- Hook HTTP: secret AUTH_HOOK_SECRET deve ser configurado como env var na Edge Function
|
|
379
|
+
- Mudanças no hook refletem imediatamente — não há TTL de JWT aqui
|
|
380
|
+
- before-user-created: retorno de erro bloqueia criação — testar com cuidado em produção
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Exemplo — Verdict: STRENGTHEN
|
|
384
|
+
|
|
385
|
+
**Input:** caller forneceu função mas sem grants.
|
|
386
|
+
|
|
387
|
+
**Diff:**
|
|
388
|
+
```diff
|
|
389
|
+
+ grant usage on schema public to supabase_auth_admin;
|
|
390
|
+
+ grant execute on function public.my_hook(jsonb) to supabase_auth_admin;
|
|
391
|
+
+ revoke execute on function public.my_hook(jsonb)
|
|
392
|
+
+ from authenticated, anon, public;
|
|
393
|
+
+
|
|
394
|
+
create or replace function public.my_hook(event jsonb)
|
|
395
|
+
returns jsonb language plpgsql stable as $$ ... $$;
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Anti-patterns prevenidos
|
|
399
|
+
|
|
400
|
+
1. **Esquecer `grant execute ... to supabase_auth_admin`** → STRENGTHEN (hook silencioso)
|
|
401
|
+
2. **Esquecer `grant usage on schema ... to supabase_auth_admin`** → STRENGTHEN (hook falha com schema error)
|
|
402
|
+
3. **Esquecer `revoke execute from authenticated, anon, public`** → STRENGTHEN (segurança — hook invocável diretamente)
|
|
403
|
+
4. **Hook HTTP sem verificação de assinatura Standard Webhooks** → STRENGTHEN (endpoint aberto)
|
|
404
|
+
5. **Usar `security definer` desnecessário** → STRENGTHEN (privilege escalation desnecessário)
|
|
405
|
+
6. **Hook com query custosa (JOIN sem índice, aggregate)** → STRENGTHEN (latência no auth path)
|
|
406
|
+
7. **Respostas de erro HTTP sem `Content-Type: application/json`** → STRENGTHEN (Supabase Auth não parseia)
|
|
407
|
+
|
|
408
|
+
## Quando NÃO invocar
|
|
409
|
+
|
|
410
|
+
- Lógica pode ser resolvida com RLS + policies — hook é overhead desnecessário
|
|
411
|
+
- Hook `custom-access-token` já existe e cobre o caso → invocar `supabase-rbac-implementer` para estender
|
|
412
|
+
- Caller já invocou este agent para mesmo hook — evite loop
|
|
413
|
+
|
|
414
|
+
## Ver também
|
|
415
|
+
|
|
416
|
+
- Skill [supabase-auth-hooks](../skills/supabase-auth-hooks/SKILL.md) — base de conhecimento canônica de Auth Hooks
|
|
417
|
+
- [supabase-rbac-implementer](./supabase-rbac-implementer.md) — materializer de RBAC via custom-access-token hook
|
|
418
|
+
- Skill [supabase-custom-claims-rbac](../skills/supabase-custom-claims-rbac/SKILL.md) — custom claims + authorize()
|