diff --git a/.agents/skills/socialite-development/SKILL.md b/.agents/skills/socialite-development/SKILL.md
new file mode 100644
index 0000000..e660da6
--- /dev/null
+++ b/.agents/skills/socialite-development/SKILL.md
@@ -0,0 +1,80 @@
+---
+name: socialite-development
+description: "Manages OAuth social authentication with Laravel Socialite. Activate when adding social login providers; configuring OAuth redirect/callback flows; retrieving authenticated user details; customizing scopes or parameters; setting up community providers; testing with Socialite fakes; or when the user mentions social login, OAuth, Socialite, or third-party authentication."
+license: MIT
+metadata:
+ author: laravel
+---
+
+# Socialite Authentication
+
+## Documentation
+
+Use `search-docs` for detailed Socialite patterns and documentation (installation, configuration, routing, callbacks, testing, scopes, stateless auth).
+
+## Available Providers
+
+Built-in: `facebook`, `twitter`, `twitter-oauth-2`, `linkedin`, `linkedin-openid`, `google`, `github`, `gitlab`, `bitbucket`, `slack`, `slack-openid`, `twitch`
+
+Community: 150+ additional providers at [socialiteproviders.com](https://socialiteproviders.com). For provider-specific setup, use `WebFetch` on `https://socialiteproviders.com/{provider-name}`.
+
+Configuration key in `config/services.php` must match the driver name exactly — note the hyphenated keys: `twitter-oauth-2`, `linkedin-openid`, `slack-openid`.
+
+Twitter/X: Use `twitter-oauth-2` (OAuth 2.0) for new projects. The legacy `twitter` driver is OAuth 1.0. Driver names remain unchanged despite the platform rebrand.
+
+Community providers differ from built-in providers in the following ways:
+- Installed via `composer require socialiteproviders/{name}`
+- Must register via event listener — NOT auto-discovered like built-in providers
+- Use `search-docs` for the registration pattern
+
+## Adding a Provider
+
+### 1. Configure the provider
+
+Add the provider's `client_id`, `client_secret`, and `redirect` to `config/services.php`. The config key must match the driver name exactly.
+
+### 2. Create redirect and callback routes
+
+Two routes are needed: one that calls `Socialite::driver('provider')->redirect()` to send the user to the OAuth provider, and one that calls `Socialite::driver('provider')->user()` to receive the callback and retrieve user details.
+
+### 3. Authenticate and store the user
+
+In the callback, use `updateOrCreate` to find or create a user record from the provider's response (`id`, `name`, `email`, `token`, `refreshToken`), then call `Auth::login()`.
+
+### 4. Customize the redirect (optional)
+
+- `scopes()` — merge additional scopes with the provider's defaults
+- `setScopes()` — replace all scopes entirely
+- `with()` — pass optional parameters (e.g., `['hd' => 'example.com']` for Google)
+- `asBotUser()` — Slack only; generates a bot token (`xoxb-`) instead of a user token (`xoxp-`). Must be called before both `redirect()` and `user()`. Only the `token` property will be hydrated on the user object.
+- `stateless()` — for API/SPA contexts where session state is not maintained
+
+### 5. Verify
+
+1. Config key matches driver name exactly (check the list above for hyphenated names)
+2. `client_id`, `client_secret`, and `redirect` are all present
+3. Redirect URL matches what is registered in the provider's OAuth dashboard
+4. Callback route handles denied grants (when user declines authorization)
+
+Use `search-docs` for complete code examples of each step.
+
+## Additional Features
+
+Use `search-docs` for usage details on: `enablePKCE()`, `userFromToken($token)`, `userFromTokenAndSecret($token, $secret)` (OAuth 1.0), retrieving user details.
+
+User object: `getId()`, `getName()`, `getEmail()`, `getAvatar()`, `getNickname()`, `token`, `refreshToken`, `expiresIn`, `approvedScopes`
+
+## Testing
+
+Socialite provides `Socialite::fake()` for testing redirects and callbacks. Use `search-docs` for faking redirects, callback user data, custom token properties, and assertion methods.
+
+## Common Pitfalls
+
+- Config key must match driver name exactly — hyphenated drivers need hyphenated keys (`linkedin-openid`, `slack-openid`, `twitter-oauth-2`). Mismatch silently fails.
+- Every provider needs `client_id`, `client_secret`, and `redirect` in `config/services.php`. Missing any one causes cryptic errors.
+- `scopes()` merges with defaults; `setScopes()` replaces all scopes entirely.
+- Missing `stateless()` in API/SPA contexts causes `InvalidStateException`.
+- Redirect URL in `config/services.php` must exactly match the provider's OAuth dashboard (including trailing slashes and protocol).
+- Do not pass `state`, `response_type`, `client_id`, `redirect_uri`, or `scope` via `with()` — these are reserved.
+- Community providers require event listener registration via `SocialiteWasCalled`.
+- `user()` throws when the user declines authorization. Always handle denied grants.
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index bfe10c3..74c806d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,6 @@
/storage/*.key
/storage/pail
/resources/js/actions
-/resources/js/routes
/resources/js/wayfinder
/vendor
.DS_Store
diff --git a/AGENTS.md b/AGENTS.md
index 16fd5b1..e34262c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -15,6 +15,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
- laravel/framework (LARAVEL) - v13
- laravel/prompts (PROMPTS) - v0
- laravel/sanctum (SANCTUM) - v4
+- laravel/socialite (SOCIALITE) - v5
- laravel/wayfinder (WAYFINDER) - v0
- laravel/boost (BOOST) - v2
- laravel/mcp (MCP) - v0
diff --git a/app/Http/Controllers/Auth/SocialiteController.php b/app/Http/Controllers/Auth/SocialiteController.php
new file mode 100644
index 0000000..a3a8931
--- /dev/null
+++ b/app/Http/Controllers/Auth/SocialiteController.php
@@ -0,0 +1,59 @@
+redirect();
+ }
+
+ /**
+ * Obtain the user information from the provider.
+ */
+ public function callback(string $provider): RedirectResponse
+ {
+ $socialUser = Socialite::driver($provider)->user();
+
+ $socialAccount = Social::where('provider', $provider)
+ ->where('provider_id', $socialUser->getId())
+ ->first();
+
+ if ($socialAccount) {
+ Auth::login($socialAccount->user);
+
+ return redirect()->route('dashboard');
+ }
+
+ $user = User::where('email', $socialUser->getEmail())->first();
+
+ if (! $user) {
+ $user = User::create([
+ 'name' => $socialUser->getName() ?? $socialUser->getNickname(),
+ 'email' => $socialUser->getEmail(),
+ 'email_verified_at' => now(),
+ ]);
+ }
+
+ $user->socials()->create([
+ 'provider' => $provider,
+ 'provider_id' => $socialUser->getId(),
+ 'avatar' => $socialUser->getAvatar(),
+ ]);
+
+ Auth::login($user);
+
+ return redirect()->route('dashboard');
+ }
+}
diff --git a/app/Models/Social.php b/app/Models/Social.php
new file mode 100644
index 0000000..65594b9
--- /dev/null
+++ b/app/Models/Social.php
@@ -0,0 +1,24 @@
+belongsTo(User::class);
+ }
+
+ public function isProvider(string $provider): bool
+ {
+ return $this->provider === $provider;
+ }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index 5f02e6f..26b422b 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -27,6 +27,22 @@ public function registries(): HasMany
return $this->hasMany(Registry::class);
}
+ public function hasLinkedProvider(string $provider): bool
+ {
+ return $this->socials()->where('provider', $provider)->exists();
+ }
+
+
+ public function socials(): HasMany
+ {
+ return $this->hasMany(Social::class);
+ }
+
+ public function connectedAccounts(): HasMany
+ {
+ return $this->socials();
+ }
+
/**
* Get the attributes that should be cast.
*
diff --git a/boost.json b/boost.json
index df9f755..3713f5f 100644
--- a/boost.json
+++ b/boost.json
@@ -10,6 +10,7 @@
"skills": [
"fortify-development",
"laravel-best-practices",
+ "socialite-development",
"wayfinder-development",
"pest-testing",
"inertia-react-development",
diff --git a/composer.json b/composer.json
index e585a0c..8c33e02 100644
--- a/composer.json
+++ b/composer.json
@@ -11,9 +11,11 @@
"require": {
"php": "^8.3",
"inertiajs/inertia-laravel": "^3.0",
+ "laravel/cashier-paddle": "^2.8",
"laravel/fortify": "^1.34",
"laravel/framework": "^13.7",
"laravel/sanctum": "^4.0",
+ "laravel/socialite": "^5.27",
"laravel/tinker": "^3.0",
"laravel/wayfinder": "^0.1.14"
},
diff --git a/composer.lock b/composer.lock
index 419ffa2..ffa884b 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "40d749de359197d818c8415695320b74",
+ "content-hash": "a855c0d51b5e5235e56513ed3142fb04",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -661,6 +661,70 @@
],
"time": "2025-03-06T22:45:56+00:00"
},
+ {
+ "name": "firebase/php-jwt",
+ "version": "v7.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/googleapis/php-jwt.git",
+ "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380",
+ "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "guzzlehttp/guzzle": "^7.4",
+ "phpfastcache/phpfastcache": "^9.2",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.5",
+ "psr/cache": "^2.0||^3.0",
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.0"
+ },
+ "suggest": {
+ "ext-sodium": "Support EdDSA (Ed25519) signatures",
+ "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Firebase\\JWT\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Neuman Vong",
+ "email": "neuman+pear@twilio.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Anant Narayanan",
+ "email": "anant@php.net",
+ "role": "Developer"
+ }
+ ],
+ "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+ "homepage": "https://github.com/firebase/php-jwt",
+ "keywords": [
+ "jwt",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/googleapis/php-jwt/issues",
+ "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5"
+ },
+ "time": "2026-04-01T20:38:03+00:00"
+ },
{
"name": "fruitcake/php-cors",
"version": "v1.4.0",
@@ -1279,6 +1343,86 @@
},
"time": "2026-04-30T15:30:29+00:00"
},
+ {
+ "name": "laravel/cashier-paddle",
+ "version": "v2.8.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/cashier-paddle.git",
+ "reference": "5bc8b16a94cc3359e874a6025b762e1345cf46cf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/cashier-paddle/zipball/5bc8b16a94cc3359e874a6025b762e1345cf46cf",
+ "reference": "5bc8b16a94cc3359e874a6025b762e1345cf46cf",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-openssl": "*",
+ "guzzlehttp/guzzle": "^7.4.5",
+ "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0",
+ "illuminate/database": "^10.0|^11.0|^12.0|^13.0",
+ "illuminate/http": "^10.0|^11.0|^12.0|^13.0",
+ "illuminate/routing": "^10.0|^11.0|^12.0|^13.0",
+ "illuminate/support": "^10.0|^11.0|^12.0|^13.0",
+ "illuminate/view": "^10.0|^11.0|^12.0|^13.0",
+ "moneyphp/money": "^3.2|^4.0",
+ "nesbot/carbon": "^2.67|^3.0",
+ "php": "^8.1",
+ "spatie/url": "^1.3.5|^2.0",
+ "symfony/http-kernel": "^6.2|^7.0|^8.0",
+ "symfony/polyfill-intl-icu": "^1.22.1"
+ },
+ "require-dev": {
+ "orchestra/testbench": "^8.36|^9.15|^10.8|^11.0",
+ "phpstan/phpstan": "^1.10"
+ },
+ "suggest": {
+ "ext-intl": "Allows for more locales besides the default \"en\" when formatting money values."
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Laravel\\Paddle\\CashierServiceProvider"
+ ]
+ },
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Paddle\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Dries Vints",
+ "email": "dries@laravel.com"
+ }
+ ],
+ "description": "Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services.",
+ "keywords": [
+ "billing",
+ "laravel",
+ "paddle"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/cashier-paddle/issues",
+ "source": "https://github.com/laravel/cashier-paddle"
+ },
+ "time": "2026-04-01T15:55:32+00:00"
+ },
{
"name": "laravel/fortify",
"version": "v1.37.0",
@@ -1818,6 +1962,78 @@
},
"time": "2026-04-16T14:03:50+00:00"
},
+ {
+ "name": "laravel/socialite",
+ "version": "v5.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/socialite.git",
+ "reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/socialite/zipball/40e0757a75637c7b2dff05d3286b0d8fc25e5c0e",
+ "reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "firebase/php-jwt": "^6.4|^7.0",
+ "guzzlehttp/guzzle": "^6.0|^7.0",
+ "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+ "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0",
+ "league/oauth1-client": "^1.11",
+ "php": "^7.2|^8.0",
+ "phpseclib/phpseclib": "^3.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "orchestra/testbench": "^4.18|^5.20|^6.47|^7.55|^8.36|^9.15|^10.8|^11.0",
+ "phpstan/phpstan": "^1.12.23",
+ "phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5|^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "aliases": {
+ "Socialite": "Laravel\\Socialite\\Facades\\Socialite"
+ },
+ "providers": [
+ "Laravel\\Socialite\\SocialiteServiceProvider"
+ ]
+ },
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Socialite\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.",
+ "homepage": "https://laravel.com",
+ "keywords": [
+ "laravel",
+ "oauth"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/socialite/issues",
+ "source": "https://github.com/laravel/socialite"
+ },
+ "time": "2026-04-24T14:05:47+00:00"
+ },
{
"name": "laravel/tinker",
"version": "v3.0.2",
@@ -1889,16 +2105,16 @@
},
{
"name": "laravel/wayfinder",
- "version": "v0.1.17",
+ "version": "v0.1.18",
"source": {
"type": "git",
"url": "https://github.com/laravel/wayfinder.git",
- "reference": "6930cbf3052adef0047af20ca8566f7a4a406dba"
+ "reference": "29598f386cdeef2cf4144f32bdaefc7542ac38ce"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/wayfinder/zipball/6930cbf3052adef0047af20ca8566f7a4a406dba",
- "reference": "6930cbf3052adef0047af20ca8566f7a4a406dba",
+ "url": "https://api.github.com/repos/laravel/wayfinder/zipball/29598f386cdeef2cf4144f32bdaefc7542ac38ce",
+ "reference": "29598f386cdeef2cf4144f32bdaefc7542ac38ce",
"shasum": ""
},
"require": {
@@ -1948,7 +2164,7 @@
"issues": "https://github.com/laravel/wayfinder/issues",
"source": "https://github.com/laravel/wayfinder"
},
- "time": "2026-05-05T20:16:56+00:00"
+ "time": "2026-05-08T14:41:03+00:00"
},
{
"name": "league/commonmark",
@@ -2327,6 +2543,82 @@
],
"time": "2024-09-21T08:32:55+00:00"
},
+ {
+ "name": "league/oauth1-client",
+ "version": "v1.11.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/oauth1-client.git",
+ "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/f9c94b088837eb1aae1ad7c4f23eb65cc6993055",
+ "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-openssl": "*",
+ "guzzlehttp/guzzle": "^6.0|^7.0",
+ "guzzlehttp/psr7": "^1.7|^2.0",
+ "php": ">=7.1||>=8.0"
+ },
+ "require-dev": {
+ "ext-simplexml": "*",
+ "friendsofphp/php-cs-fixer": "^2.17",
+ "mockery/mockery": "^1.3.3",
+ "phpstan/phpstan": "^0.12.42",
+ "phpunit/phpunit": "^7.5||9.5"
+ },
+ "suggest": {
+ "ext-simplexml": "For decoding XML-based responses."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev",
+ "dev-develop": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\OAuth1\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ben Corlett",
+ "email": "bencorlett@me.com",
+ "homepage": "http://www.webcomm.com.au",
+ "role": "Developer"
+ }
+ ],
+ "description": "OAuth 1.0 Client Library",
+ "keywords": [
+ "Authentication",
+ "SSO",
+ "authorization",
+ "bitbucket",
+ "identity",
+ "idp",
+ "oauth",
+ "oauth1",
+ "single sign on",
+ "trello",
+ "tumblr",
+ "twitter"
+ ],
+ "support": {
+ "issues": "https://github.com/thephpleague/oauth1-client/issues",
+ "source": "https://github.com/thephpleague/oauth1-client/tree/v1.11.0"
+ },
+ "time": "2024-12-10T19:59:05+00:00"
+ },
{
"name": "league/uri",
"version": "7.8.1",
@@ -2509,6 +2801,96 @@
],
"time": "2026-03-08T20:05:35+00:00"
},
+ {
+ "name": "moneyphp/money",
+ "version": "v4.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/moneyphp/money.git",
+ "reference": "d49ee625c6ba79b9d7a228ce153b02fc1032152b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/moneyphp/money/zipball/d49ee625c6ba79b9d7a228ce153b02fc1032152b",
+ "reference": "d49ee625c6ba79b9d7a228ce153b02fc1032152b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-bcmath": "*",
+ "ext-filter": "*",
+ "ext-json": "*",
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
+ },
+ "require-dev": {
+ "cache/taggable-cache": "^1.1.0",
+ "doctrine/coding-standard": "^12.0",
+ "doctrine/instantiator": "^1.5.0 || ^2.0",
+ "ext-gmp": "*",
+ "ext-intl": "*",
+ "florianv/exchanger": "^2.8.1",
+ "florianv/swap": "^4.3.0",
+ "moneyphp/crypto-currencies": "^1.1.0",
+ "moneyphp/iso-currencies": "^3.4",
+ "php-http/message": "^1.16.0",
+ "php-http/mock-client": "^1.6.0",
+ "phpbench/phpbench": "^1.2.5",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1.9",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.5.9",
+ "psr/cache": "^1.0.1 || ^2.0 || ^3.0",
+ "ticketswap/phpstan-error-formatter": "^1.1"
+ },
+ "suggest": {
+ "ext-gmp": "Calculate without integer limits",
+ "ext-intl": "Format Money objects with intl",
+ "florianv/exchanger": "Exchange rates library for PHP",
+ "florianv/swap": "Exchange rates library for PHP",
+ "psr/cache-implementation": "Used for Currency caching"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Money\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mathias Verraes",
+ "email": "mathias@verraes.net",
+ "homepage": "http://verraes.net"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com"
+ },
+ {
+ "name": "Frederik Bosch",
+ "email": "f.bosch@genkgo.nl"
+ }
+ ],
+ "description": "PHP implementation of Fowler's Money pattern",
+ "homepage": "http://moneyphp.org",
+ "keywords": [
+ "Value Object",
+ "money",
+ "vo"
+ ],
+ "support": {
+ "issues": "https://github.com/moneyphp/money/issues",
+ "source": "https://github.com/moneyphp/money/tree/v4.9.0"
+ },
+ "time": "2026-05-04T20:23:15+00:00"
+ },
{
"name": "monolog/monolog",
"version": "3.10.0",
@@ -3089,6 +3471,56 @@
},
"time": "2025-09-24T15:06:41+00:00"
},
+ {
+ "name": "paragonie/random_compat",
+ "version": "v9.99.100",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/random_compat.git",
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">= 7"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*|5.*",
+ "vimeo/psalm": "^1"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com"
+ }
+ ],
+ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "keywords": [
+ "csprng",
+ "polyfill",
+ "pseudorandom",
+ "random"
+ ],
+ "support": {
+ "email": "info@paragonie.com",
+ "issues": "https://github.com/paragonie/random_compat/issues",
+ "source": "https://github.com/paragonie/random_compat"
+ },
+ "time": "2020-10-15T08:29:30+00:00"
+ },
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
@@ -3339,6 +3771,116 @@
],
"time": "2025-12-27T19:41:33+00:00"
},
+ {
+ "name": "phpseclib/phpseclib",
+ "version": "3.0.52",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpseclib/phpseclib.git",
+ "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce",
+ "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce",
+ "shasum": ""
+ },
+ "require": {
+ "paragonie/constant_time_encoding": "^1|^2|^3",
+ "paragonie/random_compat": "^1.4|^2.0|^9.99.99",
+ "php": ">=5.6.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "*"
+ },
+ "suggest": {
+ "ext-dom": "Install the DOM extension to load XML formatted public keys.",
+ "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
+ "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
+ "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
+ "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "phpseclib/bootstrap.php"
+ ],
+ "psr-4": {
+ "phpseclib3\\": "phpseclib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jim Wigginton",
+ "email": "terrafrost@php.net",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Patrick Monnerat",
+ "email": "pm@datasphere.ch",
+ "role": "Developer"
+ },
+ {
+ "name": "Andreas Fischer",
+ "email": "bantu@phpbb.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Hans-Jürgen Petrich",
+ "email": "petrich@tronic-media.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Graham Campbell",
+ "email": "graham@alt-three.com",
+ "role": "Developer"
+ }
+ ],
+ "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
+ "homepage": "http://phpseclib.sourceforge.net",
+ "keywords": [
+ "BigInteger",
+ "aes",
+ "asn.1",
+ "asn1",
+ "blowfish",
+ "crypto",
+ "cryptography",
+ "encryption",
+ "rsa",
+ "security",
+ "sftp",
+ "signature",
+ "signing",
+ "ssh",
+ "twofish",
+ "x.509",
+ "x509"
+ ],
+ "support": {
+ "issues": "https://github.com/phpseclib/phpseclib/issues",
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.52"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/terrafrost",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/phpseclib",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-04-27T07:02:15+00:00"
+ },
{
"name": "phpstan/phpdoc-parser",
"version": "2.3.2",
@@ -4127,6 +4669,119 @@
},
"time": "2025-12-14T04:43:48+00:00"
},
+ {
+ "name": "spatie/macroable",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/macroable.git",
+ "reference": "2967f69810ba4df158391665c0850010b9d1507c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/macroable/zipball/2967f69810ba4df158391665c0850010b9d1507c",
+ "reference": "2967f69810ba4df158391665c0850010b9d1507c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.3"
+ },
+ "require-dev": {
+ "pestphp/pest": "^4",
+ "phpunit/phpunit": "^12"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Macroable\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A trait to dynamically add methods to a class",
+ "homepage": "https://github.com/spatie/macroable",
+ "keywords": [
+ "macroable",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/macroable/issues",
+ "source": "https://github.com/spatie/macroable/tree/2.1.0"
+ },
+ "time": "2026-01-30T17:13:34+00:00"
+ },
+ {
+ "name": "spatie/url",
+ "version": "2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/url.git",
+ "reference": "93a51db743cdec22b081c64593e193887c9cd395"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/url/zipball/93a51db743cdec22b081c64593e193887c9cd395",
+ "reference": "93a51db743cdec22b081c64593e193887c9cd395",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.0",
+ "psr/http-message": "^1.0 || ^2.0",
+ "spatie/macroable": "^1.0 || ^2.0"
+ },
+ "require-dev": {
+ "pestphp/pest": "^1.21"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Url\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian De Deyne",
+ "email": "sebastian@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Parse, build and manipulate URL's",
+ "homepage": "https://github.com/spatie/url",
+ "keywords": [
+ "spatie",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/url/issues",
+ "source": "https://github.com/spatie/url/tree/2.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-08T11:35:19+00:00"
+ },
{
"name": "spomky-labs/cbor-php",
"version": "3.2.3",
@@ -5445,6 +6100,94 @@
],
"time": "2026-04-26T13:13:48+00:00"
},
+ {
+ "name": "symfony/polyfill-intl-icu",
+ "version": "v1.37.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-icu.git",
+ "reference": "3510b63d07376b04e57e27e82607d468bb134f78"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/3510b63d07376b04e57e27e82607d468bb134f78",
+ "reference": "3510b63d07376b04e57e27e82607d468bb134f78",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance and support of other locales than \"en\""
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Icu\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ],
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's ICU-related data and classes",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "icu",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.37.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-04-10T16:50:15+00:00"
+ },
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.37.0",
diff --git a/config/cashier.php b/config/cashier.php
new file mode 100644
index 0000000..99c661a
--- /dev/null
+++ b/config/cashier.php
@@ -0,0 +1,90 @@
+ env('PADDLE_SELLER_ID'),
+
+ 'client_side_token' => env('PADDLE_CLIENT_SIDE_TOKEN'),
+
+ 'api_key' => env('PADDLE_AUTH_CODE') ?? env('PADDLE_API_KEY'),
+
+ 'retain_key' => env('PADDLE_RETAIN_KEY'),
+
+ 'webhook_secret' => env('PADDLE_WEBHOOK_SECRET'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cashier Path
+ |--------------------------------------------------------------------------
+ |
+ | This is the base URI path where Cashier's views, such as the webhook
+ | route, will be available. You're free to tweak this path based on
+ | the needs of your particular application or design preferences.
+ |
+ */
+
+ 'path' => env('CASHIER_PATH', 'paddle'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cashier Webhook
+ |--------------------------------------------------------------------------
+ |
+ | This is the base URI where webhooks from Paddle will be sent. The URL
+ | built into Cashier Paddle is used by default; however, you can add
+ | a custom URL when required for any application testing purposes.
+ |
+ */
+
+ 'webhook' => env('CASHIER_WEBHOOK'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Currency
+ |--------------------------------------------------------------------------
+ |
+ | This is the default currency that will be used when generating charges
+ | from your application. Of course, you are welcome to use any of the
+ | various world currencies that are currently supported via Paddle.
+ |
+ */
+
+ 'currency' => env('CASHIER_CURRENCY', 'USD'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Currency Locale
+ |--------------------------------------------------------------------------
+ |
+ | This is the default locale in which your money values are formatted in
+ | for display. To utilize other locales besides the default en locale
+ | verify you have the "intl" PHP extension installed on the system.
+ |
+ */
+
+ 'currency_locale' => env('CASHIER_CURRENCY_LOCALE', 'en'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Paddle Sandbox
+ |--------------------------------------------------------------------------
+ |
+ | This option allows you to toggle between the Paddle live environment
+ | and its sandboxed environment.
+ |
+ */
+
+ 'sandbox' => env('PADDLE_SANDBOX', false),
+
+];
diff --git a/config/mcp.php b/config/mcp.php
new file mode 100644
index 0000000..34bb81e
--- /dev/null
+++ b/config/mcp.php
@@ -0,0 +1,54 @@
+ [
+ '*',
+ // 'https://example.com',
+ // 'http://localhost',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Allowed Custom Schemes
+ |--------------------------------------------------------------------------
+ |
+ | Native desktop OAuth clients like Cursor and VS Code use private-use URI
+ | schemes (RFC 8252) for redirect callbacks instead of standard schemes
+ | like HTTPS. Here, you may list which custom schemes you will allow.
+ |
+ */
+
+ 'custom_schemes' => [
+ // 'claude',
+ // 'cursor',
+ // 'vscode',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Authorization Server
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the OAuth authorization server issuer identifier
+ | per RFC 8414. This value appears in your protected resource and auth
+ | server metadata endpoints. When null, this defaults to `url('/')`.
+ |
+ */
+
+ 'authorization_server' => null,
+
+];
diff --git a/config/services.php b/config/services.php
index 6a90eb8..b8ced15 100644
--- a/config/services.php
+++ b/config/services.php
@@ -34,5 +34,16 @@
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
+ 'github' => [
+ 'client_id' => env('GITHUB_CLIENT_ID'),
+ 'client_secret' => env('GITHUB_CLIENT_SECRET'),
+ 'redirect' => env('GITHUB_REDIRECT_URI'),
+ ],
+
+ 'google' => [
+ 'client_id' => env('GOOGLE_CLIENT_ID'),
+ 'client_secret' => env('GOOGLE_CLIENT_SECRET'),
+ 'redirect' => env('GOOGLE_REDIRECT_URI'),
+ ],
];
diff --git a/database/factories/SocialFactory.php b/database/factories/SocialFactory.php
new file mode 100644
index 0000000..30c7470
--- /dev/null
+++ b/database/factories/SocialFactory.php
@@ -0,0 +1,24 @@
+
+ */
+class SocialFactory extends Factory
+{
+ /**
+ * Define the model's default state.
+ *
+ * @return array