3.6 KiB
3.6 KiB
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:
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:
{
"sub": "admin",
"role": "ops_lead",
"exp": 1749570000,
"iat": 1749541200
}
Login request / response
POST /api/v1/auth/login
Request:
{
"username": "admin",
"password": "805353"
}
Response 200:
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 28800,
"username": "admin",
"role": "ops_lead",
"display_name": "Chefe Ops"
}
Response 401:
{
"detail": "invalid credentials"
}
Role enum
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)
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)
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 |