Skip to content

Commit

Permalink
refactor: clean up code formatting and improve user permissions handling
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentauger committed Nov 6, 2024
1 parent 277774b commit d34475d
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 180 deletions.
179 changes: 83 additions & 96 deletions app/Filament/Requests/Auth/LoginRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,152 +5,139 @@
use App\Models\User;
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Facades\Filament;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Http\Responses\Auth\Contracts\LoginResponse;
use Filament\Models\Contracts\FilamentUser;
use Filament\Notifications\Notification;
use Filament\Pages\Auth\Login as BaseAuth;
use Filament\Pages\Concerns\InteractsWithFormActions;
use Filament\Pages\SimplePage;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class LoginRequest extends BaseAuth
{
use WithRateLimiting;

/* public function authorize()
* {
return true;
* } */

protected int $maxAttempts;

protected int $decaySeconds;

public function __construct()
{
$this->maxAttempts = Config::get('auth.rate_limit.max_attempts', 5);
$this->decaySeconds = Config::get('auth.rate_limit.decay_seconds', 600);
}

public function authenticate(): ?LoginResponse
{

/**
* Check if user is rate limited. Log attempt if user is rate limited.
*
* @throws DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException
*/
$maxAttempts = Config::get('auth.rate_limit.max_attempts');
$decaySeconds = Config::get('auth.rate_limit.decay_seconds');
try {
$this->rateLimit($maxAttempts, 10);
} catch (TooManyRequestsException $exception) {
$this->logLockout();
$this->getRateLimitedNotification($exception)?->send();

return null;
}

try {
$this->ensureIsNotRateLimited();
} catch (TooManyRequestsException $exception) {
$this->getRateLimitedNotification($exception)?->send();

return null;
}

$data = $this->form->getState();

/**
* Attempt to authenticate the request's credentials.
*
* @throws \Illuminate\Validation\ValidationException
*/
if (! Filament::auth()->attempt($this->getCredentialsFromFormData($data), $data['remember'] ?? false)) {
$this->throwFailureValidationException();
RateLimiter::hit($this->throttleKey(), $this->decaySeconds);
$this->throwFailureValidationException();
}

$user = Filament::auth()->user();

/**
* Authorizes user.
*
* @thows 403 AuthorizationException
*/
Gate::authorize('viewLibrarium', $user);

/**
* Check if user's email is verified
*
* @throws \Illuminate\Validation\ValidationException
*/
if ($user && ! $user->hasVerifiedEmail()) {
Filament::auth()->logout();
$this->throwFailureValidationException();
}

/**
* Check if user has permission to access admin panel
*
* @throws \Illuminate\Validation\ValidationException
*/
$user = User::findOrFail(Filament::auth()->user()->id);

/**
* Check if user has permission to access admin panel
*
* @throws \Illuminate\Validation\ValidationException
*/
if (
($user instanceof FilamentUser) &&
(! $user->canAccessPanel(Filament::getCurrentPanel()))
(($user instanceof FilamentUser) && (! $user->canAccessPanel(Filament::getCurrentPanel()))) ||
(! $user->hasVerifiedEmail()) ||
(! $user->active)
) {
Filament::auth()->logout();

$this->throwFailureValidationException();
Filament::auth()->logout();
$this->throwFailureValidationException();
}

session()->regenerate();

return app(LoginResponse::class);
}

/**
* Ensure the login request is not rate limited.
*
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
public function ensureIsNotRateLimited()
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), $this->maxAttempts)) {
return;
}

$this->logLockout();

$component = static::class;
$method = debug_backtrace(limit: 2)[1]['function'];
$seconds = RateLimiter::availableIn($this->throttleKey());
$ip = request()->ip();

throw new TooManyRequestsException(
$component,
$method,
$ip,
$seconds
);
}

/**
* Get the rate limiting throttle key for the request
*
* @return string
*/
public function throttleKey()
{

return Str::lower($this->data['email']).'|'.request()->ip();
return Str::lower($this->data['email']).'|'.request()->ip();
}

/**
* Log lockout but rate limit to one entry per 2 minutes
* as we don't want to log all failed attempts in the
* log. This could be asbused to fill up the logs.
*/
public function logLockout()
{
RateLimiter::attempt(
$this->throttleKey().'|lockout',
Config::get('auth.rate_limit.max_attempts'),
function () {
activity()->withProperties([
'ip' => request()->ip(),
'email' => $this->data['email'],
])->log('auth.lockout');
},
120
);
RateLimiter::attempt(
$this->throttleKey().'|lockout',
1,
function () {
activity()->withProperties([
'ip' => request()->ip(),
'email' => $this->data['email'],
])->log('auth.lockout-librarium');
},
120
);
}

/**
* Overload getForms() to remove 'Remember Me' checkbox.
*
* @return array
*/
protected function getForms(): array
{
return [
'form' => $this->form(
$this->makeForm()
->schema([
$this->getEmailFormComponent(),
$this->getPasswordFormComponent(),
])
->statePath('data'),
),
];
return [
'form' => $this->form(
$this->makeForm()
->schema([
$this->getEmailFormComponent(),
$this->getPasswordFormComponent(),
])
->statePath('data'),
),
];
}
}
85 changes: 41 additions & 44 deletions app/Filament/Resources/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
namespace App\Filament\Resources;

