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:
1 parent
5e300fc341
commit
0e8b53f620
65 files changed
+5852
-455
No files matched your search
@@ -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(
|
||||
|
||||
@@ -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.';
|
||||
}
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
// Sistema → Auditoria: a página partilhada do platform-tools (só leitura, com
|
||||
// filtros e exportação CSV) sobre o audit_log deste site.
|
||||
$repo_root = dirname(__DIR__, 2);
|
||||
require_once $repo_root . '/config/db.php';
|
||||
require_once $repo_root . '/config/functions.php';
|
||||
$user = require_sysadmin();
|
||||
$page_title = 'Auditoria';
|
||||
require $repo_root . '/shared/platform-tools/audit.php';
|
||||
@@ -17,6 +17,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
csrf_check();
|
||||
$id = (int) ($_POST['id'] ?? 0);
|
||||
$action = $_POST['_action'] ?? '';
|
||||
if (in_array($action, ['retry', 'cancel', 'delete', 'restore'], true) && $id > 0) {
|
||||
audit('message.' . $action, ['target_type' => 'message', 'target_id' => $id]);
|
||||
}
|
||||
|
||||
if ($id > 0) {
|
||||
switch ($action) {
|
||||
|
||||
@@ -41,11 +41,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email_opt = isset($_POST['email_opt_out']) ? 1 : 0;
|
||||
$sms_opt = isset($_POST['sms_opt_out']) ? 1 : 0;
|
||||
$marketing = isset($_POST['marketing_consent']) ? 1 : 0;
|
||||
$before = db_get_user($pdo, (int) $user['id']) ?? [];
|
||||
$pdo->prepare(
|
||||
"UPDATE users SET phone=?, address=?, city=?, postal_code=?,
|
||||
email_opt_out=?, sms_opt_out=?, marketing_consent=?
|
||||
WHERE id=?"
|
||||
)->execute([$phone, $address, $city, $postal_code, $email_opt, $sms_opt, $marketing, $user['id']]);
|
||||
audit('user.profile_update', [
|
||||
'target_type' => 'user', 'target_id' => (int) $user['id'], 'target_label' => (string) $user['name'],
|
||||
'changes' => platform_audit_diff(
|
||||
array_intersect_key($before, array_flip(['phone', 'address', 'city', 'postal_code', 'email_opt_out', 'sms_opt_out', 'marketing_consent'])),
|
||||
['phone' => $phone, 'address' => $address, 'city' => $city, 'postal_code' => $postal_code,
|
||||
'email_opt_out' => (string) $email_opt, 'sms_opt_out' => (string) $sms_opt, 'marketing_consent' => (string) $marketing]
|
||||
),
|
||||
]);
|
||||
$saved = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user