Skip to content

Commit

Permalink
refactor: move logic from controller to service
Browse files Browse the repository at this point in the history
  • Loading branch information
sinkcup committed Aug 30, 2020
1 parent 2bd0af6 commit bfe391c
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 77 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"extra": {
"laravel": {
"providers": [
"sinkcup\\LaravelUiSocialite\\PackageServiceProvider"
"sinkcup\\LaravelUiSocialite\\UiSocialiteServiceProvider"
]
}
},
Expand Down
77 changes: 9 additions & 68 deletions src/Socialite/Controllers/SocialiteLoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
namespace sinkcup\LaravelUiSocialite\Socialite\Controllers;

use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ViewErrorBag;
use Laravel\Socialite\Facades\Socialite;
use sinkcup\LaravelUiSocialite\SocialAccount;
use sinkcup\LaravelUiSocialite\SocialiteService;

class SocialiteLoginController extends Controller
{
Expand Down Expand Up @@ -50,9 +48,9 @@ public function showLoginForm()
* @param string $providerSlug provider slug, e.g., paypal-sandbox, wechat-web
* @return \Symfony\Component\HttpFoundation\Response
*/
public function redirectToProvider($providerSlug)
public function redirectToProvider(string $providerSlug)
{
$provider = self::convertProviderSlugToServiceName($providerSlug);
$provider = SocialiteService::convertProviderSlugToServiceName($providerSlug);
return Socialite::driver($provider)
// if you have defined "scopes" in config, it will be load at here
// docs: https://laravel.com/docs/socialite#access-scopes
Expand All @@ -66,88 +64,31 @@ public function redirectToProvider($providerSlug)
* @param string $providerSlug
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handleProviderCallback($providerSlug)
public function handleProviderCallback(string $providerSlug, SocialiteService $socialiteService)
{
$provider = self::convertProviderSlugToServiceName($providerSlug);
try {
$remote_user = Socialite::driver($provider)
->scopes(config("services.{$provider}.scopes"))
->user();
$user = $socialiteService->createUser($providerSlug);
} catch (\Exception $e) {
Log::warning('Socialite Login failed', [
'provider' => $provider,
'exception' => [
'name' => get_class($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
],
]);
return $this->sendFailedSocialLoginResponse($provider);
return $this->sendFailedSocialLoginResponse($providerSlug);
}

// if logged in, should link multiple auth providers to an account
$user_id = auth()->user()->id ?? null;
// if you have defined "union_id_with" in config, it will be load at here
// some providers use one union id, e.g., WeChat Web, WeChat Service Account
if (!empty($union_id_with_providers = config("services.{$provider}.union_id_with"))) {
$user_id = SocialAccount::whereIn('provider', array_diff($union_id_with_providers, [$provider]))
->where('provider_user_id', $remote_user->getId())
->whereNotNull('user_id')
->value('user_id');
}
$social_account = SocialAccount::firstOrNew([
'provider' => $provider,
'provider_user_id' => $remote_user->getId(),
], ['user_id' => $user_id]);
$name = $remote_user->getName() ?: $remote_user->getNickname();
if (!empty($social_account->user)) {
$user = $social_account->user;
} else {
$user_model = config('auth.providers.users.model');
// faker email for unique in db
$email = $remote_user->getEmail() ?: $provider . '.' . $remote_user->getId() . '@example.com';
$user = $user_model::where('email', $email)->first();
if (empty($user)) {
$user = $user_model::create([
'email' => $email,
'name' => $name ?: $provider . ' user',
]);
}
$social_account->user()->associate($user);
}
$social_account->sync($remote_user);
if (!empty($remote_user->getAvatar())) {
$user->avatar = $remote_user->getAvatar();
}
if (!empty($name)) {
$user->name = $name;
}
$user->save();
auth()->login($user);
return redirect()->intended($this->redirectPath());
}

/**
* Get the failed social login response instance.
*
* @param $provider
* @param string $providerSlug
* @return \Symfony\Component\HttpFoundation\Response
*
*/
protected function sendFailedSocialLoginResponse($provider)
protected function sendFailedSocialLoginResponse(string $providerSlug)
{
$provider = SocialiteService::convertProviderSlugToServiceName($providerSlug);
return redirect()->route('login')->withErrors([
$provider => [trans('auth.failed')],
]);
}

/**
* Convert provider slug to service name which is using in config/services.php
* @param string $providerSlug e.g., paypal-sandbox
* @return string e.g., paypay_sandbox
*/
public static function convertProviderSlugToServiceName($providerSlug)
{
return str_replace('-', '_', $providerSlug);
}
}
98 changes: 98 additions & 0 deletions src/SocialiteService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

namespace sinkcup\LaravelUiSocialite;

use Illuminate\Support\Facades\Log;
use Laravel\Socialite\Facades\Socialite;

class SocialiteService
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;

/**
* Create a new instance.
*
* @parvi vam \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}

public function createUser(string $providerSlug, bool $stateless = false)
{
$provider = SocialiteService::convertProviderSlugToServiceName($providerSlug);
try {
$driver = Socialite::driver($provider);
$driver = $stateless ? $driver->stateless() : $driver;
$remote_user = $driver->scopes(config("services.{$provider}.scopes"))
->user();
} catch (\Exception $e) {
Log::warning('Socialite Login failed', [
'provider' => $provider,
'exception' => [
'name' => get_class($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
],
]);
throw $e;
}

// if logged in, should link multiple auth providers to an account
$user_id = auth()->user()->id ?? null;
// if you have defined "union_id_with" in config, it will be load at here
// some providers use one union id, e.g., WeChat Web, WeChat Service Account
if (!empty($union_id_with_providers = config("services.{$provider}.union_id_with"))) {
$user_id = SocialAccount::whereIn('provider', array_diff($union_id_with_providers, [$provider]))
->where('provider_user_id', $remote_user->getId())
->whereNotNull('user_id')
->value('user_id');
}
$social_account = SocialAccount::firstOrNew([
'provider' => $provider,
'provider_user_id' => $remote_user->getId(),
], ['user_id' => $user_id]);
$name = $remote_user->getName() ?: $remote_user->getNickname();
if (!empty($social_account->user)) {
$user = $social_account->user;
} else {
$user_model = config('auth.providers.users.model');
// faker email for unique in db
$email = $remote_user->getEmail() ?: $provider . '.' . $remote_user->getId() . '@example.com';
$user = $user_model::where('email', $email)->first();
if (empty($user)) {
$user = $user_model::create([
'email' => $email,
'name' => $name ?: $provider . ' user',
]);
}
$social_account->user()->associate($user);
}
$social_account->sync($remote_user);
if (!empty($remote_user->getAvatar())) {
$user->avatar = $remote_user->getAvatar();
}
if (!empty($name)) {
$user->name = $name;
}
$user->save();
return $user;
}

/**
* Convert provider slug to service name which is using in config/services.php
* @param string $providerSlug e.g., paypal-sandbox
* @return string e.g., paypay_sandbox
*/
public static function convertProviderSlugToServiceName(string $providerSlug)
{
return str_replace('-', '_', $providerSlug);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Illuminate\Support\ServiceProvider;

class PackageServiceProvider extends ServiceProvider
class UiSocialiteServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
Expand Down Expand Up @@ -33,6 +33,9 @@ public function boot()
public function register()
{
$this->mergeConfigFrom(__DIR__ . '/config/auth.php', 'auth');
$this->app->singleton(SocialiteService::class, function ($app) {
return new SocialiteService($app);
});
}

/**
Expand Down
14 changes: 9 additions & 5 deletions tests/Feature/SocialiteLoginControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Mockery;
use sinkcup\LaravelUiSocialite\SocialAccount;
use sinkcup\LaravelUiSocialite\Socialite\Controllers\SocialiteLoginController;
use sinkcup\LaravelUiSocialite\SocialiteService;
use sinkcup\LaravelUiSocialite\Tests\TestCase;

class SocialiteLoginControllerTest extends TestCase
Expand Down Expand Up @@ -106,7 +107,7 @@ public function testHandleProviderCallbackForNewUser()

public function testConvertProviderSlugToServiceName()
{
$this->assertEquals('wechat_web', SocialiteLoginController::convertProviderSlugToServiceName('wechat-web'));
$this->assertEquals('wechat_web', SocialiteService::convertProviderSlugToServiceName('wechat-web'));
}

public function testHandleProviderCallbackForOldUser()
Expand All @@ -120,11 +121,12 @@ public function testHandleProviderCallbackForOldUser()
'user_id' => $user->id,
'provider' => $provider,
]);
$newName = $socialAccount->name . ' ' . $this->faker->word;
$abstractUser
->shouldReceive('getId')
->andReturn($socialAccount->provider_user_id)
->shouldReceive('getName')
->andReturn($socialAccount->name)
->andReturn($newName)
->shouldReceive('getNickname')
->andReturn($socialAccount->nickname)
->shouldReceive('getEmail')
Expand All @@ -138,15 +140,17 @@ public function testHandleProviderCallbackForOldUser()
$response = $this->get('/login/' . $provider . '/callback');
$response->assertRedirect(route('profile.edit'));
$this->assertEquals(1, SocialAccount::count());
$socialAccountDb = SocialAccount::first();
$socialAccountDb = SocialAccount::first()->toArray();
foreach ($socialAccount->toArray() as $k => $v) {
if ($k == 'updated_at') {
continue;
}
if ($k == 'access_token') {
$this->assertNotEquals($v, $socialAccountDb->{$k});
$this->assertNotEquals($v, $socialAccountDb[$k]);
} elseif ($k == 'name') {
$this->assertEquals($newName, $socialAccountDb[$k]);
} else {
$this->assertEquals($v, $socialAccountDb->{$k});
$this->assertEquals($v, $socialAccountDb[$k]);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Route;
use Laravel\Ui\UiServiceProvider;
use sinkcup\LaravelUiSocialite\PackageServiceProvider;
use sinkcup\LaravelUiSocialite\UiSocialiteServiceProvider;
use Orchestra\Testbench\TestCase as OrchestraTestCase;

class TestCase extends OrchestraTestCase
Expand All @@ -17,7 +17,7 @@ class TestCase extends OrchestraTestCase
protected function getPackageProviders($app)
{
return [
PackageServiceProvider::class,
UiSocialiteServiceProvider::class,
UiServiceProvider::class,
];
}
Expand Down

0 comments on commit bfe391c

Please sign in to comment.