145 lines
3.7 KiB
Markdown
145 lines
3.7 KiB
Markdown
# 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.
|