Repository: DutchCodingCompany/filament-socialite
Branch: main
Commit: 49cfcb8c7175
Files: 68
Total size: 134.6 KB
Directory structure:
gitextract_wtq0hz3c/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── config.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── dependabot-auto-merge.yml
│ ├── php-cs-fixer.yml
│ ├── phpstan.yml
│ ├── run-tests.yml
│ └── update-changelog.yml
├── .gitignore
├── .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
├── package.json
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml
├── rector.php
├── resources/
│ ├── css/
│ │ └── plugin.css
│ ├── dist/
│ │ └── plugin.css
│ ├── 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
├── tailwind.config.js
└── tests/
├── Fixtures/
│ ├── TestSocialiteUser.php
│ ├── TestTeam.php
│ ├── TestTenantUser.php
│ ├── TestUser.php
│ ├── change_nullable_password_on_users_table.php
│ ├── create_socialite_users_table.php
│ ├── create_team_user_table.php
│ └── create_teams_table.php
├── SocialiteLoginAuthorizationTest.php
├── SocialiteLoginTest.php
├── SocialiteStatelessLoginTest.php
├── SocialiteTenantLoginTest.php
└── TestCase.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
================================================
FILE: .gitattributes
================================================
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
# Ignore all test and documentation with "export-ignore".
/.github export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/phpunit.xml.dist export-ignore
/art export-ignore
/docs export-ignore
/tests export-ignore
/.editorconfig export-ignore
/.php_cs.dist.php export-ignore
/psalm.xml export-ignore
/psalm.xml.dist export-ignore
/testbench.yaml export-ignore
/UPGRADING.md export-ignore
/phpstan.neon.dist export-ignore
/phpstan-baseline.neon export-ignore
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Report a problem you're experiencing
body:
- type: markdown
attributes:
value: |
Before opening a bug report, please search the existing issues (both open and closed).
---
Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information.
- type: input
id: plugin-version
attributes:
label: Plugin Version
description: Please provide the version of filament socialite installed in your project.
placeholder: v2.0.0
validations:
required: true
- type: input
id: filament-version
attributes:
label: Filament Version
description: Please provide the full filament version of your project.
placeholder: v3.0.0
validations:
required: true
- type: input
id: laravel-version
attributes:
label: Laravel Version
description: Please provide the full Laravel version of your project.
placeholder: v10.0.0
validations:
required: true
- type: input
id: livewire-version
attributes:
label: Livewire Version
description: Please provide the full Livewire version of your project, if applicable.
placeholder: v3.0.0
- type: input
id: php-version
attributes:
label: PHP Version
description: Please provide the full PHP version of your server.
placeholder: PHP 8.3.0
validations:
required: true
- type: textarea
id: description
attributes:
label: Problem description
description: What happened when you experienced the problem?
validations:
required: true
- type: textarea
id: expectation
attributes:
label: Expected behavior
description: What did you expect to happen instead?
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: Which steps do we need to take to reproduce the problem? Any code examples need to be **as short as possible**, remove any code that is unrelated to the bug.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: If applicable, provide relevant log output. No need for backticks here.
render: shell
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://discord.com/channels/883083792112300104/962299008259342366
about: Ask the community for help
- name: Request a feature
url: https://discord.com/channels/883083792112300104/962299008259342366
about: Share ideas for new features
- name: Report a security issue
url: https://github.com/DutchCodingCompany/filament-socialite/security/policy
about: Learn how to notify us for sensitive bugs
================================================
FILE: .github/dependabot.yml
================================================
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
================================================
FILE: .github/workflows/dependabot-auto-merge.yml
================================================
name: dependabot-auto-merge
on: pull_request_target
permissions:
pull-requests: write
contents: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v3.0.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Auto-merge Dependabot PRs for semver-minor updates
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Auto-merge Dependabot PRs for semver-patch updates
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
================================================
FILE: .github/workflows/php-cs-fixer.yml
================================================
name: Check & fix styling
on: [push]
jobs:
php-cs-fixer:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.head_ref }}
- name: Run PHP CS Fixer
uses: docker://oskarstark/php-cs-fixer-ga
with:
args: --config=.php-cs-fixer.php --allow-risky=yes
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: Fix styling
================================================
FILE: .github/workflows/phpstan.yml
================================================
name: PHPStan
on:
push:
paths:
- '**.php'
- .github/workflows/phpstan.yml
- phpstan.neon.dist
pull_request:
jobs:
phpstan:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [8.4, 8.3, 8.2]
laravel: ['11.*', '12.*', '13.*']
stability: [prefer-stable]
include:
- laravel: 11.*
testbench: 9.*
- laravel: 12.*
testbench: 10.*
- laravel: 13.*
testbench: 11.*
exclude:
- laravel: 13.*
php: 8.2
name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
coverage: none
- name: Install dependencies
run: |
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
- name: List Installed Dependencies
run: composer show -D
- name: Run PHPStan
run: vendor/bin/phpstan --error-format=github
================================================
FILE: .github/workflows/run-tests.yml
================================================
name: run-tests
on:
push:
paths:
- '**.php'
- .github/workflows/run-tests.yml
- phpunit.xml.dist
- composer.json
- composer.lock
pull_request:
jobs:
test:
runs-on: ${{ matrix.os }}
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [8.4, 8.3, 8.2]
laravel: ['11.*', '12.*', '13.*']
stability: [prefer-stable]
include:
- laravel: 11.*
testbench: 9.*
- laravel: 12.*
testbench: 10.*
- laravel: 13.*
testbench: 11.*
exclude:
- laravel: 13.*
php: 8.2
name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
coverage: none
- name: Setup problem matchers
run: |
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install dependencies
run: |
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
- name: List Installed Dependencies
run: composer show -D
- name: Execute tests
run: vendor/bin/phpunit tests
================================================
FILE: .github/workflows/update-changelog.yml
================================================
name: "Update Changelog"
on:
release:
types: [released]
jobs:
update:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: main
- name: Update Changelog
uses: stefanzweifel/changelog-updater-action@v1
with:
latest-version: ${{ github.event.release.name }}
release-notes: ${{ github.event.release.body }}
- name: Commit updated CHANGELOG
uses: stefanzweifel/git-auto-commit-action@v7
with:
branch: main
commit_message: Update CHANGELOG
file_pattern: CHANGELOG.md
================================================
FILE: .gitignore
================================================
.idea
.php_cs
.php_cs.cache
.phpunit.result.cache
build
composer.lock
package-lock.json
coverage
docs
phpstan.neon
testbench.yaml
vendor
node_modules
.php-cs-fixer.cache
================================================
FILE: .php-cs-fixer.php
================================================
<?php
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
$rules = [
'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'binary_operator_spaces' => [
'default' => 'single_space',
// 'operators' => ['=>' => null], // single space makes code look more coherent in style. But sometimes it is not beter, in that case, manually override.
],
'blank_line_after_namespace' => true,
'blank_line_after_opening_tag' => true,
'blank_line_before_statement' => [
'statements' => ['return'],
],
'braces' => true,
'cast_spaces' => true,
'class_attributes_separation' => [
'elements' => [
'const' => 'only_if_meta',
'method' => 'one',
'property' => 'one',
'trait_import' => 'none',
],
],
'class_definition' => [
'multi_line_extends_each_single_line' => true,
'single_item_single_line' => true,
'single_line' => true,
],
'concat_space' => [
'spacing' => 'none',
],
'constant_case' => ['case' => 'lower'],
'declare_equal_normalize' => true,
'elseif' => true,
'encoding' => true,
'full_opening_tag' => true,
'fully_qualified_strict_types' => false,
// added by Shift
'function_declaration' => true,
'function_typehint_space' => true,
'general_phpdoc_tag_rename' => true,
'heredoc_to_nowdoc' => true,
'include' => true,
'increment_style' => ['style' => 'post'],
'indentation_type' => true,
'linebreak_after_opening_tag' => true,
'line_ending' => true,
'lowercase_cast' => true,
'lowercase_keywords' => true,
'lowercase_static_reference' => true,
// added from Symfony
'magic_method_casing' => true,
// added from Symfony
'magic_constant_casing' => true,
'method_argument_space' => [
'on_multiline' => 'ignore',
],
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'native_function_casing' => true,
'no_alias_functions' => true,
'no_extra_blank_lines' => [
'tokens' => [
'extra',
'throw',
'use',
'switch',
'case',
'default',
],
],
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_closing_tag' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => [
'use' => 'echo',
],
'no_multiline_whitespace_around_double_arrow' => true,
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_after_function_name' => true,
'no_spaces_around_offset' => [
'positions' => [
'inside',
'outside',
],
],
'no_spaces_inside_parenthesis' => true,
'no_trailing_comma_in_list_call' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'no_unneeded_control_parentheses' => [
'statements' => [
'break',
'clone',
'continue',
'echo_print',
'return',
'switch_case',
'yield',
],
],
'no_unreachable_default_argument_value' => true,
'no_useless_return' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
'normalize_index_brace' => true,
'not_operator_with_successor_space' => true,
'object_operator_without_whitespace' => true,
'ordered_imports' => [
'sort_algorithm' => 'alpha',
'imports_order' => [
'class',
'function',
'const',
],
],
'psr_autoloading' => true,
'phpdoc_indent' => true,
'phpdoc_inline_tag_normalizer' => true,
'phpdoc_no_access' => true,
'phpdoc_no_package' => true,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_scalar' => true,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => false,
'phpdoc_to_comment' => false,
// override to preserve user preference
'phpdoc_tag_type' => true,
'phpdoc_trim' => true,
'phpdoc_types' => true,
'phpdoc_var_without_name' => true,
'self_accessor' => true,
'short_scalar_cast' => true,
'simplified_null_return' => false,
// disabled as "risky"
'single_blank_line_at_eof' => true,
'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => [
'elements' => [
'const',
'property',
],
],
'single_import_per_statement' => true,
'single_line_after_imports' => true,
'single_line_comment_style' => [
'comment_types' => ['hash'],
],
'single_quote' => true,
'space_after_semicolon' => true,
'standardize_not_equals' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline' => [
'elements' => [
'arrays',
'parameters',
],
],
'trim_array_spaces' => true,
'types_spaces' => [
'space' => 'single',
],
'unary_operator_spaces' => true,
'visibility_required' => [
'elements' => [
'method',
'property',
'const',
],
],
'whitespace_after_comma_in_array' => true,
// DCC
'align_multiline_comment' => ['comment_type' => 'phpdocs_like'],
'simplified_if_return' => true,
'method_chaining_indentation' => true,
];
$finder = Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->name('*.php')
->notName('*.blade.php')
->ignoreDotFiles(true)
->ignoreVCS(true);
return (new Config)
->setFinder($finder)
->setRules($rules)
->setRiskyAllowed(true)
->setUsingCache(true);
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to `filament-socialite` will be documented in this file.
## [3.2.1 - 2026-04-14](https://github.com/DutchCodingCompany/filament-socialite/compare/3.2.0...3.2.1)
## What's Changed
* fix: update state retrieval method in PanelFromUrlQuery middleware by @fului in https://github.com/DutchCodingCompany/filament-socialite/pull/153
* Bump dependabot/fetch-metadata from 2.5.0 to 3.0.0 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/151
## [3.2.0 - 2026-03-19](https://github.com/DutchCodingCompany/filament-socialite/compare/3.1.0...3.2.0)
## What's Changed
* Add return type declarations to migration methods by @phh in https://github.com/DutchCodingCompany/filament-socialite/pull/149
* Support laravel 13 https://github.com/DutchCodingCompany/filament-socialite/pull/150
## [3.1.0 - 2026-01-19](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.1...3.1.0)
## What's Changed
- Add support for filament v5 by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/147
- Bump dependabot/fetch-metadata from 2.4.0 to 2.5.0 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/145
## [3.0.1 - 2025-12-16](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.0...3.0.1)
## What's Changed
- Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/135
- Bump stefanzweifel/git-auto-commit-action from 6 to 7 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/139
- Bump actions/checkout from 5 to 6 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/141
- Add foreign key constraint with cascade behaviour. by @chrillep in https://github.com/DutchCodingCompany/filament-socialite/pull/143
- **NOTE**: this will not change existing `socialite_users` tables. Please **consider adding** the constraint in a separate migration yourself
## [3.0.0 - 2025-08-11](https://github.com/DutchCodingCompany/filament-socialite/compare/2.4.0...3.0.0)
- Tag major version
- BREAKING CHANGE: Implement fix for slug issue https://github.com/DutchCodingCompany/filament-socialite/issues/127
The package now uses `path` instead of `id` as default prefix as it should have done. In order to revert to previous behaviour, use slug to override the behaviour:
```php
->plugin(
FilamentSocialitePlugin::make()
->slug('admin') // change this to the panel's ID
// other config for plugin
)
```
## [3.0.0-beta3 - 2025-07-18](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.0-beta2...3.0.0-beta3)
## What's Changed
* Include compiled styles
## [3.0.0-beta2 - 2025-06-23](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.0-alpha1...3.0.0-beta2)
## What's Changed
* BREAKING CHANGE: Implement fix for slug issue https://github.com/DutchCodingCompany/filament-socialite/issues/127
The package now uses `path` instead of `id` as default prefix as it should have done. In order to revert to previous behaviour, use slug to override the behaviour:
```php
->plugin(
FilamentSocialitePlugin::make()
->slug('admin') // change this to the panel's ID
// other config for plugin
)
```
## [3.0.0-alpha1 - 2025-06-05](https://github.com/DutchCodingCompany/filament-socialite/compare/2.4.0...3.0.0-alpha1) / [3.0.0-beta1 - 2025-06-05](https://github.com/DutchCodingCompany/filament-socialite/compare/2.4.0...3.0.0-beta1)
## What's Changed
* Filament V4 support by @erikgaal in https://github.com/DutchCodingCompany/filament-socialite/pull/131
## [2.4.0 - 2025-02-25](https://github.com/DutchCodingCompany/filament-socialite/compare/2.3.1...2.4.0)
## What's Changed
* Laravel 12.x Compatibility by @laravel-shift in https://github.com/DutchCodingCompany/filament-socialite/pull/125
## [2.3.1 - 2025-02-06](https://github.com/DutchCodingCompany/filament-socialite/compare/2.3.0...2.3.1)
## What's Changed
* Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/123
* Add data to events by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/124
## [2.3.0 - 2024-11-29](https://github.com/DutchCodingCompany/filament-socialite/compare/2.2.1...2.3.0)
## What's Changed
* Add option to hide providers by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/122
* Add support for php 8.4
## [2.2.1 - 2024-07-17](https://github.com/DutchCodingCompany/filament-socialite/compare/2.2.0...2.2.1)
## What's Changed
* Revert model property changes by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/110
## [2.2.0 - 2024-07-15](https://github.com/DutchCodingCompany/filament-socialite/compare/2.1.1...2.2.0)
## What's Changed
* Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/106
* Add new callback route for stateless OAuth flows by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/105
## [2.1.1 - 2024-06-21](https://github.com/DutchCodingCompany/filament-socialite/compare/2.1.0...2.1.1)
## What's Changed
* Improve Socialite driver typings + callable typings by @juliangums in https://github.com/DutchCodingCompany/filament-socialite/pull/103
## New Contributors
* @juliangums made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/103
## [2.1.0 - 2024-06-21](https://github.com/DutchCodingCompany/filament-socialite/compare/2.0.0...2.1.0)
* Add Authorization Callback by @petecoop in https://github.com/DutchCodingCompany/filament-socialite/pull/100
## [2.0.0 - 2024-06-04](https://github.com/DutchCodingCompany/filament-socialite/compare/1.5.0...2.0.0)
* **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.**
* Refactor package for better consistency with Filament code standards https://github.com/DutchCodingCompany/filament-socialite/pull/90
## [1.5.0 - 2024-06-04](https://github.com/DutchCodingCompany/filament-socialite/compare/1.4.1...1.5.0)
* Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/89
* Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/91
* Compatible with Stateless Authentication by @LittleHans8 in https://github.com/DutchCodingCompany/filament-socialite/pull/96
## [1.4.1 - 2024-03-20](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.4.0...1.4.1)
* Provide oauth user to login event by @dcc-bjorn in https://github.com/DutchCodingCompany/filament-socialite/pull/88
## [1.4.0 - 2024-03-12](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.3.1...1.4.0)
* Laravel 11 support by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/87
## [1.3.1 - 2024-03-05](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.3.0...1.3.1)
* Add $provider as required by the callback by @phh in https://github.com/DutchCodingCompany/filament-socialite/pull/83
* Never use SPA mode for oauth links + spacing by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/85
## [1.3.0 - 2024-03-01](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.2.0...1.3.0)
* Update CHANGELOG.md by @bramr94 in https://github.com/DutchCodingCompany/filament-socialite/pull/70
* Add socialite test by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/78
* Improve actions by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/79
* Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/51
* feature: allow socialite user model customization by @kykurniawan in https://github.com/DutchCodingCompany/filament-socialite/pull/72
* Add registration enabled callable by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/80
* Multi-tenancy support by @bramr94 in https://github.com/DutchCodingCompany/filament-socialite/pull/76
## [1.2.0 - 2024-01-31](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.1.1...1.2.0)
- Add option to add optional parameters in https://github.com/DutchCodingCompany/filament-socialite/pull/69
## [1.1.1 - 2024-01-18](https://github.com/DutchCodingCompany/filament-socialite/compare/1.1.0...1.1.1)
- Improve domain routing in https://github.com/DutchCodingCompany/filament-socialite/pull/61
- Update README in https://github.com/DutchCodingCompany/filament-socialite/pull/64
## [1.1.0 - 2024-01-08](https://github.com/DutchCodingCompany/filament-socialite/compare/1.0.1...1.1.0)
- Add button customization options in https://github.com/DutchCodingCompany/filament-socialite/pull/59
## [1.0.1 - 2023-12-18](https://github.com/DutchCodingCompany/filament-socialite/compare/1.0.0...1.0.1)
- Resolve plugin registration issue [#54](https://github.com/DutchCodingCompany/filament-socialite/issues/54)
## [1.0.0 - 2023-12-05](https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.2...1.0.0)
- Added support for Filament v3 through the plugin setup
- Added support for multiple panels
- See [UPGRADE.md](UPGRADE.md)
## 0.2.2 - 2022-06-14
### What's Changed
- Fix readme by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/15
- use Filament-fortify render hook by @wychoong in https://github.com/DutchCodingCompany/filament-socialite/pull/16
### New Contributors
- @wychoong made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/16
**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.1...0.2.2
## 0.2.1 - 2022-05-25
## What's Changed
- Fix user model instantiating by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/14
**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.0...0.2.1
## 0.2.0 - 2022-05-24
## Breaking changes
- `Events\DomainFailed` renamed to `Events\UserNotAllowed`
- `Events\RegistrationFailed` renamed to `Events\RegistrationNotEnabled`
## What's Changed
- Refactor the controller for extendability and customization by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/13
## New Contributors
- @dododedodonl made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/13
**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.5...0.2.0
## 0.1.5 - 2022-05-20
## What's Changed
- Fix missing variable for registered event by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/11
**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.4...0.1.5
## 0.1.4 - 2022-05-06
## What's Changed
- Feature: Adds buttons blade component by @oyepez003 in https://github.com/DutchCodingCompany/filament-socialite/pull/8
- Feature: Add login events dispatching by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/5
**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.3...0.1.4
## 0.1.3 - 2022-05-04
## What's Changed
- Bugfix: Avoid returning 403 when a user exists based on the oauth-email . by @oyepez003 in https://github.com/DutchCodingCompany/filament-socialite/pull/7
## New Contributors
- @oyepez003 made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/7
**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.2...0.1.3
## 0.1.2 - 2022-05-03
## What's Changed
- Bump dependabot/fetch-metadata from 1.3.0 to 1.3.1 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/2
- Add Laravel 8 support and make fontawesome icons optional by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/4
## New Contributors
- @marcoboers made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/4
**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.1...0.1.2
## 0.1.1 - 2022-04-11
## What's Changed
- Fix registration flow
## 0.1.0 - 2022-04-08
### Initial Release
- Add social login links to login page
- Support Socialite OAuth flow
- Support registration flow
- Support domain allowlist for internal use
- Dark mode support
- Blade Font Awesome brand icons
================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)
Copyright (c) DutchCodingCompany <m@rcoboe.rs>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://banners.beyondco.de/Filament%20Socialite.png?theme=dark&packageManager=composer+require&packageName=DutchCodingCompany%2Ffilament-socialite&pattern=architect&style=style_1&description=Add+OAuth+login+through+Laravel+Socialite+to+Filament.&md=1&showWatermark=0&fontSize=100px&images=user-group">
<img src="https://banners.beyondco.de/Filament%20Socialite.png?theme=light&packageManager=composer+require&packageName=DutchCodingCompany%2Ffilament-socialite&pattern=architect&style=style_1&description=Add+OAuth+login+through+Laravel+Socialite+to+Filament.&md=1&showWatermark=0&fontSize=100px&images=user-group">
</picture>
# Social login for Filament through Laravel Socialite
[](https://packagist.org/packages/dutchcodingcompany/filament-socialite)
[](https://github.com/dutchcodingcompany/filament-socialite/actions?query=workflow%3Arun-tests+branch%3Amain)
[](https://github.com/dutchcodingcompany/filament-socialite/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain)
[](https://packagist.org/packages/dutchcodingcompany/filament-socialite)
Add OAuth2 login through Laravel Socialite to Filament. OAuth1 (eg. Twitter) is not supported at this time.
## Installation
| Filament version | Package version | Readme |
|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|--------------------------------------------------------------------------------------|
| [^5.0.0](https://github.com/filamentphp/filament/tree/5.x) | ^3.1 | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/main/README.md) |
| [^4.0.0](https://github.com/filamentphp/filament/tree/4.x) | 3.x.x | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/main/README.md) |
| [^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/2.x/README.md) |
| [^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 | |
| 3.x | 1.x.x | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/1.x/README.md) |
| 2.x | 0.x.x | |
Install the package via composer:
```bash
composer require dutchcodingcompany/filament-socialite
```
Publish and migrate the migration file:
```bash
php artisan vendor:publish --tag="filament-socialite-migrations"
php artisan migrate
```
Other configuration files include:
```bash
php artisan vendor:publish --tag="filament-socialite-config"
php artisan vendor:publish --tag="filament-socialite-views"
php artisan vendor:publish --tag="filament-socialite-translations"
```
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:
```php
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Provider;
use Filament\Support\Colors;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
use Illuminate\Contracts\Auth\Authenticatable;
// ...
->plugin(
FilamentSocialitePlugin::make()
// (required) Add providers corresponding with providers in `config/services.php`.
->providers([
// Create a provider 'gitlab' corresponding to the Socialite driver with the same name.
Provider::make('gitlab')
->label('GitLab')
->icon('fab-gitlab')
->color(Color::hex('#2f2a6b'))
->outlined(false)
->stateless(false)
->scopes(['...'])
->with(['...']),
])
// (optional) Override the panel slug to be used in the oauth routes. Defaults to the panel's configured path.
->slug('admin')
// (optional) Enable/disable registration of new (socialite-) users.
->registration(true)
// (optional) Enable/disable registration of new (socialite-) users using a callback.
// In this example, a login flow can only continue if there exists a user (Authenticatable) already.
->registration(fn (string $provider, SocialiteUserContract $oauthUser, ?Authenticatable $user) => (bool) $user)
// (optional) Change the associated model class.
->userModelClass(\App\Models\User::class)
// (optional) Change the associated socialite class (see below).
->socialiteUserModelClass(\App\Models\SocialiteUser::class)
);
```
This package automatically adds 2 routes per panel to make the OAuth flow possible: a redirector and a callback. When
setting up your **external OAuth app configuration**, enter the following callback URL (in this case for the Filament
panel with ID `admin` and the `github` provider):
```
https://example.com/admin/oauth/callback/github
```
A multi-panel callback route is available as well that does not contain the panel ID in the url. Instead, it determines
the panel ID from an encrypted `state` input (`...?state=abcd1234`). This allows you to create a single OAuth
application for multiple Filament panels that use the same callback URL. Note that this only works for _stateful_ OAuth
apps:
```
https://example.com/oauth/callback/github
```
If in doubt, run `php artisan route:list` to see which routes are available to you.
### Icons
You can specify a custom icon for each of your login providers. You can add Font Awesome brand
icons made available through [Blade Font Awesome](https://github.com/owenvoke/blade-fontawesome) by running:
```bash
composer require owenvoke/blade-fontawesome
```
### Registration flow
This package supports account creation for users. However, to support this flow it is important that the `password`
attribute on your `User` model is nullable. For example, by adding the following to your users table migration.
Or you could opt for customizing the user creation, see below.
```php
$table->string('password')->nullable();
```
### Domain Allow list
This package supports the option to limit the users that can login with the OAuth login to users of a certain domain.
This can be used to setup SSO for internal use.
```php
->plugin(
FilamentSocialitePlugin::make()
// ...
->registration(true)
->domainAllowList(['localhost'])
);
```
### Changing how an Authenticatable user is created or retrieved
You can use the `createUserUsing` and `resolveUserUsing` methods to change how a user is created or retrieved.
```php
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
->plugin(
FilamentSocialitePlugin::make()
// ...
->createUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {
// Logic to create a new user.
})
->resolveUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {
// Logic to retrieve an existing user.
})
...
);
```
### Change how a Socialite user is created or retrieved
In your plugin options in your Filament panel, add the following method:
```php
// app/Providers/Filament/AdminPanelProvider.php
->plugins([
FilamentSocialitePlugin::make()
// ...
->socialiteUserModelClass(\App\Models\SocialiteUser::class)
```
This class should at the minimum implement the [`FilamentSocialiteUser`](/src/Models/Contracts/FilamentSocialiteUser.php) interface, like so:
```php
namespace App\Models;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
class SocialiteUser implements FilamentSocialiteUserContract
{
public function getUser(): Authenticatable
{
//
}
public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self
{
//
}
public static function createForProvider(
string $provider,
SocialiteUserContract $oauthUser,
Authenticatable $user
): self {
//
}
}
```
### Check if the user is authorized to use the application
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.
```php
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
->plugin(
FilamentSocialitePlugin::make()
// ...
->authorizeUserUsing(function (FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser) {
// Logic to authorize the user.
return FilamentSocialitePlugin::checkDomainAllowList($plugin, $oauthUser);
})
// ...
);
```
### Change login redirect
When your panel has [multi-tenancy](https://filamentphp.com/docs/4.x/users/tenancy) enabled, after logging in, the user will be redirected to their [default tenant](https://filamentphp.com/docs/4.x/users/tenancy#setting-the-default-tenant).
If you want to change this behavior, you can call the 'redirectAfterLoginUsing' method on the `FilamentSocialitePlugin`.
```php
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use DutchCodingCompany\FilamentSocialite\Models\SocialiteUser;
FilamentSocialitePlugin::make()
->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) {
// Change the redirect behaviour here.
});
```
## Events
There are a few events dispatched during the authentication process:
* `InvalidState(InvalidStateException $exception)`: When trying to retrieve the oauth (socialite) user, an invalid state was encountered
* `Login(FilamentSocialiteUserContract $socialiteUser)`: When a user successfully logs in
* `Registered(string $provider, SocialiteUserContract $oauthUser, FilamentSocialiteUserContract $socialiteUser)`: When a user and socialite user is successfully registered and logged in (when enabled in config)
* `RegistrationNotEnabled(string $provider, SocialiteUserContract $oauthUser, ?Auhthenticatable $user)`: When a user tries to login with an unknown account and registration is not enabled
* `SocialiteUserConnected(string $provider, SocialiteUserContract $oauthUser, FilamentSocialiteUserContract $socialiteUser)`: When a socialite user is created for an existing user
* `UserNotAllowed(SocialiteUserContract $oauthUser)`: When a user tries to login with an email which domain is not on the allowlist
## Scopes
Scopes can be added to the provider on the panel, for example:
```php
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Provider;
FilamentSocialitePlugin::make()
->providers([
Provider::make('github')
->label('Github')
->icon('fab-github')
->scopes([
// Add scopes here.
'read:user',
'public_repo',
]),
]),
```
## Optional parameters
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:
```php
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Provider;
FilamentSocialitePlugin::make()
->providers([
Provider::make('github')
->label('Github')
->icon('fab-github')
->with([
// Add scopes here.
// Add optional parameters here.
'hd' => 'example.com',
]),
]),
```
## Visibility
You can set the visibility of a provider, if it is not visible, buttons will not be rendered. All functionality will still be enabled.
```php
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Provider;
FilamentSocialitePlugin::make()
->providers([
Provider::make('github')
->visible(fn () => true),
]),
```
## Stateless Authentication
You can add `stateless` parameters to the provider configuration in the config/services.php config file, for example:
```php
'apple' => [
'client_id' => '...',
'client_secret' => '...',
'stateless'=>true,
]
```
**Note:** you cannot use the `state` parameter, as it is used to determine from which Filament panel the user came from.
## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
## Contributing
Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.
## Security Vulnerabilities
Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
## Credits
- [All Contributors](../../contributors)
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 3.x | :white_check_mark: |
| 2.x | :warning: (security fixes only) |
| 1.x | :no_entry_sign: |
| 0.x | :no_entry_sign: |
## Reporting a Vulnerability
If you discover a security vulnerability within this plugin, please email Dutch Coding Company via [server@dutchcodingcompany.com](mailto:server@dutchcodingcompany.com).
All security vulnerabilities will be promptly addressed.
================================================
FILE: UPGRADE.md
================================================
# Upgrade Guide
## `2.x.x` to `3.x.x` (Filament v4.x)
### Slug
In v3 of the plugin, the panel's configured path is used instead of it's ID when generating the callback URLs.
In order to revert to previous behaviour, use slug to override the behaviour:
```php
->plugin(
FilamentSocialitePlugin::make()
->slug('admin') // change this to the panel's ID
// other config for plugin
)
```
## `1.x.x` to `2.x.x` (Filament v3.x)
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.
### Method names
Every method name has been changed to be more consistent with the Filament naming conventions. The following changes have been made:
- `setProviders()` -> `providers()`
- `setSlug()` -> `slug()`
- `setLoginRouteName()` -> `loginRouteName()`
- `setDashboardRouteName()` -> `dashboardRouteName()`
- `setRememberLogin()` -> `rememberLogin()`
- `setRegistrationEnabled()` -> `registration()`
- `getRegistrationEnabled()` -> `getRegistration()`
- `setDomainAllowList()` -> `domainAllowList()`
- `setSocialiteUserModelClass()` -> `socialiteUserModelClass()`
- `setUserModelClass()` -> `userModelClass()`
- `setShowDivider()` -> `showDivider()`
**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:
```bash
vendor/bin/upgrade-v2
```
#### Callbacks
**setCreateUserCallback()**
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.
```php
FilamentSocialitePlugin::make()
// ...
->createUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {
// Logic to create a new user.
})
```
**setUserResolver()**
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.
```php
FilamentSocialitePlugin::make()
// ...
->resolveUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {
// Logic to retrieve an existing user.
})
```
**setLoginRedirectCallback()**
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.
```php
FilamentSocialitePlugin::make()
// ...
->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) {
// Change the redirect behaviour here.
})
```
#### Removals
**getOptionalParameters()**
This function was used internally only inside the `SocialiteLoginController`. If you haven't extended this controller, you can ignore this change.
Provider details can now be retrieved using `$plugin->getProvider($provider)->getWith()`.
**getProviderScopes()**
This function was used internally only inside the `SocialiteLoginController`. If you haven't extended this controller, you can ignore this change.
Provider details can now be retrieved using `$plugin->getProvider($provider)->getScopes()`.
### Configuration
**Providers**
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.
```php
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Provider;
FilamentSocialitePlugin::make()
->providers([
Provider::make('gitlab')
->label('GitLab')
->icon('fab-gitlab')
->color(Color::hex('#2f2a6b')),
]),
```
**Scopes and Optional parameters**
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.
```php
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Provider;
FilamentSocialitePlugin::make()
->providers([
Provider::make('gitlab')
// ...
->scopes([
// Add scopes here.
'read:user',
'public_repo',
]),
->with([
// Add optional parameters here.
'hd' => 'example.com',
]),
]),
```
## `0.x.x` to `1.x.x` (Filament v3.x)
- Replace/republish the configuration file:
- `sail artisan vendor:publish --provider="DutchCodingCompany\FilamentSocialite\FilamentSocialiteServiceProvider"`
- Update your panel configuration `App\Providers\Filament\YourPanelProvider` to include the plugin:
- Append `->plugins([\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin::make()])`
- Configure any options by chaining functions on the plugin.
## `0.x.x` (Filament v2.x)
- Initial version
================================================
FILE: bin/upgrade-v2
================================================
#!/usr/bin/env php
<?php
namespace Composer;
exec("vendor/bin/rector process --config vendor/dutchcodingcompany/filament-socialite/rector.php --clear-cache");
================================================
FILE: composer.json
================================================
{
"name": "dutchcodingcompany/filament-socialite",
"description": "Social login for Filament through Laravel Socialite",
"keywords": [
"DutchCodingCompany",
"laravel",
"filament-socialite"
],
"homepage": "https://github.com/dutchcodingcompany/filament-socialite",
"license": "MIT",
"authors": [
{
"name": "Marco Boers",
"email": "m@rcoboe.rs",
"role": "Developer"
},
{
"name": "Tom Janssen",
"email": "dododedodonl@thor.edu",
"role": "Developer"
},
{
"name": "Bram Raaijmakers",
"email": "bram@dutchcodingcompany.com",
"role": "Developer"
}
],
"require": {
"php": "^8.2",
"filament/filament": "^4.0|^5.0",
"illuminate/contracts": "^11.0|^12.0|^13.0",
"laravel/socialite": "^5.5",
"spatie/laravel-package-tools": "^1.9.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.8",
"larastan/larastan": "^2.9|^3.0",
"nunomaduro/collision": "^7.0|^8.1",
"orchestra/testbench": "^9.0|^10.0|^11.0",
"phpunit/phpunit": "^10.0|^11.5.3|^12.5.12",
"rector/rector": "^0.19.8|^2.0"
},
"suggest": {
"owenvoke/blade-fontawesome": "^2.0"
},
"autoload": {
"psr-4": {
"DutchCodingCompany\\FilamentSocialite\\": "src",
"DutchCodingCompany\\FilamentSocialite\\Tests\\": "tests",
"DutchCodingCompany\\FilamentSocialite\\Database\\Factories\\": "database/factories",
"Utils\\Rector\\": "utils/rector/src"
}
},
"scripts": {
"analyse": "vendor/bin/phpstan analyse",
"test": "vendor/bin/phpunit"
},
"bin": [
"bin/upgrade-v2"
],
"config": {
"sort-packages": true,
"allow-plugins": {
"phpstan/extension-installer": true
}
},
"extra": {
"laravel": {
"providers": [
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialiteServiceProvider"
],
"aliases": {
"FilamentSocialite": "DutchCodingCompany\\FilamentSocialite\\Facades\\FilamentSocialite"
}
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload-dev": {
"psr-4": {
"DutchCodingCompany\\FilamentSocialite\\Tests\\": "tests"
}
}
}
================================================
FILE: config/filament-socialite.php
================================================
<?php
return [
/*
|--------------------------------------------------------------------------
| OAuth callback middleware
|--------------------------------------------------------------------------
|
| This option defines the middleware that is applied to the OAuth callback url.
|
*/
'middleware' => [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
],
];
================================================
FILE: database/migrations/create_socialite_users_table.php.stub
================================================
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration {
public function up(): void
{
Schema::create('socialite_users', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->string('provider');
$table->string('provider_id');
$table->timestamps();
$table->unique([
'provider',
'provider_id',
]);
});
}
public function down(): void
{
Schema::dropIfExists('socialite_users');
}
};
================================================
FILE: package.json
================================================
{
"private": true,
"name": "filament-socialite",
"devDependencies": {
"@tailwindcss/cli": "^4.1.10",
"@tailwindcss/postcss": "^4.1.10",
"tailwindcss": "^4.1.10"
},
"scripts": {
"dev": "npx @tailwindcss/cli -i ./resources/css/plugin.css -o ./resources/dist/plugin.css --watch",
"watch": "npx @tailwindcss/cli -i ./resources/css/plugin.css -o ./resources/dist/plugin.css --watch",
"prod": "npx @tailwindcss/cli -i ./resources/css/plugin.css -o ./resources/dist/plugin.css --minify"
}
}
================================================
FILE: phpstan-baseline.neon
================================================
parameters:
ignoreErrors:
-
message: '#^Class App\\Models\\User not found\.$#'
identifier: class.notFound
count: 1
path: src/FilamentSocialitePlugin.php
-
message: '#^Property DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin\:\:\$userModelClass \(class\-string\<Illuminate\\Contracts\\Auth\\Authenticatable\>\) does not accept default value of type string\.$#'
identifier: property.defaultValue
count: 1
path: src/FilamentSocialitePlugin.php
-
message: '#^Call to function method_exists\(\) with ''Illuminate\\\\Foundation\\\\Http\\\\Middleware\\\\VerifyCsrfToken'' and ''except'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/FilamentSocialiteServiceProvider.php
-
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\.$#'
identifier: argument.type
count: 1
path: src/Http/Controllers/SocialiteLoginController.php
-
message: '#^Method DutchCodingCompany\\FilamentSocialite\\Models\\SocialiteUser\:\:user\(\) should return Illuminate\\Database\\Eloquent\\Relations\\BelongsTo\<Illuminate\\Database\\Eloquent\\Model, \$this\(DutchCodingCompany\\FilamentSocialite\\Models\\SocialiteUser\)\> but returns Illuminate\\Database\\Eloquent\\Relations\\BelongsTo\<Illuminate\\Contracts\\Auth\\Authenticatable&Illuminate\\Database\\Eloquent\\Model, \$this\(DutchCodingCompany\\FilamentSocialite\\Models\\SocialiteUser\)\>\.$#'
identifier: return.type
count: 1
path: src/Models/SocialiteUser.php
-
message: '#^Parameter \#1 \$view of function view expects view\-string\|null, string given\.$#'
identifier: argument.type
count: 1
path: src/View/Components/Buttons.php
-
message: '#^PHPDoc tag @property for property DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestTenantUser\:\:\$teams contains generic class Illuminate\\Support\\Collection but does not specify its types\: TKey, TValue$#'
identifier: missingType.generics
count: 1
path: tests/Fixtures/TestTenantUser.php
-
message: '#^Call to an undefined method Mockery\\ExpectationInterface\|Mockery\\HigherOrderMessage\:\:andReturn\(\)\.$#'
identifier: method.notFound
count: 3
path: tests/TestCase.php
-
message: '#^Parameter \#1 \$callback of static method Illuminate\\Database\\Eloquent\\Factories\\Factory\<Illuminate\\Database\\Eloquent\\Model\>\:\:guessFactoryNamesUsing\(\) expects callable\(class\-string\<Illuminate\\Database\\Eloquent\\Model\>\)\: class\-string\<Illuminate\\Database\\Eloquent\\Factories\\Factory\>, Closure\(string\)\: non\-falsy\-string given\.$#'
identifier: argument.type
count: 1
path: tests/TestCase.php
================================================
FILE: phpstan.neon.dist
================================================
includes:
- ./vendor/larastan/larastan/extension.neon
- ./phpstan-baseline.neon
parameters:
level: 8
paths:
- config
- database
- src
- tests
================================================
FILE: phpunit.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Test">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value="testing"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>
================================================
FILE: rector.php
================================================
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Utils\Rector\Rector\RenameMethods;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
'app/Providers/Filament/',
]);
$rectorConfig->ruleWithConfiguration(
\Rector\Renaming\Rector\MethodCall\RenameMethodRector::class,
[
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setProviders",
"providers",
),
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setRegistrationEnabled",
"registration",
),
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setSlug",
"slug",
),
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setLoginRouteName",
"loginRouteName",
),
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setDashboardRouteName",
"dashboardRouteName",
),
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setRememberLogin",
"rememberLogin",
),
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setSocialiteUserModelClass",
"socialiteUserModelClass",
),
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setDomainAllowList",
"domainAllowList",
),
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setUserModelClass",
"userModelClass",
),
new \Rector\Renaming\ValueObject\MethodCallRename(
"DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin",
"setShowDivider",
"showDivider",
),
]
);
};
================================================
FILE: resources/css/plugin.css
================================================
@import "tailwindcss";
@config '../../tailwind.config.js';
================================================
FILE: resources/dist/plugin.css
================================================
/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-800:oklch(27.8% .033 256.848);--color-white:#fff;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--font-weight-medium:500;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.static{position:static}.contents{display:contents}.flex{display:flex}.grid{display:grid}.inline-block{display:inline-block}.table{display:table}.h-px{height:1px}.w-full{width:100%}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-4{gap:calc(var(--spacing)*4)}.gap-y-6{row-gap:calc(var(--spacing)*6)}.rounded-full{border-radius:3.40282e38px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing)*2)}.text-center{text-align:center}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.text-gray-500{color:var(--color-gray-500)}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{background-color:var(--color-gray-800)}.dark\:text-gray-100{color:var(--color-gray-100)}}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}
================================================
FILE: resources/lang/en/auth.php
================================================
<?php
return [
'login-via' => 'Or log in via',
'login-failed' => 'Login failed, please try again.',
'user-not-allowed' => 'Your email is not part of a domain that is allowed.',
'registration-not-enabled' => 'Registration of a new user is not allowed.',
];
================================================
FILE: resources/views/.gitkeep
================================================
================================================
FILE: resources/views/components/buttons.blade.php
================================================
<div
x-data="{}"
x-load-css="[@js(\Filament\Support\Facades\FilamentAsset::getStyleHref('filament-socialite-styles', package: 'filament-socialite'))]"
>
<div class="flex flex-col gap-y-6">
@if ($messageBag->isNotEmpty())
@foreach($messageBag->all() as $value)
<p class="fi-fo-field-wrp-error-message text-danger-600 dark:text-danger-400">{{ __($value) }}</p>
@endforeach
@endif
@if (count($visibleProviders))
@if($showDivider)
<div class="relative flex items-center justify-center text-center">
<div class="absolute border-t border-gray-200 w-full h-px"></div>
<p class="inline-block relative bg-white text-sm p-2 rounded-full font-medium text-gray-500 dark:bg-gray-800 dark:text-gray-100">
{{ __('filament-socialite::auth.login-via') }}
</p>
</div>
@endif
<div class="grid @if(count($visibleProviders) > 1) grid-cols-2 @endif gap-4">
@foreach($visibleProviders as $key => $provider)
<x-filament::button
:color="$provider->getColor()"
:outlined="$provider->getOutlined()"
:icon="$provider->getIcon()"
tag="a"
:href="route($socialiteRoute, $key)"
:spa-mode="false"
>
{{ $provider->getLabel() }}
</x-filament::button>
@endforeach
</div>
@else
<span></span>
@endif
</div>
</div>
================================================
FILE: routes/web.php
================================================
<?php
use DutchCodingCompany\FilamentSocialite\Http\Controllers\SocialiteLoginController;
use DutchCodingCompany\FilamentSocialite\Http\Middleware\PanelFromUrlQuery;
use Filament\Facades\Filament;
use Illuminate\Support\Facades\Route;
foreach (Filament::getPanels() as $panel) {
if (! $panel->hasPlugin('filament-socialite')) {
continue;
}
// Retrieve slug for route name.
$slug = $panel->getPlugin('filament-socialite')->getSlug();
$domains = $panel->getDomains();
foreach ((empty($domains) ? [null] : $domains) as $domain) {
Filament::currentDomain($domain);
Route::domain($domain)
->middleware($panel->getMiddleware())
->name("socialite.{$panel->generateRouteName('oauth.redirect')}")
->get("/$slug/oauth/{provider}", [SocialiteLoginController::class, 'redirectToProvider']);
Route::domain($domain)
->match(['get', 'post'], "$slug/oauth/callback/{provider}", [SocialiteLoginController::class, 'processCallback'])
->middleware([
...$panel->getMiddleware(),
...config('filament-socialite.middleware'),
])
->name("socialite.{$panel->generateRouteName('oauth.callback')}");
Filament::currentDomain(null);
}
}
/**
* @note This route can only distinguish between Filament panels using the `state` input. If you have a stateless OAuth
* implementation, use the "$slug/oauth/callback/{provider}" route instead which has the panel in the URL itself.
*/
Route::match(['get', 'post'], "/oauth/callback/{provider}", [SocialiteLoginController::class, 'processCallback'])
->middleware([
PanelFromUrlQuery::class,
...config('filament-socialite.middleware'),
])
->name('oauth.callback');
================================================
FILE: src/Events/InvalidState.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Laravel\Socialite\Two\InvalidStateException;
class InvalidState
{
use Dispatchable;
use SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(
public InvalidStateException $exception,
) {
}
}
================================================
FILE: src/Events/Login.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Events;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Laravel\Socialite\Contracts\User as SocialiteUser;
class Login
{
use Dispatchable;
use SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(
public FilamentSocialiteUserContract $socialiteUser,
public SocialiteUser $oauthUser,
) {
}
}
================================================
FILE: src/Events/Registered.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Events;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
class Registered
{
use Dispatchable;
use SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(
public string $provider,
public SocialiteUserContract $oauthUser,
public FilamentSocialiteUserContract $socialiteUser,
) {
}
}
================================================
FILE: src/Events/RegistrationNotEnabled.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Events;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
class RegistrationNotEnabled
{
use Dispatchable;
use SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(
public string $provider,
public SocialiteUserContract $oauthUser,
public ?Authenticatable $user,
) {
}
}
================================================
FILE: src/Events/SocialiteUserConnected.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Events;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
class SocialiteUserConnected
{
use Dispatchable;
use SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(
public string $provider,
public SocialiteUserContract $oauthUser,
public FilamentSocialiteUserContract $socialiteUser,
) {
}
}
================================================
FILE: src/Events/UserNotAllowed.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
class UserNotAllowed
{
use Dispatchable;
use SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(
public SocialiteUserContract $oauthUser,
) {
}
}
================================================
FILE: src/Exceptions/GuardNotStateful.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Exceptions;
use LogicException;
class GuardNotStateful extends LogicException
{
public static function make(string $guard): self
{
return new self('Guard "'.$guard.'" is not stateful.');
}
}
================================================
FILE: src/Exceptions/ImplementationException.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Exceptions;
class ImplementationException extends \Exception
{
//
}
================================================
FILE: src/Exceptions/InvalidCallbackPayload.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Exceptions;
use LogicException;
use Throwable;
class InvalidCallbackPayload extends LogicException
{
public function __construct(?Throwable $previous = null)
{
parent::__construct('The panel could not be decrypted from the OAuth callback.', 0, $previous);
}
public static function make(): self
{
return new self(...func_get_args());
}
}
================================================
FILE: src/Exceptions/ProviderNotConfigured.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Exceptions;
use LogicException;
class ProviderNotConfigured extends LogicException
{
public static function make(string $provider): self
{
return new self('Provider "'.$provider.'" is not configured, please configure it in config/services.php and/or on your panel.');
}
}
================================================
FILE: src/FilamentSocialitePlugin.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite;
use Closure;
use DutchCodingCompany\FilamentSocialite\Exceptions\GuardNotStateful;
use DutchCodingCompany\FilamentSocialite\Exceptions\ImplementationException;
use DutchCodingCompany\FilamentSocialite\Exceptions\ProviderNotConfigured;
use Filament\Contracts\Plugin;
use Filament\Facades\Filament;
use Filament\Panel;
use Illuminate\Contracts\Auth\Factory;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class FilamentSocialitePlugin implements Plugin
{
use Traits\Callbacks;
use Traits\Routes;
use Traits\Models;
/**
* @var array<string, \DutchCodingCompany\FilamentSocialite\Provider>
*/
protected array $providers = [];
/**
* @var array<string>
*/
protected array $domainAllowList = [];
protected bool $rememberLogin = false;
/**
* @phpstan-var (\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, ?\Illuminate\Contracts\Auth\Authenticatable $user): bool) | bool
*/
protected Closure | bool $registration = false;
protected ?string $slug = null;
protected ?string $panelId = null;
protected bool $showDivider = true;
public function __construct(
protected Repository $config,
protected Factory $auth,
) {
//
}
public static function make(): static
{
return app(static::class);
}
public static function current(): static
{
if (Filament::getCurrentPanel()?->hasPlugin('filament-socialite')) {
/** @var static $plugin */
$plugin = Filament::getCurrentPanel()->getPlugin('filament-socialite');
return $plugin;
}
throw new ImplementationException('No current panel found with filament-socialite plugin.');
}
public function getId(): string
{
return 'filament-socialite';
}
public function register(Panel $panel): void
{
$this->panelId = $panel->getId();
}
public function boot(Panel $panel): void
{
//
}
/**
* @param array<array-key, \DutchCodingCompany\FilamentSocialite\Provider> $providers
*/
public function providers(array $providers): static
{
// Assign providers as key-value pairs with the provider name as the key.
$this->providers = Arr::mapWithKeys(
$providers,
static fn (Provider $value) => [$value->getName() => $value],
);
return $this;
}
/**
* @return array<string, \DutchCodingCompany\FilamentSocialite\Provider>
*/
public function getProviders(): array
{
return $this->providers;
}
public function getProvider(string $provider): Provider
{
if (! $this->isProviderConfigured($provider)) {
throw ProviderNotConfigured::make($provider);
}
return $this->providers[$provider];
}
public function slug(?string $slug): static
{
$this->slug = $slug;
return $this;
}
public function getSlug(): string
{
return $this->slug ?? rtrim($this->getPanel()->getPath(), '/');
}
public function rememberLogin(bool $value): static
{
$this->rememberLogin = $value;
return $this;
}
public function getRememberLogin(): bool
{
return $this->rememberLogin;
}
/**
* @param (\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, ?\Illuminate\Contracts\Auth\Authenticatable $user): bool) | bool $value
* @return $this
*/
public function registration(Closure | bool $value = true): static
{
$this->registration = $value;
return $this;
}
/**
* @return (\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, ?\Illuminate\Contracts\Auth\Authenticatable $user): bool) | bool
*/
public function getRegistration(): Closure | bool
{
return $this->registration;
}
/**
* @param array<string> $values
*/
public function domainAllowList(array $values): static
{
$this->domainAllowList = $values;
return $this;
}
/**
* @return array<string>
*/
public function getDomainAllowList(): array
{
return $this->domainAllowList;
}
public function isProviderConfigured(string $provider): bool
{
return $this->config->has('services.'.$provider) && isset($this->providers[$provider]);
}
public function showDivider(bool $divider): static
{
$this->showDivider = $divider;
return $this;
}
public function getShowDivider(): bool
{
return $this->showDivider;
}
public function getPanel(): Panel
{
return Filament::getPanel($this->getPanelId());
}
public function getPanelId(): string
{
return $this->panelId ?? throw new ImplementationException('Panel ID not set.');
}
public function getGuard(): StatefulGuard
{
$guard = $this->auth->guard(
$guardName = $this->getPanel()->getAuthGuard()
);
if ($guard instanceof StatefulGuard) {
return $guard;
}
throw GuardNotStateful::make($guardName);
}
}
================================================
FILE: src/FilamentSocialiteServiceProvider.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite;
use DutchCodingCompany\FilamentSocialite\View\Components\Buttons;
use Filament\Facades\Filament;
use Filament\Support\Assets\Css;
use Filament\Support\Facades\FilamentAsset;
use Filament\Support\Facades\FilamentView;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Support\Facades\Blade;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
class FilamentSocialiteServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
$package
->name('filament-socialite')
->hasConfigFile()
->hasTranslations()
->hasViews()
->hasRoute('web')
->hasMigration('create_socialite_users_table');
}
public function packageRegistered(): void
{
//
}
public function packageBooted(): void
{
Blade::componentNamespace('DutchCodingCompany\FilamentSocialite\View\Components', 'filament-socialite');
Blade::component('buttons', Buttons::class);
FilamentAsset::register([
Css::make('filament-socialite-styles', __DIR__.'/../resources/dist/plugin.css')->loadedOnRequest(),
], package: 'filament-socialite');
FilamentView::registerRenderHook(
'panels::auth.login.form.after',
static function (): ?string {
$panel = Filament::getCurrentPanel();
if (! $panel?->hasPlugin('filament-socialite')) {
return null;
}
/** @var \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin */
$plugin = $panel->getPlugin('filament-socialite');
return Blade::render('<x-filament-socialite::buttons :show-divider="'.($plugin->getShowDivider() ? 'true' : 'false').'" />');
},
);
if (
version_compare(app()->version(), '11.0', '>=')
&& method_exists(VerifyCsrfToken::class, 'except')
) {
VerifyCsrfToken::except([
'*/oauth/callback/*',
'oauth/callback/*',
]);
}
}
}
================================================
FILE: src/Http/Controllers/SocialiteLoginController.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Http\Controllers;
use DutchCodingCompany\FilamentSocialite\Events;
use DutchCodingCompany\FilamentSocialite\Exceptions\ProviderNotConfigured;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Http\Middleware\PanelFromUrlQuery;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use Filament\Support\Concerns\EvaluatesClosures;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\InvalidStateException;
use Symfony\Component\HttpFoundation\Response;
class SocialiteLoginController extends Controller
{
use EvaluatesClosures;
private ?FilamentSocialitePlugin $plugin = null;
public function redirectToProvider(string $provider): mixed
{
if (! $this->plugin()->isProviderConfigured($provider)) {
throw ProviderNotConfigured::make($provider);
}
/** @var \Laravel\Socialite\Two\AbstractProvider $driver */
$driver = Socialite::driver($provider);
$response = $driver
->with([
...$this->plugin()->getProvider($provider)->getWith(),
'state' => $state = PanelFromUrlQuery::encrypt($this->plugin()->getPanel()->getId()),
])
->scopes($this->plugin()->getProvider($provider)->getScopes())
->redirect();
// Set state value to be equal to the encrypted panel id. This value is used to
// retrieve the panel id once the authentication returns to our application,
// and it still prevents CSRF as it is non-guessable value.
session()->put('state', $state);
return $response;
}
protected function retrieveOauthUser(string $provider): ?SocialiteUserContract
{
$stateless = $this->plugin()->getProvider($provider)->getStateless();
try {
/** @var \Laravel\Socialite\Two\AbstractProvider $driver */
$driver = Socialite::driver($provider);
return $stateless
? $driver->stateless()->user()
: $driver->user();
} catch (InvalidStateException $e) {
Events\InvalidState::dispatch($e);
}
return null;
}
protected function retrieveSocialiteUser(string $provider, SocialiteUserContract $oauthUser): ?FilamentSocialiteUserContract
{
return $this->plugin()->getSocialiteUserModel()::findForProvider($provider, $oauthUser);
}
protected function redirectToLogin(string $message): RedirectResponse
{
// Add error message to the session, this way we can show an error message on the form.
session()->flash('filament-socialite-login-error', __($message));
return redirect()->route($this->plugin()->getLoginRouteName());
}
protected function authorizeUser(SocialiteUserContract $oauthUser): bool
{
return app()->call($this->plugin()->getAuthorizeUserUsing(), ['plugin' => $this->plugin(), 'oauthUser' => $oauthUser]);
}
protected function loginUser(string $provider, FilamentSocialiteUserContract $socialiteUser, SocialiteUserContract $oauthUser): Response
{
// Log the user in
$this->plugin()->getGuard()->login($socialiteUser->getUser(), $this->plugin()->getRememberLogin());
// Dispatch the login event
Events\Login::dispatch($socialiteUser, $oauthUser);
return app()->call($this->plugin()->getRedirectAfterLoginUsing(), ['provider' => $provider, 'socialiteUser' => $socialiteUser, 'plugin' => $this->plugin]);
}
protected function registerSocialiteUser(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): Response
{
// Create a socialite user
$socialiteUser = $this->plugin()->getSocialiteUserModel()::createForProvider($provider, $oauthUser, $user);
// Dispatch the socialite user connected event
Events\SocialiteUserConnected::dispatch($provider, $oauthUser, $socialiteUser);
// Login the user
return $this->loginUser($provider, $socialiteUser, $oauthUser);
}
protected function registerOauthUser(string $provider, SocialiteUserContract $oauthUser): Response
{
$socialiteUser = DB::transaction(function () use ($provider, $oauthUser) {
// Create a user
$user = app()->call($this->plugin()->getCreateUserUsing(), ['provider' => $provider, 'oauthUser' => $oauthUser, 'plugin' => $this->plugin]);
// Create a socialite user
return $this->plugin()->getSocialiteUserModel()::createForProvider($provider, $oauthUser, $user);
});
// Dispatch the registered event
Events\Registered::dispatch($provider, $oauthUser, $socialiteUser);
// Login the user
return $this->loginUser($provider, $socialiteUser, $oauthUser);
}
public function processCallback(string $provider): Response
{
if (! $this->plugin()->isProviderConfigured($provider)) {
throw ProviderNotConfigured::make($provider);
}
// Try to retrieve existing user
$oauthUser = $this->retrieveOauthUser($provider);
if (is_null($oauthUser)) {
return $this->redirectToLogin('filament-socialite::auth.login-failed');
}
// Verify if the user is authorized.
if (! $this->authorizeUser($oauthUser)) {
Events\UserNotAllowed::dispatch($oauthUser);
return $this->redirectToLogin('filament-socialite::auth.user-not-allowed');
}
// Try to find a socialite user
$socialiteUser = $this->retrieveSocialiteUser($provider, $oauthUser);
if ($socialiteUser) {
return $this->loginUser($provider, $socialiteUser, $oauthUser);
}
// See if a user already exists, but not for this socialite provider
$user = app()->call($this->plugin()->getResolveUserUsing(), [
'provider' => $provider,
'oauthUser' => $oauthUser,
'plugin' => $this->plugin,
]);
// See if registration is allowed
if (! $this->evaluate($this->plugin()->getRegistration(), ['provider' => $provider, 'oauthUser' => $oauthUser, 'user' => $user])) {
Events\RegistrationNotEnabled::dispatch($provider, $oauthUser, $user);
return $this->redirectToLogin('filament-socialite::auth.registration-not-enabled');
}
// Handle registration
return $user
? $this->registerSocialiteUser($provider, $oauthUser, $user)
: $this->registerOauthUser($provider, $oauthUser);
}
protected function plugin(): FilamentSocialitePlugin
{
return $this->plugin ??= FilamentSocialitePlugin::current();
}
}
================================================
FILE: src/Http/Middleware/PanelFromUrlQuery.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Http\Middleware;
use Closure;
use DutchCodingCompany\FilamentSocialite\Exceptions\InvalidCallbackPayload;
use Filament\Http\Middleware\SetUpPanel;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
/**
* @note This callback uses the `state` input to determine the correct panel ID. A simpler
* implementation is to use the "$slug/oauth/callback/{provider}" route instead, which
* contains the panel ID in the url itself.
*/
class PanelFromUrlQuery
{
public function handle(Request $request, Closure $next): mixed
{
// Set the current panel based on the encrypted panel name in the url query.
return (new SetUpPanel())->handle($request, $next, static::decrypt($request));
}
public static function encrypt(string $panel): string
{
return Crypt::encrypt($panel);
}
/**
* @throws InvalidCallbackPayload
*/
public static function decrypt(Request $request): string
{
try {
if (! is_string($request->input('state'))) {
throw new DecryptException('State is not a string.');
}
return Crypt::decrypt($request->input('state'));
} catch (DecryptException $e) {
throw InvalidCallbackPayload::make($e);
}
}
}
================================================
FILE: src/Models/Contracts/FilamentSocialiteUser.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Models\Contracts;
use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
interface FilamentSocialiteUser
{
public function getUser(): Authenticatable;
public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self;
public static function createForProvider(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): self;
}
================================================
FILE: src/Models/SocialiteUser.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Models;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
/**
* @property int $user_id
* @property string $provider
* @property int $provider_id
*/
class SocialiteUser extends Model implements FilamentSocialiteUserContract
{
protected $fillable = [
'user_id',
'provider',
'provider_id',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Illuminate\Database\Eloquent\Model, $this>
*/
public function user(): BelongsTo
{
/** @var class-string<\Illuminate\Database\Eloquent\Model&\Illuminate\Contracts\Auth\Authenticatable> */
$user = FilamentSocialitePlugin::current()->getUserModelClass();
return $this->belongsTo($user);
}
public function getUser(): Authenticatable
{
assert($this->user instanceof Authenticatable);
return $this->user;
}
public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self
{
return self::query()
->where('provider', $provider)
->where('provider_id', $oauthUser->getId())
->first();
}
public static function createForProvider(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): self
{
return self::query()
->create([
'user_id' => $user->getKey(),
'provider' => $provider,
'provider_id' => $oauthUser->getId(),
]);
}
}
================================================
FILE: src/Provider.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite;
use Closure;
use DutchCodingCompany\FilamentSocialite\Traits\CanBeHidden;
use Filament\Support\Colors\Color;
use Filament\Support\Concerns\EvaluatesClosures;
use Illuminate\Support\Str;
class Provider
{
use EvaluatesClosures;
use CanBeHidden;
protected string $name;
protected string $label;
protected string | null $icon = null;
/**
* @var 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
*/
protected string | array | null $color = Color::Gray;
protected bool $outlined = true;
/**
* @var \Closure(): array<mixed> | array<mixed>
*/
protected Closure | array $scopes = [];
/**
* @var \Closure(): array<mixed> | array<mixed>
*/
protected Closure | array $with = [];
protected bool $stateless = false;
public function __construct(string $name)
{
$this->name($name);
}
public static function make(string $name): static
{
return app(static::class, ['name' => $name]);
}
/**
* @param array<string, mixed> $attributes
*/
public function fill(array $attributes): static
{
foreach ($attributes as $key => $value) {
$this->{$key}($value);
}
return $this;
}
public function name(string $name): static
{
$this->name = $name;
return $this;
}
public function getName(): string
{
return $this->name;
}
public function label(string $label): static
{
$this->label = $label;
return $this;
}
public function getLabel(): string
{
return $this->label ?? Str::title($this->getName());
}
public function icon(string | null $icon): static
{
$this->icon = $icon;
return $this;
}
public function getIcon(): string | null
{
return $this->icon;
}
/**
* @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
*/
public function color(string | array | null $color): static
{
$this->color = $color;
return $this;
}
/**
* @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
*/
public function getColor(): string | array | null
{
return $this->color;
}
public function outlined(bool $outlined = true): static
{
$this->outlined = $outlined;
return $this;
}
public function getOutlined(): bool
{
return $this->outlined;
}
/**
* @param Closure(): array<mixed> | array<mixed> $scopes
*/
public function scopes(Closure | array $scopes): static
{
$this->scopes = $scopes;
return $this;
}
/**
* @return array<mixed>
*/
public function getScopes(): array
{
return $this->evaluate($this->scopes, ['provider' => $this]);
}
/**
* @param Closure(): array<mixed> | array<mixed> $with
*/
public function with(Closure | array $with): static
{
$this->with = $with;
return $this;
}
/**
* @return array<mixed>
*/
public function getWith(): array
{
return $this->evaluate($this->with, ['provider' => $this]);
}
public function stateless(bool $stateless = true): static
{
$this->stateless = $stateless;
return $this;
}
public function getStateless(): bool
{
return $this->stateless;
}
}
================================================
FILE: src/Traits/Callbacks.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Traits;
use Closure;
use DutchCodingCompany\FilamentSocialite\Exceptions\ImplementationException;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use Filament\Facades\Filament;
use Illuminate\Support\Str;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
trait Callbacks
{
/**
* @phpstan-var ?\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, self $socialite): \Illuminate\Contracts\Auth\Authenticatable
*/
protected ?Closure $createUserUsing = null;
/**
* @phpstan-var ?\Closure(string $provider, \DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser $socialiteUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Symfony\Component\HttpFoundation\Response
*/
protected ?Closure $redirectAfterLoginUsing = null;
/**
* @phpstan-var ?\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): ?(\Illuminate\Contracts\Auth\Authenticatable)
*/
protected ?Closure $resolveUserUsing = null;
/**
* @phpstan-var ?\Closure(\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin, \Laravel\Socialite\Contracts\User $oauthUser): bool
*/
protected ?Closure $authorizeUserUsing = null;
/**
* @param ?\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Illuminate\Contracts\Auth\Authenticatable $callback
*/
public function createUserUsing(?Closure $callback = null): static
{
$this->createUserUsing = $callback;
return $this;
}
/**
* @return \Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Illuminate\Contracts\Auth\Authenticatable
*/
public function getCreateUserUsing(): Closure
{
return $this->createUserUsing ?? function (
string $provider,
SocialiteUserContract $oauthUser,
FilamentSocialitePlugin $plugin,
) {
/**
* @var \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model&\Illuminate\Contracts\Auth\Authenticatable> $query
*/
$query = (new $this->userModelClass())->query();
return $query->create([
'name' => $oauthUser->getName(),
'email' => $oauthUser->getEmail(),
]);
};
}
/**
* @param \Closure(string $provider, \DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser $socialiteUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Illuminate\Http\RedirectResponse $callback
*/
public function redirectAfterLoginUsing(Closure $callback): static
{
$this->redirectAfterLoginUsing = $callback;
return $this;
}
/**
* @return \Closure(string $provider, \DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser $socialiteUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): \Symfony\Component\HttpFoundation\Response
*/
public function getRedirectAfterLoginUsing(): Closure
{
return $this->redirectAfterLoginUsing ?? function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) {
if (($panel = $this->getPanel())->hasTenancy()) {
$tenant = Filament::getUserDefaultTenant($socialiteUser->getUser());
if (is_null($tenant) && $tenantRegistrationUrl = $panel->getTenantRegistrationUrl()) {
return redirect()->intended($tenantRegistrationUrl);
}
return redirect()->intended(
$panel->getUrl($tenant)
);
}
return redirect()->intended(
$this->getPanel()->getUrl()
);
};
}
/**
* @param ?\Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): ?(\Illuminate\Contracts\Auth\Authenticatable) $callback
*/
public function resolveUserUsing(?Closure $callback = null): static
{
$this->resolveUserUsing = $callback;
return $this;
}
/**
* @return \Closure(string $provider, \Laravel\Socialite\Contracts\User $oauthUser, \DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin): ?(\Illuminate\Contracts\Auth\Authenticatable)
*/
public function getResolveUserUsing(): Closure
{
return $this->resolveUserUsing ?? function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {
/** @var \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model&\Illuminate\Contracts\Auth\Authenticatable> $model */
$model = (new $this->userModelClass());
return $model->where(
'email',
$oauthUser->getEmail()
)->first();
};
}
/**
* @param ?\Closure(\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin, \Laravel\Socialite\Contracts\User $oauthUser): bool $callback
*/
public function authorizeUserUsing(?Closure $callback = null): static
{
$this->authorizeUserUsing = $callback;
return $this;
}
/**
* @return \Closure(\DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin $plugin, \Laravel\Socialite\Contracts\User $oauthUser): bool
*/
public function getAuthorizeUserUsing(): Closure
{
return $this->authorizeUserUsing ?? static::checkDomainAllowList(...);
}
public static function checkDomainAllowList(FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser): bool
{
$domains = $plugin->getDomainAllowList();
// When no domains are specified, all users are allowed
if (count($domains) < 1) {
return true;
}
// Get the domain of the email for the specified user
$emailDomain = Str::of($oauthUser->getEmail() ?? throw new ImplementationException('User email is required.'))
->afterLast('@')
->lower()
->__toString();
// See if everything after @ is in the domains array
return in_array($emailDomain, $domains);
}
}
================================================
FILE: src/Traits/CanBeHidden.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Traits;
use Closure;
trait CanBeHidden
{
protected bool | Closure $isHidden = false;
protected bool | Closure $isVisible = true;
public function hidden(bool | Closure $condition = true): static
{
$this->isHidden = $condition;
return $this;
}
public function visible(bool | Closure $condition = true): static
{
$this->isVisible = $condition;
return $this;
}
public function isHidden(): bool
{
if ($this->evaluate($this->isHidden)) {
return true;
}
return ! $this->evaluate($this->isVisible);
}
public function isVisible(): bool
{
return ! $this->isHidden();
}
}
================================================
FILE: src/Traits/Models.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Traits;
use DutchCodingCompany\FilamentSocialite\Exceptions\ImplementationException;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use DutchCodingCompany\FilamentSocialite\Models\SocialiteUser;
trait Models
{
/**
* @var class-string<\Illuminate\Contracts\Auth\Authenticatable>
*/
protected string $userModelClass = \App\Models\User::class;
/**
* @var class-string<\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser>
*/
protected string $socialiteUserModelClass = SocialiteUser::class;
/**
* @param class-string<\Illuminate\Contracts\Auth\Authenticatable> $value
*/
public function userModelClass(string $value): static
{
$this->userModelClass = $value;
return $this;
}
/**
* @return class-string<\Illuminate\Contracts\Auth\Authenticatable>
*/
public function getUserModelClass(): string
{
return $this->userModelClass;
}
/**
* @param class-string<\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser> $value
*/
public function socialiteUserModelClass(string $value): static
{
$this->socialiteUserModelClass = $value;
return $this;
}
/**
* @return class-string<\DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser>
*/
public function getSocialiteUserModelClass(): string
{
return $this->socialiteUserModelClass;
}
public function getSocialiteUserModel(): FilamentSocialiteUserContract
{
return new ($this->getSocialiteUserModelClass());
}
}
================================================
FILE: src/Traits/Routes.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Traits;
trait Routes
{
protected ?string $loginRouteName = null;
protected ?string $dashboardRouteName = null;
public function getRoute(): string
{
return "socialite.{$this->getPanel()->generateRouteName('oauth.redirect')}";
}
public function loginRouteName(string $value): static
{
$this->loginRouteName = $value;
return $this;
}
public function getLoginRouteName(): string
{
return $this->loginRouteName ?? $this->getPanel()->generateRouteName('auth.login');
}
public function dashboardRouteName(string $value): static
{
$this->dashboardRouteName = $value;
return $this;
}
public function getDashboardRouteName(): string
{
return $this->dashboardRouteName ?? $this->getPanel()->generateRouteName('pages.dashboard');
}
}
================================================
FILE: src/View/Components/Buttons.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\View\Components;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Provider;
use Illuminate\Support\MessageBag;
use Illuminate\View\Component;
class Buttons extends Component
{
protected FilamentSocialitePlugin $plugin;
public function __construct(
public bool $showDivider = true,
) {
$this->plugin = FilamentSocialitePlugin::current();
}
/**
* @inheritDoc
*/
public function render()
{
$messageBag = new MessageBag();
if (session()->has('filament-socialite-login-error')) {
$messageBag->add('login-failed', session()->pull('filament-socialite-login-error'));
}
return view('filament-socialite::components.buttons', [
'providers' => $providers = $this->plugin->getProviders(),
'visibleProviders' => array_filter($providers, fn (Provider $provider) => $provider->isVisible()),
'socialiteRoute' => $this->plugin->getRoute(),
'messageBag' => $messageBag,
]);
}
}
================================================
FILE: tailwind.config.js
================================================
module.exports = {
content: ["./resources/views/**/*.blade.php"],
corePlugins: {
preflight: false,
},
}
================================================
FILE: tests/Fixtures/TestSocialiteUser.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Tests\Fixtures;
use Laravel\Socialite\Contracts\User;
class TestSocialiteUser implements User
{
public string $email = 'test@example.com';
public function getId()
{
return 'test-socialite-user-id';
}
public function getNickname()
{
return 'test-socialite-user-nickname';
}
public function getName()
{
return 'test-socialite-user-name';
}
public function getEmail()
{
return $this->email;
}
public function getAvatar()
{
return null;
}
}
================================================
FILE: tests/Fixtures/TestTeam.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Tests\Fixtures;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
class TestTeam extends Model
{
protected $table = 'teams';
}
================================================
FILE: tests/Fixtures/TestTenantUser.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Tests\Fixtures;
use Filament\Models\Contracts\HasTenants;
use Filament\Panel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Collection;
/**
* @property Collection<\DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestTeam> $teams
*/
class TestTenantUser extends TestUser implements HasTenants
{
public function canAccessTenant(Model $tenant): bool
{
return $this->teams->contains($tenant);
}
/**
* @return \Illuminate\Support\Collection<int, \DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestTeam>
*/
public function getTenants(Panel $panel): Collection
{
return $this->teams;
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestTeam, $this>
*/
public function teams(): BelongsToMany
{
return $this->belongsToMany(TestTeam::class, 'team_user', 'user_id', 'team_id');
}
}
================================================
FILE: tests/Fixtures/TestUser.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Tests\Fixtures;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
class TestUser extends Model implements Authenticatable
{
protected $table = 'users';
protected $guarded = [];
public function getAuthIdentifierName(): string
{
return 'test-user-auth-identifier-name';
}
public function getAuthIdentifier(): string
{
return 'test-user-auth-identifier';
}
public function getAuthPassword(): string
{
return 'test-user-auth-password';
}
public function getAuthPasswordName(): string
{
return 'test-user-auth-password';
}
public function getRememberToken(): string
{
return 'test-user-remember-token';
}
public function setRememberToken($value): void
{
//
}
public function getRememberTokenName(): string
{
return 'test-user-remember-token-name';
}
}
================================================
FILE: tests/Fixtures/change_nullable_password_on_users_table.php
================================================
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration {
public function up(): void
{
Schema::table('users', static function (Blueprint $table): void {
$table->string('password')->nullable()->change();
});
}
public function down(): void
{
//
}
};
================================================
FILE: tests/Fixtures/create_socialite_users_table.php
================================================
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration {
public function up(): void
{
Schema::create('socialite_users', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id');
$table->string('provider');
$table->string('provider_id');
$table->timestamps();
$table->unique([
'provider',
'provider_id',
]);
});
}
public function down(): void
{
Schema::dropIfExists('socialite_users');
}
};
================================================
FILE: tests/Fixtures/create_team_user_table.php
================================================
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration {
public function up(): void
{
Schema::create('team_user', function (Blueprint $table) {
$table->integer('team_id');
$table->integer('user_id');
$table->unique(['team_id', 'user_id']);
});
}
public function down(): void
{
Schema::dropIfExists('team_user');
}
};
================================================
FILE: tests/Fixtures/create_teams_table.php
================================================
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration {
public function up(): void
{
Schema::create('teams', function (Blueprint $table) {
$table->id();
$table->string('name');
});
}
public function down(): void
{
Schema::dropIfExists('teams');
}
};
================================================
FILE: tests/SocialiteLoginAuthorizationTest.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Tests;
use DutchCodingCompany\FilamentSocialite\Events\UserNotAllowed;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Provider as PluginProvider;
use DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestSocialiteUser;
use Filament\Facades\Filament;
use Filament\Pages\Dashboard;
use Filament\Panel;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Event;
use Laravel\Socialite\Contracts\Provider;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
use Laravel\Socialite\Facades\Socialite;
use LogicException;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
class SocialiteLoginAuthorizationTest extends TestCase
{
protected function registerTestPanel(): void
{
Filament::registerPanel(
fn (): Panel => Panel::make()
->default()
->id($this::getPanelName())
->path($this::getPanelName())
->tenant(...$this->tenantArguments)
->login()
->pages([
Dashboard::class,
])
->plugins([
FilamentSocialitePlugin::make()
->providers([
PluginProvider::make('github')
->label('GitHub')
->icon('fab-github')
->color('danger')
->outlined(false),
PluginProvider::make('gitlab')
->label('GitLab')
->icon('fab-gitlab')
->color('danger')
->outlined()
->scopes([])
->with([]),
])
->registration(true)
->userModelClass($this->userModelClass)
->authorizeUserUsing(function (FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser) {
return $oauthUser->getEmail() === 'test@example.com';
}),
]),
);
}
#[DataProvider('loginDataProvider')]
public function testAuthorizationLogin(string $email, bool $dispatchesUserNotAllowedEvent): void
{
Event::fake();
$response = $this
->getJson(route("socialite.filament.{$this::getPanelName()}.oauth.redirect", ['provider' => 'github']))
->assertStatus(302);
$state = session()->get('state');
$location = $response->headers->get('location') ?? throw new LogicException('Location header not set.');
parse_str($location, $urlQuery);
// Test if the correct state is sent to the endpoint in the "Location" header.
$this->assertEquals($state, $urlQuery['state']);
// Assert decrypting of the state gives the correct panel name.
$this->assertEquals($this::getPanelName(), Crypt::decrypt($state));
$user = new TestSocialiteUser();
$user->email = $email;
Socialite::shouldReceive('driver')
->with('github')
->andReturn(static::makeOAuthProviderMock(
request()->merge(['state' => $state]),
$user
));
// Fake oauth response.
$response = $this
->getJson(route("socialite.filament.{$this::getPanelName()}.oauth.callback", ['provider' => 'github', 'state' => $state]))
->assertStatus(302);
$dispatchesUserNotAllowedEvent
? Event::assertDispatched(UserNotAllowed::class)
: Event::assertNotDispatched(UserNotAllowed::class);
}
/**
* @return array<string, array{0: string, 1: bool}>
*/
public static function loginDataProvider(): array
{
return [
'User is authorized to use the application so UserNotAllowedEvent should not be dispatched' => [
'test@example.com',
false,
],
'User is not authorized to use the application so UserNotAllowedEvent should be dispatched' => [
'test@example1.com',
true,
],
];
}
}
================================================
FILE: tests/SocialiteLoginTest.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Tests;
use Closure;
use DutchCodingCompany\FilamentSocialite\Events\InvalidState;
use DutchCodingCompany\FilamentSocialite\Events\RegistrationNotEnabled;
use DutchCodingCompany\FilamentSocialite\Events\UserNotAllowed;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestSocialiteUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
use Laravel\Socialite\Facades\Socialite;
use LogicException;
use PHPUnit\Framework\Attributes\DataProvider;
class SocialiteLoginTest extends TestCase
{
#[DataProvider('loginDataProvider')]
public function testLogin(
string $email,
string $callbackRoute,
?string $overrideState = null,
?string $dispatchedErrorEvent = null,
): void {
Event::fake();
$response = $this
->getJson(route("socialite.filament.{$this::getPanelName()}.oauth.redirect", ['provider' => 'github']))
->assertStatus(302);
$state = session()->get('state');
$location = $response->headers->get('location') ?? throw new LogicException('Location header not set.');
parse_str($location, $urlQuery);
// Test if the correct state is sent to the endpoint in the "Location" header.
$this->assertEquals($state, $urlQuery['state']);
// Assert decrypting of the state gives the correct panel name.
$this->assertEquals($this::getPanelName(), Crypt::decrypt($state));
$user = new TestSocialiteUser();
$user->email = $email;
Socialite::shouldReceive('driver')
->with('github')
->andReturn(static::makeOAuthProviderMock(
request()->merge(['state' => $overrideState ?? $state]),
$user
));
// Fake oauth response.
$response = $this
->getJson(route($callbackRoute, ['provider' => 'github', 'state' => $state]))
->assertStatus(302);
if ($dispatchedErrorEvent) {
Event::assertDispatched($dispatchedErrorEvent);
$this->assertDatabaseMissing('socialite_users', [
'provider' => 'github',
'provider_id' => 'test-socialite-user-id',
]);
$this->assertDatabaseMissing('users', [
'name' => 'test-socialite-user-name',
'email' => $user->email,
]);
} else {
$this->assertDatabaseHas('socialite_users', [
'provider' => 'github',
'provider_id' => 'test-socialite-user-id',
]);
$this->assertDatabaseHas('users', [
'name' => 'test-socialite-user-name',
'email' => $user->email,
]);
}
}
/**
* @return array<string, array{0: string, 1: string, 2: ?string, 3: ?string}>
*/
public static function loginDataProvider(): array
{
return [
'Login fails when incorrect state (panelized callback route)' => [
'test@example.com',
// Use the new callback route that already contains the panel in the url.
'socialite.filament.'.static::getPanelName().'.oauth.callback',
'invalid-mocked-state',
InvalidState::class,
],
'Login fails when incorrect state (general callback route)' => [
'test@example.com',
// Use the old callback route that determines the panel based on the state parameter.
'oauth.callback',
'invalid-mocked-state',
InvalidState::class,
],
'Login succeeds when email in domain allow list' => [
'test@example.com',
'socialite.filament.'.static::getPanelName().'.oauth.callback',
null,
null,
],
'Login fails when email not in domain allow list' => [
'test@example1.com',
'socialite.filament.'.static::getPanelName().'.oauth.callback',
null,
UserNotAllowed::class,
],
];
}
#[DataProvider('registrationBlockProvider')]
public function testRegistrationBlock(bool $createUser, Closure | bool $registrationEnabled): void
{
Event::fake();
if ($createUser) {
DB::table('users')->insert([
'name' => 'test-user',
'email' => 'test@example.com',
]);
}
FilamentSocialitePlugin::current()->registration($registrationEnabled);
$this
->getJson(route("socialite.filament.{$this::getPanelName()}.oauth.redirect", ['provider' => 'github']))
->assertStatus(302);
$state = session()->get('state');
Socialite::shouldReceive('driver')
->with('github')
->andReturn(static::makeOAuthProviderMock(
request()->merge(['state' => $state]),
new TestSocialiteUser()
));
// Fake oauth response.
$this
->getJson(route("socialite.filament.{$this::getPanelName()}.oauth.callback", ['provider' => 'github', 'state' => $state]))
->assertStatus(302);
if (! $createUser) {
// If there is no user, the event should have been dispatched since the plugin option disabled registration.
Event::assertDispatched(RegistrationNotEnabled::class);
}
}
/**
* @return array<string, array<mixed>>
*/
public static function registrationBlockProvider(): array
{
$callback = function (string $provider, SocialiteUserContract $oauthUser, ?Authenticatable $user) {
return (bool) $user;
};
return [
'Authenticatable exists for socialite user' => [true, $callback],
'Authenticatable does not exist for socialite user' => [false, $callback],
'Registration is always blocked' => [true, false],
];
}
}
================================================
FILE: tests/SocialiteStatelessLoginTest.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Tests;
use DutchCodingCompany\FilamentSocialite\Events\InvalidState;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Provider;
use DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestSocialiteUser;
use Filament\Facades\Filament;
use Filament\Pages\Dashboard;
use Filament\Panel;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Event;
use Laravel\Socialite\Facades\Socialite;
use LogicException;
use PHPUnit\Framework\Attributes\DataProvider;
class SocialiteStatelessLoginTest extends TestCase
{
protected function registerTestPanel(): void
{
Filament::registerPanel(
fn (): Panel => Panel::make()
->default()
->id($this::getPanelName())
->path($this::getPanelName())
->tenant(...$this->tenantArguments)
->login()
->pages([
Dashboard::class,
])
->plugins([
FilamentSocialitePlugin::make()
->providers([
Provider::make('github')
->label('GitHub')
->icon('fab-github')
->color('danger')
->outlined(false)
->stateless(),
Provider::make('gitlab')
->label('GitLab')
->icon('fab-gitlab')
->color('danger')
->outlined()
->scopes([])
->with([]),
])
->registration(true)
->userModelClass($this->userModelClass)
->domainAllowList(['example.com']),
]),
);
}
#[DataProvider('statelessLoginDataProvider')]
public function testStatelessLogin(
string $email,
string $callbackRoute,
?string $overrideState = null,
?string $event = null,
): void {
Event::fake();
$response = $this
->getJson(route("socialite.filament.{$this::getPanelName()}.oauth.redirect", ['provider' => 'github']))
->assertStatus(302);
$state = session()->get('state');
$location = $response->headers->get('location') ?? throw new LogicException('Location header not set.');
parse_str($location, $urlQuery);
// Test if the correct state is sent to the endpoint in the "Location" header.
$this->assertEquals($state, $urlQuery['state']);
// Assert decrypting of the state gives the correct panel name.
$this->assertEquals($this::getPanelName(), Crypt::decrypt($state));
$user = new TestSocialiteUser();
$user->email = $email;
Socialite::shouldReceive('driver')
->with('github')
->andReturn(static::makeOAuthProviderMock(
request()->merge(['state' => $overrideState ?? $state]),
$user
));
// Fake oauth response.
$response = $this
->getJson(route($callbackRoute, ['provider' => 'github', 'state' => $state]))
->assertStatus(302);
if ($event !== null) {
Event::assertNotDispatched($event);
}
$this->assertDatabaseHas('socialite_users', [
'provider' => 'github',
'provider_id' => 'test-socialite-user-id',
]);
$this->assertDatabaseHas('users', [
'name' => 'test-socialite-user-name',
'email' => $user->email,
]);
}
/**
* @return array<string, array{0: string, 1: string, 2: ?string, 3: ?string}>
*/
public static function statelessLoginDataProvider(): array
{
return [
'Stateless login succeeds (panelized callback route)' => [
'test@example.com',
// Use the new callback route that already contains the panel in the url.
'socialite.filament.'.static::getPanelName().'.oauth.callback',
null,
InvalidState::class,
],
'Stateless login succeeds with mocked state (general callback route)' => [
'test@example.com',
// Use the old callback route that determines the panel based on the state parameter.
'oauth.callback',
'invalid-mocked-state',
InvalidState::class,
],
];
}
}
================================================
FILE: tests/SocialiteTenantLoginTest.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Tests;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use DutchCodingCompany\FilamentSocialite\Models\SocialiteUser;
use DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestSocialiteUser;
use DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestTeam;
use DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestTenantUser;
use Laravel\Socialite\Contracts\Provider;
use Laravel\Socialite\Facades\Socialite;
use LogicException;
use Mockery;
class SocialiteTenantLoginTest extends TestCase
{
protected string $userModelClass = TestTenantUser::class;
protected array $tenantArguments = [
TestTeam::class,
];
public function testTenantLogin(): void
{
FilamentSocialitePlugin::current()->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) {
assert($socialiteUser instanceof SocialiteUser);
$this->assertEquals($this::getPanelName(), $plugin->getPanel()->getId());
$this->assertEquals('github', $provider);
$this->assertEquals('github', $socialiteUser->provider);
$this->assertEquals('test-socialite-user-id', $socialiteUser->provider_id);
return redirect()->to('/some-tenant-url');
});
$response = $this
->getJson("/{$this::getPanelName()}/oauth/github")
->assertStatus(302);
$state = session()->get('state');
Socialite::shouldReceive('driver')
->with('github')
->andReturn(static::makeOAuthProviderMock(
request()->merge(['state' => $state]),
new TestSocialiteUser()
));
// Fake oauth response.
$response = $this
->getJson("/oauth/callback/github?state=$state")
->assertStatus(302);
$this->assertStringContainsString('/some-tenant-url', $response->headers->get('Location') ?? throw new LogicException('Location header not set.'));
$this->assertDatabaseHas('socialite_users', [
'provider' => 'github',
'provider_id' => 'test-socialite-user-id',
]);
$this->assertDatabaseHas('users', [
'name' => 'test-socialite-user-name',
'email' => 'test@example.com',
]);
}
}
================================================
FILE: tests/TestCase.php
================================================
<?php
namespace DutchCodingCompany\FilamentSocialite\Tests;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use DutchCodingCompany\FilamentSocialite\FilamentSocialiteServiceProvider;
use DutchCodingCompany\FilamentSocialite\Models\Contracts\FilamentSocialiteUser as FilamentSocialiteUserContract;
use DutchCodingCompany\FilamentSocialite\Models\SocialiteUser;
use DutchCodingCompany\FilamentSocialite\Provider;
use DutchCodingCompany\FilamentSocialite\Tests\Fixtures\TestUser;
use Filament\Facades\Filament;
use Filament\FilamentServiceProvider;
use Filament\Pages\Dashboard;
use Filament\Panel;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Encryption\Encrypter;
use Illuminate\Http\Request;
use Illuminate\Session\Middleware\StartSession;
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
use Laravel\Socialite\SocialiteServiceProvider;
use Laravel\Socialite\Two\AbstractProvider;
use Livewire\LivewireServiceProvider;
use Mockery\LegacyMockInterface;
use Orchestra\Testbench\TestCase as Orchestra;
class TestCase extends Orchestra
{
/**
* @var class-string<\Illuminate\Contracts\Auth\Authenticatable>
*/
protected string $userModelClass = TestUser::class;
/**
* @var array{0: ?class-string<\Illuminate\Database\Eloquent\Model>, 1?: ?string, 2?: ?string}
*/
protected array $tenantArguments = [null];
protected function setUp(): void
{
parent::setUp();
Filament::setCurrentPanel(self::getPanelName());
Factory::guessFactoryNamesUsing(
fn (
string $modelName,
) => 'DutchCodingCompany\\FilamentSocialite\\Database\\Factories\\'.class_basename($modelName).'Factory'
);
$this->app?->make(Kernel::class)->pushMiddleware(StartSession::class);
}
protected function getPackageProviders($app)
{
$this->registerTestPanel();
return [
FilamentServiceProvider::class,
FilamentSocialiteServiceProvider::class,
SocialiteServiceProvider::class,
LivewireServiceProvider::class,
];
}
protected function registerTestPanel(): void
{
Filament::registerPanel(
fn (): Panel => Panel::make()
->default()
->id($this::getPanelName())
->path($this::getPanelName())
->tenant(...$this->tenantArguments)
->login()
->pages([
Dashboard::class,
])
->plugins([
FilamentSocialitePlugin::make()
->providers([
Provider::make('github')
->label('GitHub')
->icon('fab-github')
->color('danger')
->outlined(false),
Provider::make('gitlab')
->label('GitLab')
->icon('fab-gitlab')
->color('danger')
->outlined()
->scopes([])
->with([]),
])
->registration(true)
->userModelClass($this->userModelClass)
->domainAllowList(['example.com']),
]),
);
}
public function getEnvironmentSetUp($app)
{
config()->set('app.key', 'base64:'.base64_encode(
Encrypter::generateKey('AES-256-CBC')
));
config()->set('services.github', [
'client_id' => 'abcdmockedabcd',
'client_secret' => 'defgmockeddefg',
'redirect' => 'http://localhost/oauth/callback/github',
]);
}
protected function defineDatabaseMigrations()
{
$this->loadLaravelMigrations();
$this->loadMigrationsFrom(__DIR__.'/Fixtures');
}
protected static function getPanelName(): string
{
return 'testpanel';
}
protected static function makeOAuthProviderMock(
Request $request,
SocialiteUserContract $user,
): LegacyMockInterface {
$mock = \Mockery::mock(
AbstractProvider::class,
[$request, 'test-client-id', 'test-client-secret', 'test-redirect-url']
)
->makePartial()
->shouldAllowMockingProtectedMethods();
$mock->shouldReceive('getAccessTokenResponse')->andReturn([]);
$mock->shouldReceive('getUserByToken')->andReturn([]);
$mock->shouldReceive('userInstance')->andReturn($user);
return $mock;
}
}
gitextract_wtq0hz3c/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── config.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── dependabot-auto-merge.yml
│ ├── php-cs-fixer.yml
│ ├── phpstan.yml
│ ├── run-tests.yml
│ └── update-changelog.yml
├── .gitignore
├── .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
├── package.json
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml
├── rector.php
├── resources/
│ ├── css/
│ │ └── plugin.css
│ ├── dist/
│ │ └── plugin.css
│ ├── 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
├── tailwind.config.js
└── tests/
├── Fixtures/
│ ├── TestSocialiteUser.php
│ ├── TestTeam.php
│ ├── TestTenantUser.php
│ ├── TestUser.php
│ ├── change_nullable_password_on_users_table.php
│ ├── create_socialite_users_table.php
│ ├── create_team_user_table.php
│ └── create_teams_table.php
├── SocialiteLoginAuthorizationTest.php
├── SocialiteLoginTest.php
├── SocialiteStatelessLoginTest.php
├── SocialiteTenantLoginTest.php
└── TestCase.php
SYMBOL INDEX (172 symbols across 35 files)
FILE: src/Events/InvalidState.php
class InvalidState (line 9) | class InvalidState
method __construct (line 19) | public function __construct(
FILE: src/Events/Login.php
class Login (line 10) | class Login
method __construct (line 20) | public function __construct(
FILE: src/Events/Registered.php
class Registered (line 10) | class Registered
method __construct (line 20) | public function __construct(
FILE: src/Events/RegistrationNotEnabled.php
class RegistrationNotEnabled (line 10) | class RegistrationNotEnabled
method __construct (line 20) | public function __construct(
FILE: src/Events/SocialiteUserConnected.php
class SocialiteUserConnected (line 10) | class SocialiteUserConnected
method __construct (line 20) | public function __construct(
FILE: src/Events/UserNotAllowed.php
class UserNotAllowed (line 9) | class UserNotAllowed
method __construct (line 19) | public function __construct(
FILE: src/Exceptions/GuardNotStateful.php
class GuardNotStateful (line 7) | class GuardNotStateful extends LogicException
method make (line 9) | public static function make(string $guard): self
FILE: src/Exceptions/ImplementationException.php
class ImplementationException (line 5) | class ImplementationException extends \Exception
FILE: src/Exceptions/InvalidCallbackPayload.php
class InvalidCallbackPayload (line 8) | class InvalidCallbackPayload extends LogicException
method __construct (line 10) | public function __construct(?Throwable $previous = null)
method make (line 15) | public static function make(): self
FILE: src/Exceptions/ProviderNotConfigured.php
class ProviderNotConfigured (line 7) | class ProviderNotConfigured extends LogicException
method make (line 9) | public static function make(string $provider): self
FILE: src/FilamentSocialitePlugin.php
class FilamentSocialitePlugin (line 18) | class FilamentSocialitePlugin implements Plugin
method __construct (line 47) | public function __construct(
method make (line 54) | public static function make(): static
method current (line 59) | public static function current(): static
method getId (line 71) | public function getId(): string
method register (line 76) | public function register(Panel $panel): void
method boot (line 81) | public function boot(Panel $panel): void
method providers (line 89) | public function providers(array $providers): static
method getProviders (line 103) | public function getProviders(): array
method getProvider (line 108) | public function getProvider(string $provider): Provider
method slug (line 117) | public function slug(?string $slug): static
method getSlug (line 124) | public function getSlug(): string
method rememberLogin (line 129) | public function rememberLogin(bool $value): static
method getRememberLogin (line 136) | public function getRememberLogin(): bool
method registration (line 145) | public function registration(Closure | bool $value = true): static
method getRegistration (line 155) | public function getRegistration(): Closure | bool
method domainAllowList (line 163) | public function domainAllowList(array $values): static
method getDomainAllowList (line 173) | public function getDomainAllowList(): array
method isProviderConfigured (line 178) | public function isProviderConfigured(string $provider): bool
method showDivider (line 183) | public function showDivider(bool $divider): static
method getShowDivider (line 190) | public function getShowDivider(): bool
method getPanel (line 195) | public function getPanel(): Panel
method getPanelId (line 200) | public function getPanelId(): string
method getGuard (line 205) | public function getGuard(): StatefulGuard
FILE: src/FilamentSocialiteServiceProvider.php
class FilamentSocialiteServiceProvider (line 15) | class FilamentSocialiteServiceProvider extends PackageServiceProvider
method configurePackage (line 17) | public function configurePackage(Package $package): void
method packageRegistered (line 28) | public function packageRegistered(): void
method packageBooted (line 33) | public function packageBooted(): void
FILE: src/Http/Controllers/SocialiteLoginController.php
class SocialiteLoginController (line 20) | class SocialiteLoginController extends Controller
method redirectToProvider (line 26) | public function redirectToProvider(string $provider): mixed
method retrieveOauthUser (line 51) | protected function retrieveOauthUser(string $provider): ?SocialiteUser...
method retrieveSocialiteUser (line 69) | protected function retrieveSocialiteUser(string $provider, SocialiteUs...
method redirectToLogin (line 74) | protected function redirectToLogin(string $message): RedirectResponse
method authorizeUser (line 82) | protected function authorizeUser(SocialiteUserContract $oauthUser): bool
method loginUser (line 87) | protected function loginUser(string $provider, FilamentSocialiteUserCo...
method registerSocialiteUser (line 98) | protected function registerSocialiteUser(string $provider, SocialiteUs...
method registerOauthUser (line 110) | protected function registerOauthUser(string $provider, SocialiteUserCo...
method processCallback (line 127) | public function processCallback(string $provider): Response
method plugin (line 173) | protected function plugin(): FilamentSocialitePlugin
FILE: src/Http/Middleware/PanelFromUrlQuery.php
class PanelFromUrlQuery (line 17) | class PanelFromUrlQuery
method handle (line 19) | public function handle(Request $request, Closure $next): mixed
method encrypt (line 25) | public static function encrypt(string $panel): string
method decrypt (line 33) | public static function decrypt(Request $request): string
FILE: src/Models/Contracts/FilamentSocialiteUser.php
type FilamentSocialiteUser (line 8) | interface FilamentSocialiteUser
method getUser (line 10) | public function getUser(): Authenticatable;
method findForProvider (line 12) | public static function findForProvider(string $provider, SocialiteUser...
method createForProvider (line 14) | public static function createForProvider(string $provider, SocialiteUs...
FILE: src/Models/SocialiteUser.php
class SocialiteUser (line 17) | class SocialiteUser extends Model implements FilamentSocialiteUserContract
method user (line 28) | public function user(): BelongsTo
method getUser (line 36) | public function getUser(): Authenticatable
method findForProvider (line 43) | public static function findForProvider(string $provider, SocialiteUser...
method createForProvider (line 51) | public static function createForProvider(string $provider, SocialiteUs...
FILE: src/Provider.php
class Provider (line 11) | class Provider
method __construct (line 41) | public function __construct(string $name)
method make (line 46) | public static function make(string $name): static
method fill (line 54) | public function fill(array $attributes): static
method name (line 63) | public function name(string $name): static
method getName (line 70) | public function getName(): string
method label (line 75) | public function label(string $label): static
method getLabel (line 82) | public function getLabel(): string
method icon (line 87) | public function icon(string | null $icon): static
method getIcon (line 94) | public function getIcon(): string | null
method color (line 102) | public function color(string | array | null $color): static
method getColor (line 112) | public function getColor(): string | array | null
method outlined (line 117) | public function outlined(bool $outlined = true): static
method getOutlined (line 124) | public function getOutlined(): bool
method scopes (line 132) | public function scopes(Closure | array $scopes): static
method getScopes (line 142) | public function getScopes(): array
method with (line 150) | public function with(Closure | array $with): static
method getWith (line 160) | public function getWith(): array
method stateless (line 165) | public function stateless(bool $stateless = true): static
method getStateless (line 172) | public function getStateless(): bool
FILE: src/Traits/Callbacks.php
type Callbacks (line 13) | trait Callbacks
method createUserUsing (line 38) | public function createUserUsing(?Closure $callback = null): static
method getCreateUserUsing (line 48) | public function getCreateUserUsing(): Closure
method redirectAfterLoginUsing (line 70) | public function redirectAfterLoginUsing(Closure $callback): static
method getRedirectAfterLoginUsing (line 80) | public function getRedirectAfterLoginUsing(): Closure
method resolveUserUsing (line 104) | public function resolveUserUsing(?Closure $callback = null): static
method getResolveUserUsing (line 114) | public function getResolveUserUsing(): Closure
method authorizeUserUsing (line 130) | public function authorizeUserUsing(?Closure $callback = null): static
method getAuthorizeUserUsing (line 140) | public function getAuthorizeUserUsing(): Closure
method checkDomainAllowList (line 145) | public static function checkDomainAllowList(FilamentSocialitePlugin $p...
FILE: src/Traits/CanBeHidden.php
type CanBeHidden (line 7) | trait CanBeHidden
method hidden (line 13) | public function hidden(bool | Closure $condition = true): static
method visible (line 20) | public function visible(bool | Closure $condition = true): static
method isHidden (line 27) | public function isHidden(): bool
method isVisible (line 36) | public function isVisible(): bool
FILE: src/Traits/Models.php
type Models (line 9) | trait Models
method userModelClass (line 24) | public function userModelClass(string $value): static
method getUserModelClass (line 34) | public function getUserModelClass(): string
method socialiteUserModelClass (line 42) | public function socialiteUserModelClass(string $value): static
method getSocialiteUserModelClass (line 52) | public function getSocialiteUserModelClass(): string
method getSocialiteUserModel (line 57) | public function getSocialiteUserModel(): FilamentSocialiteUserContract
FILE: src/Traits/Routes.php
type Routes (line 5) | trait Routes
method getRoute (line 11) | public function getRoute(): string
method loginRouteName (line 16) | public function loginRouteName(string $value): static
method getLoginRouteName (line 23) | public function getLoginRouteName(): string
method dashboardRouteName (line 28) | public function dashboardRouteName(string $value): static
method getDashboardRouteName (line 35) | public function getDashboardRouteName(): string
FILE: src/View/Components/Buttons.php
class Buttons (line 10) | class Buttons extends Component
method __construct (line 14) | public function __construct(
method render (line 23) | public function render()
FILE: tests/Fixtures/TestSocialiteUser.php
class TestSocialiteUser (line 7) | class TestSocialiteUser implements User
method getId (line 11) | public function getId()
method getNickname (line 16) | public function getNickname()
method getName (line 21) | public function getName()
method getEmail (line 26) | public function getEmail()
method getAvatar (line 31) | public function getAvatar()
FILE: tests/Fixtures/TestTeam.php
class TestTeam (line 8) | class TestTeam extends Model
FILE: tests/Fixtures/TestTenantUser.php
class TestTenantUser (line 14) | class TestTenantUser extends TestUser implements HasTenants
method canAccessTenant (line 16) | public function canAccessTenant(Model $tenant): bool
method getTenants (line 24) | public function getTenants(Panel $panel): Collection
method teams (line 32) | public function teams(): BelongsToMany
FILE: tests/Fixtures/TestUser.php
class TestUser (line 8) | class TestUser extends Model implements Authenticatable
method getAuthIdentifierName (line 14) | public function getAuthIdentifierName(): string
method getAuthIdentifier (line 19) | public function getAuthIdentifier(): string
method getAuthPassword (line 24) | public function getAuthPassword(): string
method getAuthPasswordName (line 29) | public function getAuthPasswordName(): string
method getRememberToken (line 34) | public function getRememberToken(): string
method setRememberToken (line 39) | public function setRememberToken($value): void
method getRememberTokenName (line 44) | public function getRememberTokenName(): string
FILE: tests/Fixtures/change_nullable_password_on_users_table.php
method up (line 8) | public function up(): void
method down (line 15) | public function down(): void
FILE: tests/Fixtures/create_socialite_users_table.php
method up (line 8) | public function up(): void
method down (line 26) | public function down(): void
FILE: tests/Fixtures/create_team_user_table.php
method up (line 8) | public function up(): void
method down (line 18) | public function down(): void
FILE: tests/Fixtures/create_teams_table.php
method up (line 8) | public function up(): void
method down (line 16) | public function down(): void
FILE: tests/SocialiteLoginAuthorizationTest.php
class SocialiteLoginAuthorizationTest (line 21) | class SocialiteLoginAuthorizationTest extends TestCase
method registerTestPanel (line 23) | protected function registerTestPanel(): void
method testAuthorizationLogin (line 60) | #[DataProvider('loginDataProvider')]
method loginDataProvider (line 104) | public static function loginDataProvider(): array
FILE: tests/SocialiteLoginTest.php
class SocialiteLoginTest (line 20) | class SocialiteLoginTest extends TestCase
method testLogin (line 22) | #[DataProvider('loginDataProvider')]
method loginDataProvider (line 90) | public static function loginDataProvider(): array
method testRegistrationBlock (line 122) | #[DataProvider('registrationBlockProvider')]
method registrationBlockProvider (line 163) | public static function registrationBlockProvider(): array
FILE: tests/SocialiteStatelessLoginTest.php
class SocialiteStatelessLoginTest (line 18) | class SocialiteStatelessLoginTest extends TestCase
method registerTestPanel (line 20) | protected function registerTestPanel(): void
method testStatelessLogin (line 56) | #[DataProvider('statelessLoginDataProvider')]
method statelessLoginDataProvider (line 114) | public static function statelessLoginDataProvider(): array
FILE: tests/SocialiteTenantLoginTest.php
class SocialiteTenantLoginTest (line 16) | class SocialiteTenantLoginTest extends TestCase
method testTenantLogin (line 24) | public function testTenantLogin(): void
FILE: tests/TestCase.php
class TestCase (line 27) | class TestCase extends Orchestra
method setUp (line 39) | protected function setUp(): void
method getPackageProviders (line 54) | protected function getPackageProviders($app)
method registerTestPanel (line 66) | protected function registerTestPanel(): void
method getEnvironmentSetUp (line 101) | public function getEnvironmentSetUp($app)
method defineDatabaseMigrations (line 114) | protected function defineDatabaseMigrations()
method getPanelName (line 120) | protected static function getPanelName(): string
method makeOAuthProviderMock (line 125) | protected static function makeOAuthProviderMock(
Condensed preview — 68 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (148K chars).
[
{
"path": ".editorconfig",
"chars": 220,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": ".gitattributes",
"chars": 671,
"preview": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 2338,
"preview": "name: Bug report\ndescription: Report a problem you're experiencing\nbody:\n - type: markdown\n attributes:\n value:"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 495,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Ask a question\n url: https://discord.com/channels/88308379211230"
},
{
"path": ".github/dependabot.yml",
"chars": 322,
"preview": "# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repositor"
},
{
"path": ".github/workflows/dependabot-auto-merge.yml",
"chars": 1030,
"preview": "name: dependabot-auto-merge\non: pull_request_target\n\npermissions:\n pull-requests: write\n contents: write\n\njobs:\n depe"
},
{
"path": ".github/workflows/php-cs-fixer.yml",
"chars": 507,
"preview": "name: Check & fix styling\n\non: [push]\n\njobs:\n php-cs-fixer:\n runs-on: ubuntu-latest\n\n steps:\n - name: Checko"
},
{
"path": ".github/workflows/phpstan.yml",
"chars": 1489,
"preview": "name: PHPStan\n\non:\n push:\n paths:\n - '**.php'\n - .github/workflows/phpstan.yml\n - phpstan.neon.dist\n "
},
{
"path": ".github/workflows/run-tests.yml",
"chars": 1732,
"preview": "name: run-tests\n\non:\n push:\n paths:\n - '**.php'\n - .github/workflows/run-tests.yml\n - phpunit.xml.dis"
},
{
"path": ".github/workflows/update-changelog.yml",
"chars": 645,
"preview": "name: \"Update Changelog\"\n\non:\n release:\n types: [released]\n\njobs:\n update:\n runs-on: ubuntu-latest\n\n steps:\n "
},
{
"path": ".gitignore",
"chars": 170,
"preview": ".idea\n.php_cs\n.php_cs.cache\n.phpunit.result.cache\nbuild\ncomposer.lock\npackage-lock.json\ncoverage\ndocs\nphpstan.neon\ntestb"
},
{
"path": ".php-cs-fixer.php",
"chars": 6085,
"preview": "<?php\n\nuse PhpCsFixer\\Config;\nuse PhpCsFixer\\Finder;\n\n$rules = [\n 'array_indentation' => true,\n 'array_syntax' => "
},
{
"path": "CHANGELOG.md",
"chars": 12944,
"preview": "# Changelog\n\nAll notable changes to `filament-socialite` will be documented in this file.\n\n## [3.2.1 - 2026-04-14](https"
},
{
"path": "LICENSE.md",
"chars": 1094,
"preview": "The MIT License (MIT)\n\nCopyright (c) DutchCodingCompany <m@rcoboe.rs>\n\nPermission is hereby granted, free of charge, to "
},
{
"path": "README.md",
"chars": 15029,
"preview": "<picture>\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://banners.beyondco.de/Filament%20Socialite.png?th"
},
{
"path": "SECURITY.md",
"chars": 621,
"preview": "# Security Policy\n\n## Supported Versions\n\nUse this section to tell people about which versions of your project are\ncurre"
},
{
"path": "UPGRADE.md",
"chars": 5340,
"preview": "# Upgrade Guide\n## `2.x.x` to `3.x.x` (Filament v4.x)\n\n### Slug\nIn v3 of the plugin, the panel's configured path is used"
},
{
"path": "bin/upgrade-v2",
"chars": 161,
"preview": "#!/usr/bin/env php\n<?php\n\nnamespace Composer;\n\nexec(\"vendor/bin/rector process --config vendor/dutchcodingcompany/filame"
},
{
"path": "composer.json",
"chars": 2503,
"preview": "{\n \"name\": \"dutchcodingcompany/filament-socialite\",\n \"description\": \"Social login for Filament through Laravel Soc"
},
{
"path": "config/filament-socialite.php",
"chars": 680,
"preview": "<?php\n\nreturn [\n\n /*\n |--------------------------------------------------------------------------\n | OAuth call"
},
{
"path": "database/migrations/create_socialite_users_table.php.stub",
"chars": 742,
"preview": "<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migratio"
},
{
"path": "package.json",
"chars": 556,
"preview": "{\n \"private\": true,\n \"name\": \"filament-socialite\",\n \"devDependencies\": {\n \"@tailwindcss/cli\": \"^4.1.10\","
},
{
"path": "phpstan-baseline.neon",
"chars": 2947,
"preview": "parameters:\n\tignoreErrors:\n\t\t-\n\t\t\tmessage: '#^Class App\\\\Models\\\\User not found\\.$#'\n\t\t\tidentifier: class.notFound\n\t\t\tco"
},
{
"path": "phpstan.neon.dist",
"chars": 192,
"preview": "includes:\n - ./vendor/larastan/larastan/extension.neon\n - ./phpstan-baseline.neon\n\nparameters:\n level: 8\n\n p"
},
{
"path": "phpunit.xml",
"chars": 955,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:noNam"
},
{
"path": "rector.php",
"chars": 2681,
"preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Rector\\Config\\RectorConfig;\nuse Utils\\Rector\\Rector\\RenameMethods;\n\nreturn static f"
},
{
"path": "resources/css/plugin.css",
"chars": 60,
"preview": "@import \"tailwindcss\";\n\n@config '../../tailwind.config.js';\n"
},
{
"path": "resources/dist/plugin.css",
"chars": 5711,
"preview": "/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */\n@layer properties{@supports (((-webkit-hyphens:none))"
},
{
"path": "resources/lang/en/auth.php",
"chars": 276,
"preview": "<?php\n\nreturn [\n 'login-via' => 'Or log in via',\n\n 'login-failed' => 'Login failed, please try again.',\n\n 'user"
},
{
"path": "resources/views/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "resources/views/components/buttons.blade.php",
"chars": 1710,
"preview": "<div\n x-data=\"{}\"\n x-load-css=\"[@js(\\Filament\\Support\\Facades\\FilamentAsset::getStyleHref('filament-socialite-styl"
},
{
"path": "routes/web.php",
"chars": 1799,
"preview": "<?php\n\nuse DutchCodingCompany\\FilamentSocialite\\Http\\Controllers\\SocialiteLoginController;\nuse DutchCodingCompany\\Filame"
},
{
"path": "src/Events/InvalidState.php",
"chars": 446,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Illumi"
},
{
"path": "src/Events/Login.php",
"chars": 612,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts"
},
{
"path": "src/Events/Registered.php",
"chars": 666,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts"
},
{
"path": "src/Events/RegistrationNotEnabled.php",
"chars": 589,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illumi"
},
{
"path": "src/Events/SocialiteUserConnected.php",
"chars": 678,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts"
},
{
"path": "src/Events/UserNotAllowed.php",
"chars": 462,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Illumi"
},
{
"path": "src/Exceptions/GuardNotStateful.php",
"chars": 267,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Exceptions;\n\nuse LogicException;\n\nclass GuardNotStateful extends L"
},
{
"path": "src/Exceptions/ImplementationException.php",
"chars": 127,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Exceptions;\n\nclass ImplementationException extends \\Exception\n{\n "
},
{
"path": "src/Exceptions/InvalidCallbackPayload.php",
"chars": 434,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Exceptions;\n\nuse LogicException;\nuse Throwable;\n\nclass InvalidCall"
},
{
"path": "src/Exceptions/ProviderNotConfigured.php",
"chars": 348,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Exceptions;\n\nuse LogicException;\n\nclass ProviderNotConfigured exte"
},
{
"path": "src/FilamentSocialitePlugin.php",
"chars": 5378,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSocialite\\Exceptions"
},
{
"path": "src/FilamentSocialiteServiceProvider.php",
"chars": 2253,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite;\n\nuse DutchCodingCompany\\FilamentSocialite\\View\\Components\\Buttons"
},
{
"path": "src/Http/Controllers/SocialiteLoginController.php",
"chars": 7059,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Http\\Controllers;\n\nuse DutchCodingCompany\\FilamentSocialite\\Events"
},
{
"path": "src/Http/Middleware/PanelFromUrlQuery.php",
"chars": 1390,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Http\\Middleware;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSoci"
},
{
"path": "src/Models/Contracts/FilamentSocialiteUser.php",
"chars": 500,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Models\\Contracts;\n\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\n"
},
{
"path": "src/Models/SocialiteUser.php",
"chars": 1880,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Models;\n\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialit"
},
{
"path": "src/Provider.php",
"chars": 3839,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSocialite\\Traits\\Can"
},
{
"path": "src/Traits/Callbacks.php",
"chars": 6743,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Traits;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSocialite\\Exc"
},
{
"path": "src/Traits/CanBeHidden.php",
"chars": 757,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Traits;\n\nuse Closure;\n\ntrait CanBeHidden\n{\n protected bool | Cl"
},
{
"path": "src/Traits/Models.php",
"chars": 1754,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Traits;\n\nuse DutchCodingCompany\\FilamentSocialite\\Exceptions\\Imple"
},
{
"path": "src/Traits/Routes.php",
"chars": 909,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Traits;\n\ntrait Routes\n{\n protected ?string $loginRouteName = nu"
},
{
"path": "src/View/Components/Buttons.php",
"chars": 1136,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\View\\Components;\n\nuse DutchCodingCompany\\FilamentSocialite\\Filamen"
},
{
"path": "tailwind.config.js",
"chars": 114,
"preview": "module.exports = {\n content: [\"./resources/views/**/*.blade.php\"],\n corePlugins: {\n preflight: false,\n },\n}\n"
},
{
"path": "tests/Fixtures/TestSocialiteUser.php",
"chars": 602,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures;\n\nuse Laravel\\Socialite\\Contracts\\User;\n\nclass Test"
},
{
"path": "tests/Fixtures/TestTeam.php",
"chars": 224,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures;\n\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nus"
},
{
"path": "tests/Fixtures/TestTenantUser.php",
"chars": 1079,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures;\n\nuse Filament\\Models\\Contracts\\HasTenants;\nuse Fil"
},
{
"path": "tests/Fixtures/TestUser.php",
"chars": 996,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures;\n\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nus"
},
{
"path": "tests/Fixtures/change_nullable_password_on_users_table.php",
"chars": 425,
"preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Fa"
},
{
"path": "tests/Fixtures/create_socialite_users_table.php",
"chars": 691,
"preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Fa"
},
{
"path": "tests/Fixtures/create_team_user_table.php",
"chars": 520,
"preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Fa"
},
{
"path": "tests/Fixtures/create_teams_table.php",
"chars": 441,
"preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Fa"
},
{
"path": "tests/SocialiteLoginAuthorizationTest.php",
"chars": 4389,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse DutchCodingCompany\\FilamentSocialite\\Events\\UserNotAll"
},
{
"path": "tests/SocialiteLoginTest.php",
"chars": 6302,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSocialite\\Even"
},
{
"path": "tests/SocialiteStatelessLoginTest.php",
"chars": 4757,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse DutchCodingCompany\\FilamentSocialite\\Events\\InvalidSta"
},
{
"path": "tests/SocialiteTenantLoginTest.php",
"chars": 2507,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialite"
},
{
"path": "tests/TestCase.php",
"chars": 4808,
"preview": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialite"
}
]
About this extraction
This page contains the full source code of the DutchCodingCompany/filament-socialite GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 68 files (134.6 KB), approximately 34.6k tokens, and a symbol index with 172 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.