Skip to content

Commit 9178a41

Browse files
committed
Added: Delete safe devices option
1 parent 1a49d9d commit 9178a41

File tree

6 files changed

+96
-55
lines changed

6 files changed

+96
-55
lines changed

src/Contracts/TwoFactorAuthenticatable.php

+5
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@ public function hasTwoFactor(): MorphOne;
1919
*/
2020
public function getTwoFactorAuth(): TwoFactorTotp;
2121

22+
/**
23+
* Remove safe devices
24+
*/
25+
public function forgetSafeDevices(): bool;
26+
2227
}

src/Filament/Pages/Configure.php

+81-48
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,32 @@
22

33
namespace Visualbuilder\Filament2fa\Filament\Pages;
44

5+
use Carbon\Carbon;
56
use Exception;
6-
use Filament\Forms\Components\Actions;
7-
use Filament\Forms\Components\Actions\Action as FormAction;
87
use Filament\Actions\Action;
98
use Filament\Facades\Filament;
9+
use Filament\Forms\Components\Actions;
10+
use Filament\Forms\Components\Actions\Action as FormAction;
1011
use Filament\Forms\Components\Component;
11-
use Filament\Forms\Components\DateTimePicker;
12+
use Filament\Forms\Components\Grid;
1213
use Filament\Forms\Components\Group;
1314
use Filament\Forms\Components\Placeholder;
1415
use Filament\Forms\Components\Section;
1516
use Filament\Forms\Components\TextInput;
16-
use Filament\Forms\Components\Toggle;
1717
use Filament\Forms\Components\ViewField;
1818
use Filament\Notifications\Notification;
1919
use Filament\Pages\Auth\EditProfile;
2020
use Filament\Pages\SubNavigationPosition;
2121
use Filament\Support\Enums\Alignment;
22-
use Filament\Support\Facades\FilamentView;
2322
use Illuminate\Contracts\Auth\Authenticatable;
2423
use Illuminate\Contracts\Support\Htmlable;
2524
use Illuminate\Database\Eloquent\Model;
2625
use Illuminate\Support\Arr;
2726
use Illuminate\Support\Collection;
2827
use Illuminate\Support\HtmlString;
28+
use Laragear\TwoFactor\Models\TwoFactorAuthentication;
2929
use Visualbuilder\Filament2fa\Contracts\TwoFactorAuthenticatable;
3030

31-
use function Filament\Support\is_app_url;
32-
3331

3432
class Configure extends EditProfile
3533
{
@@ -47,6 +45,17 @@ public function __construct()
4745
$this->recoveryCodes = $this->getUser()->hasTwoFactorEnabled() ? $this->getUser()->getRecoveryCodes() : [];
4846
}
4947

48+
public function getUser(): Authenticatable & Model
49+
{
50+
$user = Filament::auth()->user();
51+
52+
if (!$user instanceof Model || !$user instanceof TwoFactorAuthenticatable) {
53+
throw new Exception('The authenticated user must be an Eloquent model implementing TwoFactorAuthenticatable class.');
54+
}
55+
56+
return $user;
57+
}
58+
5059
public static function shouldRegisterNavigation(): bool
5160
{
5261
return config('filament-2fa.navigation.visible_on_navbar');
@@ -82,31 +91,20 @@ public static function getNavigationSort(): ?int
8291
return config('filament-2fa.navigation.sort_no');
8392
}
8493

85-
public function getSubNavigationPosition(): SubNavigationPosition
94+
public static function getRouteName(?string $panel = null): string
8695
{
87-
return config('filament-2fa.navigation.subnav_position');
96+
$panel = $panel ? Filament::getPanel($panel) : Filament::getCurrentPanel();
97+
return $panel->generateRouteName(static::getRelativeRouteName());
8898
}
8999

90100
public static function getRelativeRouteName(): string
91101
{
92102
return self::$slug;
93103
}
94104

