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

+3
View File
@@ -34,6 +34,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = 'Endereço de email inválido.';
} else {
$user = user_find_by_email($pdo, $email);
// Registado exista ou não a conta (sem dizer qual) — pedidos em série
// ao mesmo endereço ficam visíveis.
audit('auth.password_reset_requested', ['target_type' => 'user', 'target_label' => audit_mask_email($email), 'actor' => null]);
if ($user) {
$token = auth_create_reset_token($pdo, (int) $user['id']);
send_password_reset_email(
+3
View File
@@ -43,6 +43,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$acct = throttle_account_key((string) ($_POST['email'] ?? ''));
if (!throttle_check($pdo, 'login') || !throttle_check($pdo, 'login_acct', 10, 50, $acct)) {
$report_login('login_throttled');
audit('auth.login_throttled', ['target_type' => 'user', 'target_label' => audit_mask_email((string) ($_POST['email'] ?? '')), 'actor' => null]);
throttle_block('login form');
}
throttle_record($pdo, 'login');
@@ -51,10 +52,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = auth_login($pdo, $_POST['email'] ?? '', $_POST['password'] ?? '');
if ($user) {
auth_set_session($user);
audit('auth.login', ['target_type' => 'user', 'target_id' => (int) $user['id'], 'target_label' => (string) $user['name']]);
header('Location: ' . base_url($destination($user['role'])));
exit;
}
$report_login('login_failed');
audit('auth.login_failed', ['target_type' => 'user', 'target_label' => audit_mask_email((string) ($_POST['email'] ?? '')), 'actor' => null]);
// Deliberately vague — doesn't reveal whether the address exists.
$error = 'Email ou palavra-passe incorretos.';
}
+3
View File
@@ -5,6 +5,9 @@
require __DIR__ . '/../../config/db.php';
require __DIR__ . '/../../config/functions.php';
if (!empty($_SESSION['user_id'])) {
audit('auth.logout', ['target_type' => 'user', 'target_id' => (int) $_SESSION['user_id']]);
}
$_SESSION = [];
// Drop the cookie too, so the browser doesn't keep presenting a dead id.
if (ini_get('session.use_cookies')) {