use App\Filament\Resources\UserResource\Pages;
use App\Filament\Resources\UserResource\RelationManagers;
use App\Models\User;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Facades\Gate;

class UserResource extends Resource
Expand All @@ -26,24 +23,24 @@ public static function form(Form $form): Form
return $form
->schema([
Forms\Components\TextInput::make('first_name')
->disabledOn('edit')
->Filled()
->required(),
Forms\Components\TextInput::make('last_name')
->disabledOn('edit')
->Filled(),
Forms\Components\TextInput::make('email')
->disabledOn('edit')
->Filled(),
Forms\Components\Section::make([
Forms\Components\CheckboxList::make('roles')
->relationship(titleAttribute: 'name')
->default(['1'])
->options([
'3' => 'Admin',
'2' => 'Director',
]),
])
->disabledOn('edit')
->Filled()
->required(),
Forms\Components\TextInput::make('last_name')
->disabledOn('edit')
->Filled(),
Forms\Components\TextInput::make('email')
->disabledOn('edit')
->Filled(),
Forms\Components\Section::make([
Forms\Components\CheckboxList::make('roles')
->relationship(titleAttribute: 'name')
->default(['1'])
->options([
'3' => 'Admin',
'2' => 'Director',
]),
]),
]);
}

Expand All @@ -52,24 +49,24 @@ public static function table(Table $table): Table
return $table
->columns([
Tables\Columns\TextColumn::make('first_name')
->sortable(),
Tables\Columns\TextColumn::make('last_name')
->sortable(),
Tables\Columns\TextColumn::make('email'),
Tables\Columns\IconColumn::make('email_verified_at')
->boolean()
->sortable(),
Tables\Columns\IconColumn::make('active')
->boolean()
->sortable(),
Tables\Columns\TextColumn::make('roles.name')
->badge()
->color(fn (string $state): string => match ($state) {
'author' => 'success',
'director' => 'warning',
'admin' => 'danger',
})
->searchable(isIndividual: true),
->sortable(),
Tables\Columns\TextColumn::make('last_name')
->sortable(),
Tables\Columns\TextColumn::make('email'),
Tables\Columns\IconColumn::make('email_verified_at')
->boolean()
->sortable(),
Tables\Columns\IconColumn::make('active')
->boolean()
->sortable(),
Tables\Columns\TextColumn::make('roles.name')
->badge()
->color(fn (string $state): string => match ($state) {
'author' => 'success',
'director' => 'warning',
'admin' => 'danger',
})
->searchable(isIndividual: true),
])
->filters([
//
Expand All @@ -79,10 +76,10 @@ public static function table(Table $table): Table
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
// Placeholder
// Placeholder
]),
])
->defaultSort('first_name');
->defaultSort('first_name');
}

public static function getRelations(): array
Expand All @@ -94,9 +91,9 @@ public static function getRelations(): array

public static function getPages(): array
{
// $user = Filament::auth()->user();
// Gate::authorize('updateAnyUser', $user);
// $user = Filament::auth()->user();
// Gate::authorize('updateAnyUser', $user);

return [
'index' => Pages\ListUsers::route('/'),
'create' => Pages\CreateUser::route('/create'),
Expand Down
1 change: 0 additions & 1 deletion app/Filament/Resources/UserResource/Pages/CreateUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace App\Filament\Resources\UserResource\Pages;

use App\Filament\Resources\UserResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;

class CreateUser extends CreateRecord
Expand Down
3 changes: 1 addition & 2 deletions app/Filament/Resources/UserResource/Pages/EditUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ protected function getHeaderActions(): array
*/
protected function getRedirectUrl(): string
{
return $this->getResource()::getUrl('index');
return $this->getResource()::getUrl('index');
}

}
Loading

0 comments on commit d34475d

Please sign in to comment.