DOMAIN: Admin Dashboard com RBAC
Skill gerado a partir do pack templates-claude-code. Arquivo de origem: dominio/03-dashboard-admin-rbac.md. Use como baseline e adapte ao projeto antes de mudancas grandes.
Conteudo do template
CONTEXT
RBAC implementado errado é uma vulnerabilidade de segurança, não só um bug de UX.
Times costumam criar enums hardcoded de roles e if/else espalhado pelo código — isso não escala
e garante que qualquer nova feature quebre o controle de acesso. O modelo correto é
resource + action + conditions, com verificação consistente em API e UI.
STACK ASSUMPTIONS
- Backend: qualquer framework com middleware support
- DB: PostgreSQL (com Row Level Security opcional, mas recomendado)
- Cache: Redis para cache de permissões por sessão
- Export: streams para CSV/Excel grandes (não buffer em memória)
- Frontend: React/Vue/Angular com route guards baseados em permissões
CORE CONCEPTS
- Role: agrupamento de permissions (admin, editor, viewer, billing_manager)
- Permission:
resource:action — ex: users:read, orders:write, reports:export
- Resource: entidade do sistema (users, orders, products, reports)
- Action: operação (read, write, delete, export, impersonate)
- Condition: restrição contextual (only_own, same_organization, same_region)
- Role Hierarchy: admin herda todas as permissions de editor que herda de viewer
- Row-Level Security: filtrar rows automaticamente baseado no contexto do usuário
- Audit Log: registro imutável de toda ação sensitiva com before/after state
ARCHITECTURE RULES
Modelo de Permissões
Role (admin, editor, viewer)
└── has many Permissions
└── resource: string (users, orders, reports)
└── action: string (read, write, delete, export)
└── conditions: jsonb (opcional: { scope: 'own' })
Verificação de Permissão
- Sempre verificar no backend — frontend faz UX, não segurança
- Criar
PermissionService centralizado (não if/else inline)
- Verificação:
can(user, 'orders:write') para ações simples
- Verificação com condição:
can(user, 'orders:read', { resourceOwnerId: order.userId })
- Cache de permissões resolvidas por
userId no Redis (invalididar no PATCH de role)
Audit Logging
- Log antes e depois de toda mutação em recursos sensitivos
- Campos obrigatórios:
actor_id, action, resource_type, resource_id, before, after, ip, user_agent, timestamp
- Audit logs são imutáveis — sem UPDATE/DELETE na tabela de audit
- Particionamento da tabela por mês se volume alto
Data Tables (Server-Side)
- Paginação por cursor (não offset) para tabelas grandes
- Filtros e sorts via query string validados contra lista de campos permitidos
- Nunca permitir ordenação/filtro em campo não indexado sem aviso
- Export máximo de 100k rows por requisição — acima disso, async com email
Bulk Operations
- Operações em bulk sempre assíncronas via queue
- Retornar
job_id imediatamente, status via polling ou WebSocket
- Validar cada item individualmente — retornar relatório de sucessos/falhas
- Bulk delete deve exigir confirmação explícita com count no frontend
ROUTING TABLE
| Trigger |
Action |
| GET /api/admin/users?page=...&filter=... |
Listar com server-side pagination/filter/sort |
| PATCH /api/admin/users/:id/role |
Alterar role → invalidar cache → audit log |
| POST /api/admin/users/bulk-action |
Enfileirar bulk op → retornar job_id |
| GET /api/admin/audit-logs?actor=...&resource=... |
Listar audit log com filtros |
| GET /api/admin/reports/export?format=csv |
Stream response para exports grandes |
| GET /api/admin/permissions/check |
Verificar permissão para ação/recurso específico |
| POST /api/admin/roles |
Criar role com permissions → audit log |
| PATCH /api/admin/roles/:id/permissions |
Atualizar permissions → invalidar caches |
| GET /api/admin/jobs/:id/status |
Polling de status de bulk operation |
| POST /api/admin/users/:id/impersonate |
Requer users:impersonate permission + audit log obrigatório |
| DELETE /api/admin/users/:id |
Soft delete → cascata de desativação → audit log |
| GET /api/admin/roles |
Listar roles com permission count (não expand permissions por default) |
CRITICAL RULES
- Verificação de permissão SEMPRE no backend — guard de rota no frontend é apenas UX
- Audit log para toda ação que modifica dados (não só deletes)
- Cache de permissões invalidado imediatamente ao alterar role do usuário
- Super-admin não é um role — é uma flag separada para evitar herança acidental
- Bulk operations sempre assíncronas, nunca processar > 1000 items em request síncrono
- Export de dados: stream, nunca carregar tudo em memória (OutOfMemory em produção)
- Impersonation requer: permission específica + log do admin que fez + TTL de sessão curto
- Row-level security: aplicar via
WHERE clause automática no repository layer
- Nunca expor IDs sequenciais de usuários na URL — usar UUIDs
- Permissões deny-by-default: se não tem permissão explícita, nega
COMMON PITFALLS
❌ Role check hardcoded espalhado
// ERRADO: if/else com strings mágicas em todo lugar
if (user.role === 'admin' || user.role === 'super_admin') {
await deleteUser(id);
}
// CORRETO: permission check centralizado
await permissionService.authorize(user, 'users:delete');
await deleteUser(id);
❌ Export carregado em memória
// ERRADO: OOM para 500k rows
const users = await db.users.findMany({ where: filters }); // 500k objects in RAM
res.json(users);
// CORRETO: stream
const cursor = db.users.stream({ where: filters });
res.setHeader('Content-Type', 'text/csv');
cursor.pipe(csvTransformer).pipe(res);
❌ Audit log como afterthought
// ERRADO: lembrar de logar manualmente em cada endpoint
await deleteUser(id);
await auditLog.create({ action: 'delete_user' }); // esquecido em 30% dos casos
// CORRETO: middleware/decorator automático
@Audited('users:delete')
async deleteUser(id: string, actor: User) { ... }
QUALITY GATES
FORBIDDEN
- If/else de role hardcoded fora do PermissionService centralizado
- Audit log opcional ou condicional em ações sensitivas
- Carregar dataset completo em memória para export
- Bulk operations síncronas em request HTTP
- Deletar fisicamente audit logs
- Expor stack trace ou SQL errors para usuários admin (use errorId para referência)
- Frontend como última linha de defesa para controle de acesso
1---2name: tpl-dominio-dashboard-admin-rbac3description: Template do pack (dominio/03-dashboard-admin-rbac.md). Orienta o agente em regras de negocio e requisitos de produto alinhado a esse contexto.4---56# DOMAIN: Admin Dashboard com RBAC78Skill gerado a partir do pack `templates-claude-code`. Arquivo de origem: `dominio/03-dashboard-admin-rbac.md`. Use como baseline e adapte ao projeto antes de mudancas grandes.910## Conteudo do template1112## CONTEXT13RBAC implementado errado é uma vulnerabilidade de segurança, não só um bug de UX.14Times costumam criar enums hardcoded de roles e if/else espalhado pelo código — isso não escala15e garante que qualquer nova feature quebre o controle de acesso. O modelo correto é16resource + action + conditions, com verificação consistente em API e UI.1718## STACK ASSUMPTIONS19- Backend: qualquer framework com middleware support20- DB: PostgreSQL (com Row Level Security opcional, mas recomendado)21- Cache: Redis para cache de permissões por sessão22- Export: streams para CSV/Excel grandes (não buffer em memória)23- Frontend: React/Vue/Angular com route guards baseados em permissões2425## CORE CONCEPTS26- **Role**: agrupamento de permissions (admin, editor, viewer, billing_manager)27- **Permission**: `resource:action` — ex: `users:read`, `orders:write`, `reports:export`28- **Resource**: entidade do sistema (users, orders, products, reports)29- **Action**: operação (read, write, delete, export, impersonate)30- **Condition**: restrição contextual (only_own, same_organization, same_region)31- **Role Hierarchy**: admin herda todas as permissions de editor que herda de viewer32- **Row-Level Security**: filtrar rows automaticamente baseado no contexto do usuário33- **Audit Log**: registro imutável de toda ação sensitiva com before/after state3435## ARCHITECTURE RULES3637### Modelo de Permissões38```39Role (admin, editor, viewer)40 └── has many Permissions41 └── resource: string (users, orders, reports)42 └── action: string (read, write, delete, export)43 └── conditions: jsonb (opcional: { scope: 'own' })44```4546### Verificação de Permissão47- Sempre verificar no backend — frontend faz UX, não segurança48- Criar `PermissionService` centralizado (não if/else inline)49- Verificação: `can(user, 'orders:write')` para ações simples50- Verificação com condição: `can(user, 'orders:read', { resourceOwnerId: order.userId })`51- Cache de permissões resolvidas por `userId` no Redis (invalididar no PATCH de role)5253### Audit Logging54- Log antes e depois de toda mutação em recursos sensitivos55- Campos obrigatórios: `actor_id`, `action`, `resource_type`, `resource_id`, `before`, `after`, `ip`, `user_agent`, `timestamp`56- Audit logs são **imutáveis** — sem UPDATE/DELETE na tabela de audit57- Particionamento da tabela por mês se volume alto5859### Data Tables (Server-Side)60- Paginação por cursor (não offset) para tabelas grandes61- Filtros e sorts via query string validados contra lista de campos permitidos62- Nunca permitir ordenação/filtro em campo não indexado sem aviso63- Export máximo de 100k rows por requisição — acima disso, async com email6465### Bulk Operations66- Operações em bulk sempre assíncronas via queue67- Retornar `job_id` imediatamente, status via polling ou WebSocket68- Validar cada item individualmente — retornar relatório de sucessos/falhas69- Bulk delete deve exigir confirmação explícita com count no frontend7071## ROUTING TABLE72| Trigger | Action |73|---------|--------|74| GET /api/admin/users?page=...&filter=... | Listar com server-side pagination/filter/sort |75| PATCH /api/admin/users/:id/role | Alterar role → invalidar cache → audit log |76| POST /api/admin/users/bulk-action | Enfileirar bulk op → retornar job_id |77| GET /api/admin/audit-logs?actor=...&resource=... | Listar audit log com filtros |78| GET /api/admin/reports/export?format=csv | Stream response para exports grandes |79| GET /api/admin/permissions/check | Verificar permissão para ação/recurso específico |80| POST /api/admin/roles | Criar role com permissions → audit log |81| PATCH /api/admin/roles/:id/permissions | Atualizar permissions → invalidar caches |82| GET /api/admin/jobs/:id/status | Polling de status de bulk operation |83| POST /api/admin/users/:id/impersonate | Requer `users:impersonate` permission + audit log obrigatório |84| DELETE /api/admin/users/:id | Soft delete → cascata de desativação → audit log |85| GET /api/admin/roles | Listar roles com permission count (não expand permissions por default) |8687## CRITICAL RULES881. Verificação de permissão SEMPRE no backend — guard de rota no frontend é apenas UX892. Audit log para toda ação que modifica dados (não só deletes)903. Cache de permissões invalidado imediatamente ao alterar role do usuário914. Super-admin não é um role — é uma flag separada para evitar herança acidental925. Bulk operations sempre assíncronas, nunca processar > 1000 items em request síncrono936. Export de dados: stream, nunca carregar tudo em memória (OutOfMemory em produção)947. Impersonation requer: permission específica + log do admin que fez + TTL de sessão curto958. Row-level security: aplicar via `WHERE` clause automática no repository layer969. Nunca expor IDs sequenciais de usuários na URL — usar UUIDs9710. Permissões deny-by-default: se não tem permissão explícita, nega9899## COMMON PITFALLS100101### ❌ Role check hardcoded espalhado102```javascript103// ERRADO: if/else com strings mágicas em todo lugar104if (user.role === 'admin' || user.role === 'super_admin') {105 await deleteUser(id);106}107108// CORRETO: permission check centralizado109await permissionService.authorize(user, 'users:delete');110await deleteUser(id);111```112113### ❌ Export carregado em memória114```javascript115// ERRADO: OOM para 500k rows116const users = await db.users.findMany({ where: filters }); // 500k objects in RAM117res.json(users);118119// CORRETO: stream120const cursor = db.users.stream({ where: filters });121res.setHeader('Content-Type', 'text/csv');122cursor.pipe(csvTransformer).pipe(res);123```124125### ❌ Audit log como afterthought126```javascript127// ERRADO: lembrar de logar manualmente em cada endpoint128await deleteUser(id);129await auditLog.create({ action: 'delete_user' }); // esquecido em 30% dos casos130131// CORRETO: middleware/decorator automático132@Audited('users:delete')133async deleteUser(id: string, actor: User) { ... }134```135136## QUALITY GATES137- [ ] Zero verificações de role hardcoded fora do PermissionService138- [ ] Toda rota de admin tem middleware de autenticação + autorização139- [ ] Audit log cobre 100% das mutações em recursos sensitivos140- [ ] Export usa streaming (não load em memória)141- [ ] Bulk operations retornam job_id e processam assincronamente142- [ ] Cache de permissões invalidado ao alterar role143- [ ] Impersonation tem log obrigatório e TTL de sessão144- [ ] Paginação cursor-based (não offset) em tabelas grandes145- [ ] Frontend exibe/oculta elementos baseado em permissões (UX) mas não confia nisso para segurança146- [ ] Filtros e sorts validados contra whitelist de campos permitidos147148## FORBIDDEN149- If/else de role hardcoded fora do PermissionService centralizado150- Audit log opcional ou condicional em ações sensitivas151- Carregar dataset completo em memória para export152- Bulk operations síncronas em request HTTP153- Deletar fisicamente audit logs154- Expor stack trace ou SQL errors para usuários admin (use errorId para referência)155- Frontend como última linha de defesa para controle de acesso