├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── socialment.php ├── database ├── factories │ └── ModelFactory.php └── migrations │ ├── create_connected_accounts_table.php.stub │ └── modify_users_table_nullable_password.php.stub ├── postcss.config.cjs ├── resources ├── css │ └── index.css └── views │ ├── login-error.blade.php │ └── providers-list.blade.php ├── routes ├── spa.php └── web.php ├── src ├── Exceptions │ └── AbortedLoginException.php ├── Facades │ └── Socialment.php ├── Http │ ├── Controllers │ │ ├── BaseController.php │ │ ├── CsrfCookieController.php │ │ ├── SocialmentController.php │ │ └── SpaAuthController.php │ ├── Middleware │ │ └── SpaAuthentication.php │ ├── Requests │ │ └── SpaLoginRequest.php │ └── Resources │ │ └── UserResponse.php ├── Models │ └── ConnectedAccount.php ├── Socialment.php ├── SocialmentPlugin.php ├── SocialmentServiceProvider.php ├── Testing │ └── TestsSocialment.php └── Traits │ └── HasConnectedAccounts.php └── stubs └── .gitkeep /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `socialment` will be documented in this file. 4 | 5 | > [!NOTE] 6 | Due to an issue in the workflow that generates this changelog, the first two entries were manually added. 7 | 8 | ## v3.10.1 - 2025-04-21 9 | 10 | ### What's Changed 11 | 12 | * build(deps): bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/chrisreedio/socialment/pull/64 13 | * build(deps): bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/chrisreedio/socialment/pull/71 14 | * build(deps): bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot in https://github.com/chrisreedio/socialment/pull/72 15 | * remove illuminate/contracts to support any laravel version by @atmonshi in https://github.com/chrisreedio/socialment/pull/74 16 | 17 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.9.1...v3.10.1 18 | 19 | ## v3.9.1 - 2024-07-02 20 | 21 | ### What's Changed 22 | 23 | * 🐛 fix: initialized $createUserClosure to null in `SocialmentPlugin.php` by @chrisreedio in https://github.com/chrisreedio/socialment/pull/63 24 | 25 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.9.0...v3.9.1 26 | 27 | Thanks to @Cybrarist for finding the bug and suggesting a fix in #62 28 | 29 | ## v3.9.0 - 2024-06-18 30 | 31 | ### What's Changed 32 | 33 | * ✨ feat: added custom user creation logic in `SocialmentPlugin` by @chrisreedio in https://github.com/chrisreedio/socialment/pull/59 34 | * 🛠️ fix(controllers): added check for null user in `SocialmentController` by @chrisreedio in https://github.com/chrisreedio/socialment/pull/60 35 | 36 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.8.0...v3.9.0 37 | 38 | ## v3.8.0 - 2024-05-04 39 | 40 | ### What's Changed 41 | 42 | * ✨ feat: Added support for custom scopes in social providers by @chrisreedio in https://github.com/chrisreedio/socialment/pull/58 43 | 44 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.7.0...v3.8.0 45 | 46 | ## v3.7.0 - 2024-05-03 47 | 48 | ### What's Changed 49 | 50 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 by @dependabot in https://github.com/chrisreedio/socialment/pull/52 51 | * hot fix: add missing import by @atmonshi in https://github.com/chrisreedio/socialment/pull/51 52 | * Bump aglipanci/laravel-pint-action from 2.3.1 to 2.4 by @dependabot in https://github.com/chrisreedio/socialment/pull/54 53 | * Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 by @dependabot in https://github.com/chrisreedio/socialment/pull/56 54 | * add `getProviders` by @atmonshi in https://github.com/chrisreedio/socialment/pull/55 55 | * (fix): typehint by @pepperfm in https://github.com/chrisreedio/socialment/pull/42 56 | 57 | ### New Contributors 58 | 59 | * @pepperfm made their first contribution in https://github.com/chrisreedio/socialment/pull/42 60 | 61 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.6.1...v3.7.0 62 | 63 | ## v3.6.1 - 2024-03-20 64 | 65 | ### What's Changed 66 | 67 | * 🔧 fix(icons): Exchanged FA Pro icon for a free version by @chrisreedio in https://github.com/chrisreedio/socialment/pull/49 68 | 69 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.6.0...v3.6.1 70 | 71 | ## v3.6.0 - 2024-03-15 72 | 73 | ### What's Changed 74 | 75 | * Allow for custom 'me' SPA response by @chrisreedio in https://github.com/chrisreedio/socialment/pull/47 76 | * Narrowed test matrix for CI runs by @chrisreedio in https://github.com/chrisreedio/socialment/pull/46 77 | 78 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.5.0...v3.6.0 79 | 80 | ## v3.5.0 - 2024-03-12 81 | 82 | ### SPA Support 83 | 84 | This feature is still experimental and a work in progress. Use at your own risk. 85 | 86 | ### Configuration Deprecations 87 | 88 | Support for configuring providers via the configuration file has been deprecated in favor of the configuring the providers via the panel provider. 89 | 90 | Configuring options at the panel level allows for far better multi-panel support among many other improvements. 91 | 92 | ### Laravel 11 Support 93 | 94 | This package should now work with Laravel 11 projects! 🚀 95 | 96 | ### What's Changed 97 | 98 | * Save Previous Panel URL for after Login by @chrisreedio in https://github.com/chrisreedio/socialment/pull/34 99 | * Bump aglipanci/laravel-pint-action from 2.3.0 to 2.3.1 by @dependabot in https://github.com/chrisreedio/socialment/pull/39 100 | * Bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/chrisreedio/socialment/pull/41 101 | * SPA Documentation Improvements by @chrisreedio in https://github.com/chrisreedio/socialment/pull/44 102 | * Laravel 11 Support by @chrisreedio in https://github.com/chrisreedio/socialment/pull/45 103 | 104 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.4.1...v3.5.0 105 | 106 | ## v3.4.1 - 2023-11-16 107 | 108 | ### What's Changed 109 | 110 | - Fixes Test Workflow - Updated Minimum Dependency Versions by @chrisreedio in https://github.com/chrisreedio/socialment/pull/32 111 | - hot fix for github provider for nullable names by @atmonshi in https://github.com/chrisreedio/socialment/pull/35 112 | 113 | ### New Contributors 114 | 115 | - @atmonshi made their first contribution in https://github.com/chrisreedio/socialment/pull/35 116 | 117 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.4.0...v3.4.1 118 | 119 | ## v3.4.0 - 2023-11-02 120 | 121 | ### What's Changed 122 | 123 | - Per-Panel Configuration of Providers by @chrisreedio in https://github.com/chrisreedio/socialment/pull/31 124 | 125 | This feature is still highly experimental and the signature is highly likely to change (become more standardized). 126 | 127 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.3.0...v3.4.0 128 | 129 | ## v3.3.0 - 2023-11-02 130 | 131 | ### What's Changed 132 | 133 | - Multiple Pre/Post Login Hooks/Callbacks by @chrisreedio in https://github.com/chrisreedio/socialment/pull/29 134 | - Update README to include pre-login hooks by @chrisreedio in https://github.com/chrisreedio/socialment/pull/30 135 | 136 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.2.1...v3.3.0 137 | 138 | ## v3.2.1 - 2023-11-02 139 | 140 | ### What's Changed 141 | 142 | - Fixed assigning the `preLogin` hook to the `postLoginCallback`. 143 | - Migration to allow nullable passwords for users by @chrisreedio in https://github.com/chrisreedio/socialment/pull/28 144 | 145 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.2.0...v3.2.1 146 | 147 | ## v3.2.0 - 2023-10-31 148 | 149 | **Socialment is still considered beta but should no longer require the dev stability composer setting** 150 | 151 | ### What's Changed 152 | 153 | - Redirects InvalidStateExceptions to Login Route by @chrisreedio in https://github.com/chrisreedio/socialment/pull/22 154 | - Prelogin Hook and Aborted Login Exceptions by @chrisreedio in https://github.com/chrisreedio/socialment/pull/23 155 | 156 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/3.1.1-beta...v3.2.0 157 | 158 | ## 3.1.1-beta - 2023-10-25 159 | 160 | ### What's Changed 161 | 162 | - Update Connected Account Details on Login by @chrisreedio in https://github.com/chrisreedio/socialment/pull/21 163 | 164 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.1.0-beta...3.1.1-beta 165 | 166 | ## v3.1.0-beta - Post Login Callback - 2023-10-16 167 | 168 | ### What's Changed 169 | 170 | - Fixing changelog generation by @chrisreedio in https://github.com/chrisreedio/socialment/pull/15 171 | - Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/chrisreedio/socialment/pull/16 172 | - Login Callback by @chrisreedio in https://github.com/chrisreedio/socialment/pull/20 173 | 174 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.0.1-beta...v3.1.0-beta 175 | 176 | ## 3.0.1-beta - 2023-09-26 177 | 178 | ### What's Changed 179 | 180 | - **Handling existing user accounts** by @chrisreedio in https://github.com/chrisreedio/socialment/pull/14 181 | - Fix README badge by @chrisreedio in https://github.com/chrisreedio/socialment/pull/9 182 | - Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/chrisreedio/socialment/pull/10 183 | - Added sample provider configuration to README by @chrisreedio in https://github.com/chrisreedio/socialment/pull/12 184 | - Formatting by @chrisreedio in https://github.com/chrisreedio/socialment/pull/13 185 | 186 | ### New Contributors 187 | 188 | - @dependabot made their first contribution in https://github.com/chrisreedio/socialment/pull/10 189 | 190 | **Full Changelog**: https://github.com/chrisreedio/socialment/compare/v3.0.0-beta...v3.0.1-beta 191 | 192 | ## 3.0.0-beta - 2023-09-05 193 | 194 | > [!WARNING] 195 | This package is still in ***BETA***. 196 | It has only been tested at length with the Azure AD provider. 197 | Please report issues if you find them and I'm open to PRs! 198 | 199 | ### What's Changed 200 | 201 | - Package is working in Demo Project by @chrisreedio in https://github.com/chrisreedio/socialment/pull/1 202 | - Update README.md by @chrisreedio in https://github.com/chrisreedio/socialment/pull/2 203 | - Adding package description. by @chrisreedio in https://github.com/chrisreedio/socialment/pull/3 204 | - README updates and config formatting. by @chrisreedio in https://github.com/chrisreedio/socialment/pull/4 205 | - README updates by @chrisreedio in https://github.com/chrisreedio/socialment/pull/5 206 | - PHPStan passing by @chrisreedio in https://github.com/chrisreedio/socialment/pull/6 207 | - Added gap when multiple providers in use. by @chrisreedio in https://github.com/chrisreedio/socialment/pull/7 208 | - Added warning to readme about the current beta state. by @chrisreedio in https://github.com/chrisreedio/socialment/pull/8 209 | 210 | ### New Contributors 211 | 212 | - @chrisreedio made their first contribution in https://github.com/chrisreedio/socialment/pull/1 213 | 214 | **Full Changelog**: https://github.com/chrisreedio/socialment/commits/v3.0.0-beta 215 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) chrisreedio 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 | # Socialment - Socialite OAuth Support for Filament 2 | 3 | ![Socialment](https://github.com/chrisreedio/socialment/assets/77644584/53dd1b45-d775-4335-a7ec-ae18456bcab4) 4 | 5 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/chrisreedio/socialment.svg?style=flat-square)](https://packagist.org/packages/chrisreedio/socialment) 6 | ![Tests Action Status](https://github.com/chrisreedio/socialment/actions/workflows/run-tests.yml/badge.svg) 7 | ![Code Style Action Status](https://github.com/chrisreedio/socialment/actions/workflows/fix-php-code-styling.yml/badge.svg) 8 | ![PHPStan Action Status](https://github.com/chrisreedio/socialment/actions/workflows/phpstan.yml/badge.svg) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/chrisreedio/socialment.svg?style=flat-square)](https://packagist.org/packages/chrisreedio/socialment) 10 | 11 | ## About 12 | 13 | Bring up-to-date and simple Socialite support to your Filament admin panel with this plugin. Adds OAuth buttons to your 14 | login page. 15 | 16 | Ideal for Laravel and Filament users seeking a straightforward OAuth integration. 17 | 18 | > [!WARNING] 19 | > Socialment is currently in beta. Please report any issues you encounter. 20 | > 21 | > Caution is advised if you choose to use this package in production. 22 | > 23 | > Azure AD support has been the only tested provider so far. 24 | 25 | #### References 26 | 27 | This package extends [Laravel Socialite](https://laravel.com/docs/master/socialite). Socialite currently supports 28 | authentication via Facebook, Twitter, LinkedIn, Google, GitHub, GitLab, and Bitbucket out of the box. 29 | 30 | Refer to the [Socialite documentation](https://laravel.com/docs/master/socialite) for more information on how to 31 | configure your application to use these providers. 32 | 33 | Many other providers are available via the [Socialite Providers](https://socialiteproviders.com/) website. Refer to the 34 | documentation for each provider for information on how to configure your application to use them. 35 | 36 | ### Demo 37 | 38 | For an example usage of this package, see [ChrisReedIO/Socialment-Demo](https://github.com/chrisreedio/socialment-demo). 39 | 40 | ![image](https://github.com/chrisreedio/socialment/assets/77644584/c07c6518-df0b-4143-8826-efa3cbdaa681) 41 | 42 | --- 43 | 44 | ## Installation 45 | 46 | You can install the package via composer: 47 | 48 | ```bash 49 | composer require chrisreedio/socialment 50 | ``` 51 | 52 | ## Usage 53 | 54 | #### Initial Setup 55 | 56 | You can easily perform the initial setup by running the following command: 57 | 58 | ```bash 59 | php artisan socialment:install 60 | ``` 61 | 62 | Additionally, edit your panel's `tailwind.config.js` content section to include the last line of the following: 63 | 64 | ```js 65 | content: [ 66 | "./app/Filament/**/*.php", 67 | "./resources/views/filament/**/*.blade.php", 68 | "./vendor/filament/**/*.blade.php", 69 | // ... Other Content Paths 70 | 71 | // Ensure the line below is listed!!! 72 | "./vendor/chrisreedio/socialment/resources/**/*.blade.php", 73 | ], 74 | ``` 75 | 76 | If this step is forgotten, the styling of the plugin will not be applied. 77 | 78 | Please continue to the next sections to continue the setup process. 79 | 80 | ### Panel Configuration 81 | 82 | Include this plugin in your panel configuration: 83 | 84 | ```php 85 | $panel 86 | ->plugins([ 87 | // ... Other Plugins 88 | \ChrisReedIO\Socialment\SocialmentPlugin::make(), 89 | ]) 90 | ``` 91 | 92 | #### Provider Configuration 93 | 94 | > [!IMPORTANT] 95 | > At this point, you'll need to configure your application to use the provider(s) you want to support. 96 | > 97 | > Either configure the needed stock socialite providers 98 | > or [community maintained providers](https://socialiteproviders.com/). 99 | > 100 | > Refer to the [Socialite documentation](https://laravel.com/docs/master/socialite) for more information. 101 | > 102 | > This will usually involve installing a package and configuring your application's `config/services.php` file. 103 | 104 | ##### Socialment Configuration 105 | 106 | ###### Provider Configuration 107 | 108 | > [!WARNING] 109 | > This method of provider configuration is now deprecated and will be removed in a future release. 110 | > 111 | > Configuring providers in your [panel configuration](#per-panel-provider-configuration) has many advantages and is the recommended method. 112 | 113 | Whether you're using the default providers or adding your own, you'll need to configure them in the `socialment.php` 114 | config file. 115 | 116 | Configure the `socialment.php` config file to specify providers in the following format: 117 | 118 | ```php 119 | return [ 120 | 'providers' => [ 121 | 'azure' => [ 122 | 'icon' => 'fab-microsoft', // Font Awesome Brand Icon 123 | 'label' => 'Azure', // Display Name on the Login Page 124 | ] 125 | ], 126 | // ... Other Configuration Parameters 127 | ]; 128 | ``` 129 | 130 | Providers specified in the config file are global across all panels. 131 | 132 | ##### Per-Panel Provider Configuration 133 | 134 | You should specify providers on a per-panel basis. To do this use the `->registerProvider` method on the plugin. 135 | 136 | ```php 137 | $panel->plugins([ 138 | \ChrisReedIO\Socialment\SocialmentPlugin::make() 139 | ->registerProvider('azure', 'fab-microsoft', 'Azure Active Directory'), 140 | ]); 141 | ``` 142 | 143 | ##### Sample Provider Configuration - Azure Active Directory 144 | 145 | > [!IMPORTANT] 146 | > For this configured Azure provider, the redirect URI would be `https://DOMAIN/login/azure/redirect` 147 | > 148 | > The callback URI would be `https://DOMAIN/login/azure/callback` 149 | 150 | For example, the sample provider included in the stock `socialment.php` config is Azure Active Directory. 151 | To start, You would refer to the documentation for 152 | the [Azure Socialite Provider](https://socialiteproviders.com/Microsoft-Azure/). 153 | 154 | Normally, you would follow the providers documentation on the aforementioned link but to demostrate the process for 155 | Socialment, I'll include the steps here. 156 | 157 | Per their documentation, you would install the community Azure provider via 158 | 159 | ```bash 160 | composer require socialiteproviders/microsoft-azure 161 | ``` 162 | 163 | Then you would configure your `config/services.php` file to include the Azure provider's credentials: 164 | 165 | ```php 166 | 'azure' => [ 167 | 'client_id' => env('AZURE_CLIENT_ID'), 168 | 'client_secret' => env('AZURE_CLIENT_SECRET'), 169 | 'redirect' => env('AZURE_REDIRECT_URI'), 170 | 'tenant' => env('AZURE_TENANT_ID'), 171 | 'proxy' => env('PROXY') // optionally 172 | ], 173 | ``` 174 | 175 | In addition, you need to add this provider's event listener to your `app/Providers/EventServiceProvider.php` file: 176 | 177 | ```php 178 | protected $listen = [ 179 | // ... other listeners 180 | 181 | \SocialiteProviders\Manager\SocialiteWasCalled::class => [ 182 | // ... other providers 183 | \SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle', 184 | ], 185 | ]; 186 | ``` 187 | 188 | Finally, don't forget to add the needed environment variables to your `.env` file: 189 | 190 | ```dotenv 191 | AZURE_CLIENT_ID= 192 | AZURE_CLIENT_SECRET= 193 | AZURE_REDIRECT_URI= 194 | AZURE_TENANT_ID= 195 | ``` 196 | 197 | The `usage` section can usually be ignored as that is the main part this package should handle for you. 198 | 199 | > [!NOTE] 200 | > It is in the plans to improve the handling of the sign in process to align more with Socialstream in allowing you to 201 | > specify an `action` class or closure to handle the sign in process. 202 | > 203 | > This will allow for customized handling on a per provider, per application basis. 204 | 205 | This package also uses the [Blade Font Awesome package](https://github.com/owenvoke/blade-fontawesome) 206 | by [Owen Voke](https://github.com/owenvoke). 207 | 208 | Search for brand icons on the [Font Awesome Website](https://fontawesome.com/search?o=r&f=brands). 209 | 210 | ### Visibility Override 211 | 212 | By default, the plugin displays the configured providers at the bottom of the login form. 213 | You can additionally override the visibility of the plugin by passing a boolean or closure to the `visible` method: 214 | 215 | ```php 216 | $panel->plugins([ 217 | \ChrisReedIO\Socialment\SocialmentPlugin::make() 218 | ->visible(fn () => false) 219 | ]); 220 | ``` 221 | 222 | ### Extras 223 | 224 | You may publish and customize the views using 225 | 226 | ```bash 227 | php artisan vendor:publish --tag="socialment-views" 228 | 229 | ``` 230 | 231 | #### Login Callbacks 232 | 233 | You may configure pre/post login hooks/callbacks by adding code similar to the following to the `boot` method of a 234 | service provider: 235 | 236 | ```php 237 | use ChrisReedIO\Socialment\Models\ConnectedAccount; 238 | 239 | public function boot(): void 240 | { 241 | // Post Login Hook 242 | Socialment::preLogin(function (ConnectedAccount $connectedAccount) { 243 | // Handle custom pre login logic here. 244 | }); 245 | 246 | // Multiple hooks can be added 247 | Socialment::preLogin(function (ConnectedAccount $connectedAccount) { 248 | // Handle additional custom pre login logic here if you need. 249 | }); 250 | 251 | // Post Login Hook 252 | Socialment::postLogin(function (ConnectedAccount $connectedAccount) { 253 | // Handle custom post login logic here. 254 | Log::info('User logged in with ' . $connectedAccount->provider . ' account', [ 255 | 'connectedAccount' => $connectedAccount, 256 | ]); 257 | }); 258 | } 259 | ``` 260 | 261 | The user relation can be accessed via `$connectedAccount->user`. 262 | 263 | The `ConnectedAccount` is passed instead of the `User` so that you can easily know which social account was used for the 264 | login. 265 | 266 | #### Login Route for failed logins 267 | 268 | If a login fails or encounters a InvalidStateException, the user will be redirected to the configured `loginRoute` 269 | route. 270 | 271 | This defaults to `filament.admin.auth.login` but can be overriden on the plugin declaration in your panel provider 272 | configuration: 273 | 274 | ```php 275 | $panel->plugins([ 276 | \ChrisReedIO\Socialment\SocialmentPlugin::make() 277 | ->loginRoute('filament.staff.auth.login') 278 | ]); 279 | ``` 280 | 281 | You may also use a closure here to dynamically set the route: 282 | 283 | ```php 284 | $panel->plugins([ 285 | \ChrisReedIO\Socialment\SocialmentPlugin::make() 286 | ->loginRoute(fn () => SomeFunctionToGetTheRouteName()) 287 | ]); 288 | ``` 289 | 290 | ### Config 291 | 292 | This is the contents of the published config file: 293 | 294 | ```php 295 | return [ 296 | 'view' => [ 297 | // Set the text above the provider list 298 | 'prompt' => 'Or Login Via', 299 | // Or change out the view completely with your own 300 | 'providers-list' => 'socialment::providers-list', 301 | ], 302 | 303 | // DEPRECATED: This will be removed in a future version. 304 | // Configure routes via the panel provider. 305 | 'routes' => [ 306 | 'home' => 'filament.admin.pages.dashboard', 307 | ], 308 | 309 | 'models' => [ 310 | // If you want to use a custom user model, you can specify it here. 311 | 'user' => \App\Models\User::class, 312 | ], 313 | 314 | // DEPRECATED: This will be removed in a future version. 315 | // Configure providers via the panel provider. 316 | 'providers' => [ 317 | 'azure' => [ 318 | 'icon' => 'fab-microsoft', 319 | 'label' => 'Azure Active Directory', 320 | ] 321 | ], 322 | ]; 323 | ``` 324 | 325 | ## Frontend SPA Authentication 326 | 327 | > [!CAUTION] 328 | > This feature is still in development and thus highly experimental. 329 | > 330 | > Expect breaking changes and bugs. Use at your own risk. 331 | > 332 | > The documentation will be updated as the feature is finalized. 333 | > 334 | > This feature may be spun off into a separate package in the future. 335 | 336 | This package includes support for authenticating with a Single Page Application (SPA) frontend. Both the Filament backend and SPA frontend must be hosted on the same domain. 337 | 338 | The login session is shared so logging into either the SPA or the backend will log you into both. 339 | 340 | Special CORS and session settings are required to make this work and caution must be taken to ensure that proper access controls (Policies / Panel Access / Etc) are in place. 341 | 342 | ### Setup 343 | 344 | You'll need to add the new `spaAuth` routes to your `routes/web` file. 345 | 346 | ```php 347 | // In this example, we pass 'dashboard' as the SPA route name. 348 | // We'll want to make sure the 'prefix' our custom routes match. 349 | // If no prefix is set/passed to spaAuth, the default is 'spa'. 350 | 351 | Route::spaAuth('dashboard'); 352 | 353 | Route::middleware('auth:sanctum') 354 | ->prefix('dashboard') 355 | ->as('dashboard.') 356 | ->group(function () { 357 | // Custom Routes 358 | }); 359 | ``` 360 | 361 | ### Configuration Changes 362 | 363 | You'll need to modify the `config/cors.php` file. 364 | 365 | You'll need to add the following to the `paths` array: 366 | 367 | ```php 368 | 'paths' => [ 369 | // ... Other Paths 370 | 'spa/*', // OR use the custom prefix you set in the routes/web file. 371 | ], 372 | ``` 373 | 374 | Also ensure that the `supports_credentials` is set to `true`: 375 | 376 | ```php 377 | 'supports_credentials' => true, 378 | ``` 379 | 380 | ### Environment Variables 381 | 382 | We need to set a few ENV variables to ensure that the SPA authentication works properly. 383 | 384 | ```dotenv 385 | SANCTUM_STATEFUL_DOMAINS="https://frontend.localhost:3000,https://backend.localhost" 386 | SESSION_DOMAIN=".localhost" 387 | SESSION_SECURE_COOKIE=true 388 | SPA_URL="https://frontend.localhost:3000" 389 | ``` 390 | 391 | The `SESSION_DOMAIN` should be the shared domain between your SPA and your backend. It should begin with a period. 392 | 393 | The `SPA_URL` is the URL of your SPA application. 394 | 395 | > [!NOTE] 396 | > Ths SPA functionality is a work in progress and is subject to change. 397 | > 398 | > This documentation section will be updated as the feature is finalized. 399 | 400 | ## Testing 401 | 402 | > [!NOTE] 403 | > Tests have yet to be written for this package. They are on my TODO list. I'm also open to PRs. 404 | 405 | ```bash 406 | composer test 407 | ``` 408 | 409 | ## Changelog 410 | 411 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 412 | 413 | ## Contributing 414 | 415 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 416 | 417 | ## Security Vulnerabilities 418 | 419 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 420 | 421 | ## Credits 422 | 423 | - [Chris Reed](https://github.com/chrisreedio) 424 | - [All Contributors](../../contributors) 425 | 426 | ## License 427 | 428 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 429 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrisreedio/socialment", 3 | "description": "Provides Socialite functionality for Filament.", 4 | "keywords": [ 5 | "chrisreedio", 6 | "laravel", 7 | "filament", 8 | "socialment", 9 | "authentication", 10 | "socialite" 11 | ], 12 | "homepage": "https://github.com/chrisreedio/socialment", 13 | "support": { 14 | "issues": "https://github.com/chrisreedio/socialment/issues", 15 | "source": "https://github.com/chrisreedio/socialment" 16 | }, 17 | "license": "MIT", 18 | "authors": [ 19 | { 20 | "name": "Chris Reed", 21 | "email": "chris@reedtech.us", 22 | "role": "Developer" 23 | } 24 | ], 25 | "require": { 26 | "php": "^8.1", 27 | "filament/filament": "^3.0", 28 | "laravel/socialite": "^5.8", 29 | "owenvoke/blade-fontawesome": "^2.4", 30 | "spatie/laravel-package-tools": "^1.15.0" 31 | }, 32 | "require-dev": { 33 | "barryvdh/laravel-ide-helper": "^2.13|^3.0", 34 | "larastan/larastan": "^2.9", 35 | "laravel/pint": "^1.13", 36 | "nunomaduro/collision": "^7.10|^8.0", 37 | "orchestra/testbench": "^v8.14|^9.0", 38 | "pestphp/pest": "^2.10", 39 | "pestphp/pest-plugin-arch": "^2.0", 40 | "pestphp/pest-plugin-laravel": "^2.0", 41 | "phpstan/extension-installer": "^1.1", 42 | "phpstan/phpstan-deprecation-rules": "^1.0", 43 | "phpstan/phpstan-phpunit": "^1.3", 44 | "socialiteproviders/manager": "^4.4" 45 | }, 46 | "autoload": { 47 | "psr-4": { 48 | "ChrisReedIO\\Socialment\\": "src/", 49 | "ChrisReedIO\\Socialment\\Database\\Factories\\": "database/factories/" 50 | } 51 | }, 52 | "autoload-dev": { 53 | "psr-4": { 54 | "ChrisReedIO\\Socialment\\Tests\\": "tests/" 55 | } 56 | }, 57 | "scripts": { 58 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", 59 | "analyse": "vendor/bin/phpstan analyse", 60 | "test": "vendor/bin/pest", 61 | "test-coverage": "vendor/bin/pest --coverage", 62 | "format": "vendor/bin/pint" 63 | }, 64 | "config": { 65 | "sort-packages": true, 66 | "allow-plugins": { 67 | "pestphp/pest-plugin": true, 68 | "phpstan/extension-installer": true 69 | } 70 | }, 71 | "extra": { 72 | "laravel": { 73 | "providers": [ 74 | "ChrisReedIO\\Socialment\\SocialmentServiceProvider" 75 | ], 76 | "aliases": { 77 | "Socialment": "ChrisReedIO\\Socialment\\Facades\\Socialment" 78 | } 79 | } 80 | }, 81 | "minimum-stability": "dev", 82 | "prefer-stable": true 83 | } 84 | -------------------------------------------------------------------------------- /config/socialment.php: -------------------------------------------------------------------------------- 1 | [ 13 | // Use the key based on the provider's documentation 14 | // 'azure' => [ 15 | // 'icon' => 'fab-microsoft', // Font Awesome icon class 16 | // 'label' => 'Azure Active Directory', // The label to display for the provider 17 | // ] 18 | ], 19 | 20 | 'view' => [ 21 | // Set the text above the provider list 22 | 'prompt' => 'Or Login Via', 23 | // Or change out the view completely with your own 24 | 'providers-list' => 'socialment::providers-list', 25 | ], 26 | 27 | // DEPRECATED: This will be removed in a future version. 28 | // Configure routes via the panel provider. 29 | 'routes' => [ 30 | 'home' => 'filament.admin.pages.dashboard', 31 | ], 32 | 33 | 'spa' => [ 34 | // The URL to redirect to after a successful login 35 | 'home' => env('SPA_URL', 'http://localhost:3000'), 36 | 'responses' => [ 37 | // Replace with your own JsonResource class if you want to customize the response 38 | // 'me' => \ChrisReedIO\Socialment\Http\Resources\UserResponse::class, 39 | ], 40 | ], 41 | 42 | 'models' => [ 43 | // If you want to use a custom user model, you can specify it here. 44 | 'user' => '\App\Models\User', 45 | ], 46 | 47 | ]; 48 | -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | id(); 13 | 14 | $table->foreignId('user_id')->constrained()->cascadeOnDelete(); 15 | $table->string('provider'); 16 | $table->string('provider_user_id'); 17 | $table->string('name')->nullable(); 18 | $table->string('nickname')->nullable(); 19 | $table->string('email')->nullable(); 20 | $table->string('phone')->nullable(); 21 | $table->text('avatar')->nullable(); 22 | $table->text('token'); 23 | $table->text('refresh_token')->nullable(); 24 | $table->timestamp('expires_at')->nullable(); 25 | 26 | $table->timestamps(); 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/modify_users_table_nullable_password.php.stub: -------------------------------------------------------------------------------- 1 | string('password')->nullable()->change(); 14 | }); 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "postcss-import": {}, 4 | "tailwindcss/nesting": {}, 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /resources/css/index.css: -------------------------------------------------------------------------------- 1 | @import '../../vendor/filament/filament/resources/css/theme.css'; 2 | -------------------------------------------------------------------------------- /resources/views/login-error.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{-- --}} 7 | 8 |
9 |
10 |

