├── resources ├── views │ ├── .gitkeep │ ├── password-reset.blade.php │ ├── verify-email.blade.php │ ├── register.blade.php │ ├── request-password-reset.blade.php │ ├── login-two-factor.blade.php │ ├── password-confirmation.blade.php │ ├── login.blade.php │ └── filament │ │ └── pages │ │ └── two-factor.blade.php └── lang │ └── en │ ├── verify-email.php │ ├── password-confirmation.php │ ├── password-reset.php │ ├── register.php │ └── two-factor.php ├── screenshots ├── login.png ├── 2fa-login.png ├── 2fa-page.png ├── register.png ├── forgot-password.png ├── confirm-password.png └── email-verification.png ├── database ├── factories │ └── ModelFactory.php └── migrations │ └── create_filament_fortify_table.php.stub ├── src ├── Facades │ └── FilamentFortify.php ├── Http │ ├── Responses │ │ └── LoginResponse.php │ └── Livewire │ │ └── Auth │ │ ├── LoginTwoFactor.php │ │ ├── PasswordConfirmation.php │ │ ├── RequestPasswordReset.php │ │ ├── Login.php │ │ ├── PasswordReset.php │ │ └── Register.php ├── Pages │ ├── Concerns │ │ ├── ConfirmPassword.php │ │ ├── ActionButtons.php │ │ └── HasActionButtons.php │ └── TwoFactor.php ├── FilamentFortify.php ├── Commands │ └── FilamentFortifyCommand.php └── FilamentFortifyServiceProvider.php ├── config └── filament-fortify.php ├── CHANGELOG.md ├── LICENSE.md ├── composer.json └── README.md /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshots/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wychoong/filament-fortify/HEAD/screenshots/login.png -------------------------------------------------------------------------------- /screenshots/2fa-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wychoong/filament-fortify/HEAD/screenshots/2fa-login.png -------------------------------------------------------------------------------- /screenshots/2fa-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wychoong/filament-fortify/HEAD/screenshots/2fa-page.png -------------------------------------------------------------------------------- /screenshots/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wychoong/filament-fortify/HEAD/screenshots/register.png -------------------------------------------------------------------------------- /screenshots/forgot-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wychoong/filament-fortify/HEAD/screenshots/forgot-password.png -------------------------------------------------------------------------------- /screenshots/confirm-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wychoong/filament-fortify/HEAD/screenshots/confirm-password.png -------------------------------------------------------------------------------- /screenshots/email-verification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wychoong/filament-fortify/HEAD/screenshots/email-verification.png -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | 'Verify email', 5 | 6 | 'buttons' => [ 7 | 8 | 'resend' => [ 9 | 'label' => 'Resend email', 10 | ], 11 | ], 12 | 13 | 'fields' => [], 14 | 15 | 'messages' => [ 16 | 'throttled' => 'Too many register attempts. Please try again in :seconds seconds.', 17 | 'verify' => 'Check your email to verify', 18 | ], 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/views/password-reset.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | @csrf 5 | {{ $this->form }} 6 | 7 | 8 | {{ __('filament-fortify::password-reset.buttons.reset.label') }} 9 | 10 |
11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /database/migrations/create_filament_fortify_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 13 | 14 | // add fields 15 | 16 | $table->timestamps(); 17 | }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /resources/views/verify-email.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | @csrf 6 | 7 | {{ __('filament-fortify::verify-email.buttons.resend.label') }} 8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /resources/views/register.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | @csrf 5 | {{ $this->form }} 6 | 7 | 8 | {{ __('filament-fortify::register.buttons.submit.label') }} 9 | 10 | 11 |
12 | {{__('filament-fortify::register.buttons.login.label')}} 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/Facades/FilamentFortify.php: -------------------------------------------------------------------------------- 1 | 'Password confirmation', 5 | 6 | 'heading' => 'Password confirmation', 7 | 8 | 'buttons' => [ 9 | 10 | 'confirm' => [ 11 | 'label' => 'Confirm', 12 | ], 13 | ], 14 | 15 | 'fields' => [ 16 | 'password' => [ 17 | 'label' => 'Password', 18 | ] 19 | ], 20 | 21 | 'messages' => [ 22 | 'throttled' => 'Too many register attempts. Please try again in :seconds seconds.', 23 | 'confirmation' => 'Please confirm password before proceed', 24 | ], 25 | ]; 26 | -------------------------------------------------------------------------------- /resources/views/request-password-reset.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | @csrf 4 | {{ $this->form }} 5 | 6 | 7 | {{ __('filament-fortify::password-reset.buttons.submit.label') }} 8 | 9 | 10 | 11 | {{ __('filament-fortify::password-reset.buttons.cancel.label') }} 12 | 13 |
14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /src/Http/Responses/LoginResponse.php: -------------------------------------------------------------------------------- 1 | wantsJson() 19 | ? response()->json(['two_factor' => false]) 20 | : redirect()->intended(Filament::getUrl()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/views/login-two-factor.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | @csrf 5 | {{ $this->form }} 6 | 7 | 8 | {{ __('filament-fortify::two-factor.login.buttons.submit.label') }} 9 | 10 | 11 | 12 | {{ __('filament-fortify::two-factor.login.buttons.cancel.label') }} 13 | 14 |
15 | 16 | 17 |
18 | -------------------------------------------------------------------------------- /config/filament-fortify.php: -------------------------------------------------------------------------------- 1 | true, 8 | 9 | 'auth' => [ 10 | 'login' => Auth\Login::class, 11 | 'register' => Auth\Register::class, 12 | 'password-reset' => Auth\PasswordReset::class, 13 | 'request-password-reset' => Auth\RequestPasswordReset::class, 14 | 'password-confirmation' => Auth\PasswordConfirmation::class, 15 | 'login-two-factor' => Auth\LoginTwoFactor::class, 16 | ], 17 | 18 | 'pages' => [ 19 | 'two-factor' => Pages\TwoFactor::class, 20 | ], 21 | 22 | 'view' => [ 23 | 'verify-email' => 'filament-fortify::verify-email', 24 | ], 25 | 26 | 'navigation-icon' => 'heroicon-o-key', 27 | ]; 28 | -------------------------------------------------------------------------------- /src/Pages/Concerns/ConfirmPassword.php: -------------------------------------------------------------------------------- 1 | time()]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `filament-fortify` will be documented in this file. 4 | 5 | ## v0.3.4 - 2023-02-20 6 | 7 | ### What's Changed 8 | 9 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/wychoong/filament-fortify/pull/18 10 | - Laravel v10.x support by @rjocoleman in https://github.com/wychoong/filament-fortify/pull/19 11 | 12 | ### New Contributors 13 | 14 | - @rjocoleman made their first contribution in https://github.com/wychoong/filament-fortify/pull/19 15 | 16 | **Full Changelog**: https://github.com/wychoong/filament-fortify/compare/v0.3.3...v0.3.4 17 | 18 | ## release v0.3.1 - 2022-07-28 19 | 20 | - Fix compatibility with latest filament versions #12 21 | 22 | ## 2fa confirm enable flow - 2022-06-04 23 | 24 | - add 2fa confirm enable flow 25 | - update layout 26 | - navigation icon config 27 | 28 | ## 1.0.0 - 202X-XX-XX 29 | 30 | - initial release 31 | -------------------------------------------------------------------------------- /resources/lang/en/password-reset.php: -------------------------------------------------------------------------------- 1 | 'Reset password', 5 | 6 | 'heading' => 'Reset password', 7 | 8 | 'buttons' => [ 9 | 10 | 'submit' => [ 11 | 'label' => 'Send email', 12 | ], 13 | 14 | 'cancel' => [ 15 | 'label' => 'Cancel', 16 | ], 17 | 18 | 'reset' => [ 19 | 'label' => 'Update password', 20 | ], 21 | 22 | 'request' => [ 23 | 'label' => 'Forgot password?', 24 | ] 25 | ], 26 | 27 | 'fields' => [ 28 | 'email' => [ 29 | 'label' => 'Email address', 30 | ], 31 | 32 | 'password' => [ 33 | 'label' => 'Password', 34 | ], 35 | 36 | 'password_confirm' => [ 37 | 'label' => 'Confirm password', 38 | ], 39 | ], 40 | 41 | 'messages' => [ 42 | 'throttled' => 'Too many register attempts. Please try again in :seconds seconds.', 43 | ], 44 | ]; 45 | -------------------------------------------------------------------------------- /resources/lang/en/register.php: -------------------------------------------------------------------------------- 1 | 'Register', 5 | 6 | 'heading' => 'Register an account', 7 | 8 | 'or' => 'Or', 9 | 10 | 'login-link' => 'register an account', 11 | 12 | 'buttons' => [ 13 | 14 | 'submit' => [ 15 | 'label' => 'Register', 16 | ], 17 | 18 | 'login' => [ 19 | 'label' => 'Have an account?', 20 | ], 21 | 22 | 'register' => [ 23 | 'label' => 'Create an account', 24 | ], 25 | ], 26 | 27 | 'fields' => [ 28 | 'name' => [ 29 | 'label' => 'Name' 30 | ], 31 | 32 | 'email' => [ 33 | 'label' => 'Email address', 34 | ], 35 | 36 | 'password' => [ 37 | 'label' => 'Password', 38 | ], 39 | 40 | 'password_confirm' => [ 41 | 'label' => 'Confirm password', 42 | ], 43 | 44 | ], 45 | 46 | 'messages' => [ 47 | 'throttled' => 'Too many register attempts. Please try again in :seconds seconds.', 48 | ], 49 | ]; 50 | -------------------------------------------------------------------------------- /resources/views/password-confirmation.blade.php: -------------------------------------------------------------------------------- 1 |
config('filament.dark_mode'), 4 | ])> 5 |
6 |
config('filament.dark_mode'), 9 | ])> 10 | 11 |

