From f02c71d075213ec62237e042ed1a9c5d0c541dfd Mon Sep 17 00:00:00 2001 From: Ian Morland <16573496+imorland@users.noreply.github.com> Date: Mon, 14 Nov 2022 11:03:21 +0000 Subject: [PATCH] feat: allow linking oauth to existing account (#3) * 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 --- .gitignore | 2 + src/Controllers/AbstractOAuthController.php | 50 +++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..987e2a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/src/Controllers/AbstractOAuthController.php b/src/Controllers/AbstractOAuthController.php index 721408f..c984b6e 100644 --- a/src/Controllers/AbstractOAuthController.php +++ b/src/Controllers/AbstractOAuthController.php @@ -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; @@ -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()); @@ -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), @@ -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 = ''; + + return new HtmlResponse($content); + } + /** * Get OAuth route name, used for redirect url * Example: 'auth.github'.