├── workbench ├── app │ ├── .gitkeep │ ├── Providers │ │ ├── WorkbenchServiceProvider.php │ │ └── Filament │ │ │ ├── AppPanelProvider.php │ │ │ └── AdminPanelProvider.php │ └── Filament │ │ └── Pages │ │ └── Auth │ │ └── Login.php └── .gitignore ├── .github ├── FUNDING.yml └── workflows │ ├── update-changelog.yml │ └── tests.yml ├── tests ├── Browser │ ├── source │ │ └── .gitignore │ ├── console │ │ └── .gitignore │ ├── screenshots │ │ └── .gitignore │ ├── Pages │ │ ├── AppLogin.php │ │ └── AdminLogin.php │ └── LoginTest.php ├── Pest.php ├── WithSecretKey.php └── DuskTestCase.php ├── .gitignore ├── docs ├── advance-usage │ ├── _index.md │ └── enhance-security.md ├── basic-usage │ ├── _index.md │ ├── disable-secret-access.md │ ├── multiple-panel-support.md │ └── enable-secret-access.md └── getting-started │ ├── _index.md │ ├── introduction.md │ ├── installation.md │ └── how-it-works.md ├── art └── banner.png ├── src ├── Exceptions │ └── InvalidAccessSecretCookieException.php ├── AccessSecretCookie.php ├── Contracts │ └── AccessSecretCookie.php ├── Controllers │ └── StoreSecret.php ├── Middleware │ └── VerifyAdminAccessSecret.php ├── DefaultAccessSecretCookie.php └── FilamentAccessSecretServiceProvider.php ├── testbench.yaml ├── config └── filament-access-secret.php ├── phpunit.xml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── CONTRIBUTING.md └── composer.json /workbench/app/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dasundev 2 | -------------------------------------------------------------------------------- /workbench/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.dusk 3 | -------------------------------------------------------------------------------- /tests/Browser/source/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .idea 4 | .DS_Store -------------------------------------------------------------------------------- /tests/Browser/console/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/Browser/screenshots/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /docs/advance-usage/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advance Usage 3 | weight: 3 4 | --- -------------------------------------------------------------------------------- /docs/basic-usage/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Basic Usage 3 | weight: 2 4 | --- -------------------------------------------------------------------------------- /docs/getting-started/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | weight: 1 4 | --- -------------------------------------------------------------------------------- /art/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dasundev/filament-access-secret/HEAD/art/banner.png -------------------------------------------------------------------------------- /docs/getting-started/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | weight: 1 4 | --- 5 | 6 | # Introduction 7 | 8 | This package provides a middleware for securing access to Filament by requiring a secret key to be provided in the URL. 9 | -------------------------------------------------------------------------------- /workbench/app/Providers/WorkbenchServiceProvider.php: -------------------------------------------------------------------------------- 1 | MyAccessSecretCookie::class 25 | ]; 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Browser'); 17 | 18 | Options::withoutUI(); 19 | -------------------------------------------------------------------------------- /docs/getting-started/how-it-works.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How it works 3 | weight: 2 4 | --- 5 | 6 | # How it works 7 | 8 | Once you set up and configure this package, it works by preventing access to `http://my-website.com/admin`. If you try to visit that link, you will see a "404" message. But if you add the secret key at the end of the URL like this: `http://my-website.com/admin/secret`, you will be able to access the admin panel. 9 | 10 | This functionality is facilitated through a specific type of cookie working behind the scenes. This cookie validates whether you possess the authorization to access the Filament panel. 11 | 12 | > [!IMPORTANT] 13 | > The filament access secret key only works if your panel provider ID and path are the same. 14 | -------------------------------------------------------------------------------- /testbench.yaml: -------------------------------------------------------------------------------- 1 | providers: 2 | - Workbench\App\Providers\WorkbenchServiceProvider 3 | - Livewire\LivewireServiceProvider 4 | - Filament\Forms\FormsServiceProvider 5 | - Filament\Support\SupportServiceProvider 6 | - Filament\Actions\ActionsServiceProvider 7 | - Filament\Notifications\NotificationsServiceProvider 8 | - Filament\FilamentServiceProvider 9 | - Workbench\App\Providers\Filament\AdminPanelProvider 10 | - Workbench\App\Providers\Filament\AppPanelProvider 11 | - Dasundev\FilamentAccessSecret\FilamentAccessSecretServiceProvider 12 | 13 | workbench: 14 | welcome: true 15 | install: true 16 | guard: web 17 | build: 18 | - create-sqlite-db 19 | - migrate:refresh 20 | - filament:assets 21 | discovers: 22 | web: true 23 | config: true -------------------------------------------------------------------------------- /tests/WithSecretKey.php: -------------------------------------------------------------------------------- 1 | withSecretKey = true; 16 | 17 | $this->url = "/$this->panel/$key"; 18 | 19 | return $this; 20 | } 21 | 22 | public function url(): string 23 | { 24 | if ($this->withSecretKey) { 25 | return $this->url; 26 | } 27 | 28 | return "/$this->panel/login"; 29 | } 30 | 31 | public function assert(Browser $browser): void 32 | { 33 | $browser->assertPathIs("/$this->panel/login"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: Update Changelog 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | update: 9 | runs-on: ubuntu-latest 10 | 11 | permissions: 12 | contents: write 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | with: 18 | ref: main 19 | 20 | - name: Update Changelog 21 | uses: stefanzweifel/changelog-updater-action@v1 22 | with: 23 | latest-version: ${{ github.event.release.name }} 24 | release-notes: ${{ github.event.release.body }} 25 | 26 | - name: Commit updated CHANGELOG 27 | uses: stefanzweifel/git-auto-commit-action@v5 28 | with: 29 | branch: main 30 | commit_message: 'docs: Update CHANGELOG.md' 31 | file_pattern: CHANGELOG.md -------------------------------------------------------------------------------- /docs/basic-usage/multiple-panel-support.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Multiple panel support 3 | weight: 3 4 | --- 5 | 6 | # Multiple panel support 7 | 8 | The filament access secret is supports for multiple panels with different secret keys. 9 | 10 | To enable it, you must publish the configuration file by running the following command. 11 | 12 | ```bash 13 | php artisan vendor:publish --tag="filament-access-secret-config" 14 | ``` 15 | 16 | Then open the config file at `config/filament-access-secret.php` and add your new key with the env variable as follows. 17 | 18 | ```php 19 | 'keys' => [ 20 | ... 21 | 'app' => env('APP_FILAMENT_ACCESS_SECRET_KEY', ''), // "app" is the id of the panel 22 | ], 23 | ``` 24 | 25 | Now you can set a secret key for the new panel (in this case for the "app" panel). 26 | 27 | ```dotenv 28 | APP_FILAMENT_ACCESS_SECRET_KEY=app123 29 | ``` 30 | -------------------------------------------------------------------------------- /config/filament-access-secret.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'default' => env('DEFAULT_FILAMENT_ACCESS_SECRET_KEY', ''), 16 | ], 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Access Secret Cookie 21 | |-------------------------------------------------------------------------- 22 | | 23 | | To use your own access secret cookie, set it here. 24 | | 25 | */ 26 | 27 | 'cookie' => \Dasundev\FilamentAccessSecret\DefaultAccessSecretCookie::class, 28 | ]; 29 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ./app 20 | ./src 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `filament-access-secret` will be documented in this file. 4 | 5 | ## v3.0.1 - 2024-02-25 6 | 7 | ### What's Changed 8 | 9 | * refactor: The name of the middleware folder by @dasundev in https://github.com/dasundev/filament-access-secret/pull/15 10 | 11 | **Full Changelog**: https://github.com/dasundev/filament-access-secret/compare/v3.0.0...v3.0.1 12 | 13 | ## v3.0.0 - 2024-02-24 14 | 15 | ### What's Changed 16 | 17 | * feat: Add support for multiple panels using different access keys by @dasundev in https://github.com/dasundev/filament-access-secret/pull/12 18 | 19 | **Full Changelog**: https://github.com/dasundev/filament-access-secret/compare/v2.1.2...v3.0.0 20 | 21 | ## v2.1.2 - 2024-01-13 22 | 23 | ### What's Changed 24 | 25 | * Add tests by @dasundev 26 | * Add github workflows by @dasundev 27 | 28 | **Full Changelog**: https://github.com/dasundev/filament-access-secret/compare/v2.1.1...v2.1.2 29 | 30 | ## 1.0.0 - 2023-07-03 31 | 32 | - Everything, initial release. 33 | -------------------------------------------------------------------------------- /src/Controllers/StoreSecret.php: -------------------------------------------------------------------------------- 1 | getId(); 24 | 25 | $keyName = $panel->isDefault() ? 'default' : $panelId; 26 | 27 | $secret = Config::get("filament-access-secret.keys.$keyName"); 28 | 29 | return to_route("filament.{$panelId}.auth.login") 30 | ->withCookie(AccessSecretCookie::create( 31 | keyName: $keyName, 32 | key: $secret 33 | )); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * *' 8 | 9 | jobs: 10 | build-test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | fail-fast: true 15 | matrix: 16 | php: [8.1, 8.2, 8.3] 17 | laravel: [10] 18 | 19 | name: Test PHP ${{ matrix.php }} with Laravel ${{ matrix.laravel }} 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: Setup PHP 25 | uses: shivammathur/setup-php@v2 26 | with: 27 | php-version: ${{ matrix.php }} 28 | ini-values: error_reporting=E_ALL 29 | tools: composer:v2 30 | coverage: none 31 | 32 | - name: Install Dependencies 33 | run: | 34 | composer update --prefer-dist --no-interaction --no-progress --${{ matrix.stability }} 35 | 36 | - name: Install Chromedriver 37 | run: ./vendor/bin/testbench dusk:chrome-driver --detect 38 | 39 | - name: Run Tests 40 | run: composer run test -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) Dasun Tharanga hello@dasun.dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | **The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.** 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /docs/basic-usage/enable-secret-access.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Enable secret access 3 | weight: 1 4 | --- 5 | 6 | # Enable secret access 7 | 8 | After installing the package, open the .env file and add the following key with your secret key: 9 | 10 | ```dotenv 11 | DEFAULT_FILAMENT_ACCESS_SECRET_KEY=default123 12 | ``` 13 | 14 | To access Filament, append the secret key to the Filament URL like this: 15 | 16 | ``` 17 | https://my-website.com/admin/secret 18 | ``` 19 | 20 | Open the `app/Providers/Filament/AdminPanelProvider.php` and right at the start of the list of middleware, add `VerifyAdminAccessSecret` middleware as follows. 21 | 22 | ```php 23 | use Dasundev\FilamentAccessSecret\Middleware\VerifyAdminAccessSecret; 24 | 25 | class AdminPanelProvider extends PanelProvider 26 | { 27 | public function panel(Panel $panel): Panel 28 | { 29 | return $panel 30 | ... 31 | ->middleware([ 32 | VerifyAdminAccessSecret::class, 33 | // Other middlewares... 34 | ]) 35 | ...; 36 | } 37 | } 38 | ``` 39 | 40 | Now, your Filament access is secured with the provided secret key. 41 | -------------------------------------------------------------------------------- /src/Middleware/VerifyAdminAccessSecret.php: -------------------------------------------------------------------------------- 1 | getId(); 27 | 28 | $keyName = $panel->isDefault() ? 'default' : $panelId; 29 | 30 | $secret = Config::get("filament-access-secret.keys.$keyName"); 31 | 32 | $cookie = $request->cookie("filament_access_secret_$keyName"); 33 | 34 | if ($cookie && AccessSecretCookie::isValid($cookie, $secret) || blank($secret)) { 35 | return $next($request); 36 | } 37 | 38 | abort(404); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Filament Access Secret

