Skip to content

Commit

Permalink
feat: allow linking oauth to existing account (#3)
Browse files Browse the repository at this point in the history
* feat: allow linking oauth to existing account

* Apply fixes from StyleCI

* handle first time login via the redirectUrl (edge case)

* Apply fixes from StyleCI

* dont pass unneeded param

* check provider id is not already linked to another user

* Apply fixes from StyleCI

Co-authored-by: StyleCI Bot <[email protected]>
  • Loading branch information
imorland and StyleCIBot authored Nov 14, 2022
1 parent c0d0c79 commit f02c71d
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
composer.lock
vendor
50 changes: 50 additions & 0 deletions src/Controllers/AbstractOAuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@
use Exception;
use Flarum\Forum\Auth\Registration;
use Flarum\Forum\Auth\ResponseFactory;
use Flarum\Foundation\ValidationException;
use Flarum\Http\RequestUtil;
use Flarum\Http\UrlGenerator;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\LoginProvider;
use Flarum\User\User;
use Illuminate\Session\Store;
use Illuminate\Support\Arr;
use Laminas\Diactoros\Response\HtmlResponse;
use Laminas\Diactoros\Response\RedirectResponse;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
Expand Down Expand Up @@ -65,11 +71,16 @@ public function handle(ServerRequestInterface $request): ResponseInterface
$redirectUri = $this->url->to('forum')->route($this->getRouteName());
$provider = $this->getProvider($redirectUri);

/** @var Store $session */
$session = $request->getAttribute('session');
$queryParams = $request->getQueryParams();
$code = Arr::get($queryParams, 'code');
$state = Arr::get($queryParams, 'state');

if ($requestLinkTo = Arr::pull($queryParams, 'linkTo')) {
$session->put('linkTo', $requestLinkTo);
}

if (!$code) {
$authUrl = $provider->getAuthorizationUrl($this->getAuthorizationUrlOptions());
$session->put('oauth2state', $provider->getState());
Expand All @@ -84,6 +95,21 @@ public function handle(ServerRequestInterface $request): ResponseInterface
$token = $provider->getAccessToken('authorization_code', compact('code'));
$user = $provider->getResourceOwner($token);

if ($shouldLink = $session->remove('linkTo')) {
// Don't register a new user, just link to the existing account, else continue with registration.
$actor = RequestUtil::getActor($request);

if ($actor->exists) {
$actor->assertRegistered();

if ($actor->id !== (int) $shouldLink) {
throw new ValidationException(['linkAccount' => 'User data mismatch']);
}

return $this->link($actor, $user);
}
}

return $this->response->make(
$this->getProviderName(),
$this->getIdentifier($user),
Expand All @@ -93,6 +119,30 @@ function (Registration $registration) use ($user, $token) {
);
}

/**
* Link the currently authenticated user to the OAuth account.
*
* @param User $user
* @param ResourceOwnerInterface $resourceOwner
*
* @return HtmlResponse
*/
protected function link(User $user, $resourceOwner): HtmlResponse
{
if (LoginProvider::where('identifier', $this->getIdentifier($resourceOwner))->where('provider', $this->getProviderName())->exists()) {
throw new ValidationException(['linkAccount' => 'Account already linked to another user']);
}

$user->loginProviders()->firstOrCreate([
'provider' => $this->getProviderName(),
'identifier' => $this->getIdentifier($resourceOwner),
])->touch();

$content = '<script>window.close(); window.opener.location.reload();</script>';

return new HtmlResponse($content);
}

/**
* Get OAuth route name, used for redirect url
* Example: 'auth.github'.
Expand Down

0 comments on commit f02c71d

Please sign in to comment.