├── .php-cs-fixer.php ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── UPGRADE.md ├── bin └── upgrade-v2 ├── composer.json ├── config └── filament-socialite.php ├── database └── migrations │ └── create_socialite_users_table.php.stub ├── phpstan-baseline.laravel-10.neon ├── phpstan.laravel-10.neon.dist ├── phpunit.xml ├── rector.php ├── resources ├── lang │ └── en │ │ └── auth.php └── views │ ├── .gitkeep │ └── components │ └── buttons.blade.php ├── routes └── web.php └── src ├── Events ├── InvalidState.php ├── Login.php ├── Registered.php ├── RegistrationNotEnabled.php ├── SocialiteUserConnected.php └── UserNotAllowed.php ├── Exceptions ├── GuardNotStateful.php ├── ImplementationException.php ├── InvalidCallbackPayload.php └── ProviderNotConfigured.php ├── FilamentSocialitePlugin.php ├── FilamentSocialiteServiceProvider.php ├── Http ├── Controllers │ └── SocialiteLoginController.php └── Middleware │ └── PanelFromUrlQuery.php ├── Models ├── Contracts │ └── FilamentSocialiteUser.php └── SocialiteUser.php ├── Provider.php ├── Traits ├── Callbacks.php ├── CanBeHidden.php ├── Models.php └── Routes.php └── View └── Components └── Buttons.php /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | true, 8 | 'array_syntax' => ['syntax' => 'short'], 9 | 'binary_operator_spaces' => [ 10 | 'default' => 'single_space', 11 | // 'operators' => ['=>' => null], // single space makes code look more coherent in style. But sometimes it is not beter, in that case, manually override. 12 | ], 13 | 'blank_line_after_namespace' => true, 14 | 'blank_line_after_opening_tag' => true, 15 | 'blank_line_before_statement' => [ 16 | 'statements' => ['return'], 17 | ], 18 | 'braces' => true, 19 | 'cast_spaces' => true, 20 | 'class_attributes_separation' => [ 21 | 'elements' => [ 22 | 'const' => 'only_if_meta', 23 | 'method' => 'one', 24 | 'property' => 'one', 25 | 'trait_import' => 'none', 26 | ], 27 | ], 28 | 'class_definition' => [ 29 | 'multi_line_extends_each_single_line' => true, 30 | 'single_item_single_line' => true, 31 | 'single_line' => true, 32 | ], 33 | 'concat_space' => [ 34 | 'spacing' => 'none', 35 | ], 36 | 'constant_case' => ['case' => 'lower'], 37 | 'declare_equal_normalize' => true, 38 | 'elseif' => true, 39 | 'encoding' => true, 40 | 'full_opening_tag' => true, 41 | 'fully_qualified_strict_types' => false, 42 | // added by Shift 43 | 'function_declaration' => true, 44 | 'function_typehint_space' => true, 45 | 'general_phpdoc_tag_rename' => true, 46 | 'heredoc_to_nowdoc' => true, 47 | 'include' => true, 48 | 'increment_style' => ['style' => 'post'], 49 | 'indentation_type' => true, 50 | 'linebreak_after_opening_tag' => true, 51 | 'line_ending' => true, 52 | 'lowercase_cast' => true, 53 | 'lowercase_keywords' => true, 54 | 'lowercase_static_reference' => true, 55 | // added from Symfony 56 | 'magic_method_casing' => true, 57 | // added from Symfony 58 | 'magic_constant_casing' => true, 59 | 'method_argument_space' => [ 60 | 'on_multiline' => 'ignore', 61 | ], 62 | 'multiline_whitespace_before_semicolons' => [ 63 | 'strategy' => 'no_multi_line', 64 | ], 65 | 'native_function_casing' => true, 66 | 'no_alias_functions' => true, 67 | 'no_extra_blank_lines' => [ 68 | 'tokens' => [ 69 | 'extra', 70 | 'throw', 71 | 'use', 72 | 'switch', 73 | 'case', 74 | 'default', 75 | ], 76 | ], 77 | 'no_blank_lines_after_class_opening' => true, 78 | 'no_blank_lines_after_phpdoc' => true, 79 | 'no_closing_tag' => true, 80 | 'no_empty_phpdoc' => true, 81 | 'no_empty_statement' => true, 82 | 'no_leading_import_slash' => true, 83 | 'no_leading_namespace_whitespace' => true, 84 | 'no_mixed_echo_print' => [ 85 | 'use' => 'echo', 86 | ], 87 | 'no_multiline_whitespace_around_double_arrow' => true, 88 | 'no_short_bool_cast' => true, 89 | 'no_singleline_whitespace_before_semicolons' => true, 90 | 'no_spaces_after_function_name' => true, 91 | 'no_spaces_around_offset' => [ 92 | 'positions' => [ 93 | 'inside', 94 | 'outside', 95 | ], 96 | ], 97 | 'no_spaces_inside_parenthesis' => true, 98 | 'no_trailing_comma_in_list_call' => true, 99 | 'no_trailing_comma_in_singleline_array' => true, 100 | 'no_trailing_whitespace' => true, 101 | 'no_trailing_whitespace_in_comment' => true, 102 | 'no_unneeded_control_parentheses' => [ 103 | 'statements' => [ 104 | 'break', 105 | 'clone', 106 | 'continue', 107 | 'echo_print', 108 | 'return', 109 | 'switch_case', 110 | 'yield', 111 | ], 112 | ], 113 | 'no_unreachable_default_argument_value' => true, 114 | 'no_useless_return' => true, 115 | 'no_whitespace_before_comma_in_array' => true, 116 | 'no_whitespace_in_blank_line' => true, 117 | 'normalize_index_brace' => true, 118 | 'not_operator_with_successor_space' => true, 119 | 'object_operator_without_whitespace' => true, 120 | 'ordered_imports' => [ 121 | 'sort_algorithm' => 'alpha', 122 | 'imports_order' => [ 123 | 'class', 124 | 'function', 125 | 'const', 126 | ], 127 | ], 128 | 'psr_autoloading' => true, 129 | 'phpdoc_indent' => true, 130 | 'phpdoc_inline_tag_normalizer' => true, 131 | 'phpdoc_no_access' => true, 132 | 'phpdoc_no_package' => true, 133 | 'phpdoc_no_useless_inheritdoc' => true, 134 | 'phpdoc_scalar' => true, 135 | 'phpdoc_single_line_var_spacing' => true, 136 | 'phpdoc_summary' => false, 137 | 'phpdoc_to_comment' => false, 138 | // override to preserve user preference 139 | 'phpdoc_tag_type' => true, 140 | 'phpdoc_trim' => true, 141 | 'phpdoc_types' => true, 142 | 'phpdoc_var_without_name' => true, 143 | 'self_accessor' => true, 144 | 'short_scalar_cast' => true, 145 | 'simplified_null_return' => false, 146 | // disabled as "risky" 147 | 'single_blank_line_at_eof' => true, 148 | 'single_blank_line_before_namespace' => true, 149 | 'single_class_element_per_statement' => [ 150 | 'elements' => [ 151 | 'const', 152 | 'property', 153 | ], 154 | ], 155 | 'single_import_per_statement' => true, 156 | 'single_line_after_imports' => true, 157 | 'single_line_comment_style' => [ 158 | 'comment_types' => ['hash'], 159 | ], 160 | 'single_quote' => true, 161 | 'space_after_semicolon' => true, 162 | 'standardize_not_equals' => true, 163 | 'switch_case_semicolon_to_colon' => true, 164 | 'switch_case_space' => true, 165 | 'ternary_operator_spaces' => true, 166 | 'trailing_comma_in_multiline' => [ 167 | 'elements' => [ 168 | 'arrays', 169 | 'parameters', 170 | ], 171 | ], 172 | 'trim_array_spaces' => true, 173 | 'types_spaces' => [ 174 | 'space' => 'single', 175 | ], 176 | 'unary_operator_spaces' => true, 177 | 'visibility_required' => [ 178 | 'elements' => [ 179 | 'method', 180 | 'property', 181 | 'const', 182 | ], 183 | ], 184 | 'whitespace_after_comma_in_array' => true, 185 | 186 | // DCC 187 | 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], 188 | 'simplified_if_return' => true, 189 | 'method_chaining_indentation' => true, 190 | ]; 191 | 192 | $finder = Finder::create() 193 | ->in([ 194 | __DIR__ . '/src', 195 | __DIR__ . '/tests', 196 | ]) 197 | ->name('*.php') 198 | ->notName('*.blade.php') 199 | ->ignoreDotFiles(true) 200 | ->ignoreVCS(true); 201 | 202 | return (new Config) 203 | ->setFinder($finder) 204 | ->setRules($rules) 205 | ->setRiskyAllowed(true) 206 | ->setUsingCache(true); 207 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `filament-socialite` will be documented in this file. 4 | 5 | ## [2.4.0 - 2025-02-25](https://github.com/DutchCodingCompany/filament-socialite/compare/2.3.1...2.4.0) 6 | 7 | ## What's Changed 8 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/DutchCodingCompany/filament-socialite/pull/125 9 | 10 | ## [2.3.1 - 2025-02-06](https://github.com/DutchCodingCompany/filament-socialite/compare/2.3.0...2.3.1) 11 | 12 | ## What's Changed 13 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/123 14 | * Add data to events by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/124 15 | 16 | ## [2.3.0 - 2024-11-29](https://github.com/DutchCodingCompany/filament-socialite/compare/2.2.1...2.3.0) 17 | 18 | ## What's Changed 19 | * Add option to hide providers by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/122 20 | * Add support for php 8.4 21 | 22 | ## [2.2.1 - 2024-07-17](https://github.com/DutchCodingCompany/filament-socialite/compare/2.2.0...2.2.1) 23 | 24 | ## What's Changed 25 | * Revert model property changes by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/110 26 | 27 | ## [2.2.0 - 2024-07-15](https://github.com/DutchCodingCompany/filament-socialite/compare/2.1.1...2.2.0) 28 | 29 | ## What's Changed 30 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/106 31 | * Add new callback route for stateless OAuth flows by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/105 32 | 33 | ## [2.1.1 - 2024-06-21](https://github.com/DutchCodingCompany/filament-socialite/compare/2.1.0...2.1.1) 34 | 35 | ## What's Changed 36 | * Improve Socialite driver typings + callable typings by @juliangums in https://github.com/DutchCodingCompany/filament-socialite/pull/103 37 | 38 | ## New Contributors 39 | * @juliangums made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/103 40 | 41 | ## [2.1.0 - 2024-06-21](https://github.com/DutchCodingCompany/filament-socialite/compare/2.0.0...2.1.0) 42 | 43 | * Add Authorization Callback by @petecoop in https://github.com/DutchCodingCompany/filament-socialite/pull/100 44 | 45 | ## [2.0.0 - 2024-06-04](https://github.com/DutchCodingCompany/filament-socialite/compare/1.5.0...2.0.0) 46 | * **Please check the revised [README.md](https://github.com/DutchCodingCompany/filament-socialite/blob/main/README.md) and [UPGRADE.md](https://github.com/DutchCodingCompany/filament-socialite/blob/main/UPGRADE.md)! Many functions have been renamed.** 47 | * Refactor package for better consistency with Filament code standards https://github.com/DutchCodingCompany/filament-socialite/pull/90 48 | 49 | ## [1.5.0 - 2024-06-04](https://github.com/DutchCodingCompany/filament-socialite/compare/1.4.1...1.5.0) 50 | 51 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/89 52 | * Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/91 53 | * Compatible with Stateless Authentication by @LittleHans8 in https://github.com/DutchCodingCompany/filament-socialite/pull/96 54 | 55 | ## [1.4.1 - 2024-03-20](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.4.0...1.4.1) 56 | * Provide oauth user to login event by @dcc-bjorn in https://github.com/DutchCodingCompany/filament-socialite/pull/88 57 | 58 | ## [1.4.0 - 2024-03-12](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.3.1...1.4.0) 59 | * Laravel 11 support by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/87 60 | 61 | ## [1.3.1 - 2024-03-05](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.3.0...1.3.1) 62 | * Add $provider as required by the callback by @phh in https://github.com/DutchCodingCompany/filament-socialite/pull/83 63 | * Never use SPA mode for oauth links + spacing by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/85 64 | 65 | ## [1.3.0 - 2024-03-01](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.2.0...1.3.0) 66 | * Update CHANGELOG.md by @bramr94 in https://github.com/DutchCodingCompany/filament-socialite/pull/70 67 | * Add socialite test by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/78 68 | * Improve actions by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/79 69 | * Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/51 70 | * feature: allow socialite user model customization by @kykurniawan in https://github.com/DutchCodingCompany/filament-socialite/pull/72 71 | * Add registration enabled callable by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/80 72 | * Multi-tenancy support by @bramr94 in https://github.com/DutchCodingCompany/filament-socialite/pull/76 73 | 74 | ## [1.2.0 - 2024-01-31](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.1.1...1.2.0) 75 | - Add option to add optional parameters in https://github.com/DutchCodingCompany/filament-socialite/pull/69 76 | 77 | ## [1.1.1 - 2024-01-18](https://github.com/DutchCodingCompany/filament-socialite/compare/1.1.0...1.1.1) 78 | - Improve domain routing in https://github.com/DutchCodingCompany/filament-socialite/pull/61 79 | - Update README in https://github.com/DutchCodingCompany/filament-socialite/pull/64 80 | 81 | ## [1.1.0 - 2024-01-08](https://github.com/DutchCodingCompany/filament-socialite/compare/1.0.1...1.1.0) 82 | - Add button customization options in https://github.com/DutchCodingCompany/filament-socialite/pull/59 83 | 84 | ## [1.0.1 - 2023-12-18](https://github.com/DutchCodingCompany/filament-socialite/compare/1.0.0...1.0.1) 85 | - Resolve plugin registration issue [#54](https://github.com/DutchCodingCompany/filament-socialite/issues/54) 86 | 87 | ## [1.0.0 - 2023-12-05](https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.2...1.0.0) 88 | - Added support for Filament v3 through the plugin setup 89 | - Added support for multiple panels 90 | - See [UPGRADE.md](UPGRADE.md) 91 | 92 | ## 0.2.2 - 2022-06-14 93 | 94 | ### What's Changed 95 | 96 | - Fix readme by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/15 97 | - use Filament-fortify render hook by @wychoong in https://github.com/DutchCodingCompany/filament-socialite/pull/16 98 | 99 | ### New Contributors 100 | 101 | - @wychoong made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/16 102 | 103 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.1...0.2.2 104 | 105 | ## 0.2.1 - 2022-05-25 106 | 107 | ## What's Changed 108 | 109 | - Fix user model instantiating by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/14 110 | 111 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.0...0.2.1 112 | 113 | ## 0.2.0 - 2022-05-24 114 | 115 | ## Breaking changes 116 | 117 | - `Events\DomainFailed` renamed to `Events\UserNotAllowed` 118 | - `Events\RegistrationFailed` renamed to `Events\RegistrationNotEnabled` 119 | 120 | ## What's Changed 121 | 122 | - Refactor the controller for extendability and customization by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/13 123 | 124 | ## New Contributors 125 | 126 | - @dododedodonl made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/13 127 | 128 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.5...0.2.0 129 | 130 | ## 0.1.5 - 2022-05-20 131 | 132 | ## What's Changed 133 | 134 | - Fix missing variable for registered event by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/11 135 | 136 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.4...0.1.5 137 | 138 | ## 0.1.4 - 2022-05-06 139 | 140 | ## What's Changed 141 | 142 | - Feature: Adds buttons blade component by @oyepez003 in https://github.com/DutchCodingCompany/filament-socialite/pull/8 143 | - Feature: Add login events dispatching by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/5 144 | 145 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.3...0.1.4 146 | 147 | ## 0.1.3 - 2022-05-04 148 | 149 | ## What's Changed 150 | 151 | - Bugfix: Avoid returning 403 when a user exists based on the oauth-email . by @oyepez003 in https://github.com/DutchCodingCompany/filament-socialite/pull/7 152 | 153 | ## New Contributors 154 | 155 | - @oyepez003 made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/7 156 | 157 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.2...0.1.3 158 | 159 | ## 0.1.2 - 2022-05-03 160 | 161 | ## What's Changed 162 | 163 | - Bump dependabot/fetch-metadata from 1.3.0 to 1.3.1 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/2 164 | - Add Laravel 8 support and make fontawesome icons optional by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/4 165 | 166 | ## New Contributors 167 | 168 | - @marcoboers made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/4 169 | 170 | **Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.1...0.1.2 171 | 172 | ## 0.1.1 - 2022-04-11 173 | 174 | ## What's Changed 175 | 176 | - Fix registration flow 177 | 178 | ## 0.1.0 - 2022-04-08 179 | 180 | ### Initial Release 181 | 182 | - Add social login links to login page 183 | - Support Socialite OAuth flow 184 | - Support registration flow 185 | - Support domain allowlist for internal use 186 | - Dark mode support 187 | - Blade Font Awesome brand icons 188 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) DutchCodingCompany 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | # Social login for Filament through Laravel Socialite 7 | 8 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/dutchcodingcompany/filament-socialite.svg?style=flat-square)](https://packagist.org/packages/dutchcodingcompany/filament-socialite) 9 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/dutchcodingcompany/filament-socialite/run-tests?label=tests)](https://github.com/dutchcodingcompany/filament-socialite/actions?query=workflow%3Arun-tests+branch%3Amain) 10 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/dutchcodingcompany/filament-socialite/Check%20&%20fix%20styling?label=code%20style)](https://github.com/dutchcodingcompany/filament-socialite/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 11 | [![Total Downloads](https://img.shields.io/packagist/dt/dutchcodingcompany/filament-socialite.svg?style=flat-square)](https://packagist.org/packages/dutchcodingcompany/filament-socialite) 12 | 13 | Add OAuth2 login through Laravel Socialite to Filament. OAuth1 (eg. Twitter) is not supported at this time. 14 | 15 | ## Installation 16 | 17 | | Filament version | Package version | Readme | 18 | |----------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|--------------------------------------------------------------------------------------| 19 | | [^3.2.44](https://github.com/filamentphp/filament/releases/tag/v3.2.44) (if using [SPA mode](https://filamentphp.com/docs/3.x/panels/configuration#spa-mode)) | 2.x.x | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/main/README.md) | 20 | | [^3.2.44](https://github.com/filamentphp/filament/releases/tag/v3.2.44) (if using [SPA mode](https://filamentphp.com/docs/3.x/panels/configuration#spa-mode)) | ^1.3.1 | | 21 | | 3.x | 1.x.x | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/1.x/README.md) | 22 | | 2.x | 0.x.x | | 23 | 24 | Install the package via composer: 25 | 26 | ```bash 27 | composer require dutchcodingcompany/filament-socialite 28 | ``` 29 | 30 | Publish and migrate the migration file: 31 | 32 | ```bash 33 | php artisan vendor:publish --tag="filament-socialite-migrations" 34 | php artisan migrate 35 | ``` 36 | 37 | Other configuration files include: 38 | ```bash 39 | php artisan vendor:publish --tag="filament-socialite-config" 40 | php artisan vendor:publish --tag="filament-socialite-views" 41 | php artisan vendor:publish --tag="filament-socialite-translations" 42 | ``` 43 | 44 | You need to register the plugin in the Filament panel provider (the default filename is `app/Providers/Filament/AdminPanelProvider.php`). The following options are available: 45 | 46 | ```php 47 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 48 | use DutchCodingCompany\FilamentSocialite\Provider; 49 | use Filament\Support\Colors; 50 | use Laravel\Socialite\Contracts\User as SocialiteUserContract; 51 | use Illuminate\Contracts\Auth\Authenticatable; 52 | 53 | // ... 54 | ->plugin( 55 | FilamentSocialitePlugin::make() 56 | // (required) Add providers corresponding with providers in `config/services.php`. 57 | ->providers([ 58 | // Create a provider 'gitlab' corresponding to the Socialite driver with the same name. 59 | Provider::make('gitlab') 60 | ->label('GitLab') 61 | ->icon('fab-gitlab') 62 | ->color(Color::hex('#2f2a6b')) 63 | ->outlined(false) 64 | ->stateless(false) 65 | ->scopes(['...']) 66 | ->with(['...']), 67 | ]) 68 | // (optional) Override the panel slug to be used in the oauth routes. Defaults to the panel ID. 69 | ->slug('admin') 70 | // (optional) Enable/disable registration of new (socialite-) users. 71 | ->registration(true) 72 | // (optional) Enable/disable registration of new (socialite-) users using a callback. 73 | // In this example, a login flow can only continue if there exists a user (Authenticatable) already. 74 | ->registration(fn (string $provider, SocialiteUserContract $oauthUser, ?Authenticatable $user) => (bool) $user) 75 | // (optional) Change the associated model class. 76 | ->userModelClass(\App\Models\User::class) 77 | // (optional) Change the associated socialite class (see below). 78 | ->socialiteUserModelClass(\App\Models\SocialiteUser::class) 79 | ); 80 | ``` 81 | 82 | This package automatically adds 2 routes per panel to make the OAuth flow possible: a redirector and a callback. When 83 | setting up your **external OAuth app configuration**, enter the following callback URL (in this case for the Filament 84 | panel with ID `admin` and the `github` provider): 85 | ``` 86 | https://example.com/admin/oauth/callback/github 87 | ``` 88 | 89 | A multi-panel callback route is available as well that does not contain the panel ID in the url. Instead, it determines 90 | the panel ID from an encrypted `state` input (`...?state=abcd1234`). This allows you to create a single OAuth 91 | application for multiple Filament panels that use the same callback URL. Note that this only works for _stateful_ OAuth 92 | apps: 93 | 94 | ``` 95 | https://example.com/oauth/callback/github 96 | ``` 97 | 98 | If in doubt, run `php artisan route:list` to see which routes are available to you. 99 | 100 | ### CSRF protection 101 | _(Only applicable to Laravel 10.x users can ignore this section)_ 102 | 103 | If your third-party provider calls the OAuth callback using a `POST` request, you need to add the callback route to the 104 | exception list in your `VerifyCsrfToken` middleware. This can be done by adding the url to the `$except` array: 105 | 106 | ```php 107 | protected $except = [ 108 | '*/oauth/callback/*', 109 | 'oauth/callback/*', 110 | ]; 111 | ```` 112 | 113 | For Laravel 11.x (or newer) users, this exception is automatically added by our service provider. 114 | 115 | See [Socialite Providers](https://socialiteproviders.com/) for additional Socialite providers. 116 | 117 | ### Icons 118 | 119 | You can specify a custom icon for each of your login providers. You can add Font Awesome brand 120 | icons made available through [Blade Font Awesome](https://github.com/owenvoke/blade-fontawesome) by running: 121 | ```bash 122 | composer require owenvoke/blade-fontawesome 123 | ``` 124 | 125 | ### Registration flow 126 | 127 | This package supports account creation for users. However, to support this flow it is important that the `password` 128 | attribute on your `User` model is nullable. For example, by adding the following to your users table migration. 129 | Or you could opt for customizing the user creation, see below. 130 | 131 | ```php 132 | $table->string('password')->nullable(); 133 | ``` 134 | 135 | ### Domain Allow list 136 | 137 | This package supports the option to limit the users that can login with the OAuth login to users of a certain domain. 138 | This can be used to setup SSO for internal use. 139 | 140 | ```php 141 | ->plugin( 142 | FilamentSocialitePlugin::make() 143 | // ... 144 | ->registration(true) 145 | ->domainAllowList(['localhost']) 146 | ); 147 | ``` 148 | 149 | ### Changing how an Authenticatable user is created or retrieved 150 | 151 | You can use the `createUserUsing` and `resolveUserUsing` methods to change how a user is created or retrieved. 152 | 153 | ```php 154 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 155 | use Laravel\Socialite\Contracts\User as SocialiteUserContract; 156 | 157 | ->plugin( 158 | FilamentSocialitePlugin::make() 159 | // ... 160 | ->createUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 161 | // Logic to create a new user. 162 | }) 163 | ->resolveUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 164 | // Logic to retrieve an existing user. 165 | }) 166 | ... 167 | ); 168 | ``` 169 | 170 | ### Change how a Socialite user is created or retrieved 171 | 172 | In your plugin options in your Filament panel, add the following method: 173 | 174 | ```php 175 | // app/Providers/Filament/AdminPanelProvider.php 176 | ->plugins([ 177 | FilamentSocialitePlugin::make() 178 | // ... 179 | ->socialiteUserModelClass(\App\Models\SocialiteUser::class) 180 | ``` 181 | 182 | This class should at the minimum implement the [`FilamentSocialiteUser`](/src/Models/Contracts/FilamentSocialiteUser.php) interface, like so: 183 | 184 | ```php 185 | namespace App\Models; 186 | 187 | use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract; 188 | use Illuminate\Contracts\Auth\Authenticatable; 189 | use Laravel\Socialite\Contracts\User as SocialiteUserContract; 190 | 191 | class SocialiteUser implements FilamentSocialiteUserContract 192 | { 193 | public function getUser(): Authenticatable 194 | { 195 | // 196 | } 197 | 198 | public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self 199 | { 200 | // 201 | } 202 | 203 | public static function createForProvider( 204 | string $provider, 205 | SocialiteUserContract $oauthUser, 206 | Authenticatable $user 207 | ): self { 208 | // 209 | } 210 | } 211 | ``` 212 | 213 | ### Check if the user is authorized to use the application 214 | 215 | You can use the `authorizeUserUsing` method to check if the user is authorized to use the application. **Note:** by [default](/src/Traits/Callbacks.php#L145) this method check if the user's email domain is in the domain allow list. 216 | 217 | ```php 218 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 219 | use Laravel\Socialite\Contracts\User as SocialiteUserContract; 220 | 221 | ->plugin( 222 | FilamentSocialitePlugin::make() 223 | // ... 224 | ->authorizeUserUsing(function (FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser) { 225 | // Logic to authorize the user. 226 | return FilamentSocialitePlugin::checkDomainAllowList($plugin, $oauthUser); 227 | }) 228 | // ... 229 | ); 230 | ``` 231 | 232 | ### Change login redirect 233 | 234 | When your panel has [multi-tenancy](https://filamentphp.com/docs/3.x/panels/tenancy) enabled, after logging in, the user will be redirected to their [default tenant](https://filamentphp.com/docs/3.x/panels/tenancy#setting-the-default-tenant). 235 | If you want to change this behavior, you can call the 'redirectAfterLoginUsing' method on the `FilamentSocialitePlugin`. 236 | 237 | ```php 238 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 239 | use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract; 240 | use DutchCodingCompany\FilamentSocialite\Models\SocialiteUser; 241 | 242 | FilamentSocialitePlugin::make() 243 | ->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) { 244 | // Change the redirect behaviour here. 245 | }); 246 | ``` 247 | 248 | ### Filament Fortify 249 | 250 | This component can also be added while using the [Fortify plugin](https://filamentphp.com/plugins/fortify) plugin. 251 | 252 | ```php 253 | ## in Service Provider file 254 | public function boot() 255 | { 256 | //... 257 | 258 | Filament::registerRenderHook( 259 | 'filament-fortify.login.end', 260 | fn (): string => Blade::render(''), 261 | ); 262 | } 263 | ``` 264 | 265 | ### Filament Breezy 266 | 267 | This component can also be added while using the [Breezy plugin](https://filamentphp.com/plugins/jeffgreco-breezy) plugin. 268 | 269 | You can publish the login page for **Filament Breezy** by running: 270 | 271 | ```bash 272 | php artisan vendor:publish --tag="filament-breezy-views" 273 | ``` 274 | 275 | Which produces a login page at `resources/views/vendor/filament-breezy/login.blade.php`. 276 | 277 | You can then add the following snippet in your form: 278 | 279 | ```html 280 | 281 | ``` 282 | 283 | ## Events 284 | 285 | There are a few events dispatched during the authentication process: 286 | 287 | * `InvalidState(InvalidStateException $exception)`: When trying to retrieve the oauth (socialite) user, an invalid state was encountered 288 | * `Login(FilamentSocialiteUserContract $socialiteUser)`: When a user successfully logs in 289 | * `Registered(string $provider, SocialiteUserContract $oauthUser, FilamentSocialiteUserContract $socialiteUser)`: When a user and socialite user is successfully registered and logged in (when enabled in config) 290 | * `RegistrationNotEnabled(string $provider, SocialiteUserContract $oauthUser, ?Auhthenticatable $user)`: When a user tries to login with an unknown account and registration is not enabled 291 | * `SocialiteUserConnected(string $provider, SocialiteUserContract $oauthUser, FilamentSocialiteUserContract $socialiteUser)`: When a socialite user is created for an existing user 292 | * `UserNotAllowed(SocialiteUserContract $oauthUser)`: When a user tries to login with an email which domain is not on the allowlist 293 | 294 | ## Scopes 295 | 296 | Scopes can be added to the provider on the panel, for example: 297 | 298 | ```php 299 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 300 | use DutchCodingCompany\FilamentSocialite\Provider; 301 | 302 | FilamentSocialitePlugin::make() 303 | ->providers([ 304 | Provider::make('github') 305 | ->label('Github') 306 | ->icon('fab-github') 307 | ->scopes([ 308 | // Add scopes here. 309 | 'read:user', 310 | 'public_repo', 311 | ]), 312 | ]), 313 | ``` 314 | 315 | ## Optional parameters 316 | 317 | You can add [optional parameters](https://laravel.com/docs/10.x/socialite#optional-parameters) to the request by adding a `with` key to the provider on the panel, for example: 318 | 319 | ```php 320 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 321 | use DutchCodingCompany\FilamentSocialite\Provider; 322 | 323 | FilamentSocialitePlugin::make() 324 | ->providers([ 325 | Provider::make('github') 326 | ->label('Github') 327 | ->icon('fab-github') 328 | ->with([ 329 | // Add scopes here. 330 | // Add optional parameters here. 331 | 'hd' => 'example.com', 332 | ]), 333 | ]), 334 | ``` 335 | ## Visibility 336 | 337 | You can set the visibility of a provider, if it is not visible, buttons will not be rendered. All functionality will still be enabled. 338 | 339 | ```php 340 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 341 | use DutchCodingCompany\FilamentSocialite\Provider; 342 | 343 | FilamentSocialitePlugin::make() 344 | ->providers([ 345 | Provider::make('github') 346 | ->visible(fn () => true), 347 | ]), 348 | ``` 349 | 350 | ## Stateless Authentication 351 | You can add `stateless` parameters to the provider configuration in the config/services.php config file, for example: 352 | 353 | ```php 354 | 'apple' => [ 355 | 'client_id' => '...', 356 | 'client_secret' => '...', 357 | 'stateless'=>true, 358 | ] 359 | ``` 360 | 361 | **Note:** you cannot use the `state` parameter, as it is used to determine from which Filament panel the user came from. 362 | 363 | ## Changelog 364 | 365 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 366 | 367 | ## Contributing 368 | 369 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 370 | 371 | ## Security Vulnerabilities 372 | 373 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 374 | 375 | ## Credits 376 | - [All Contributors](../../contributors) 377 | 378 | ## License 379 | 380 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 381 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 2.x | :white_check_mark: | 11 | | 1.x | :warning: (security fixes only) | 12 | | 0.x | :no_entry_sign: | 13 | 14 | ## Reporting a Vulnerability 15 | 16 | If you discover a security vulnerability within this plugin, please email Dutch Coding Company via [server@dutchcodingcompany.com](mailto:server@dutchcodingcompany.com). 17 | All security vulnerabilities will be promptly addressed. 18 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | ## `1.x.x` to `2.x.x` (Filament v3.x) 3 | 4 | For version 2 we refactored most of the plugin to be more consistent with the Filament naming conventions. We've also moved some of the callbacks to the plugin, so they are configurable per panel. 5 | 6 | ### Method names 7 | 8 | Every method name has been changed to be more consistent with the Filament naming conventions. The following changes have been made: 9 | 10 | - `setProviders()` -> `providers()` 11 | - `setSlug()` -> `slug()` 12 | - `setLoginRouteName()` -> `loginRouteName()` 13 | - `setDashboardRouteName()` -> `dashboardRouteName()` 14 | - `setRememberLogin()` -> `rememberLogin()` 15 | - `setRegistrationEnabled()` -> `registration()` 16 | - `getRegistrationEnabled()` -> `getRegistration()` 17 | - `setDomainAllowList()` -> `domainAllowList()` 18 | - `setSocialiteUserModelClass()` -> `socialiteUserModelClass()` 19 | - `setUserModelClass()` -> `userModelClass()` 20 | - `setShowDivider()` -> `showDivider()` 21 | 22 | **Note:** We've included a simple rector script which automatically updates the method names. It checks all panel providers in the `app\Provider\Filament` directory. You can run the script by executing the following command: 23 | 24 | ```bash 25 | vendor/bin/upgrade-v2 26 | ``` 27 | #### Callbacks 28 | 29 | **setCreateUserCallback()** 30 | 31 | The `setCreateUserCallback()` has been renamed to `createUserUsing()`. This function was first registered in the `boot` method of your `AppServiceProvider.php`, but now it should be called on the plugin. 32 | 33 | ```php 34 | FilamentSocialitePlugin::make() 35 | // ... 36 | ->createUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 37 | // Logic to create a new user. 38 | }) 39 | ``` 40 | 41 | **setUserResolver()** 42 | 43 | The `setUserResolver()` has been renamed to `resolveUserUsing()`. This function was first registered in the `boot` method of your `AppServiceProvider.php`, but now it should be called on the plugin. 44 | 45 | ```php 46 | FilamentSocialitePlugin::make() 47 | // ... 48 | ->resolveUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 49 | // Logic to retrieve an existing user. 50 | }) 51 | ``` 52 | 53 | **setLoginRedirectCallback()** 54 | 55 | The `setLoginRedirectCallback()` has been renamed to `redirectAfterLoginUsing()`. This function was first registered in the `boot` method of your `AppServiceProvider.php`, but now it should be called on the plugin. 56 | 57 | ```php 58 | FilamentSocialitePlugin::make() 59 | // ... 60 | ->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) { 61 | // Change the redirect behaviour here. 62 | }) 63 | ``` 64 | 65 | #### Removals 66 | 67 | **getOptionalParameters()** 68 | 69 | This function was used internally only inside the `SocialiteLoginController`. If you haven't extended this controller, you can ignore this change. 70 | 71 | Provider details can now be retrieved using `$plugin->getProvider($provider)->getWith()`. 72 | 73 | **getProviderScopes()** 74 | 75 | This function was used internally only inside the `SocialiteLoginController`. If you haven't extended this controller, you can ignore this change. 76 | 77 | Provider details can now be retrieved using `$plugin->getProvider($provider)->getScopes()`. 78 | 79 | ### Configuration 80 | 81 | **Providers** 82 | 83 | Previously, providers were configured by passing a plain array. In the new setup, they should be created using the `Provider` class. The key should be passed as part of the `make()` function. 84 | 85 | ```php 86 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 87 | use DutchCodingCompany\FilamentSocialite\Provider; 88 | 89 | FilamentSocialitePlugin::make() 90 | ->providers([ 91 | Provider::make('gitlab') 92 | ->label('GitLab') 93 | ->icon('fab-gitlab') 94 | ->color(Color::hex('#2f2a6b')), 95 | ]), 96 | ``` 97 | 98 | **Scopes and Optional parameters** 99 | 100 | Scopes and additional parameters for Socialite providers were previously configured in the `services.php` file, but have now been moved to the `->providers()` method on the Filament plugin. 101 | 102 | ```php 103 | use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; 104 | use DutchCodingCompany\FilamentSocialite\Provider; 105 | 106 | FilamentSocialitePlugin::make() 107 | ->providers([ 108 | Provider::make('gitlab') 109 | // ... 110 | ->scopes([ 111 | // Add scopes here. 112 | 'read:user', 113 | 'public_repo', 114 | ]), 115 | ->with([ 116 | // Add optional parameters here. 117 | 'hd' => 'example.com', 118 | ]), 119 | ]), 120 | ``` 121 | 122 | ## `0.x.x` to `1.x.x` (Filament v3.x) 123 | - Replace/republish the configuration file: 124 | - `sail artisan vendor:publish --provider="DutchCodingCompany\FilamentSocialite\FilamentSocialiteServiceProvider"` 125 | - Update your panel configuration `App\Providers\Filament\YourPanelProvider` to include the plugin: 126 | - Append `->plugins([\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin::make()])` 127 | - Configure any options by chaining functions on the plugin. 128 | 129 | ## `0.x.x` (Filament v2.x) 130 | - Initial version 131 | -------------------------------------------------------------------------------- /bin/upgrade-v2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | [ 15 | \Illuminate\Cookie\Middleware\EncryptCookies::class, 16 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 17 | \Illuminate\Session\Middleware\StartSession::class, 18 | \Illuminate\Session\Middleware\AuthenticateSession::class, 19 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 20 | ], 21 | ]; 22 | -------------------------------------------------------------------------------- /database/migrations/create_socialite_users_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 12 | 13 | $table->foreignId('user_id'); 14 | $table->string('provider'); 15 | $table->string('provider_id'); 16 | 17 | $table->timestamps(); 18 | 19 | $table->unique([ 20 | 'provider', 21 | 'provider_id', 22 | ]); 23 | }); 24 | } 25 | 26 | public function down() 27 | { 28 | Schema::dropIfExists('socialite_users'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /phpstan-baseline.laravel-10.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Class App\\\\Models\\\\User not found\\.$#" 5 | count: 1 6 | path: src/FilamentSocialitePlugin.php 7 | 8 | - 9 | message: "#^Property DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\\:\\:\\$userModelClass \\(class\\-string\\\\) does not accept default value of type string\\.$#" 10 | count: 1 11 | path: src/FilamentSocialitePlugin.php 12 | 13 | - 14 | message: "#^Parameter \\#1 \\$value of method DutchCodingCompany\\\\FilamentSocialite\\\\Http\\\\Controllers\\\\SocialiteLoginController\\:\\:evaluate\\(\\) expects bool\\|\\(callable\\(\\)\\: bool\\), bool\\|\\(Closure\\(string, Laravel\\\\Socialite\\\\Contracts\\\\User, Illuminate\\\\Contracts\\\\Auth\\\\Authenticatable\\|null\\)\\: bool\\) given\\.$#" 15 | count: 1 16 | path: src/Http/Controllers/SocialiteLoginController.php 17 | 18 | - 19 | message: "#^Generic type Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\ in PHPDoc tag @return specifies 2 template types, but class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany supports only 1\\: TRelatedModel$#" 20 | count: 1 21 | path: tests/Fixtures/TestTenantUser.php 22 | 23 | - 24 | message: "#^Method DutchCodingCompany\\\\FilamentSocialite\\\\Tests\\\\Fixtures\\\\TestTenantUser\\:\\:teams\\(\\) should return Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\ but returns Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\.$#" 25 | count: 1 26 | path: tests/Fixtures/TestTenantUser.php 27 | 28 | - 29 | message: "#^Call to an undefined method Mockery\\\\ExpectationInterface\\|Mockery\\\\HigherOrderMessage\\:\\:andReturn\\(\\)\\.$#" 30 | count: 3 31 | path: tests/TestCase.php 32 | 33 | - 34 | message: "#^Parameter \\#1 \\$callback of static method Illuminate\\\\Database\\\\Eloquent\\\\Factories\\\\Factory\\\\:\\:guessFactoryNamesUsing\\(\\) expects callable\\(class\\-string\\\\)\\: class\\-string\\, Closure\\(string\\)\\: non\\-falsy\\-string given\\.$#" 35 | count: 1 36 | path: tests/TestCase.php 37 | -------------------------------------------------------------------------------- /phpstan.laravel-10.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/larastan/larastan/extension.neon 3 | - ./phpstan-baseline.laravel-10.neon 4 | 5 | parameters: 6 | level: 8 7 | 8 | paths: 9 | - config 10 | - database 11 | - src 12 | - tests 13 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests 10 | 11 | 12 | 13 | 14 | src 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | paths([ 10 | 'app/Providers/Filament/', 11 | ]); 12 | 13 | $rectorConfig->ruleWithConfiguration( 14 | \Rector\Renaming\Rector\MethodCall\RenameMethodRector::class, 15 | [ 16 | new \Rector\Renaming\ValueObject\MethodCallRename( 17 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 18 | "setProviders", 19 | "providers", 20 | ), 21 | new \Rector\Renaming\ValueObject\MethodCallRename( 22 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 23 | "setRegistrationEnabled", 24 | "registration", 25 | ), 26 | new \Rector\Renaming\ValueObject\MethodCallRename( 27 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 28 | "setSlug", 29 | "slug", 30 | ), 31 | new \Rector\Renaming\ValueObject\MethodCallRename( 32 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 33 | "setLoginRouteName", 34 | "loginRouteName", 35 | ), 36 | new \Rector\Renaming\ValueObject\MethodCallRename( 37 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 38 | "setDashboardRouteName", 39 | "dashboardRouteName", 40 | ), 41 | new \Rector\Renaming\ValueObject\MethodCallRename( 42 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 43 | "setRememberLogin", 44 | "rememberLogin", 45 | ), 46 | new \Rector\Renaming\ValueObject\MethodCallRename( 47 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 48 | "setSocialiteUserModelClass", 49 | "socialiteUserModelClass", 50 | ), 51 | new \Rector\Renaming\ValueObject\MethodCallRename( 52 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 53 | "setDomainAllowList", 54 | "domainAllowList", 55 | ), 56 | new \Rector\Renaming\ValueObject\MethodCallRename( 57 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 58 | "setUserModelClass", 59 | "userModelClass", 60 | ), 61 | new \Rector\Renaming\ValueObject\MethodCallRename( 62 | "DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin", 63 | "setShowDivider", 64 | "showDivider", 65 | ), 66 | ] 67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'Or log in via', 5 | 6 | 'login-failed' => 'Login failed, please try again.', 7 | 8 | 'user-not-allowed' => 'Your email is not part of a domain that is allowed.', 9 | 10 | 'registration-not-enabled' => 'Registration of a new user is not allowed.', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DutchCodingCompany/filament-socialite/4b2723d36fde670e2c2e5fc7ec1e09bf37c05e09/resources/views/.gitkeep -------------------------------------------------------------------------------- /resources/views/components/buttons.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @if ($messageBag->isNotEmpty()) 3 | @foreach($messageBag->all() as $value) 4 |

{{ __($value) }}

5 | @endforeach 6 | @endif 7 | 8 | @if (count($visibleProviders)) 9 | @if($showDivider) 10 |
11 |
12 |

13 | {{ __('filament-socialite::auth.login-via') }} 14 |

15 |
16 | @endif 17 | 18 |
19 | @foreach($visibleProviders as $key => $provider) 20 | 28 | {{ $provider->getLabel() }} 29 | 30 | @endforeach 31 |
32 | @else 33 | 34 | @endif 35 |
36 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | hasPlugin('filament-socialite')) { 10 | continue; 11 | } 12 | 13 | // Retrieve slug for route name. 14 | $slug = $panel->getPlugin('filament-socialite')->getSlug(); 15 | 16 | $domains = $panel->getDomains(); 17 | 18 | foreach ((empty($domains) ? [null] : $domains) as $domain) { 19 | Filament::currentDomain($domain); 20 | 21 | Route::domain($domain) 22 | ->middleware($panel->getMiddleware()) 23 | ->name("socialite.{$panel->generateRouteName('oauth.redirect')}") 24 | ->get("/$slug/oauth/{provider}", [SocialiteLoginController::class, 'redirectToProvider']); 25 | 26 | Route::domain($domain) 27 | ->match(['get', 'post'], "$slug/oauth/callback/{provider}", [SocialiteLoginController::class, 'processCallback']) 28 | ->middleware([ 29 | ...$panel->getMiddleware(), 30 | ...config('filament-socialite.middleware'), 31 | ]) 32 | ->name("socialite.{$panel->generateRouteName('oauth.callback')}"); 33 | 34 | Filament::currentDomain(null); 35 | } 36 | } 37 | 38 | /** 39 | * @note This route can only distinguish between Filament panels using the `state` input. If you have a stateless OAuth 40 | * implementation, use the "$slug/oauth/callback/{provider}" route instead which has the panel in the URL itself. 41 | */ 42 | Route::match(['get', 'post'], "/oauth/callback/{provider}", [SocialiteLoginController::class, 'processCallback']) 43 | ->middleware([ 44 | PanelFromUrlQuery::class, 45 | ...config('filament-socialite.middleware'), 46 | ]) 47 | ->name('oauth.callback'); 48 | -------------------------------------------------------------------------------- /src/Events/InvalidState.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | protected array $providers = []; 28 | 29 | /** 30 | * @var array 31 | */ 32 | protected array $domainAllowList = []; 33 | 34 | protected bool $rememberLogin = false; 35 | 36 | /** 37 | * @phpstan-var (\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, ?\Illuminate\Contracts\Auth\Authenticatable $user): bool) | bool 38 | */ 39 | protected Closure | bool $registration = false; 40 | 41 | protected ?string $slug = null; 42 | 43 | protected ?string $panelId = null; 44 | 45 | protected bool $showDivider = true; 46 | 47 | public function __construct( 48 | protected Repository $config, 49 | protected Factory $auth, 50 | ) { 51 | // 52 | } 53 | 54 | public static function make(): static 55 | { 56 | return app(static::class); 57 | } 58 | 59 | public static function current(): static 60 | { 61 | if (Filament::getCurrentPanel()?->hasPlugin('filament-socialite')) { 62 | /** @var static $plugin */ 63 | $plugin = Filament::getCurrentPanel()->getPlugin('filament-socialite'); 64 | 65 | return $plugin; 66 | } 67 | 68 | throw new ImplementationException('No current panel found with filament-socialite plugin.'); 69 | } 70 | 71 | public function getId(): string 72 | { 73 | return 'filament-socialite'; 74 | } 75 | 76 | public function register(Panel $panel): void 77 | { 78 | $this->panelId = $panel->getId(); 79 | } 80 | 81 | public function boot(Panel $panel): void 82 | { 83 | // 84 | } 85 | 86 | /** 87 | * @param array $providers 88 | */ 89 | public function providers(array $providers): static 90 | { 91 | // Assign providers as key-value pairs with the provider name as the key. 92 | $this->providers = Arr::mapWithKeys( 93 | $providers, 94 | static fn (Provider $value) => [$value->getName() => $value], 95 | ); 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * @return array 102 | */ 103 | public function getProviders(): array 104 | { 105 | return $this->providers; 106 | } 107 | 108 | public function getProvider(string $provider): Provider 109 | { 110 | if (! $this->isProviderConfigured($provider)) { 111 | throw ProviderNotConfigured::make($provider); 112 | } 113 | 114 | return $this->providers[$provider]; 115 | } 116 | 117 | public function slug(?string $slug): static 118 | { 119 | $this->slug = $slug; 120 | 121 | return $this; 122 | } 123 | 124 | public function getSlug(): string 125 | { 126 | return $this->slug ?? Str::slug($this->getPanelId()); 127 | } 128 | 129 | public function rememberLogin(bool $value): static 130 | { 131 | $this->rememberLogin = $value; 132 | 133 | return $this; 134 | } 135 | 136 | public function getRememberLogin(): bool 137 | { 138 | return $this->rememberLogin; 139 | } 140 | 141 | /** 142 | * @param (\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, ?\Illuminate\Contracts\Auth\Authenticatable $user): bool) | bool $value 143 | * @return $this 144 | */ 145 | public function registration(Closure | bool $value = true): static 146 | { 147 | $this->registration = $value; 148 | 149 | return $this; 150 | } 151 | 152 | /** 153 | * @return (\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, ?\Illuminate\Contracts\Auth\Authenticatable $user): bool) | bool 154 | */ 155 | public function getRegistration(): Closure | bool 156 | { 157 | return $this->registration; 158 | } 159 | 160 | /** 161 | * @param array $values 162 | */ 163 | public function domainAllowList(array $values): static 164 | { 165 | $this->domainAllowList = $values; 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * @return array 172 | */ 173 | public function getDomainAllowList(): array 174 | { 175 | return $this->domainAllowList; 176 | } 177 | 178 | public function isProviderConfigured(string $provider): bool 179 | { 180 | return $this->config->has('services.'.$provider) && isset($this->providers[$provider]); 181 | } 182 | 183 | public function showDivider(bool $divider): static 184 | { 185 | $this->showDivider = $divider; 186 | 187 | return $this; 188 | } 189 | 190 | public function getShowDivider(): bool 191 | { 192 | return $this->showDivider; 193 | } 194 | 195 | public function getPanel(): Panel 196 | { 197 | return Filament::getPanel($this->getPanelId()); 198 | } 199 | 200 | public function getPanelId(): string 201 | { 202 | return $this->panelId ?? throw new ImplementationException('Panel ID not set.'); 203 | } 204 | 205 | public function getGuard(): StatefulGuard 206 | { 207 | $guard = $this->auth->guard( 208 | $guardName = $this->getPanel()->getAuthGuard() 209 | ); 210 | 211 | if ($guard instanceof StatefulGuard) { 212 | return $guard; 213 | } 214 | 215 | throw GuardNotStateful::make($guardName); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/FilamentSocialiteServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('filament-socialite') 19 | ->hasConfigFile() 20 | ->hasTranslations() 21 | ->hasViews() 22 | ->hasRoute('web') 23 | ->hasMigration('create_socialite_users_table'); 24 | } 25 | 26 | public function packageRegistered(): void 27 | { 28 | // 29 | } 30 | 31 | public function packageBooted(): void 32 | { 33 | Blade::componentNamespace('DutchCodingCompany\FilamentSocialite\View\Components', 'filament-socialite'); 34 | Blade::component('buttons', Buttons::class); 35 | 36 | FilamentView::registerRenderHook( 37 | 'panels::auth.login.form.after', 38 | static function (): ?string { 39 | $panel = Filament::getCurrentPanel(); 40 | 41 | if (! $panel?->hasPlugin('filament-socialite')) { 42 | return null; 43 | } 44 | 45 | /** @var \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin */ 46 | $plugin = $panel->getPlugin('filament-socialite'); 47 | 48 | return Blade::render(''); 49 | }, 50 | ); 51 | 52 | if ( 53 | version_compare(app()->version(), '11.0', '>=') 54 | && method_exists(VerifyCsrfToken::class, 'except') 55 | ) { 56 | VerifyCsrfToken::except([ 57 | '*/oauth/callback/*', 58 | 'oauth/callback/*', 59 | ]); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Http/Controllers/SocialiteLoginController.php: -------------------------------------------------------------------------------- 1 | plugin()->isProviderConfigured($provider)) { 29 | throw ProviderNotConfigured::make($provider); 30 | } 31 | 32 | /** @var \Laravel\Socialite\Two\AbstractProvider $driver */ 33 | $driver = Socialite::driver($provider); 34 | 35 | $response = $driver 36 | ->with([ 37 | ...$this->plugin()->getProvider($provider)->getWith(), 38 | 'state' => $state = PanelFromUrlQuery::encrypt($this->plugin()->getPanel()->getId()), 39 | ]) 40 | ->scopes($this->plugin()->getProvider($provider)->getScopes()) 41 | ->redirect(); 42 | 43 | // Set state value to be equal to the encrypted panel id. This value is used to 44 | // retrieve the panel id once the authentication returns to our application, 45 | // and it still prevents CSRF as it is non-guessable value. 46 | session()->put('state', $state); 47 | 48 | return $response; 49 | } 50 | 51 | protected function retrieveOauthUser(string $provider): ?SocialiteUserContract 52 | { 53 | $stateless = $this->plugin()->getProvider($provider)->getStateless(); 54 | 55 | try { 56 | /** @var \Laravel\Socialite\Two\AbstractProvider $driver */ 57 | $driver = Socialite::driver($provider); 58 | 59 | return $stateless 60 | ? $driver->stateless()->user() 61 | : $driver->user(); 62 | } catch (InvalidStateException $e) { 63 | Events\InvalidState::dispatch($e); 64 | } 65 | 66 | return null; 67 | } 68 | 69 | protected function retrieveSocialiteUser(string $provider, SocialiteUserContract $oauthUser): ?FilamentSocialiteUserContract 70 | { 71 | return $this->plugin()->getSocialiteUserModel()::findForProvider($provider, $oauthUser); 72 | } 73 | 74 | protected function redirectToLogin(string $message): RedirectResponse 75 | { 76 | // Add error message to the session, this way we can show an error message on the form. 77 | session()->flash('filament-socialite-login-error', __($message)); 78 | 79 | return redirect()->route($this->plugin()->getLoginRouteName()); 80 | } 81 | 82 | protected function authorizeUser(SocialiteUserContract $oauthUser): bool 83 | { 84 | return app()->call($this->plugin()->getAuthorizeUserUsing(), ['plugin' => $this->plugin(), 'oauthUser' => $oauthUser]); 85 | } 86 | 87 | protected function loginUser(string $provider, FilamentSocialiteUserContract $socialiteUser, SocialiteUserContract $oauthUser): Response 88 | { 89 | // Log the user in 90 | $this->plugin()->getGuard()->login($socialiteUser->getUser(), $this->plugin()->getRememberLogin()); 91 | 92 | // Dispatch the login event 93 | Events\Login::dispatch($socialiteUser, $oauthUser); 94 | 95 | return app()->call($this->plugin()->getRedirectAfterLoginUsing(), ['provider' => $provider, 'socialiteUser' => $socialiteUser, 'plugin' => $this->plugin]); 96 | } 97 | 98 | protected function registerSocialiteUser(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): Response 99 | { 100 | // Create a socialite user 101 | $socialiteUser = $this->plugin()->getSocialiteUserModel()::createForProvider($provider, $oauthUser, $user); 102 | 103 | // Dispatch the socialite user connected event 104 | Events\SocialiteUserConnected::dispatch($provider, $oauthUser, $socialiteUser); 105 | 106 | // Login the user 107 | return $this->loginUser($provider, $socialiteUser, $oauthUser); 108 | } 109 | 110 | protected function registerOauthUser(string $provider, SocialiteUserContract $oauthUser): Response 111 | { 112 | $socialiteUser = DB::transaction(function () use ($provider, $oauthUser) { 113 | // Create a user 114 | $user = app()->call($this->plugin()->getCreateUserUsing(), ['provider' => $provider, 'oauthUser' => $oauthUser, 'plugin' => $this->plugin]); 115 | 116 | // Create a socialite user 117 | return $this->plugin()->getSocialiteUserModel()::createForProvider($provider, $oauthUser, $user); 118 | }); 119 | 120 | // Dispatch the registered event 121 | Events\Registered::dispatch($provider, $oauthUser, $socialiteUser); 122 | 123 | // Login the user 124 | return $this->loginUser($provider, $socialiteUser, $oauthUser); 125 | } 126 | 127 | public function processCallback(string $provider): Response 128 | { 129 | if (! $this->plugin()->isProviderConfigured($provider)) { 130 | throw ProviderNotConfigured::make($provider); 131 | } 132 | 133 | // Try to retrieve existing user 134 | $oauthUser = $this->retrieveOauthUser($provider); 135 | 136 | if (is_null($oauthUser)) { 137 | return $this->redirectToLogin('filament-socialite::auth.login-failed'); 138 | } 139 | 140 | // Verify if the user is authorized. 141 | if (! $this->authorizeUser($oauthUser)) { 142 | Events\UserNotAllowed::dispatch($oauthUser); 143 | 144 | return $this->redirectToLogin('filament-socialite::auth.user-not-allowed'); 145 | } 146 | 147 | // Try to find a socialite user 148 | $socialiteUser = $this->retrieveSocialiteUser($provider, $oauthUser); 149 | if ($socialiteUser) { 150 | return $this->loginUser($provider, $socialiteUser, $oauthUser); 151 | } 152 | 153 | // See if a user already exists, but not for this socialite provider 154 | $user = app()->call($this->plugin()->getResolveUserUsing(), [ 155 | 'provider' => $provider, 156 | 'oauthUser' => $oauthUser, 157 | 'plugin' => $this->plugin, 158 | ]); 159 | 160 | // See if registration is allowed 161 | if (! $this->evaluate($this->plugin()->getRegistration(), ['provider' => $provider, 'oauthUser' => $oauthUser, 'user' => $user])) { 162 | Events\RegistrationNotEnabled::dispatch($provider, $oauthUser, $user); 163 | 164 | return $this->redirectToLogin('filament-socialite::auth.registration-not-enabled'); 165 | } 166 | 167 | // Handle registration 168 | return $user 169 | ? $this->registerSocialiteUser($provider, $oauthUser, $user) 170 | : $this->registerOauthUser($provider, $oauthUser); 171 | } 172 | 173 | protected function plugin(): FilamentSocialitePlugin 174 | { 175 | return $this->plugin ??= FilamentSocialitePlugin::current(); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/Http/Middleware/PanelFromUrlQuery.php: -------------------------------------------------------------------------------- 1 | handle($request, $next, static::decrypt($request)); 23 | } 24 | 25 | public static function encrypt(string $panel): string 26 | { 27 | return Crypt::encrypt($panel); 28 | } 29 | 30 | /** 31 | * @throws InvalidCallbackPayload 32 | */ 33 | public static function decrypt(Request $request): string 34 | { 35 | try { 36 | if (! is_string($request->get('state'))) { 37 | throw new DecryptException('State is not a string.'); 38 | } 39 | 40 | return Crypt::decrypt($request->get('state')); 41 | } catch (DecryptException $e) { 42 | throw InvalidCallbackPayload::make($e); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Models/Contracts/FilamentSocialiteUser.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | public function user(): BelongsTo 29 | { 30 | /** @var class-string<\Illuminate\Database\Eloquent\Model&\Illuminate\Contracts\Auth\Authenticatable> */ 31 | $user = FilamentSocialitePlugin::current()->getUserModelClass(); 32 | 33 | return $this->belongsTo($user); 34 | } 35 | 36 | public function getUser(): Authenticatable 37 | { 38 | assert($this->user instanceof Authenticatable); 39 | 40 | return $this->user; 41 | } 42 | 43 | public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self 44 | { 45 | return self::query() 46 | ->where('provider', $provider) 47 | ->where('provider_id', $oauthUser->getId()) 48 | ->first(); 49 | } 50 | 51 | public static function createForProvider(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): self 52 | { 53 | return self::query() 54 | ->create([ 55 | 'user_id' => $user->getKey(), 56 | 'provider' => $provider, 57 | 'provider_id' => $oauthUser->getId(), 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Provider.php: -------------------------------------------------------------------------------- 1 | | array 31 | */ 32 | protected Closure | array $scopes = []; 33 | 34 | /** 35 | * @var \Closure(): array | array 36 | */ 37 | protected Closure | array $with = []; 38 | 39 | protected bool $stateless = false; 40 | 41 | public function __construct(string $name) 42 | { 43 | $this->name($name); 44 | } 45 | 46 | public static function make(string $name): static 47 | { 48 | return app(static::class, ['name' => $name]); 49 | } 50 | 51 | /** 52 | * @param array $attributes 53 | */ 54 | public function fill(array $attributes): static 55 | { 56 | foreach ($attributes as $key => $value) { 57 | $this->{$key}($value); 58 | } 59 | 60 | return $this; 61 | } 62 | 63 | public function name(string $name): static 64 | { 65 | $this->name = $name; 66 | 67 | return $this; 68 | } 69 | 70 | public function getName(): string 71 | { 72 | return $this->name; 73 | } 74 | 75 | public function label(string $label): static 76 | { 77 | $this->label = $label; 78 | 79 | return $this; 80 | } 81 | 82 | public function getLabel(): string 83 | { 84 | return $this->label ?? Str::title($this->getName()); 85 | } 86 | 87 | public function icon(string | null $icon): static 88 | { 89 | $this->icon = $icon; 90 | 91 | return $this; 92 | } 93 | 94 | public function getIcon(): string | null 95 | { 96 | return $this->icon; 97 | } 98 | 99 | /** 100 | * @param string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | null $color 101 | */ 102 | public function color(string | array | null $color): static 103 | { 104 | $this->color = $color; 105 | 106 | return $this; 107 | } 108 | 109 | /** 110 | * @return string | array{50: string, 100: string, 200: string, 300: string, 400: string, 500: string, 600: string, 700: string, 800: string, 900: string, 950: string} | null 111 | */ 112 | public function getColor(): string | array | null 113 | { 114 | return $this->color; 115 | } 116 | 117 | public function outlined(bool $outlined = true): static 118 | { 119 | $this->outlined = $outlined; 120 | 121 | return $this; 122 | } 123 | 124 | public function getOutlined(): bool 125 | { 126 | return $this->outlined; 127 | } 128 | 129 | /** 130 | * @param Closure(): array | array $scopes 131 | */ 132 | public function scopes(Closure | array $scopes): static 133 | { 134 | $this->scopes = $scopes; 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * @return array 141 | */ 142 | public function getScopes(): array 143 | { 144 | return $this->evaluate($this->scopes, ['provider' => $this]); 145 | } 146 | 147 | /** 148 | * @param Closure(): array | array $with 149 | */ 150 | public function with(Closure | array $with): static 151 | { 152 | $this->with = $with; 153 | 154 | return $this; 155 | } 156 | 157 | /** 158 | * @return array 159 | */ 160 | public function getWith(): array 161 | { 162 | return $this->evaluate($this->with, ['provider' => $this]); 163 | } 164 | 165 | public function stateless(bool $stateless = true): static 166 | { 167 | $this->stateless = $stateless; 168 | 169 | return $this; 170 | } 171 | 172 | public function getStateless(): bool 173 | { 174 | return $this->stateless; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Traits/Callbacks.php: -------------------------------------------------------------------------------- 1 | createUserUsing = $callback; 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * @return \Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Illuminate\Contracts\Auth\Authenticatable 47 | */ 48 | public function getCreateUserUsing(): Closure 49 | { 50 | return $this->createUserUsing ?? function ( 51 | string $provider, 52 | SocialiteUserContract $oauthUser, 53 | FilamentSocialitePlugin $plugin, 54 | ) { 55 | /** 56 | * @var \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model&\Illuminate\Contracts\Auth\Authenticatable> $query 57 | */ 58 | $query = (new $this->userModelClass())->query(); 59 | 60 | return $query->create([ 61 | 'name' => $oauthUser->getName(), 62 | 'email' => $oauthUser->getEmail(), 63 | ]); 64 | }; 65 | } 66 | 67 | /** 68 | * @param \Closure(string $provider, \DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser $socialiteUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Illuminate\Http\RedirectResponse $callback 69 | */ 70 | public function redirectAfterLoginUsing(Closure $callback): static 71 | { 72 | $this->redirectAfterLoginUsing = $callback; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * @return \Closure(string $provider, \DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser $socialiteUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Symfony\Component\HttpFoundation\Response 79 | */ 80 | public function getRedirectAfterLoginUsing(): Closure 81 | { 82 | return $this->redirectAfterLoginUsing ?? function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) { 83 | if (($panel = $this->getPanel())->hasTenancy()) { 84 | $tenant = Filament::getUserDefaultTenant($socialiteUser->getUser()); 85 | 86 | if (is_null($tenant) && $tenantRegistrationUrl = $panel->getTenantRegistrationUrl()) { 87 | return redirect()->intended($tenantRegistrationUrl); 88 | } 89 | 90 | return redirect()->intended( 91 | $panel->getUrl($tenant) 92 | ); 93 | } 94 | 95 | return redirect()->intended( 96 | $this->getPanel()->getUrl() 97 | ); 98 | }; 99 | } 100 | 101 | /** 102 | * @param ?\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): ?(\Illuminate\Contracts\Auth\Authenticatable) $callback 103 | */ 104 | public function resolveUserUsing(?Closure $callback = null): static 105 | { 106 | $this->resolveUserUsing = $callback; 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * @return \Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): ?(\Illuminate\Contracts\Auth\Authenticatable) 113 | */ 114 | public function getResolveUserUsing(): Closure 115 | { 116 | return $this->resolveUserUsing ?? function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) { 117 | /** @var \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model&\Illuminate\Contracts\Auth\Authenticatable> $model */ 118 | $model = (new $this->userModelClass()); 119 | 120 | return $model->where( 121 | 'email', 122 | $oauthUser->getEmail() 123 | )->first(); 124 | }; 125 | } 126 | 127 | /** 128 | * @param ?\Closure(\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin, \Laravel\Socialite\Contracts\User $oauthUser): bool $callback 129 | */ 130 | public function authorizeUserUsing(?Closure $callback = null): static 131 | { 132 | $this->authorizeUserUsing = $callback; 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * @return \Closure(\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin, \Laravel\Socialite\Contracts\User $oauthUser): bool 139 | */ 140 | public function getAuthorizeUserUsing(): Closure 141 | { 142 | return $this->authorizeUserUsing ?? static::checkDomainAllowList(...); 143 | } 144 | 145 | public static function checkDomainAllowList(FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser): bool 146 | { 147 | $domains = $plugin->getDomainAllowList(); 148 | 149 | // When no domains are specified, all users are allowed 150 | if (count($domains) < 1) { 151 | return true; 152 | } 153 | 154 | // Get the domain of the email for the specified user 155 | $emailDomain = Str::of($oauthUser->getEmail() ?? throw new ImplementationException('User email is required.')) 156 | ->afterLast('@') 157 | ->lower() 158 | ->__toString(); 159 | 160 | // See if everything after @ is in the domains array 161 | return in_array($emailDomain, $domains); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/Traits/CanBeHidden.php: -------------------------------------------------------------------------------- 1 | isHidden = $condition; 16 | 17 | return $this; 18 | } 19 | 20 | public function visible(bool | Closure $condition = true): static 21 | { 22 | $this->isVisible = $condition; 23 | 24 | return $this; 25 | } 26 | 27 | public function isHidden(): bool 28 | { 29 | if ($this->evaluate($this->isHidden)) { 30 | return true; 31 | } 32 | 33 | return ! $this->evaluate($this->isVisible); 34 | } 35 | 36 | public function isVisible(): bool 37 | { 38 | return ! $this->isHidden(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Traits/Models.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | protected string $userModelClass = User::class; 17 | 18 | /** 19 | * @var class-string<\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser> 20 | */ 21 | protected string $socialiteUserModelClass = SocialiteUser::class; 22 | 23 | /** 24 | * @param class-string<\Illuminate\Contracts\Auth\Authenticatable> $value 25 | * @throws ImplementationException 26 | */ 27 | public function userModelClass(string $value): static 28 | { 29 | if (! is_a($value, Authenticatable::class, true)) { 30 | throw new ImplementationException('The user model class must implement the "\Illuminate\Contracts\Auth\Authenticatable" interface.'); 31 | } 32 | 33 | $this->userModelClass = $value; 34 | 35 | return $this; 36 | } 37 | 38 | /** 39 | * @return class-string<\Illuminate\Contracts\Auth\Authenticatable> 40 | */ 41 | public function getUserModelClass(): string 42 | { 43 | return $this->userModelClass; 44 | } 45 | 46 | /** 47 | * @param class-string<\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser> $value 48 | */ 49 | public function socialiteUserModelClass(string $value): static 50 | { 51 | if (! is_a($value, FilamentSocialiteUserContract::class, true)) { 52 | throw new ImplementationException('The socialite user model class must implement the "\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser" interface.'); 53 | } 54 | 55 | $this->socialiteUserModelClass = $value; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @return class-string<\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser> 62 | */ 63 | public function getSocialiteUserModelClass(): string 64 | { 65 | return $this->socialiteUserModelClass; 66 | } 67 | 68 | public function getSocialiteUserModel(): FilamentSocialiteUserContract 69 | { 70 | return new ($this->getSocialiteUserModelClass()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Traits/Routes.php: -------------------------------------------------------------------------------- 1 | getPanel()->generateRouteName('oauth.redirect')}"; 14 | } 15 | 16 | public function loginRouteName(string $value): static 17 | { 18 | $this->loginRouteName = $value; 19 | 20 | return $this; 21 | } 22 | 23 | public function getLoginRouteName(): string 24 | { 25 | return $this->loginRouteName ?? $this->getPanel()->generateRouteName('auth.login'); 26 | } 27 | 28 | public function dashboardRouteName(string $value): static 29 | { 30 | $this->dashboardRouteName = $value; 31 | 32 | return $this; 33 | } 34 | 35 | public function getDashboardRouteName(): string 36 | { 37 | return $this->dashboardRouteName ?? $this->getPanel()->generateRouteName('pages.dashboard'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/View/Components/Buttons.php: -------------------------------------------------------------------------------- 1 | plugin = FilamentSocialitePlugin::current(); 18 | } 19 | 20 | /** 21 | * @inheritDoc 22 | */ 23 | public function render() 24 | { 25 | $messageBag = new MessageBag(); 26 | 27 | if (session()->has('filament-socialite-login-error')) { 28 | $messageBag->add('login-failed', session()->pull('filament-socialite-login-error')); 29 | } 30 | 31 | return view('filament-socialite::components.buttons', [ 32 | 'providers' => $providers = $this->plugin->getProviders(), 33 | 'visibleProviders' => array_filter($providers, fn (Provider $provider) => $provider->isVisible()), 34 | 'socialiteRoute' => $this->plugin->getRoute(), 35 | 'messageBag' => $messageBag, 36 | ]); 37 | } 38 | } 39 | --------------------------------------------------------------------------------