@luanpdd/kit-mcp 1.30.2 → 1.31.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.
Files changed (347) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -168
  3. package/gates/agent-no-recursive-dispatch.md +84 -82
  4. package/kit/COMANDOS.md +138 -138
  5. package/kit/README.md +76 -76
  6. package/kit/agents/advisor-researcher.md +107 -106
  7. package/kit/agents/ai-mutation-tester.md +1 -0
  8. package/kit/agents/assumptions-analyzer.md +108 -107
  9. package/kit/agents/audit-log-implementer.md +314 -313
  10. package/kit/agents/auditor-consistencia-isolamento.md +414 -413
  11. package/kit/agents/b2b-saas-architect.md +157 -156
  12. package/kit/agents/burn-rate-forecaster.md +1 -0
  13. package/kit/agents/cascading-failures-auditor.md +299 -298
  14. package/kit/agents/codebase-mapper.md +769 -768
  15. package/kit/agents/crm-pipeline-implementer.md +257 -256
  16. package/kit/agents/debugger.md +814 -813
  17. package/kit/agents/detector-tenant-quente.md +338 -337
  18. package/kit/agents/evolution-go-integrator.md +201 -200
  19. package/kit/agents/example-reviewer.md +22 -21
  20. package/kit/agents/executor.md +565 -564
  21. package/kit/agents/golden-signals-instrumenter.md +1 -0
  22. package/kit/agents/incident-investigator.md +1 -0
  23. package/kit/agents/integration-checker.md +201 -200
  24. package/kit/agents/invite-flow-implementer.md +190 -189
  25. package/kit/agents/legacy-characterizer.md +369 -368
  26. package/kit/agents/lgpd-compliance-auditor.md +296 -295
  27. package/kit/agents/load-shedding-instrumenter.md +1 -0
  28. package/kit/agents/multi-tenant-isolation-auditor.md +254 -253
  29. package/kit/agents/multi-tenant-rls-writer.md +341 -340
  30. package/kit/agents/nyquist-auditor.md +179 -178
  31. package/kit/agents/observability-coverage-auditor.md +316 -315
  32. package/kit/agents/observability-instrumenter.md +1 -0
  33. package/kit/agents/omm-auditor.md +1 -0
  34. package/kit/agents/org-onboarding-implementer.md +224 -223
  35. package/kit/agents/payload-capture-instrumenter.md +274 -273
  36. package/kit/agents/phase-researcher.md +697 -696
  37. package/kit/agents/plan-checker.md +273 -272
  38. package/kit/agents/planner.md +923 -922
  39. package/kit/agents/postmortem-writer.md +1 -0
  40. package/kit/agents/project-researcher.md +653 -652
  41. package/kit/agents/prr-conductor.md +1 -0
  42. package/kit/agents/refactor-safety-auditor.md +405 -404
  43. package/kit/agents/release-pipeline-auditor.md +1 -0
  44. package/kit/agents/research-synthesizer.md +246 -245
  45. package/kit/agents/roadmapper.md +678 -677
  46. package/kit/agents/schema-checker.md +1 -0
  47. package/kit/agents/seam-finder.md +360 -359
  48. package/kit/agents/shotgun-surgery-detector.md +350 -349
  49. package/kit/agents/slo-engineer.md +1 -0
  50. package/kit/agents/storytelling-analyst.md +1 -0
  51. package/kit/agents/supabase-architect.md +1 -0
  52. package/kit/agents/supabase-auth-bootstrapper.md +1 -0
  53. package/kit/agents/supabase-branching-architect.md +563 -562
  54. package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -777
  55. package/kit/agents/supabase-column-privileges-writer.md +400 -399
  56. package/kit/agents/supabase-edge-fn-tester.md +2 -1
  57. package/kit/agents/supabase-edge-fn-writer.md +2 -1
  58. package/kit/agents/supabase-migration-writer.md +386 -385
  59. package/kit/agents/supabase-rbac-implementer.md +393 -392
  60. package/kit/agents/supabase-realtime-implementer.md +364 -363
  61. package/kit/agents/supabase-rls-hardener.md +522 -521
  62. package/kit/agents/supabase-rls-writer.md +324 -323
  63. package/kit/agents/supabase-roles-implementer.md +356 -355
  64. package/kit/agents/supabase-storage-implementer.md +1 -0
  65. package/kit/agents/super-admin-implementer.md +282 -281
  66. package/kit/agents/toil-auditor.md +1 -0
  67. package/kit/agents/ui-auditor.md +438 -437
  68. package/kit/agents/ui-checker.md +303 -302
  69. package/kit/agents/ui-researcher.md +356 -355
  70. package/kit/agents/user-profiler.md +176 -175
  71. package/kit/agents/validador-evolucao-schema.md +336 -335
  72. package/kit/agents/verifier.md +729 -728
  73. package/kit/commands/adicionar-backlog.md +75 -75
  74. package/kit/commands/adicionar-fase.md +42 -42
  75. package/kit/commands/adicionar-tarefa.md +45 -45
  76. package/kit/commands/adicionar-testes.md +41 -41
  77. package/kit/commands/ajuda.md +21 -21
  78. package/kit/commands/atualizar.md +37 -37
  79. package/kit/commands/auditar-cascading.md +111 -111
  80. package/kit/commands/auditar-marco.md +179 -179
  81. package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
  82. package/kit/commands/auditar-refactor.md +219 -219
  83. package/kit/commands/auditar-release.md +109 -109
  84. package/kit/commands/auditar-uat.md +23 -23
  85. package/kit/commands/autonomo.md +40 -40
  86. package/kit/commands/branch-pr.md +24 -24
  87. package/kit/commands/burn-rate-status.md +408 -408
  88. package/kit/commands/capturar-payloads.md +193 -193
  89. package/kit/commands/caracterizar.md +212 -212
  90. package/kit/commands/concluir-marco.md +247 -247
  91. package/kit/commands/configuracoes.md +36 -36
  92. package/kit/commands/dados-distribuidos.md +188 -188
  93. package/kit/commands/definir-perfil.md +10 -10
  94. package/kit/commands/depurar.md +190 -190
  95. package/kit/commands/detectar-duplicacao.md +197 -197
  96. package/kit/commands/discutir-fase.md +131 -131
  97. package/kit/commands/encontrar-seams.md +136 -136
  98. package/kit/commands/entrar-discord.md +17 -17
  99. package/kit/commands/estatisticas.md +18 -18
  100. package/kit/commands/example-greeting.md +33 -33
  101. package/kit/commands/executar-fase.md +58 -58
  102. package/kit/commands/expresso.md +56 -56
  103. package/kit/commands/fase-ui.md +34 -34
  104. package/kit/commands/fazer.md +57 -57
  105. package/kit/commands/fio.md +125 -125
  106. package/kit/commands/fluxos-trabalho.md +64 -64
  107. package/kit/commands/forense.md +176 -176
  108. package/kit/commands/gerenciador.md +38 -38
  109. package/kit/commands/inserir-fase.md +31 -31
  110. package/kit/commands/legacy.md +263 -263
  111. package/kit/commands/limpeza.md +17 -17
  112. package/kit/commands/listar-hipoteses-fase.md +45 -45
  113. package/kit/commands/listar-workspaces.md +18 -18
  114. package/kit/commands/load-shedding.md +117 -117
  115. package/kit/commands/mapear-codebase.md +70 -70
  116. package/kit/commands/multi-tenant.md +163 -163
  117. package/kit/commands/nota.md +33 -33
  118. package/kit/commands/novo-marco.md +43 -43
  119. package/kit/commands/novo-projeto.md +41 -41
  120. package/kit/commands/novo-workspace.md +43 -43
  121. package/kit/commands/pausar-trabalho.md +37 -37
  122. package/kit/commands/perfil-usuario.md +45 -45
  123. package/kit/commands/pesquisar-fase.md +195 -195
  124. package/kit/commands/planejar-fase.md +67 -67
  125. package/kit/commands/planejar-lacunas.md +33 -33
  126. package/kit/commands/plantar-ideia.md +25 -25
  127. package/kit/commands/progresso.md +24 -24
  128. package/kit/commands/proximo.md +30 -30
  129. package/kit/commands/publicar.md +490 -490
  130. package/kit/commands/rapido.md +35 -35
  131. package/kit/commands/reaplicar-patches.md +124 -124
  132. package/kit/commands/refactor-seguro.md +321 -321
  133. package/kit/commands/relatorio-sessao.md +19 -19
  134. package/kit/commands/remover-fase.md +31 -31
  135. package/kit/commands/remover-workspace.md +26 -26
  136. package/kit/commands/resumo-marco.md +50 -50
  137. package/kit/commands/retomar-trabalho.md +40 -40
  138. package/kit/commands/revisar-backlog.md +60 -60
  139. package/kit/commands/revisar-ui.md +32 -32
  140. package/kit/commands/revisar.md +37 -37
  141. package/kit/commands/saude.md +21 -21
  142. package/kit/commands/setup-notion.md +93 -93
  143. package/kit/commands/storytelling.md +179 -179
  144. package/kit/commands/sync-main.md +68 -68
  145. package/kit/commands/validar-fase.md +35 -35
  146. package/kit/commands/verificar-tarefas.md +44 -44
  147. package/kit/commands/verificar-trabalho.md +64 -64
  148. package/kit/file-manifest.json +82 -81
  149. package/kit/framework/bin/lib/commands.cjs +959 -959
  150. package/kit/framework/bin/lib/config.cjs +442 -442
  151. package/kit/framework/bin/lib/core.cjs +1230 -1230
  152. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  153. package/kit/framework/bin/lib/init.cjs +1442 -1442
  154. package/kit/framework/bin/lib/milestone.cjs +252 -252
  155. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  156. package/kit/framework/bin/lib/phase.cjs +888 -888
  157. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  158. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  159. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  160. package/kit/framework/bin/lib/security.cjs +382 -382
  161. package/kit/framework/bin/lib/state.cjs +1031 -1031
  162. package/kit/framework/bin/lib/template.cjs +222 -222
  163. package/kit/framework/bin/lib/uat.cjs +282 -282
  164. package/kit/framework/bin/lib/verify.cjs +888 -888
  165. package/kit/framework/bin/lib/workstream.cjs +491 -491
  166. package/kit/framework/bin/tools.cjs +918 -918
  167. package/kit/framework/commands/workstreams.md +63 -63
  168. package/kit/framework/references/checkpoints.md +778 -778
  169. package/kit/framework/references/continuation-format.md +249 -249
  170. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  171. package/kit/framework/references/git-integration.md +295 -295
  172. package/kit/framework/references/git-planning-commit.md +38 -38
  173. package/kit/framework/references/model-profile-resolution.md +36 -36
  174. package/kit/framework/references/model-profiles.md +139 -139
  175. package/kit/framework/references/phase-argument-parsing.md +61 -61
  176. package/kit/framework/references/planning-config.md +202 -202
  177. package/kit/framework/references/questioning.md +162 -162
  178. package/kit/framework/references/tdd.md +263 -263
  179. package/kit/framework/references/ui-brand.md +160 -160
  180. package/kit/framework/references/user-profiling.md +657 -657
  181. package/kit/framework/references/verification-patterns.md +612 -612
  182. package/kit/framework/references/workstream-flag.md +58 -58
  183. package/kit/framework/templates/DEBUG.md +164 -164
  184. package/kit/framework/templates/UAT.md +265 -265
  185. package/kit/framework/templates/UI-SPEC.md +100 -100
  186. package/kit/framework/templates/VALIDATION.md +76 -76
  187. package/kit/framework/templates/claude-md.md +122 -122
  188. package/kit/framework/templates/codebase/architecture.md +185 -185
  189. package/kit/framework/templates/codebase/concerns.md +205 -205
  190. package/kit/framework/templates/codebase/conventions.md +204 -204
  191. package/kit/framework/templates/codebase/integrations.md +192 -192
  192. package/kit/framework/templates/codebase/stack.md +158 -158
  193. package/kit/framework/templates/codebase/structure.md +199 -199
  194. package/kit/framework/templates/codebase/testing.md +301 -301
  195. package/kit/framework/templates/config.json +44 -44
  196. package/kit/framework/templates/context.md +352 -352
  197. package/kit/framework/templates/continue-here.md +78 -78
  198. package/kit/framework/templates/copilot-instructions.md +7 -7
  199. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  200. package/kit/framework/templates/dev-preferences.md +20 -20
  201. package/kit/framework/templates/discovery.md +146 -146
  202. package/kit/framework/templates/discussion-log.md +63 -63
  203. package/kit/framework/templates/milestone-archive.md +123 -123
  204. package/kit/framework/templates/milestone.md +115 -115
  205. package/kit/framework/templates/phase-prompt.md +610 -610
  206. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  207. package/kit/framework/templates/project.md +186 -186
  208. package/kit/framework/templates/requirements.md +231 -231
  209. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  210. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  211. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  212. package/kit/framework/templates/research-project/STACK.md +120 -120
  213. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  214. package/kit/framework/templates/research.md +419 -419
  215. package/kit/framework/templates/retrospective.md +54 -54
  216. package/kit/framework/templates/roadmap.md +202 -202
  217. package/kit/framework/templates/state.md +176 -176
  218. package/kit/framework/templates/summary-complex.md +59 -59
  219. package/kit/framework/templates/summary-minimal.md +41 -41
  220. package/kit/framework/templates/summary-standard.md +48 -48
  221. package/kit/framework/templates/summary.md +209 -209
  222. package/kit/framework/templates/user-profile.md +146 -146
  223. package/kit/framework/templates/user-setup.md +256 -256
  224. package/kit/framework/templates/verification-report.md +258 -258
  225. package/kit/framework/workflows/add-phase.md +112 -112
  226. package/kit/framework/workflows/add-tests.md +351 -351
  227. package/kit/framework/workflows/add-todo.md +158 -158
  228. package/kit/framework/workflows/audit-milestone.md +340 -340
  229. package/kit/framework/workflows/audit-uat.md +109 -109
  230. package/kit/framework/workflows/autonomous.md +891 -891
  231. package/kit/framework/workflows/check-todos.md +177 -177
  232. package/kit/framework/workflows/cleanup.md +152 -152
  233. package/kit/framework/workflows/complete-milestone.md +696 -696
  234. package/kit/framework/workflows/diagnose-issues.md +231 -231
  235. package/kit/framework/workflows/discovery-phase.md +289 -289
  236. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  237. package/kit/framework/workflows/discuss-phase.md +784 -784
  238. package/kit/framework/workflows/do.md +104 -104
  239. package/kit/framework/workflows/execute-phase.md +838 -838
  240. package/kit/framework/workflows/execute-plan.md +510 -510
  241. package/kit/framework/workflows/fast.md +102 -102
  242. package/kit/framework/workflows/forensics.md +265 -265
  243. package/kit/framework/workflows/health.md +181 -181
  244. package/kit/framework/workflows/help.md +619 -619
  245. package/kit/framework/workflows/insert-phase.md +130 -130
  246. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  247. package/kit/framework/workflows/list-workspaces.md +56 -56
  248. package/kit/framework/workflows/manager.md +362 -362
  249. package/kit/framework/workflows/map-codebase.md +377 -377
  250. package/kit/framework/workflows/milestone-summary.md +223 -223
  251. package/kit/framework/workflows/new-milestone.md +486 -486
  252. package/kit/framework/workflows/new-project.md +1159 -1159
  253. package/kit/framework/workflows/new-workspace.md +237 -237
  254. package/kit/framework/workflows/next.md +97 -97
  255. package/kit/framework/workflows/node-repair.md +92 -92
  256. package/kit/framework/workflows/note.md +156 -156
  257. package/kit/framework/workflows/pause-work.md +176 -176
  258. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  259. package/kit/framework/workflows/plan-phase.md +765 -765
  260. package/kit/framework/workflows/plant-seed.md +169 -169
  261. package/kit/framework/workflows/pr-branch.md +129 -129
  262. package/kit/framework/workflows/profile-user.md +450 -450
  263. package/kit/framework/workflows/progress.md +507 -507
  264. package/kit/framework/workflows/quick.md +757 -757
  265. package/kit/framework/workflows/remove-phase.md +155 -155
  266. package/kit/framework/workflows/remove-workspace.md +90 -90
  267. package/kit/framework/workflows/research-phase.md +82 -82
  268. package/kit/framework/workflows/resume-project.md +326 -326
  269. package/kit/framework/workflows/review.md +228 -228
  270. package/kit/framework/workflows/session-report.md +146 -146
  271. package/kit/framework/workflows/settings.md +283 -283
  272. package/kit/framework/workflows/ship.md +228 -228
  273. package/kit/framework/workflows/stats.md +60 -60
  274. package/kit/framework/workflows/transition.md +671 -671
  275. package/kit/framework/workflows/ui-phase.md +302 -302
  276. package/kit/framework/workflows/ui-review.md +165 -165
  277. package/kit/framework/workflows/update.md +323 -323
  278. package/kit/framework/workflows/validate-phase.md +174 -174
  279. package/kit/framework/workflows/verify-phase.md +252 -252
  280. package/kit/framework/workflows/verify-work.md +637 -637
  281. package/kit/hooks/check-update.js +118 -118
  282. package/kit/hooks/context-monitor.js +163 -163
  283. package/kit/hooks/kit-attribution-reminder.cjs +29 -50
  284. package/kit/hooks/kit-router.cjs +137 -0
  285. package/kit/hooks/prompt-guard.js +103 -103
  286. package/kit/hooks/statusline.js +125 -125
  287. package/kit/hooks/workflow-guard.js +101 -101
  288. package/kit/settings.json +45 -45
  289. package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
  290. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
  291. package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
  292. package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
  293. package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
  294. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
  295. package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
  296. package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
  297. package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
  298. package/kit/skills/example-skill/SKILL.md +42 -42
  299. package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
  300. package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
  301. package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
  302. package/kit/skills/legacy-extract-class/SKILL.md +203 -203
  303. package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
  304. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
  305. package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
  306. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
  307. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
  308. package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
  309. package/kit/skills/member-invite-flow/SKILL.md +305 -305
  310. package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
  311. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
  312. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
  313. package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
  314. package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
  315. package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
  316. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
  317. package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
  318. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
  319. package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
  320. package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
  321. package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
  322. package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
  323. package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
  324. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
  325. package/kit/skills/supabase-edge-functions/SKILL.md +1 -1
  326. package/kit/skills/supabase-edge-functions-auth/SKILL.md +1 -1
  327. package/kit/skills/supabase-edge-functions-limits/SKILL.md +1 -1
  328. package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +1 -1
  329. package/kit/skills/supabase-edge-functions-testing/SKILL.md +1 -1
  330. package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +1 -1
  331. package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
  332. package/kit/skills/supabase-migrations/SKILL.md +297 -297
  333. package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
  334. package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
  335. package/kit/skills/supabase-realtime/SKILL.md +460 -460
  336. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
  337. package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
  338. package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
  339. package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
  340. package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
  341. package/package.json +1 -1
  342. package/src/core/kit.js +216 -216
  343. package/src/core/reflect.js +247 -247
  344. package/src/core/reverse-sync.js +372 -372
  345. package/src/core/sync.js +437 -418
  346. package/src/core/watch.js +121 -121
  347. package/src/mcp-server/index.js +794 -746
