123 lines
3.9 KiB
PHP
123 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Modals;
|
|
|
|
use App\Models\AuditEvent;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\Rule;
|
|
use Illuminate\Validation\Rules\Password;
|
|
use LivewireUI\Modal\ModalComponent;
|
|
|
|
/**
|
|
* Form modal: add a new administrator account. Every account is a full admin (no roles). The creator
|
|
* may set a password directly, or leave it blank — then a strong initial password is generated, the
|
|
* account is flagged `must_change_password`, and the clear-text password is revealed ONCE in the modal
|
|
* (never persisted or logged). An operator-set password is the user's own, so no forced rotation.
|
|
*/
|
|
class CreateUser extends ModalComponent
|
|
{
|
|
public string $name = '';
|
|
|
|
public string $email = '';
|
|
|
|
/** Optional operator-set password; blank = generate an initial password and reveal it once. */
|
|
public string $password = '';
|
|
|
|
/** Clear-text temp password, held in component state only to reveal it once. */
|
|
public ?string $tempPassword = null;
|
|
|
|
/** Name of the account just created, used in the reveal copy. */
|
|
public string $createdName = '';
|
|
|
|
public static function modalMaxWidth(): string
|
|
{
|
|
return 'lg';
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<int, mixed>>
|
|
*/
|
|
protected function rules(): array
|
|
{
|
|
return [
|
|
'name' => ['required', 'string', 'max:120'],
|
|
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')],
|
|
// Optional — when set it must meet the same strength as a self-chosen password.
|
|
'password' => ['nullable', 'string', Password::min(6)],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
protected function validationAttributes(): array
|
|
{
|
|
return [
|
|
'name' => __('accounts.name_label'),
|
|
'email' => __('accounts.email_label'),
|
|
'password' => __('accounts.password_label'),
|
|
];
|
|
}
|
|
|
|
public function save(): void
|
|
{
|
|
$data = $this->validate();
|
|
|
|
// Re-check uniqueness server-side regardless of the live validator state.
|
|
$email = mb_strtolower(trim($data['email']));
|
|
if (User::where('email', $email)->exists()) {
|
|
$this->addError('email', __('validation.unique', ['attribute' => __('accounts.email_label')]));
|
|
|
|
return;
|
|
}
|
|
|
|
// Operator-set password = the user's own login (no forced rotation). Blank = generate a strong
|
|
// initial password that the user is prompted to rotate, and reveal it once below.
|
|
$operatorSet = $this->password !== '';
|
|
$password = $operatorSet ? $this->password : Str::password(16);
|
|
|
|
$user = User::create([
|
|
'name' => trim($data['name']),
|
|
'email' => $email,
|
|
'password' => Hash::make($password),
|
|
'must_change_password' => ! $operatorSet,
|
|
]);
|
|
|
|
AuditEvent::create([
|
|
'user_id' => Auth::id(),
|
|
'actor' => Auth::user()?->name ?? 'system',
|
|
'action' => 'user.create',
|
|
'target' => $user->email,
|
|
'ip' => request()->ip(),
|
|
]);
|
|
|
|
// Operator already knows the password they set → no reveal; just confirm and close.
|
|
if ($operatorSet) {
|
|
$this->dispatch('usersChanged');
|
|
$this->dispatch('notify', message: __('accounts.created_notify', ['name' => $user->name]));
|
|
$this->closeModal();
|
|
|
|
return;
|
|
}
|
|
|
|
// Reveal the generated initial password ONCE — swaps the form for the reveal panel.
|
|
$this->createdName = $user->name;
|
|
$this->tempPassword = $password;
|
|
}
|
|
|
|
/** "Fertig": reload the list and close. */
|
|
public function finish(): void
|
|
{
|
|
$this->dispatch('usersChanged');
|
|
$this->closeModal();
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.modals.create-user');
|
|
}
|
|
}
|