2 | 3 |

Filament Access Secret Banner

4 | 5 | Build Status 6 | Total Downloads 7 | Latest Stable Version 8 | License 9 | 10 | This package provides a middleware for securing access to Filament by requiring a secret key to be provided in the URL. 11 | 12 | ## Documentation 13 | 14 | You can find the documentation [here](https://dasun.dev/docs/filament-access-secret), which provides detailed information on installing and using the package. 15 | 16 | ## Changelog 17 | 18 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 19 | 20 | ## Contributing 21 | 22 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 23 | -------------------------------------------------------------------------------- /src/DefaultAccessSecretCookie.php: -------------------------------------------------------------------------------- 1 | addHours(12); 20 | 21 | return new Cookie("filament_access_secret_$keyName", base64_encode(json_encode([ 22 | 'expires_at' => $expiresAt->getTimestamp(), 23 | 'mac' => hash_hmac('sha256', $expiresAt->getTimestamp(), $key), 24 | ])), $expiresAt, config('session.path'), config('session.domain')); 25 | } 26 | 27 | /** 28 | * Determine if the given access secret is valid. 29 | * @param string $cookie 30 | * @param string $key 31 | * @return bool 32 | */ 33 | public static function isValid(string $cookie, string $key): bool 34 | { 35 | $payload = json_decode(base64_decode($cookie), true); 36 | 37 | return is_array($payload) && 38 | is_numeric($payload['expires_at'] ?? null) && 39 | isset($payload['mac']) && 40 | hash_equals(hash_hmac('sha256', $payload['expires_at'], $key), $payload['mac']) && 41 | (int) $payload['expires_at'] >= Carbon::now()->getTimestamp(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Browser/LoginTest.php: -------------------------------------------------------------------------------- 1 | beforeServingApplication(static function ($app, $config) { 18 | $config->set('filament-access-secret.keys.default', null); 19 | }); 20 | 21 | $this->assertNull(config('filament-access-secret.keys.default')); 22 | 23 | $this->browse(function (Browser $browser) { 24 | $browser->visit(new AdminLogin) 25 | ->assertSee('Sign in'); 26 | }); 27 | } 28 | 29 | /** 30 | * @test 31 | */ 32 | public function it_can_not_render_secure_admin_login_page_without_a_secret_key() 33 | { 34 | $this->browse(function (Browser $browser) { 35 | $browser->visit((new AdminLogin)) 36 | ->assertSee('404'); 37 | }); 38 | } 39 | 40 | /** 41 | * @test 42 | */ 43 | public function it_can_render_secure_admin_login_page_with_a_secret_key() 44 | { 45 | $this->browse(function (Browser $browser) { 46 | $browser->visit((new AdminLogin)->withSecretKey('default-secret')) 47 | ->on(new AdminLogin) 48 | ->assertSee('Sign in') 49 | ->assertHasPlainCookie('filament_access_secret_default'); 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/DuskTestCase.php: -------------------------------------------------------------------------------- 1 | name('filament-access-secret') 23 | ->hasConfigFile(); 24 | } 25 | 26 | /** 27 | * Booting the package. 28 | */ 29 | public function bootingPackage(): void 30 | { 31 | $this->registerSingleton(); 32 | 33 | if (! $this->app->runningInConsole()) { 34 | $this->registerRoute(); 35 | } 36 | } 37 | 38 | /** 39 | * Register the route. 40 | */ 41 | private function registerRoute(): void 42 | { 43 | $panels = Filament::getPanels(); 44 | 45 | foreach ($panels as $panel) { 46 | $panelId = $panel->getId(); 47 | 48 | $key = $panel->isDefault() ? 'default' : $panelId; 49 | 50 | $secret = Config::get("filament-access-secret.keys.$key"); 51 | 52 | Route::get('{'.$panelId.'}'."/$secret", StoreSecret::class); 53 | } 54 | } 55 | 56 | /** 57 | * Register the singleton. 58 | */ 59 | public function registerSingleton(): void 60 | { 61 | $this->app->singleton('filament-access-secret', function () { 62 | $cookie = config('filament-access-secret.cookie'); 63 | 64 | if (! class_exists($cookie) || ! class_implements($cookie, AccessSecretCookie::class)) { 65 | throw new InvalidAccessSecretCookieException; 66 | } 67 | 68 | return new $cookie; 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dasundev/filament-access-secret", 3 | "description": "Secures access to Filament by requiring a secret key in the URL.", 4 | "keywords": [ 5 | "dasundev", 6 | "laravel", 7 | "filament", 8 | "access", 9 | "secret", 10 | "login" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Dasun Tharanga", 15 | "email": "hello@dasun.dev" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.0", 20 | "filament/filament": "^3.0", 21 | "laravel/framework": "^10.0" 22 | }, 23 | "require-dev": { 24 | "larastan/larastan": "^2.0", 25 | "laravel/pint": "^1.13", 26 | "orchestra/testbench": "^8.0", 27 | "pestphp/pest": "^2.30.0", 28 | "orchestra/testbench-dusk": "^8.12" 29 | }, 30 | "license": "MIT", 31 | "autoload": { 32 | "psr-4": { 33 | "Dasundev\\FilamentAccessSecret\\": "src/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Dasundev\\FilamentAccessSecret\\Tests\\": "tests", 39 | "Workbench\\App\\": "workbench/app/" 40 | } 41 | }, 42 | "scripts": { 43 | "test": "vendor/bin/pest", 44 | "pint": "vendor/bin/pint --dirty", 45 | "analyse": "vendor/bin/phpstan analyse", 46 | "post-autoload-dump": [ 47 | "@clear", 48 | "@prepare", 49 | "@dusk:install-chromedriver" 50 | ], 51 | "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", 52 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 53 | "build": "@php vendor/bin/testbench workbench:build --ansi", 54 | "dusk:install-chromedriver": "@php vendor/bin/dusk-updater detect --auto-update --ansi", 55 | "serve": [ 56 | "@build", 57 | "@php vendor/bin/testbench serve" 58 | ], 59 | "lint": [ 60 | "@php vendor/bin/pint", 61 | "@php vendor/bin/phpstan analyse" 62 | ] 63 | }, 64 | "config": { 65 | "allow-plugins": { 66 | "pestphp/pest-plugin": true 67 | } 68 | }, 69 | "extra": { 70 | "laravel": { 71 | "providers": [ 72 | "Dasundev\\FilamentAccessSecret\\FilamentAccessSecretServiceProvider" 73 | ] 74 | } 75 | }, 76 | "minimum-stability": "stable", 77 | "prefer-stable": true 78 | } 79 | -------------------------------------------------------------------------------- /workbench/app/Providers/Filament/AppPanelProvider.php: -------------------------------------------------------------------------------- 1 | default() 29 | ->id('app') 30 | ->path('app') 31 | ->login(Login::class) 32 | ->registration() 33 | ->colors([ 34 | 'primary' => Color::Amber, 35 | ]) 36 | ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') 37 | ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') 38 | ->pages([ 39 | Pages\Dashboard::class, 40 | ]) 41 | ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') 42 | ->widgets([ 43 | Widgets\AccountWidget::class, 44 | Widgets\FilamentInfoWidget::class, 45 | ]) 46 | ->middleware([ 47 | VerifyAdminAccessSecret::class, 48 | EncryptCookies::class, 49 | AddQueuedCookiesToResponse::class, 50 | StartSession::class, 51 | AuthenticateSession::class, 52 | ShareErrorsFromSession::class, 53 | VerifyCsrfToken::class, 54 | SubstituteBindings::class, 55 | DisableBladeIconComponents::class, 56 | DispatchServingFilamentEvent::class, 57 | ]) 58 | ->authMiddleware([ 59 | Authenticate::class, 60 | ]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /workbench/app/Providers/Filament/AdminPanelProvider.php: -------------------------------------------------------------------------------- 1 | default() 29 | ->id('admin') 30 | ->path('admin') 31 | ->login(Login::class) 32 | ->registration() 33 | ->colors([ 34 | 'primary' => Color::Amber, 35 | ]) 36 | ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') 37 | ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') 38 | ->pages([ 39 | Pages\Dashboard::class, 40 | ]) 41 | ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') 42 | ->widgets([ 43 | Widgets\AccountWidget::class, 44 | Widgets\FilamentInfoWidget::class, 45 | ]) 46 | ->middleware([ 47 | VerifyAdminAccessSecret::class, 48 | EncryptCookies::class, 49 | AddQueuedCookiesToResponse::class, 50 | StartSession::class, 51 | AuthenticateSession::class, 52 | ShareErrorsFromSession::class, 53 | VerifyCsrfToken::class, 54 | SubstituteBindings::class, 55 | DisableBladeIconComponents::class, 56 | DispatchServingFilamentEvent::class, 57 | ]) 58 | ->authMiddleware([ 59 | Authenticate::class, 60 | ]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /workbench/app/Filament/Pages/Auth/Login.php: -------------------------------------------------------------------------------- 1 | | null 39 | */ 40 | public ?array $data = []; 41 | 42 | public function mount(): void 43 | { 44 | if (Filament::auth()->check()) { 45 | redirect()->intended(Filament::getUrl()); 46 | } 47 | 48 | $this->form->fill(); 49 | } 50 | 51 | public function authenticate(): ?LoginResponse 52 | { 53 | try { 54 | $this->rateLimit(5); 55 | } catch (TooManyRequestsException $exception) { 56 | Notification::make() 57 | ->title(__('filament-panels::pages/auth/login.notifications.throttled.title', [ 58 | 'seconds' => $exception->secondsUntilAvailable, 59 | 'minutes' => ceil($exception->secondsUntilAvailable / 60), 60 | ])) 61 | ->body(array_key_exists('body', __('filament-panels::pages/auth/login.notifications.throttled') ?: []) ? __('filament-panels::pages/auth/login.notifications.throttled.body', [ 62 | 'seconds' => $exception->secondsUntilAvailable, 63 | 'minutes' => ceil($exception->secondsUntilAvailable / 60), 64 | ]) : null) 65 | ->danger() 66 | ->send(); 67 | 68 | return null; 69 | } 70 | 71 | $data = $this->form->getState(); 72 | 73 | if (! Filament::auth()->attempt($this->getCredentialsFromFormData($data), $data['remember'] ?? false)) { 74 | $this->throwFailureValidationException(); 75 | } 76 | 77 | $user = Filament::auth()->user(); 78 | 79 | if ( 80 | ($user instanceof FilamentUser) && 81 | (! $user->canAccessPanel(Filament::getCurrentPanel())) 82 | ) { 83 | Filament::auth()->logout(); 84 | 85 | $this->throwFailureValidationException(); 86 | } 87 | 88 | session()->regenerate(); 89 | 90 | return app(LoginResponse::class); 91 | } 92 | 93 | protected function throwFailureValidationException(): never 94 | { 95 | throw ValidationException::withMessages([ 96 | 'data.email' => __('filament-panels::pages/auth/login.messages.failed'), 97 | ]); 98 | } 99 | 100 | public function form(Form $form): Form 101 | { 102 | return $form; 103 | } 104 | 105 | /** 106 | * @return array 107 | */ 108 | protected function getForms(): array 109 | { 110 | return [ 111 | 'form' => $this->form( 112 | $this->makeForm() 113 | ->schema([ 114 | $this->getEmailFormComponent(), 115 | $this->getPasswordFormComponent(), 116 | ]) 117 | ->statePath('data'), 118 | ), 119 | ]; 120 | } 121 | 122 | protected function getEmailFormComponent(): Component 123 | { 124 | return TextInput::make('email') 125 | ->label(__('filament-panels::pages/auth/login.form.email.label')) 126 | ->email() 127 | ->required() 128 | ->autocomplete() 129 | ->autofocus() 130 | ->extraInputAttributes(['tabindex' => 1]); 131 | } 132 | 133 | protected function getPasswordFormComponent(): Component 134 | { 135 | return TextInput::make('password') 136 | ->label(__('filament-panels::pages/auth/login.form.password.label')) 137 | ->hint(filament()->hasPasswordReset() ? new HtmlString(Blade::render(' {{ __(\'filament-panels::pages/auth/login.actions.request_password_reset.label\') }}')) : null) 138 | ->password() 139 | ->revealable(filament()->arePasswordsRevealable()) 140 | ->autocomplete('current-password') 141 | ->required() 142 | ->extraInputAttributes(['tabindex' => 2]); 143 | } 144 | 145 | protected function getRememberFormComponent(): Component 146 | { 147 | return Checkbox::make('remember') 148 | ->label(__('filament-panels::pages/auth/login.form.remember.label')); 149 | } 150 | 151 | public function registerAction(): Action 152 | { 153 | return Action::make('register') 154 | ->link() 155 | ->label(__('filament-panels::pages/auth/login.actions.register.label')) 156 | ->url(filament()->getRegistrationUrl()); 157 | } 158 | 159 | public function getTitle(): string|Htmlable 160 | { 161 | return __('filament-panels::pages/auth/login.title'); 162 | } 163 | 164 | public function getHeading(): string|Htmlable 165 | { 166 | return __('filament-panels::pages/auth/login.heading'); 167 | } 168 | 169 | /** 170 | * @return array 171 | */ 172 | protected function getFormActions(): array 173 | { 174 | return [ 175 | $this->getAuthenticateFormAction(), 176 | ]; 177 | } 178 | 179 | protected function getAuthenticateFormAction(): Action 180 | { 181 | return Action::make('authenticate') 182 | ->label(__('filament-panels::pages/auth/login.form.actions.authenticate.label')) 183 | ->submit('authenticate'); 184 | } 185 | 186 | protected function hasFullWidthFormActions(): bool 187 | { 188 | return true; 189 | } 190 | 191 | /** 192 | * @param array $data 193 | * @return array 194 | */ 195 | protected function getCredentialsFromFormData(array $data): array 196 | { 197 | return [ 198 | 'email' => $data['email'], 199 | 'password' => $data['password'], 200 | ]; 201 | } 202 | } 203 | --------------------------------------------------------------------------------