[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.{yml,yaml}]\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and documentation with \"export-ignore\".\n/.github            export-ignore\n/.gitattributes     export-ignore\n/.gitignore         export-ignore\n/phpunit.xml.dist   export-ignore\n/art                export-ignore\n/docs               export-ignore\n/tests              export-ignore\n/.editorconfig      export-ignore\n/.php_cs.dist.php   export-ignore\n/psalm.xml          export-ignore\n/psalm.xml.dist     export-ignore\n/testbench.yaml     export-ignore\n/UPGRADING.md       export-ignore\n/phpstan.neon.dist  export-ignore\n/phpstan-baseline.neon  export-ignore\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: Report a problem you're experiencing\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Before opening a bug report, please search the existing issues (both open and closed).\n\n        ---\n\n        Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information.\n  - type: input\n    id: plugin-version\n    attributes:\n      label: Plugin Version\n      description: Please provide the version of filament socialite installed in your project.\n      placeholder: v2.0.0\n    validations:\n      required: true\n  - type: input\n    id: filament-version\n    attributes:\n      label: Filament Version\n      description: Please provide the full filament version of your project.\n      placeholder: v3.0.0\n    validations:\n      required: true\n  - type: input\n    id: laravel-version\n    attributes:\n      label: Laravel Version\n      description: Please provide the full Laravel version of your project.\n      placeholder: v10.0.0\n    validations:\n      required: true\n  - type: input\n    id: livewire-version\n    attributes:\n      label: Livewire Version\n      description: Please provide the full Livewire version of your project, if applicable.\n      placeholder: v3.0.0\n  - type: input\n    id: php-version\n    attributes:\n      label: PHP Version\n      description: Please provide the full PHP version of your server.\n      placeholder: PHP 8.3.0\n    validations:\n      required: true\n  - type: textarea\n    id: description\n    attributes:\n      label: Problem description\n      description: What happened when you experienced the problem?\n    validations:\n      required: true\n  - type: textarea\n    id: expectation\n    attributes:\n      label: Expected behavior\n      description: What did you expect to happen instead?\n    validations:\n      required: true\n  - type: textarea\n    id: steps\n    attributes:\n      label: Steps to reproduce\n      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.\n    validations:\n      required: true\n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log output\n      description: If applicable, provide relevant log output. No need for backticks here.\n      render: shell\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Ask a question\n    url: https://discord.com/channels/883083792112300104/962299008259342366\n    about: Ask the community for help\n  - name: Request a feature\n    url: https://discord.com/channels/883083792112300104/962299008259342366\n    about: Share ideas for new features\n  - name: Report a security issue\n    url: https://github.com/DutchCodingCompany/filament-socialite/security/policy\n    about: Learn how to notify us for sensitive bugs\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    labels:\n      - \"dependencies\"\n"
  },
  {
    "path": ".github/workflows/dependabot-auto-merge.yml",
    "content": "name: dependabot-auto-merge\non: pull_request_target\n\npermissions:\n  pull-requests: write\n  contents: write\n\njobs:\n  dependabot:\n    runs-on: ubuntu-latest\n    if: ${{ github.actor == 'dependabot[bot]' }}\n    steps:\n\n      - name: Dependabot metadata\n        id: metadata\n        uses: dependabot/fetch-metadata@v3.0.0\n        with:\n          github-token: \"${{ secrets.GITHUB_TOKEN }}\"\n\n      - name: Auto-merge Dependabot PRs for semver-minor updates\n        if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}\n        run: gh pr merge --auto --merge \"$PR_URL\"\n        env:\n          PR_URL: ${{github.event.pull_request.html_url}}\n          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}\n\n      - name: Auto-merge Dependabot PRs for semver-patch updates\n        if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}\n        run: gh pr merge --auto --merge \"$PR_URL\"\n        env:\n          PR_URL: ${{github.event.pull_request.html_url}}\n          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}\n"
  },
  {
    "path": ".github/workflows/php-cs-fixer.yml",
    "content": "name: Check & fix styling\n\non: [push]\n\njobs:\n  php-cs-fixer:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ github.head_ref }}\n\n      - name: Run PHP CS Fixer\n        uses: docker://oskarstark/php-cs-fixer-ga\n        with:\n          args: --config=.php-cs-fixer.php --allow-risky=yes\n\n      - name: Commit changes\n        uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          commit_message: Fix styling\n"
  },
  {
    "path": ".github/workflows/phpstan.yml",
    "content": "name: PHPStan\n\non:\n  push:\n    paths:\n      - '**.php'\n      - .github/workflows/phpstan.yml\n      - phpstan.neon.dist\n  pull_request:\n\njobs:\n  phpstan:\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest]\n        php: [8.4, 8.3, 8.2]\n        laravel: ['11.*', '12.*', '13.*']\n        stability: [prefer-stable]\n        include:\n          - laravel: 11.*\n            testbench: 9.*\n          - laravel: 12.*\n            testbench: 10.*\n          - laravel: 13.*\n            testbench: 11.*\n        exclude:\n          - laravel: 13.*\n            php: 8.2\n\n    name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.os }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo\n          coverage: none\n\n      - name: Install dependencies\n        run: |\n          composer require \"laravel/framework:${{ matrix.laravel }}\" \"orchestra/testbench:${{ matrix.testbench }}\" --no-interaction --no-update\n          composer update --${{ matrix.stability }} --prefer-dist --no-interaction\n\n      - name: List Installed Dependencies\n        run: composer show -D\n\n      - name: Run PHPStan\n        run: vendor/bin/phpstan --error-format=github\n"
  },
  {
    "path": ".github/workflows/run-tests.yml",
    "content": "name: run-tests\n\non:\n  push:\n    paths:\n      - '**.php'\n      - .github/workflows/run-tests.yml\n      - phpunit.xml.dist\n      - composer.json\n      - composer.lock\n  pull_request:\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n\n    timeout-minutes: 5\n\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest]\n        php: [8.4, 8.3, 8.2]\n        laravel: ['11.*', '12.*', '13.*']\n        stability: [prefer-stable]\n        include:\n          - laravel: 11.*\n            testbench: 9.*\n          - laravel: 12.*\n            testbench: 10.*\n          - laravel: 13.*\n            testbench: 11.*\n        exclude:\n          - laravel: 13.*\n            php: 8.2\n\n    name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.os }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo\n          coverage: none\n\n      - name: Setup problem matchers\n        run: |\n          echo \"::add-matcher::${{ runner.tool_cache }}/php.json\"\n          echo \"::add-matcher::${{ runner.tool_cache }}/phpunit.json\"\n\n      - name: Install dependencies\n        run: |\n          composer require \"laravel/framework:${{ matrix.laravel }}\" \"orchestra/testbench:${{ matrix.testbench }}\" --no-interaction --no-update\n          composer update --${{ matrix.stability }} --prefer-dist --no-interaction\n\n      - name: List Installed Dependencies\n        run: composer show -D\n\n      - name: Execute tests\n        run: vendor/bin/phpunit tests\n"
  },
  {
    "path": ".github/workflows/update-changelog.yml",
    "content": "name: \"Update Changelog\"\n\non:\n  release:\n    types: [released]\n\njobs:\n  update:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          ref: main\n\n      - name: Update Changelog\n        uses: stefanzweifel/changelog-updater-action@v1\n        with:\n          latest-version: ${{ github.event.release.name }}\n          release-notes: ${{ github.event.release.body }}\n\n      - name: Commit updated CHANGELOG\n        uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          branch: main\n          commit_message: Update CHANGELOG\n          file_pattern: CHANGELOG.md\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n.php_cs\n.php_cs.cache\n.phpunit.result.cache\nbuild\ncomposer.lock\npackage-lock.json\ncoverage\ndocs\nphpstan.neon\ntestbench.yaml\nvendor\nnode_modules\n.php-cs-fixer.cache\n"
  },
  {
    "path": ".php-cs-fixer.php",
    "content": "<?php\n\nuse PhpCsFixer\\Config;\nuse PhpCsFixer\\Finder;\n\n$rules = [\n    'array_indentation' => true,\n    'array_syntax' => ['syntax' => 'short'],\n    'binary_operator_spaces' => [\n        'default' => 'single_space',\n        // 'operators' => ['=>' => null], // single space makes code look more coherent in style. But sometimes it is not beter, in that case, manually override.\n    ],\n    'blank_line_after_namespace' => true,\n    'blank_line_after_opening_tag' => true,\n    'blank_line_before_statement' => [\n        'statements' => ['return'],\n    ],\n    'braces' => true,\n    'cast_spaces' => true,\n    'class_attributes_separation' => [\n        'elements' => [\n            'const' => 'only_if_meta',\n            'method' => 'one',\n            'property' => 'one',\n            'trait_import' => 'none',\n        ],\n    ],\n    'class_definition' => [\n        'multi_line_extends_each_single_line' => true,\n        'single_item_single_line' => true,\n        'single_line' => true,\n    ],\n    'concat_space' => [\n        'spacing' => 'none',\n    ],\n    'constant_case' => ['case' => 'lower'],\n    'declare_equal_normalize' => true,\n    'elseif' => true,\n    'encoding' => true,\n    'full_opening_tag' => true,\n    'fully_qualified_strict_types' => false,\n    // added by Shift\n    'function_declaration' => true,\n    'function_typehint_space' => true,\n    'general_phpdoc_tag_rename' => true,\n    'heredoc_to_nowdoc' => true,\n    'include' => true,\n    'increment_style' => ['style' => 'post'],\n    'indentation_type' => true,\n    'linebreak_after_opening_tag' => true,\n    'line_ending' => true,\n    'lowercase_cast' => true,\n    'lowercase_keywords' => true,\n    'lowercase_static_reference' => true,\n    // added from Symfony\n    'magic_method_casing' => true,\n    // added from Symfony\n    'magic_constant_casing' => true,\n    'method_argument_space' => [\n        'on_multiline' => 'ignore',\n    ],\n    'multiline_whitespace_before_semicolons' => [\n        'strategy' => 'no_multi_line',\n    ],\n    'native_function_casing' => true,\n    'no_alias_functions' => true,\n    'no_extra_blank_lines' => [\n        'tokens' => [\n            'extra',\n            'throw',\n            'use',\n            'switch',\n            'case',\n            'default',\n        ],\n    ],\n    'no_blank_lines_after_class_opening' => true,\n    'no_blank_lines_after_phpdoc' => true,\n    'no_closing_tag' => true,\n    'no_empty_phpdoc' => true,\n    'no_empty_statement' => true,\n    'no_leading_import_slash' => true,\n    'no_leading_namespace_whitespace' => true,\n    'no_mixed_echo_print' => [\n        'use' => 'echo',\n    ],\n    'no_multiline_whitespace_around_double_arrow' => true,\n    'no_short_bool_cast' => true,\n    'no_singleline_whitespace_before_semicolons' => true,\n    'no_spaces_after_function_name' => true,\n    'no_spaces_around_offset' => [\n        'positions' => [\n            'inside',\n            'outside',\n        ],\n    ],\n    'no_spaces_inside_parenthesis' => true,\n    'no_trailing_comma_in_list_call' => true,\n    'no_trailing_comma_in_singleline_array' => true,\n    'no_trailing_whitespace' => true,\n    'no_trailing_whitespace_in_comment' => true,\n    'no_unneeded_control_parentheses' => [\n        'statements' => [\n            'break',\n            'clone',\n            'continue',\n            'echo_print',\n            'return',\n            'switch_case',\n            'yield',\n        ],\n    ],\n    'no_unreachable_default_argument_value' => true,\n    'no_useless_return' => true,\n    'no_whitespace_before_comma_in_array' => true,\n    'no_whitespace_in_blank_line' => true,\n    'normalize_index_brace' => true,\n    'not_operator_with_successor_space' => true,\n    'object_operator_without_whitespace' => true,\n    'ordered_imports' => [\n        'sort_algorithm' => 'alpha',\n        'imports_order' => [\n            'class',\n            'function',\n            'const',\n        ],\n    ],\n    'psr_autoloading' => true,\n    'phpdoc_indent' => true,\n    'phpdoc_inline_tag_normalizer' => true,\n    'phpdoc_no_access' => true,\n    'phpdoc_no_package' => true,\n    'phpdoc_no_useless_inheritdoc' => true,\n    'phpdoc_scalar' => true,\n    'phpdoc_single_line_var_spacing' => true,\n    'phpdoc_summary' => false,\n    'phpdoc_to_comment' => false,\n    // override to preserve user preference\n    'phpdoc_tag_type' => true,\n    'phpdoc_trim' => true,\n    'phpdoc_types' => true,\n    'phpdoc_var_without_name' => true,\n    'self_accessor' => true,\n    'short_scalar_cast' => true,\n    'simplified_null_return' => false,\n    // disabled as \"risky\"\n    'single_blank_line_at_eof' => true,\n    'single_blank_line_before_namespace' => true,\n    'single_class_element_per_statement' => [\n        'elements' => [\n            'const',\n            'property',\n        ],\n    ],\n    'single_import_per_statement' => true,\n    'single_line_after_imports' => true,\n    'single_line_comment_style' => [\n        'comment_types' => ['hash'],\n    ],\n    'single_quote' => true,\n    'space_after_semicolon' => true,\n    'standardize_not_equals' => true,\n    'switch_case_semicolon_to_colon' => true,\n    'switch_case_space' => true,\n    'ternary_operator_spaces' => true,\n    'trailing_comma_in_multiline' => [\n        'elements' => [\n            'arrays',\n            'parameters',\n        ],\n    ],\n    'trim_array_spaces' => true,\n    'types_spaces' => [\n        'space' => 'single',\n    ],\n    'unary_operator_spaces' => true,\n    'visibility_required' => [\n        'elements' => [\n            'method',\n            'property',\n            'const',\n        ],\n    ],\n    'whitespace_after_comma_in_array' => true,\n\n    // DCC\n    'align_multiline_comment' => ['comment_type' => 'phpdocs_like'],\n    'simplified_if_return' => true,\n    'method_chaining_indentation' => true,\n];\n\n$finder = Finder::create()\n    ->in([\n        __DIR__ . '/src',\n        __DIR__ . '/tests',\n    ])\n    ->name('*.php')\n    ->notName('*.blade.php')\n    ->ignoreDotFiles(true)\n    ->ignoreVCS(true);\n\nreturn (new Config)\n    ->setFinder($finder)\n    ->setRules($rules)\n    ->setRiskyAllowed(true)\n    ->setUsingCache(true);\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to `filament-socialite` will be documented in this file.\n\n## [3.2.1 - 2026-04-14](https://github.com/DutchCodingCompany/filament-socialite/compare/3.2.0...3.2.1)\n\n## What's Changed\n* fix: update state retrieval method in PanelFromUrlQuery middleware by @fului in https://github.com/DutchCodingCompany/filament-socialite/pull/153\n* Bump dependabot/fetch-metadata from 2.5.0 to 3.0.0 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/151\n\n## [3.2.0 - 2026-03-19](https://github.com/DutchCodingCompany/filament-socialite/compare/3.1.0...3.2.0)\n\n## What's Changed\n* Add return type declarations to migration methods by @phh in https://github.com/DutchCodingCompany/filament-socialite/pull/149\n* Support laravel 13 https://github.com/DutchCodingCompany/filament-socialite/pull/150\n\n## [3.1.0 - 2026-01-19](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.1...3.1.0)\n\n## What's Changed\n- Add support for filament v5 by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/147\n- Bump dependabot/fetch-metadata from 2.4.0 to 2.5.0 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/145\n\n## [3.0.1 - 2025-12-16](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.0...3.0.1)\n\n## What's Changed\n- Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/135\n- Bump stefanzweifel/git-auto-commit-action from 6 to 7 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/139\n- Bump actions/checkout from 5 to 6 by @dependabot[bot] in https://github.com/DutchCodingCompany/filament-socialite/pull/141\n- Add foreign key constraint with cascade behaviour. by @chrillep in https://github.com/DutchCodingCompany/filament-socialite/pull/143\n  - **NOTE**: this will not change existing `socialite_users` tables. Please **consider adding** the constraint in a separate migration yourself \n\n## [3.0.0 - 2025-08-11](https://github.com/DutchCodingCompany/filament-socialite/compare/2.4.0...3.0.0)\n- Tag major version\n- BREAKING CHANGE: Implement fix for slug issue https://github.com/DutchCodingCompany/filament-socialite/issues/127  \n  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:\n  ```php\n  ->plugin(\n      FilamentSocialitePlugin::make()\n          ->slug('admin') // change this to the panel's ID\n          // other config for plugin\n  )\n  ```\n\n## [3.0.0-beta3 - 2025-07-18](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.0-beta2...3.0.0-beta3)\n\n## What's Changed\n* Include compiled styles\n  \n## [3.0.0-beta2 - 2025-06-23](https://github.com/DutchCodingCompany/filament-socialite/compare/3.0.0-alpha1...3.0.0-beta2)\n\n## What's Changed\n* BREAKING CHANGE: Implement fix for slug issue https://github.com/DutchCodingCompany/filament-socialite/issues/127  \n  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:\n  ```php\n  ->plugin(\n      FilamentSocialitePlugin::make()\n          ->slug('admin') // change this to the panel's ID\n          // other config for plugin\n  )\n  ```\n\n## [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)\n\n## What's Changed\n* Filament V4 support by @erikgaal in https://github.com/DutchCodingCompany/filament-socialite/pull/131\n\n## [2.4.0 - 2025-02-25](https://github.com/DutchCodingCompany/filament-socialite/compare/2.3.1...2.4.0)\n\n## What's Changed\n* Laravel 12.x Compatibility by @laravel-shift in https://github.com/DutchCodingCompany/filament-socialite/pull/125\n\n## [2.3.1 - 2025-02-06](https://github.com/DutchCodingCompany/filament-socialite/compare/2.3.0...2.3.1)\n\n## What's Changed\n* Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/123\n* Add data to events by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/124\n\n## [2.3.0 - 2024-11-29](https://github.com/DutchCodingCompany/filament-socialite/compare/2.2.1...2.3.0)\n\n## What's Changed\n* Add option to hide providers by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/122\n* Add support for php 8.4\n\n## [2.2.1 - 2024-07-17](https://github.com/DutchCodingCompany/filament-socialite/compare/2.2.0...2.2.1)\n\n## What's Changed\n* Revert model property changes by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/110\n\n## [2.2.0 - 2024-07-15](https://github.com/DutchCodingCompany/filament-socialite/compare/2.1.1...2.2.0)\n\n## What's Changed\n* Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/106\n* Add new callback route for stateless OAuth flows by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/105\n\n## [2.1.1 - 2024-06-21](https://github.com/DutchCodingCompany/filament-socialite/compare/2.1.0...2.1.1)\n\n## What's Changed\n* Improve Socialite driver typings + callable typings by @juliangums in https://github.com/DutchCodingCompany/filament-socialite/pull/103\n\n## New Contributors\n* @juliangums made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/103\n\n## [2.1.0 - 2024-06-21](https://github.com/DutchCodingCompany/filament-socialite/compare/2.0.0...2.1.0)\n\n* Add Authorization Callback by @petecoop in https://github.com/DutchCodingCompany/filament-socialite/pull/100\n\n## [2.0.0 - 2024-06-04](https://github.com/DutchCodingCompany/filament-socialite/compare/1.5.0...2.0.0)\n* **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.**\n* Refactor package for better consistency with Filament code standards https://github.com/DutchCodingCompany/filament-socialite/pull/90\n\n## [1.5.0 - 2024-06-04](https://github.com/DutchCodingCompany/filament-socialite/compare/1.4.1...1.5.0)\n\n* Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/89\n* Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/91\n* Compatible with Stateless Authentication by @LittleHans8 in https://github.com/DutchCodingCompany/filament-socialite/pull/96\n\n## [1.4.1 - 2024-03-20](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.4.0...1.4.1)\n* Provide oauth user to login event by @dcc-bjorn in https://github.com/DutchCodingCompany/filament-socialite/pull/88\n\n## [1.4.0 - 2024-03-12](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.3.1...1.4.0)\n* Laravel 11 support by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/87\n\n## [1.3.1 - 2024-03-05](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.3.0...1.3.1)\n* Add $provider as required by the callback by @phh in https://github.com/DutchCodingCompany/filament-socialite/pull/83\n* Never use SPA mode for oauth links + spacing by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/85\n\n## [1.3.0 - 2024-03-01](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.2.0...1.3.0)\n* Update CHANGELOG.md by @bramr94 in https://github.com/DutchCodingCompany/filament-socialite/pull/70\n* Add socialite test by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/78\n* Improve actions by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/79\n* Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/51\n* feature: allow socialite user model customization by @kykurniawan in https://github.com/DutchCodingCompany/filament-socialite/pull/72\n* Add registration enabled callable by @bert-w in https://github.com/DutchCodingCompany/filament-socialite/pull/80\n* Multi-tenancy support by @bramr94 in https://github.com/DutchCodingCompany/filament-socialite/pull/76\n\n## [1.2.0 - 2024-01-31](https://github.com/DutchCodingCompany/filament-socialite/compare/v1.1.1...1.2.0)\n- Add option to add optional parameters in https://github.com/DutchCodingCompany/filament-socialite/pull/69\n\n## [1.1.1 - 2024-01-18](https://github.com/DutchCodingCompany/filament-socialite/compare/1.1.0...1.1.1)\n- Improve domain routing in https://github.com/DutchCodingCompany/filament-socialite/pull/61\n- Update README in https://github.com/DutchCodingCompany/filament-socialite/pull/64\n\n## [1.1.0 - 2024-01-08](https://github.com/DutchCodingCompany/filament-socialite/compare/1.0.1...1.1.0)\n- Add button customization options in https://github.com/DutchCodingCompany/filament-socialite/pull/59\n\n## [1.0.1 - 2023-12-18](https://github.com/DutchCodingCompany/filament-socialite/compare/1.0.0...1.0.1)\n- Resolve plugin registration issue [#54](https://github.com/DutchCodingCompany/filament-socialite/issues/54)\n\n## [1.0.0 - 2023-12-05](https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.2...1.0.0)\n- Added support for Filament v3 through the plugin setup\n- Added support for multiple panels\n- See [UPGRADE.md](UPGRADE.md)\n\n## 0.2.2 - 2022-06-14\n\n### What's Changed\n\n- Fix readme by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/15\n- use Filament-fortify render hook by @wychoong in https://github.com/DutchCodingCompany/filament-socialite/pull/16\n\n### New Contributors\n\n- @wychoong made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/16\n\n**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.1...0.2.2\n\n## 0.2.1 - 2022-05-25\n\n## What's Changed\n\n- Fix user model instantiating by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/14\n\n**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.2.0...0.2.1\n\n## 0.2.0 - 2022-05-24\n\n## Breaking changes\n\n- `Events\\DomainFailed` renamed to `Events\\UserNotAllowed`\n- `Events\\RegistrationFailed` renamed to `Events\\RegistrationNotEnabled`\n\n## What's Changed\n\n- Refactor the controller for extendability and customization by @dododedodonl in https://github.com/DutchCodingCompany/filament-socialite/pull/13\n\n## New Contributors\n\n- @dododedodonl made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/13\n\n**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.5...0.2.0\n\n## 0.1.5 - 2022-05-20\n\n## What's Changed\n\n- Fix missing variable for registered event by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/11\n\n**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.4...0.1.5\n\n## 0.1.4 - 2022-05-06\n\n## What's Changed\n\n- Feature: Adds buttons blade component by @oyepez003 in https://github.com/DutchCodingCompany/filament-socialite/pull/8\n- Feature: Add login events dispatching by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/5\n\n**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.3...0.1.4\n\n## 0.1.3 - 2022-05-04\n\n## What's Changed\n\n- Bugfix: Avoid returning 403 when a user exists based on the oauth-email . by @oyepez003 in https://github.com/DutchCodingCompany/filament-socialite/pull/7\n\n## New Contributors\n\n- @oyepez003 made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/7\n\n**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.2...0.1.3\n\n## 0.1.2 - 2022-05-03\n\n## What's Changed\n\n- Bump dependabot/fetch-metadata from 1.3.0 to 1.3.1 by @dependabot in https://github.com/DutchCodingCompany/filament-socialite/pull/2\n- Add Laravel 8 support and make fontawesome icons optional by @marcoboers in https://github.com/DutchCodingCompany/filament-socialite/pull/4\n\n## New Contributors\n\n- @marcoboers made their first contribution in https://github.com/DutchCodingCompany/filament-socialite/pull/4\n\n**Full Changelog**: https://github.com/DutchCodingCompany/filament-socialite/compare/0.1.1...0.1.2\n\n## 0.1.1 - 2022-04-11\n\n## What's Changed\n\n- Fix registration flow\n\n## 0.1.0 - 2022-04-08\n\n### Initial Release\n\n- Add social login links to login page\n- Support Socialite OAuth flow\n- Support registration flow\n- Support domain allowlist for internal use\n- Dark mode support\n- Blade Font Awesome brand icons\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) DutchCodingCompany <m@rcoboe.rs>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<picture>\n  <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\">\n  <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\">\n</picture>\n\n# Social login for Filament through Laravel Socialite\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/dutchcodingcompany/filament-socialite.svg?style=flat-square)](https://packagist.org/packages/dutchcodingcompany/filament-socialite)\n[![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/dutchcodingcompany/filament-socialite/run-tests?label=tests)](https://github.com/dutchcodingcompany/filament-socialite/actions?query=workflow%3Arun-tests+branch%3Amain)\n[![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/dutchcodingcompany/filament-socialite/Check%20&%20fix%20styling?label=code%20style)](https://github.com/dutchcodingcompany/filament-socialite/actions?query=workflow%3A\"Check+%26+fix+styling\"+branch%3Amain)\n[![Total Downloads](https://img.shields.io/packagist/dt/dutchcodingcompany/filament-socialite.svg?style=flat-square)](https://packagist.org/packages/dutchcodingcompany/filament-socialite)\n\nAdd OAuth2 login through Laravel Socialite to Filament. OAuth1 (eg. Twitter) is not supported at this time.\n\n## Installation\n\n| Filament version                                                                                                                                               | Package version | Readme                                                                               |\n|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|--------------------------------------------------------------------------------------|\n| [^5.0.0](https://github.com/filamentphp/filament/tree/5.x)                                                                              | ^3.1           | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/main/README.md) |\n| [^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) |\n| [^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) |\n| [^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          |                                                                                      |\n| 3.x                                                                                                                                                            | 1.x.x           | [Link](https://github.com/DutchCodingCompany/filament-socialite/blob/1.x/README.md)  |\n| 2.x                                                                                                                                                            | 0.x.x           |                                                                                      |\n\nInstall the package via composer:\n\n```bash\ncomposer require dutchcodingcompany/filament-socialite\n```\n\nPublish and migrate the migration file:\n\n```bash\nphp artisan vendor:publish --tag=\"filament-socialite-migrations\"\nphp artisan migrate\n```\n\nOther configuration files include:\n```bash\nphp artisan vendor:publish --tag=\"filament-socialite-config\"\nphp artisan vendor:publish --tag=\"filament-socialite-views\"\nphp artisan vendor:publish --tag=\"filament-socialite-translations\"\n```\n\nYou need to register the plugin in the Filament panel provider (the default filename is `app/Providers/Filament/AdminPanelProvider.php`). The following options are available:\n\n```php\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Provider;\nuse Filament\\Support\\Colors;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\n\n// ...\n->plugin(\n    FilamentSocialitePlugin::make()\n        // (required) Add providers corresponding with providers in `config/services.php`. \n        ->providers([\n            // Create a provider 'gitlab' corresponding to the Socialite driver with the same name.\n            Provider::make('gitlab')\n                ->label('GitLab')\n                ->icon('fab-gitlab')\n                ->color(Color::hex('#2f2a6b'))\n                ->outlined(false)\n                ->stateless(false)\n                ->scopes(['...'])\n                ->with(['...']),\n        ])\n        // (optional) Override the panel slug to be used in the oauth routes. Defaults to the panel's configured path.\n        ->slug('admin')\n        // (optional) Enable/disable registration of new (socialite-) users.\n        ->registration(true)\n        // (optional) Enable/disable registration of new (socialite-) users using a callback.\n        // In this example, a login flow can only continue if there exists a user (Authenticatable) already.\n        ->registration(fn (string $provider, SocialiteUserContract $oauthUser, ?Authenticatable $user) => (bool) $user)\n        // (optional) Change the associated model class.\n        ->userModelClass(\\App\\Models\\User::class)\n        // (optional) Change the associated socialite class (see below).\n        ->socialiteUserModelClass(\\App\\Models\\SocialiteUser::class)\n);\n```\n\nThis package automatically adds 2 routes per panel to make the OAuth flow possible: a redirector and a callback. When\nsetting up your **external OAuth app configuration**, enter the following callback URL (in this case for the Filament\npanel with ID `admin` and the `github` provider):\n```\nhttps://example.com/admin/oauth/callback/github\n```\n\nA multi-panel callback route is available as well that does not contain the panel ID in the url. Instead, it determines\nthe panel ID from an encrypted `state` input (`...?state=abcd1234`). This allows you to create a single OAuth\napplication for multiple Filament panels that use the same callback URL. Note that this only works for _stateful_ OAuth\napps:\n\n```\nhttps://example.com/oauth/callback/github\n```\n\nIf in doubt, run `php artisan route:list` to see which routes are available to you.\n\n### Icons\n\nYou can specify a custom icon for each of your login providers. You can add Font Awesome brand\nicons made available through [Blade Font Awesome](https://github.com/owenvoke/blade-fontawesome) by running:\n```bash\ncomposer require owenvoke/blade-fontawesome\n```\n\n### Registration flow\n\nThis package supports account creation for users. However, to support this flow it is important that the `password`\nattribute on your `User` model is nullable. For example, by adding the following to your users table migration.\nOr you could opt for customizing the user creation, see below.\n\n```php\n$table->string('password')->nullable();\n```\n\n### Domain Allow list\n\nThis package supports the option to limit the users that can login with the OAuth login to users of a certain domain.\nThis can be used to setup SSO for internal use.\n\n```php\n->plugin(\n    FilamentSocialitePlugin::make()\n        // ...\n        ->registration(true)\n        ->domainAllowList(['localhost'])\n);\n```\n\n### Changing how an Authenticatable user is created or retrieved\n\nYou can use the `createUserUsing` and `resolveUserUsing` methods to change how a user is created or retrieved.\n\n```php\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\n->plugin(\n    FilamentSocialitePlugin::make()\n        // ...\n        ->createUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {\n            // Logic to create a new user.\n        })\n        ->resolveUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {\n            // Logic to retrieve an existing user.\n        })\n        ...\n);\n```\n\n### Change how a Socialite user is created or retrieved\n\nIn your plugin options in your Filament panel, add the following method:\n\n```php\n// app/Providers/Filament/AdminPanelProvider.php\n->plugins([\n    FilamentSocialitePlugin::make()\n        // ...\n        ->socialiteUserModelClass(\\App\\Models\\SocialiteUser::class)\n```\n\nThis class should at the minimum implement the [`FilamentSocialiteUser`](/src/Models/Contracts/FilamentSocialiteUser.php) interface, like so:\n\n```php\nnamespace App\\Models;\n\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\nclass SocialiteUser implements FilamentSocialiteUserContract\n{\n    public function getUser(): Authenticatable\n    {\n        //\n    }\n\n    public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self\n    {\n        //\n    }\n    \n    public static function createForProvider(\n        string $provider,\n        SocialiteUserContract $oauthUser,\n        Authenticatable $user\n    ): self {\n        //\n    }\n}\n```\n\n### Check if the user is authorized to use the application\n\nYou 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.\n\n```php\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\n->plugin(\n    FilamentSocialitePlugin::make()\n        // ...\n        ->authorizeUserUsing(function (FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser) {\n            // Logic to authorize the user.\n            return FilamentSocialitePlugin::checkDomainAllowList($plugin, $oauthUser);\n        })\n        // ...\n);\n```\n\n### Change login redirect\n\nWhen 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).\nIf you want to change this behavior, you can call the 'redirectAfterLoginUsing' method on the `FilamentSocialitePlugin`.\n\n```php\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\SocialiteUser;\n\nFilamentSocialitePlugin::make()\n    ->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) {\n        // Change the redirect behaviour here.\n    });\n```\n\n## Events\n\nThere are a few events dispatched during the authentication process:\n\n* `InvalidState(InvalidStateException $exception)`: When trying to retrieve the oauth (socialite) user, an invalid state was encountered\n* `Login(FilamentSocialiteUserContract $socialiteUser)`: When a user successfully logs in\n* `Registered(string $provider, SocialiteUserContract $oauthUser, FilamentSocialiteUserContract $socialiteUser)`: When a user and socialite user is successfully registered and logged in (when enabled in config)\n* `RegistrationNotEnabled(string $provider, SocialiteUserContract $oauthUser, ?Auhthenticatable $user)`: When a user tries to login with an unknown account and registration is not enabled\n* `SocialiteUserConnected(string $provider, SocialiteUserContract $oauthUser, FilamentSocialiteUserContract $socialiteUser)`: When a socialite user is created for an existing user\n* `UserNotAllowed(SocialiteUserContract $oauthUser)`: When a user tries to login with an email which domain is not on the allowlist\n\n## Scopes\n\nScopes can be added to the provider on the panel, for example:\n\n```php\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Provider;\n\nFilamentSocialitePlugin::make()\n    ->providers([\n        Provider::make('github')\n            ->label('Github')\n            ->icon('fab-github')\n            ->scopes([\n                // Add scopes here.\n                'read:user',\n                'public_repo',\n            ]),\n    ]),\n```\n\n## Optional parameters\n\nYou 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:\n\n```php\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Provider;\n\nFilamentSocialitePlugin::make()\n    ->providers([\n        Provider::make('github')\n            ->label('Github')\n            ->icon('fab-github')\n            ->with([\n                // Add scopes here.\n                // Add optional parameters here.\n                'hd' => 'example.com',\n            ]),\n    ]),\n```\n## Visibility\n\nYou can set the visibility of a provider, if it is not visible, buttons will not be rendered. All functionality will still be enabled.\n\n```php\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Provider;\n\nFilamentSocialitePlugin::make()\n    ->providers([\n        Provider::make('github')\n            ->visible(fn () => true),\n    ]),\n```\n\n## Stateless Authentication\nYou can add `stateless` parameters to the provider configuration in the config/services.php config file, for example:\n\n```php\n'apple' => [\n    'client_id' => '...',\n    'client_secret' => '...',\n    'stateless'=>true,\n]\n```\n\n**Note:** you cannot use the `state` parameter, as it is used to determine from which Filament panel the user came from.\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.\n\n## Security Vulnerabilities\n\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## Credits\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nUse this section to tell people about which versions of your project are\ncurrently being supported with security updates.\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 3.x   | :white_check_mark: |\n| 2.x   | :warning: (security fixes only) |\n| 1.x   | :no_entry_sign: |\n| 0.x   | :no_entry_sign: |\n\n## Reporting a Vulnerability\n\nIf you discover a security vulnerability within this plugin, please email Dutch Coding Company via [server@dutchcodingcompany.com](mailto:server@dutchcodingcompany.com). \nAll security vulnerabilities will be promptly addressed.\n"
  },
  {
    "path": "UPGRADE.md",
    "content": "# 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 instead of it's ID when generating the callback URLs.  \nIn order to revert to previous behaviour, use slug to override the behaviour:\n  ```php\n  ->plugin(\n      FilamentSocialitePlugin::make()\n          ->slug('admin') // change this to the panel's ID\n          // other config for plugin\n  )\n  ```\n\n## `1.x.x` to `2.x.x` (Filament v3.x)\n\nFor 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.\n\n### Method names\n\nEvery method name has been changed to be more consistent with the Filament naming conventions. The following changes have been made:\n\n- `setProviders()` -> `providers()` \n- `setSlug()` -> `slug()` \n- `setLoginRouteName()` -> `loginRouteName()` \n- `setDashboardRouteName()` -> `dashboardRouteName()` \n- `setRememberLogin()` -> `rememberLogin()` \n- `setRegistrationEnabled()` -> `registration()` \n- `getRegistrationEnabled()` -> `getRegistration()` \n- `setDomainAllowList()` -> `domainAllowList()` \n- `setSocialiteUserModelClass()` -> `socialiteUserModelClass()`\n- `setUserModelClass()` -> `userModelClass()` \n- `setShowDivider()` -> `showDivider()` \n\n**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:\n\n```bash\nvendor/bin/upgrade-v2\n```\n#### Callbacks\n\n**setCreateUserCallback()**\n\nThe `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.\n\n```php\nFilamentSocialitePlugin::make()\n    // ...\n    ->createUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {\n        // Logic to create a new user.\n    })\n```\n\n**setUserResolver()**\n\nThe `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.\n\n```php\nFilamentSocialitePlugin::make()\n    // ...\n    ->resolveUserUsing(function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {\n        // Logic to retrieve an existing user.\n    })\n```\n\n**setLoginRedirectCallback()**\n\nThe `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.\n\n```php\nFilamentSocialitePlugin::make()\n    // ...\n    ->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) {\n        // Change the redirect behaviour here.\n    })\n```\n\n#### Removals\n\n**getOptionalParameters()**\n\nThis function was used internally only inside the `SocialiteLoginController`. If you haven't extended this controller, you can ignore this change.\n\nProvider details can now be retrieved using `$plugin->getProvider($provider)->getWith()`.\n\n**getProviderScopes()**\n\nThis function was used internally only inside the `SocialiteLoginController`. If you haven't extended this controller, you can ignore this change.\n\nProvider details can now be retrieved using `$plugin->getProvider($provider)->getScopes()`.\n\n### Configuration\n\n**Providers**\n\nPreviously, 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.\n\n```php\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Provider;\n\nFilamentSocialitePlugin::make()\n    ->providers([\n        Provider::make('gitlab')\n            ->label('GitLab')\n            ->icon('fab-gitlab')\n            ->color(Color::hex('#2f2a6b')),\n    ]),\n```\n\n**Scopes and Optional parameters**\n\nScopes 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.\n\n```php\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Provider;\n\nFilamentSocialitePlugin::make()\n    ->providers([\n        Provider::make('gitlab')\n            // ...\n            ->scopes([\n                // Add scopes here.\n                'read:user',\n                'public_repo',\n            ]),\n            ->with([\n                // Add optional parameters here.\n                 'hd' => 'example.com',\n            ]),\n    ]),\n```\n\n## `0.x.x` to `1.x.x` (Filament v3.x)\n- Replace/republish the configuration file:\n  - `sail artisan vendor:publish --provider=\"DutchCodingCompany\\FilamentSocialite\\FilamentSocialiteServiceProvider\"`\n- Update your panel configuration `App\\Providers\\Filament\\YourPanelProvider` to include the plugin:\n  - Append `->plugins([\\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin::make()])`\n  - Configure any options by chaining functions on the plugin.\n\n## `0.x.x` (Filament v2.x)\n- Initial version\n"
  },
  {
    "path": "bin/upgrade-v2",
    "content": "#!/usr/bin/env php\n<?php\n\nnamespace Composer;\n\nexec(\"vendor/bin/rector process --config vendor/dutchcodingcompany/filament-socialite/rector.php --clear-cache\");\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"dutchcodingcompany/filament-socialite\",\n    \"description\": \"Social login for Filament through Laravel Socialite\",\n    \"keywords\": [\n        \"DutchCodingCompany\",\n        \"laravel\",\n        \"filament-socialite\"\n    ],\n    \"homepage\": \"https://github.com/dutchcodingcompany/filament-socialite\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Marco Boers\",\n            \"email\": \"m@rcoboe.rs\",\n            \"role\": \"Developer\"\n        },\n        {\n            \"name\": \"Tom Janssen\",\n            \"email\": \"dododedodonl@thor.edu\",\n            \"role\": \"Developer\"\n        },\n        {\n            \"name\": \"Bram Raaijmakers\",\n            \"email\": \"bram@dutchcodingcompany.com\",\n            \"role\": \"Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"^8.2\",\n        \"filament/filament\": \"^4.0|^5.0\",\n        \"illuminate/contracts\": \"^11.0|^12.0|^13.0\",\n        \"laravel/socialite\": \"^5.5\",\n        \"spatie/laravel-package-tools\": \"^1.9.2\"\n    },\n    \"require-dev\": {\n        \"friendsofphp/php-cs-fixer\": \"^3.8\",\n        \"larastan/larastan\": \"^2.9|^3.0\",\n        \"nunomaduro/collision\": \"^7.0|^8.1\",\n        \"orchestra/testbench\": \"^9.0|^10.0|^11.0\",\n        \"phpunit/phpunit\": \"^10.0|^11.5.3|^12.5.12\",\n        \"rector/rector\": \"^0.19.8|^2.0\"\n    },\n    \"suggest\": {\n        \"owenvoke/blade-fontawesome\": \"^2.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"DutchCodingCompany\\\\FilamentSocialite\\\\\": \"src\",\n            \"DutchCodingCompany\\\\FilamentSocialite\\\\Tests\\\\\": \"tests\",\n            \"DutchCodingCompany\\\\FilamentSocialite\\\\Database\\\\Factories\\\\\": \"database/factories\",\n            \"Utils\\\\Rector\\\\\": \"utils/rector/src\"\n        }\n    },\n    \"scripts\": {\n        \"analyse\": \"vendor/bin/phpstan analyse\",\n        \"test\": \"vendor/bin/phpunit\"\n    },\n    \"bin\": [\n        \"bin/upgrade-v2\"\n    ],\n    \"config\": {\n        \"sort-packages\": true,\n        \"allow-plugins\": {\n            \"phpstan/extension-installer\": true\n        }\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialiteServiceProvider\"\n            ],\n            \"aliases\": {\n                \"FilamentSocialite\": \"DutchCodingCompany\\\\FilamentSocialite\\\\Facades\\\\FilamentSocialite\"\n            }\n        }\n    },\n    \"minimum-stability\": \"dev\",\n    \"prefer-stable\": true,\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"DutchCodingCompany\\\\FilamentSocialite\\\\Tests\\\\\": \"tests\"\n        }\n    }\n}\n"
  },
  {
    "path": "config/filament-socialite.php",
    "content": "<?php\n\nreturn [\n\n    /*\n    |--------------------------------------------------------------------------\n    | OAuth callback middleware\n    |--------------------------------------------------------------------------\n    |\n    | This option defines the middleware that is applied to the OAuth callback url.\n    |\n    */\n\n    'middleware' => [\n        \\Illuminate\\Cookie\\Middleware\\EncryptCookies::class,\n        \\Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse::class,\n        \\Illuminate\\Session\\Middleware\\StartSession::class,\n        \\Illuminate\\Session\\Middleware\\AuthenticateSession::class,\n        \\Illuminate\\View\\Middleware\\ShareErrorsFromSession::class,\n    ],\n];\n"
  },
  {
    "path": "database/migrations/create_socialite_users_table.php.stub",
    "content": "<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migrations\\Migration;\n\nreturn new class extends Migration {\n    public function up(): void\n    {\n        Schema::create('socialite_users', function (Blueprint $table) {\n            $table->id();\n\n            $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate();\n            $table->string('provider');\n            $table->string('provider_id');\n\n            $table->timestamps();\n\n            $table->unique([\n                'provider',\n                'provider_id',\n            ]);\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::dropIfExists('socialite_users');\n    }\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"private\": true,\n    \"name\": \"filament-socialite\",\n    \"devDependencies\": {\n        \"@tailwindcss/cli\": \"^4.1.10\",\n        \"@tailwindcss/postcss\": \"^4.1.10\",\n        \"tailwindcss\": \"^4.1.10\"\n    },\n    \"scripts\": {\n        \"dev\": \"npx @tailwindcss/cli -i ./resources/css/plugin.css -o ./resources/dist/plugin.css --watch\",\n        \"watch\": \"npx @tailwindcss/cli -i ./resources/css/plugin.css -o ./resources/dist/plugin.css --watch\",\n        \"prod\": \"npx @tailwindcss/cli -i ./resources/css/plugin.css -o ./resources/dist/plugin.css --minify\"\n    }\n}\n"
  },
  {
    "path": "phpstan-baseline.neon",
    "content": "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\tcount: 1\n\t\t\tpath: src/FilamentSocialitePlugin.php\n\n\t\t-\n\t\t\tmessage: '#^Property DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\\:\\:\\$userModelClass \\(class\\-string\\<Illuminate\\\\Contracts\\\\Auth\\\\Authenticatable\\>\\) does not accept default value of type string\\.$#'\n\t\t\tidentifier: property.defaultValue\n\t\t\tcount: 1\n\t\t\tpath: src/FilamentSocialitePlugin.php\n\n\t\t-\n\t\t\tmessage: '#^Call to function method_exists\\(\\) with ''Illuminate\\\\\\\\Foundation\\\\\\\\Http\\\\\\\\Middleware\\\\\\\\VerifyCsrfToken'' and ''except'' will always evaluate to true\\.$#'\n\t\t\tidentifier: function.alreadyNarrowedType\n\t\t\tcount: 1\n\t\t\tpath: src/FilamentSocialiteServiceProvider.php\n\n\t\t-\n\t\t\tmessage: '#^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\\.$#'\n\t\t\tidentifier: argument.type\n\t\t\tcount: 1\n\t\t\tpath: src/Http/Controllers/SocialiteLoginController.php\n\n\t\t-\n\t\t\tmessage: '#^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\\)\\>\\.$#'\n\t\t\tidentifier: return.type\n\t\t\tcount: 1\n\t\t\tpath: src/Models/SocialiteUser.php\n\n\t\t-\n\t\t\tmessage: '#^Parameter \\#1 \\$view of function view expects view\\-string\\|null, string given\\.$#'\n\t\t\tidentifier: argument.type\n\t\t\tcount: 1\n\t\t\tpath: src/View/Components/Buttons.php\n\n\t\t-\n\t\t\tmessage: '#^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$#'\n\t\t\tidentifier: missingType.generics\n\t\t\tcount: 1\n\t\t\tpath: tests/Fixtures/TestTenantUser.php\n\n\t\t-\n\t\t\tmessage: '#^Call to an undefined method Mockery\\\\ExpectationInterface\\|Mockery\\\\HigherOrderMessage\\:\\:andReturn\\(\\)\\.$#'\n\t\t\tidentifier: method.notFound\n\t\t\tcount: 3\n\t\t\tpath: tests/TestCase.php\n\n\t\t-\n\t\t\tmessage: '#^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\\.$#'\n\t\t\tidentifier: argument.type\n\t\t\tcount: 1\n\t\t\tpath: tests/TestCase.php\n"
  },
  {
    "path": "phpstan.neon.dist",
    "content": "includes:\n    - ./vendor/larastan/larastan/extension.neon\n    - ./phpstan-baseline.neon\n\nparameters:\n    level: 8\n\n    paths:\n        - config\n        - database\n        - src\n        - tests\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n         bootstrap=\"vendor/autoload.php\"\n         colors=\"true\"\n>\n    <testsuites>\n        <testsuite name=\"Test\">\n            <directory>tests</directory>\n        </testsuite>\n    </testsuites>\n    <source>\n        <include>\n            <directory>src</directory>\n        </include>\n    </source>\n    <php>\n        <env name=\"APP_ENV\" value=\"testing\"/>\n        <env name=\"BCRYPT_ROUNDS\" value=\"4\"/>\n        <env name=\"CACHE_DRIVER\" value=\"array\"/>\n        <env name=\"DB_CONNECTION\" value=\"sqlite\"/>\n        <env name=\"DB_DATABASE\" value=\"testing\"/>\n        <env name=\"MAIL_MAILER\" value=\"array\"/>\n        <env name=\"QUEUE_CONNECTION\" value=\"sync\"/>\n        <env name=\"SESSION_DRIVER\" value=\"array\"/>\n        <env name=\"TELESCOPE_ENABLED\" value=\"false\"/>\n    </php>\n</phpunit>\n"
  },
  {
    "path": "rector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Rector\\Config\\RectorConfig;\nuse Utils\\Rector\\Rector\\RenameMethods;\n\nreturn static function (RectorConfig $rectorConfig): void {\n    $rectorConfig->paths([\n        'app/Providers/Filament/',\n    ]);\n\n    $rectorConfig->ruleWithConfiguration(\n        \\Rector\\Renaming\\Rector\\MethodCall\\RenameMethodRector::class,\n        [\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setProviders\",\n                \"providers\",\n            ),\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setRegistrationEnabled\",\n                \"registration\",\n            ),\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setSlug\",\n                \"slug\",\n            ),\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setLoginRouteName\",\n                \"loginRouteName\",\n            ),\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setDashboardRouteName\",\n                \"dashboardRouteName\",\n            ),\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setRememberLogin\",\n                \"rememberLogin\",\n            ),\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setSocialiteUserModelClass\",\n                \"socialiteUserModelClass\",\n            ),\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setDomainAllowList\",\n                \"domainAllowList\",\n            ),\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setUserModelClass\",\n                \"userModelClass\",\n            ),\n            new \\Rector\\Renaming\\ValueObject\\MethodCallRename(\n                \"DutchCodingCompany\\\\FilamentSocialite\\\\FilamentSocialitePlugin\",\n                \"setShowDivider\",\n                \"showDivider\",\n            ),\n        ]\n    );\n};\n"
  },
  {
    "path": "resources/css/plugin.css",
    "content": "@import \"tailwindcss\";\n\n@config '../../tailwind.config.js';\n"
  },
  {
    "path": "resources/dist/plugin.css",
    "content": "/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */\n@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}"
  },
  {
    "path": "resources/lang/en/auth.php",
    "content": "<?php\n\nreturn [\n    'login-via' => 'Or log in via',\n\n    'login-failed' => 'Login failed, please try again.',\n\n    'user-not-allowed' => 'Your email is not part of a domain that is allowed.',\n\n    'registration-not-enabled' => 'Registration of a new user is not allowed.',\n];\n"
  },
  {
    "path": "resources/views/.gitkeep",
    "content": ""
  },
  {
    "path": "resources/views/components/buttons.blade.php",
    "content": "<div\n    x-data=\"{}\"\n    x-load-css=\"[@js(\\Filament\\Support\\Facades\\FilamentAsset::getStyleHref('filament-socialite-styles', package: 'filament-socialite'))]\"\n>\n    <div class=\"flex flex-col gap-y-6\">\n        @if ($messageBag->isNotEmpty())\n            @foreach($messageBag->all() as $value)\n                <p class=\"fi-fo-field-wrp-error-message text-danger-600 dark:text-danger-400\">{{ __($value) }}</p>\n            @endforeach\n        @endif\n\n        @if (count($visibleProviders))\n            @if($showDivider)\n                <div class=\"relative flex items-center justify-center text-center\">\n                    <div class=\"absolute border-t border-gray-200 w-full h-px\"></div>\n                    <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\">\n                        {{ __('filament-socialite::auth.login-via') }}\n                    </p>\n                </div>\n            @endif\n\n            <div class=\"grid @if(count($visibleProviders) > 1) grid-cols-2 @endif gap-4\">\n                @foreach($visibleProviders as $key => $provider)\n                    <x-filament::button\n                        :color=\"$provider->getColor()\"\n                        :outlined=\"$provider->getOutlined()\"\n                        :icon=\"$provider->getIcon()\"\n                        tag=\"a\"\n                        :href=\"route($socialiteRoute, $key)\"\n                        :spa-mode=\"false\"\n                    >\n                        {{ $provider->getLabel() }}\n                    </x-filament::button>\n                @endforeach\n            </div>\n        @else\n            <span></span>\n        @endif\n    </div>\n</div>\n"
  },
  {
    "path": "routes/web.php",
    "content": "<?php\n\nuse DutchCodingCompany\\FilamentSocialite\\Http\\Controllers\\SocialiteLoginController;\nuse DutchCodingCompany\\FilamentSocialite\\Http\\Middleware\\PanelFromUrlQuery;\nuse Filament\\Facades\\Filament;\nuse Illuminate\\Support\\Facades\\Route;\n\nforeach (Filament::getPanels() as $panel) {\n    if (! $panel->hasPlugin('filament-socialite')) {\n        continue;\n    }\n\n    // Retrieve slug for route name.\n    $slug = $panel->getPlugin('filament-socialite')->getSlug();\n\n    $domains = $panel->getDomains();\n\n    foreach ((empty($domains) ? [null] : $domains) as $domain) {\n        Filament::currentDomain($domain);\n\n        Route::domain($domain)\n            ->middleware($panel->getMiddleware())\n            ->name(\"socialite.{$panel->generateRouteName('oauth.redirect')}\")\n            ->get(\"/$slug/oauth/{provider}\", [SocialiteLoginController::class, 'redirectToProvider']);\n\n        Route::domain($domain)\n            ->match(['get', 'post'], \"$slug/oauth/callback/{provider}\", [SocialiteLoginController::class, 'processCallback'])\n            ->middleware([\n                ...$panel->getMiddleware(),\n                ...config('filament-socialite.middleware'),\n            ])\n            ->name(\"socialite.{$panel->generateRouteName('oauth.callback')}\");\n\n        Filament::currentDomain(null);\n    }\n}\n\n/**\n * @note This route can only distinguish between Filament panels using the `state` input. If you have a stateless OAuth\n * implementation, use the \"$slug/oauth/callback/{provider}\" route instead which has the panel in the URL itself.\n */\nRoute::match(['get', 'post'], \"/oauth/callback/{provider}\", [SocialiteLoginController::class, 'processCallback'])\n    ->middleware([\n        PanelFromUrlQuery::class,\n        ...config('filament-socialite.middleware'),\n    ])\n    ->name('oauth.callback');\n"
  },
  {
    "path": "src/Events/InvalidState.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Illuminate\\Queue\\SerializesModels;\nuse Laravel\\Socialite\\Two\\InvalidStateException;\n\nclass InvalidState\n{\n    use Dispatchable;\n    use SerializesModels;\n\n    /**\n     * Create a new event instance.\n     *\n     * @return void\n     */\n    public function __construct(\n        public InvalidStateException $exception,\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Events/Login.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Illuminate\\Queue\\SerializesModels;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUser;\n\nclass Login\n{\n    use Dispatchable;\n    use SerializesModels;\n\n    /**\n     * Create a new event instance.\n     *\n     * @return void\n     */\n    public function __construct(\n        public FilamentSocialiteUserContract $socialiteUser,\n        public SocialiteUser $oauthUser,\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Events/Registered.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Illuminate\\Queue\\SerializesModels;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\nclass Registered\n{\n    use Dispatchable;\n    use SerializesModels;\n\n    /**\n     * Create a new event instance.\n     *\n     * @return void\n     */\n    public function __construct(\n        public string $provider,\n        public SocialiteUserContract $oauthUser,\n        public FilamentSocialiteUserContract $socialiteUser,\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Events/RegistrationNotEnabled.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Illuminate\\Queue\\SerializesModels;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\nclass RegistrationNotEnabled\n{\n    use Dispatchable;\n    use SerializesModels;\n\n    /**\n     * Create a new event instance.\n     *\n     * @return void\n     */\n    public function __construct(\n        public string $provider,\n        public SocialiteUserContract $oauthUser,\n        public ?Authenticatable $user,\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Events/SocialiteUserConnected.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Illuminate\\Queue\\SerializesModels;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\nclass SocialiteUserConnected\n{\n    use Dispatchable;\n    use SerializesModels;\n\n    /**\n     * Create a new event instance.\n     *\n     * @return void\n     */\n    public function __construct(\n        public string $provider,\n        public SocialiteUserContract $oauthUser,\n        public FilamentSocialiteUserContract $socialiteUser,\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Events/UserNotAllowed.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Events;\n\nuse Illuminate\\Foundation\\Events\\Dispatchable;\nuse Illuminate\\Queue\\SerializesModels;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\nclass UserNotAllowed\n{\n    use Dispatchable;\n    use SerializesModels;\n\n    /**\n     * Create a new event instance.\n     *\n     * @return void\n     */\n    public function __construct(\n        public SocialiteUserContract $oauthUser,\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Exceptions/GuardNotStateful.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Exceptions;\n\nuse LogicException;\n\nclass GuardNotStateful extends LogicException\n{\n    public static function make(string $guard): self\n    {\n        return new self('Guard \"'.$guard.'\" is not stateful.');\n    }\n}\n"
  },
  {
    "path": "src/Exceptions/ImplementationException.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Exceptions;\n\nclass ImplementationException extends \\Exception\n{\n    //\n}\n"
  },
  {
    "path": "src/Exceptions/InvalidCallbackPayload.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Exceptions;\n\nuse LogicException;\nuse Throwable;\n\nclass InvalidCallbackPayload extends LogicException\n{\n    public function __construct(?Throwable $previous = null)\n    {\n        parent::__construct('The panel could not be decrypted from the OAuth callback.', 0, $previous);\n    }\n\n    public static function make(): self\n    {\n        return new self(...func_get_args());\n    }\n}\n"
  },
  {
    "path": "src/Exceptions/ProviderNotConfigured.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Exceptions;\n\nuse LogicException;\n\nclass ProviderNotConfigured extends LogicException\n{\n    public static function make(string $provider): self\n    {\n        return new self('Provider \"'.$provider.'\" is not configured, please configure it in config/services.php and/or on your panel.');\n    }\n}\n"
  },
  {
    "path": "src/FilamentSocialitePlugin.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSocialite\\Exceptions\\GuardNotStateful;\nuse DutchCodingCompany\\FilamentSocialite\\Exceptions\\ImplementationException;\nuse DutchCodingCompany\\FilamentSocialite\\Exceptions\\ProviderNotConfigured;\nuse Filament\\Contracts\\Plugin;\nuse Filament\\Facades\\Filament;\nuse Filament\\Panel;\nuse Illuminate\\Contracts\\Auth\\Factory;\nuse Illuminate\\Contracts\\Auth\\StatefulGuard;\nuse Illuminate\\Contracts\\Config\\Repository;\nuse Illuminate\\Support\\Arr;\nuse Illuminate\\Support\\Str;\n\nclass FilamentSocialitePlugin implements Plugin\n{\n    use Traits\\Callbacks;\n    use Traits\\Routes;\n    use Traits\\Models;\n\n    /**\n     * @var array<string, \\DutchCodingCompany\\FilamentSocialite\\Provider>\n     */\n    protected array $providers = [];\n\n    /**\n     * @var array<string>\n     */\n    protected array $domainAllowList = [];\n\n    protected bool $rememberLogin = false;\n\n    /**\n     * @phpstan-var (\\Closure(string $provider, \\Laravel\\Socialite\\Contracts\\User $oauthUser, ?\\Illuminate\\Contracts\\Auth\\Authenticatable $user): bool) | bool\n     */\n    protected Closure | bool $registration = false;\n\n    protected ?string $slug = null;\n\n    protected ?string $panelId = null;\n\n    protected bool $showDivider = true;\n\n    public function __construct(\n        protected Repository $config,\n        protected Factory $auth,\n    ) {\n        //\n    }\n\n    public static function make(): static\n    {\n        return app(static::class);\n    }\n\n    public static function current(): static\n    {\n        if (Filament::getCurrentPanel()?->hasPlugin('filament-socialite')) {\n            /** @var static $plugin */\n            $plugin = Filament::getCurrentPanel()->getPlugin('filament-socialite');\n\n            return $plugin;\n        }\n\n        throw new ImplementationException('No current panel found with filament-socialite plugin.');\n    }\n\n    public function getId(): string\n    {\n        return 'filament-socialite';\n    }\n\n    public function register(Panel $panel): void\n    {\n        $this->panelId = $panel->getId();\n    }\n\n    public function boot(Panel $panel): void\n    {\n        //\n    }\n\n    /**\n     * @param array<array-key, \\DutchCodingCompany\\FilamentSocialite\\Provider> $providers\n     */\n    public function providers(array $providers): static\n    {\n        // Assign providers as key-value pairs with the provider name as the key.\n        $this->providers = Arr::mapWithKeys(\n            $providers,\n            static fn (Provider $value) => [$value->getName() => $value],\n        );\n\n        return $this;\n    }\n\n    /**\n     * @return array<string, \\DutchCodingCompany\\FilamentSocialite\\Provider>\n     */\n    public function getProviders(): array\n    {\n        return $this->providers;\n    }\n\n    public function getProvider(string $provider): Provider\n    {\n        if (! $this->isProviderConfigured($provider)) {\n            throw ProviderNotConfigured::make($provider);\n        }\n\n        return $this->providers[$provider];\n    }\n\n    public function slug(?string $slug): static\n    {\n        $this->slug = $slug;\n\n        return $this;\n    }\n\n    public function getSlug(): string\n    {\n        return $this->slug ?? rtrim($this->getPanel()->getPath(), '/');\n    }\n\n    public function rememberLogin(bool $value): static\n    {\n        $this->rememberLogin = $value;\n\n        return $this;\n    }\n\n    public function getRememberLogin(): bool\n    {\n        return $this->rememberLogin;\n    }\n\n    /**\n     * @param (\\Closure(string $provider, \\Laravel\\Socialite\\Contracts\\User $oauthUser, ?\\Illuminate\\Contracts\\Auth\\Authenticatable $user): bool) | bool $value\n     * @return $this\n     */\n    public function registration(Closure | bool $value = true): static\n    {\n        $this->registration = $value;\n\n        return $this;\n    }\n\n    /**\n     * @return (\\Closure(string $provider, \\Laravel\\Socialite\\Contracts\\User $oauthUser, ?\\Illuminate\\Contracts\\Auth\\Authenticatable $user): bool) | bool\n     */\n    public function getRegistration(): Closure | bool\n    {\n        return $this->registration;\n    }\n\n    /**\n     * @param array<string> $values\n     */\n    public function domainAllowList(array $values): static\n    {\n        $this->domainAllowList = $values;\n\n        return $this;\n    }\n\n    /**\n     * @return array<string>\n     */\n    public function getDomainAllowList(): array\n    {\n        return $this->domainAllowList;\n    }\n\n    public function isProviderConfigured(string $provider): bool\n    {\n        return $this->config->has('services.'.$provider) && isset($this->providers[$provider]);\n    }\n\n    public function showDivider(bool $divider): static\n    {\n        $this->showDivider = $divider;\n\n        return $this;\n    }\n\n    public function getShowDivider(): bool\n    {\n        return $this->showDivider;\n    }\n\n    public function getPanel(): Panel\n    {\n        return Filament::getPanel($this->getPanelId());\n    }\n\n    public function getPanelId(): string\n    {\n        return $this->panelId ?? throw new ImplementationException('Panel ID not set.');\n    }\n\n    public function getGuard(): StatefulGuard\n    {\n        $guard = $this->auth->guard(\n            $guardName = $this->getPanel()->getAuthGuard()\n        );\n\n        if ($guard instanceof StatefulGuard) {\n            return $guard;\n        }\n\n        throw GuardNotStateful::make($guardName);\n    }\n}\n"
  },
  {
    "path": "src/FilamentSocialiteServiceProvider.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite;\n\nuse DutchCodingCompany\\FilamentSocialite\\View\\Components\\Buttons;\nuse Filament\\Facades\\Filament;\nuse Filament\\Support\\Assets\\Css;\nuse Filament\\Support\\Facades\\FilamentAsset;\nuse Filament\\Support\\Facades\\FilamentView;\nuse Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken;\nuse Illuminate\\Support\\Facades\\Blade;\nuse Spatie\\LaravelPackageTools\\Package;\nuse Spatie\\LaravelPackageTools\\PackageServiceProvider;\n\nclass FilamentSocialiteServiceProvider extends PackageServiceProvider\n{\n    public function configurePackage(Package $package): void\n    {\n        $package\n            ->name('filament-socialite')\n            ->hasConfigFile()\n            ->hasTranslations()\n            ->hasViews()\n            ->hasRoute('web')\n            ->hasMigration('create_socialite_users_table');\n    }\n\n    public function packageRegistered(): void\n    {\n        //\n    }\n\n    public function packageBooted(): void\n    {\n        Blade::componentNamespace('DutchCodingCompany\\FilamentSocialite\\View\\Components', 'filament-socialite');\n        Blade::component('buttons', Buttons::class);\n\n        FilamentAsset::register([\n            Css::make('filament-socialite-styles', __DIR__.'/../resources/dist/plugin.css')->loadedOnRequest(),\n        ], package: 'filament-socialite');\n\n        FilamentView::registerRenderHook(\n            'panels::auth.login.form.after',\n            static function (): ?string {\n                $panel = Filament::getCurrentPanel();\n\n                if (! $panel?->hasPlugin('filament-socialite')) {\n                    return null;\n                }\n\n                /** @var \\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin */\n                $plugin = $panel->getPlugin('filament-socialite');\n\n                return Blade::render('<x-filament-socialite::buttons :show-divider=\"'.($plugin->getShowDivider() ? 'true' : 'false').'\" />');\n            },\n        );\n\n        if (\n            version_compare(app()->version(), '11.0', '>=')\n            && method_exists(VerifyCsrfToken::class, 'except')\n        ) {\n            VerifyCsrfToken::except([\n                '*/oauth/callback/*',\n                'oauth/callback/*',\n            ]);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Http/Controllers/SocialiteLoginController.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Http\\Controllers;\n\nuse DutchCodingCompany\\FilamentSocialite\\Events;\nuse DutchCodingCompany\\FilamentSocialite\\Exceptions\\ProviderNotConfigured;\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Http\\Middleware\\PanelFromUrlQuery;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse Filament\\Support\\Concerns\\EvaluatesClosures;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\Routing\\Controller;\nuse Illuminate\\Support\\Facades\\DB;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\nuse Laravel\\Socialite\\Facades\\Socialite;\nuse Laravel\\Socialite\\Two\\InvalidStateException;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass SocialiteLoginController extends Controller\n{\n    use EvaluatesClosures;\n\n    private ?FilamentSocialitePlugin $plugin = null;\n\n    public function redirectToProvider(string $provider): mixed\n    {\n        if (! $this->plugin()->isProviderConfigured($provider)) {\n            throw ProviderNotConfigured::make($provider);\n        }\n\n        /** @var \\Laravel\\Socialite\\Two\\AbstractProvider $driver */\n        $driver = Socialite::driver($provider);\n\n        $response = $driver\n            ->with([\n                ...$this->plugin()->getProvider($provider)->getWith(),\n                'state' => $state = PanelFromUrlQuery::encrypt($this->plugin()->getPanel()->getId()),\n            ])\n            ->scopes($this->plugin()->getProvider($provider)->getScopes())\n            ->redirect();\n\n        // Set state value to be equal to the encrypted panel id. This value is used to\n        // retrieve the panel id once the authentication returns to our application,\n        // and it still prevents CSRF as it is non-guessable value.\n        session()->put('state', $state);\n\n        return $response;\n    }\n\n    protected function retrieveOauthUser(string $provider): ?SocialiteUserContract\n    {\n        $stateless = $this->plugin()->getProvider($provider)->getStateless();\n\n        try {\n            /** @var \\Laravel\\Socialite\\Two\\AbstractProvider $driver */\n            $driver = Socialite::driver($provider);\n\n            return $stateless\n                ? $driver->stateless()->user()\n                : $driver->user();\n        } catch (InvalidStateException $e) {\n            Events\\InvalidState::dispatch($e);\n        }\n\n        return null;\n    }\n\n    protected function retrieveSocialiteUser(string $provider, SocialiteUserContract $oauthUser): ?FilamentSocialiteUserContract\n    {\n        return $this->plugin()->getSocialiteUserModel()::findForProvider($provider, $oauthUser);\n    }\n\n    protected function redirectToLogin(string $message): RedirectResponse\n    {\n        // Add error message to the session, this way we can show an error message on the form.\n        session()->flash('filament-socialite-login-error', __($message));\n\n        return redirect()->route($this->plugin()->getLoginRouteName());\n    }\n\n    protected function authorizeUser(SocialiteUserContract $oauthUser): bool\n    {\n        return app()->call($this->plugin()->getAuthorizeUserUsing(), ['plugin' => $this->plugin(), 'oauthUser' => $oauthUser]);\n    }\n\n    protected function loginUser(string $provider, FilamentSocialiteUserContract $socialiteUser, SocialiteUserContract $oauthUser): Response\n    {\n        // Log the user in\n        $this->plugin()->getGuard()->login($socialiteUser->getUser(), $this->plugin()->getRememberLogin());\n\n        // Dispatch the login event\n        Events\\Login::dispatch($socialiteUser, $oauthUser);\n\n        return app()->call($this->plugin()->getRedirectAfterLoginUsing(), ['provider' => $provider, 'socialiteUser' => $socialiteUser, 'plugin' => $this->plugin]);\n    }\n\n    protected function registerSocialiteUser(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): Response\n    {\n        // Create a socialite user\n        $socialiteUser = $this->plugin()->getSocialiteUserModel()::createForProvider($provider, $oauthUser, $user);\n\n        // Dispatch the socialite user connected event\n        Events\\SocialiteUserConnected::dispatch($provider, $oauthUser, $socialiteUser);\n\n        // Login the user\n        return $this->loginUser($provider, $socialiteUser, $oauthUser);\n    }\n\n    protected function registerOauthUser(string $provider, SocialiteUserContract $oauthUser): Response\n    {\n        $socialiteUser = DB::transaction(function () use ($provider, $oauthUser) {\n            // Create a user\n            $user = app()->call($this->plugin()->getCreateUserUsing(), ['provider' => $provider, 'oauthUser' => $oauthUser, 'plugin' => $this->plugin]);\n\n            // Create a socialite user\n            return $this->plugin()->getSocialiteUserModel()::createForProvider($provider, $oauthUser, $user);\n        });\n\n        // Dispatch the registered event\n        Events\\Registered::dispatch($provider, $oauthUser, $socialiteUser);\n\n        // Login the user\n        return $this->loginUser($provider, $socialiteUser, $oauthUser);\n    }\n\n    public function processCallback(string $provider): Response\n    {\n        if (! $this->plugin()->isProviderConfigured($provider)) {\n            throw ProviderNotConfigured::make($provider);\n        }\n\n        // Try to retrieve existing user\n        $oauthUser = $this->retrieveOauthUser($provider);\n\n        if (is_null($oauthUser)) {\n            return $this->redirectToLogin('filament-socialite::auth.login-failed');\n        }\n\n        // Verify if the user is authorized.\n        if (! $this->authorizeUser($oauthUser)) {\n            Events\\UserNotAllowed::dispatch($oauthUser);\n\n            return $this->redirectToLogin('filament-socialite::auth.user-not-allowed');\n        }\n\n        // Try to find a socialite user\n        $socialiteUser = $this->retrieveSocialiteUser($provider, $oauthUser);\n        if ($socialiteUser) {\n            return $this->loginUser($provider, $socialiteUser, $oauthUser);\n        }\n\n        // See if a user already exists, but not for this socialite provider\n        $user = app()->call($this->plugin()->getResolveUserUsing(), [\n            'provider' => $provider,\n            'oauthUser' => $oauthUser,\n            'plugin' => $this->plugin,\n        ]);\n\n        // See if registration is allowed\n        if (! $this->evaluate($this->plugin()->getRegistration(), ['provider' => $provider, 'oauthUser' => $oauthUser, 'user' => $user])) {\n            Events\\RegistrationNotEnabled::dispatch($provider, $oauthUser, $user);\n\n            return $this->redirectToLogin('filament-socialite::auth.registration-not-enabled');\n        }\n\n        // Handle registration\n        return $user\n            ? $this->registerSocialiteUser($provider, $oauthUser, $user)\n            : $this->registerOauthUser($provider, $oauthUser);\n    }\n\n    protected function plugin(): FilamentSocialitePlugin\n    {\n        return $this->plugin ??= FilamentSocialitePlugin::current();\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware/PanelFromUrlQuery.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Http\\Middleware;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSocialite\\Exceptions\\InvalidCallbackPayload;\nuse Filament\\Http\\Middleware\\SetUpPanel;\nuse Illuminate\\Contracts\\Encryption\\DecryptException;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Crypt;\n\n/**\n * @note This callback uses the `state` input to determine the correct panel ID. A simpler\n * implementation is to use the \"$slug/oauth/callback/{provider}\" route instead, which\n * contains the panel ID in the url itself.\n */\nclass PanelFromUrlQuery\n{\n    public function handle(Request $request, Closure $next): mixed\n    {\n        // Set the current panel based on the encrypted panel name in the url query.\n        return (new SetUpPanel())->handle($request, $next, static::decrypt($request));\n    }\n\n    public static function encrypt(string $panel): string\n    {\n        return Crypt::encrypt($panel);\n    }\n\n    /**\n     * @throws InvalidCallbackPayload\n     */\n    public static function decrypt(Request $request): string\n    {\n        try {\n            if (! is_string($request->input('state'))) {\n                throw new DecryptException('State is not a string.');\n            }\n\n            return Crypt::decrypt($request->input('state'));\n        } catch (DecryptException $e) {\n            throw InvalidCallbackPayload::make($e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Models/Contracts/FilamentSocialiteUser.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Models\\Contracts;\n\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\ninterface FilamentSocialiteUser\n{\n    public function getUser(): Authenticatable;\n\n    public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self;\n\n    public static function createForProvider(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): self;\n}\n"
  },
  {
    "path": "src/Models/SocialiteUser.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Models;\n\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\n/**\n * @property int $user_id\n * @property string $provider\n * @property int $provider_id\n */\nclass SocialiteUser extends Model implements FilamentSocialiteUserContract\n{\n    protected $fillable = [\n        'user_id',\n        'provider',\n        'provider_id',\n    ];\n\n    /**\n     * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo<\\Illuminate\\Database\\Eloquent\\Model, $this>\n     */\n    public function user(): BelongsTo\n    {\n        /** @var class-string<\\Illuminate\\Database\\Eloquent\\Model&\\Illuminate\\Contracts\\Auth\\Authenticatable> */\n        $user = FilamentSocialitePlugin::current()->getUserModelClass();\n\n        return $this->belongsTo($user);\n    }\n\n    public function getUser(): Authenticatable\n    {\n        assert($this->user instanceof Authenticatable);\n\n        return $this->user;\n    }\n\n    public static function findForProvider(string $provider, SocialiteUserContract $oauthUser): ?self\n    {\n        return self::query()\n            ->where('provider', $provider)\n            ->where('provider_id', $oauthUser->getId())\n            ->first();\n    }\n\n    public static function createForProvider(string $provider, SocialiteUserContract $oauthUser, Authenticatable $user): self\n    {\n        return self::query()\n            ->create([\n                'user_id' => $user->getKey(),\n                'provider' => $provider,\n                'provider_id' => $oauthUser->getId(),\n            ]);\n    }\n}\n"
  },
  {
    "path": "src/Provider.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSocialite\\Traits\\CanBeHidden;\nuse Filament\\Support\\Colors\\Color;\nuse Filament\\Support\\Concerns\\EvaluatesClosures;\nuse Illuminate\\Support\\Str;\n\nclass Provider\n{\n    use EvaluatesClosures;\n    use CanBeHidden;\n\n    protected string $name;\n\n    protected string $label;\n\n    protected string | null $icon = null;\n\n    /**\n     * @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\n     */\n    protected string | array | null $color = Color::Gray;\n\n    protected bool $outlined = true;\n\n    /**\n     * @var \\Closure(): array<mixed> | array<mixed>\n     */\n    protected Closure | array $scopes = [];\n\n    /**\n     * @var \\Closure(): array<mixed> | array<mixed>\n     */\n    protected Closure | array $with = [];\n\n    protected bool $stateless = false;\n\n    public function __construct(string $name)\n    {\n        $this->name($name);\n    }\n\n    public static function make(string $name): static\n    {\n        return app(static::class, ['name' => $name]);\n    }\n\n    /**\n     * @param array<string, mixed> $attributes\n     */\n    public function fill(array $attributes): static\n    {\n        foreach ($attributes as $key => $value) {\n            $this->{$key}($value);\n        }\n\n        return $this;\n    }\n\n    public function name(string $name): static\n    {\n        $this->name = $name;\n\n        return $this;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function label(string $label): static\n    {\n        $this->label = $label;\n\n        return $this;\n    }\n\n    public function getLabel(): string\n    {\n        return $this->label ?? Str::title($this->getName());\n    }\n\n    public function icon(string | null $icon): static\n    {\n        $this->icon = $icon;\n\n        return $this;\n    }\n\n    public function getIcon(): string | null\n    {\n        return $this->icon;\n    }\n\n    /**\n     * @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\n     */\n    public function color(string | array | null $color): static\n    {\n        $this->color = $color;\n\n        return $this;\n    }\n\n    /**\n     * @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\n     */\n    public function getColor(): string | array | null\n    {\n        return $this->color;\n    }\n\n    public function outlined(bool $outlined = true): static\n    {\n        $this->outlined = $outlined;\n\n        return $this;\n    }\n\n    public function getOutlined(): bool\n    {\n        return $this->outlined;\n    }\n\n    /**\n     * @param Closure(): array<mixed> | array<mixed> $scopes\n     */\n    public function scopes(Closure | array $scopes): static\n    {\n        $this->scopes = $scopes;\n\n        return $this;\n    }\n\n    /**\n     * @return array<mixed>\n     */\n    public function getScopes(): array\n    {\n        return $this->evaluate($this->scopes, ['provider' => $this]);\n    }\n\n    /**\n     * @param Closure(): array<mixed> | array<mixed> $with\n     */\n    public function with(Closure | array $with): static\n    {\n        $this->with = $with;\n\n        return $this;\n    }\n\n    /**\n     * @return array<mixed>\n     */\n    public function getWith(): array\n    {\n        return $this->evaluate($this->with, ['provider' => $this]);\n    }\n\n    public function stateless(bool $stateless = true): static\n    {\n        $this->stateless = $stateless;\n\n        return $this;\n    }\n\n    public function getStateless(): bool\n    {\n        return $this->stateless;\n    }\n}\n"
  },
  {
    "path": "src/Traits/Callbacks.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Traits;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSocialite\\Exceptions\\ImplementationException;\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse Filament\\Facades\\Filament;\nuse Illuminate\\Support\\Str;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\n\ntrait Callbacks\n{\n    /**\n     * @phpstan-var ?\\Closure(string $provider, \\Laravel\\Socialite\\Contracts\\User $oauthUser, self $socialite): \\Illuminate\\Contracts\\Auth\\Authenticatable\n     */\n    protected ?Closure $createUserUsing = null;\n\n    /**\n     * @phpstan-var ?\\Closure(string $provider, \\DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser $socialiteUser, \\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin): \\Symfony\\Component\\HttpFoundation\\Response\n     */\n    protected ?Closure $redirectAfterLoginUsing = null;\n\n    /**\n     * @phpstan-var ?\\Closure(string $provider, \\Laravel\\Socialite\\Contracts\\User $oauthUser, \\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin): ?(\\Illuminate\\Contracts\\Auth\\Authenticatable)\n     */\n    protected ?Closure $resolveUserUsing = null;\n\n    /**\n     * @phpstan-var ?\\Closure(\\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin, \\Laravel\\Socialite\\Contracts\\User $oauthUser): bool\n     */\n    protected ?Closure $authorizeUserUsing = null;\n\n    /**\n     * @param ?\\Closure(string $provider, \\Laravel\\Socialite\\Contracts\\User $oauthUser, \\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin): \\Illuminate\\Contracts\\Auth\\Authenticatable $callback\n     */\n    public function createUserUsing(?Closure $callback = null): static\n    {\n        $this->createUserUsing = $callback;\n\n        return $this;\n    }\n\n    /**\n     * @return \\Closure(string $provider, \\Laravel\\Socialite\\Contracts\\User $oauthUser, \\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin): \\Illuminate\\Contracts\\Auth\\Authenticatable\n     */\n    public function getCreateUserUsing(): Closure\n    {\n        return $this->createUserUsing ?? function (\n            string $provider,\n            SocialiteUserContract $oauthUser,\n            FilamentSocialitePlugin $plugin,\n        ) {\n            /**\n             * @var \\Illuminate\\Database\\Eloquent\\Builder<\\Illuminate\\Database\\Eloquent\\Model&\\Illuminate\\Contracts\\Auth\\Authenticatable> $query\n             */\n            $query = (new $this->userModelClass())->query();\n\n            return $query->create([\n                'name' => $oauthUser->getName(),\n                'email' => $oauthUser->getEmail(),\n            ]);\n        };\n    }\n\n    /**\n     * @param \\Closure(string $provider, \\DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser $socialiteUser, \\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin): \\Illuminate\\Http\\RedirectResponse $callback\n     */\n    public function redirectAfterLoginUsing(Closure $callback): static\n    {\n        $this->redirectAfterLoginUsing = $callback;\n\n        return $this;\n    }\n\n    /**\n     * @return \\Closure(string $provider, \\DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser $socialiteUser, \\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin): \\Symfony\\Component\\HttpFoundation\\Response\n     */\n    public function getRedirectAfterLoginUsing(): Closure\n    {\n        return $this->redirectAfterLoginUsing ?? function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) {\n            if (($panel = $this->getPanel())->hasTenancy()) {\n                $tenant = Filament::getUserDefaultTenant($socialiteUser->getUser());\n\n                if (is_null($tenant) && $tenantRegistrationUrl = $panel->getTenantRegistrationUrl()) {\n                    return redirect()->intended($tenantRegistrationUrl);\n                }\n\n                return redirect()->intended(\n                    $panel->getUrl($tenant)\n                );\n            }\n\n            return redirect()->intended(\n                $this->getPanel()->getUrl()\n            );\n        };\n    }\n\n    /**\n     * @param ?\\Closure(string $provider, \\Laravel\\Socialite\\Contracts\\User $oauthUser, \\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin): ?(\\Illuminate\\Contracts\\Auth\\Authenticatable) $callback\n     */\n    public function resolveUserUsing(?Closure $callback = null): static\n    {\n        $this->resolveUserUsing = $callback;\n\n        return $this;\n    }\n\n    /**\n     * @return \\Closure(string $provider, \\Laravel\\Socialite\\Contracts\\User $oauthUser, \\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin): ?(\\Illuminate\\Contracts\\Auth\\Authenticatable)\n     */\n    public function getResolveUserUsing(): Closure\n    {\n        return $this->resolveUserUsing ?? function (string $provider, SocialiteUserContract $oauthUser, FilamentSocialitePlugin $plugin) {\n            /** @var \\Illuminate\\Database\\Eloquent\\Builder<\\Illuminate\\Database\\Eloquent\\Model&\\Illuminate\\Contracts\\Auth\\Authenticatable> $model */\n            $model = (new $this->userModelClass());\n\n            return $model->where(\n                'email',\n                $oauthUser->getEmail()\n            )->first();\n        };\n    }\n\n    /**\n     * @param ?\\Closure(\\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin, \\Laravel\\Socialite\\Contracts\\User $oauthUser): bool $callback\n     */\n    public function authorizeUserUsing(?Closure $callback = null): static\n    {\n        $this->authorizeUserUsing = $callback;\n\n        return $this;\n    }\n\n    /**\n     * @return \\Closure(\\DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin $plugin, \\Laravel\\Socialite\\Contracts\\User $oauthUser): bool\n     */\n    public function getAuthorizeUserUsing(): Closure\n    {\n        return $this->authorizeUserUsing ?? static::checkDomainAllowList(...);\n    }\n\n    public static function checkDomainAllowList(FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser): bool\n    {\n        $domains = $plugin->getDomainAllowList();\n\n        // When no domains are specified, all users are allowed\n        if (count($domains) < 1) {\n            return true;\n        }\n\n        // Get the domain of the email for the specified user\n        $emailDomain = Str::of($oauthUser->getEmail() ?? throw new ImplementationException('User email is required.'))\n            ->afterLast('@')\n            ->lower()\n            ->__toString();\n\n        // See if everything after @ is in the domains array\n        return in_array($emailDomain, $domains);\n    }\n}\n"
  },
  {
    "path": "src/Traits/CanBeHidden.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Traits;\n\nuse Closure;\n\ntrait CanBeHidden\n{\n    protected bool | Closure $isHidden = false;\n\n    protected bool | Closure $isVisible = true;\n\n    public function hidden(bool | Closure $condition = true): static\n    {\n        $this->isHidden = $condition;\n\n        return $this;\n    }\n\n    public function visible(bool | Closure $condition = true): static\n    {\n        $this->isVisible = $condition;\n\n        return $this;\n    }\n\n    public function isHidden(): bool\n    {\n        if ($this->evaluate($this->isHidden)) {\n            return true;\n        }\n\n        return ! $this->evaluate($this->isVisible);\n    }\n\n    public function isVisible(): bool\n    {\n        return ! $this->isHidden();\n    }\n}\n"
  },
  {
    "path": "src/Traits/Models.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Traits;\n\nuse DutchCodingCompany\\FilamentSocialite\\Exceptions\\ImplementationException;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\SocialiteUser;\n\ntrait Models\n{\n    /**\n     * @var class-string<\\Illuminate\\Contracts\\Auth\\Authenticatable>\n     */\n    protected string $userModelClass = \\App\\Models\\User::class;\n\n    /**\n     * @var class-string<\\DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser>\n     */\n    protected string $socialiteUserModelClass = SocialiteUser::class;\n\n    /**\n     * @param class-string<\\Illuminate\\Contracts\\Auth\\Authenticatable> $value\n     */\n    public function userModelClass(string $value): static\n    {\n        $this->userModelClass = $value;\n\n        return $this;\n    }\n\n    /**\n     * @return class-string<\\Illuminate\\Contracts\\Auth\\Authenticatable>\n     */\n    public function getUserModelClass(): string\n    {\n        return $this->userModelClass;\n    }\n\n    /**\n     * @param class-string<\\DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser> $value\n     */\n    public function socialiteUserModelClass(string $value): static\n    {\n        $this->socialiteUserModelClass = $value;\n\n        return $this;\n    }\n\n    /**\n     * @return class-string<\\DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser>\n     */\n    public function getSocialiteUserModelClass(): string\n    {\n        return $this->socialiteUserModelClass;\n    }\n\n    public function getSocialiteUserModel(): FilamentSocialiteUserContract\n    {\n        return new ($this->getSocialiteUserModelClass());\n    }\n}\n"
  },
  {
    "path": "src/Traits/Routes.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Traits;\n\ntrait Routes\n{\n    protected ?string $loginRouteName = null;\n\n    protected ?string $dashboardRouteName = null;\n\n    public function getRoute(): string\n    {\n        return \"socialite.{$this->getPanel()->generateRouteName('oauth.redirect')}\";\n    }\n\n    public function loginRouteName(string $value): static\n    {\n        $this->loginRouteName = $value;\n\n        return $this;\n    }\n\n    public function getLoginRouteName(): string\n    {\n        return $this->loginRouteName ?? $this->getPanel()->generateRouteName('auth.login');\n    }\n\n    public function dashboardRouteName(string $value): static\n    {\n        $this->dashboardRouteName = $value;\n\n        return $this;\n    }\n\n    public function getDashboardRouteName(): string\n    {\n        return $this->dashboardRouteName ?? $this->getPanel()->generateRouteName('pages.dashboard');\n    }\n}\n"
  },
  {
    "path": "src/View/Components/Buttons.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\View\\Components;\n\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Provider;\nuse Illuminate\\Support\\MessageBag;\nuse Illuminate\\View\\Component;\n\nclass Buttons extends Component\n{\n    protected FilamentSocialitePlugin $plugin;\n\n    public function __construct(\n        public bool $showDivider = true,\n    ) {\n        $this->plugin = FilamentSocialitePlugin::current();\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function render()\n    {\n        $messageBag = new MessageBag();\n\n        if (session()->has('filament-socialite-login-error')) {\n            $messageBag->add('login-failed', session()->pull('filament-socialite-login-error'));\n        }\n\n        return view('filament-socialite::components.buttons', [\n            'providers' => $providers = $this->plugin->getProviders(),\n            'visibleProviders' => array_filter($providers, fn (Provider $provider) => $provider->isVisible()),\n            'socialiteRoute' => $this->plugin->getRoute(),\n            'messageBag' => $messageBag,\n        ]);\n    }\n}\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "module.exports = {\n  content: [\"./resources/views/**/*.blade.php\"],\n  corePlugins: {\n    preflight: false,\n  },\n}\n"
  },
  {
    "path": "tests/Fixtures/TestSocialiteUser.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures;\n\nuse Laravel\\Socialite\\Contracts\\User;\n\nclass TestSocialiteUser implements User\n{\n    public string $email = 'test@example.com';\n\n    public function getId()\n    {\n        return 'test-socialite-user-id';\n    }\n\n    public function getNickname()\n    {\n        return 'test-socialite-user-nickname';\n    }\n\n    public function getName()\n    {\n        return 'test-socialite-user-name';\n    }\n\n    public function getEmail()\n    {\n        return $this->email;\n    }\n\n    public function getAvatar()\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "tests/Fixtures/TestTeam.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures;\n\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass TestTeam extends Model\n{\n    protected $table = 'teams';\n}\n"
  },
  {
    "path": "tests/Fixtures/TestTenantUser.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures;\n\nuse Filament\\Models\\Contracts\\HasTenants;\nuse Filament\\Panel;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Support\\Collection;\n\n/**\n * @property Collection<\\DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestTeam> $teams\n */\nclass TestTenantUser extends TestUser implements HasTenants\n{\n    public function canAccessTenant(Model $tenant): bool\n    {\n        return $this->teams->contains($tenant);\n    }\n\n    /**\n     * @return \\Illuminate\\Support\\Collection<int, \\DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestTeam>\n     */\n    public function getTenants(Panel $panel): Collection\n    {\n        return $this->teams;\n    }\n\n    /**\n     * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany<\\DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestTeam, $this>\n     */\n    public function teams(): BelongsToMany\n    {\n        return $this->belongsToMany(TestTeam::class, 'team_user', 'user_id', 'team_id');\n    }\n}\n"
  },
  {
    "path": "tests/Fixtures/TestUser.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures;\n\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass TestUser extends Model implements Authenticatable\n{\n    protected $table = 'users';\n\n    protected $guarded = [];\n\n    public function getAuthIdentifierName(): string\n    {\n        return 'test-user-auth-identifier-name';\n    }\n\n    public function getAuthIdentifier(): string\n    {\n        return 'test-user-auth-identifier';\n    }\n\n    public function getAuthPassword(): string\n    {\n        return 'test-user-auth-password';\n    }\n\n    public function getAuthPasswordName(): string\n    {\n        return 'test-user-auth-password';\n    }\n\n    public function getRememberToken(): string\n    {\n        return 'test-user-remember-token';\n    }\n\n    public function setRememberToken($value): void\n    {\n        //\n    }\n\n    public function getRememberTokenName(): string\n    {\n        return 'test-user-remember-token-name';\n    }\n}\n"
  },
  {
    "path": "tests/Fixtures/change_nullable_password_on_users_table.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class() extends Migration {\n    public function up(): void\n    {\n        Schema::table('users', static function (Blueprint $table): void {\n            $table->string('password')->nullable()->change();\n        });\n    }\n\n    public function down(): void\n    {\n        //\n    }\n};\n"
  },
  {
    "path": "tests/Fixtures/create_socialite_users_table.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class() extends Migration {\n    public function up(): void\n    {\n        Schema::create('socialite_users', function (Blueprint $table) {\n            $table->id();\n\n            $table->foreignId('user_id');\n            $table->string('provider');\n            $table->string('provider_id');\n\n            $table->timestamps();\n\n            $table->unique([\n                'provider',\n                'provider_id',\n            ]);\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::dropIfExists('socialite_users');\n    }\n};\n"
  },
  {
    "path": "tests/Fixtures/create_team_user_table.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class() extends Migration {\n    public function up(): void\n    {\n        Schema::create('team_user', function (Blueprint $table) {\n            $table->integer('team_id');\n            $table->integer('user_id');\n\n            $table->unique(['team_id', 'user_id']);\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::dropIfExists('team_user');\n    }\n};\n"
  },
  {
    "path": "tests/Fixtures/create_teams_table.php",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class() extends Migration {\n    public function up(): void\n    {\n        Schema::create('teams', function (Blueprint $table) {\n            $table->id();\n            $table->string('name');\n        });\n    }\n\n    public function down(): void\n    {\n        Schema::dropIfExists('teams');\n    }\n};\n"
  },
  {
    "path": "tests/SocialiteLoginAuthorizationTest.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse DutchCodingCompany\\FilamentSocialite\\Events\\UserNotAllowed;\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Provider as PluginProvider;\nuse DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestSocialiteUser;\nuse Filament\\Facades\\Filament;\nuse Filament\\Pages\\Dashboard;\nuse Filament\\Panel;\nuse Illuminate\\Support\\Facades\\Crypt;\nuse Illuminate\\Support\\Facades\\Event;\nuse Laravel\\Socialite\\Contracts\\Provider;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\nuse Laravel\\Socialite\\Facades\\Socialite;\nuse LogicException;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass SocialiteLoginAuthorizationTest extends TestCase\n{\n    protected function registerTestPanel(): void\n    {\n        Filament::registerPanel(\n            fn (): Panel => Panel::make()\n                ->default()\n                ->id($this::getPanelName())\n                ->path($this::getPanelName())\n                ->tenant(...$this->tenantArguments)\n                ->login()\n                ->pages([\n                    Dashboard::class,\n                ])\n                ->plugins([\n                    FilamentSocialitePlugin::make()\n                        ->providers([\n                            PluginProvider::make('github')\n                                ->label('GitHub')\n                                ->icon('fab-github')\n                                ->color('danger')\n                                ->outlined(false),\n                            PluginProvider::make('gitlab')\n                                ->label('GitLab')\n                                ->icon('fab-gitlab')\n                                ->color('danger')\n                                ->outlined()\n                                ->scopes([])\n                                ->with([]),\n                        ])\n                        ->registration(true)\n                        ->userModelClass($this->userModelClass)\n                        ->authorizeUserUsing(function (FilamentSocialitePlugin $plugin, SocialiteUserContract $oauthUser) {\n                            return $oauthUser->getEmail() === 'test@example.com';\n                        }),\n                ]),\n        );\n    }\n\n    #[DataProvider('loginDataProvider')]\n    public function testAuthorizationLogin(string $email, bool $dispatchesUserNotAllowedEvent): void\n    {\n        Event::fake();\n\n        $response = $this\n            ->getJson(route(\"socialite.filament.{$this::getPanelName()}.oauth.redirect\", ['provider' => 'github']))\n            ->assertStatus(302);\n\n        $state = session()->get('state');\n\n        $location = $response->headers->get('location') ?? throw new LogicException('Location header not set.');\n\n        parse_str($location, $urlQuery);\n\n        // Test if the correct state is sent to the endpoint in the \"Location\" header.\n        $this->assertEquals($state, $urlQuery['state']);\n\n        // Assert decrypting of the state gives the correct panel name.\n        $this->assertEquals($this::getPanelName(), Crypt::decrypt($state));\n\n        $user = new TestSocialiteUser();\n        $user->email = $email;\n\n        Socialite::shouldReceive('driver')\n            ->with('github')\n            ->andReturn(static::makeOAuthProviderMock(\n                request()->merge(['state' => $state]),\n                $user\n            ));\n\n        // Fake oauth response.\n        $response = $this\n            ->getJson(route(\"socialite.filament.{$this::getPanelName()}.oauth.callback\", ['provider' => 'github', 'state' => $state]))\n            ->assertStatus(302);\n\n        $dispatchesUserNotAllowedEvent\n            ? Event::assertDispatched(UserNotAllowed::class)\n            : Event::assertNotDispatched(UserNotAllowed::class);\n    }\n\n    /**\n     * @return array<string, array{0: string, 1: bool}>\n     */\n    public static function loginDataProvider(): array\n    {\n        return [\n            'User is authorized to use the application so UserNotAllowedEvent should not be dispatched' => [\n                'test@example.com',\n                false,\n            ],\n            'User is not authorized to use the application so UserNotAllowedEvent should be dispatched' => [\n                'test@example1.com',\n                true,\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/SocialiteLoginTest.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse Closure;\nuse DutchCodingCompany\\FilamentSocialite\\Events\\InvalidState;\nuse DutchCodingCompany\\FilamentSocialite\\Events\\RegistrationNotEnabled;\nuse DutchCodingCompany\\FilamentSocialite\\Events\\UserNotAllowed;\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestSocialiteUser;\nuse Illuminate\\Contracts\\Auth\\Authenticatable;\nuse Illuminate\\Support\\Facades\\Crypt;\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Facades\\Event;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\nuse Laravel\\Socialite\\Facades\\Socialite;\nuse LogicException;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass SocialiteLoginTest extends TestCase\n{\n    #[DataProvider('loginDataProvider')]\n    public function testLogin(\n        string $email,\n        string $callbackRoute,\n        ?string $overrideState = null,\n        ?string $dispatchedErrorEvent = null,\n    ): void {\n        Event::fake();\n\n        $response = $this\n            ->getJson(route(\"socialite.filament.{$this::getPanelName()}.oauth.redirect\", ['provider' => 'github']))\n            ->assertStatus(302);\n\n        $state = session()->get('state');\n\n        $location = $response->headers->get('location') ?? throw new LogicException('Location header not set.');\n\n        parse_str($location, $urlQuery);\n\n        // Test if the correct state is sent to the endpoint in the \"Location\" header.\n        $this->assertEquals($state, $urlQuery['state']);\n\n        // Assert decrypting of the state gives the correct panel name.\n        $this->assertEquals($this::getPanelName(), Crypt::decrypt($state));\n\n        $user = new TestSocialiteUser();\n        $user->email = $email;\n\n        Socialite::shouldReceive('driver')\n            ->with('github')\n            ->andReturn(static::makeOAuthProviderMock(\n                request()->merge(['state' => $overrideState ?? $state]),\n                $user\n            ));\n\n        // Fake oauth response.\n        $response = $this\n            ->getJson(route($callbackRoute, ['provider' => 'github', 'state' => $state]))\n            ->assertStatus(302);\n\n        if ($dispatchedErrorEvent) {\n            Event::assertDispatched($dispatchedErrorEvent);\n\n            $this->assertDatabaseMissing('socialite_users', [\n                'provider' => 'github',\n                'provider_id' => 'test-socialite-user-id',\n            ]);\n\n            $this->assertDatabaseMissing('users', [\n                'name' => 'test-socialite-user-name',\n                'email' => $user->email,\n            ]);\n        } else {\n            $this->assertDatabaseHas('socialite_users', [\n                'provider' => 'github',\n                'provider_id' => 'test-socialite-user-id',\n            ]);\n\n            $this->assertDatabaseHas('users', [\n                'name' => 'test-socialite-user-name',\n                'email' => $user->email,\n            ]);\n        }\n    }\n\n    /**\n     * @return array<string, array{0: string, 1: string, 2: ?string, 3: ?string}>\n     */\n    public static function loginDataProvider(): array\n    {\n        return [\n            'Login fails when incorrect state (panelized callback route)' => [\n                'test@example.com',\n                // Use the new callback route that already contains the panel in the url.\n                'socialite.filament.'.static::getPanelName().'.oauth.callback',\n                'invalid-mocked-state',\n                InvalidState::class,\n            ],\n            'Login fails when incorrect state (general callback route)' => [\n                'test@example.com',\n                // Use the old callback route that determines the panel based on the state parameter.\n                'oauth.callback',\n                'invalid-mocked-state',\n                InvalidState::class,\n            ],\n            'Login succeeds when email in domain allow list' => [\n                'test@example.com',\n                'socialite.filament.'.static::getPanelName().'.oauth.callback',\n                null,\n                null,\n            ],\n            'Login fails when email not in domain allow list' => [\n                'test@example1.com',\n                'socialite.filament.'.static::getPanelName().'.oauth.callback',\n                null,\n                UserNotAllowed::class,\n            ],\n        ];\n    }\n\n    #[DataProvider('registrationBlockProvider')]\n    public function testRegistrationBlock(bool $createUser, Closure | bool $registrationEnabled): void\n    {\n        Event::fake();\n\n        if ($createUser) {\n            DB::table('users')->insert([\n                'name' => 'test-user',\n                'email' => 'test@example.com',\n            ]);\n        }\n\n        FilamentSocialitePlugin::current()->registration($registrationEnabled);\n\n        $this\n            ->getJson(route(\"socialite.filament.{$this::getPanelName()}.oauth.redirect\", ['provider' => 'github']))\n            ->assertStatus(302);\n\n        $state = session()->get('state');\n\n        Socialite::shouldReceive('driver')\n            ->with('github')\n            ->andReturn(static::makeOAuthProviderMock(\n                request()->merge(['state' => $state]),\n                new TestSocialiteUser()\n            ));\n\n        // Fake oauth response.\n        $this\n            ->getJson(route(\"socialite.filament.{$this::getPanelName()}.oauth.callback\", ['provider' => 'github', 'state' => $state]))\n            ->assertStatus(302);\n\n        if (! $createUser) {\n            // If there is no user, the event should have been dispatched since the plugin option disabled registration.\n            Event::assertDispatched(RegistrationNotEnabled::class);\n        }\n    }\n\n    /**\n     * @return array<string, array<mixed>>\n     */\n    public static function registrationBlockProvider(): array\n    {\n        $callback = function (string $provider, SocialiteUserContract $oauthUser, ?Authenticatable $user) {\n            return (bool) $user;\n        };\n\n        return [\n            'Authenticatable exists for socialite user' => [true, $callback],\n            'Authenticatable does not exist for socialite user' => [false, $callback],\n            'Registration is always blocked' => [true, false],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/SocialiteStatelessLoginTest.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse DutchCodingCompany\\FilamentSocialite\\Events\\InvalidState;\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Provider;\nuse DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestSocialiteUser;\nuse Filament\\Facades\\Filament;\nuse Filament\\Pages\\Dashboard;\nuse Filament\\Panel;\nuse Illuminate\\Support\\Facades\\Crypt;\nuse Illuminate\\Support\\Facades\\Event;\nuse Laravel\\Socialite\\Facades\\Socialite;\nuse LogicException;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass SocialiteStatelessLoginTest extends TestCase\n{\n    protected function registerTestPanel(): void\n    {\n        Filament::registerPanel(\n            fn (): Panel => Panel::make()\n                ->default()\n                ->id($this::getPanelName())\n                ->path($this::getPanelName())\n                ->tenant(...$this->tenantArguments)\n                ->login()\n                ->pages([\n                    Dashboard::class,\n                ])\n                ->plugins([\n                    FilamentSocialitePlugin::make()\n                        ->providers([\n                            Provider::make('github')\n                                ->label('GitHub')\n                                ->icon('fab-github')\n                                ->color('danger')\n                                ->outlined(false)\n                                ->stateless(),\n                            Provider::make('gitlab')\n                                ->label('GitLab')\n                                ->icon('fab-gitlab')\n                                ->color('danger')\n                                ->outlined()\n                                ->scopes([])\n                                ->with([]),\n                        ])\n                        ->registration(true)\n                        ->userModelClass($this->userModelClass)\n                        ->domainAllowList(['example.com']),\n                ]),\n        );\n    }\n\n    #[DataProvider('statelessLoginDataProvider')]\n    public function testStatelessLogin(\n        string $email,\n        string $callbackRoute,\n        ?string $overrideState = null,\n        ?string $event = null,\n    ): void {\n        Event::fake();\n\n        $response = $this\n            ->getJson(route(\"socialite.filament.{$this::getPanelName()}.oauth.redirect\", ['provider' => 'github']))\n            ->assertStatus(302);\n\n        $state = session()->get('state');\n\n        $location = $response->headers->get('location') ?? throw new LogicException('Location header not set.');\n\n        parse_str($location, $urlQuery);\n\n        // Test if the correct state is sent to the endpoint in the \"Location\" header.\n        $this->assertEquals($state, $urlQuery['state']);\n\n        // Assert decrypting of the state gives the correct panel name.\n        $this->assertEquals($this::getPanelName(), Crypt::decrypt($state));\n\n        $user = new TestSocialiteUser();\n        $user->email = $email;\n\n        Socialite::shouldReceive('driver')\n            ->with('github')\n            ->andReturn(static::makeOAuthProviderMock(\n                request()->merge(['state' => $overrideState ?? $state]),\n                $user\n            ));\n\n        // Fake oauth response.\n        $response = $this\n            ->getJson(route($callbackRoute, ['provider' => 'github', 'state' => $state]))\n            ->assertStatus(302);\n\n        if ($event !== null) {\n            Event::assertNotDispatched($event);\n        }\n\n        $this->assertDatabaseHas('socialite_users', [\n            'provider' => 'github',\n            'provider_id' => 'test-socialite-user-id',\n        ]);\n\n        $this->assertDatabaseHas('users', [\n            'name' => 'test-socialite-user-name',\n            'email' => $user->email,\n        ]);\n    }\n\n    /**\n     * @return array<string, array{0: string, 1: string, 2: ?string, 3: ?string}>\n     */\n    public static function statelessLoginDataProvider(): array\n    {\n        return [\n            'Stateless login succeeds (panelized callback route)' => [\n                'test@example.com',\n                // Use the new callback route that already contains the panel in the url.\n                'socialite.filament.'.static::getPanelName().'.oauth.callback',\n                null,\n                InvalidState::class,\n            ],\n            'Stateless login succeeds with mocked state (general callback route)' => [\n                'test@example.com',\n                // Use the old callback route that determines the panel based on the state parameter.\n                'oauth.callback',\n                'invalid-mocked-state',\n                InvalidState::class,\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/SocialiteTenantLoginTest.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\SocialiteUser;\nuse DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestSocialiteUser;\nuse DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestTeam;\nuse DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestTenantUser;\nuse Laravel\\Socialite\\Contracts\\Provider;\nuse Laravel\\Socialite\\Facades\\Socialite;\nuse LogicException;\nuse Mockery;\n\nclass SocialiteTenantLoginTest extends TestCase\n{\n    protected string $userModelClass = TestTenantUser::class;\n\n    protected array $tenantArguments = [\n        TestTeam::class,\n    ];\n\n    public function testTenantLogin(): void\n    {\n        FilamentSocialitePlugin::current()->redirectAfterLoginUsing(function (string $provider, FilamentSocialiteUserContract $socialiteUser, FilamentSocialitePlugin $plugin) {\n            assert($socialiteUser instanceof SocialiteUser);\n\n            $this->assertEquals($this::getPanelName(), $plugin->getPanel()->getId());\n            $this->assertEquals('github', $provider);\n            $this->assertEquals('github', $socialiteUser->provider);\n            $this->assertEquals('test-socialite-user-id', $socialiteUser->provider_id);\n\n            return redirect()->to('/some-tenant-url');\n        });\n\n        $response = $this\n            ->getJson(\"/{$this::getPanelName()}/oauth/github\")\n            ->assertStatus(302);\n\n        $state = session()->get('state');\n\n        Socialite::shouldReceive('driver')\n            ->with('github')\n            ->andReturn(static::makeOAuthProviderMock(\n                request()->merge(['state' => $state]),\n                new TestSocialiteUser()\n            ));\n\n        // Fake oauth response.\n        $response = $this\n            ->getJson(\"/oauth/callback/github?state=$state\")\n            ->assertStatus(302);\n\n        $this->assertStringContainsString('/some-tenant-url', $response->headers->get('Location') ?? throw new LogicException('Location header not set.'));\n\n        $this->assertDatabaseHas('socialite_users', [\n            'provider' => 'github',\n            'provider_id' => 'test-socialite-user-id',\n        ]);\n\n        $this->assertDatabaseHas('users', [\n            'name' => 'test-socialite-user-name',\n            'email' => 'test@example.com',\n        ]);\n    }\n}\n"
  },
  {
    "path": "tests/TestCase.php",
    "content": "<?php\n\nnamespace DutchCodingCompany\\FilamentSocialite\\Tests;\n\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialitePlugin;\nuse DutchCodingCompany\\FilamentSocialite\\FilamentSocialiteServiceProvider;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\Contracts\\FilamentSocialiteUser as FilamentSocialiteUserContract;\nuse DutchCodingCompany\\FilamentSocialite\\Models\\SocialiteUser;\nuse DutchCodingCompany\\FilamentSocialite\\Provider;\nuse DutchCodingCompany\\FilamentSocialite\\Tests\\Fixtures\\TestUser;\nuse Filament\\Facades\\Filament;\nuse Filament\\FilamentServiceProvider;\nuse Filament\\Pages\\Dashboard;\nuse Filament\\Panel;\nuse Illuminate\\Contracts\\Http\\Kernel;\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Illuminate\\Encryption\\Encrypter;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Session\\Middleware\\StartSession;\nuse Laravel\\Socialite\\Contracts\\User as SocialiteUserContract;\nuse Laravel\\Socialite\\SocialiteServiceProvider;\nuse Laravel\\Socialite\\Two\\AbstractProvider;\nuse Livewire\\LivewireServiceProvider;\nuse Mockery\\LegacyMockInterface;\nuse Orchestra\\Testbench\\TestCase as Orchestra;\n\nclass TestCase extends Orchestra\n{\n    /**\n     * @var class-string<\\Illuminate\\Contracts\\Auth\\Authenticatable>\n     */\n    protected string $userModelClass = TestUser::class;\n\n    /**\n     * @var array{0: ?class-string<\\Illuminate\\Database\\Eloquent\\Model>, 1?: ?string, 2?: ?string}\n     */\n    protected array $tenantArguments = [null];\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        Filament::setCurrentPanel(self::getPanelName());\n\n        Factory::guessFactoryNamesUsing(\n            fn (\n                string $modelName,\n            ) => 'DutchCodingCompany\\\\FilamentSocialite\\\\Database\\\\Factories\\\\'.class_basename($modelName).'Factory'\n        );\n\n        $this->app?->make(Kernel::class)->pushMiddleware(StartSession::class);\n    }\n\n    protected function getPackageProviders($app)\n    {\n        $this->registerTestPanel();\n\n        return [\n            FilamentServiceProvider::class,\n            FilamentSocialiteServiceProvider::class,\n            SocialiteServiceProvider::class,\n            LivewireServiceProvider::class,\n        ];\n    }\n\n    protected function registerTestPanel(): void\n    {\n        Filament::registerPanel(\n            fn (): Panel => Panel::make()\n                ->default()\n                ->id($this::getPanelName())\n                ->path($this::getPanelName())\n                ->tenant(...$this->tenantArguments)\n                ->login()\n                ->pages([\n                    Dashboard::class,\n                ])\n                ->plugins([\n                    FilamentSocialitePlugin::make()\n                        ->providers([\n                            Provider::make('github')\n                                ->label('GitHub')\n                                ->icon('fab-github')\n                                ->color('danger')\n                                ->outlined(false),\n                            Provider::make('gitlab')\n                                ->label('GitLab')\n                                ->icon('fab-gitlab')\n                                ->color('danger')\n                                ->outlined()\n                                ->scopes([])\n                                ->with([]),\n                        ])\n                        ->registration(true)\n                        ->userModelClass($this->userModelClass)\n                        ->domainAllowList(['example.com']),\n                ]),\n        );\n    }\n\n    public function getEnvironmentSetUp($app)\n    {\n        config()->set('app.key', 'base64:'.base64_encode(\n            Encrypter::generateKey('AES-256-CBC')\n        ));\n\n        config()->set('services.github', [\n            'client_id' => 'abcdmockedabcd',\n            'client_secret' => 'defgmockeddefg',\n            'redirect' => 'http://localhost/oauth/callback/github',\n        ]);\n    }\n\n    protected function defineDatabaseMigrations()\n    {\n        $this->loadLaravelMigrations();\n        $this->loadMigrationsFrom(__DIR__.'/Fixtures');\n    }\n\n    protected static function getPanelName(): string\n    {\n        return 'testpanel';\n    }\n\n    protected static function makeOAuthProviderMock(\n        Request $request,\n        SocialiteUserContract $user,\n    ): LegacyMockInterface {\n        $mock = \\Mockery::mock(\n            AbstractProvider::class,\n            [$request, 'test-client-id', 'test-client-secret', 'test-redirect-url']\n        )\n            ->makePartial()\n            ->shouldAllowMockingProtectedMethods();\n\n        $mock->shouldReceive('getAccessTokenResponse')->andReturn([]);\n        $mock->shouldReceive('getUserByToken')->andReturn([]);\n        $mock->shouldReceive('userInstance')->andReturn($user);\n\n        return $mock;\n    }\n}\n"
  }
]