++ fix richiesta password prima di azzeramento applicazione

This commit is contained in:
2026-07-29 15:27:11 +02:00
parent 588910b893
commit fd15903c9c
4 changed files with 143 additions and 9 deletions

View File

@@ -5,6 +5,8 @@ namespace App\Livewire\Settings;
use App\Jobs\CreateBackupArchive;
use App\Jobs\ResetApplication;
use App\Services\BackupOperationState;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Component;
/**
@@ -26,8 +28,26 @@ class BackupManager extends Component
/** Frase da digitare per sbloccare l'azzeramento. */
public const CONFERMA_AZZERAMENTO = 'AZZERA TUTTO';
/** Password sbagliate ammesse prima di bloccare l'azzeramento, e per quanto. */
private const TENTATIVI_PASSWORD = 5;
private const FINESTRA_TENTATIVI = 300;
public string $confermaReset = '';
/**
* Esiste solo per dare un nome agli errori del campo password: Livewire
* scarta dal messaggio di risposta ogni errore la cui chiave non
* corrisponda a una proprieta' del componente (SupportValidation::dehydrate),
* quindi senza questa riga "Password non corretta" non arriverebbe mai a
* schermo e il pulsante sembrerebbe non fare nulla.
*
* NON collegarla al campo con wire:model: la password viaggia come
* parametro di confermaAzzeramento() proprio per non finire nello
* snapshot, che Livewire rimanda al browser a ogni risposta. Qui resta
* sempre stringa vuota.
*/
public string $passwordConferma = '';
public function mount(): void
{
$this->authorize('settings.manage');
@@ -79,7 +99,7 @@ class BackupManager extends Component
* resterebbe indistinguibile da uno riuscito. Chiedere all'operatore e'
* l'unico controllo che copre anche quel caso.
*/
public function confermaAzzeramento(): void
public function confermaAzzeramento(string $password = ''): void
{
$this->authorize('settings.manage');
@@ -97,6 +117,10 @@ class BackupManager extends Component
return;
}
if (! $this->passwordConfermata($password)) {
return;
}
app(BackupOperationState::class)->update($this->operationId, function (array $state) {
$state['fase'] = 'Azzeramento in avvio';
$state['processed'] = 0;
@@ -110,6 +134,56 @@ class BackupManager extends Component
$this->aggiorna();
}
/**
* Verifica la password dell'amministratore prima della cancellazione.
*
* A questo punto la sessione e' gia' autenticata, quindi la password non
* serve a stabilire *chi* sia: serve a stabilire che davanti allo schermo
* ci sia ancora quella persona. E' l'ultimo passo prima di
* un'operazione irreversibile, e una scheda lasciata aperta su una
* postazione condivisa e' lo scenario esatto contro cui protegge.
*
* La password arriva come parametro del metodo e non come proprieta' del
* componente: le proprieta' pubbliche di Livewire vengono serializzate
* nello snapshot e rispedite al browser a ogni risposta, e una password
* non ha motivo di fare quel viaggio di ritorno.
*/
private function passwordConfermata(string $password): bool
{
$utente = auth()->user();
$chiave = 'azzeramento-password:' . $utente->id;
// Senza un limite, la richiesta di password su una sessione dirottata
// sarebbe solo un rallentamento: si proverebbero le password comuni a
// raffica. Il blocco e' sull'azione, non sull'account, quindi nessuno
// resta chiuso fuori dall'applicazione.
if (RateLimiter::tooManyAttempts($chiave, self::TENTATIVI_PASSWORD)) {
$this->addError('passwordConferma', sprintf(
'Troppi tentativi. Riprova fra %d secondi.',
RateLimiter::availableIn($chiave)
));
return false;
}
if ($password === '') {
$this->addError('passwordConferma', 'Inserisci la tua password per confermare.');
return false;
}
if (! Hash::check($password, $utente->getAuthPassword())) {
RateLimiter::hit($chiave, self::FINESTRA_TENTATIVI);
$this->addError('passwordConferma', 'Password non corretta.');
return false;
}
RateLimiter::clear($chiave);
return true;
}
/**
* Annulla un azzeramento fermo in attesa di conferma. Il backup resta
* scaricabile: e' comunque un backup valido.