ligbox-ops-platform/specs/003-desk-auth-rbac/research.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

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/shadow facilmente; acoplamento desnecessário
  • Emails como username → equipa já usa root/admin/mini no SSH

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 → omitido
  • email_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.