95-
public function getUser(): Authenticatable & Model
96-
{
97-
$user = Filament::auth()->user();
98-
99-
if (! $user instanceof Model || !$user instanceof TwoFactorAuthenticatable) {
100-
throw new Exception('The authenticated user must be an Eloquent model implementing TwoFactorAuthenticatable class.');
101-
}
102-
103-
return $user;
104-
}
105-
106-
public static function getRouteName(?string $panel = null): string
105+
public function getSubNavigationPosition(): SubNavigationPosition
107106
{
108-
$panel = $panel ? Filament::getPanel($panel) : Filament::getCurrentPanel();
109-
return $panel->generateRouteName(static::getRelativeRouteName());
107+
return config('filament-2fa.navigation.subnav_position');
110108
}
111109

112110
public function getLayout(): string
@@ -124,20 +122,11 @@ public function hasLogo(): bool
124122
return true;
125123
}
126124

127-
public function getFormActionsAlignment(): string | Alignment
125+
public function getFormActionsAlignment(): string|Alignment
128126
{
129127
return Alignment::End;
130128
}
131129

132-
protected function getSaveFormAction(): Action
133-
{
134-
return Action::make('save')
135-
->label($this->getUser()->hasTwoFactorEnabled() ? __('filament-2fa::two-factor.save_changes') : __('filament-2fa::two-factor.action_label'))
136-
->submit('save')
137-
->visible(fn()=>!$this->getUser()->hasTwoFactorEnabled())
138-
->keyBindings(['mod+s']);
139-
}
140-
141130
public function getCancelFormAction(): Action
142131
{
143132
return Action::make('back')
@@ -146,6 +135,15 @@ public function getCancelFormAction(): Action
146135
->color('gray');
147136
}
148137

