# 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): ```bash 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 --- ## 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` → 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.