Login Failed

11 |
12 | {{ $message }} 13 |
14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /resources/views/providers-list.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $panelId = $panel->getId(); 3 | $panelPath = $panel->getPath(); 4 | @endphp 5 |
6 |
7 |
8 | 9 | {{ config('socialment.view.prompt', 'Or Login Via') }} 10 | 11 |
12 |
13 |
14 | @foreach ($providers as $providerName => $provider) 15 | $panelId, 'provider' => $providerName]) }}'> 17 | 18 | {{ $provider['label'] }} 19 | 20 | @endforeach 21 |
22 |
23 | -------------------------------------------------------------------------------- /routes/spa.php: -------------------------------------------------------------------------------- 1 | name('csrf-cookie'); 10 | // Non-Social User Login 11 | Route::post('login', [SpaAuthController::class, 'login'])->name('login'); 12 | // Redirect out to OAuth Provider 13 | Route::get('login/{provider}', [SocialmentController::class, 'redirectSpa']) 14 | ->name('redirect'); 15 | // Authenticated Routes 16 | Route::middleware(['auth:sanctum'])->group(function () { 17 | Route::post('logout', [SpaAuthController::class, 'logout'])->name('logout'); 18 | Route::get('me', [SpaAuthController::class, 'me'])->name('me'); 19 | }); 20 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | group(function () { 7 | // OAuth Callback from Provider 8 | Route::get('/login/{provider}/callback', [SocialmentController::class, 'callback']) 9 | ->name('socialment.callback'); 10 | 11 | // Multi-Panel Redirect 12 | Route::get('/login/{provider}/panel/{panelId}', [SocialmentController::class, 'redirectPanel']) 13 | ->name('socialment.redirect.panel'); 14 | 15 | // Single-Panel Routes and panels with a path of empty string (root level panels) 16 | Route::get('/login/{provider}', [SocialmentController::class, 'redirect']) 17 | ->name('socialment.redirect'); 18 | }); 19 | -------------------------------------------------------------------------------- /src/Exceptions/AbortedLoginException.php: -------------------------------------------------------------------------------- 1 | json([ 19 | 'message' => 'Not yet implemented', 20 | ], 501); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Http/Controllers/CsrfCookieController.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 19 | return new JsonResponse(null, 204); 20 | } 21 | 22 | return new Response('', 204); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Http/Controllers/SocialmentController.php: -------------------------------------------------------------------------------- 1 | getProviderRedirect($provider); 31 | } 32 | 33 | public function redirectSpa(string $provider): RedirectResponse 34 | { 35 | // Store the referring url in the session 36 | request()->session()->put('socialment.intended.url', request()->headers->get('referer')); 37 | 38 | return $this->getProviderRedirect($provider); 39 | } 40 | 41 | public function redirectPanel(string $provider, string $panelId): RedirectResponse 42 | { 43 | $referer = request()->headers->get('referer'); 44 | if (! request()->session()->exists('socialment.intended.url')) { 45 | request()->session()->put('socialment.intended.url', $referer); 46 | } 47 | 48 | return $this->getProviderRedirect($provider); 49 | } 50 | 51 | private function getProviderRedirect(string $providerName): \Illuminate\Http\RedirectResponse 52 | { 53 | /** @var AbstractProvider $provider */ 54 | $provider = Socialite::driver($providerName); 55 | $providerConfig = App::make(SocialmentPlugin::class)->getProvider($providerName); 56 | if (! empty($providerConfig['scopes'])) { 57 | $provider->scopes($providerConfig['scopes']); 58 | } 59 | 60 | return $provider->redirect(); 61 | } 62 | 63 | public function callback(string $provider): RedirectResponse 64 | { 65 | try { 66 | /** @var \SocialiteProviders\Manager\OAuth2\User $socialUser */ 67 | $socialUser = Socialite::driver($provider)->user(); 68 | 69 | $tokenExpiration = match ($provider) { 70 | 'azure' => now()->addSeconds($socialUser->expiresIn), 71 | default => null, 72 | }; 73 | 74 | // Create a user or log them in... 75 | $connectedAccount = ConnectedAccount::firstOrNew([ 76 | 'provider' => $provider, 77 | 'provider_user_id' => $socialUser->getId(), 78 | ], [ 79 | 'name' => $socialUser->getName(), 80 | 'nickname' => $socialUser->getNickname(), 81 | 'email' => $socialUser->getEmail(), 82 | 'avatar' => $socialUser->getAvatar(), 83 | 'token' => $socialUser->token, 84 | 'refresh_token' => $socialUser->refreshToken, 85 | 'expires_at' => $tokenExpiration, 86 | ]); 87 | 88 | if (! $connectedAccount->exists) { 89 | // Check for an existing user with this email 90 | // Create a new user if one doesn't exist 91 | $user = Socialment::createUser($connectedAccount); 92 | 93 | if ($user === null) { 94 | throw new AbortedLoginException('This account is not authorized to log in.'); 95 | } 96 | 97 | // Associate the user and save this connected account 98 | $connectedAccount->user()->associate($user)->save(); 99 | } else { 100 | // Update the connected account with the latest data 101 | $connectedAccount->update([ 102 | 'name' => $socialUser->getName(), 103 | 'nickname' => $socialUser->getNickname(), 104 | 'email' => $socialUser->getEmail(), 105 | 'avatar' => $socialUser->getAvatar(), 106 | 'token' => $socialUser->token, 107 | 'refresh_token' => $socialUser->refreshToken, 108 | 'expires_at' => $tokenExpiration, 109 | ]); 110 | } 111 | 112 | Socialment::executePreLogin($connectedAccount); 113 | 114 | Auth::login($connectedAccount->user); 115 | 116 | Socialment::executePostLogin($connectedAccount); 117 | } catch (InvalidStateException $e) { 118 | Session::flash('socialment.error', 'Something went wrong. Please try again.'); 119 | } catch (\GuzzleHttp\Exception\ClientException $e) { 120 | Session::flash('socialment.error', 'We had a problem contacting the authentication server. Please try again.'); 121 | } catch (AbortedLoginException $e) { 122 | Session::flash('socialment.error', $e->getMessage()); 123 | } catch (Exception $e) { 124 | Session::flash('socialment.error', 'An unknown error occurred: ' . $e->getMessage() . '. Please try again.'); 125 | } 126 | 127 | return redirect()->to($this->getRedirectUrl()); 128 | } 129 | 130 | private function getRedirectUrl(): string 131 | { 132 | return request()->session()->pull('socialment.intended.url') ?? Filament::getLoginUrl(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Http/Controllers/SpaAuthController.php: -------------------------------------------------------------------------------- 1 | validated()); 18 | 19 | // If the user is not found or the password is invalid, return an error 20 | if (! $authSuccess) { 21 | return response()->json(['message' => 'Invalid credentials'], 401); 22 | } 23 | 24 | // Get the 'now logged in' user 25 | $user = Auth::user(); 26 | 27 | // Cookie Auth 28 | return UserResponse::make($user); 29 | 30 | // Token Auth 31 | // Send the token back as a response 32 | // return UserResponse::make($user) 33 | // ->additional([ 34 | // 'token' => $user->createToken('auth_token')->plainTextToken, 35 | // ]); 36 | } 37 | 38 | public function logout(Request $request) 39 | { 40 | // Revoke the token that was used to authenticate the current request... 41 | // Auth::user()->currentAccessToken()->delete(); 42 | // Log the user out of the application... 43 | Auth::guard('web')->logout(); 44 | 45 | // Invalidate the session token to prevent reuse 46 | $request->session()->invalidate(); 47 | 48 | // Regenerate the session token to prevent session fixation attacks 49 | $request->session()->regenerateToken(); 50 | 51 | // Respond with the logged out message 52 | return response()->json(['message' => 'Logged out']); 53 | } 54 | 55 | public function me() 56 | { 57 | $resourceClass = config('socialment.spa.responses.me', UserResponse::class); 58 | 59 | return new $resourceClass(Auth::user()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Http/Middleware/SpaAuthentication.php: -------------------------------------------------------------------------------- 1 | check()) { 22 | return response()->json(['message' => 'Not logged in'], 401); 23 | } 24 | 25 | // If we get here, the user is logged in 26 | return $next($request); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Http/Requests/SpaLoginRequest.php: -------------------------------------------------------------------------------- 1 | 'required|email', 13 | 'password' => 'required|string', 14 | ]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Http/Resources/UserResponse.php: -------------------------------------------------------------------------------- 1 | $this->id, 22 | /* @phpstan-ignore-next-line */ 23 | 'name' => $this->name, 24 | /* @phpstan-ignore-next-line */ 25 | 'email' => $this->email, 26 | // 'avatar' => $this->avatar, 27 | // 'email_verified_at' => $this->email_verified_at, 28 | // 'created_at' => $this->created_at, 29 | // 'updated_at' => $this->updated_at, 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Models/ConnectedAccount.php: -------------------------------------------------------------------------------- 1 | belongsTo(config('socialment.models.user')); 38 | } 39 | 40 | public function scopeProvider(Builder $query, string $provider): Builder 41 | { 42 | return $query->where('provider', $provider); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Socialment.php: -------------------------------------------------------------------------------- 1 | */ 24 | public array $preLoginCallbacks = []; 25 | 26 | public static array $globalPreLoginCallbacks = []; 27 | 28 | /** @var array */ 29 | public array $postLoginCallbacks = []; 30 | 31 | public static array $globalPostLoginCallbacks = []; 32 | 33 | public ?Closure $createUserClosure = null; 34 | 35 | protected string | Closure | null $loginRoute = null; 36 | 37 | protected string | Closure | null $homeRoute = null; 38 | 39 | protected array $providers = []; 40 | 41 | protected ?bool $multiPanel = null; 42 | 43 | public Panel $panel; 44 | 45 | public function getId(): string 46 | { 47 | return 'socialment'; 48 | } 49 | 50 | public function getProviders(): array 51 | { 52 | return array_merge(config('socialment.providers'), $this->providers); 53 | } 54 | 55 | public function getProvider(string $provider): array 56 | { 57 | return $this->getProviders()[$provider]; 58 | } 59 | 60 | public function register(Panel $panel): void 61 | { 62 | $panel->renderHook('panels::auth.login.form.before', function () { 63 | $errorMessage = Session::get('socialment.error'); 64 | 65 | if (! $this->evaluate($this->visible) || ! $errorMessage) { 66 | return ''; 67 | } 68 | 69 | return View::make( 70 | config('socialment.view.login-error', 'socialment::login-error'), 71 | [ 72 | 'message' => $errorMessage, 73 | ] 74 | ); 75 | }); 76 | 77 | $panel->renderHook('panels::auth.login.form.after', function () { 78 | if (! $this->evaluate($this->visible)) { 79 | return ''; 80 | } 81 | 82 | $providers = array_merge(config('socialment.providers'), $this->providers); 83 | 84 | return View::make( 85 | config('socialment.view.providers-list', 'socialment::providers-list'), 86 | [ 87 | 'providers' => $providers, 88 | 'multiPanel' => $this->isMultiPanel(), 89 | 'panel' => $this->panel, 90 | ] 91 | ); 92 | }); 93 | } 94 | 95 | public function boot(Panel $panel): void 96 | { 97 | $this->panel = $panel; 98 | } 99 | 100 | public static function make(): static 101 | { 102 | $plugin = app(static::class); 103 | 104 | $plugin->visible = fn () => true; 105 | 106 | return $plugin; 107 | } 108 | 109 | public static function get(): static 110 | { 111 | /** @var static $plugin */ 112 | $plugin = filament(app(static::class)->getId()); 113 | 114 | return $plugin; 115 | } 116 | 117 | public function visible(bool | Closure | null $visible = null): static 118 | { 119 | $this->visible = $visible; 120 | 121 | return $this; 122 | } 123 | 124 | public function userModel(string | Closure $model): static 125 | { 126 | config()->set('socialment.models.user', (($model instanceof Closure) ? $model() : $model)); 127 | 128 | return $this; 129 | } 130 | 131 | public function loginRoute(null | string | Closure $route = null): static 132 | { 133 | $this->loginRoute = $route; 134 | 135 | return $this; 136 | } 137 | 138 | public function getLoginRoute(): ?string 139 | { 140 | // dd($this->panel->getId()); 141 | if ($this->loginRoute === null) { 142 | return null; 143 | // return $this->panel->getLoginUrl(); 144 | } 145 | 146 | return (string) $this->evaluate($this->loginRoute); 147 | } 148 | 149 | public function homeRoute(null | string | Closure $route = null): static 150 | { 151 | $this->homeRoute = $route; 152 | 153 | return $this; 154 | } 155 | 156 | public function getHomeRoute(): ?string 157 | { 158 | if ($this->homeRoute === null) { 159 | return null; 160 | // return $this->panel->getHomeUrl(); 161 | // return config('app.url') . '/' . Filament::getDefaultPanel()->getPath(); 162 | } 163 | 164 | return (string) $this->evaluate($this->homeRoute); 165 | } 166 | 167 | public static function globalPreLogin(Closure $callback): void 168 | { 169 | self::$globalPreLoginCallbacks[] = $callback; 170 | } 171 | 172 | public static function globalPostLogin(Closure $callback): void 173 | { 174 | self::$globalPostLoginCallbacks[] = $callback; 175 | } 176 | 177 | /** 178 | * Sets up a callback to be called before a user is logged in. 179 | * This is useful if you wish to check a user's roles before allowing them to login. 180 | * Throw a Socialment\Exceptions\AbortedLoginException to abort the login. 181 | */ 182 | public function preLogin(Closure $callback): static 183 | { 184 | // config()->set('socialment.post_login', $callback); 185 | $this->preLoginCallbacks[] = $callback; 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * Executes the pre login callback. Set up closure to execute via the preLogin method. 192 | */ 193 | public function executePreLogin(ConnectedAccount $account): void 194 | { 195 | // dump('plugin ID: ' . $this->getId()); 196 | // dump('panel ID: ' . $this->panel->getId()); 197 | // dump('executePreLogin'); 198 | // dump('Count of hooks: ' . count($this->preLoginCallbacks)); 199 | // dd('Count of global hooks: ' . count(self::$loginHooks)) 200 | 201 | foreach ($this->preLoginCallbacks as $callback) { 202 | ($callback)($account); 203 | } 204 | } 205 | 206 | /** 207 | * Sets up a callback to be called after a user logs in. 208 | */ 209 | public function postLogin(Closure $callback): static 210 | { 211 | // config()->set('socialment.post_login', $callback); 212 | $this->postLoginCallbacks[] = $callback; 213 | 214 | return $this; 215 | } 216 | 217 | /** 218 | * Executes the post login callback. Set up closure to execute via the postLogin method. 219 | */ 220 | public function executePostLogin(ConnectedAccount $account): void 221 | { 222 | foreach ($this->postLoginCallbacks as $callback) { 223 | ($callback)($account); 224 | } 225 | } 226 | 227 | // New Standard trying to match Filament proper 228 | public function createUserUsing(Closure $closure): static 229 | { 230 | $this->createUserClosure = $closure; 231 | 232 | return $this; 233 | } 234 | 235 | public function createUser(ConnectedAccount $account) 236 | { 237 | // If the closure is set, use it to create the user 238 | if ($this->createUserClosure !== null) { 239 | return ($this->createUserClosure)($account); 240 | } 241 | 242 | // Otherwise, use the default method - Get the user model from the config 243 | $userModel = config('socialment.models.user'); 244 | 245 | // Check for an existing user with this email 246 | // Create a new user if one doesn't exist 247 | return $userModel::where('email', $account->email)->first() 248 | ?? $userModel::create([ 249 | 'name' => $account->name, 250 | 'email' => $account->email, 251 | ]); 252 | } 253 | 254 | public function registerProvider(string $provider, string $icon, string $label, array $scopes = []): static 255 | { 256 | $this->providers[$provider] = [ 257 | 'icon' => $icon, 258 | 'label' => $label, 259 | 'scopes' => $scopes, 260 | ]; 261 | 262 | return $this; 263 | } 264 | 265 | public function multiPanel(bool $multiPanel = true): static 266 | { 267 | $this->multiPanel = $multiPanel; 268 | 269 | return $this; 270 | } 271 | 272 | public function isMultiPanel(): bool 273 | { 274 | // 'Guess' what setting this should be if it's not explicitly set. 275 | if ($this->multiPanel === null) { 276 | return count(Filament::getPanels()) > 1; 277 | } 278 | 279 | return $this->multiPanel; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/SocialmentServiceProvider.php: -------------------------------------------------------------------------------- 1 | name(static::$name) 30 | // ->hasCommands($this->getCommands()) 31 | ->hasRoute('web') 32 | ->hasInstallCommand(function (InstallCommand $command) { 33 | $command 34 | ->publishConfigFile() 35 | ->publishMigrations() 36 | ->askToRunMigrations() 37 | ->askToStarRepoOnGitHub('chrisreedio/socialment'); 38 | }); 39 | 40 | $configFileName = $package->shortName(); 41 | 42 | if (file_exists($package->basePath("/../config/{$configFileName}.php"))) { 43 | $package->hasConfigFile(); 44 | } 45 | 46 | if (file_exists($package->basePath('/../database/migrations'))) { 47 | $package->hasMigrations($this->getMigrations()); 48 | } 49 | 50 | if (file_exists($package->basePath('/../resources/lang'))) { 51 | $package->hasTranslations(); 52 | } 53 | 54 | if (file_exists($package->basePath('/../resources/views'))) { 55 | $package->hasViews(static::$viewNamespace); 56 | } 57 | } 58 | 59 | public function packageRegistered(): void 60 | { 61 | $this->app->singleton(SocialmentPlugin::class, fn () => new SocialmentPlugin()); 62 | } 63 | 64 | public function packageBooted(): void 65 | { 66 | Route::macro('spaAuth', function (string $prefix = 'spa') { 67 | $namePrefix = 'socialment.spa.'; 68 | $namePrefix .= ($prefix === 'spa') ? 'default.' : "{$prefix}."; 69 | 70 | Route::middleware('web') 71 | ->prefix($prefix) 72 | ->as($namePrefix) 73 | ->group(__DIR__ . '/../routes/spa.php'); 74 | 75 | // Now add this to the cors paths 76 | config([ 77 | 'cors.paths' => array_merge(config('cors.paths'), [ 78 | "{$prefix}/*", 79 | ]), 80 | ]); 81 | 82 | // Set the supports_credentials flag or the frontend can't send the goodies 83 | config([ 84 | 'cors.supports_credentials' => true, 85 | ]); 86 | }); 87 | 88 | // Asset Registration 89 | FilamentAsset::register( 90 | $this->getAssets(), 91 | $this->getAssetPackageName() 92 | ); 93 | 94 | // FilamentAsset::registerScriptData( 95 | // $this->getScriptData(), 96 | // $this->getAssetPackageName() 97 | // ); 98 | 99 | // Icon Registration 100 | FilamentIcon::register($this->getIcons()); 101 | 102 | // Handle Stubs 103 | if (app()->runningInConsole()) { 104 | foreach (app(Filesystem::class)->files(__DIR__ . '/../stubs/') as $file) { 105 | $this->publishes([ 106 | $file->getRealPath() => base_path("stubs/socialment/{$file->getFilename()}"), 107 | ], 'socialment-stubs'); 108 | } 109 | } 110 | 111 | // Testing 112 | Testable::mixin(new TestsSocialment()); 113 | } 114 | 115 | protected function getAssetPackageName(): ?string 116 | { 117 | return 'chrisreedio/socialment'; 118 | } 119 | 120 | /** 121 | * @return array 122 | */ 123 | protected function getAssets(): array 124 | { 125 | return [ 126 | // AlpineComponent::make('socialment', __DIR__ . '/../resources/dist/components/socialment.js'), 127 | // Css::make('socialment-styles', __DIR__ . '/../resources/dist/socialment.css'), 128 | // Js::make('socialment-scripts', __DIR__ . '/../resources/dist/socialment.js'), 129 | ]; 130 | } 131 | 132 | /** 133 | * @return array 134 | */ 135 | protected function getCommands(): array 136 | { 137 | return []; 138 | } 139 | 140 | /** 141 | * @return array 142 | */ 143 | protected function getIcons(): array 144 | { 145 | return []; 146 | } 147 | 148 | /** 149 | * @return array 150 | */ 151 | // protected function getRoutes(): array 152 | // { 153 | // return []; 154 | // } 155 | 156 | /** 157 | * @return array 158 | */ 159 | protected function getScriptData(): array 160 | { 161 | return []; 162 | } 163 | 164 | /** 165 | * @return array 166 | */ 167 | protected function getMigrations(): array 168 | { 169 | return [ 170 | 'create_connected_accounts_table', 171 | 'modify_users_table_nullable_password', 172 | ]; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/Testing/TestsSocialment.php: -------------------------------------------------------------------------------- 1 | hasMany(ConnectedAccount::class); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /stubs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisreedio/socialment/c9bffd5136385fe58cb4a0648a8751b9a2a82a96/stubs/.gitkeep --------------------------------------------------------------------------------