3.7 KiB
Research: Desk Auth & RBAC (003)
Date: 2026-06-10
Feature: 003-desk-auth-rbac
R1 — Estado actual da API (exposição pública)
Decisão: Auth JWT obrigatório em endpoints humanos.
Evidência (2026-06-10):
curl -sf https://api.ops.ligbox.com.br/api/v1/desk/tickets
# → 200, 11 tickets com company_profile/CNPJ sem auth
Endpoints sem protecção em api/app/main.py:
- 14 rotas GET/PATCH humanas
- 2 rotas POST webhook (protegidas por secret ✅)
- 2 rotas health (devem ficar públicas ✅)
Alternativas rejeitadas:
- Basic Auth no Traefik → não dá RBAC granular por endpoint
- VPN only → desk precisa ser acessível à equipa remota
- API key única partilhada → não distingue roles
R2 — Utilizadores VM122
Decisão: Mapear usernames Linux existentes para Desk; criar noc só na app.
Evidência:
| OS user | Existe VM122 | Role Desk |
|---|---|---|
| root | ✅ sudo | super_admin |
| admin | ✅ sudo | ops_lead |
| mini | ✅ sudo | technician |
| noc | ❌ criar seed app | noc |
Passwords OS bootstrap 805353 — usar mesma senha no seed Desk com obrigação de rotação documentada.
Alternativas rejeitadas:
- PAM/Linux auth directo → container Docker não vê
/etc/shadowfacilmente; acoplamento desnecessário - Emails como username → equipa já usa root/admin/mini no SSH
R3 — JWT vs session cookie
Decisão: JWT Bearer em Authorization header; token em sessionStorage no frontend.
Motivo:
- Frontend é nginx static files sem backend de sessão
- API já é stateless FastAPI
- Traefik pode servir desk + api no mesmo origin
TTL: 8 horas (turno de trabalho); refresh token fora de scope MVP.
Alternativas rejeitadas:
- HttpOnly cookie → requer same-site config Traefik + CSRF; mais complexo
- SQLite sessions → estado no servidor; YAGNI
R4 — Worker interno (audit cycle)
Decisão: Header X-Ops-Internal-Token para POST /api/v1/audit/cycle.
Evidência: worker/audit_runner.py chama API periodicamente sem utilizador humano.
Alternativas rejeitadas:
- Service account JWT com expiry longo → rotação mais complexa
- Worker escreve SQLite directo → viola separação container
R5 — Mascaramento NOC
Decisão: Server-side mask em _enrich_ticket() quando role == noc.
Campos mascarados:
company_profile.tax_id→***company_profile.address→ omitidoemail_billing,email_legal→***@***billing_state→ omitido
UI não é única linha de defesa — API também mascara.
R6 — Technician assignment
Decisão: Coluna assigned_to TEXT em tickets; PATCH permitido se assignee match ou null.
MVP: ops_lead atribui via PATCH body {status, assigned_to}; technician pode self-assign ao abrir ticket.
Alternativas rejeitadas:
- Fila separada por user → over-engineering para 11 tickets actuais
- Technician vê só assigned → demasiado restritivo sem processo de triagem definido
R7 — Rate limiting login
Decisão: In-memory dict {ip: [timestamps]} no processo API para MVP; migrar Redis se multi-worker.
Limite: 5 tentativas / 60s / IP → HTTP 429.
Alternativas rejeitadas:
- fail2ban no login HTTP → fail2ban é SSH-focused; complementar depois
R8 — Feature flag rollback
Decisão: DESK_AUTH_ENABLED=false desactiva verificação JWT (emergência).
Default true após implementação completa e verify-auth.sh verde.
R9 — Dependências Python
Decisão:
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
bcrypt==4.2.1
passlib + bcrypt compatíveis com Python 3.11 no container Debian.