feat(auth): «Manter sessão iniciada» (auditoria: item Médio)

config/functions_remember.php reimplementa o desenho selector/validator
do platform-tools (functions_remember.php) localmente, porque esta app
tem autenticação própria e não passa por functions_auth.php partilhado:
selector em claro e UNIQUE indexa a linha, só o SHA-256 do validator
fica na BD, hash_equals() na comparação, rotação a cada uso, e um
validator que não bate certo revoga TODOS os tokens da conta (sinal de
cookie em circulação).

- Checkbox no login; remember_issue() só quando marcada.
- require_login() tenta remember_resume() antes de mandar para o login.
- logout.php apaga só o token deste browser (não a conta toda).
- auth_set_password() revoga todos os tokens da conta — uma password
  nova não pode deixar um cookie antigo continuar a entrar.

Migração: database/migration_20260925_remember_me.sql (remember_tokens).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
andreandClaude Sonnet 5 committed 2026-09-25 13:01:18 +01:00
1 parent ee30e6f116
commit 300655b92a
7 files changed
+188

No files matched your search

+3
View File
@@ -75,6 +75,9 @@ function auth_login(PDO $pdo, string $email, string $password): ?array {
function auth_set_password(PDO $pdo, int $userId, string $password): void {
$pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?")
->execute([password_hash($password, PASSWORD_DEFAULT), $userId]);
// Uma password nova invalida qualquer «manter sessão iniciada» antigo —
// sem isto, um cookie roubado antes da mudança continuava a entrar depois.
remember_forget_user($pdo, $userId);
$self = (int) ($_SESSION['user_id'] ?? 0) === $userId;
audit($self ? 'auth.password_change' : 'user.password_set', [
'target_type' => 'user', 'target_id' => $userId,