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

176 lines
3.6 KiB
Markdown

# Data Model: Desk Auth & RBAC (003)
---
## desk_users (nova tabela)
| Coluna | Tipo | Obrigatório | Descrição |
|--------|------|-------------|-----------|
| `id` | INTEGER PK | sim | auto |
| `username` | TEXT UNIQUE | sim | `root`, `admin`, `mini`, `noc` |
| `password_hash` | TEXT | sim | bcrypt |
| `role` | TEXT | sim | `super_admin` \| `ops_lead` \| `technician` \| `noc` |
| `display_name` | TEXT | não | ex. "Roger" para root |
| `active` | INTEGER | sim | 1=activo, 0=desactivado |
| `last_login_at` | TEXT ISO8601 | não | UTC |
| `created_at` | TEXT ISO8601 | sim | UTC |
| `updated_at` | TEXT ISO8601 | sim | UTC |
### Seed inicial
| username | role | display_name | active |
|----------|------|--------------|--------|
| root | super_admin | Roger | 1 |
| admin | ops_lead | Chefe Ops | 1 |
| mini | technician | Suporte | 1 |
| noc | noc | NOC | 1 |
---
## tickets (alteração)
| Coluna nova | Tipo | Descrição |
|-------------|------|-----------|
| `assigned_to` | TEXT NULL | username do técnico responsável |
| `assigned_at` | TEXT ISO8601 NULL | quando foi atribuído |
Migration SQL:
```sql
ALTER TABLE tickets ADD COLUMN assigned_to TEXT;
ALTER TABLE tickets ADD COLUMN assigned_at TEXT;
```
---
## JWT payload
| Claim | Tipo | Descrição |
|-------|------|-----------|
| `sub` | string | username |
| `role` | string | role actual |
| `exp` | int | unix expiry |
| `iat` | int | issued at |
Exemplo decodificado:
```json
{
"sub": "admin",
"role": "ops_lead",
"exp": 1749570000,
"iat": 1749541200
}
```
---
## Login request / response
### POST /api/v1/auth/login
**Request**:
```json
{
"username": "admin",
"password": "805353"
}
```
**Response 200**:
```json
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 28800,
"username": "admin",
"role": "ops_lead",
"display_name": "Chefe Ops"
}
```
**Response 401**:
```json
{
"detail": "invalid credentials"
}
```
---
## Role enum
```text
super_admin > ops_lead > technician > noc
```
Ordem hierárquica usada apenas para UI; permissões são explícitas na matriz (não herança automática).
---
## Permission helpers (lógica)
```python
def can_read_tickets(role: str) -> bool:
return role in ALL_ROLES
def can_patch_ticket(role: str, ticket: dict, username: str) -> bool:
if role in ("super_admin", "ops_lead"):
return True
if role == "technician":
assignee = ticket.get("assigned_to")
return assignee is None or assignee == username
return False # noc
def can_run_audit(role: str) -> bool:
return role in ("super_admin", "ops_lead")
def can_manage_users(role: str) -> bool:
return role == "super_admin"
def should_mask_ticket(role: str) -> bool:
return role == "noc"
```
---
## Masked ticket (noc view)
Campos removidos ou substituídos em `company_profile`:
| Campo original | Valor noc |
|----------------|-----------|
| `tax_id` | `***` |
| `address` | `{}` |
| `email_billing` | `***` |
| `email_legal` | `***` |
| `phone_landline` | `***` |
| `billing_state` | omitido |
| `payload.funnel_notes[].data.company_profile` | mascarado recursivo |
---
## State: login session (client)
```text
sessionStorage:
ligbox_ops_token: "<jwt>"
ligbox_ops_user: {"username","role","display_name","expires_at"}
```
Logout: clear sessionStorage → redirect `/login.html`
---
## Endpoints auth (novos)
| Method | Path | Auth | Roles |
|--------|------|------|-------|
| POST | `/api/v1/auth/login` | público | — |
| POST | `/api/v1/auth/logout` | JWT | all |
| GET | `/api/v1/auth/me` | JWT | all |
| GET | `/api/v1/auth/users` | JWT | super_admin |
| PATCH | `/api/v1/auth/users/{username}` | JWT | super_admin |