├── 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 | 
4 |
5 |
6 |
7 |
8 |
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 |
--------------------------------------------------------------------------------