12 | {{ __('filament-fortify::password-confirmation.messages.confirmation') }} 13 |

14 | 15 | @csrf 16 | {{ $this->form }} 17 | 18 | 19 | {{ __('filament-fortify::password-confirmation.buttons.confirm.label') }} 20 | 21 |
22 | 23 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) wychoong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /resources/views/login.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | @if ($registrationEnabled) 5 |

6 | {{__('filament-fortify::register.or')}} 7 | 8 | {{ __('filament-fortify::register.login-link') }} 9 | 10 |

11 | @endif 12 | 13 | {{ \Filament\Facades\Filament::renderHook('filament-fortify.login.start') }} 14 | 15 | @csrf 16 | {{ $this->form }} 17 | 18 | 19 | {{ __('filament::login.buttons.submit.label') }} 20 | 21 | 22 | @if ($resetPasswordEnabled) 23 |
24 | {{__('filament-fortify::password-reset.buttons.request.label')}} 25 |
26 | @endif 27 | 28 | {{ \Filament\Facades\Filament::renderHook('filament-fortify.login.end') }} 29 |
30 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /src/Http/Livewire/Auth/LoginTwoFactor.php: -------------------------------------------------------------------------------- 1 | form->fill(); 18 | } 19 | 20 | protected function getFormSchema(): array 21 | { 22 | return [ 23 | TextInput::make('code') 24 | ->extraInputAttributes(['name' => 'code']) 25 | ->label(__('filament-fortify::two-factor.login.fields.code.label')), 26 | TextInput::make('recovery_code') 27 | ->extraInputAttributes(['name' => 'recovery_code']) 28 | ->label(__('filament-fortify::two-factor.login.fields.recovery_code.label')), 29 | 30 | ]; 31 | } 32 | 33 | public function render(): View 34 | { 35 | return view('filament-fortify::login-two-factor') 36 | ->layout('filament::components.layouts.card', [ 37 | 'title' => __('filament-fortify::two-factor.login.heading'), 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/FilamentFortify.php: -------------------------------------------------------------------------------- 1 | form->fill(); 19 | 20 | if (session('status')) { 21 | Filament::notify('success', session('status'), true); 22 | } 23 | } 24 | 25 | protected function getFormSchema(): array 26 | { 27 | return [ 28 | TextInput::make('password') 29 | ->extraInputAttributes(['name' => 'password']) 30 | ->label(__('filament-fortify::password-confirmation.fields.password.label')) 31 | ->password() 32 | ->required(), 33 | ]; 34 | } 35 | 36 | public function render(): View 37 | { 38 | return view('filament-fortify::password-confirmation') 39 | ->layout('filament::components.layouts.app', [ 40 | 'title' => __('filament-fortify::password-confirmation.title'), 41 | 'breadcrumbs' => [ 42 | __('filament-fortify::password-confirmation.title'), 43 | ], 44 | ]); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Http/Livewire/Auth/RequestPasswordReset.php: -------------------------------------------------------------------------------- 1 | check()) { 22 | redirect()->intended(Filament::getUrl()); 23 | } 24 | 25 | $this->form->fill(); 26 | 27 | if (session('status')) { 28 | Filament::notify('success', session('status'), true); 29 | redirect()->route('login'); 30 | } 31 | } 32 | 33 | protected function getFormSchema(): array 34 | { 35 | return [ 36 | TextInput::make('email') 37 | ->extraInputAttributes(['name' => 'email']) 38 | ->label(__('filament-fortify::password-reset.fields.email.label')) 39 | ->email() 40 | ->required() 41 | ->autocomplete(), 42 | ]; 43 | } 44 | 45 | public function render(): View 46 | { 47 | return view('filament-fortify::request-password-reset') 48 | ->layout('filament::components.layouts.card', [ 49 | 'title' => __('filament-fortify::password-reset.heading'), 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Commands/FilamentFortifyCommand.php: -------------------------------------------------------------------------------- 1 | line('Publishing Fortify Assets'); 18 | $this->call('vendor:publish', ['--provider' => 'Laravel\Fortify\FortifyServiceProvider']); 19 | 20 | $this->addProvider(); 21 | 22 | $this->comment('All done'); 23 | $this->warn('Don\'t forget to run `php artisan migrate`'); 24 | 25 | return self::SUCCESS; 26 | } 27 | 28 | private function addProvider() 29 | { 30 | $appConfig = file_get_contents(config_path('app.php')); 31 | 32 | if (! Str::contains($appConfig, 'App\\Providers\\FortifyServiceProvider::class')) { 33 | $this->line('Adding FortifyServiceProvider to config/app.php'); 34 | File::put( 35 | config_path('app.php'), 36 | str_replace( 37 | "App\Providers\RouteServiceProvider::class,", 38 | "App\Providers\RouteServiceProvider::class," . PHP_EOL . " App\Providers\FortifyServiceProvider::class,", 39 | $appConfig 40 | ) 41 | ); 42 | $this->info('FortifyServiceProvider configured'); 43 | } else { 44 | $this->line('FortifyServiceProvider already configured'); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Http/Livewire/Auth/Login.php: -------------------------------------------------------------------------------- 1 | resetPasswordEnabled = Features::enabled(Features::resetPasswords()); 25 | $this->registrationEnabled = Features::enabled(Features::registration()); 26 | 27 | if (session('status')) { 28 | Filament::notify('success', session('status'), true); 29 | } 30 | } 31 | 32 | protected function getFormSchema(): array 33 | { 34 | return [ 35 | TextInput::make('email') 36 | ->extraInputAttributes(['name' => 'email']) 37 | ->label(__('filament::login.fields.email.label')) 38 | ->email() 39 | ->required() 40 | ->autocomplete(), 41 | TextInput::make('password') 42 | ->extraInputAttributes(['name' => 'password']) 43 | ->label(__('filament::login.fields.password.label')) 44 | ->password() 45 | ->required(), 46 | Checkbox::make('remember') 47 | ->extraInputAttributes(['name' => 'remember']) 48 | ->label(__('filament::login.fields.remember.label')), 49 | ]; 50 | } 51 | 52 | public function render(): View 53 | { 54 | return view('filament-fortify::login') 55 | ->layout('filament::components.layouts.card', [ 56 | 'title' => __('filament::login.heading'), 57 | ]); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wychoong/filament-fortify", 3 | "description": "Laravel Fortify for Filament Admin", 4 | "keywords": [ 5 | "wychoong", 6 | "laravel", 7 | "filament-fortify" 8 | ], 9 | "homepage": "https://github.com/wychoong/filament-fortify", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "wychoong", 14 | "email": "wychoong@outlook.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.0|^8.1|^8.2", 20 | "filament/filament": "^2.12.18", 21 | "illuminate/contracts": "^9.0|^10.0", 22 | "laravel/fortify": "^1.11", 23 | "spatie/laravel-package-tools": "^1.9.2" 24 | }, 25 | "require-dev": { 26 | "nunomaduro/collision": "^6.0", 27 | "orchestra/testbench": "^7.0", 28 | "pestphp/pest": "^1.21", 29 | "pestphp/pest-plugin-laravel": "^1.1", 30 | "phpunit/phpunit": "^9.5", 31 | "spatie/laravel-ray": "^1.26" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "WyChoong\\FilamentFortify\\": "src", 36 | "WyChoong\\FilamentFortify\\Database\\Factories\\": "database/factories" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "WyChoong\\FilamentFortify\\Tests\\": "tests" 42 | } 43 | }, 44 | "scripts": { 45 | "analyse": "vendor/bin/phpstan analyse", 46 | "test": "vendor/bin/pest", 47 | "test-coverage": "vendor/bin/pest --coverage" 48 | }, 49 | "config": { 50 | "sort-packages": true 51 | }, 52 | "extra": { 53 | "laravel": { 54 | "providers": [ 55 | "WyChoong\\FilamentFortify\\FilamentFortifyServiceProvider" 56 | ], 57 | "aliases": { 58 | "FilamentFortify": "WyChoong\\FilamentFortify\\Facades\\FilamentFortify" 59 | } 60 | } 61 | }, 62 | "minimum-stability": "dev", 63 | "prefer-stable": true 64 | } 65 | -------------------------------------------------------------------------------- /src/Pages/TwoFactor.php: -------------------------------------------------------------------------------- 1 | actionButtonsMount(); 26 | 27 | if ( 28 | Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm') && 29 | is_null(Auth::user()->two_factor_confirmed_at) 30 | ) { 31 | app(DisableTwoFactorAuthentication::class)(Auth::user()); 32 | } 33 | 34 | if (session('status') == 'two-factor-authentication-enabled') { 35 | Filament::notify('success', __("filament-fortify::two-factor.messages.enabled"), true); 36 | } 37 | } 38 | 39 | protected function getBreadcrumbs(): array 40 | { 41 | return [ 42 | url()->current() => FilamentFortify::pageTitle(), 43 | ]; 44 | } 45 | 46 | protected static function getNavigationIcon(): string 47 | { 48 | return static::$navigationIcon ?? config('filament-fortify.navigation-icon', 'heroicon-o-key'); 49 | } 50 | 51 | protected static function getNavigationGroup(): ?string 52 | { 53 | return FilamentFortify::navigationGroup(); 54 | } 55 | 56 | public static function getNavigationLabel(): string 57 | { 58 | return FilamentFortify::navigationLabel(); 59 | } 60 | 61 | protected function getTitle(): string 62 | { 63 | return FilamentFortify::pageTitle(); 64 | } 65 | 66 | /** 67 | * Show two factor authentication info. 68 | * 69 | * @return bool 70 | */ 71 | private function showTwoFactor(): bool 72 | { 73 | return ! empty(Auth::user()->two_factor_secret); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Http/Livewire/Auth/PasswordReset.php: -------------------------------------------------------------------------------- 1 | check()) { 24 | redirect()->intended(Filament::getUrl()); 25 | } 26 | 27 | $this->form->fill(); 28 | 29 | if (session('status')) { 30 | Filament::notify('success', session('status'), true); 31 | } 32 | } 33 | 34 | protected function getFormSchema(): array 35 | { 36 | return [ 37 | TextInput::make('email_label') 38 | ->label(__('filament-fortify::register.fields.email.label')) 39 | ->afterStateHydrated(fn ($component) => $component->state(request()->get('email'))) 40 | ->disabled(), 41 | TextInput::make('password') 42 | ->extraInputAttributes(['name' => 'password']) 43 | ->label(__('filament-fortify::register.fields.password.label')) 44 | ->password() 45 | ->required() 46 | ->rules(['confirmed']) 47 | ->autocomplete('new-password'), 48 | TextInput::make('password_confirmation') 49 | ->extraInputAttributes(['name' => 'password_confirmation']) 50 | ->label(__('filament-fortify::register.fields.password_confirm.label')) 51 | ->password() 52 | ->autocomplete('new-password') 53 | ->required(), 54 | Hidden::make('email') 55 | ->extraAttributes(['name' => 'email']) 56 | ->afterStateHydrated(fn ($component) => $component->state(request()->get('email'))), 57 | Hidden::make('token') 58 | ->extraAttributes(['name' => 'token']) 59 | ->afterStateHydrated(fn ($component) => $component->state(request()->route('token'))), 60 | ]; 61 | } 62 | 63 | public function render(): View 64 | { 65 | return view('filament-fortify::password-reset') 66 | ->layout('filament::components.layouts.card', [ 67 | 'title' => __('filament-fortify::password-reset.heading'), 68 | ]); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /resources/lang/en/two-factor.php: -------------------------------------------------------------------------------- 1 | 'Reset password', 5 | 6 | 'heading' => 'Reset password', 7 | 8 | 'confirm-password' => [ 9 | 'modal-heading' => "Sensitive action", 10 | 'modal-subheading' => "Please confirm your password before proceed this action.", 11 | ], 12 | 13 | 'buttons' => [ 14 | 15 | 'enable' => [ 16 | 'label' => 'Enable two factor auth', 17 | ], 18 | 19 | 'confirm' => [ 20 | 'label' => 'Enable', 21 | ], 22 | 23 | 'disable' => [ 24 | 'label' => 'Disable', 25 | ], 26 | 27 | 'regenerate' => [ 28 | 'label' => 'Regenerate recovery code', 29 | ], 30 | 31 | 'show-recovery-code' => [ 32 | 'label' => 'Show recovery code', 33 | ], 34 | 35 | ], 36 | 37 | 'label' => [ 38 | 'setup-key' => 'Setup key', 39 | ], 40 | 41 | 'fields' => [ 42 | 'email' => [ 43 | 'label' => 'Email address', 44 | ], 45 | 46 | 'password' => [ 47 | 'label' => 'Password', 48 | ], 49 | 50 | 'password_confirm' => [ 51 | 'label' => 'Confirm password', 52 | ], 53 | 54 | 'code' => [ 55 | 'label' => 'OTP code', 56 | ], 57 | 58 | 'current_password' => [ 59 | 'label' => 'Password', 60 | ] 61 | ], 62 | 63 | 'messages' => [ 64 | 'throttled' => 'Too many register attempts. Please try again in :seconds seconds.', 65 | 'enabled' => 'Two factor authentication has been enabled', 66 | 'scan-qr' => 'Scan the following QR code using your phone\'s authenticator application or enter the setup key.', 67 | 'store-recovery-code' => 'Store these recovery codes in a secure place to recover access to your account if your two factor authentication device is lost.', 68 | 'confirm-two-factor-code' => 'Confirm the two factor code by entering the OTP code from the authenticator application', 69 | ], 70 | 71 | 'login' => [ 72 | 'title' => 'Two factor login', 73 | 74 | 'heading' => 'Two factor login', 75 | 76 | 'buttons' => [ 77 | 'submit' => [ 78 | 'label' => 'Login', 79 | ], 80 | 81 | 'cancel' => [ 82 | 'label' => 'Cancel', 83 | ], 84 | ], 85 | 'fields' => [ 86 | 'code' => [ 87 | 'label' => 'Code', 88 | ], 89 | 90 | 'recovery_code' => [ 91 | 'label' => 'Recovery code', 92 | ], 93 | ] 94 | ], 95 | 96 | 'page' => [ 97 | 'title' => 'Two Factor Auth', 98 | 99 | 'navigation-group' => 'Profile', 100 | 101 | 'navigation-label' => 'Two Factor Auth' 102 | ] 103 | ]; 104 | -------------------------------------------------------------------------------- /src/Http/Livewire/Auth/Register.php: -------------------------------------------------------------------------------- 1 | check()) { 32 | redirect()->intended(Filament::getUrl()); 33 | } 34 | 35 | $this->form->fill(); 36 | } 37 | 38 | protected function getFormSchema(): array 39 | { 40 | return [ 41 | TextInput::make('name') 42 | ->extraInputAttributes(['name' => 'name']) 43 | ->label(__('filament-fortify::register.fields.name.label')) 44 | ->maxLength(255) 45 | ->required(), 46 | TextInput::make('email') 47 | ->extraInputAttributes(['name' => 'email']) 48 | ->label(__('filament-fortify::register.fields.email.label')) 49 | ->email() 50 | ->required() 51 | ->autocomplete(), 52 | TextInput::make('password') 53 | ->extraInputAttributes(['name' => 'password']) 54 | ->label(__('filament-fortify::register.fields.password.label')) 55 | ->password() 56 | ->required() 57 | ->rules(['confirmed']) 58 | ->autocomplete('new-password'), 59 | TextInput::make('password_confirmation') 60 | ->extraInputAttributes(['name' => 'password_confirmation']) 61 | ->label(__('filament-fortify::register.fields.password_confirm.label')) 62 | ->password() 63 | ->autocomplete('new-password') 64 | ->required(), 65 | ]; 66 | } 67 | 68 | public function render(): View 69 | { 70 | return view('filament-fortify::register') 71 | ->layout('filament::components.layouts.card', [ 72 | 'title' => __('filament-fortify::register.heading'), 73 | ]); 74 | } 75 | 76 | public function register(CreatesNewUsers $creator) 77 | { 78 | try { 79 | $this->rateLimit(5); 80 | } catch (TooManyRequestsException $exception) { 81 | $this->addError('email', __('filament-fortify::register.messages.throttled', [ 82 | 'seconds' => $exception->secondsUntilAvailable, 83 | 'minutes' => ceil($exception->secondsUntilAvailable / 60), 84 | ])); 85 | 86 | return; 87 | } 88 | 89 | $data = $this->form->getState(); 90 | 91 | event(new Registered($user = $creator->create($data))); 92 | 93 | Filament::auth()->login($user); 94 | 95 | return app(RegisterResponse::class); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /resources/views/filament/pages/two-factor.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @php 3 | $buttons = $this->getCachedButtons(); 4 | @endphp 5 | 6 | @if($this->showTwoFactor()) 7 |
8 |
9 | 10 | 11 |
12 |
13 | @unless($showingQrCode) 14 |
15 | {{__("filament-fortify::two-factor.messages.enabled")}} 16 |
17 | @else 18 |
19 | {{__("filament-fortify::two-factor.messages.scan-qr")}} 20 |
21 |
22 | {!!auth()->user()->twoFactorQrCodeSvg()!!} 23 |
24 |
25 |

26 | {{ __('filament-fortify::two-factor.label.setup-key') }}: {{ decrypt(auth()->user()->two_factor_secret) }} 27 |

28 | @endif 29 |
30 | 31 | @if($showingRecoveryCodes) 32 |
33 | {{__("filament-fortify::two-factor.messages.store-recovery-code")}} 34 |
35 |
36 | @foreach((array) auth()->user()->recoveryCodes() as $index => $code) 37 |

{{$index + 1}}) {{$code}}

38 | @endforeach 39 |
40 |
41 |
42 | @endif 43 | 44 | @if($showingConfirmation) 45 |
46 |
49 | 52 | {{__('filament-fortify::two-factor.messages.confirm-two-factor-code')}} 53 | * 54 | 55 |
56 |
57 | {{ $this->form}} 58 |
59 |
60 | 61 | @endif 62 | 63 | 64 |
65 | {{ $buttons['disable'] }} 66 | 67 | @if($showingRecoveryCodes) 68 | {{ $buttons['regenerate'] }} 69 | @elseif($showingConfirmation) 70 | {{ $buttons['confirm'] }} 71 | @else 72 | {{ $buttons['show-recovery-code'] }} 73 | @endif 74 |
75 |
76 |
77 | 78 |
79 |
80 |
81 | 82 | @else 83 | {{ $buttons['enable'] }} 84 | @endif 85 |
86 | -------------------------------------------------------------------------------- /src/Pages/Concerns/ActionButtons.php: -------------------------------------------------------------------------------- 1 | form->fill([ 42 | 'code' => null, 43 | ]); 44 | } 45 | 46 | /** 47 | * Enable two factor authentication for the user. 48 | * 49 | * @param \Laravel\Fortify\Actions\EnableTwoFactorAuthentication $enable 50 | * @return void 51 | */ 52 | public function enableTwoFactorAuthentication(EnableTwoFactorAuthentication $enable) 53 | { 54 | $enable(Auth::user()); 55 | 56 | $this->showingQrCode = true; 57 | 58 | if (Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm')) { 59 | $this->showingConfirmation = true; 60 | } else { 61 | $this->showingRecoveryCodes = true; 62 | } 63 | } 64 | 65 | /** 66 | * Disable two factor authentication for the user. 67 | * 68 | * @param \Laravel\Fortify\Actions\DisableTwoFactorAuthentication $disable 69 | * @return void 70 | */ 71 | public function disableTwoFactorAuthentication(DisableTwoFactorAuthentication $disable) 72 | { 73 | $disable(Auth::user()); 74 | 75 | $this->showingQrCode = false; 76 | $this->showingConfirmation = false; 77 | $this->showingRecoveryCodes = false; 78 | } 79 | 80 | /** 81 | * Display the user's recovery codes. 82 | * 83 | * @return void 84 | */ 85 | public function showRecoveryCodes() 86 | { 87 | $this->showingRecoveryCodes = true; 88 | } 89 | 90 | /** 91 | * Generate new recovery codes for the user. 92 | * 93 | * @param \Laravel\Fortify\Actions\GenerateNewRecoveryCodes $generate 94 | * @return void 95 | */ 96 | public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generate) 97 | { 98 | $generate(Auth::user()); 99 | 100 | $this->showingRecoveryCodes = true; 101 | } 102 | 103 | /** 104 | * Confirm two factor authentication for the user. 105 | * 106 | * @param \Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication $confirm 107 | * @return void 108 | */ 109 | public function confirmTwoFactorAuthentication(ConfirmTwoFactorAuthentication $confirm) 110 | { 111 | $data = $this->form->getState(); 112 | $confirm(Auth::user(), $data['code']); 113 | 114 | $this->showingQrCode = false; 115 | $this->showingConfirmation = false; 116 | $this->showingRecoveryCodes = true; 117 | } 118 | 119 | /** 120 | * Input field for two factor authentication confirmation code 121 | * 122 | * @return array 123 | */ 124 | protected function getFormSchema(): array 125 | { 126 | return [ 127 | TextInput::make('code') 128 | ->label(__('filament-fortify::two-factor.fields.code.label')) 129 | ->validationAttribute(__('filament-fortify::two-factor.fields.code.label')) 130 | ->inlineLabel() 131 | ->required(), 132 | ]; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Pages/Concerns/HasActionButtons.php: -------------------------------------------------------------------------------- 1 | cachedButtons === null) { 21 | $this->cacheButtons(); 22 | } 23 | 24 | return $this->cachedButtons; 25 | } 26 | 27 | protected function cacheButtons(): void 28 | { 29 | $this->cachedButtons = collect($this->getButtons()) 30 | ->mapWithKeys(function (Action $action): array { 31 | $action->livewire($this); 32 | 33 | return [$action->getName() => $action]; 34 | }) 35 | ->toArray(); 36 | } 37 | 38 | public function getCachedAction(string $name): ?Action 39 | { 40 | return $this->getCachedButtons()[$name] ?? parent::getCachedAction($name); 41 | } 42 | 43 | protected function getButtons(): array | View | null 44 | { 45 | $confirmation = fn () => ! $this->passwordIsConfirmed(); 46 | 47 | $confirmationForm = $confirmation() ? [ 48 | TextInput::make("current_password") 49 | ->label(__("filament-fortify::two-factor.fields.current_password.label")) 50 | ->dehydrateStateUsing(fn ($state) => filled($state)) 51 | ->required() 52 | ->password() 53 | ->inlineLabel() 54 | ->rule("current_password"), 55 | ] : []; 56 | 57 | $buttons = [ 58 | 'enable' => [ 59 | 'action' => 'enableTwoFactorAuthentication', 60 | ], 61 | 'disable' => [ 62 | 'action' => 'disableTwoFactorAuthentication', 63 | 'confirmation' => fn () => is_null(Auth::user()->two_factor_confirmed_at) ? false : $confirmation(), 64 | 'color' => 'danger', 65 | ], 66 | 'regenerate' => [ 67 | 'action' => 'regenerateRecoveryCodes', 68 | 'color' => 'secondary', 69 | ], 70 | 'confirm' => [ 71 | 'action' => 'confirmTwoFactorAuthentication', 72 | 'color' => 'success', 73 | ], 74 | 'show-recovery-code' => [ 75 | 'action' => 'showRecoveryCodes', 76 | 'color' => 'secondary', 77 | ], 78 | ]; 79 | 80 | return collect($buttons)->map(function ($button, $name) use ($confirmation, $confirmationForm) { 81 | $action = $button['action']; 82 | $buttonColor = $button['color'] ?? 'primary'; 83 | $confirmation = isset($button['confirmation']) ? $button['confirmation'] : $confirmation; 84 | 85 | return Action::make($name) 86 | ->label(__("filament-fortify::two-factor.buttons.{$name}.label")) 87 | ->form($confirmation() ? $confirmationForm : []) 88 | ->color($buttonColor) 89 | ->action(function ($data) use ($action) { 90 | if (isset($data['current_password']) && $data['current_password']) { 91 | $this->userConfirmedPassword(); 92 | } 93 | 94 | app()->call([$this, $action]); 95 | }) 96 | ->modalHeading(__("filament-fortify::two-factor.confirm-password.modal-heading")) 97 | ->modalSubheading(__("filament-fortify::two-factor.confirm-password.modal-subheading")) 98 | ->requiresConfirmation($confirmation); 99 | })->toArray(); 100 | } 101 | 102 | /** 103 | * Enable two factor authentication for the user. 104 | * 105 | * @param \Laravel\Fortify\Actions\EnableTwoFactorAuthentication $enable 106 | * @return void 107 | */ 108 | abstract public function enableTwoFactorAuthentication(EnableTwoFactorAuthentication $enable); 109 | 110 | /** 111 | * Disable two factor authentication for the user. 112 | * 113 | * @param \Laravel\Fortify\Actions\DisableTwoFactorAuthentication $disable 114 | * @return void 115 | */ 116 | abstract public function disableTwoFactorAuthentication(DisableTwoFactorAuthentication $disable); 117 | 118 | /** 119 | * Display the user's recovery codes. 120 | * 121 | * @return void 122 | */ 123 | abstract public function showRecoveryCodes(); 124 | 125 | /** 126 | * Generate new recovery codes for the user. 127 | * 128 | * @param \Laravel\Fortify\Actions\GenerateNewRecoveryCodes $generate 129 | * @return void 130 | */ 131 | abstract public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generate); 132 | 133 | /** 134 | * Confirm two factor authentication for the user. 135 | * 136 | * @param \Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication $confirm 137 | * @return void 138 | */ 139 | abstract public function confirmTwoFactorAuthentication(ConfirmTwoFactorAuthentication $confirm); 140 | } 141 | -------------------------------------------------------------------------------- /src/FilamentFortifyServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('filament-fortify') 32 | ->hasConfigFile() 33 | ->hasViews() 34 | ->hasTranslations() 35 | ->hasCommand(FilamentFortifyCommand::class); 36 | } 37 | 38 | public function packageBooted(): void 39 | { 40 | $this->mergeConfigFrom(__DIR__ . '/../config/filament-fortify.php', 'filament-fortify'); 41 | 42 | config([ 43 | ## override filament login page 44 | 'filament.auth.pages.login' => config('filament-fortify.auth.login'), 45 | ## force fortify view enabled 46 | 'fortify.views' => true, 47 | ## force fortify to use filament home_url 48 | 'fortify.home' => config('filament.home_url'), 49 | ## mirror admin config 50 | 'forms.dark_mode' => config('filament.dark_mode'), 51 | ]); 52 | 53 | Livewire::component(config('filament-fortify.auth.register')::getName(), config('filament-fortify.auth.register')); 54 | Livewire::component(config('filament-fortify.auth.password-reset')::getName(), config('filament-fortify.auth.password-reset')); 55 | Livewire::component(config('filament-fortify.auth.request-password-reset')::getName(), config('filament-fortify.auth.request-password-reset')); 56 | Livewire::component(config('filament-fortify.pages.two-factor')::getName(), config('filament-fortify.pages.two-factor')); 57 | 58 | Fortify::loginView(function () { 59 | return app()->call(config('filament-fortify.auth.login')); 60 | }); 61 | 62 | $this->app->singleton(LoginResponseContract::class, LoginResponse::class); 63 | 64 | if (Features::enabled(Features::registration())) { 65 | Fortify::registerView(function () { 66 | return app()->call(config('filament-fortify.auth.register')); 67 | }); 68 | } 69 | 70 | if (Features::enabled(Features::resetPasswords())) { 71 | Fortify::requestPasswordResetLinkView(function () { 72 | return app()->call(config('filament-fortify.auth.request-password-reset')); 73 | }); 74 | 75 | Fortify::resetPasswordView(function ($request) { 76 | return app()->call(config('filament-fortify.auth.password-reset')); 77 | }); 78 | } 79 | 80 | if (Features::enabled(Features::emailVerification())) { 81 | Fortify::verifyEmailView(function () { 82 | return view(config('filament-fortify.view.verify-email')); 83 | }); 84 | } 85 | 86 | Fortify::confirmPasswordView(function () { 87 | return app()->call(config('filament-fortify.auth.password-confirmation')); 88 | }); 89 | 90 | if (Features::enabled(Features::twoFactorAuthentication())) { 91 | Fortify::twoFactorChallengeView(function () { 92 | return app()->call(config('filament-fortify.auth.login-two-factor')); 93 | }); 94 | } 95 | 96 | Route::domain(config('filament.domain')) 97 | ->middleware(config('filament.middleware.base')) 98 | ->name('filament.') 99 | ->group(function () { 100 | /** 101 | * We do not need to override logout response and logout path as: 102 | * - logout response for both filament and fortify does 103 | * basically the same things except fortify handle for api calls 104 | * - for api calls still can use POST fortify's /logout route 105 | * - filament's logout route is at /filament/logout 106 | */ 107 | 108 | /** 109 | * Redeclare filament.auth.login route as fortify override it 110 | * This route name is used multiple places in filament. 111 | */ 112 | Route::prefix(config('filament.path'))->group(function () { 113 | Route::get('/filament-login', fn () => Redirect::route('login')) 114 | ->name('auth.login'); 115 | }); 116 | }); 117 | } 118 | 119 | protected function getPages(): array 120 | { 121 | return config('filament-fortify.register-page') ? [ 122 | config('filament-fortify.pages.two-factor'), 123 | ] : []; 124 | } 125 | 126 | protected function mergeConfigFrom($path, $key): void 127 | { 128 | $config = $this->app['config']->get($key, []); 129 | 130 | $this->app['config']->set($key, $this->mergeConfig(require $path, $config)); 131 | } 132 | 133 | protected function mergeConfig(array $original, array $merging): array 134 | { 135 | $array = array_merge($original, $merging); 136 | 137 | foreach ($original as $key => $value) { 138 | if (! is_array($value)) { 139 | continue; 140 | } 141 | 142 | if (! Arr::exists($merging, $key)) { 143 | continue; 144 | } 145 | 146 | if (is_numeric($key)) { 147 | continue; 148 | } 149 | 150 | $array[$key] = $this->mergeConfig($value, $merging[$key]); 151 | } 152 | 153 | return $array; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://banners.beyondco.de/Filament%20Fortify.png?theme=light&packageManager=composer+require&packageName=wychoong%2Ffilament-fortify&pattern=skulls&style=style_2&description=Laravel+Fortify+for+Filament+Admin&md=1&showWatermark=1&fontSize=175px&images=https%3A%2F%2Flaravel.com%2Fimg%2Flogomark.min.svg) 2 | # Laravel Fortify for Filament Admin 3 | 4 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/wychoong/filament-fortify.svg?style=flat-square)](https://packagist.org/packages/wychoong/filament-fortify) 5 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/wychoong/filament-fortify/run-tests?label=tests)](https://github.com/wychoong/filament-fortify/actions?query=workflow%3Arun-tests+branch%3Amain) 6 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/wychoong/filament-fortify/Check%20&%20fix%20styling?label=code%20style)](https://github.com/wychoong/filament-fortify/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/wychoong/filament-fortify.svg?style=flat-square)](https://packagist.org/packages/wychoong/filament-fortify) 8 | 9 | This package provides the UI for using Fortify in Filament Admin Panel 10 | 11 | ## Screenshots 12 | _Login_ 13 | 14 | ![Screenshot of Login Screen](./screenshots/login.png) 15 | 16 | _Register_ 17 | 18 | ![Screenshot of Register Screen](./screenshots/register.png) 19 | 20 | _Email Verification_ 21 | 22 | ![Screenshot of Register Screen](./screenshots/email-verification.png) 23 | 24 | _Forgot Password_ 25 | 26 | ![Screenshot of Forgot Password Screen](./screenshots/forgot-password.png) 27 | 28 | _Two Factor Login_ 29 | 30 | ![Screenshot of 2fa Login Screen](./screenshots/2fa-login.png) 31 | 32 | _Two Factor Page_ 33 | 34 | ![Screenshot of 2fa Page Screen](./screenshots/2fa-page.png) 35 | 36 | _Password Confirmation_ 37 | 38 | ![Screenshot of Password Confirmation Screen](./screenshots/confirm-password.png) 39 | 40 | ## Installation 41 | 42 | You can install the package via composer: 43 | 44 | ```bash 45 | composer require wychoong/filament-fortify 46 | ``` 47 | 48 | You can intallation command and run the migrations with: 49 | 50 | ```bash 51 | php artisan filament-fortify:install 52 | php artisan migrate 53 | ``` 54 | The installation command does few things to speed up the installation process: 55 | ``` 56 | - Publish Fortify files 57 | - Publish Fortify migration 58 | - Add FortifyServiceProvider to config/app.php 59 | ``` 60 | 61 | **As this package is only providing UI for Fortify, kindly refer [Laravel Fortify](https://laravel.com/docs/9.x/fortify) documentation to setup, eg: User model.** 62 | 63 | ## Optional 64 | 65 | You can publish filament-fortify config file with: 66 | 67 | ```bash 68 | php artisan vendor:publish --tag="filament-fortify-config" 69 | ``` 70 | 71 | Optionally, you can publish the views using 72 | 73 | ```bash 74 | php artisan vendor:publish --tag="filament-fortify-views" 75 | ``` 76 | 77 | ## Usage 78 | 79 | This package respect the features configured in config/fortify.php, refer [Laravel Fortify](https://laravel.com/docs/9.x/fortify) to enable/disable features. 80 | 81 | To make the installation seemless, the following configs are overrided in the package. 82 | ```php 83 | config([ 84 | ## override filament login page 85 | 'filament.auth.pages.login' => Auth\Login::class, 86 | ## force fortify view enabled 87 | 'fortify.views' => true, 88 | ## force fortify to use filament home_url 89 | 'fortify.home' => config('filament.home_url'), 90 | ]); 91 | ``` 92 | 93 | #### Email Verification 94 | ![Screenshot of Email Verification Screen](./screenshots/email-verification.png) 95 | 96 | To allow user access only after email verified, enable the feature in config/fortify.php and update config/filament.php 97 | 98 | ```php 99 | 'middleware' => [ 100 | 'auth' => [ 101 | // ... 102 | 'verified' 103 | ], 104 | // ... 105 | ] 106 | 107 | ## update your User model 108 | 109 | use Illuminate\Contracts\Auth\MustVerifyEmail; 110 | 111 | class User extends Authenticatable implements FilamentUser, MustVerifyEmail 112 | { 113 | // ... 114 | } 115 | ``` 116 | 117 | #### Password Confirmation 118 | ![Screenshot of Password Confirmation Screen](./screenshots/confirm-password.png) 119 | 120 | To request user password confirmation before access a Page/Resource, add 121 | ```php 122 | protected static string | array $middlewares = ['password.confirm']; 123 | ``` 124 | to relevant Page/Resource. 125 | 126 | #### 2FA 127 | Update your User model 128 | ```php 129 | use Laravel\Fortify\TwoFactorAuthenticatable; 130 | 131 | class User extends Authenticatable implements FilamentUser 132 | { 133 | // ... 134 | use TwoFactorAuthenticatable; 135 | // ... 136 | } 137 | ``` 138 | A simple enable/disable user's 2fa page is included. 139 | 140 | You can change the page's title, navigation group, navigation label in service provider: 141 | 142 | ```php 143 | use WyChoong\FilamentFortify\Facades\FilamentFortify; 144 | 145 | public function boot() 146 | { 147 | FilamentFortify::navigationGroup(__('your-nav-group')); 148 | FilamentFortify::navigationLabel(__('your-nav-label')); 149 | FilamentFortify::pageTitle(__('your-page-title')); 150 | } 151 | ``` 152 | 153 | To disable it, publish the config file and set: 154 | ```php 155 | 'register-page' => false, 156 | ``` 157 | 158 | ## Customization 159 | 160 | To use your own form, publish the config file and set your own livewire component 161 | ```php 162 | # config/filament-fortify.php 163 | use App\Http\Livewire\Login; 164 | 165 | return [ 166 | // ... 167 | 'auth' => [ 168 | 'login' => Login::class, 169 | // ... 170 | ], 171 | // ... 172 | ]; 173 | 174 | # app/Http/Livewire/Login.php 175 | namespace App\Http\Livewire\Auth; 176 | 177 | use WyChoong\FilamentFortify\Http\Livewire\Auth\Login as BaseLogin; 178 | 179 | class Login extends BaseLogin{ 180 | 181 | protected function getFormSchema(): array 182 | { 183 | return [ 184 | // your form schema 185 | ]; 186 | } 187 | } 188 | ``` 189 | 190 | ### Theme 191 | Depends on your project setup, you might register the css file with `Filament::serving` as per [Filament Documentation](https://filamentphp.com/docs/2.x/admin/appearance#building-themes), then you need to publish the `fortify.config` and add `DispatchServingFilamentEvent::class` to the middleware for Fortify's routes. 192 | ```php 193 | use Filament\Http\Middleware\DispatchServingFilamentEvent; 194 | 195 | return [ 196 | // ... 197 | 'middleware' => ['web', DispatchServingFilamentEvent::class], 198 | //... 199 | ]; 200 | ``` 201 | 202 | ### Render hooks 203 | Make use of Filament's render hook to register additional content without publishing views. 204 | 205 | Example with [Filament-Socialite](https://github.com/DutchCodingCompany/filament-socialite) 206 | ```php 207 | ## in Service Provider file 208 | public function boot() 209 | { 210 | Filament::registerRenderHook( 211 | 'filament-fortify.login.end', 212 | fn (): string => Blade::render('@livewire(\'filament-socialite.buttons\')'), 213 | ); 214 | } 215 | ``` 216 | 217 | ![Screenshot of hook for socialite](https://user-images.githubusercontent.com/67364036/171989863-67e10be6-6619-4bb0-b258-2fd64edecc00.png) 218 | 219 | Available hooks 220 | - filament-fortify.login.start 221 | - filament-fortify.login.end 222 | 223 | ## Testing 224 | 225 | ```bash 226 | composer test 227 | ``` 228 | 229 | ## Changelog 230 | 231 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 232 | 233 | ## Contributing 234 | 235 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 236 | 237 | ## Security Vulnerabilities 238 | 239 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 240 | 241 | ## Credits 242 | 243 | - [wychoong](https://github.com/wychoong) 244 | - [All Contributors](../../contributors) 245 | 246 | ## License 247 | 248 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 249 | --------------------------------------------------------------------------------