diff --git a/HANDOFF.md b/HANDOFF.md index b7f6afb..03dcdbc 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -210,6 +210,15 @@ Decisioni successive dell'utente (20 ottobre 2026): obbligatoria `users.email`. XML: niente più `` né ``; da un file vecchio non si importano le utenze disattivate né i gruppi con ``. +- **Primo accesso del sorvegliante**: la pagina del cambio password obbligato + diceva sempre "Ripristino da backup eseguito" (era l'unico motivo + previsto). Ora `users.password_temporanea` (migrazione `000040`, che la + accende per i sorveglianti che non hanno ancora cambiato la password) + distingue la password provvisoria data dall'amministratore — creazione del + gruppo, "Nuova password temporanea" — e la pagina dice "Password + temporanea". Si spegne a password cambiata (pagina obbligatoria o + profilo); il ripristino e l'import XML la spengono per tutti, perché lì il + motivo è il ripristino. - **Anche i membri si segnalano**: `Proclamatore::gruppo_etichetta` ("Gruppo Zisa" / "Sorvegliante del gruppo Zisa") come badge nell'elenco (più il filtro *In un gruppo di servizio*, `filtroStato=gruppo`) e in testa diff --git a/README.md b/README.md index 2d87484..99f5fec 100644 --- a/README.md +++ b/README.md @@ -745,7 +745,8 @@ docker compose --profile dev up -d --build fittizia** creata dall'applicazione: iniziale del nome e cognome sul dominio `termanager.it` (Francesco Fontana → `ffontana@termanager.it`; se è già presa, un numero in coda), e una password temporanea. Si vedono una - volta sola; la password va cambiata al primo accesso. L'email non si + volta sola; la password va cambiata al primo accesso (la pagina dice + "Password temporanea", non "Ripristino da backup"). L'email non si chiede, non riceve posta e non va confermata. Due gruppi non possono avere lo stesso nome. Se perde la password, l'amministratore ne genera un'altra dalla pagina del gruppo @@ -894,7 +895,7 @@ una revoca ha effetto immediato senza bisogno di ricaricare la pagina. | Tabella | Descrizione | |-------------------|--------------------------------------------------------------| -| `users` | Utenti; `password_change_required` blocca l'accesso finché non si sceglie una password nuova; `proclamatore_id` lega l'utenza del sorvegliante (email fittizia) al suo proclamatore; sciolto il gruppo l'utenza si elimina | +| `users` | Utenti; `password_change_required` blocca l'accesso finché non si sceglie una password nuova (`password_temporanea` dice se il motivo è una password provvisoria data dall'amministratore invece di un ripristino); `proclamatore_id` lega l'utenza del sorvegliante (email fittizia) al suo proclamatore; sciolto il gruppo l'utenza si elimina | | `settings` | Configurazione singleton | | `zone` | Zone territoriali | | `tipologie` | Tipologie di territorio | diff --git a/app/Http/Controllers/Auth/PasswordChangeController.php b/app/Http/Controllers/Auth/PasswordChangeController.php index ee8eaed..4aaf547 100644 --- a/app/Http/Controllers/Auth/PasswordChangeController.php +++ b/app/Http/Controllers/Auth/PasswordChangeController.php @@ -9,7 +9,9 @@ use Illuminate\Support\Facades\Cache; use Illuminate\Validation\Rules\Password; /** - * Cambio password obbligatorio, imposto dopo il ripristino di un backup. + * Cambio password obbligatorio, imposto dopo il ripristino di un backup o al + * primo accesso con una password temporanea data dall'amministratore + * (sorvegliante di gruppo): `password_temporanea` dice quale dei due. * * Volutamente NON e' un componente Livewire: la pagina deve restare * raggiungibile mentre RequirePasswordChange respinge tutto il resto, incluse @@ -27,6 +29,8 @@ class PasswordChangeController extends Controller // Chi arriva qui spontaneamente (dal profilo) puo' tornare indietro; // chi e' stato dirottato dal middleware no. 'forced' => (bool) $user->password_change_required, + // Perché: una password temporanea, oppure un ripristino da backup. + 'temporanea' => (bool) $user->password_temporanea, // Il ripristino gira in coda e non ha una sessione in cui lasciare // un flash; deposita l'esito in cache a nome di chi l'ha avviato e // lo si legge qui, che e' la prima pagina che quella persona vede. @@ -71,6 +75,7 @@ class PasswordChangeController extends Controller $user->update([ 'password' => $validated['password'], 'password_change_required' => false, + 'password_temporanea' => false, ]); // La sessione corrente sopravvive al cambio, le altre no: dopo un diff --git a/app/Jobs/RestoreBackupArchive.php b/app/Jobs/RestoreBackupArchive.php index f4bf864..d2f9274 100644 --- a/app/Jobs/RestoreBackupArchive.php +++ b/app/Jobs/RestoreBackupArchive.php @@ -250,6 +250,9 @@ class RestoreBackupArchive implements ShouldQueue { DB::table('users')->update([ 'password_change_required' => true, + // Il motivo ora è il ripristino, anche per chi aveva una + // password temporanea (pagina del cambio password). + 'password_temporanea' => false, 'remember_token' => null, ]); diff --git a/app/Livewire/Gruppi/GruppoShow.php b/app/Livewire/Gruppi/GruppoShow.php index ea7a5d0..0056824 100644 --- a/app/Livewire/Gruppi/GruppoShow.php +++ b/app/Livewire/Gruppi/GruppoShow.php @@ -109,7 +109,7 @@ class GruppoShow extends Component } $password = Str::password(12, symbols: false); - $utente->forceFill(['password' => $password, 'password_change_required' => true])->save(); + $utente->forceFill(['password' => $password, 'password_change_required' => true, 'password_temporanea' => true])->save(); activity()->causedBy(auth()->user())->performedOn($utente)->log('password_reimpostata'); $this->passwordTemporanea = $password; diff --git a/app/Livewire/Profile.php b/app/Livewire/Profile.php index 28fdf4b..a303bb9 100644 --- a/app/Livewire/Profile.php +++ b/app/Livewire/Profile.php @@ -80,6 +80,7 @@ class Profile extends Component auth()->user()->update([ 'password' => $this->password, 'password_change_required' => false, + 'password_temporanea' => false, ]); $this->reset(['current_password', 'password', 'password_confirmation']); diff --git a/app/Livewire/Settings/XmlExchange.php b/app/Livewire/Settings/XmlExchange.php index 9f9aa04..94c69f3 100644 --- a/app/Livewire/Settings/XmlExchange.php +++ b/app/Livewire/Settings/XmlExchange.php @@ -582,6 +582,7 @@ class XmlExchange extends Component if ($passwordHash !== '' && ! $eAttore) { $meta['password'] = $passwordHash; $meta['password_change_required'] = true; + $meta['password_temporanea'] = false; } if (! $eAttore) { $meta['remember_token'] = null; diff --git a/app/Models/User.php b/app/Models/User.php index ea57ed3..60a4002 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -26,6 +26,7 @@ class User extends Authenticatable implements MustVerifyEmail 'email', 'password', 'password_change_required', + 'password_temporanea', 'proclamatore_id', ]; @@ -40,6 +41,9 @@ class User extends Authenticatable implements MustVerifyEmail 'email_verified_at' => 'datetime', 'password' => 'hashed', 'password_change_required' => 'boolean', + // Il cambio obbligatorio viene da una password temporanea data + // dall'amministratore, non da un ripristino (pagina del cambio). + 'password_temporanea' => 'boolean', 'proclamatore_id' => 'integer', ]; } diff --git a/app/Services/GruppiServizio.php b/app/Services/GruppiServizio.php index 9e312fb..b08060e 100644 --- a/app/Services/GruppiServizio.php +++ b/app/Services/GruppiServizio.php @@ -65,6 +65,7 @@ class GruppiServizio 'email' => self::emailFittizia($sorvegliante), 'password' => $password, 'password_change_required' => true, + 'password_temporanea' => true, 'proclamatore_id' => $sorvegliante->id, ]); $utente->assignRole('sorvegliante'); diff --git a/database/migrations/2026_10_20_000040_password_temporanea.php b/database/migrations/2026_10_20_000040_password_temporanea.php new file mode 100644 index 0000000..e009ce1 --- /dev/null +++ b/database/migrations/2026_10_20_000040_password_temporanea.php @@ -0,0 +1,37 @@ +boolean('password_temporanea')->default(false)->after('password_change_required'); + }); + + // I sorveglianti che non hanno ancora cambiato la password di + // creazione del gruppo: la loro è temporanea, non da ripristino. + DB::table('users') + ->whereNotNull('proclamatore_id') + ->where('password_change_required', true) + ->update(['password_temporanea' => true]); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('password_temporanea'); + }); + } +}; diff --git a/resources/views/auth/password-change.blade.php b/resources/views/auth/password-change.blade.php index c1cc438..45a70e5 100644 --- a/resources/views/auth/password-change.blade.php +++ b/resources/views/auth/password-change.blade.php @@ -21,7 +21,15 @@ @endif - @if($forced) + @if($forced && $temporanea) + {{-- Primo accesso del sorvegliante, o password nuova data dall'amministratore --}} +
+ Password temporanea. + Stai usando la password provvisoria che ti ha dato l’amministratore: + scegline una tua per continuare. Fino ad allora il resto + dell’applicazione resta bloccato. +
+ @elseif($forced)
Ripristino da backup eseguito. Il pacchetto di backup contiene le password di tutti gli account, quindi diff --git a/tests/Feature/GruppiServizioTest.php b/tests/Feature/GruppiServizioTest.php index 12a3d9d..5e98d01 100644 --- a/tests/Feature/GruppiServizioTest.php +++ b/tests/Feature/GruppiServizioTest.php @@ -219,6 +219,33 @@ class GruppiServizioTest extends TestCase $this->get(route('dashboard'))->assertOk()->assertDontSee('Il mio gruppo')->assertSee('Gruppi di servizio'); } + public function test_al_primo_accesso_il_sorvegliante_cambia_la_password_temporanea(): void + { + $rossi = $this->proclamatore('Rossi', 'Mario'); + [$gruppo] = app(GruppiServizio::class)->crea('Zisa', $rossi); + $s = User::where('email', 'mrossi@termanager.it')->sole(); + $this->assertTrue($s->password_temporanea); + $this->flushSession(); + $this->actingAs($s); + + $this->get(route('mio-gruppo'))->assertRedirect(route('password.change')); + $this->get(route('password.change'))->assertOk() + ->assertSee('Password temporanea') + ->assertDontSee('Ripristino da backup'); + + $this->post(route('password.change.store'), ['password' => 'una-password-mia-1', 'password_confirmation' => 'una-password-mia-1']) + ->assertRedirect(route('dashboard')); + $this->assertFalse($s->fresh()->password_temporanea); + $this->assertFalse($s->fresh()->password_change_required); + $this->get(route('mio-gruppo'))->assertOk(); + + // "Nuova password temporanea" dalla pagina del gruppo: di nuovo temporanea. + $this->flushSession(); + Livewire::actingAs($this->admin)->test(GruppoShow::class, ['gruppo' => $gruppo])->call('nuovaPassword'); + $this->assertTrue($s->fresh()->password_temporanea); + $this->assertTrue($s->fresh()->password_change_required); + } + public function test_il_sorvegliante_entra_con_l_email_fittizia_senza_conferma_e_va_al_suo_gruppo(): void { $this->gruppo();