# 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.