138+
protected function getSaveFormAction(): Action
139+
{
140+
return Action::make('save')
141+
->label($this->getUser()->hasTwoFactorEnabled() ? __('filament-2fa::two-factor.save_changes') : __('filament-2fa::two-factor.action_label'))
142+
->submit('save')
143+
->visible(fn() => !$this->getUser()->hasTwoFactorEnabled())
144+
->keyBindings(['mod+s']);
145+
}
146+
149147
protected function afterSave(): void
150148
{
151149
if (isset($this->data['disable_two_factor_auth']) && $this->data['disable_two_factor_auth'] === true) {
@@ -162,11 +160,11 @@ protected function afterSave(): void
162160
->title(__('filament-2fa::two-factor.enabled'))
163161
->success()
164162
->send();
165-
/**
166-
* Todo Redirect back to this page or refresh?
167-
*/
168-
$redirectUrl = self::$slug;
169-
$this->redirect($redirectUrl, navigate: FilamentView::hasSpaMode() && is_app_url($redirectUrl));
163+
// /**
164+
// * Todo Redirect back to this page or refresh?
165+
// */
166+
// $redirectUrl = self::$slug;
167+
// $this->redirect($redirectUrl, navigate: FilamentView::hasSpaMode() && is_app_url($redirectUrl));
170168
} else {
171169
Notification::make()
172170
->title(__('filament-2fa::two-factor.fail_confirm'))
@@ -211,8 +209,8 @@ protected function enable2FactorAuthGroupComponent(): Component
211209
->schema([
212210
Placeholder::make('2fa_info')
213211
->label(__('filament-2fa::two-factor.setup_title'))
214-
->content(new HtmlString('<p class="text-justify">' . __('filament-2fa::two-factor.setup_message_1', ['interval' => config('two-factor.totp.seconds')]) . '</p>
215-
<p class="text-justify">' . __('filament-2fa::two-factor.setup_message_2') . '</p>')),
212+
->content(new HtmlString('<p class="text-justify">'.__('filament-2fa::two-factor.setup_message_1', ['interval' => config('two-factor.totp.seconds')]).'</p>
213+
<p class="text-justify">'.__('filament-2fa::two-factor.setup_message_2').'</p>')),
216214

217215
Group::make()
218216
->schema([
@@ -221,7 +219,7 @@ protected function enable2FactorAuthGroupComponent(): Component
221219
->schema([
222220
Placeholder::make('step1')
223221
->label(false)
224-
->content(fn () => new HtmlString('<h3 class="text-lg font-bold text-primary">' . __('filament-2fa::two-factor.setup_step_1') . '</h3>')),
222+
->content(fn() => new HtmlString('<h3 class="text-lg font-bold text-primary">'.__('filament-2fa::two-factor.setup_step_1').'</h3>')),
225223
ViewField::make('2fa_auth')
226224
->view('filament-2fa::forms.components.2fa-settings')
227225
->viewData($this->prepareTwoFactor()),
@@ -236,15 +234,15 @@ protected function enable2FactorAuthGroupComponent(): Component
236234
->schema([
237235
Placeholder::make('step2')
238236
->label(false)
239-
->content(fn () => new HtmlString('<h3 class="text-lg font-bold text-primary">' . __('filament-2fa::two-factor.setup_step_2') . '</h3>')),
237+
->content(fn() => new HtmlString('<h3 class="text-lg font-bold text-primary">'.__('filament-2fa::two-factor.setup_step_2').'</h3>')),
240238
TextInput::make('2fa_code')
241239
->label(__('filament-2fa::two-factor.confirm'))
242240
->autofocus()
243241
->required(!$this->getUser()->hasTwoFactorEnabled())
244242
->length(config('two-factor.totp.digits'))
245243
->autocomplete(false)
246244
->live()
247-
->extraInputAttributes(['class'=>'text-center','style'=>'font-size:3em; letter-spacing:1rem'])
245+
->extraInputAttributes(['class' => 'text-center', 'style' => 'font-size:3em; letter-spacing:1rem'])
248246
->afterStateUpdated(function ($state) {
249247
$requiredLength = config('two-factor.totp.digits');
250248
if (strlen($state) == $requiredLength) {
@@ -278,12 +276,32 @@ protected function prepareTwoFactor(): array
278276

279277
protected function manage2FactorAuthGroupComponent(): Component
280278
{
281-
return Group::make()
279+
return Grid::make()
282280
->schema([
283281
Placeholder::make('2fa_info')
284-
->label(fn($record) =>
285-
__('filament-2fa::two-factor.enabled_message',
286-
['date'=>$record->enabled_at?->format(config('filament-2fa.defaultDateTimeDisplayFormat'))])),
282+
->inlineLabel(false)
283+
->label(fn(TwoFactorAuthentication $record) => __('filament-2fa::two-factor.enabled_message',
284+
['date' => $record->enabled_at?->format(config('filament-2fa.defaultDateTimeDisplayFormat'))])),
285+
286+
Placeholder::make('trusted_devices')
287+
->inlineLabel(false)
288+
->label('Trusted devices')
289+
->content(function (TwoFactorAuthentication $record) {
290+
$devices = $record->safe_devices;
291+
$items = '';
292+
293+
// Iterate over each device and create an <li> element
294+
foreach ($devices as $device) {
295+
$formattedDate = Carbon::parse($device['added_at'])->format(config('filament-2fa.defaultDateTimeDisplayFormat'));
296+
$items .= "<li>{$device['ip']} added on {$formattedDate}</li>";
297+
}
298+
return new HtmlString("<ul>{$items}</ul>");
299+
})->visible(fn($record) =>
300+
$record->safe_devices
301+
&& $record->safe_devices instanceof Collection
302+
&& $record->safe_devices->isNotEmpty()
303+
),
304+
287305
Actions::make([
288306
FormAction::make('ShowRecoveryCode')
289307
->color('success')
@@ -301,6 +319,19 @@ protected function manage2FactorAuthGroupComponent(): Component
301319
})
302320
->visible($this->showRecoveryCodes)
303321
->requiresConfirmation(),
322+
FormAction::make('clearSafeDevices')
323+
->label('Forget safe devices')
324+
->color('warning')
325+
->requiresConfirmation()
326+
->icon('heroicon-m-shield-exclamation')
327+
->modalDescription('These devices will require 2FA at next login')
328+
->visible(fn($record) => $record->safe_devices
329+
&& $record->safe_devices instanceof Collection
330+
&& $record->safe_devices->isNotEmpty())
331+
->action(function () {
332+
$this->getUser()->forgetSafeDevices();
333+
$this->js('$wire.$refresh()');
334+
}),
304335
FormAction::make('disableTwoFactorAuth')
305336
->label(__('filament-2fa::two-factor.disable_2fa'))
306337
->color('danger')
@@ -319,17 +350,19 @@ protected function manage2FactorAuthGroupComponent(): Component
319350
->visible($this->showRecoveryCodes),
320351

321352
])
353+
->columns(1)
322354
->visible($this->getUser()->hasTwoFactorEnabled());
323355
}
324356

325357
public function prepareRecoveryCodes(): HtmlString
326358
{
327359
$recoveryCodesArray = Arr::pluck($this->recoveryCodes, 'code');
328-
$recoveryCodes = "<p>" . __('filament-2fa::two-factor.recovery_instruction') . "</p><ul>";
360+
$recoveryCodes = "<p>".__('filament-2fa::two-factor.recovery_instruction')."</p><ul>";
329361
foreach ($recoveryCodesArray as $code) {
330362
$recoveryCodes .= "<li>$code</li>";
331363
}
332364
$recoveryCodes .= '</ul>';
333365
return new HtmlString($recoveryCodes);
334366
}
367+
335368
}

src/Filament/Pages/Login.php

+1-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function authenticate(): null|TwoFactorAuthResponse|LoginResponse
3737
$this->storeCredentials($credentials, $remember);
3838
Filament::auth()->logout();
3939
// Regenerate session to prevent fixation
40-
//session()->regenerate();
40+
session()->regenerate();
4141
return app(TwoFactorAuthResponse::class);
4242
}
4343

@@ -59,12 +59,9 @@ protected function handleRateLimiting(): void
5959
try {
6060
$this->rateLimit(5);
6161
} catch (TooManyRequestsException $exception) {
62-
// Optionally send a notification (can be omitted if not needed)
6362
$this->getRateLimitedNotification($exception)?->send();
64-
6563
// Use the available property to get the number of seconds
6664
$this->addError('email', __('auth.throttle', ['seconds' => $exception->secondsUntilAvailable]));
67-
6865
// Stop further execution by returning early
6966
return;
7067
}

src/Http/Middleware/EnsureTwoFactorSession.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Visualbuilder\Filament2fa\Http\Middleware;
44

55
use Closure;
6+
use Filament\Facades\Filament;
67
use Illuminate\Http\Request;
78
use Illuminate\Support\Facades\Config;
89

@@ -20,7 +21,7 @@ public function handle(Request $request, Closure $next)
2021
$hasPanelId = $request->session()->has("{$sessionKey}.panel_id");
2122

2223
if (!$hasCredentials || !$hasPanelId) {
23-
return redirect()->route('filament.login');
24+
return redirect()->back();
2425
}
2526

2627
return $next($request);

src/Livewire/Confirm2Fa.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public function mount()
5050
// Initialize the form with default values
5151
$this->form->fill([
5252
'totp_code' => '',
53-
'safe_device_enable' => true,
53+
'safe_device_enable' => false,
5454
]);
5555
}
5656

@@ -95,7 +95,7 @@ public function submit(): void
9595
->icon('heroicon-o-check-circle')
9696
->color('success')
9797
->send();
98-
98+
9999
session()->forget("{$sessionKey}.credentials");
100100
session()->forget("{$sessionKey}.remember");
101101
session()->forget("{$sessionKey}.panel_id");

src/Traits/TwoFactorAuthentication.php

+5
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,9 @@ public function getTwoFactorAuth(): TwoFactorTotp
2929
{
3030
return $this->twoFactorAuth;
3131
}
32+
33+
public function forgetSafeDevices(): bool
34+
{
35+
return $this->twoFactorAuth->forceFill(['safe_devices' => []])->save();
36+
}
3237
}

0 commit comments

Comments
 (0)