Add possibility to delete the registered user and reset 2FAuth data

This commit is contained in:
Bubka 2022-03-28 13:45:19 +02:00
parent 984e6d253c
commit cdfda1591b
8 changed files with 152 additions and 5 deletions

View File

@ -2,20 +2,36 @@
namespace App\Http\Controllers\Auth;
use App\Models\User;
use App\Services\TwoFAccountService;
use App\Http\Requests\UserUpdateRequest;
use App\Http\Requests\UserDeleteRequest;
use App\Api\v1\Resources\UserResource;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Artisan;
use App\Exceptions\UnsupportedWithReverseProxyException;
use Exception;
class UserController extends Controller
{
/**
* Create a new controller instance.
* The TwoFAccount Service instance.
*/
public function __construct()
protected $twofaccountService;
/**
* Create a new controller instance.
*
* @param \App\Services\TwoFAccountService $twofaccountService
* @return void
*/
public function __construct(TwoFAccountService $twofaccountService)
{
$this->twofaccountService = $twofaccountService;
$authGuard = config('auth.defaults.guard');
if ($authGuard === 'reverse-proxy-guard') {
@ -27,7 +43,7 @@ public function __construct()
/**
* Update the user's profile information.
*
* @param \App\Api\v1\Requests\UserUpdateRequest $request
* @param \App\Http\Requests\UserUpdateRequest $request
* @return \App\Api\v1\Resources\UserResource
*/
public function update(UserUpdateRequest $request)
@ -48,4 +64,45 @@ public function update(UserUpdateRequest $request)
return new UserResource($user);
}
/**
* Delete the user's account.
*
* @param \App\Http\Requests\UserDeleteRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function delete(UserDeleteRequest $request)
{
$validated = $request->validated();
if (!Hash::check( $validated['password'], Auth::user()->password) ) {
return response()->json(['message' => __('errors.wrong_current_password')], 400);
}
try {
DB::transaction(function () {
DB::table('twofaccounts')->delete();
DB::table('groups')->delete();
DB::table('options')->delete();
DB::table('web_authn_credentials')->delete();
DB::table('web_authn_recoveries')->delete();
DB::table('oauth_access_tokens')->delete();
DB::table('oauth_auth_codes')->delete();
DB::table('oauth_clients')->delete();
DB::table('oauth_personal_access_clients')->delete();
DB::table('oauth_refresh_tokens')->delete();
DB::table('password_resets')->delete();
DB::table('users')->delete();
});
Artisan::call('passport:install --force');
Artisan::call('config:clear');
}
catch (\Throwable $e) {
return response()->json(['message' => __('errors.user_deletion_failed')], 400);
}
return response()->json(null, 204);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class UserDeleteRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'password' => 'required|string',
];
}
}

View File

@ -6,6 +6,9 @@
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\JsonResource;
use Laravel\Passport\Console\ClientCommand;
use Laravel\Passport\Console\InstallCommand;
use Laravel\Passport\Console\KeysCommand;
class AppServiceProvider extends ServiceProvider
@ -30,5 +33,11 @@ public function boot()
Blade::withoutComponentTags();
Schema::defaultStringLength(191);
JsonResource::withoutWrapping();
$this->commands([
InstallCommand::class,
ClientCommand::class,
KeysCommand::class,
]);
}
}

View File

@ -1,7 +1,7 @@
<template>
<div class="field is-grouped">
<div class="control">
<v-button :isLoading="isBusy" :disabled="isDisabled" >{{ caption }}</v-button>
<v-button :color="color" :isLoading="isBusy" :disabled="isDisabled" >{{ caption }}</v-button>
</div>
<div class="control" v-if="showCancelButton">
<router-link :to="{ name: cancelLandingView }" class="button is-text">{{ $t('commons.cancel') }}</router-link>
@ -44,6 +44,11 @@
type: String,
default: ''
},
color: {
type: String,
default: 'is-link'
},
}
}
</script>

View File

@ -22,6 +22,16 @@
<form-buttons :isBusy="formPassword.isBusy" :caption="$t('auth.forms.change_password')" />
</fieldset>
</form>
<form @submit.prevent="submitDelete" @keydown="formDelete.onKeydown($event)">
<h4 class="title is-4 pt-6 has-text-danger">{{ $t('auth.forms.delete_account') }}</h4>
<div class="field is-size-7-mobile">
{{ $t('auth.forms.delete_your_account_and_reset_all_data')}}
</div>
<fieldset :disabled="isRemoteUser">
<form-field :form="formDelete" fieldName="password" inputType="password" :label="$t('auth.forms.current_password.label')" :help="$t('auth.forms.current_password.help')" />
<form-buttons :isBusy="formDelete.isBusy" :caption="$t('auth.forms.delete_your_account')" :color="'is-danger'" />
</fieldset>
</form>
</form-wrapper>
</div>
<vue-footer :showButtons="true">
@ -52,6 +62,9 @@
password : '',
password_confirmation : '',
}),
formDelete: new Form({
password : '',
}),
isRemoteUser: false,
}
},
@ -101,7 +114,31 @@
this.$router.push({ name: 'genericError', params: { err: error.response } });
}
});
}
},
submitDelete(e) {
e.preventDefault()
if(confirm(this.$t('auth.confirm.delete_account'))) {
this.formDelete.delete('/user', {returnError: true})
.then(response => {
this.$notify({ type: 'is-success', text: this.$t('auth.forms.user_account_successfully_deleted') })
this.$router.push({ name: 'register' });
})
.catch(error => {
if( error.response.status === 400 ) {
this.$notify({ type: 'is-danger', text: error.response.data.message })
}
else if( error.response.status !== 422 ) {
this.$router.push({ name: 'genericError', params: { err: error.response } });
}
});
}
},
},
}
</script>

View File

@ -33,6 +33,7 @@
'confirm' => [
'logout' => 'Are you sure you want to log out?',
'revoke_device' => 'Are you sure you want to revoke this device?',
'delete_account' => 'Are you sure you want to delete your account?',
],
'webauthn' => [
'security_device' => 'a security device',
@ -96,6 +97,10 @@
'register_punchline' => 'Welcome to 2FAuth.<br/>You need an account to go further. Fill this form to register yourself, and please, choose a strong password, 2FA data are sensitives.',
'reset_punchline' => '2FAuth will send you a password reset link to this address. Click the link in the received email to set a new password.',
'name_this_device' => 'Name this device',
'delete_account' => 'Delete account',
'delete_your_account' => 'Delete your account',
'delete_your_account_and_reset_all_data' => 'This will reset 2FAuth. Your user account will be deleted as well as all 2FA data. There is no going back.',
'user_account_successfully_deleted' => 'User account successfully deleted',
],
];

View File

@ -36,4 +36,5 @@
'aborted_by_user' => 'Aborted by user',
'security_device_unsupported' => 'Security device unsupported',
'unsupported_with_reverseproxy' => 'Not applicable when using an auth proxy',
'user_deletion_failed' => 'User account deletion failed, no data have been deleted',
];

View File

@ -42,6 +42,7 @@
Route::put('user', 'Auth\UserController@update')->name('user.update');
Route::patch('user/password', 'Auth\PasswordController@update')->name('user.password.update');
Route::get('user/logout', 'Auth\LoginController@logout')->name('user.logout');
Route::delete('user', 'Auth\UserController@delete')->name('user.delete')->middleware('disableInDemoMode');
Route::get('oauth/personal-access-tokens', 'Auth\PersonalAccessTokenController@forUser')->name('passport.personal.tokens.index');
Route::post('oauth/personal-access-tokens', 'Auth\PersonalAccessTokenController@store')->name('passport.personal.tokens.store');