44 lines
3.1 KiB
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;
|
|
}
|
|
}
|