Files
SGF/app/Console/Commands/RecoverIdentityUser.php

44 lines
3.1 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\User;
use App\Modules\Identity\Services\Audit;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
final class RecoverIdentityUser extends Command
{
protected $signature = 'identity:recover {--email=} {--operator=} {--reason=} {--apply}';
protected $description = 'Recuperação local assistida, com confirmação e invalidação das sessões existentes.';
public function handle(): int
{
if (! \App\Modules\Identity\Services\LocalDatabaseGuard::allows()) { $this->error('Destino local inválido.'); return self::FAILURE; }
$user = User::where('email', mb_strtolower(trim((string) $this->option('email'))))->first();
if (! $user || ! $this->option('operator') || ! $this->option('reason')) { $this->error('Indique utilizador existente, operador e motivo.'); return self::FAILURE; }
$this->table(['Operação', 'Valor'], [['Base', 'sgf_tempo_dev'], ['Utilizador', "$user->id — $user->name <$user->email>"],
['Efeito', 'Alterar palavra-passe e invalidar todas as sessões; manter concessões existentes'], ['Operador', $this->option('operator')], ['Motivo', $this->option('reason')]]);
if (! $this->option('apply')) { $this->info('Pré-visualização apenas. Confirme a identidade e autorização da pessoa antes de --apply.'); return self::SUCCESS; }
if (! $this->input->isInteractive() || ! $this->confirm('Confirma a recuperação exacta apresentada?', false)) { return self::FAILURE; }
$password = $this->secret('Nova palavra-passe (mínimo 12 caracteres)', false);
$repeat = $this->secret('Repita a palavra-passe', false);
if (! is_string($password) || $password === '') { $this->error('A entrada oculta não recebeu uma palavra-passe. Nada alterado.'); return self::FAILURE; }
if (mb_strlen($password) < 12) { $this->error('A palavra-passe precisa de pelo menos 12 caracteres. Nada alterado.'); return self::FAILURE; }
if (strlen($password) > 72) { $this->error('A palavra-passe excede o limite de 72 bytes do algoritmo actual. Nada alterado.'); return self::FAILURE; }
if ($password !== $repeat) { $this->error('As duas entradas ocultas não coincidem. Nada alterado.'); return self::FAILURE; }
try {
DB::transaction(function () use ($user, $password): void {
$current = User::whereKey($user->id)->lockForUpdate()->firstOrFail();
$current->password = $password;
$current->auth_version++;
$current->remember_token = bin2hex(random_bytes(30));
$current->save();
Audit::record('local.password_recovered', null, subject: $current->id,
details: ['operator' => $this->option('operator'), 'reason' => $this->option('reason')]);
});
} catch (\Throwable) { $this->error('Recuperação não concluída; nenhum detalhe sensível foi apresentado.'); return self::FAILURE; }
$this->info('Recuperação concluída. As sessões anteriores serão recusadas no pedido seguinte.'); return self::SUCCESS;
}
}