Add touch-ready Filament login and admin update tooling
This commit is contained in:
196
app/Filament/Pages/Auth/Login.php
Normal file
196
app/Filament/Pages/Auth/Login.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user