ligbox-ops-platform/specs/003-desk-auth-rbac/spec.md
Ligbox Spec Hub 3a2c64834b Initial import: ligbox-ops-platform + specs + LAPTOP + obsidian merge (CT130)
Source: VM122 /opt + obsidian-infra + LAPTOP
Hub: CT130 spec-hub 10.10.10.130
2026-06-19 17:26:41 +00:00

192 lines
10 KiB
Markdown

# Feature Specification: Desk Auth & RBAC (003)
**Feature Branch**: `003-desk-auth-rbac`
**Created**: 2026-06-10
**Status**: Draft → Ready for plan
**Input**: Proteger o Ligbox Ops Desk (API pública em `api.ops.ligbox.com.br`) com login de utilizadores e controlo de acesso por perfil (RBAC). Utilizadores iniciais mapeados aos accounts Linux da VM122: `root`, `admin`, `mini`, mais `noc` para monitorização.
**Backlog**: OPS-4 (auth), OPS-6 (RBAC), DESK-1/2 (Desk protegido)
---
## User Scenarios & Testing *(mandatory)*
### User Story 1 — Login no Support Desk (Priority: P1)
Como membro da equipa Ligbox, quero autenticar-me no Desk com utilizador e senha para aceder a tickets e dashboards sem que dados de clientes fiquem expostos na internet.
**Why this priority**: Hoje qualquer pessoa com o URL lê tickets, CNPJs e perfis de empresa. É o maior risco de segurança e privacidade da plataforma.
**Independent Test**: Abrir `https://desk.ligbox.com.br` sem sessão → ver ecrã de login. Após credenciais válidas → dashboard carrega. `curl` sem token em `/api/v1/desk/tickets` → HTTP 401.
**Acceptance Scenarios**:
1. **Given** utilizador não autenticado, **When** abre o Desk UI, **Then** vê formulário de login (não o dashboard).
2. **Given** credenciais válidas (`root` / senha correcta), **When** submete login, **Then** recebe token de sessão e acede ao dashboard.
3. **Given** credenciais inválidas, **When** submete login, **Then** recebe erro genérico sem revelar se o utilizador existe.
4. **Given** token expirado, **When** UI chama API protegida, **Then** redirecciona para login.
5. **Given** pedido API sem `Authorization` nem webhook secret, **When** acede endpoint humano (`/api/v1/desk/*`), **Then** HTTP 401.
---
### User Story 2 — Perfis e permissões (Priority: P1)
Como administrador, quero que cada técnico veja e faça apenas o que o seu perfil permite — Roger com controlo total, chefe de ops com gestão operacional, suporte com tickets, NOC só leitura.
**Why this priority**: Autenticação sem autorização não resolve o problema; roles definem o modelo operacional da equipa.
**Independent Test**: Login como `mini` (technician) → pode ver tickets e fechar os atribuídos. Login como `noc` → vê dashboard e alertas Wazuh mas botão "Fechar ticket" ausente e PATCH retorna 403.
**Acceptance Scenarios**:
1. **Given** `root` (super_admin), **When** acede qualquer endpoint humano, **Then** permitido (incluindo gestão de utilizadores).
2. **Given** `admin` (ops_lead), **When** tenta criar utilizador, **Then** HTTP 403; **When** fecha ticket ou dispara audit, **Then** permitido.
3. **Given** `mini` (technician), **When** consulta tickets, **Then** vê lista completa; **When** altera ticket não atribuído a si, **Then** HTTP 403 (excepto tickets sem assignee — pode assumir).
4. **Given** `noc`, **When** consulta dashboard/health/Wazuh, **Then** permitido em leitura; **When** tenta PATCH ticket ou POST audit, **Then** HTTP 403.
5. **Given** `noc`, **When** vê ticket com `company_profile`, **Then** campos sensíveis (CNPJ, morada, emails billing) mascarados na resposta API e UI.
---
### User Story 3 — Webhooks e integrações intactos (Priority: P1)
Como sistema integrado (VM112, Wazuh), continuo a enviar eventos via secret sem passar pelo login humano — a auth RBAC não pode quebrar o funil de onboarding.
**Why this priority**: A plataforma já recebe eventos de produção; regressão aqui bloqueia novos clientes.
**Independent Test**: `POST /api/v1/webhooks/onboard` com `X-Webhook-Secret` válido sem JWT → HTTP 200. Com secret inválido → 401.
**Acceptance Scenarios**:
1. **Given** header `X-Webhook-Secret` válido, **When** POST webhook onboard ou wazuh, **Then** processado sem JWT.
2. **Given** JWT válido mas sem secret, **When** POST webhook, **Then** HTTP 401 (webhooks não aceitam JWT como substituto).
3. **Given** `GET /health`, **When** sem auth, **Then** HTTP 200 (healthcheck Traefik/monitoring).
---
### User Story 4 — Gestão de utilizadores (Priority: P2)
Como super_admin (Roger), quero listar utilizadores do Desk, activar/desactivar contas e alterar roles sem SSH na VM.
**Why this priority**: Operação diária; não bloqueia MVP se seeds iniciais bastarem no lançamento.
**Independent Test**: Login `root``GET /api/v1/auth/users` lista 4 users. `PATCH` role de `mini` → technician mantido; tentativa por `admin` → 403.
**Acceptance Scenarios**:
1. **Given** super_admin autenticado, **When** lista utilizadores, **Then** vê username, role, activo, último login (sem password hash).
2. **Given** super_admin, **When** desactiva utilizador `noc`, **Then** login desse user falha no próximo pedido.
3. **Given** ops_lead ou inferior, **When** acede `/api/v1/auth/users`, **Then** HTTP 403.
---
## Utilizadores iniciais (seed VM122)
| Username | Role | Perfil operacional | Senha inicial |
|----------|------|-------------------|---------------|
| `root` | `super_admin` | Roger / dono — tudo: users, tenants, audit, tickets, config | `805353` (bootstrap; **rotacionar em produção**) |
| `admin` | `ops_lead` | Chefe de operações — tickets, audit, fechar casos, funil completo | `805353` |
| `mini` | `technician` | Suporte N1/N2 — tickets atribuídos, timeline, acções limitadas | `805353` |
| `noc` | `noc` | Monitorização — só leitura: dashboard, Wazuh, health | `805353` |
> **Nota**: Accounts Linux (`root`, `admin`, `mini`) já existem na VM122 com sudo. O utilizador `noc` é criado no seed do Desk (conta só na app, não requer user OS). Passwords Desk são independentes do OS após seed — alteração no Desk não muda SSH.
---
## Matriz de permissões (RBAC)
Legenda: ✅ permitido · 🔒 leitura restrita (dados mascarados) · ❌ negado
| Recurso / Acção | super_admin | ops_lead | technician | noc |
|-----------------|:-----------:|:--------:|:----------:|:---:|
| Login / logout | ✅ | ✅ | ✅ | ✅ |
| `GET /health` | ✅ público | ✅ | ✅ | ✅ |
| Webhooks (`POST /webhooks/*`) | secret | secret | secret | secret |
| Dashboard summary | ✅ | ✅ | ✅ | 🔒 |
| Listar tickets | ✅ | ✅ | ✅ | 🔒 |
| Ver detalhe ticket | ✅ | ✅ | ✅ | 🔒 |
| Fechar / reabrir ticket | ✅ | ✅ | ✅* | ❌ |
| Atribuir ticket (`assigned_to`) | ✅ | ✅ | ✅** | ❌ |
| Funil onboarding (completo) | ✅ | ✅ | parcial | 🔒 resumo |
| Timeline sessão | ✅ | ✅ | ✅ | 🔒 |
| Audit overview / scorecard | ✅ | ✅ | ❌ | 🔒 |
| Disparar audit manual | ✅ | ✅ | ❌ | ❌ |
| Listar tenants | ✅ | ✅ | ✅ | 🔒 |
| Eventos webhook (todos) | ✅ | ✅ | onboard+wazuh | wazuh only |
| Infra status (VM112, Wazuh) | ✅ | ✅ | ✅ | ✅ |
| Gestão utilizadores | ✅ | ❌ | ❌ | ❌ |
| Ver `company_profile` completo | ✅ | ✅ | ✅ | ❌ mascarado |
| Ver `billing_state` | ✅ | ✅ | ✅ | ❌ |
\* technician: PATCH apenas se `assigned_to` = self OU `assigned_to` IS NULL (pode assumir ao fechar).
\** technician: pode atribuir a si próprio; ops_lead+ pode atribuir a qualquer user.
---
## Functional Requirements
- **FR-001**: Sistema MUST exigir autenticação (JWT Bearer) em todos os endpoints humanos sob `/api/v1/` excepto `/health`, `/api/health`, e webhooks.
- **FR-002**: Sistema MUST validar role em cada endpoint conforme matriz RBAC.
- **FR-003**: Sistema MUST armazenar passwords com hash bcrypt (cost ≥ 12); nunca plaintext.
- **FR-004**: Sistema MUST emitir JWT com claims: `sub` (username), `role`, `exp` (TTL configurável, default 8h).
- **FR-005**: Sistema MUST manter canal paralelo de auth para webhooks via `X-Webhook-Secret` (inalterado).
- **FR-006**: UI MUST apresentar login antes de qualquer vista; MUST enviar `Authorization: Bearer <token>` em pedidos API.
- **FR-007**: UI MUST ocultar acções não permitidas ao role (ex.: noc sem botão fechar ticket).
- **FR-008**: Sistema MUST mascarar campos sensíveis em respostas para role `noc` (`tax_id`, morada, emails billing).
- **FR-009**: Sistema MUST seed 4 utilizadores na primeira execução se tabela `desk_users` vazia.
- **FR-010**: super_admin MUST poder listar, activar/desactivar users e alterar roles (P2).
- **FR-011**: Sistema MUST adicionar `assigned_to` (nullable) em tickets para controlo technician (P2 mínimo: campo + PATCH por ops_lead).
- **FR-012**: Sistema MUST registar `last_login_at` por utilizador.
- **FR-013**: Sistema MUST falhar de forma segura: 401 sem token, 403 com token mas sem permissão.
---
## Success Criteria
- **SC-001**: 100% dos endpoints humanos devolvem 401 sem autenticação (verificado por script `verify-auth.sh`).
- **SC-002**: Nenhum dado de ticket acessível publicamente em `api.ops.ligbox.com.br` após deploy.
- **SC-003**: Webhooks VM112 e Wazuh continuam a funcionar sem alteração de secret.
- **SC-004**: Cada um dos 4 roles passa testes de permissão documentados em quickstart.
- **SC-005**: Tempo de login < 2s p95 na LAN.
---
## Edge Cases
- Token expirado durante sessão activa UI pede re-login; não perde navegação abrupta sem mensagem.
- Utilizador desactivado com token válido próximo pedido API retorna 401.
- Traefik healthcheck usa `/health` permanece público.
- Worker interno chama API (audit cycle) usa `OPS_INTERNAL_TOKEN` ou chama localhost sem auth (decisão no plan).
- Brute force login rate limit 5 tentativas/min por IP (Redis ou in-memory MVP).
- CORS: frontend e API no mesmo origin via Traefik (`desk.ligbox.com.br/api` proxy) validar no deploy.
---
## Assumptions
- VM122 Debian 12, API FastAPI existente, SQLite `ops.db`.
- Frontend estático servido por nginx; sem framework JS pesado.
- Traefik CT114 termina TLS e expõe `desk.ligbox.com.br` e `api.ops.ligbox.com.br`.
- Senha bootstrap `805353` será rotacionada após primeiro login super_admin (processo manual documentado).
- Assignment de tickets (FR-011) pode entrar na mesma entrega se simples; caso contrário fase 2 da 003.
---
## Out of Scope
- SSO / OAuth externo (Google, Azure AD)
- MFA / TOTP no Desk (portal onboard tem; Desk fica para spec futura)
- Permissões por tenant (todos os roles Ligbox vêem todos os tenants no MVP)
- Audit log de acções de utilizador (spec futura compliance)
- Sincronização automática password Desk Linux PAM
---
## Dependencies
- Features **001** (webhooks) e **002** (Wazuh) deployadas e funcionais.
- `python-jose` ou `PyJWT` + `passlib[bcrypt]` no requirements API.
- Redis existente (rate limit opcional).