diff --git a/.env.example b/.env.example index aea47f943..8e191e89a 100644 --- a/.env.example +++ b/.env.example @@ -62,6 +62,10 @@ DISCORD_TOKEN=null DISCORD_SERVER_ID=null DISCORD_INVITE_URL="https://discordapp.com/invite/EUhwdrq" +DISCORD_CLIENT_ID=null +DISCORD_CLIENT_SECRET=null +DISCORD_REDIRECT_URI="/login/discord/callback" + SENTRY_LARAVEL_DSN=null SENTRY_TRACES_SAMPLE_RATE=null @@ -76,4 +80,8 @@ LOGIN_AUTH_BLOG_POST=null SCRIBE_AUTH_KEY=null -MAIL_TO_ADDRESS="${SUPER_ADMIN_EMAIL}" \ No newline at end of file +MAIL_TO_ADDRESS="${SUPER_ADMIN_EMAIL}" + +TWITTER_CLIENT_ID=null +TWITTER_CLIENT_SECRET=null +TWITTER_REDIRECT_URI="/login/twitter/callback" \ No newline at end of file diff --git a/app/Achievements/User/AssociatedTwitter.php b/app/Achievements/User/AssociatedTwitter.php new file mode 100644 index 000000000..a0f2c2cc2 --- /dev/null +++ b/app/Achievements/User/AssociatedTwitter.php @@ -0,0 +1,29 @@ +redirect(); + } + + /** + * Obtain the user information from Twitter. + * + * @return \Illuminate\Http\Response + */ + public function handleProviderCallback() + { + try { + + $twitterUser = Socialite::driver('twitter')->user(); + + if ($twitterUser->user['suspended']) { + return redirect()->route('login')->withError('Twitter user is suspended.'); + } + + $userProfile = [ + 'id' => $twitterUser->id, + 'username' => $twitterUser->nickname, + 'name' => $twitterUser->name, + 'email' => $twitterUser->email, + 'avatar' => $twitterUser->avatar, + ]; + + // Check if user exists with email + $twitterAccount = TwitterAccount::where('id', $twitterUser->id)->first(); + if (!$twitterAccount && auth()->guest()) { + return redirect()->route('login')->withError('Twitter account association not found with any P3D account.'); + } + + $user = $twitterAccount ? $twitterAccount->user : null; + if ($user) { + Auth::login($user); + return redirect()->route('dashboard'); + } + + if (auth()->guest() && !$user) { + return redirect()->route('login')->withError('You are not logged in and user was not found.'); + } + + // Create new twitter account + $user = auth()->user(); + $userProfile['user_id'] = $user->id; + $userProfile['verified_at'] = now(); + TwitterAccount::create($userProfile); + $user->unlock(new AssociatedTwitter()); + return redirect()->route('profile.show'); + + } catch (InvalidStateException $e) { + return redirect()->route('home')->withError('Something went wrong with Twitter login. Please try again.'); + } catch (ClientException $e) { + return redirect()->route('home')->withError('Something went wrong with Twitter login. Please try again.'); + } + + + return redirect()->route('dashboard'); + } +} diff --git a/app/Http/Livewire/Profile/TwitterAccount.php b/app/Http/Livewire/Profile/TwitterAccount.php new file mode 100644 index 000000000..caad1b433 --- /dev/null +++ b/app/Http/Livewire/Profile/TwitterAccount.php @@ -0,0 +1,53 @@ +username = ($user->twitter ? $user->twitter->username : null); + $this->name = ($user->twitter ? $user->twitter->name : null); + $this->avatar = ($user->twitter ? $user->twitter->avatar : null); + $this->updated_at = ($user->twitter ? $user->twitter->updated_at->diffForHumans() : null); + $this->verified_at = ($user->twitter ? $user->twitter->verified_at->diffForHumans() : null); + } + + /** + * Update the user's GameJolt Account credentials. + * + * @return void + */ + public function remove() + { + $this->resetErrorBag(); + $this->resetValidation(); + + $user = Auth::user(); + + if ($user->twitter) { + $user->twitter->delete(); + $this->username = null; + $this->name = null; + $this->avatar = null; + $this->updated_at = null; + $this->verified_at = null; + } + + $this->emit('refresh'); + + return; + } + + public function render() + { + return view('livewire.profile.twitter-account'); + } +} diff --git a/app/Models/TwitterAccount.php b/app/Models/TwitterAccount.php new file mode 100644 index 000000000..cf3b128e5 --- /dev/null +++ b/app/Models/TwitterAccount.php @@ -0,0 +1,85 @@ + 'datetime', + ]; + + /** + * The attributes that should be hidden + * + * @var array + */ + protected $hidden = [ + 'aid', + ]; + + public function touchVerify() + { + $this->verified_at = $this->freshTimestamp(); + return $this->save(); + } + + /** + * Get the user associated with the gamejolt account. + */ + public function user() + { + return $this->hasOne(User::class, 'id', 'user_id'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index b23c316f9..509f0af2f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -106,4 +106,12 @@ public function forum() { return $this->hasOne(ForumAccount::class); } + + /** + * Get the twitter account associated with the user. + */ + public function twitter() + { + return $this->hasOne(TwitterAccount::class); + } } diff --git a/composer.json b/composer.json index c506fd9a0..ea362fb18 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,7 @@ "power-components/livewire-powergrid": "^1.5", "restcord/restcord": "dev-develop", "sentry/sentry-laravel": "^2.5", + "socialiteproviders/twitter": "^4.1", "spatie/cpu-load-health-check": "^1.0", "spatie/laravel-activitylog": "^3.16", "spatie/laravel-cookie-consent": "^2.12", diff --git a/composer.lock b/composer.lock index f0eec0e1d..4b569e596 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": "ecd405b91581ce69304bd7db1344209f", + "content-hash": "e0823281fe39c2150e39e262d86e813d", "packages": [ { "name": "anlutro/l4-settings", @@ -7996,6 +7996,114 @@ ], "time": "2021-09-20T13:50:15+00:00" }, + { + "name": "socialiteproviders/manager", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Manager.git", + "reference": "0f5e82af0404df0080bdc5c105cef936c1711524" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/0f5e82af0404df0080bdc5c105cef936c1711524", + "reference": "0f5e82af0404df0080bdc5c105cef936c1711524", + "shasum": "" + }, + "require": { + "illuminate/support": "^6.0|^7.0|^8.0", + "laravel/socialite": "~4.0|~5.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^9.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" + }, + { + "name": "atymic", + "email": "atymicq@gmail.com", + "homepage": "https://atymic.dev" + } + ], + "description": "Easily add new or override built-in providers in Laravel Socialite.", + "homepage": "https://socialiteproviders.com/", + "support": { + "issues": "https://github.com/SocialiteProviders/Manager/issues", + "source": "https://github.com/SocialiteProviders/Manager/tree/4.0.1" + }, + "time": "2020-12-01T23:09:06+00:00" + }, + { + "name": "socialiteproviders/twitter", + "version": "4.1.1", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Twitter.git", + "reference": "e5edf2b6e3f37e64be6488111629ed5e41e645ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Twitter/zipball/e5edf2b6e3f37e64be6488111629ed5e41e645ad", + "reference": "e5edf2b6e3f37e64be6488111629ed5e41e645ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.2 || ^8.0", + "socialiteproviders/manager": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "SocialiteProviders\\Twitter\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Faust", + "email": "hello@brianfaust.de" + } + ], + "description": "Twitter OAuth1 Provider for Laravel Socialite", + "support": { + "source": "https://github.com/SocialiteProviders/Twitter/tree/4.1.1" + }, + "time": "2021-01-29T05:41:11+00:00" + }, { "name": "spatie/cpu-load-health-check", "version": "1.0.2", diff --git a/config/services.php b/config/services.php index 34f13d044..14ad60b25 100644 --- a/config/services.php +++ b/config/services.php @@ -36,4 +36,10 @@ 'redirect' => env('DISCORD_REDIRECT_URI', '/login/discord/callback'), ], + 'twitter' => [ + 'client_id' => env('TWITTER_CLIENT_ID'), + 'client_secret' => env('TWITTER_CLIENT_SECRET'), + 'redirect' => env('TWITTER_REDIRECT_URI', '/login/twitter/callback'), + ], + ]; diff --git a/database/migrations/2022_01_03_195850_create_twitter_accounts_table.php b/database/migrations/2022_01_03_195850_create_twitter_accounts_table.php new file mode 100644 index 000000000..a4f7ffce8 --- /dev/null +++ b/database/migrations/2022_01_03_195850_create_twitter_accounts_table.php @@ -0,0 +1,40 @@ +increments('aid'); + $table->uuid('uuid')->unique(); + $table->bigInteger('id')->comment('Twitter ID'); + $table->text('username')->comment('Twitter Username'); + $table->text('name')->comment('Twitter Name'); + $table->text('email')->comment('Twitter Email'); + $table->text('avatar')->comment('Twitter Avatar URL'); + $table->timestamp('verified_at')->nullable(); + $table->unsignedBigInteger('user_id'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('twitter_accounts'); + } +} diff --git a/resources/views/components/achievement.blade.php b/resources/views/components/achievement.blade.php index 7c3232a58..d97390ef9 100644 --- a/resources/views/components/achievement.blade.php +++ b/resources/views/components/achievement.blade.php @@ -8,6 +8,12 @@ + @elseif ($achievement->name == 'AssociatedTwitter') + + + + + @else diff --git a/resources/views/livewire/profile/twitter-account.blade.php b/resources/views/livewire/profile/twitter-account.blade.php new file mode 100644 index 000000000..07f9cc3ea --- /dev/null +++ b/resources/views/livewire/profile/twitter-account.blade.php @@ -0,0 +1,45 @@ + + + + + + + + Twitter + + + + {{ __('Link your account with your Twitter account.') }} + {{ __('Last Updated:') }} {{ $updated_at ?? 'Never.' }} · {{ __('Last Verified:') }} {{ $verified_at ?? 'Never.' }} + + + + @if (!isset($username)) + + + + + + + Login with Twitter + + @else +
+
+ {{ $username }} +
+
+ {{ $name }} +
{{ '@'.$username }}
+
+
+ +
+ + {{ __('Remove Association') }} + +
+ @endif +
+ + diff --git a/resources/views/member/show.blade.php b/resources/views/member/show.blade.php index ead89ad00..66ac32381 100644 --- a/resources/views/member/show.blade.php +++ b/resources/views/member/show.blade.php @@ -110,6 +110,24 @@ @endif + @if($user->twitter) +
+ + + + + Twitter +
+
+ {{ $user->twitter->username }} +
+
+ {{ $user->twitter->name }} +
{{ '@'.$user->twitter->username }}
+
+
+
+ @endif
@if($user->gamejolt) diff --git a/resources/views/profile/edit.blade.php b/resources/views/profile/edit.blade.php index 03459dc73..4fe6937ac 100644 --- a/resources/views/profile/edit.blade.php +++ b/resources/views/profile/edit.blade.php @@ -28,6 +28,11 @@ @endif + @if(config("services.twitter.client_id") && config("services.twitter.client_secret")) + @livewire('profile.twitter-account') + + @endif + @livewire('profile.preference') diff --git a/routes/web.php b/routes/web.php index 5f2d491e8..abd1ecab5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -12,6 +12,7 @@ use App\Http\Controllers\PermissionController; use App\Http\Controllers\Skin\ImportController; use App\Http\Controllers\Auth\DiscordController; +use App\Http\Controllers\Auth\TwitterController; use App\Http\Controllers\Skin\SkinHomeController; use App\Http\Controllers\Skin\PlayerSkinController; use App\Http\Controllers\Skin\UploadedSkinController; @@ -51,6 +52,8 @@ Route::group(['prefix' => 'login'], function () { Route::get('/discord', [DiscordController::class, 'redirectToProvider'])->name('discord.login'); Route::get('/discord/callback', [DiscordController::class, 'handleProviderCallback']); + Route::get('/twitter', [TwitterController::class, 'redirectToProvider'])->name('twitter.login'); + Route::get('/twitter/callback', [TwitterController::class, 'handleProviderCallback']); }); Route::group(['middleware' => ['auth:sanctum', 'verified']], function () {