feat: add Discord facade

This commit is contained in:
Miguel Nogueira 2022-08-28 05:46:32 +01:00
parent d58ea51de1
commit c793596a3a
No known key found for this signature in database
GPG Key ID: 3C6A7E29AF26D370
8 changed files with 182 additions and 48 deletions

32
app/Facades/Discord.php Normal file
View File

@ -0,0 +1,32 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Discord extends Facade
{
protected static function getFacadeAccessor()
{
return 'discordServiceFacade';
}
}

View File

@ -24,8 +24,11 @@ namespace App\Helpers;
use App\Exceptions\AccountNotLinkedException; use App\Exceptions\AccountNotLinkedException;
use App\User; use App\User;
use Carbon\Carbon;
use Illuminate\Http\Client\RequestException; use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
@ -50,7 +53,7 @@ class Discord
public function __construct() { public function __construct() {
if (!is_null($this->workingGuild)) { if (isset($this->workingGuild)) {
$this->setWorkingGuild(config('services.discord.home_guild')); $this->setWorkingGuild(config('services.discord.home_guild'));
} }
} }
@ -85,6 +88,47 @@ class Discord
throw new AccountNotLinkedException('Specified website user has not linked their Discord account yet.'); throw new AccountNotLinkedException('Specified website user has not linked their Discord account yet.');
} }
/**
* Obtains the current user's authentication info. Caches the data for the access_token's TTL, thus
* preventing unnecessary API requests.
*
* @return object Current user's authentication info (has no sensitive fields)
* @throws RequestException
*/
public function getAuthorizationInfo(): object {
if (Cache::has($this->user->discord_user_id)) {
return unserialize(Cache::get($this->user->discord_user_id));
}
else {
$authInfo = (object) Http::withToken($this->user->discord_token)
->get(config('services.discord.base_url') . '/oauth2/@me')
->throw()
->json();
Cache::put($this->user->discord_user_id, serialize($authInfo), Carbon::parse($authInfo->expires));
return $authInfo;
}
}
/**
* Checks if the user's token is close to expiring.
* Tokens should be refreshed the day before they expire.
*
* @return bool Whether the user's token needs to be refreshed
* @throws RequestException
*/
public function needsRefresh(): bool {
return Carbon::parse($this->getAuthorizationInfo()->expires)->diffInDays() == 1;
}
public function exchangeRefreshToken() {
}

View File

