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
@@ -21,6 +21,8 @@
|
||||
|
||||
require_once __DIR__ . '/functions_settings.php';
|
||||
require_once __DIR__ . '/functions_client_ip.php';
|
||||
require_once __DIR__ . '/functions_security_events.php';
|
||||
require_once __DIR__ . '/functions_connector.php';
|
||||
|
||||
// ─── Schema map ─────────────────────────────────────────────────────────────
|
||||
//
|
||||
@@ -30,24 +32,11 @@ require_once __DIR__ . '/functions_client_ip.php';
|
||||
// is a function call. Sites converging on the standard names can simply not
|
||||
// call auth_schema() at all.
|
||||
|
||||
// The defaults live in functions_connector.php, which the conformance check
|
||||
// reads too — one list of standard names, not two that drift apart.
|
||||
function auth_schema(?array $map = null): array {
|
||||
static $schema = [
|
||||
'table' => 'users',
|
||||
'id' => 'id',
|
||||
'email' => 'email',
|
||||
// The column the login form matches on. null => use `email`, which is
|
||||
// what five of the six sites do. quack authenticates by `username`
|
||||
// instead, and still needs `email` to stay the address password
|
||||
// recovery mails to -- so the two cannot be the same key.
|
||||
'login' => null,
|
||||
'password' => 'password_hash',
|
||||
'name' => 'name',
|
||||
'role' => 'role',
|
||||
'active' => 'is_active', // null => no active flag on this site
|
||||
'deleted_at' => null, // set when the site soft-deletes instead
|
||||
'last_login' => 'last_login_at',
|
||||
'last_ip' => 'last_login_ip',
|
||||
];
|
||||
static $schema = null;
|
||||
$schema ??= platform_auth_schema_defaults();
|
||||
if ($map !== null) $schema = array_merge($schema, $map);
|
||||
return $schema;
|
||||
}
|
||||
@@ -243,6 +232,9 @@ function auth_throttle_check(PDO $pdo, string $identifier, int $max = 5, int $wi
|
||||
|
||||
$count = (int) $row['c'];
|
||||
$blocked = $count >= $max;
|
||||
if ($blocked) {
|
||||
platform_security_event($pdo, 'login_throttled', hub_security_mask_identifier($identifier), $ip);
|
||||
}
|
||||
return [
|
||||
'blocked' => $blocked,
|
||||
'remaining' => max(0, $max - $count),
|
||||
@@ -261,6 +253,7 @@ function auth_throttle_hit(PDO $pdo, string $identifier): void {
|
||||
} catch (Throwable $e) {
|
||||
error_log('[auth] throttle record failed: ' . $e->getMessage());
|
||||
}
|
||||
platform_security_event($pdo, 'login_failed', hub_security_mask_identifier($identifier));
|
||||
}
|
||||
|
||||
function auth_throttle_clear(PDO $pdo, string $identifier): void {
|
||||
@@ -327,6 +320,11 @@ function auth_login(PDO $pdo, array $user): void {
|
||||
];
|
||||
$_SESSION['_auth_seen'] = time();
|
||||
|
||||
if (function_exists('platform_audit')) {
|
||||
platform_audit($pdo, 'auth.login', ['target_type' => 'user', 'target_id' => (int) $user[$s['id']],
|
||||
'target_label' => (string) ($user[$s['email']] ?? '')]);
|
||||
}
|
||||
|
||||
$sets = []; $params = [];
|
||||
if ($s['last_login']) { $sets[] = "`{$s['last_login']}` = NOW()"; }
|
||||
if ($s['last_ip']) { $sets[] = "`{$s['last_ip']}` = ?"; $params[] = auth_client_ip(); }
|
||||
@@ -342,6 +340,10 @@ function auth_login(PDO $pdo, array $user): void {
|
||||
}
|
||||
|
||||
function auth_logout(bool $destroy = true): void {
|
||||
// Antes de limpar a sessão: depois já não se sabe quem saiu.
|
||||
if (function_exists('platform_audit') && !empty($_SESSION['user']['id'])) {
|
||||
platform_audit(null, 'auth.logout', ['target_type' => 'user', 'target_id' => (int) $_SESSION['user']['id']]);
|
||||
}
|
||||
$_SESSION = [];
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$p = session_get_cookie_params();
|
||||
|
||||
Reference in new issue
Block a user