From d2a2378f4b27fa0b26e3e815c6cc7fb1a5b032a9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 May 2019 13:18:18 +1000 Subject: [PATCH] OAuth from Third Party Client <> Server finished --- app/Http/Controllers/Auth/LoginController.php | 83 ++++++++++- app/Libraries/OAuth.php | 39 +++++ app/Libraries/OAuth/OAuth.php | 140 ++++++++++++++++++ app/Libraries/OAuth/Providers/Google.php | 22 +++ .../OAuth/Providers/ProviderInterface.php | 9 ++ .../2014_10_13_000000_create_users_table.php | 7 +- routes/api.php | 3 +- 7 files changed, 293 insertions(+), 10 deletions(-) create mode 100644 app/Libraries/OAuth/OAuth.php create mode 100644 app/Libraries/OAuth/Providers/Google.php create mode 100644 app/Libraries/OAuth/Providers/ProviderInterface.php diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 60103f7fdb6d..8261556de3fa 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -15,7 +15,7 @@ use App\Http\Controllers\BaseController; use App\Http\Controllers\Controller; use App\Jobs\Account\CreateAccount; use App\Libraries\MultiDB; -use App\Libraries\OAuth; +use App\Libraries\OAuth\OAuth; use App\Models\User; use App\Transformers\UserTransformer; use App\Utils\Traits\UserSessionAttributes; @@ -59,8 +59,9 @@ class LoginController extends BaseController */ public function __construct() { + parent::__construct(); - // $this->middleware('guest:user')->except('logout'); + } /** @@ -110,19 +111,17 @@ class LoginController extends BaseController /** * Received the returning object from the provider - * which we will use to resolve the user + * which we will use to resolve the user, we return the response in JSON format * - * @return redirect + * @return json */ - public function handleProviderCallback(string $provider) + public function handleProviderCallbackApiUser(string $provider) { $socialite_user = Socialite::driver($provider)->stateless()->user(); if($user = OAuth::handleAuth($socialite_user, $provider)) { - //Auth::login($user, true); return $this->itemResponse($user); - //return redirect($this->redirectTo); //todo return USERACCOUNT json } else if(MultiDB::checkUserEmailExists($socialite_user->getEmail())) { @@ -149,4 +148,74 @@ class LoginController extends BaseController } + + /** + * We use this function when OAUTHING via the web interface + * + * @return redirect + */ + public function handleProviderCallback(string $provider) + { + $socialite_user = Socialite::driver($provider)->stateless()->user(); + + if($user = OAuth::handleAuth($socialite_user, $provider)) + { + Auth::login($user, true); + + return redirect($this->redirectTo) + } + else if(MultiDB::checkUserEmailExists($socialite_user->getEmail())) + { + Session::flash('error', 'User exists in system, but not with this authentication method'); //todo add translations + + return view('auth.login'); + } + /** 3. Automagically creating a new account here. */ + else { + //todo + $name = OAuth::splitName($socialite_user->getName()); + + $new_account = [ + 'first_name' => $name[0], + 'last_name' => $name[1], + 'password' => '', + 'email' => $socialite_user->getEmail(), + 'oauth_user_id' => $socialite_user->getId(), + 'oauth_provider_id' => $provider + ]; + + $account = CreateAccount::dispatchNow($new_account); + + Auth::login($user, true); + + return redirect($this->redirectTo); + } + + + } + + /** + * A client side authentication has taken place. + * We now digest the token and confirm authentication with + * the authentication server, the correct user object + * is returned to us here and we send back the correct + * user object payload - or error. + * + * return User $user + */ + public function oauthApiLogin() + { + + $user = false; + + $oauth = new OAuth(); + + $user = $oauth->getProvider(request()->input('provider'))->getTokenResponse(request()->input('token')); + + if ($user) + return $this->itemResponse($user); + else + return $this->errorResponse(['message' => 'Invalid credentials'], 401); + + } } diff --git a/app/Libraries/OAuth.php b/app/Libraries/OAuth.php index 2256ad55a08c..13cfac42f627 100644 --- a/app/Libraries/OAuth.php +++ b/app/Libraries/OAuth.php @@ -22,6 +22,10 @@ use Laravel\Socialite\Facades\Socialite; class OAuth { + protected $provider_instance; + + protected $provider_id; + /** * Socialite Providers */ @@ -101,4 +105,39 @@ class OAuth return SOCIAL_BITBUCKET; } } + + public function getProvider($provider) + { + switch ($provider) + { + case 'google'; + $this->provider_instance = new Providers\Google(); + $this->provider_id = self::SOCIAL_GOOGLE; + return $this; + + default: + return null; + break; + } + } + + public function getTokenResponse($token) + { + $user = null; + + $payload = $this->provider_instance->getTokenResponse($token); + $oauthUserId = $this->provider_instance->harvestSubField($payload); + + LookupUser::setServerByField('oauth_user_key', $this->providerId . '-' . $oauthUserId); + + if($this->provider_instance) + $user = User::where('oauth_user_id', $oauthUserId)->where('oauth_provider_id', $this->provider_id)->first(); + + + if ($user) + return $user; + else + return false; + + } } \ No newline at end of file diff --git a/app/Libraries/OAuth/OAuth.php b/app/Libraries/OAuth/OAuth.php new file mode 100644 index 000000000000..62cd14e469d5 --- /dev/null +++ b/app/Libraries/OAuth/OAuth.php @@ -0,0 +1,140 @@ +$user->getId(), + 'oauth_provider_id'=>$provider + ]; + + if($user = MultiDB::hasUser($query)) + { + return $user; + } + else + return false; + + } + + /* Splits a socialite user name into first and last names */ + public static function splitName($name) + { + $name = trim($name); + $last_name = (strpos($name, ' ') === false) ? '' : preg_replace('#.*\s([\w-]*)$#', '$1', $name); + $first_name = trim(preg_replace('#' . preg_quote($last_name, '/') . '#', '', $name)); + + return [$first_name, $last_name]; + } + + public static function providerToString(int $social_provider) : string + { + switch ($social_provider) + { + case SOCIAL_GOOGLE: + return 'google'; + case SOCIAL_FACEBOOK: + return 'facebook'; + case SOCIAL_GITHUB: + return 'github'; + case SOCIAL_LINKEDIN: + return 'linkedin'; + case SOCIAL_TWITTER: + return 'twitter'; + case SOCIAL_BITBUCKET: + return 'bitbucket'; + } + } + + public static function providerToInt(string $social_provider) : int + { + switch ($social_provider) + { + case 'google': + return SOCIAL_GOOGLE; + case 'facebook': + return SOCIAL_FACEBOOK; + case 'github': + return SOCIAL_GITHUB; + case 'linkedin': + return SOCIAL_LINKEDIN; + case 'twitter': + return SOCIAL_TWITTER; + case 'bitbucket': + return SOCIAL_BITBUCKET; + } + } + + public function getProvider($provider) + { + switch ($provider) + { + case 'google'; + $this->provider_instance = new Google(); + $this->provider_id = self::SOCIAL_GOOGLE; + return $this; + + default: + return null; + break; + } + } + + public function getTokenResponse($token) + { + $user = false; + + $payload = $this->provider_instance->getTokenResponse($token); + + $oauth_user_id = $this->provider_instance->harvestSubField($payload); + + $data = [ + 'oauth_user_id' => $oauth_user_id, + 'oauth_provider_id' => $this->provider_id + ]; + + if($this->provider_instance) + $user = MultiDB::hasUser($data); + + return $user; + + } +} \ No newline at end of file diff --git a/app/Libraries/OAuth/Providers/Google.php b/app/Libraries/OAuth/Providers/Google.php new file mode 100644 index 000000000000..2debd7a8b178 --- /dev/null +++ b/app/Libraries/OAuth/Providers/Google.php @@ -0,0 +1,22 @@ +verifyIdToken($token); + } + + public function harvestEmail($payload) + { + return $payload['email']; + } + + public function harvestSubField($payload) + { + return $payload['sub']; // user ID + } +} diff --git a/app/Libraries/OAuth/Providers/ProviderInterface.php b/app/Libraries/OAuth/Providers/ProviderInterface.php new file mode 100644 index 000000000000..689130f38c67 --- /dev/null +++ b/app/Libraries/OAuth/Providers/ProviderInterface.php @@ -0,0 +1,9 @@ +integer('theme_id')->nullable(); $table->smallInteger('failed_logins')->nullable(); $table->string('referral_code')->nullable(); - $table->string('oauth_user_id',100)->nullable()->unique(); - $table->unsignedInteger('oauth_provider_id')->nullable()->unique(); + $table->string('oauth_user_id',100)->nullable(); + $table->string('oauth_provider_id')->nullable(); $table->string('google_2fa_secret')->nullable(); $table->string('accepted_terms_version')->nullable(); $table->string('avatar', 100)->default(''); @@ -233,6 +233,9 @@ class CreateUsersTable extends Migration $table->timestamps(6); $table->softDeletes(); + $table->unique(['oauth_user_id', 'oauth_provider_id']); + + // $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); }); diff --git a/routes/api.php b/routes/api.php index e111ef6ff9e9..91be28bb0510 100644 --- a/routes/api.php +++ b/routes/api.php @@ -22,7 +22,8 @@ Route::group(['middleware' => ['api_secret_check']], function () { Route::post('api/v1/signup', 'AccountController@store')->name('signup.submit'); Route::post('api/v1/login', 'Auth\LoginController@apiLogin')->name('login.submit'); - + Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin'); + });