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; } }