197 lines
6.0 KiB
PHP
197 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Pages\Auth;
|
|
|
|
use App\Models\User;
|
|
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
|
use Filament\Auth\Http\Responses\Contracts\LoginResponse;
|
|
use Filament\Auth\Pages\Login as BaseLogin;
|
|
use Filament\Facades\Filament;
|
|
use Filament\Forms\Components\Hidden;
|
|
use Filament\Models\Contracts\FilamentUser;
|
|
use Filament\Schemas\Components\Component;
|
|
use Filament\Schemas\Components\View;
|
|
use Filament\Schemas\Schema;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Hash;
|
|
|
|
class Login extends BaseLogin
|
|
{
|
|
protected int $maxPinLength = 8;
|
|
|
|
public function form(Schema $schema): Schema
|
|
{
|
|
return $schema
|
|
->components([
|
|
Hidden::make('email')
|
|
->rules(fn (): array => $this->shouldShowPinLogin() ? ['required', 'email'] : [])
|
|
->visible(fn (): bool => $this->shouldShowPinLogin())
|
|
->dehydrated(fn (): bool => $this->shouldShowPinLogin())
|
|
->live(),
|
|
Hidden::make('pin')
|
|
->rules(fn (): array => $this->shouldShowPinLogin() ? [
|
|
'nullable',
|
|
'min:4',
|
|
"max:{$this->maxPinLength}",
|
|
'regex:/^\\d+$/',
|
|
] : [])
|
|
->visible(fn (): bool => $this->shouldShowPinLogin())
|
|
->dehydrated(fn (): bool => $this->shouldShowPinLogin())
|
|
->live(),
|
|
$this->getEmailFormComponent(),
|
|
$this->getPasswordFormComponent(),
|
|
$this->getRememberFormComponent(),
|
|
View::make('filament.pages.auth.kiosk-login')
|
|
->viewData([
|
|
'hasPinUsers' => $this->shouldShowPinLogin(),
|
|
'users' => $this->getKioskUsers(),
|
|
'maxPinLength' => $this->maxPinLength,
|
|
])
|
|
->columnSpanFull(),
|
|
]);
|
|
}
|
|
|
|
public function authenticate(): ?LoginResponse
|
|
{
|
|
try {
|
|
$this->rateLimit(5);
|
|
} catch (TooManyRequestsException $exception) {
|
|
$this->getRateLimitedNotification($exception)?->send();
|
|
|
|
return null;
|
|
}
|
|
|
|
$data = $this->form->getState();
|
|
|
|
if (blank($data['pin'] ?? null)) {
|
|
return parent::authenticate();
|
|
}
|
|
|
|
$authGuard = Filament::auth();
|
|
$credentials = [
|
|
'email' => $data['email'] ?? null,
|
|
'password' => '',
|
|
];
|
|
|
|
$user = User::query()
|
|
->where('email', $data['email'] ?? '')
|
|
->first();
|
|
|
|
$pin = (string) ($data['pin'] ?? '');
|
|
|
|
if (
|
|
(! $user)
|
|
|| blank($user->admin_pin_hash)
|
|
|| $pin === ''
|
|
|| (! ctype_digit($pin))
|
|
|| (strlen($pin) < 4 || strlen($pin) > $this->maxPinLength)
|
|
|| (! Hash::check($pin, $user->admin_pin_hash))
|
|
) {
|
|
$this->userUndertakingMultiFactorAuthentication = null;
|
|
|
|
$this->fireFailedEvent($authGuard, $user, $credentials);
|
|
$this->throwFailureValidationException();
|
|
}
|
|
|
|
if (
|
|
filled($this->userUndertakingMultiFactorAuthentication) &&
|
|
(decrypt($this->userUndertakingMultiFactorAuthentication) === $user->getAuthIdentifier())
|
|
) {
|
|
$this->multiFactorChallengeForm->validate();
|
|
} else {
|
|
foreach (Filament::getMultiFactorAuthenticationProviders() as $multiFactorAuthenticationProvider) {
|
|
if (! $multiFactorAuthenticationProvider->isEnabled($user)) {
|
|
continue;
|
|
}
|
|
|
|
$this->userUndertakingMultiFactorAuthentication = encrypt($user->getAuthIdentifier());
|
|
|
|
if ($multiFactorAuthenticationProvider instanceof \Filament\Auth\MultiFactor\Contracts\HasBeforeChallengeHook) {
|
|
$multiFactorAuthenticationProvider->beforeChallenge($user);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (filled($this->userUndertakingMultiFactorAuthentication)) {
|
|
$this->multiFactorChallengeForm->fill();
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if ($user instanceof FilamentUser) {
|
|
if (! $user->canAccessPanel(Filament::getCurrentOrDefaultPanel())) {
|
|
$this->fireFailedEvent($authGuard, $user, $credentials);
|
|
$this->throwFailureValidationException();
|
|
}
|
|
}
|
|
|
|
$authGuard->login($user, $data['remember'] ?? true);
|
|
session()->regenerate();
|
|
|
|
return app(LoginResponse::class);
|
|
}
|
|
|
|
public function selectUser(int $userId): void
|
|
{
|
|
$user = $this->getKioskUsers()->firstWhere('id', $userId);
|
|
|
|
if (! $user) {
|
|
return;
|
|
}
|
|
|
|
$this->data['email'] = $user->email;
|
|
$this->data['pin'] = '';
|
|
}
|
|
|
|
public function appendPinDigit(int $digit): void
|
|
{
|
|
$pin = (string) ($this->data['pin'] ?? '');
|
|
|
|
if (strlen($pin) >= $this->maxPinLength) {
|
|
return;
|
|
}
|
|
|
|
$this->data['pin'] = $pin.$digit;
|
|
}
|
|
|
|
public function deletePinDigit(): void
|
|
{
|
|
$pin = (string) ($this->data['pin'] ?? '');
|
|
|
|
if ($pin === '') {
|
|
return;
|
|
}
|
|
|
|
$this->data['pin'] = substr($pin, 0, -1);
|
|
}
|
|
|
|
public function clearPin(): void
|
|
{
|
|
$this->data['pin'] = '';
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Support\Collection<int, User>
|
|
*/
|
|
protected function getKioskUsers(): Collection
|
|
{
|
|
return User::query()
|
|
->whereNotNull('admin_pin_hash')
|
|
->orderBy('name')
|
|
->get(['id', 'name', 'email', 'admin_pin_hash']);
|
|
}
|
|
|
|
protected function shouldShowPinLogin(): bool
|
|
{
|
|
return $this->getKioskUsers()->isNotEmpty();
|
|
}
|
|
|
|
protected function getPasswordFormComponent(): Component
|
|
{
|
|
return parent::getPasswordFormComponent()
|
|
->required(fn (): bool => blank($this->data['pin'] ?? null));
|
|
}
|
|
}
|