2
2
3
3
namespace Visualbuilder \Filament2fa \Filament \Pages ;
4
4
5
+ use Carbon \Carbon ;
5
6
use Exception ;
6
- use Filament \Forms \Components \Actions ;
7
- use Filament \Forms \Components \Actions \Action as FormAction ;
8
7
use Filament \Actions \Action ;
9
8
use Filament \Facades \Filament ;
9
+ use Filament \Forms \Components \Actions ;
10
+ use Filament \Forms \Components \Actions \Action as FormAction ;
10
11
use Filament \Forms \Components \Component ;
11
- use Filament \Forms \Components \DateTimePicker ;
12
+ use Filament \Forms \Components \Grid ;
12
13
use Filament \Forms \Components \Group ;
13
14
use Filament \Forms \Components \Placeholder ;
14
15
use Filament \Forms \Components \Section ;
15
16
use Filament \Forms \Components \TextInput ;
16
- use Filament \Forms \Components \Toggle ;
17
17
use Filament \Forms \Components \ViewField ;
18
18
use Filament \Notifications \Notification ;
19
19
use Filament \Pages \Auth \EditProfile ;
20
20
use Filament \Pages \SubNavigationPosition ;
21
21
use Filament \Support \Enums \Alignment ;
22
- use Filament \Support \Facades \FilamentView ;
23
22
use Illuminate \Contracts \Auth \Authenticatable ;
24
23
use Illuminate \Contracts \Support \Htmlable ;
25
24
use Illuminate \Database \Eloquent \Model ;
26
25
use Illuminate \Support \Arr ;
27
26
use Illuminate \Support \Collection ;
28
27
use Illuminate \Support \HtmlString ;
28
+ use Laragear \TwoFactor \Models \TwoFactorAuthentication ;
29
29
use Visualbuilder \Filament2fa \Contracts \TwoFactorAuthenticatable ;
30
30
31
- use function Filament \Support \is_app_url ;
32
-
33
31
34
32
class Configure extends EditProfile
35
33
{
@@ -47,6 +45,17 @@ public function __construct()
47
45
$ this ->recoveryCodes = $ this ->getUser ()->hasTwoFactorEnabled () ? $ this ->getUser ()->getRecoveryCodes () : [];
48
46
}
49
47
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
+
50
59
public static function shouldRegisterNavigation (): bool
51
60
{
52
61
return config ('filament-2fa.navigation.visible_on_navbar ' );
@@ -82,31 +91,20 @@ public static function getNavigationSort(): ?int
82
91
return config ('filament-2fa.navigation.sort_no ' );
83
92
}
84
93
85
- public function getSubNavigationPosition ( ): SubNavigationPosition
94
+ public static function getRouteName (? string $ panel = null ): string
86
95
{
87
- return config ('filament-2fa.navigation.subnav_position ' );
96
+ $ panel = $ panel ? Filament::getPanel ($ panel ) : Filament::getCurrentPanel ();
97
+ return $ panel ->generateRouteName (static ::getRelativeRouteName ());
88
98
}
89
99
90
100
public static function getRelativeRouteName (): string
91
101
{
92
102
return self ::$ slug ;
93
103
}
94
104
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
107
106
{
108
- $ panel = $ panel ? Filament::getPanel ($ panel ) : Filament::getCurrentPanel ();
109
- return $ panel ->generateRouteName (static ::getRelativeRouteName ());
107
+ return config ('filament-2fa.navigation.subnav_position ' );
110
108
}
111
109
112
110
public function getLayout (): string
@@ -124,20 +122,11 @@ public function hasLogo(): bool
124
122
return true ;
125
123
}
126
124
127
- public function getFormActionsAlignment (): string | Alignment
125
+ public function getFormActionsAlignment (): string | Alignment
128
126
{
129
127
return Alignment::End;
130
128
}
131
129
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
-
141
130
public function getCancelFormAction (): Action
142
131
{
143
132
return Action::make ('back ' )
@@ -146,6 +135,15 @@ public function getCancelFormAction(): Action
146
135
->color ('gray ' );
147
136
}
148
137
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
+
149
147
protected function afterSave (): void
150
148
{
151
149
if (isset ($ this ->data ['disable_two_factor_auth ' ]) && $ this ->data ['disable_two_factor_auth ' ] === true ) {
@@ -162,11 +160,11 @@ protected function afterSave(): void
162
160
->title (__ ('filament-2fa::two-factor.enabled ' ))
163
161
->success ()
164
162
->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));
170
168
} else {
171
169
Notification::make ()
172
170
->title (__ ('filament-2fa::two-factor.fail_confirm ' ))
@@ -211,8 +209,8 @@ protected function enable2FactorAuthGroupComponent(): Component
211
209
->schema ([
212
210
Placeholder::make ('2fa_info ' )
213
211
->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> ' )),
216
214
217
215
Group::make ()
218
216
->schema ([
@@ -221,7 +219,7 @@ protected function enable2FactorAuthGroupComponent(): Component
221
219
->schema ([
222
220
Placeholder::make ('step1 ' )
223
221
->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> ' )),
225
223
ViewField::make ('2fa_auth ' )
226
224
->view ('filament-2fa::forms.components.2fa-settings ' )
227
225
->viewData ($ this ->prepareTwoFactor ()),
@@ -236,15 +234,15 @@ protected function enable2FactorAuthGroupComponent(): Component
236
234
->schema ([
237
235
Placeholder::make ('step2 ' )
238
236
->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> ' )),
240
238
TextInput::make ('2fa_code ' )
241
239
->label (__ ('filament-2fa::two-factor.confirm ' ))
242
240
->autofocus ()
243
241
->required (!$ this ->getUser ()->hasTwoFactorEnabled ())
244
242
->length (config ('two-factor.totp.digits ' ))
245
243
->autocomplete (false )
246
244
->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 ' ])
248
246
->afterStateUpdated (function ($ state ) {
249
247
$ requiredLength = config ('two-factor.totp.digits ' );
250
248
if (strlen ($ state ) == $ requiredLength ) {
@@ -278,12 +276,32 @@ protected function prepareTwoFactor(): array
278
276
279
277
protected function manage2FactorAuthGroupComponent (): Component
280
278
{
281
- return Group ::make ()
279
+ return Grid ::make ()
282
280
->schema ([
283
281
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
+
287
305
Actions::make ([
288
306
FormAction::make ('ShowRecoveryCode ' )
289
307
->color ('success ' )
@@ -301,6 +319,19 @@ protected function manage2FactorAuthGroupComponent(): Component
301
319
})
302
320
->visible ($ this ->showRecoveryCodes )
303
321
->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
+ }),
304
335
FormAction::make ('disableTwoFactorAuth ' )
305
336
->label (__ ('filament-2fa::two-factor.disable_2fa ' ))
306
337
->color ('danger ' )
@@ -319,17 +350,19 @@ protected function manage2FactorAuthGroupComponent(): Component
319
350
->visible ($ this ->showRecoveryCodes ),
320
351
321
352
])
353
+ ->columns (1 )
322
354
->visible ($ this ->getUser ()->hasTwoFactorEnabled ());
323
355
}
324
356
325
357
public function prepareRecoveryCodes (): HtmlString
326
358
{
327
359
$ 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> " ;
329
361
foreach ($ recoveryCodesArray as $ code ) {
330
362
$ recoveryCodes .= "<li> $ code</li> " ;
331
363
}
332
364
$ recoveryCodes .= '</ul> ' ;
333
365
return new HtmlString ($ recoveryCodes );
334
366
}
367
+
335
368
}
0 commit comments