CluPilotCloud/app/Livewire/Auth/OperatorSetPassword.php

116 lines
4.1 KiB
PHP

<?php
namespace App\Livewire\Auth;
use App\Models\Operator;
use App\Support\PasswordPolicy;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Validate;
use Livewire\Component;
/**
* Das andere Ende des Einladungslinks: hier vergibt ein eingeladener Betreiber
* sein Passwort.
*
* Eigene Seite und nicht die des Portals (R21). Auch wenn beide Seiten dasselbe
* Formular zeigen, lösen sie gegen verschiedene Broker und damit gegen
* verschiedene Tabellen ein — ein Betreiber-Token gilt hier und sonst nirgends,
* ein Kunden-Token nirgends hier. Eine gemeinsame Seite wäre genau die
* Vermischung, die R21 beendet hat.
*
* Der Token wird von Laravels Broker geprüft, nicht hier: er hält den Hash, die
* Frist und das Löschen nach dem Einlösen. Ein Link, der zweimal funktioniert,
* entsteht dadurch, dass jemand eines dieser drei Dinge nachbaut.
*
* Angemeldet wird danach NIEMAND. Der Link kam per E-Mail, und wer dieses
* Postfach lesen kann, bekäme sonst die Sitzung gleich dazu — auf einer
* Konsole, die die ganze Flotte verwaltet. Der Eingeladene tippt das Passwort
* einmal mehr auf der Anmeldeseite, und dort greift dann auch die
* Zwei-Faktor-Pflicht, falls der Inhaber sie eingeschaltet hat.
*/
#[Layout('layouts.portal')]
class OperatorSetPassword extends Component
{
public string $token = '';
#[Validate('required|email|max:255')]
public string $email = '';
public string $password = '';
public string $password_confirmation = '';
public bool $done = false;
/**
* Die Länge kommt aus PasswordPolicy, nicht aus einem `#[Validate]`-Attribut:
* ein Attribut ist ein fester String und kann eine Einstellung nicht lesen.
*
* @return array<string, mixed>
*/
protected function rules(): array
{
return ['password' => [...PasswordPolicy::rules(), 'confirmed']];
}
public function mount(string $token): void
{
$this->token = $token;
// Aus der Adresszeile vorbelegt, aber als Feld sichtbar: eine Mail, die
// unterwegs umgebrochen wurde, lässt sich so noch von Hand retten.
$this->email = (string) request()->query('email', '');
}
/**
* NICHT reset(): Livewire\Component::reset() leert Eigenschaften, und eine
* Komponente, die sie neu deklariert, lädt gar nicht erst.
*/
public function save(): void
{
$this->validate();
$status = Password::broker('operators')->reset(
[
'email' => $this->email,
'password' => $this->password,
'password_confirmation' => $this->password_confirmation,
'token' => $this->token,
],
function (Operator $operator, string $password) {
$operator->forceFill([
'password' => Hash::make($password),
// Jede bestehende „Angemeldet bleiben"-Sitzung stirbt mit.
// Beim ersten Setzen ist keine da; beim zweiten Weg über
// diese Seite — einer erneuten Einladung — sehr wohl.
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($operator));
},
);
if ($status !== Password::PASSWORD_RESET) {
// EINE Meldung für alle Fehlschläge, und ausdrücklich nicht
// __($status): Laravels Statusschlüssel (passwords.token,
// passwords.user) haben in dieser Installation keine
// Übersetzungsdatei und stünden roh als „passwords.token" auf der
// Seite. Sie zu unterscheiden wäre ohnehin falsch — „diese Adresse
// kennen wir nicht" verrät einem Fremden, wer Betreiber ist.
$this->addError('email', __('auth.console_invite_invalid'));
return;
}
$this->done = true;
}
public function render()
{
return view('livewire.auth.operator-set-password');
}
}