-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: Initial implementation * feat: Added user migration step * fix: Removed github mention from api
- Loading branch information
1 parent
59ed941
commit fd0e3ac
Showing
25 changed files
with
1,124 additions
and
426 deletions.
There are no files selected for viewing
124 changes: 0 additions & 124 deletions
124
app/Http/Controllers/Auth/GitHubSocialiteController.php
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
144 changes: 144 additions & 0 deletions
144
app/Http/Controllers/Connections/ConnectionsController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Http\Controllers\Connections; | ||
|
||
use App\Http\Controllers\Controller; | ||
use App\Models\User; | ||
use App\Models\UserConnection; | ||
use Illuminate\Http\RedirectResponse; | ||
use Illuminate\Support\Facades\Auth; | ||
use Laravel\Socialite\Contracts\User as SocialiteUser; | ||
use Laravel\Socialite\Facades\Socialite; | ||
use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirectResponse; | ||
use Toaster; | ||
|
||
abstract class ConnectionsController extends Controller | ||
{ | ||
/** | ||
* Redirect the user to the provider's authentication page. | ||
*/ | ||
protected function redirectToProvider(string $provider): SymfonyRedirectResponse | ||
{ | ||
return Socialite::driver($provider)->redirect(); | ||
} | ||
|
||
/** | ||
* Handle the provider callback for authentication or account linking. | ||
*/ | ||
protected function handleProviderCallback(string $provider): RedirectResponse | ||
{ | ||
$socialiteUser = Socialite::driver($provider)->user(); | ||
|
||
$existingConnection = UserConnection::where('provider_name', $provider) | ||
->where('provider_user_id', $socialiteUser->getId()) | ||
->first(); | ||
|
||
if ($existingConnection) { | ||
return $this->signInWithExistingConnection($existingConnection); | ||
} | ||
|
||
$user = Auth::user(); | ||
if ($user instanceof User) { | ||
return $this->linkAccount($user, $provider, $socialiteUser); | ||
} | ||
|
||
return $this->registerOrSignInUser($provider, $socialiteUser); | ||
} | ||
|
||
/** | ||
* Sign in with an existing connection. | ||
*/ | ||
protected function signInWithExistingConnection(UserConnection $userConnection): RedirectResponse | ||
{ | ||
$user = $userConnection->getAttribute('user'); | ||
if ($user instanceof User) { | ||
Auth::login($user); | ||
|
||
return redirect()->route('overview'); | ||
} | ||
|
||
Toaster::error('Unable to sign in. User not found.'); | ||
|
||
return redirect()->route('login'); | ||
} | ||
|
||
/** | ||
* Register a new user or sign in an existing user based on their email. | ||
*/ | ||
protected function registerOrSignInUser(string $provider, SocialiteUser $socialiteUser): RedirectResponse | ||
{ | ||
$user = User::where('email', $socialiteUser->getEmail())->first(); | ||
|
||
if ($user instanceof User) { | ||
$this->createConnection($user, $provider, $socialiteUser); | ||
Auth::login($user); | ||
Toaster::success(ucfirst($provider) . ' account successfully linked and authenticated.'); | ||
|
||
return redirect()->route('overview'); | ||
} | ||
|
||
$user = User::create([ | ||
'name' => $socialiteUser->getName(), | ||
'email' => $socialiteUser->getEmail(), | ||
]); | ||
|
||
$this->createConnection($user, $provider, $socialiteUser); | ||
Auth::login($user); | ||
|
||
Toaster::success('Account created and authenticated via ' . ucfirst($provider) . '.'); | ||
|
||
return redirect()->route('overview'); | ||
} | ||
|
||
/** | ||
* Link a provider account to an existing user. | ||
*/ | ||
protected function linkAccount(User $user, string $provider, SocialiteUser $socialiteUser): RedirectResponse | ||
{ | ||
$this->createConnection($user, $provider, $socialiteUser); | ||
|
||
Toaster::success(ucfirst($provider) . ' account successfully linked to your profile.'); | ||
|
||
return redirect()->route('profile.connections'); | ||
} | ||
|
||
/** | ||
* Create a new user connection. | ||
*/ | ||
protected function createConnection(User $user, string $provider, SocialiteUser $socialiteUser): void | ||
{ | ||
$expiresIn = method_exists($socialiteUser, 'getExpiresIn') ? $socialiteUser->getExpiresIn() : null; | ||
$approvedScopes = method_exists($socialiteUser, 'getApprovedScopes') ? $socialiteUser->getApprovedScopes() : null; | ||
|
||
// Handle access token retrieval | ||
$accessToken = null; | ||
if (method_exists($socialiteUser, 'getToken')) { | ||
$accessToken = $socialiteUser->getToken(); | ||
} elseif (property_exists($socialiteUser, 'token')) { | ||
$accessToken = $socialiteUser->token; | ||
} elseif (method_exists($socialiteUser, 'accessTokenResponseBody')) { | ||
$tokenResponse = $socialiteUser->accessTokenResponseBody(); | ||
$accessToken = $tokenResponse['access_token'] ?? null; | ||
} | ||
|
||
// Handle refresh token retrieval | ||
$refreshToken = null; | ||
if (method_exists($socialiteUser, 'getRefreshToken')) { | ||
$refreshToken = $socialiteUser->getRefreshToken(); | ||
} elseif (property_exists($socialiteUser, 'refreshToken')) { | ||
$refreshToken = $socialiteUser->refreshToken; | ||
} | ||
|
||
$user->connections()->create([ | ||
'provider_name' => $provider, | ||
'provider_user_id' => $socialiteUser->getId(), | ||
'provider_email' => $socialiteUser->getEmail(), | ||
'access_token' => $accessToken, | ||
'refresh_token' => $refreshToken, | ||
'token_expires_at' => $expiresIn ? now()->addSeconds($expiresIn) : null, | ||
'scopes' => $approvedScopes, | ||
]); | ||
} | ||
} |
Oops, something went wrong.