the browser-facing portion of osu!
at master 4.2 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6declare(strict_types=1); 7 8namespace App\Http\Controllers\Account; 9 10use App\Http\Controllers\Controller; 11use App\Models\GithubUser; 12use League\OAuth2\Client\Provider\Exception\GithubIdentityProviderException; 13use League\OAuth2\Client\Provider\Github as GithubProvider; 14 15class GithubUsersController extends Controller 16{ 17 public function __construct() 18 { 19 $this->middleware('auth'); 20 $this->middleware('verify-user'); 21 22 parent::__construct(); 23 } 24 25 public function callback() 26 { 27 $params = get_params(request()->all(), null, [ 28 'code:string', 29 'error:string', 30 'state:string', 31 ], ['null_missing' => true]); 32 33 abort_if($params['state'] === null, 422, 'Missing state parameter.'); 34 abort_unless( 35 hash_equals(session()->pull('github_auth_state', ''), $params['state']), 36 403, 37 'Invalid state.', 38 ); 39 40 // If the user denied authorization on GitHub, redirect back to the GitHub account settings 41 // <https://docs.github.com/en/apps/oauth-apps/maintaining-oauth-apps/troubleshooting-authorization-request-errors#access-denied> 42 if ($params['error'] === 'access_denied') { 43 return redirect(route('account.edit').'#github'); 44 } 45 46 abort_if($params['error'] !== null, 500, 'Error obtaining authorization from GitHub.'); 47 abort_if($params['code'] === null, 422, 'Missing code parameter.'); 48 49 try { 50 $token = $this 51 ->makeGithubOAuthProvider() 52 ->getAccessToken('authorization_code', ['code' => $params['code']]); 53 } catch (GithubIdentityProviderException $exception) { 54 switch ($exception->getMessage()) { 55 // <https://docs.github.com/en/apps/oauth-apps/maintaining-oauth-apps/troubleshooting-oauth-app-access-token-request-errors#bad-verification-code> 56 case 'bad_verification_code': 57 return abort(422, 'Invalid authorization code.'); 58 59 // <https://docs.github.com/en/apps/oauth-apps/maintaining-oauth-apps/troubleshooting-oauth-app-access-token-request-errors#unverified-user-email> 60 case 'unverified_user_email': 61 return abort(422, osu_trans('accounts.github_user.error.unverified_email')); 62 63 default: 64 throw $exception; 65 } 66 } 67 68 $client = new \Github\Client(); 69 $client->authenticate($token->getToken(), \Github\AuthMethod::ACCESS_TOKEN); 70 $apiUser = $client->currentUser()->show(); 71 72 $githubUser = GithubUser::firstWhere('canonical_id', $apiUser['id']); 73 74 abort_if($githubUser === null, 422, osu_trans('accounts.github_user.error.no_contribution')); 75 abort_if($githubUser->user_id !== null, 422, osu_trans('accounts.github_user.error.already_linked')); 76 77 $githubUser->update([ 78 'user_id' => auth()->id(), 79 'username' => $apiUser['login'], 80 ]); 81 82 return redirect(route('account.edit').'#github'); 83 } 84 85 public function create() 86 { 87 abort_unless(GithubUser::canAuthenticate(), 404); 88 89 if (auth()->user()->githubUser()->exists()) { 90 return redirect(route('account.edit').'#github'); 91 } 92 93 $provider = $this->makeGithubOAuthProvider(); 94 $url = $provider->getAuthorizationUrl([ 95 'allow_signup' => 'false', 96 'scope' => ' ', // Provider doesn't support empty scope 97 ]); 98 99 session()->put('github_auth_state', $provider->getState()); 100 101 return redirect($url); 102 } 103 104 public function destroy() 105 { 106 auth()->user()->githubUser()->update(['user_id' => null]); 107 108 return response(null, 204); 109 } 110 111 private function makeGithubOAuthProvider(): GithubProvider 112 { 113 return new GithubProvider([ 114 'clientId' => $GLOBALS['cfg']['osu']['github']['client_id'], 115 'clientSecret' => $GLOBALS['cfg']['osu']['github']['client_secret'], 116 ]); 117 } 118}