From 91fbb424315e1e6b9e053d62ad2f8592ae52bd0e Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Sun, 15 Dec 2019 11:19:10 +0800 Subject: [PATCH] Add OAuth client --- app/Http/Controllers/AuthController.php | 41 ++++ app/Providers/AppServiceProvider.php | 3 + app/Providers/ViewServiceProvider.php | 4 + composer.json | 48 ++-- composer.lock | 206 +++++++++++++++++- resources/lang/en/general.yml | 1 + resources/misc/changelogs/en/5.0.0.md | 1 + resources/misc/changelogs/zh_CN/5.0.0.md | 1 + resources/views/auth/login.twig | 5 +- resources/views/auth/oauth.twig | 9 + resources/views/auth/register.twig | 1 + routes/web.php | 3 + scripts/build.ps1 | 3 + .../ControllersTest/AuthControllerTest.php | 78 +++++++ 14 files changed, 372 insertions(+), 32 deletions(-) create mode 100644 resources/views/auth/oauth.twig diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index a3319809..c24fdc27 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -11,6 +11,7 @@ use App\Rules\Captcha; use Auth; use Cache; use Illuminate\Http\Request; +use Laravel\Socialite\Facades\Socialite; use Mail; use Session; use URL; @@ -301,4 +302,44 @@ class AuthController extends Controller { return json(['token' => Auth::guard('jwt')->refresh()]); } + + public function oauthLogin($driver) + { + return Socialite::driver($driver)->redirect(); + } + + public function oauthCallback($driver) + { + $remoteUser = Socialite::driver($driver)->user(); + + $email = $remoteUser->email; + if (empty($email)) { + abort(500, 'Unsupported OAuth Server which does not provide email.'); + } + + $user = User::where('email', $email)->first(); + if ($user) { + event(new Events\UserLoggedIn($user)); + + Auth::login($user); + } else { + $user = new User(); + $user->email = $email; + $user->nickname = $remoteUser->nickname ?? $remoteUser->name ?? $email; + $user->score = option('user_initial_score'); + $user->avatar = 0; + $user->password = ''; + $user->ip = get_client_ip(); + $user->permission = User::NORMAL; + $user->register_at = get_datetime_string(); + $user->last_sign_at = get_datetime_string(time() - 86400); + + $user->save(); + event(new Events\UserRegistered($user)); + + Auth::login($user); + } + + return redirect('/user'); + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index fa84f927..362e0487 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -23,6 +23,9 @@ class AppServiceProvider extends ServiceProvider $this->app->singleton('parsedown', \Parsedown::class); $this->app->singleton(\App\Services\Webpack::class); $this->app->singleton(\App\Services\Filter::class); + $this->app->singleton('oauth.providers', function () { + return new \Illuminate\Support\Collection(); + }); } /** diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php index fe8bf7c8..782e344b 100644 --- a/app/Providers/ViewServiceProvider.php +++ b/app/Providers/ViewServiceProvider.php @@ -83,5 +83,9 @@ class ViewServiceProvider extends ServiceProvider ], ]); }); + + View::composer('auth.oauth', function ($view) { + $view->with('providers', resolve('oauth.providers')); + }); } } diff --git a/composer.json b/composer.json index 08f425fb..60e1cbcb 100644 --- a/composer.json +++ b/composer.json @@ -4,45 +4,46 @@ "license": "MIT", "require": { "php": ">=7.2.0", - "ext-zip": "*", + "ext-ctype": "*", + "ext-gd": "*", + "ext-json": "*", + "ext-mbstring": "*", "ext-openssl": "*", "ext-pdo": "*", - "ext-mbstring": "*", "ext-tokenizer": "*", - "ext-gd": "*", "ext-xml": "*", - "ext-ctype": "*", - "ext-json": "*", - "predis/predis": "~1.0", - "doctrine/inflector": "1.1.0", - "laravel/framework": "6.*", - "nesbot/carbon": "^2.0", + "ext-zip": "*", + "composer/ca-bundle": "^1.2", "composer/semver": "^1.4", + "doctrine/dbal": "^2.9", + "doctrine/inflector": "1.1.0", + "facade/ignition": "^1.4", "gregwar/captcha": "1.*", "guzzlehttp/guzzle": "^6.3", - "doctrine/dbal": "^2.9", - "tymon/jwt-auth": "dev-develop", + "laravel/framework": "6.*", "laravel/passport": "^7.3", - "composer/ca-bundle": "^1.2", - "facade/ignition": "^1.4", + "nesbot/carbon": "^2.0", + "predis/predis": "~1.0", + "rcrowe/twigbridge": "^0.11.1", + "socialiteproviders/manager": "^3.4", "spatie/laravel-translation-loader": "^2.4", "symfony/process": "^4.4", "symfony/yaml": "^4.3", "twig/twig": "^2.11", - "rcrowe/twigbridge": "^0.11.1" + "tymon/jwt-auth": "dev-develop" }, "require-dev": { + "barryvdh/laravel-debugbar": "^3.2", + "barryvdh/laravel-ide-helper": "^2.6", + "beyondcode/laravel-dump-server": "^1.2", "fzaninotto/faker": "~1.8", + "laravel/browser-kit-testing": "~5.0", + "laravel/tinker": "^1.0", "mockery/mockery": "^1.2.2", "phpdocumentor/reflection-docblock": "3.2.2", "phpunit/phpunit": "~8.0", - "laravel/browser-kit-testing": "~5.0", - "laravel/tinker": "^1.0", - "barryvdh/laravel-debugbar": "^3.2", - "beyondcode/laravel-dump-server": "^1.2", - "symfony/dom-crawler": "^4.3", "symfony/css-selector": "^4.3", - "barryvdh/laravel-ide-helper": "^2.6" + "symfony/dom-crawler": "^4.3" }, "autoload": { "classmap": [ @@ -72,6 +73,13 @@ "preferred-install": "dist", "sort-packages": true }, + "extra": { + "laravel": { + "dont-discover": [ + "laravel/socialite" + ] + } + }, "repositories": { "packagist": { "type": "composer", diff --git a/composer.lock b/composer.lock index 0a7d296c..f9c31e52 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5cc00172e30620b65fb629cecea98e5b", + "content-hash": "1d98f570b7b67b1ecd3b72dfcec58e7b", "packages": [ { "name": "composer/ca-bundle", @@ -1459,6 +1459,70 @@ ], "time": "2019-10-08T16:45:24+00:00" }, + { + "name": "laravel/socialite", + "version": "v4.3.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/socialite.git", + "reference": "2d670d5b100ef2dc72dc578126b2b97985791f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/socialite/zipball/2d670d5b100ef2dc72dc578126b2b97985791f52", + "reference": "2d670d5b100ef2dc72dc578126b2b97985791f52", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "~6.0", + "illuminate/http": "~5.7.0|~5.8.0|^6.0|^7.0", + "illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0", + "league/oauth1-client": "~1.0", + "php": "^7.1.3" + }, + "require-dev": { + "illuminate/contracts": "~5.7.0|~5.8.0|^6.0|^7.0", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.0|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Socialite\\SocialiteServiceProvider" + ], + "aliases": { + "Socialite": "Laravel\\Socialite\\Facades\\Socialite" + } + } + }, + "autoload": { + "psr-4": { + "Laravel\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.", + "homepage": "https://laravel.com", + "keywords": [ + "laravel", + "oauth" + ], + "time": "2019-11-26T17:39:15+00:00" + }, { "name": "lcobucci/jwt", "version": "3.3.1", @@ -1648,6 +1712,69 @@ ], "time": "2019-12-08T21:46:50+00:00" }, + { + "name": "league/oauth1-client", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth1-client.git", + "reference": "fca5f160650cb74d23fc11aa570dd61f86dcf647" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/fca5f160650cb74d23fc11aa570dd61f86dcf647", + "reference": "fca5f160650cb74d23fc11aa570dd61f86dcf647", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0" + }, + "require-dev": { + "mockery/mockery": "^0.9", + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth1\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Corlett", + "email": "bencorlett@me.com", + "homepage": "http://www.webcomm.com.au", + "role": "Developer" + } + ], + "description": "OAuth 1.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "bitbucket", + "identity", + "idp", + "oauth", + "oauth1", + "single sign on", + "trello", + "tumblr", + "twitter" + ], + "time": "2016-08-17T00:36:58+00:00" + }, { "name": "league/oauth2-server", "version": "7.4.0", @@ -2047,16 +2174,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.7.0", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "318ad673ba36e53e10ca510aec1c54b4f2a5f2ae" + "reference": "100a25207566930efd926cf205542946aa692e01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/318ad673ba36e53e10ca510aec1c54b4f2a5f2ae", - "reference": "318ad673ba36e53e10ca510aec1c54b4f2a5f2ae", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/100a25207566930efd926cf205542946aa692e01", + "reference": "100a25207566930efd926cf205542946aa692e01", "shasum": "" }, "require": { @@ -2098,7 +2225,7 @@ "php", "type" ], - "time": "2019-12-13T00:10:22+00:00" + "time": "2019-12-14T13:46:39+00:00" }, { "name": "phpseclib/phpseclib", @@ -2755,6 +2882,63 @@ ], "time": "2019-12-13T21:54:06+00:00" }, + { + "name": "socialiteproviders/manager", + "version": "v3.4.3", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Manager.git", + "reference": "09903d33429f9f6c0da32c545c036a3e18964bbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/09903d33429f9f6c0da32c545c036a3e18964bbf", + "reference": "09903d33429f9f6c0da32c545c036a3e18964bbf", + "shasum": "" + }, + "require": { + "illuminate/support": "~5.4|~5.7.0|~5.8.0|^6.0", + "laravel/socialite": "~3.0|~4.0", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "SocialiteProviders\\Manager\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "SocialiteProviders\\Manager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andy Wendt", + "email": "andy@awendt.com" + }, + { + "name": "Anton Komarev", + "email": "a.komarev@cybercog.su" + }, + { + "name": "Miguel Piedrafita", + "email": "soy@miguelpiedrafita.com" + } + ], + "description": "Easily add new or override built-in providers in Laravel Socialite.", + "time": "2019-09-25T06:06:35+00:00" + }, { "name": "spatie/laravel-translation-loader", "version": "2.4.0", @@ -7557,15 +7741,15 @@ "prefer-lowest": false, "platform": { "php": ">=7.2.0", - "ext-zip": "*", + "ext-ctype": "*", + "ext-gd": "*", + "ext-json": "*", + "ext-mbstring": "*", "ext-openssl": "*", "ext-pdo": "*", - "ext-mbstring": "*", "ext-tokenizer": "*", - "ext-gd": "*", "ext-xml": "*", - "ext-ctype": "*", - "ext-json": "*" + "ext-zip": "*" }, "platform-dev": [] } diff --git a/resources/lang/en/general.yml b/resources/lang/en/general.yml index 3268b38b..96fee1af 100644 --- a/resources/lang/en/general.yml +++ b/resources/lang/en/general.yml @@ -42,6 +42,7 @@ rotation: Rotation pause: Pause reset: Reset +more: More submit: Submit cancel: Cancel yes: Yes diff --git a/resources/misc/changelogs/en/5.0.0.md b/resources/misc/changelogs/en/5.0.0.md index 1067f4e5..eca06487 100644 --- a/resources/misc/changelogs/en/5.0.0.md +++ b/resources/misc/changelogs/en/5.0.0.md @@ -15,6 +15,7 @@ - Added badge "STAFF" for administrators. - Added badges at texture detail page. - Added FAQ link at error page. +- Added login with 3rd-party services. ## Tweaked diff --git a/resources/misc/changelogs/zh_CN/5.0.0.md b/resources/misc/changelogs/zh_CN/5.0.0.md index 8b2d69a3..f7874557 100644 --- a/resources/misc/changelogs/zh_CN/5.0.0.md +++ b/resources/misc/changelogs/zh_CN/5.0.0.md @@ -15,6 +15,7 @@ - 增加管理员专有的「STAFF」badge - 在材质详情页中显示上传者的 badge - 在错误页面增加指向 FAQ 页面的链接 +- 第三方登录 ## 调整 diff --git a/resources/views/auth/login.twig b/resources/views/auth/login.twig index 37cabce5..b15a9502 100644 --- a/resources/views/auth/login.twig +++ b/resources/views/auth/login.twig @@ -11,7 +11,10 @@ {% endif %}

- {{ trans('auth.register-link') }} + {{ include('auth.oauth') }} +
+ {{ trans('auth.register-link') }} +
{% endblock %} {% block before_foot %} diff --git a/resources/views/auth/oauth.twig b/resources/views/auth/oauth.twig new file mode 100644 index 00000000..9efe51ae --- /dev/null +++ b/resources/views/auth/oauth.twig @@ -0,0 +1,9 @@ +
+
- {{ trans('general.more') }} -
+ {% for name, provider in providers %} + + + {{ provider.displayName }} + + {% endfor %} +
diff --git a/resources/views/auth/register.twig b/resources/views/auth/register.twig index 40a08315..0e414cef 100644 --- a/resources/views/auth/register.twig +++ b/resources/views/auth/register.twig @@ -7,6 +7,7 @@ {{ trans('auth.register.message', {sitename: site_name}) }}

+ {{ include('auth.oauth') }} {% endblock %} {% block before_foot %} diff --git a/routes/web.php b/routes/web.php index 7ac356b0..838861a4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -27,6 +27,9 @@ Route::group(['prefix' => 'auth'], function () { Route::get('/register', 'AuthController@register'); Route::get('/forgot', 'AuthController@forgot'); Route::get('/reset/{uid}', 'AuthController@reset')->name('auth.reset')->middleware('signed'); + + Route::get('/login/{driver}', 'AuthController@oauthLogin'); + Route::get('/login/{driver}/callback', 'AuthController@oauthCallback'); }); Route::any('/logout', 'AuthController@logout'); diff --git a/scripts/build.ps1 b/scripts/build.ps1 index ffde505a..5e280bbe 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -13,6 +13,9 @@ if (Test-Path ./public/app) { # Run webpack yarn build +New-Item -ItemType Directory ./public/app/brand-icons +Copy-Item -Path ./node_modules/@fortawesome/fontawesome-free/svgs/brands/*.svg -Destination ./public/app/brand-icons + if ($Simple) { exit } diff --git a/tests/HttpTest/ControllersTest/AuthControllerTest.php b/tests/HttpTest/ControllersTest/AuthControllerTest.php index add6e0a1..1bab50ec 100644 --- a/tests/HttpTest/ControllersTest/AuthControllerTest.php +++ b/tests/HttpTest/ControllersTest/AuthControllerTest.php @@ -13,6 +13,8 @@ use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\URL; use Illuminate\Support\Str; +use Laravel\Socialite\AbstractUser; +use Laravel\Socialite\Facades\Socialite; class AuthControllerTest extends TestCase { @@ -606,4 +608,80 @@ class AuthControllerTest extends TestCase ])->decodeResponseJson('token'); $this->assertTrue(is_string($token)); } + + public function testOAuthLogin() + { + Socialite::shouldReceive('driver') + ->with('github') + ->once() + ->andReturn(new class() { + public function redirect() + { + return redirect('/'); + } + }); + + $this->get('/auth/login/github')->assertRedirect(); + } + + public function testOAuthCallback() + { + Event::fake(); + + Socialite::shouldReceive('driver') + ->with('github') + ->times(3) + ->andReturn( + new class() { + public function user() + { + return new class() extends AbstractUser { + }; + } + }, + new class() { + public function user() + { + return new class() extends AbstractUser { + public $email = 'a@b.c'; + + public $nickname = 'abc'; + }; + } + }, + new class() { + public function user() + { + return new class() extends AbstractUser { + public $email = 'a@b.c'; + + public $nickname = 'abc'; + }; + } + } + ); + + $this->get('/auth/login/github/callback') + ->assertStatus(500) + ->assertSee('Unsupported'); + + $this->get('/auth/login/github/callback')->assertRedirect('/user'); + $this->assertDatabaseHas('users', [ + 'email' => 'a@b.c', + 'nickname' => 'abc', + 'score' => option('user_initial_score'), + 'avatar' => 0, + 'ip' => '127.0.0.1', + 'permission' => User::NORMAL, + ]); + Event::assertDispatched(Events\UserRegistered::class); + $this->assertAuthenticated(); + + auth()->logout(); + $this->assertGuest(); + + $this->get('/auth/login/github/callback')->assertRedirect('/user'); + Event::assertDispatched(Events\UserLoggedIn::class); + $this->assertAuthenticated(); + } }