feat: complete link/unlink flow for social accts

Signed-off-by: miguel456 <me@nogueira.codes>
This commit is contained in:
Miguel Nogueira 2022-10-21 07:12:09 +01:00
parent 2901f76a11
commit 376350cda2
No known key found for this signature in database
GPG Key ID: 3C6A7E29AF26D370
7 changed files with 131 additions and 31 deletions

View File

@ -26,8 +26,10 @@ use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\InvalidStateException;
class DiscordController extends Controller
{
@ -41,7 +43,20 @@ class DiscordController extends Controller
public function discordCallback() {
try {
$discordUser = Socialite::driver('discord')->user();
} catch (InvalidStateException $stateException) {
Log::warning('Invalid state for social authentication: ', [
'message' => $stateException->getMessage(),
'ua' => request()->userAgent(),
'ip' => request()->ip()
]);
return redirect(route('discordRedirect'));
}
$appUser = User::where('email', $discordUser->getEmail())->first();
if ($appUser) {

View File

@ -45,8 +45,11 @@ use App\Traits\HandlesAccountDeletion;
use App\Traits\ReceivesAccountTokens;
use App\User;
use Google2FA;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
@ -248,6 +251,32 @@ class UserController extends Controller
}
/**
* Sets a new password for the user.
*
* @param SetNewPasswordRequest $request
* @return Application|RedirectResponse|Redirector
*/
public function setPassword(SetNewPasswordRequest $request) {
if (!Auth::user()->hasPassword()) {
Auth::user()->password = Hash::make($request->newpass);
Auth::user()->save();
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect(route('login'));
}
return redirect()
->back()
->with('error', __('Your account already has a password.'));
}
/**
* Sets a user's password and removes their discord information from storage
*
@ -255,11 +284,9 @@ class UserController extends Controller
* @param SetNewPasswordRequest $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function setUnlinkPassword(SetNewPasswordRequest $request, DiscordService $discordService)
public function unlinkDiscordAccount(Request $request, DiscordService $discordService)
{
Auth::user()->password = Hash::make($request->newpass);
Auth::user()->save();
if ($request->user()->hasPassword()) {
try {
$discordService->revokeAccountTokens(Auth::user());
Log::warning('Revoking social account tokens, user initiated', [
@ -277,13 +304,14 @@ class UserController extends Controller
->with('error', __('An unknown error ocurred. Please try again later.'));
}
$request->session()->flash('success', __('Discord account unlinked successfully. Link it again by re-authorizing the app with the same account in the login screen, or through your account settings.'));
return redirect()->back();
}
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()
->back()
->with('error', __('Please set a password for your account first before trying to unlink Discord.'));
$request->session()->flash('success', 'Discord account unlinked! You may now login with your Discord email and brand new password.');
return redirect(route('login'));
}

View File

@ -28,7 +28,7 @@ class SetNewPasswordRequest extends FormRequest
public function rules()
{
return [
'newpass' => 'required|string|min:10|confirmed'
'newpass' => 'required|string|min:10|confirmed',
];
}
}

View File

@ -190,4 +190,14 @@ class User extends Authenticatable implements MustVerifyEmail
return !is_null($this->discord_token) && !is_null($this->discord_refresh_token);
}
/**
* Check if user has a password
*
* @return bool
*/
public function hasPassword(): bool {
return !is_null($this->password);
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 79.99 15"><defs><style>.cls-1{fill:#5865f2;}</style></defs><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Small_-_Blurple" data-name="Discord Logo - Small - Blurple"><path class="cls-1" d="M16.77,1.26A16.32,16.32,0,0,0,12.68,0a10.4,10.4,0,0,0-.52,1.06,15.63,15.63,0,0,0-4.53,0C7.49.73,7.28.31,7.11,0A16.07,16.07,0,0,0,3,1.26,16.47,16.47,0,0,0,.08,12.49h0a16.67,16.67,0,0,0,5,2.51,11.34,11.34,0,0,0,1.07-1.73,10,10,0,0,1-1.68-.81l.41-.32a11.7,11.7,0,0,0,10,0c.14.11.27.22.42.32a10.6,10.6,0,0,1-1.7.81A11.4,11.4,0,0,0,14.7,15a16.67,16.67,0,0,0,5-2.51h0A16.48,16.48,0,0,0,16.77,1.26Zm-10.16,9a1.89,1.89,0,0,1-1.78-2,1.88,1.88,0,0,1,1.78-2,1.87,1.87,0,0,1,1.78,2A1.89,1.89,0,0,1,6.61,10.22Zm6.57,0a1.9,1.9,0,0,1-1.78-2,1.89,1.89,0,0,1,1.78-2,1.87,1.87,0,0,1,1.78,2A1.88,1.88,0,0,1,13.18,10.22Z"/><path class="cls-1" d="M26.62,3.14h4.25a6.32,6.32,0,0,1,2.6.48A3.59,3.59,0,0,1,35.06,5a3.65,3.65,0,0,1,.52,2,3.6,3.6,0,0,1-.55,2,3.7,3.7,0,0,1-1.67,1.39,6.83,6.83,0,0,1-2.8.51H26.62Zm3.9,5.69a2.22,2.22,0,0,0,1.59-.52,1.82,1.82,0,0,0,.55-1.41,1.78,1.78,0,0,0-.49-1.32,2,2,0,0,0-1.5-.5H29.34V8.83Z"/><path class="cls-1" d="M42.21,10.76a5.68,5.68,0,0,1-1.59-.67V8.28A4.24,4.24,0,0,0,42.05,9a6.39,6.39,0,0,0,1.74.26,1.31,1.31,0,0,0,.59-.1c.14-.07.2-.15.2-.25a.38.38,0,0,0-.1-.27,1,1,0,0,0-.43-.18l-1.31-.3a3.28,3.28,0,0,1-1.59-.72,1.63,1.63,0,0,1-.48-1.22,1.61,1.61,0,0,1,.42-1.1,2.66,2.66,0,0,1,1.17-.72,5.68,5.68,0,0,1,1.79-.26,6.79,6.79,0,0,1,1.68.2,4.66,4.66,0,0,1,1.26.5V6.52A5.1,5.1,0,0,0,45.81,6a5.32,5.32,0,0,0-1.38-.17c-.68,0-1,.11-1,.34a.27.27,0,0,0,.16.25,2.84,2.84,0,0,0,.58.17l1.09.19a3.26,3.26,0,0,1,1.59.65,1.74,1.74,0,0,1,.52,1.37,1.8,1.8,0,0,1-.86,1.57A4.38,4.38,0,0,1,44,11,7.32,7.32,0,0,1,42.21,10.76Z"/><path class="cls-1" d="M50.05,10.53a3.37,3.37,0,0,1-1.41-1.24,3.42,3.42,0,0,1-.47-1.77,3.16,3.16,0,0,1,.49-1.75A3.35,3.35,0,0,1,50.1,4.55a5.39,5.39,0,0,1,2.26-.44,5,5,0,0,1,2.72.69v2a3.66,3.66,0,0,0-.89-.42,3.49,3.49,0,0,0-1.08-.17,3,3,0,0,0-1.59.37,1.12,1.12,0,0,0,0,1.94,2.9,2.9,0,0,0,1.62.37,3.89,3.89,0,0,0,1.07-.15,4.29,4.29,0,0,0,.91-.39v1.93a5.31,5.31,0,0,1-2.78.72A5.22,5.22,0,0,1,50.05,10.53Z"/><path class="cls-1" d="M57.93,10.53a3.41,3.41,0,0,1-1.44-1.25A3.32,3.32,0,0,1,56,7.5a3.17,3.17,0,0,1,.5-1.75,3.34,3.34,0,0,1,1.43-1.2,5.9,5.9,0,0,1,4.47,0,3.28,3.28,0,0,1,1.43,1.2,3.2,3.2,0,0,1,.49,1.75,3.4,3.4,0,0,1-.49,1.78,3.44,3.44,0,0,1-1.43,1.25,5.64,5.64,0,0,1-4.46,0ZM61.24,8.6a1.42,1.42,0,0,0,.4-1.06,1.38,1.38,0,0,0-.4-1,1.51,1.51,0,0,0-1.08-.38,1.55,1.55,0,0,0-1.1.38,1.42,1.42,0,0,0-.4,1,1.47,1.47,0,0,0,.4,1.06,1.51,1.51,0,0,0,1.1.39A1.48,1.48,0,0,0,61.24,8.6Z"/><path class="cls-1" d="M71,4.51V6.87a2,2,0,0,0-1.08-.27,1.7,1.7,0,0,0-1.34.52,2.45,2.45,0,0,0-.47,1.64v2H65.43V4.4h2.62v2a3.34,3.34,0,0,1,.7-1.64A1.62,1.62,0,0,1,70,4.25,1.87,1.87,0,0,1,71,4.51Z"/><path class="cls-1" d="M80,2.92v7.85H77.32V9.34a2.56,2.56,0,0,1-1,1.23,3.23,3.23,0,0,1-1.71.42A2.86,2.86,0,0,1,73,10.55a3,3,0,0,1-1-1.21A4.1,4.1,0,0,1,71.6,7.6,3.9,3.9,0,0,1,72,5.8a3,3,0,0,1,1.13-1.24,3,3,0,0,1,1.65-.45,2.5,2.5,0,0,1,2.56,1.66V2.92ZM76.92,8.55a1.38,1.38,0,0,0,.41-1,1.28,1.28,0,0,0-.4-1,1.51,1.51,0,0,0-1.08-.38,1.49,1.49,0,0,0-1.08.39,1.3,1.3,0,0,0-.4,1,1.35,1.35,0,0,0,.4,1,1.66,1.66,0,0,0,2.15,0Z"/><ellipse class="cls-1" cx="38.05" cy="3.53" rx="1.39" ry="1.25"/><path class="cls-1" d="M39.44,5.64a3.53,3.53,0,0,1-2.78,0v5.13h2.78Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -33,7 +33,7 @@
<p>{{ __('Deleting your account is an irreversible process. The following data will be deleted (including personally identifiable data):') }}</p>
<ul>
<li>{{ __('Last IP address') }}</li>
<li>{{ __('Name, Email and MC Username') }}</li>
<li>{{ __('Your name and email address') }}</li>
<li>{{ __('Your previous applications') }}</li>
<li>{{ __('Your profile data and preferences') }}</li>
<li>{{ __('Any other information stored in your user profile') }}</li>
@ -240,6 +240,9 @@
<li class="nav-item">
<a class="nav-link" id="accountSecurityTab" data-toggle="tab" href="#accountSecurity" role="tab" aria-controls="AccountSecurity" aria-selected="true">{{__('Account Security')}}</a>
</li>
<li class="nav-item">
<a class="nav-link" id="connectedAccountsTab" data-toggle="tab" href="#connectedAccounts" role="tab" aria-controls="ConnectedAccounts" aria-selected="false">{{__('Connected Accounts')}}</a>
</li>
<li class="nav-item">
<a class="nav-link" id="twofaTab" data-toggle="tab" href="#twofa" role="tab" aria-controls="TwoFa" aria-selected="false">{{__('Two Factor Authentication')}}</a>
</li>
@ -263,13 +266,12 @@
</div>
@endif
@if (Auth::user()->hasDiscordConnection())
@if (!Auth::user()->hasPassword())
<h5><i class="fab fa-discord"></i> {{ __('Your :appName account is linked to Discord', ['appName' => config('app.name')]) }}</h5>
<h5>{{ __('Your :appName account does not have a password', ['appName' => config('app.name')]) }}</h5>
<p>{!! __('Your account is linked to Discord, which means you only need your Discord account credentials and two-factor code (if enabled) to sign in. If you would like to change your password, you will need to do so in your Discord account settings. Please <a href=":articleLink" target="_blank">read this article</a> to learn more.', ['articleLink' => 'https://support.discord.com/hc/en-us/articles/218410947-I-forgot-my-Password-Where-can-I-set-a-new-one-']) !!}</p>
<p>{{ __('Alternatively, you can unlink your Discord account and set a password which you can use to sign in. Please note that any roles and/or privileges you may have been given as a result of this integration may be automatically removed until you link your Discord account again.') }}</p>
<p>{!! __('Because your account is linked to one or more <a href=":oAuthExplanationLink" target="_blank">OAuth</a> providers (such as Discord), there is no need for your account to use a password.', ['oAuthExplanationLink' => 'https://en.wikipedia.org/wiki/OAuth']) !!}</p>
<p>{{ __('Because several sensitive actions on the app require you to confirm your password, you may define one below. This will also allow you to unlink your accounts and login with a password.') }}</p>
<form method="POST" action="{{ route('addPassword') }}" id="setPassword">
@csrf
@ -289,7 +291,9 @@
</div>
</div>
<button type="submit" class="btn btn-warning btn-md mt-4"><i class="fas fa-check"></i> {{ __('Set password and unlink account') }}</button>
<input type="hidden" name="dc" value="false">
<button type="submit" class="btn btn-warning btn-md mt-4"><i class="fas fa-check"></i> {{ __('Set password') }}</button>
<p class="text-sm"><i>{{ __('You will be logged out afterwards.') }}</i></p>
</form>
@else
@ -320,6 +324,45 @@
<button {{ ($demoActive) ? 'disabled' : '' }} class="btn btn-success" type="button" onclick="document.getElementById('changePassword').submit()">{{__('Change Password')}}</button>
@endif
</div>
<div class="tab-pane fade p-3" id="connectedAccounts" role="tabpanel" aria-labelledby="connectedAccountsTab">
<h5 class="card-title">{{ __('Connected Accounts') }}</h5>
<p class="card-text">{{__('Manage your connected external accounts here.')}}</p>
@if(Auth::user()->hasDiscordConnection())
<p class="text-muted">{{ __('Your account is currently connected to Discord.') }}</p>
@if(!Auth::user()->hasPassword())
<x-alert icon="fas fa-exclamation-triangle" alert-type="warning" title="{{ __('Account unlinking disabled') }}">
{{ __("We've disabled unlinking your Discord account for now. To re-enable it, please add a password in the Account Security screen. This is to ensure you still have access to your account even after you change your Discord account email address.") }}
</x-alert>
@else
<x-alert icon="fas fa-info-circle" alert-type="info" title="{{ __('Note on disconnecting accounts') }}">
<p>{{ __("If you choose to disconnect your Discord account, we'll let Discord know by revoking your authorization. This means you will no longer be able to apply for positions that require a linked Discord account, and you may also lose any server privileges/roles that may have been granted through your linked account.") }}</p>
<p>{{ __(" You'll be able to link your account again through this page or by signing in with Discord again, but remember that if you changed your Discord email address, you will no longer have access to your old account.") }}</p>
</x-alert>
@endif
<div class="row mt-2">
<div class="col">
<x-button id="discordConnected" type="button" icon="fab fa-discord" color="primary" disabled> {{ __('Connected to Discord') }}</x-button>
</div>
<div class="col-9">
@if(Auth::user()->hasPassword())
<form name="disconnectAccount" method="post" action="{{ route('unlink-discord-account') }}">
@method('PATCH')
@csrf
<x-button id="discordDisconnect" type="submit" icon="fas fa-unlink" color="danger">{{ __('Disconnect') }}</x-button>
</form>
@else
<x-button id="discordDisconnect" type="button" icon="fas fa-unlink" color="danger" disabled>{{ __('Disconnect') }}</x-button>
@endif
</div>
</div>
@else
<p class="text-muted">{{ __('Your Discord account is not connected. Connect your Discord account below in order to apply for positions that require it.') }}</p>
<x-button id="discordDisconnected" type="button" icon="fab fa-discord" color="primary" link="{{ route('discordRedirect') }}"> {{ __('Connect to Discord') }}</x-button>
@endif
</div>
<div class="tab-pane fade p-3" id="twofa" role="tabpanel" aria-labelledby="twofaTab">
<h5 class="card-title">{{__('Two Factor Authentication')}}</h5>
<br />

View File

@ -219,8 +219,11 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
Route::patch('/settings/account/change-password', [UserController::class, 'changePassword'])
->name('changePassword');
Route::patch('/settings/account/add-password', [UserController::class, 'setUnlinkPassword'])
Route::patch('/settings/account/add-password', [UserController::class, 'setPassword'])
->name('addPassword');
Route::patch('/settings/account/unlink-oauth', [UserController::class, 'unlinkDiscordAccount'])
->name('unlink-discord-account');