@ -21,6 +21,7 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Facades\Discord;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\User; use App\User;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -41,21 +42,36 @@ class DiscordController extends Controller
public function discordCallback() { public function discordCallback() {
$discordUser = Socialite::driver('discord')->user(); $discordUser = Socialite::driver('discord')->user();
$appUser = User::upsert([ $appUser = User::where('email', $discordUser->getEmail())->first();
if ($appUser) {
$appUser->discord_token = $discordUser->token;
$appUser->discord_refresh_token = $discordUser->refreshToken;
$appUser->discord_user_id = $discordUser->getId();
$appUser->discord_pfp = $discordUser->getAvatar();
$appUser->save();
Auth::login($appUser, true);
} else {
$oAuthUser = User::create([
'uuid' => Str::uuid(), 'uuid' => Str::uuid(),
'name' => $discordUser->getName(), 'name' => $discordUser->getName(),
'email' => $discordUser->getEmail(), 'email' => $discordUser->getEmail(),
'email_verified_at' => now(), // verify the account since it came from a trusted provider 'email_verified_at' => now(), // verify the account since it came from a trusted provider
'username' => $discordUser->getNickname(), 'username' => $discordUser->getNickname(),
'currentIp' => \request()->ip(), 'currentIp' => \request()->ip(),
'registrationIp' => request()->ip(),
'discord_user_id' => $discordUser->getId(), 'discord_user_id' => $discordUser->getId(),
'discord_pfp' => $discordUser->getAvatar(),
'discord_token' => $discordUser->token, 'discord_token' => $discordUser->token,
'discord_refresh_token' => $discordUser->refreshToken 'discord_refresh_token' => $discordUser->refreshToken
], [
'email' => $discordUser->getEmail()
]); ]);
Auth::login(User::where('email', $discordUser->getEmail())->first()); Auth::login($oAuthUser, true);
}
return redirect() return redirect()
->route('dashboard'); ->route('dashboard');

View File

@ -41,8 +41,10 @@ class UserObserver
* @param \App\User $user * @param \App\User $user
* @return void * @return void
*/ */
public function created(ProfileService $profileService, User $user) public function created(User $user)
{ {
$profileService = new ProfileService();
try try
{ {
$profileService->createProfile($user); $profileService->createProfile($user);

View File

@ -0,0 +1,31 @@
<?php
namespace App\Providers;
use App\Helpers\Discord;
use Illuminate\Support\ServiceProvider;
class DiscordOuthProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
\App::bind('discordServiceFacade', function () {
return new Discord();
});
}
}

View File

@ -42,7 +42,19 @@ class User extends Authenticatable implements MustVerifyEmail
* @var array * @var array
*/ */
protected $fillable = [ protected $fillable = [
'name', 'email', 'password', 'originalIP', 'username', 'uuid', 'dob', 'name',
'email',
'password',
'originalIP',
'registrationIp',
'username',
'uuid',
'dob',
'email_verified_at',
'currentIp',
'discord_user_id',
'discord_token',
'discord_refresh_token'
]; ];
/** /**
@ -51,7 +63,7 @@ class User extends Authenticatable implements MustVerifyEmail
* @var array * @var array
*/ */
protected $hidden = [ protected $hidden = [
'password', 'remember_token', 'password', 'remember_token', 'discord_token', 'discord_refresh_token'
]; ];
/** /**
@ -61,6 +73,8 @@ class User extends Authenticatable implements MustVerifyEmail
*/ */
protected $casts = [ protected $casts = [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'discord_token' => 'encrypted',
'discord_refresh_token' => 'encrypted'
]; ];
// RELATIONSHIPS // RELATIONSHIPS
@ -101,35 +115,6 @@ class User extends Authenticatable implements MustVerifyEmail
return $this->hasMany('App\Absence', 'requesterID'); return $this->hasMany('App\Absence', 'requesterID');
} }
// ACCESSORS AND MUTATORS
/**
* Encrypt/decrypt the Discord access token. Data is encrypted at rest.
*
* @return Attribute
*/
public function discordToken(): Attribute {
return Attribute::make(
get: fn ($value) => Crypt::decryptString($value),
set: fn ($value) => Crypt::encryptString($value)
);
}
/**
* Encrypt/decrypt the Discord refresh token. Data is encrypted at rest.
*
* @return Attribute
*/
public function discordRefreshToken(): Attribute {
return Attribute::make(
get: fn ($value) => Crypt::decryptString($value),
set: fn ($value) => Crypt::encryptString($value)
);
}
// UTILITY LOGIC // UTILITY LOGIC
public function isVerified(): bool { public function isVerified(): bool {

View File

@ -291,6 +291,7 @@ return [
\App\Providers\OptionsProvider::class, \App\Providers\OptionsProvider::class,
App\Providers\DigitalStorageProvider::class, App\Providers\DigitalStorageProvider::class,
App\Providers\JSONProvider::class, App\Providers\JSONProvider::class,
App\Providers\DiscordOuthProvider::class,
NotificationChannels\Discord\DiscordServiceProvider::class, NotificationChannels\Discord\DiscordServiceProvider::class,
], ],
@ -349,7 +350,8 @@ return [
'Markdown' => GrahamCampbell\Markdown\Facades\Markdown::class, 'Markdown' => GrahamCampbell\Markdown\Facades\Markdown::class,
'ContextAwareValidator' => App\Facades\ContextAwareValidation::class, 'ContextAwareValidator' => App\Facades\ContextAwareValidation::class,
'Settings' => App\Facades\Options::class, 'Settings' => App\Facades\Options::class,
'JSON' => App\Facades\JSON::class 'JSON' => App\Facades\JSON::class,
'DiscordOauth' => App\Facades\Discord::class
], ],

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddIntProfilePicToUsers extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->longText('discord_pfp')->after('discord_user_id')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('discord_pfp');
});
}
}