浏览代码

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

Bubka 3 年之前
父节点
当前提交
cdfda1591b

+ 59 - 2
app/Http/Controllers/Auth/UserController.php

@@ -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
 {
+    /**
+     * The TwoFAccount Service instance.
+     */
+    protected $twofaccountService;
+
+
     /**
      * Create a new controller instance.
+     *
+     * @param  \App\Services\TwoFAccountService  $twofaccountService
+     * @return void
      */
-    public function __construct()
+    public function __construct(TwoFAccountService $twofaccountService)
     {
+        $this->twofaccountService = $twofaccountService;
         $authGuard = config('auth.defaults.guard');
 
         if ($authGuard === 'reverse-proxy-guard') {
@@ -27,7 +43,7 @@ class UserController extends Controller
     /**
      * 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 @@ class UserController extends Controller
 
         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);
+    }
 }

+ 32 - 0
app/Http/Requests/UserDeleteRequest.php

@@ -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',
+        ];
+    }
+}

+ 9 - 0
app/Providers/AppServiceProvider.php

@@ -6,6 +6,9 @@ use Illuminate\Support\Facades\Blade;
 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 @@ class AppServiceProvider extends ServiceProvider
         Blade::withoutComponentTags();
         Schema::defaultStringLength(191);
         JsonResource::withoutWrapping();
+
+        $this->commands([
+            InstallCommand::class,
+            ClientCommand::class,
+            KeysCommand::class,
+        ]);
     }
 }

+ 6 - 1
resources/js/components/FormButtons.vue

@@ -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>

+ 38 - 1
resources/js/views/settings/Account.vue

@@ -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>

+ 5 - 0
resources/lang/en/auth.php

@@ -33,6 +33,7 @@ return [
     '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 @@ return [
         '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',
     ],
 
 ];

+ 1 - 0
resources/lang/en/errors.php

@@ -36,4 +36,5 @@ return [
     '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',
 ];

+ 1 - 0
routes/web.php

@@ -42,6 +42,7 @@ Route::group(['middleware' => 'behind-auth'], function () {
     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');