- shared/platform-tools: ea3616a → 12f26fd (27 commits). Traz o módulo de audit (f761d99, o pin da frota), «manter sessão iniciada», o registo de segurança da frota (o gancho que o login já tinha passa a funcionar) e a correção do IP do cliente encontrada nesta auditoria (último X-Forwarded-For / X-Real-IP), promovida ao partilhado. client_ip() da app delega nele. - config/functions_audit.php: audit() sobre platform_audit(); tabela audit_log criada no primeiro uso, segredos ocultados, nunca parte a ação. - Registado: login, falha de login, bloqueio por excesso de tentativas, logout, pedidos de recuperação, mudança/definição de palavra-passe, registo e criação de utilizadores, convites, perfil (antes/depois), todas as definições (db_update_setting, com valor anterior), cada passo da lixeira (apagar, restaurar, eliminar de vez) e ações nas mensagens. - Sistema → Auditoria (views/sysadmin/audit.php), só sysadmin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
3.0 KiB
PHP
70 lines
3.0 KiB
PHP
<?php
|
|
// ─── Endpoint da bolha de feedback ───────────────────────────────────────────
|
|
//
|
|
// POST multipart/form-data (o que um <form> envia), resposta JSON:
|
|
// {"ok":true,"ref":"T-1234","track_url":"https://…"}
|
|
// {"ok":false,"error":"…"}
|
|
//
|
|
// Ao contrário dos outros endpoints desta pasta, este NÃO se autentica por
|
|
// chave: quem chama é o browser de um administrador com sessão aberta neste
|
|
// site. Precisa por isso da sessão e dos helpers de autenticação do site — e
|
|
// obtém-nos do mesmo hub_bootstrap.php que os outros, que abre o config/db.php
|
|
// do site e deixa em cima da mesa a sessão aberta, o $pdo e o functions_hub.
|
|
//
|
|
// Assim o stub de cada site é uma linha, igual à dos restantes endpoints.
|
|
//
|
|
// Sendo autenticado por cookie, é atacável por CSRF: um site qualquer podia
|
|
// levar o browser de um administrador a abrir tickets em nome dele. Daí o
|
|
// token do platform_csrf_ok(). Não substitui a verificação do papel — CSRF e
|
|
// permissão respondem a perguntas diferentes.
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../hub_bootstrap.php';
|
|
require_once __DIR__ . '/../functions_feedback.php';
|
|
require_once __DIR__ . '/../functions_csrf.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
// Nada disto deve ficar em cache nem ser partilhado entre sessões.
|
|
header('Cache-Control: no-store');
|
|
|
|
function feedback_out(int $status, array $payload): never
|
|
{
|
|
http_response_code($status);
|
|
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
|
feedback_out(405, ['ok' => false, 'error' => 'Usa POST.']);
|
|
}
|
|
|
|
$user = platform_feedback_user();
|
|
if (!$user || !platform_feedback_is_admin($user)) {
|
|
// 403 e não 401: quem chega aqui sem sessão não tem como a obter a partir
|
|
// deste endereço, e um 401 mandaria o browser pedir credenciais.
|
|
feedback_out(403, ['ok' => false, 'error' => 'Sessão terminada ou sem permissão. Recarrega a página.']);
|
|
}
|
|
|
|
if (!platform_csrf_ok()) {
|
|
feedback_out(403, ['ok' => false, 'error' => 'Pedido rejeitado (token expirado). Recarrega a página e tenta outra vez.']);
|
|
}
|
|
|
|
$res = platform_feedback_submit([
|
|
'title' => (string) ($_POST['title'] ?? ''),
|
|
'description' => (string) ($_POST['description'] ?? ''),
|
|
'category' => (string) ($_POST['category'] ?? 'problema'),
|
|
'blocking' => !empty($_POST['blocking']),
|
|
'page_url' => (string) ($_POST['page_url'] ?? ''),
|
|
], $user);
|
|
|
|
// 200 mesmo quando o hub recusa: o erro já vem escrito para a pessoa ler, e é
|
|
// o corpo que a bolha mostra. Um 4xx aqui só faria o fetch tratar como avaria
|
|
// de rede uma coisa que tem explicação.
|
|
feedback_out(200, [
|
|
'ok' => (bool) $res['ok'],
|
|
'error' => (string) ($res['error'] ?? ''),
|
|
'ref' => (string) ($res['ref'] ?? ''),
|
|
'track_url' => (string) ($res['track_url'] ?? ''),
|
|
]);
|