@@ -1,472 +1,472 @@
1
- ---
2
- name: supabase-custom-claims-rbac
3
- description: Use ao implementar Custom Claims via Custom Access Token Auth Hook para RBAC em Supabase…
4
- ---
5
-
6
- # Supabase — Custom Claims & RBAC via Auth Hooks
7
-
8
- ## Quando usar
9
-
10
- LLM carrega esta skill quando implementar **Role-Based Access Control (RBAC)** via Custom Claims no JWT. Pattern canônico Supabase v1.25.
11
-
12
- Trigger phrases:
13
-
14
- - "custom claims Supabase", "RBAC Supabase"
15
- - "Custom Access Token Auth Hook", "auth hook RBAC"
16
- - "user_role no JWT", "authorize() function"
17
- - "role-based access control com auth hook"
18
- - "como evitar JOIN em RLS policy" (custom claims é a alternativa moderna)
19
-
20
- ## Princípio canônico
21
-
22
- RBAC em Supabase tem **3 mecanismos de delivery** dos claims:
23
-
24
- 1. **`app_metadata`** (skill `supabase-rls-policies` v1.23) — JSONB no JWT setado via service_role admin API. Simples mas limitado (não suporta enum types, sem normalização).
25
- 2. **Dedicated role table com helper function STABLE** (skill `rbac-permissions-matrix-supabase` v1.21) — `user_roles` + `private.has_role(role)` consultada em policies. Dinâmico mas faz JOIN custoso por query.
26
- 3. **Custom Claims via Custom Access Token Auth Hook** (v1.25 — **RECOMENDADO**) — `user_role` injetado no JWT durante geração do token via auth hook. Evita JOIN em policies (claim lido direto via `auth.jwt() ->> 'user_role'`). Eventually consistent (refresh TTL).
27
-
28
- **Quando usar custom claims (v1.25):**
29
-
30
- - ✅ RBAC com 2-10 roles fixos por user (admin, moderator, user, etc.)
31
- - ✅ Permission matrix relativamente estática (mudanças em horas/dias, não segundos)
32
- - ✅ Policies que precisam ser rápidas (sem JOIN custoso)
33
- - ✅ Cliente front-end precisa saber o role (UI conditional rendering)
34
-
35
- **Quando NÃO usar custom claims:**
36
-
37
- - ❌ Permission mudada em real-time (segundos) — JWT freshness não cobre
38
- - ❌ Permission depende de row context (ex: "can edit if owner of this row") — use RLS row-level com `auth.uid()`
39
- - ❌ Multi-tenant com user em N orgs com role diferente em cada — custom claim é per-user, não per-org-context
40
-
41
- Para multi-tenant complexo, combine: custom claim para role global (super_admin) + RLS hierárquica com `private.has_role(role, org_id)` para context-aware (skill `multi-tenant-rls-hierarchy`).
42
-
43
- ## Pattern canônico — 7 passos
44
-
45
- ### Passo 1: Enum types
46
-
47
- Defina enum types Postgres para roles e permissions — tipo-seguro, refactorable, documentado.
48
-
49
- ```sql
50
- -- enum de roles canônicos
51
- create type public.app_role as enum ('admin', 'moderator', 'user');
52
-
53
- -- enum de permissions canônicos (resource.action)
54
- create type public.app_permission as enum (
55
- 'channels.delete',
56
- 'channels.create',
57
- 'messages.delete',
58
- 'messages.update',
59
- 'users.ban'
60
- );
61
- ```
62
-
63
- **Caveat:** adicionar novo enum value exige migration `alter type public.app_permission add value 'novo.permission'` — não pode ser feito dentro de transação (Postgres limitation).
64
-
65
- ### Passo 2: Tabelas user_roles + role_permissions
66
-
67
- ```sql
68
- -- USER → ROLE mapping (N:1 user pode ter múltiplos roles)
69
- create table public.user_roles (
70
- id bigint generated by default as identity primary key,
71
- user_id uuid references auth.users on delete cascade not null,
72
- role app_role not null,
73
- unique (user_id, role)
74
- );
75
- comment on table public.user_roles is 'Application roles for each user.';
76
-
77
- -- ROLE → PERMISSION mapping (N:N role tem múltiplas permissions)
78
- create table public.role_permissions (
79
- id bigint generated by default as identity primary key,
80
- role app_role not null,
81
- permission app_permission not null,
82
- unique (role, permission)
83
- );
84
- comment on table public.role_permissions is 'Application permissions for each role.';
85
- ```
86
-
87
- Seed inicial:
88
-
89
- ```sql
90
- insert into public.role_permissions (role, permission)
91
- values
92
- ('admin', 'channels.delete'),
93
- ('admin', 'channels.create'),
94
- ('admin', 'messages.delete'),
95
- ('admin', 'messages.update'),
96
- ('admin', 'users.ban'),
97
- ('moderator', 'messages.delete'),
98
- ('moderator', 'messages.update');
99
- ```
100
-
101
- ### Passo 3: Custom Access Token Auth Hook function
102
-
103
- A função `custom_access_token_hook(event jsonb) returns jsonb` roda **antes** do JWT ser issued — recebe o event (com `user_id` + claims atuais) e devolve event modificado.
104
-
105
- ```sql
106
- create or replace function public.custom_access_token_hook(event jsonb)
107
- returns jsonb
108
- language plpgsql
109
- stable
110
- as $$
111
- declare
112
- claims jsonb;
113
- user_role public.app_role;
114
- begin
115
- -- buscar role do user em user_roles
116
- select role into user_role
117
- from public.user_roles
118
- where user_id = (event->>'user_id')::uuid;
119
-
120
- claims := event->'claims';
121
-
122
- if user_role is not null then
123
- claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
124
- else
125
- claims := jsonb_set(claims, '{user_role}', 'null');
126
- end if;
127
-
128
- -- atualiza objeto claims no event original
129
- event := jsonb_set(event, '{claims}', claims);
130
-
131
- return event;
132
- end;
133
- $$;
134
- ```
135
-
136
- **Caveat para multi-role:** se user tem múltiplos roles em `user_roles`, esta versão pega o primeiro (`select role into user_role`). Para múltiplos roles no JWT, use:
137
-
138
- ```sql
139
- -- variante multi-role: claim user_roles como array
140
- select array_agg(role) into user_roles_array
141
- from public.user_roles
142
- where user_id = (event->>'user_id')::uuid;
143
- -- ...
144
- claims := jsonb_set(claims, '{user_roles}', to_jsonb(user_roles_array));
145
- ```
146
-
147
- ### Passo 4: Permissions canônicos para `supabase_auth_admin`
148
-
149
- `supabase_auth_admin` é o Postgres role usado pelo Supabase Auth service ao invocar o hook. Precisa de permissions específicos:
150
-
151
- ```sql
152
- -- 1. acesso ao schema public
153
- grant usage on schema public to supabase_auth_admin;
154
-
155
- -- 2. permissão de EXECUTE no hook function (apenas auth_admin pode invocar)
156
- grant execute
157
- on function public.custom_access_token_hook
158
- to supabase_auth_admin;
159
-
160
- -- 3. REVOKE EXECUTE de roles públicos (cliente NÃO pode chamar diretamente)
161
- revoke execute
162
- on function public.custom_access_token_hook
163
- from authenticated, anon, public;
164
-
165
- -- 4. acesso à tabela user_roles
166
- grant all
167
- on table public.user_roles
168
- to supabase_auth_admin;
169
-
170
- -- 5. REVOKE acesso público à user_roles (cliente NÃO pode mutar roles)
171
- revoke all
172
- on table public.user_roles
173
- from authenticated, anon, public;
174
-
175
- -- 6. RLS policy permitindo supabase_auth_admin ler user_roles
176
- create policy "Allow auth admin to read user roles" on public.user_roles
177
- as permissive for select
178
- to supabase_auth_admin
179
- using (true);
180
- ```
181
-
182
- **Por quê REVOKE EXECUTE do hook:** sem isso, qualquer cliente autenticado poderia chamar `custom_access_token_hook(...)` e potencialmente abusar.
183
-
184
- **Por quê REVOKE ALL FROM authenticated em user_roles:** roles são metadado privilegiado — apenas admins (service_role) podem inserir/atualizar. Cliente não deve ter SELECT direto na tabela (consultar via claim do JWT).
185
-
186
- ### Passo 5: Habilitar o hook
187
-
188
- **Dashboard (production):**
189
-
190
- 1. Acesse `Authentication > Hooks (Beta)` no Dashboard Supabase
191
- 2. Selecione "Custom Access Token" hook type
192
- 3. Dropdown: selecione `public.custom_access_token_hook` (função criada no Passo 3)
193
- 4. Save
194
-
195
- **Local development:**
196
-
197
- Em `supabase/config.toml`:
198
-
199
- ```toml
200
- [auth.hook.custom_access_token]
201
- enabled = true
202
- uri = "pg-functions://postgres/public/custom_access_token_hook"
203
- ```
204
-
205
- Reinicie o Supabase local (`supabase stop && supabase start`).
206
-
207
- ### Passo 6: `authorize()` function
208
-
209
- A função `authorize(permission app_permission) returns boolean` é o coração do RBAC — lê `user_role` do JWT e checa se o role tem a permission.
210
-
211
- ```sql
212
- create or replace function public.authorize(
213
- requested_permission app_permission
214
- )
215
- returns boolean
216
- language plpgsql
217
- stable
218
- security definer
219
- set search_path = ''
220
- as $$
221
- declare
222
- bind_permissions int;
223
- user_role public.app_role;
224
- begin
225
- -- ler user_role do JWT (delivered via auth hook do Passo 3)
226
- select (auth.jwt() ->> 'user_role')::public.app_role into user_role;
227
-
228
- -- contar permissions matching
229
- select count(*)
230
- into bind_permissions
231
- from public.role_permissions
232
- where role_permissions.permission = requested_permission
233
- and role_permissions.role = user_role;
234
-
235
- return bind_permissions > 0;
236
- end;
237
- $$;
238
- ```
239
-
240
- **Decisões canônicas:**
241
-
242
- - `stable` — função consulta DB mas não muta; resultado é estável dentro de uma transação (Postgres pode cachear)
243
- - `security definer` — roda com privilégios do owner (geralmente `postgres`) — necessário para acessar role_permissions sem RLS recursivo
244
- - `set search_path = ''` — anti schema injection (cross-ref skill `supabase-database-functions`)
245
-
246
- ### Passo 7: RLS policies usando authorize()
247
-
248
- Em vez de hard-code role em policy, use `authorize(permission)`:
249
-
250
- ```sql
251
- -- ANTES (anti-pattern): hard-code role direto na policy
252
- create policy "Allow admin delete channels" on public.channels for delete
253
- to authenticated
254
- using ((auth.jwt() ->> 'user_role') = 'admin');
255
-
256
- -- DEPOIS (canônico v1.25): authorize() abstrai role → permission
257
- create policy "Allow authorized delete access" on public.channels for delete
258
- to authenticated
259
- using ((SELECT authorize('channels.delete')));
260
-
261
- create policy "Allow authorized delete access" on public.messages for delete
262
- to authenticated
263
- using ((SELECT authorize('messages.delete')));
264
- ```
265
-
266
- **Vantagens canônicas:**
267
-
268
- - Adicionar nova permission = INSERT em role_permissions (sem alterar policy)
269
- - Mudar quem tem permission = UPDATE em role_permissions
270
- - Policies ficam estáveis; matriz permissions evolui
271
- - Reutilizável em N policies
272
-
273
- **Por que `(SELECT authorize(...))` com wrapper:** caching (cross-ref REGRA #2 da skill `supabase-rls-policies` v1.23) — função roda 1 vez por query, não 1 vez por linha.
274
-
275
- ## Client-side — acessar custom claims
276
-
277
- Auth hook só modifica o **access_token JWT**, não a auth response. Para acessar custom claims no cliente, decode o JWT:
278
-
279
- ```bash
280
- npm install jwt-decode
281
- ```
282
-
283
- ```js
284
- import { jwtDecode } from 'jwt-decode'
285
- import { createClient } from '@supabase/supabase-js'
286
-
287
- const supabase = createClient(URL, ANON_KEY)
288
-
289
- // listener para mudanças de auth state (login, logout, refresh)
290
- supabase.auth.onAuthStateChange(async (event, session) => {
291
- if (session) {
292
- const jwt = jwtDecode(session.access_token)
293
- const userRole = jwt.user_role // 'admin' | 'moderator' | 'user' | null
294
-
295
- // usar userRole para UI conditional rendering
296
- if (userRole === 'admin') {
297
- // show admin panel
298
- }
299
- }
300
- })
301
- ```
302
-
303
- **Para Next.js v16 + SSR:** decode o JWT no server-side via middleware ou Server Component:
304
-
305
- ```ts
306
- // app/middleware.ts ou Server Component
307
- import { jwtDecode } from 'jwt-decode'
308
-
309
- const { data: { session } } = await supabase.auth.getSession()
310
- if (session) {
311
- const jwt = jwtDecode(session.access_token)
312
- const userRole = jwt.user_role
313
- // ...
314
- }
315
- ```
316
-
317
- **Para backend (Node.js/Python/etc):** use bibliotecas equivalentes:
318
-
319
- - Node.js: `jsonwebtoken`, `express-jwt`, `koa-jwt`
320
- - Python: `PyJWT`
321
- - Dart: `dart_jsonwebtoken`
322
- - .NET: `Microsoft.AspNetCore.Authentication.JwtBearer`
323
-
324
- ## ⚠ Caveat — JWT Freshness
325
-
326
- Mudanças em `user_roles` **NÃO** refletem imediatamente no JWT do user — apenas após **token refresh** (default TTL 1h em Supabase).
327
-
328
- **Cenários:**
329
-
330
- | Situação | Comportamento |
331
- |----------|---------------|
332
- | Admin adiciona role moderator ao user X | User X continua com `user_role: null` até próximo refresh (até 1h) |
333
- | Admin remove role admin do user Y | User Y continua com `user_role: admin` no JWT atual; após refresh, policies bloqueiam |
334
- | Permission adicionada em role_permissions (admin → users.ban) | Reflete IMEDIATAMENTE — authorize() consulta DB cada query (stable mas re-executa em query nova) |
335
-
336
- **Implicações:**
337
-
338
- - Adicionar role: aceitável demora (user pode logout/login para forçar refresh)
339
- - **Remover role: PROBLEMA** — user comprometido pode usar JWT antigo até expirar. Para invalidação imediata:
340
-
341
- ```ts
342
- // force logout via admin API (server-side com service_role)
343
- await supabase.auth.admin.signOut(userId)
344
- // próximo request do user vai falhar; user precisa login novamente
345
- ```
346
-
347
- - Mudança em permissions matrix (role_permissions): IMEDIATA — não precisa refresh
348
-
349
- ## Anti-patterns
350
-
351
- ### Anti-pattern 1: Esquecer GRANT EXECUTE ao supabase_auth_admin
352
-
353
- **Errado:**
354
- ```sql
355
- create or replace function public.custom_access_token_hook(event jsonb) returns jsonb ...;
356
- -- esqueceu o GRANT EXECUTE TO supabase_auth_admin
357
- ```
358
-
359
- **Por quê:** auth hook falha silenciosamente — JWT é issued mas SEM o claim `user_role`. Difícil de debug.
360
-
361
- **Certo:** sempre incluir os 6 GRANTs/REVOKEs do Passo 4.
362
-
363
- ### Anti-pattern 2: Hardcode role em policy ao invés de authorize()
364
-
365
- **Errado:**
366
- ```sql
367
- create policy "admin_delete" on public.channels for delete to authenticated
368
- using ((auth.jwt() ->> 'user_role') = 'admin');
369
- ```
370
-
371
- **Por quê:** acoplamento policy ↔ role. Mudar quem pode deletar requer ALTER POLICY (DDL); com authorize(), basta UPDATE em role_permissions.
372
-
373
- **Certo:** `using ((SELECT authorize('channels.delete')))` — policy estável, matriz evolui.
374
-
375
- ### Anti-pattern 3: Assumir JWT fresh sem invalidação
376
-
377
- **Errado:**
378
- ```ts
379
- // admin remove role admin do user X
380
- await supabase.from('user_roles').delete().eq('user_id', xId).eq('role', 'admin')
381
- // CRÍTICO: user X ainda tem JWT antigo válido por até 1h
382
- ```
383
-
384
- **Por quê:** revogação de role não é imediata. Em casos de comprometimento, atacante usa o JWT antigo para abusar de privilégios admin.
385
-
386
- **Certo:** sempre force logout após mudança crítica de role:
387
- ```ts
388
- await supabase.from('user_roles').delete().eq('user_id', xId).eq('role', 'admin')
389
- await supabase.auth.admin.signOut(xId) // força refresh imediato
390
- ```
391
-
392
- ### Anti-pattern 4: Mutar app_metadata do cliente para mudar role
393
-
394
- **Errado:**
395
- ```ts
396
- // no cliente
397
- await supabase.auth.updateUser({ data: { role: 'admin' } })
398
- // user_metadata é editável pelo cliente — privilege escalation
399
- ```
400
-
401
- **Por quê:** mesmo anti-pattern de `user_metadata` da skill `supabase-rls-policies` (REGRA #1). Pior — confunde "role via custom claim" com "role via user_metadata".
402
-
403
- **Certo:** roles são mutados APENAS via INSERT em `public.user_roles` por backend (service_role) ou admin API. Cliente nunca toca em metadata de role.
404
-
405
- ### Anti-pattern 5: Auth hook que faz query custosa
406
-
407
- **Errado:**
408
- ```sql
409
- create or replace function public.custom_access_token_hook(event jsonb) returns jsonb ... as $$
410
- begin
411
- -- query custosa em cada token issuance
412
- select * into user_data from public.users
413
- join public.organizations o on u.org_id = o.id
414
- join public.subscriptions s on o.id = s.org_id
415
- where u.id = (event->>'user_id')::uuid;
416
- -- ...
417
- end;
418
- $$;
419
- ```
420
-
421
- **Por quê:** hook roda em **cada login + cada refresh**. Query lenta degrada latência de auth. Bom hook é < 10ms.
422
-
423
- **Certo:** mantenha hook simples — query single table (user_roles), aggregate se necessário, sem JOINs. Se precisa de info complexa, materialize em coluna de user_roles (denormalize).
424
-
425
- ## Postgres Roles vs Custom Claims — distinção canônica (v1.26)
426
-
427
- **Custom Claims** (v1.25) e **Postgres Roles** (v1.26) são conceitos **complementares**, não substitutos:
428
-
429
- | | Custom Claims (v1.25) | Postgres Roles (v1.26) |
430
- |---|---|---|
431
- | Tipo de access | Application access (end-users) | System access (service accounts) |
432
- | Identidade | JWT claim `user_role` | Postgres login |
433
- | Quem usa | End-users via PostgREST | Cron jobs, BI tools, ETL, admin scripts |
434
- | Granularidade | Per-permission via `authorize()` | Per schema/table/function/column |
435
- | Dinamicidade | Eventually consistent (TTL refresh) | Estática (alterações via SQL DDL) |
436
- | Exemplo | "User admin pode deletar messages" | "Cron job pode SELECT em todas tabelas para backup" |
437
-
438
- **Combine quando ambos aplicam:**
439
-
440
- - End-user com role `admin` (via custom claim) acessa endpoint que invoca função `private.org_analytics()` com SECURITY DEFINER (que tem GRANT EXECUTE apenas para Postgres role `analytics_reader`)
441
- - Service account `metabase_reader` (Postgres role) acessa view `public.user_active_tasks` com `security_invoker=true` (que respeita RLS via auth.jwt() — mas auth.jwt() é null para Postgres role login, então policies precisam considerar service_role bypass)
442
-
443
- **NÃO use Postgres roles para application access:**
444
-
445
- - ❌ Criar role Postgres `app_admin` para gerenciar quem é admin no app
446
- - ❌ Criar role Postgres por end-user
447
- - ✅ Use RLS + Custom Claims via auth hook (pattern v1.25)
448
-
449
- **NÃO use Custom Claims para system access:**
450
-
451
- - ❌ Service account cron job autenticando via JWT customizado
452
- - ❌ BI tool authenticando via auth hook
453
- - ✅ Use Postgres role dedicado (pattern v1.26)
454
-
455
- Padrão completo de Postgres roles em [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md) (v1.26).
456
-
457
- ## Cross-suite integration (v1.25)
458
-
459
- Esta skill é base para o agent novo `supabase-rbac-implementer` (Phase 139) — recebe spec (roles + permissions matrix) via `Task()` e materializa setup completo. Pattern de handoff cooperativo herdado de v1.23/v1.24.
460
-
461
- Para multi-tenant com role por org, **combine** custom claim (role global) + helper function PG (`private.has_role(role, org_id)` em RLS hierárquica — skill `multi-tenant-rls-hierarchy` v1.21). Custom claim sozinho não cobre context-aware multi-tenant.
462
-
463
- ## Ver também
464
-
465
- - [supabase-rbac-implementer](../../agents/supabase-rbac-implementer.md) (v1.25) — canonical materializer
466
- - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) (v1.23) — section "RBAC via Custom Claims + authorize() function (v1.25)"
467
- - [supabase-rls-defense-in-depth](../supabase-rls-defense-in-depth/SKILL.md) (v1.24) — Camada 9 (Auth Hooks - Custom Claims) v1.25
468
- - [supabase-database-functions](../supabase-database-functions/SKILL.md) — pattern SECURITY DEFINER + supabase_auth_admin grants
469
- - [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — Next.js v16 + onAuthStateChange + jwt-decode integration
470
- - [supabase-rls-hardener](../../agents/supabase-rls-hardener.md) (v1.23) — Detector 9 valida auth hook instalado (v1.25)
471
- - [rbac-permissions-matrix-supabase](../rbac-permissions-matrix-supabase/SKILL.md) (v1.21) — alternativa via helper function STABLE (vs custom claim v1.25)
472
- - [glossário compartilhado](../_shared-supabase/glossary.md) — termos custom claims, Custom Access Token Auth Hook, JWT user_role claim, authorize() function, supabase_auth_admin role, app_role enum, app_permission enum, jwt-decode client pattern
1
+ ---
2
+ name: supabase-custom-claims-rbac
3
+ description: Use ao implementar Custom Claims via Custom Access Token Auth Hook para RBAC em Supabase…
4
+ ---
5
+
6
+ # Supabase — Custom Claims & RBAC via Auth Hooks
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando implementar **Role-Based Access Control (RBAC)** via Custom Claims no JWT. Pattern canônico Supabase v1.25.
11
+
12
+ Trigger phrases:
13
+
14
+ - "custom claims Supabase", "RBAC Supabase"
15
+ - "Custom Access Token Auth Hook", "auth hook RBAC"
16
+ - "user_role no JWT", "authorize() function"
17
+ - "role-based access control com auth hook"
18
+ - "como evitar JOIN em RLS policy" (custom claims é a alternativa moderna)
19
+
20
+ ## Princípio canônico
21
+
22
+ RBAC em Supabase tem **3 mecanismos de delivery** dos claims:
23
+
24
+ 1. **`app_metadata`** (skill `supabase-rls-policies` v1.23) — JSONB no JWT setado via service_role admin API. Simples mas limitado (não suporta enum types, sem normalização).
25
+ 2. **Dedicated role table com helper function STABLE** (skill `rbac-permissions-matrix-supabase` v1.21) — `user_roles` + `private.has_role(role)` consultada em policies. Dinâmico mas faz JOIN custoso por query.
26
+ 3. **Custom Claims via Custom Access Token Auth Hook** (v1.25 — **RECOMENDADO**) — `user_role` injetado no JWT durante geração do token via auth hook. Evita JOIN em policies (claim lido direto via `auth.jwt() ->> 'user_role'`). Eventually consistent (refresh TTL).
27
+
28
+ **Quando usar custom claims (v1.25):**
29
+
30
+ - ✅ RBAC com 2-10 roles fixos por user (admin, moderator, user, etc.)
31
+ - ✅ Permission matrix relativamente estática (mudanças em horas/dias, não segundos)
32
+ - ✅ Policies que precisam ser rápidas (sem JOIN custoso)
33
+ - ✅ Cliente front-end precisa saber o role (UI conditional rendering)
34
+
35
+ **Quando NÃO usar custom claims:**
36
+
37
+ - ❌ Permission mudada em real-time (segundos) — JWT freshness não cobre
38
+ - ❌ Permission depende de row context (ex: "can edit if owner of this row") — use RLS row-level com `auth.uid()`
39
+ - ❌ Multi-tenant com user em N orgs com role diferente em cada — custom claim é per-user, não per-org-context
40
+
41
+ Para multi-tenant complexo, combine: custom claim para role global (super_admin) + RLS hierárquica com `private.has_role(role, org_id)` para context-aware (skill `multi-tenant-rls-hierarchy`).
42
+
43
+ ## Pattern canônico — 7 passos
44
+
45
+ ### Passo 1: Enum types
46
+
47
+ Defina enum types Postgres para roles e permissions — tipo-seguro, refactorable, documentado.
48
+
49
+ ```sql
50
+ -- enum de roles canônicos
51
+ create type public.app_role as enum ('admin', 'moderator', 'user');
52
+
53
+ -- enum de permissions canônicos (resource.action)
54
+ create type public.app_permission as enum (
55
+ 'channels.delete',
56
+ 'channels.create',
57
+ 'messages.delete',
58
+ 'messages.update',
59
+ 'users.ban'
60
+ );
61
+ ```
62
+
63
+ **Caveat:** adicionar novo enum value exige migration `alter type public.app_permission add value 'novo.permission'` — não pode ser feito dentro de transação (Postgres limitation).
64
+
65
+ ### Passo 2: Tabelas user_roles + role_permissions
66
+
67
+ ```sql
68
+ -- USER → ROLE mapping (N:1 user pode ter múltiplos roles)
69
+ create table public.user_roles (
70
+ id bigint generated by default as identity primary key,
71
+ user_id uuid references auth.users on delete cascade not null,
72
+ role app_role not null,
73
+ unique (user_id, role)
74
+ );
75
+ comment on table public.user_roles is 'Application roles for each user.';
76
+
77
+ -- ROLE → PERMISSION mapping (N:N role tem múltiplas permissions)
78
+ create table public.role_permissions (
79
+ id bigint generated by default as identity primary key,
80
+ role app_role not null,
81
+ permission app_permission not null,
82
+ unique (role, permission)
83
+ );
84
+ comment on table public.role_permissions is 'Application permissions for each role.';
85
+ ```
86
+
87
+ Seed inicial:
88
+
89
+ ```sql
90
+ insert into public.role_permissions (role, permission)
91
+ values
92
+ ('admin', 'channels.delete'),
93
+ ('admin', 'channels.create'),
94
+ ('admin', 'messages.delete'),
95
+ ('admin', 'messages.update'),
96
+ ('admin', 'users.ban'),
97
+ ('moderator', 'messages.delete'),
98
+ ('moderator', 'messages.update');
99
+ ```
100
+
101
+ ### Passo 3: Custom Access Token Auth Hook function
102
+
103
+ A função `custom_access_token_hook(event jsonb) returns jsonb` roda **antes** do JWT ser issued — recebe o event (com `user_id` + claims atuais) e devolve event modificado.
104
+
105
+ ```sql
106
+ create or replace function public.custom_access_token_hook(event jsonb)
107
+ returns jsonb
108
+ language plpgsql
109
+ stable
110
+ as $$
111
+ declare
112
+ claims jsonb;
113
+ user_role public.app_role;
114
+ begin
115
+ -- buscar role do user em user_roles
116
+ select role into user_role
117
+ from public.user_roles
118
+ where user_id = (event->>'user_id')::uuid;
119
+
120
+ claims := event->'claims';
121
+
122
+ if user_role is not null then
123
+ claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
124
+ else
125
+ claims := jsonb_set(claims, '{user_role}', 'null');
126
+ end if;
127
+
128
+ -- atualiza objeto claims no event original
129
+ event := jsonb_set(event, '{claims}', claims);
130
+
131
+ return event;
132
+ end;
133
+ $$;
134
+ ```
135
+
136
+ **Caveat para multi-role:** se user tem múltiplos roles em `user_roles`, esta versão pega o primeiro (`select role into user_role`). Para múltiplos roles no JWT, use:
137
+
138
+ ```sql
139
+ -- variante multi-role: claim user_roles como array
140
+ select array_agg(role) into user_roles_array
141
+ from public.user_roles
142
+ where user_id = (event->>'user_id')::uuid;
143
+ -- ...
144
+ claims := jsonb_set(claims, '{user_roles}', to_jsonb(user_roles_array));
145
+ ```
146
+
147
+ ### Passo 4: Permissions canônicos para `supabase_auth_admin`
148
+
149
+ `supabase_auth_admin` é o Postgres role usado pelo Supabase Auth service ao invocar o hook. Precisa de permissions específicos:
150
+
151
+ ```sql
152
+ -- 1. acesso ao schema public
153
+ grant usage on schema public to supabase_auth_admin;
154
+
155
+ -- 2. permissão de EXECUTE no hook function (apenas auth_admin pode invocar)
156
+ grant execute
157
+ on function public.custom_access_token_hook
158
+ to supabase_auth_admin;
159
+
160
+ -- 3. REVOKE EXECUTE de roles públicos (cliente NÃO pode chamar diretamente)
161
+ revoke execute
162
+ on function public.custom_access_token_hook
163
+ from authenticated, anon, public;
164
+
165
+ -- 4. acesso à tabela user_roles
166
+ grant all
167
+ on table public.user_roles
168
+ to supabase_auth_admin;
169
+
170
+ -- 5. REVOKE acesso público à user_roles (cliente NÃO pode mutar roles)
171
+ revoke all
172
+ on table public.user_roles
173
+ from authenticated, anon, public;
174
+
175
+ -- 6. RLS policy permitindo supabase_auth_admin ler user_roles
176
+ create policy "Allow auth admin to read user roles" on public.user_roles
177
+ as permissive for select
178
+ to supabase_auth_admin
179
+ using (true);
180
+ ```
181
+
182
+ **Por quê REVOKE EXECUTE do hook:** sem isso, qualquer cliente autenticado poderia chamar `custom_access_token_hook(...)` e potencialmente abusar.
183
+
184
+ **Por quê REVOKE ALL FROM authenticated em user_roles:** roles são metadado privilegiado — apenas admins (service_role) podem inserir/atualizar. Cliente não deve ter SELECT direto na tabela (consultar via claim do JWT).
185
+
186
+ ### Passo 5: Habilitar o hook
187
+
188
+ **Dashboard (production):**
189
+
190
+ 1. Acesse `Authentication > Hooks (Beta)` no Dashboard Supabase
191
+ 2. Selecione "Custom Access Token" hook type
192
+ 3. Dropdown: selecione `public.custom_access_token_hook` (função criada no Passo 3)
193
+ 4. Save
194
+
195
+ **Local development:**
196
+
197
+ Em `supabase/config.toml`:
198
+
199
+ ```toml
200
+ [auth.hook.custom_access_token]
201
+ enabled = true
202
+ uri = "pg-functions://postgres/public/custom_access_token_hook"
203
+ ```
204
+
205
+ Reinicie o Supabase local (`supabase stop && supabase start`).
206
+
207
+ ### Passo 6: `authorize()` function
208
+
209
+ A função `authorize(permission app_permission) returns boolean` é o coração do RBAC — lê `user_role` do JWT e checa se o role tem a permission.
210
+
211
+ ```sql
212
+ create or replace function public.authorize(
213
+ requested_permission app_permission
214
+ )
215
+ returns boolean
216
+ language plpgsql
217
+ stable
218
+ security definer
219
+ set search_path = ''
220
+ as $$
221
+ declare
222
+ bind_permissions int;
223
+ user_role public.app_role;
224
+ begin
225
+ -- ler user_role do JWT (delivered via auth hook do Passo 3)
226
+ select (auth.jwt() ->> 'user_role')::public.app_role into user_role;
227
+
228
+ -- contar permissions matching
229
+ select count(*)
230
+ into bind_permissions
231
+ from public.role_permissions
232
+ where role_permissions.permission = requested_permission
233
+ and role_permissions.role = user_role;
234
+
235
+ return bind_permissions > 0;
236
+ end;
237
+ $$;
238
+ ```
239
+
240
+ **Decisões canônicas:**
241
+
242
+ - `stable` — função consulta DB mas não muta; resultado é estável dentro de uma transação (Postgres pode cachear)
243
+ - `security definer` — roda com privilégios do owner (geralmente `postgres`) — necessário para acessar role_permissions sem RLS recursivo
244
+ - `set search_path = ''` — anti schema injection (cross-ref skill `supabase-database-functions`)
245
+
246
+ ### Passo 7: RLS policies usando authorize()
247
+
248
+ Em vez de hard-code role em policy, use `authorize(permission)`:
249
+
250
+ ```sql
251
+ -- ANTES (anti-pattern): hard-code role direto na policy
252
+ create policy "Allow admin delete channels" on public.channels for delete
253
+ to authenticated
254
+ using ((auth.jwt() ->> 'user_role') = 'admin');
255
+
256
+ -- DEPOIS (canônico v1.25): authorize() abstrai role → permission
257
+ create policy "Allow authorized delete access" on public.channels for delete
258
+ to authenticated
259
+ using ((SELECT authorize('channels.delete')));
260
+
261
+ create policy "Allow authorized delete access" on public.messages for delete
262
+ to authenticated
263
+ using ((SELECT authorize('messages.delete')));
264
+ ```
265
+
266
+ **Vantagens canônicas:**
267
+
268
+ - Adicionar nova permission = INSERT em role_permissions (sem alterar policy)
269
+ - Mudar quem tem permission = UPDATE em role_permissions
270
+ - Policies ficam estáveis; matriz permissions evolui
271
+ - Reutilizável em N policies
272
+
273
+ **Por que `(SELECT authorize(...))` com wrapper:** caching (cross-ref REGRA #2 da skill `supabase-rls-policies` v1.23) — função roda 1 vez por query, não 1 vez por linha.
274
+
275
+ ## Client-side — acessar custom claims
276
+
277
+ Auth hook só modifica o **access_token JWT**, não a auth response. Para acessar custom claims no cliente, decode o JWT:
278
+
279
+ ```bash
280
+ npm install jwt-decode
281
+ ```
282
+
283
+ ```js
284
+ import { jwtDecode } from 'jwt-decode'
285
+ import { createClient } from '@supabase/supabase-js'
286
+
287
+ const supabase = createClient(URL, ANON_KEY)
288
+
289
+ // listener para mudanças de auth state (login, logout, refresh)
290
+ supabase.auth.onAuthStateChange(async (event, session) => {
291
+ if (session) {
292
+ const jwt = jwtDecode(session.access_token)
293
+ const userRole = jwt.user_role // 'admin' | 'moderator' | 'user' | null
294
+
295
+ // usar userRole para UI conditional rendering
296
+ if (userRole === 'admin') {
297
+ // show admin panel
298
+ }
299
+ }
300
+ })
301
+ ```
302
+
303
+ **Para Next.js v16 + SSR:** decode o JWT no server-side via middleware ou Server Component:
304
+
305
+ ```ts
306
+ // app/middleware.ts ou Server Component
307
+ import { jwtDecode } from 'jwt-decode'
308
+
309
+ const { data: { session } } = await supabase.auth.getSession()
310
+ if (session) {
311
+ const jwt = jwtDecode(session.access_token)
312
+ const userRole = jwt.user_role
313
+ // ...
314
+ }
315
+ ```
316
+
317
+ **Para backend (Node.js/Python/etc):** use bibliotecas equivalentes:
318
+
319
+ - Node.js: `jsonwebtoken`, `express-jwt`, `koa-jwt`
320
+ - Python: `PyJWT`
321
+ - Dart: `dart_jsonwebtoken`
322
+ - .NET: `Microsoft.AspNetCore.Authentication.JwtBearer`
323
+
324
+ ## ⚠ Caveat — JWT Freshness
325
+
326
+ Mudanças em `user_roles` **NÃO** refletem imediatamente no JWT do user — apenas após **token refresh** (default TTL 1h em Supabase).
327
+
328
+ **Cenários:**
329
+
330
+ | Situação | Comportamento |
331
+ |----------|---------------|
332
+ | Admin adiciona role moderator ao user X | User X continua com `user_role: null` até próximo refresh (até 1h) |
333
+ | Admin remove role admin do user Y | User Y continua com `user_role: admin` no JWT atual; após refresh, policies bloqueiam |
334
+ | Permission adicionada em role_permissions (admin → users.ban) | Reflete IMEDIATAMENTE — authorize() consulta DB cada query (stable mas re-executa em query nova) |
335
+
336
+ **Implicações:**
337
+
338
+ - Adicionar role: aceitável demora (user pode logout/login para forçar refresh)
339
+ - **Remover role: PROBLEMA** — user comprometido pode usar JWT antigo até expirar. Para invalidação imediata:
340
+
341
+ ```ts
342
+ // force logout via admin API (server-side com service_role)
343
+ await supabase.auth.admin.signOut(userId)
344
+ // próximo request do user vai falhar; user precisa login novamente
345
+ ```
346
+
347
+ - Mudança em permissions matrix (role_permissions): IMEDIATA — não precisa refresh
348
+
349
+ ## Anti-patterns
350
+
351
+ ### Anti-pattern 1: Esquecer GRANT EXECUTE ao supabase_auth_admin
352
+
353
+ **Errado:**
354
+ ```sql
355
+ create or replace function public.custom_access_token_hook(event jsonb) returns jsonb ...;
356
+ -- esqueceu o GRANT EXECUTE TO supabase_auth_admin
357
+ ```
358
+
359
+ **Por quê:** auth hook falha silenciosamente — JWT é issued mas SEM o claim `user_role`. Difícil de debug.
360
+
361
+ **Certo:** sempre incluir os 6 GRANTs/REVOKEs do Passo 4.
362
+
363
+ ### Anti-pattern 2: Hardcode role em policy ao invés de authorize()
364
+
365
+ **Errado:**
366
+ ```sql
367
+ create policy "admin_delete" on public.channels for delete to authenticated
368
+ using ((auth.jwt() ->> 'user_role') = 'admin');
369
+ ```
370
+
371
+ **Por quê:** acoplamento policy ↔ role. Mudar quem pode deletar requer ALTER POLICY (DDL); com authorize(), basta UPDATE em role_permissions.
372
+
373
+ **Certo:** `using ((SELECT authorize('channels.delete')))` — policy estável, matriz evolui.
374
+
375
+ ### Anti-pattern 3: Assumir JWT fresh sem invalidação
376
+
377
+ **Errado:**
378
+ ```ts
379
+ // admin remove role admin do user X
380
+ await supabase.from('user_roles').delete().eq('user_id', xId).eq('role', 'admin')
381
+ // CRÍTICO: user X ainda tem JWT antigo válido por até 1h
382
+ ```
383
+
384
+ **Por quê:** revogação de role não é imediata. Em casos de comprometimento, atacante usa o JWT antigo para abusar de privilégios admin.
385
+
386
+ **Certo:** sempre force logout após mudança crítica de role:
387
+ ```ts
388
+ await supabase.from('user_roles').delete().eq('user_id', xId).eq('role', 'admin')
389
+ await supabase.auth.admin.signOut(xId) // força refresh imediato
390
+ ```
391
+
392
+ ### Anti-pattern 4: Mutar app_metadata do cliente para mudar role
393
+
394
+ **Errado:**
395
+ ```ts
396
+ // no cliente
397
+ await supabase.auth.updateUser({ data: { role: 'admin' } })
398
+ // user_metadata é editável pelo cliente — privilege escalation
399
+ ```
400
+
401
+ **Por quê:** mesmo anti-pattern de `user_metadata` da skill `supabase-rls-policies` (REGRA #1). Pior — confunde "role via custom claim" com "role via user_metadata".
402
+
403
+ **Certo:** roles são mutados APENAS via INSERT em `public.user_roles` por backend (service_role) ou admin API. Cliente nunca toca em metadata de role.
404
+
405
+ ### Anti-pattern 5: Auth hook que faz query custosa
406
+
407
+ **Errado:**
408
+ ```sql
409
+ create or replace function public.custom_access_token_hook(event jsonb) returns jsonb ... as $$
410
+ begin
411
+ -- query custosa em cada token issuance
412
+ select * into user_data from public.users
413
+ join public.organizations o on u.org_id = o.id
414
+ join public.subscriptions s on o.id = s.org_id
415
+ where u.id = (event->>'user_id')::uuid;
416
+ -- ...
417
+ end;
418
+ $$;
419
+ ```
420
+
421
+ **Por quê:** hook roda em **cada login + cada refresh**. Query lenta degrada latência de auth. Bom hook é < 10ms.
422
+
423
+ **Certo:** mantenha hook simples — query single table (user_roles), aggregate se necessário, sem JOINs. Se precisa de info complexa, materialize em coluna de user_roles (denormalize).
424
+
425
+ ## Postgres Roles vs Custom Claims — distinção canônica (v1.26)
426
+
427
+ **Custom Claims** (v1.25) e **Postgres Roles** (v1.26) são conceitos **complementares**, não substitutos:
428
+
429
+ | | Custom Claims (v1.25) | Postgres Roles (v1.26) |
430
+ |---|---|---|
431
+ | Tipo de access | Application access (end-users) | System access (service accounts) |
432
+ | Identidade | JWT claim `user_role` | Postgres login |
433
+ | Quem usa | End-users via PostgREST | Cron jobs, BI tools, ETL, admin scripts |
434
+ | Granularidade | Per-permission via `authorize()` | Per schema/table/function/column |
435
+ | Dinamicidade | Eventually consistent (TTL refresh) | Estática (alterações via SQL DDL) |
436
+ | Exemplo | "User admin pode deletar messages" | "Cron job pode SELECT em todas tabelas para backup" |
437
+
438
+ **Combine quando ambos aplicam:**
439
+
440
+ - End-user com role `admin` (via custom claim) acessa endpoint que invoca função `private.org_analytics()` com SECURITY DEFINER (que tem GRANT EXECUTE apenas para Postgres role `analytics_reader`)
441
+ - Service account `metabase_reader` (Postgres role) acessa view `public.user_active_tasks` com `security_invoker=true` (que respeita RLS via auth.jwt() — mas auth.jwt() é null para Postgres role login, então policies precisam considerar service_role bypass)
442
+
443
+ **NÃO use Postgres roles para application access:**
444
+
445
+ - ❌ Criar role Postgres `app_admin` para gerenciar quem é admin no app
446
+ - ❌ Criar role Postgres por end-user
447
+ - ✅ Use RLS + Custom Claims via auth hook (pattern v1.25)
448
+
449
+ **NÃO use Custom Claims para system access:**
450
+
451
+ - ❌ Service account cron job autenticando via JWT customizado
452
+ - ❌ BI tool authenticando via auth hook
453
+ - ✅ Use Postgres role dedicado (pattern v1.26)
454
+
455
+ Padrão completo de Postgres roles em [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md) (v1.26).
456
+
457
+ ## Cross-suite integration (v1.25)
458
+
459
+ Esta skill é base para o agent novo `supabase-rbac-implementer` (Phase 139) — recebe spec (roles + permissions matrix) via `Task()` e materializa setup completo. Pattern de handoff cooperativo herdado de v1.23/v1.24.
460
+
461
+ Para multi-tenant com role por org, **combine** custom claim (role global) + helper function PG (`private.has_role(role, org_id)` em RLS hierárquica — skill `multi-tenant-rls-hierarchy` v1.21). Custom claim sozinho não cobre context-aware multi-tenant.
462
+
463
+ ## Ver também
464
+
465
+ - [supabase-rbac-implementer](../../agents/supabase-rbac-implementer.md) (v1.25) — canonical materializer
466
+ - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) (v1.23) — section "RBAC via Custom Claims + authorize() function (v1.25)"
467
+ - [supabase-rls-defense-in-depth](../supabase-rls-defense-in-depth/SKILL.md) (v1.24) — Camada 9 (Auth Hooks - Custom Claims) v1.25
468
+ - [supabase-database-functions](../supabase-database-functions/SKILL.md) — pattern SECURITY DEFINER + supabase_auth_admin grants
469
+ - [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — Next.js v16 + onAuthStateChange + jwt-decode integration
470
+ - [supabase-rls-hardener](../../agents/supabase-rls-hardener.md) (v1.23) — Detector 9 valida auth hook instalado (v1.25)
471
+ - [rbac-permissions-matrix-supabase](../rbac-permissions-matrix-supabase/SKILL.md) (v1.21) — alternativa via helper function STABLE (vs custom claim v1.25)
472
+ - [glossário compartilhado](../_shared-supabase/glossary.md) — termos custom claims, Custom Access Token Auth Hook, JWT user_role claim, authorize() function, supabase_auth_admin role, app_role enum, app_permission enum, jwt-decode client pattern