# 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 já 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).
