176 lines
3.6 KiB
Markdown
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 |
|