feat(audit): platform-tools atualizado (12f26fd) e registo de auditoria ligado

- 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>
This commit is contained in:
andreandClaude Opus 5.5 committed 2026-09-25 07:28:13 +01:00
1 parent 5e300fc341
commit 0e8b53f620
65 files changed
+5852 -455

No files matched your search

@@ -115,6 +115,13 @@ function platform_antispam_ident_ok(string $ident): bool
* tabela de settings. Não é um segredo tão bom como os outros, mas é
* constante — e uma defesa que tranca todos os formulários é pior do que
* uma assinatura mais fraca.
*
* A mesma escada vive agora em platform_derived_key() (functions_settings.php),
* que é o que os módulos novos usam — o captcha, por exemplo. Esta cópia fica
* porque separa os domínios de outra maneira (ver o comentário lá dentro sobre
* a chave do ambiente) e porque trocá-la num site a correr invalida os
* formulários abertos nesse instante. Quem a converter faz uma janela que
* aceite as duas assinaturas antes de tirar a antiga.
*/
function platform_antispam_key(?PDO $pdo = null): string
{
@@ -195,8 +202,13 @@ function platform_antispam_trap_field(string $form = 'form', ?PDO $pdo = null):
if (!$scriptPrinted) {
$scriptPrinted = true;
$html .= '<script>document.querySelectorAll(\'input[data-js-check]\').forEach('
. 'function(i){i.value=i.dataset.jsCheck;});</script>';
// Corre já (o formulário que o imprime está acima) e outra vez quando a
// página acaba de carregar: o script sai uma vez só, junto ao PRIMEIRO
// formulário, e um segundo mais abaixo na mesma página ainda não existia
// quando ele correu — ficava sem a prova de JS (silver, 2026-09-23).
$html .= '<script>(function(){function f(){document.querySelectorAll(\'input[data-js-check]\').forEach('
. 'function(i){i.value=i.dataset.jsCheck;});}f();'
. 'document.addEventListener(\'DOMContentLoaded\',f);})();</script>';
}
return $html;