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') }}
+
{% 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();
+ }
}