diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index e9f81a1..15dc202 100755 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -22,6 +22,7 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Services\AccountSuspensionService; use App\User; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; @@ -65,13 +66,16 @@ class LoginController extends Controller // We can't customise the error message, since that would imply overriding the login method, which is large. // Also, the user should never know that they're banned. - public function attemptLogin(Request $request) + public function attemptLogin(Request $request): bool { + $service = new AccountSuspensionService; $user = User::where('email', $request->email)->first(); if ($user) { - $isBanned = $user->isBanned(); - if ($isBanned) { + $isBanned = $service->isSuspended($user); + $isLocked = $service->isLocked($user); + + if ($isBanned || $isLocked) { return false; } else { return $this->originalAttemptLogin($request); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 25c5c7a..edec917 100755 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -33,6 +33,7 @@ use App\Http\Requests\UpdateUserRequest; use App\Notifications\ChangedPassword; use App\Notifications\EmailChanged; use App\Traits\DisablesFeatures; +use App\Traits\HandlesAccountDeletion; use App\Traits\ReceivesAccountTokens; use App\User; use Google2FA; @@ -44,7 +45,7 @@ use Spatie\Permission\Models\Role; class UserController extends Controller { - use ReceivesAccountTokens; + use HandlesAccountDeletion; public function showUsers() { diff --git a/app/Jobs/ProcessAccountDelete.php b/app/Jobs/ProcessAccountDelete.php new file mode 100644 index 0000000..7cd8bb9 --- /dev/null +++ b/app/Jobs/ProcessAccountDelete.php @@ -0,0 +1,51 @@ +user = $user; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + // It shouldn't need the suspension service, because if it was dispatched, the account was already locked + + Log::alert('[Worker] Processing account deletion request', [ + 'email' => $this->user->email + ]); + + $this->user->delete(); + } +} diff --git a/app/Mail/UserAccountDeleteConfirmation.php b/app/Mail/UserAccountDeleteConfirmation.php index 33b021f..c4baf0a 100755 --- a/app/Mail/UserAccountDeleteConfirmation.php +++ b/app/Mail/UserAccountDeleteConfirmation.php @@ -30,27 +30,23 @@ class UserAccountDeleteConfirmation extends Mailable { use Queueable, SerializesModels; - public $deleteToken; + public string + $approveLink, + $cancelLink, + $name, + $userID; - public $cancelToken; - - public $originalIP; - - public $name; - - public $userID; /** * Create a new message instance. * * @return void */ - public function __construct(User $user, array $tokens, string $originalIP) + public function __construct(User $user, array $links) { - $this->deleteToken = $tokens['delete']; - $this->cancelToken = $tokens['cancel']; + $this->approveLink = $links['approveURL']; + $this->cancelLink = $links['cancelURL']; - $this->originalIP = $originalIP; $this->name = $user->name; $this->userID = $user->id; } @@ -62,6 +58,7 @@ class UserAccountDeleteConfirmation extends Mailable */ public function build() { - return $this->view('mail.deleted-account'); + return $this->subject('[ACTION REQUIRED] Please confirm account removal') + ->view('mail.deleted-account'); } } diff --git a/app/Observers/ApplicationObserver.php b/app/Observers/ApplicationObserver.php deleted file mode 100755 index 16b6615..0000000 --- a/app/Observers/ApplicationObserver.php +++ /dev/null @@ -1,109 +0,0 @@ -. - */ - -namespace App\Observers; - -use App\Application; -use Illuminate\Support\Facades\Log; - -class ApplicationObserver -{ - /** - * Handle the application "created" event. - * - * @param \App\Application $application - * @return void - */ - public function created(Application $application) - { - // - } - - /** - * Handle the application "updated" event. - * - * @param \App\Application $application - * @return void - */ - public function updated(Application $application) - { - // - } - - public function deleting(Application $application) - { - $application->response()->delete(); - $votes = $application->votes; - - foreach ($votes as $vote) { - Log::debug('Referential integrity cleanup: Deleting and detaching vote '.$vote->id); - $vote->application()->detach($application->id); - $vote->delete(); - } - - if (! is_null($application->appointment)) { - Log::debug('RIC: Deleting appointment!'); - $application->appointment()->delete(); - } - - if (! $application->comments->isEmpty()) { - Log::debug('RIC: Deleting comments!'); - foreach ($application->comments as $comment) { - $comment->delete(); - } - } - - // application can now be deleted - } - - /** - * Handle the application "deleted" event. - * - * @param \App\Application $application - * @return void - */ - public function deleted(Application $application) - { - // - } - - /** - * Handle the application "restored" event. - * - * @param \App\Application $application - * @return void - */ - public function restored(Application $application) - { - // - } - - /** - * Handle the application "force deleted" event. - * - * @param \App\Application $application - * @return void - */ - public function forceDeleted(Application $application) - { - // - } -} diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index 16d2cdb..486e257 100755 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -40,6 +40,7 @@ class UserObserver */ public function created(User $user) { + // TODO: Make sure that the profile is created, throw an exception if otherwise, or try to recreate the profile. Profile::create([ 'profileShortBio' => 'Write a one-liner about you here!', @@ -61,29 +62,6 @@ class UserObserver // } - public function deleting(User $user) - { - Log::debug("Deleting observer running"); - if ($user->isForceDeleting()) { - $user->profile->delete(); - Log::debug('Referential integrity cleanup: Deleted profile!'); - $applications = $user->applications; - - if (! $applications->isEmpty()) { - Log::debug('RIC: Now trying to delete applications and responses...'); - foreach ($applications as $application) { - // code moved to Application observer, where it gets rid of attached elements individually - Log::debug('RIC: Deleting application '.$application->id); - $application->delete(); - } - } - } else { - Log::debug('RIC: Not cleaning up soft deleted models!'); - } - - Log::debug('RIC: Cleanup done!'); - } - /** * Handle the user "deleted" event. * diff --git a/app/Observers/VacancyObserver.php b/app/Observers/VacancyObserver.php deleted file mode 100755 index c0752c0..0000000 --- a/app/Observers/VacancyObserver.php +++ /dev/null @@ -1,94 +0,0 @@ -. - */ - -namespace App\Observers; - -use App\Application; -use App\Vacancy; -use Illuminate\Support\Facades\Log; - -class VacancyObserver -{ - /** - * Handle the vacancy "created" event. - * - * @param \App\Vacancy $vacancy - * @return void - */ - public function created(Vacancy $vacancy) - { - // - } - - /** - * Handle the vacancy "updated" event. - * - * @param \App\Vacancy $vacancy - * @return void - */ - public function updated(Vacancy $vacancy) - { - // - } - - public function deleting(Vacancy $vacancy) - { - foreach(Application::with('response.vacancy')->get() as $app) { - if ($app->response->vacancy->id == $vacancy->id) - { - $app->delete(); - } - } - } - - /** - * Handle the vacancy "deleted" event. - * - * @param \App\Vacancy $vacancy - * @return void - */ - public function deleted(Vacancy $vacancy) - { - - } - - /** - * Handle the vacancy "restored" event. - * - * @param \App\Vacancy $vacancy - * @return void - */ - public function restored(Vacancy $vacancy) - { - // - } - - /** - * Handle the vacancy "force deleted" event. - * - * @param \App\Vacancy $vacancy - * @return void - */ - public function forceDeleted(Vacancy $vacancy) - { - // - } -} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c383be6..148fb87 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -38,6 +38,7 @@ use Sentry; class AppServiceProvider extends ServiceProvider { + /** * Register any application services. * @@ -62,11 +63,6 @@ class AppServiceProvider extends ServiceProvider Schema::defaultStringLength(191); Paginator::useBootstrap(); - // Register observers - User::observe(UserObserver::class); - Application::observe(ApplicationObserver::class); - Vacancy::observe(VacancyObserver::class); - $https = ($this->app->environment() != 'local'); $collect = true; @@ -78,6 +74,10 @@ class AppServiceProvider extends ServiceProvider $collect = false; } + // Initialize user observer + User::observe(UserObserver::class); + + $this->app['request']->server->set('HTTPS', $https); View::share('shouldCollect', $collect); diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index c997c76..6eb82d3 100755 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -21,9 +21,15 @@ namespace App\Providers; +use App\Application; use App\Listeners\LogAuthenticationFailure; use App\Listeners\LogAuthenticationSuccess; use App\Listeners\OnUserRegistration; +use App\Observers\ApplicationObserver; +use App\Observers\UserObserver; +use App\Observers\VacancyObserver; +use App\User; +use App\Vacancy; use Illuminate\Auth\Events\Failed; use Illuminate\Auth\Events\Login; use Illuminate\Auth\Events\Registered; @@ -74,7 +80,5 @@ class EventServiceProvider extends ServiceProvider public function boot() { parent::boot(); - - // } } diff --git a/app/Services/AccountSuspensionService.php b/app/Services/AccountSuspensionService.php index 6676efd..9e9137d 100755 --- a/app/Services/AccountSuspensionService.php +++ b/app/Services/AccountSuspensionService.php @@ -62,6 +62,47 @@ class AccountSuspensionService $user->bans->delete(); } + + + + /** + * Sets an administrative lock on a user account. + * Used to prevent logins after a deletion process is initiated, but may be used for + * other things where a suspension is not necessary/warranted, such as a security breach event. + * These locks cannot be overridden manually be administrators. + * + * @param User $user The account to lock + * @return bool + */ + public function lockAccount(User $user): bool + { + Log::alert('User account locked!', [ + 'email' => $user->email + ]); + + $user->administratively_locked = 1; + return $user->save(); + } + + + /** + * Unlocks a user account. Reverse of lockAccount(). + * + * @param User $user + * @return bool + */ + public function unlockAccount(User $user): bool + { + Log::alert('User account unlocked!', [ + 'email' => $user->email + ]); + + $user->administratively_locked = 0; + return $user->save(); + } + + + /** * Checks whether a user is suspended * @@ -73,6 +114,16 @@ class AccountSuspensionService } + /** + * Checks whether an account is locked + * + * @param User $user The user to check + * @return bool Whether the mentioned account is locked + */ + public function isLocked(User $user): bool { + return $user->administratively_locked == 1; + } + /** * Takes a suspension directly and makes it permanent. * diff --git a/app/Traits/HandlesAccountDeletion.php b/app/Traits/HandlesAccountDeletion.php new file mode 100755 index 0000000..054f260 --- /dev/null +++ b/app/Traits/HandlesAccountDeletion.php @@ -0,0 +1,137 @@ +. + */ + +namespace App\Traits; + +use App\Http\Requests\UserDeleteRequest; +use App\Jobs\ProcessAccountDelete; +use App\Mail\UserAccountDeleteConfirmation; +use App\Services\AccountSuspensionService; +use App\User; +use Carbon\Carbon; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\URL; + +trait HandlesAccountDeletion +{ + + /** + * Starts the user account deletion process. + * + * @param AccountSuspensionService $suspensionService + * @param UserDeleteRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function userDelete(AccountSuspensionService $suspensionService, UserDeleteRequest $request) + { + if (config('demo.is_enabled')) + { + return redirect() + ->back() + ->with('error', 'This feature is disabled'); + } + + $links = [ + 'approveURL' => URL::temporarySignedRoute( + 'processDeleteConfirmation', now()->addDays(7), ['accountID' => $request->user()->id, 'action' => 'confirm'] + ), + 'cancelURL' => URL::temporarySignedRoute( + 'processDeleteConfirmation', now()->addDays(7), ['accountID' => $request->user()->id, 'action' => 'cancel'] + ) + ]; + + Mail::to($request->user()) + ->send(new UserAccountDeleteConfirmation($request->user(), $links)); + + // Only locked accounts can be deleted + $suspensionService->lockAccount($request->user()); + Auth::logout(); + + $request->session()->flash('success', __('Please check your email to finish deleting your account.')); + return redirect()->to('/'); + } + + + /** + * Dispatches the correct jobs and events to delete the specified user account + * + * @param Request $request + * @param AccountSuspensionService $suspensionService + * @param $accountID + * @param $action + * @return \Illuminate\Http\RedirectResponse|void + */ + public function processDeleteConfirmation(Request $request, AccountSuspensionService $suspensionService, $accountID, $action) + { + if (config('demo.is_enabled') || !$request->hasValidSignature()) + { + abort(403); + } + + // It's almost impossible for this to fail, unless the model has already been deleted by someone else, because: + // The request URL can't be tampered with and the request can't be initiated without a valid account in the first place + $account = User::find($accountID); + + if (!is_null($account)) + { + if (!$suspensionService->isLocked($account)) { + abort(403); + } + + Log::alert('Signed account deletion request received!', [ + 'user' => $account->name, + 'email' => $account->name, + 'created_at' => $account->created_at, + 'updated_at' => $account->updated_at, + 'deleted_at' => Carbon::now(), + 'ipAddress' => $request->ip(), + 'userAgent' => $request->userAgent(), + + ]); + + if ($action == 'confirm') { + // dispatch event (for notifications) and job (for async processing) + ProcessAccountDelete::dispatch($account); + + $request->session()->flash('success', __('Thank you for confirming. Your account will now be deleted shortly.')); + + return redirect() + ->to('/'); + } + + $suspensionService->unlockAccount($account); + $request->session()->flash('success', __('Account removal request cancelled. Your account has been unlocked and you can now sign in.')); + + return redirect() + ->route('login'); + + } + + Log::error("Cannot delete account that doesn't exist!", [ + 'validSignature' => $request->hasValidSignature() + ]); + abort(400); + + } +} diff --git a/app/Traits/HandlesAccountTokens.php b/app/Traits/HandlesAccountTokens.php deleted file mode 100755 index 965e54a..0000000 --- a/app/Traits/HandlesAccountTokens.php +++ /dev/null @@ -1,62 +0,0 @@ -. - */ - -namespace App\Traits; - -use Illuminate\Support\Facades\Hash; - -trait HandlesAccountTokens -{ - public function generateAccountTokens() - { - $deleteToken = bin2hex(openssl_random_pseudo_bytes(32)); - $cancelToken = bin2hex(openssl_random_pseudo_bytes(32)); - - $tokens = [ - - 'delete' => Hash::make($deleteToken), - 'cancel' => Hash::make($cancelToken), - - ]; - - $this->account_tokens = json_encode($tokens); - $this->save(); - - return [ - - 'delete' => $deleteToken, - 'cancel' => $cancelToken, - ]; - } - - public function verifyAccountToken(string $token, string $type): bool - { - $tokens = json_decode($this->account_tokens); - - if ($type == 'deleteToken') { - return Hash::check($token, $tokens->delete); - } elseif ($type == 'cancelToken') { - return Hash::check($token, $tokens->cancel); - } - - return false; - } -} diff --git a/app/Traits/ReceivesAccountTokens.php b/app/Traits/ReceivesAccountTokens.php deleted file mode 100755 index 7ed9de3..0000000 --- a/app/Traits/ReceivesAccountTokens.php +++ /dev/null @@ -1,113 +0,0 @@ -. - */ - -namespace App\Traits; - -use App\Http\Requests\UserDeleteRequest; -use App\Mail\UserAccountDeleteConfirmation; -use App\User; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Log; -use Illuminate\Support\Facades\Mail; - -trait ReceivesAccountTokens -{ - public function userDelete(UserDeleteRequest $request) - { - //Fixme: TEMPORARY, PLEASE REMOVE UNTIL FIXED OR DURING DEVELOPMENT - return redirect() - ->back() - ->with('error', 'This feature is disabled'); - - if (config('demo.is_enabled')) - { - return redirect() - ->back() - ->with('error', 'This feature is disabled'); - } - - // a little verbose - $user = User::find(Auth::user()->id); - $tokens = $user->generateAccountTokens(); - - Mail::to($user)->send(new UserAccountDeleteConfirmation($user, $tokens, $request->ip())); - - $user->delete(); - Auth::logout(); - - $request->session()->flash('success', __('Please check your email to finish deleting your account.')); - - return redirect()->to('/'); - } - - public function processDeleteConfirmation(Request $request, $ID, $action, $token) - { - if (config('demo.is_enabled')) - { - return redirect() - ->back() - ->with('error', 'This feature is disabled'); - } - - // We can't rely on Laravel's route model injection, because it'll ignore soft-deleted models, - // so we have to use a special scope to find them ourselves. - $user = User::withTrashed()->findOrFail($ID); - $email = $user->email; - - switch ($action) { - case 'confirm': - - if ($user->verifyAccountToken($token, 'deleteToken')) { - Log::info('SECURITY: User deleted account!', [ - - 'confirmDeleteToken' => $token, - 'ipAddress' => $request->ip(), - 'email' => $user->email, - - ]); - - $user->forceDelete(); - - $request->session()->flash('success', __('Account permanently deleted. Thank you for using our service.')); - - return redirect()->to('/'); - } - - break; - - case 'cancel': - - if ($user->verifyAccountToken($token, 'cancelToken')) { - $user->restore(); - $request->session()->flash('success', __('Account deletion cancelled! You may now login.')); - - return redirect()->to(route('login')); - } - - break; - - default: - - abort(404, __('The page you were trying to access may not exist or may be expired.')); - } - } -} diff --git a/app/User.php b/app/User.php index 752c47b..2bca37a 100755 --- a/app/User.php +++ b/app/User.php @@ -21,6 +21,7 @@ namespace App; +use App\Services\AccountSuspensionService; use App\Traits\HandlesAccountTokens; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\SoftDeletes; @@ -31,7 +32,7 @@ use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable implements MustVerifyEmail { - use UserHasTeams, Notifiable, HasRoles, SoftDeletes, HandlesAccountTokens; + use UserHasTeams, Notifiable, HasRoles; /** * The attributes that are mass assignable. @@ -101,7 +102,15 @@ class User extends Authenticatable implements MustVerifyEmail // UTILITY LOGIC - public function isBanned() + /** + * Checks if a user is banned. + * + * @deprecated This method is obsolete, as it has been replaced by the suspension service. + * @see AccountSuspensionService::isSuspended() + * + * @return bool Whether the user is banned + */ + public function isBanned(): bool { return ! $this->bans()->get()->isEmpty(); } diff --git a/database/migrations/2020_04_29_022245_create_profiles_table.php b/database/migrations/2020_04_29_022245_create_profiles_table.php index bc21b71..a14ddb2 100755 --- a/database/migrations/2020_04_29_022245_create_profiles_table.php +++ b/database/migrations/2020_04_29_022245_create_profiles_table.php @@ -46,7 +46,8 @@ class CreateProfilesTable extends Migration $table->foreign('userID') ->references('id') - ->on('users'); + ->on('users') + ->cascadeOnDelete(); }); } diff --git a/database/migrations/2020_04_29_022402_create_applications_table.php b/database/migrations/2020_04_29_022402_create_applications_table.php index 529e046..0faf6ef 100755 --- a/database/migrations/2020_04_29_022402_create_applications_table.php +++ b/database/migrations/2020_04_29_022402_create_applications_table.php @@ -53,7 +53,8 @@ class CreateApplicationsTable extends Migration $table->foreign('applicantUserID') ->references('id') - ->on('users'); + ->on('users') + ->cascadeOnDelete(); }); } diff --git a/database/migrations/2020_04_29_022421_create_votes_table.php b/database/migrations/2020_04_29_022421_create_votes_table.php index 6164f56..2838cac 100755 --- a/database/migrations/2020_04_29_022421_create_votes_table.php +++ b/database/migrations/2020_04_29_022421_create_votes_table.php @@ -43,7 +43,8 @@ class CreateVotesTable extends Migration $table->foreign('userID') ->references('id') - ->on('users'); + ->on('users') + ->cascadeOnDelete(); }); } diff --git a/database/migrations/2020_04_29_022442_create_appointments_table.php b/database/migrations/2020_04_29_022442_create_appointments_table.php index 08d60f2..d79ff0e 100755 --- a/database/migrations/2020_04_29_022442_create_appointments_table.php +++ b/database/migrations/2020_04_29_022442_create_appointments_table.php @@ -54,7 +54,8 @@ class CreateAppointmentsTable extends Migration $table->foreign('applicationID') ->references('id') - ->on('applications'); + ->on('applications') + ->cascadeOnDelete(); }); } diff --git a/database/migrations/2020_04_29_030107_create_responses_table.php b/database/migrations/2020_04_29_030107_create_responses_table.php index 02acc46..fd1e053 100755 --- a/database/migrations/2020_04_29_030107_create_responses_table.php +++ b/database/migrations/2020_04_29_030107_create_responses_table.php @@ -41,7 +41,8 @@ class CreateResponsesTable extends Migration // A better way would be to link responses directly to vacancies, that subsquently have a form $table->foreign('responseFormID') ->references('id') - ->on('forms'); + ->on('forms') + ->cascadeOnDelete(); }); } diff --git a/database/migrations/2020_04_29_195848_votes_has_application.php b/database/migrations/2020_04_29_195848_votes_has_application.php index 8ccf13f..32eae4c 100755 --- a/database/migrations/2020_04_29_195848_votes_has_application.php +++ b/database/migrations/2020_04_29_195848_votes_has_application.php @@ -38,8 +38,8 @@ class VotesHasApplication extends Migration $table->bigInteger('application_id')->unsigned(); $table->timestamps(); - $table->foreign('vote_id')->references('id')->on('votes'); - $table->foreign('application_id')->references('id')->on('applications'); + $table->foreign('vote_id')->references('id')->on('votes')->cascadeOnDelete(); + $table->foreign('application_id')->references('id')->on('applications')->cascadeOnDelete(); }); } diff --git a/database/migrations/2020_06_08_153602_create_bans_table.php b/database/migrations/2020_06_08_153602_create_bans_table.php index dd18593..98b645d 100755 --- a/database/migrations/2020_06_08_153602_create_bans_table.php +++ b/database/migrations/2020_06_08_153602_create_bans_table.php @@ -43,7 +43,8 @@ class CreateBansTable extends Migration $table->foreign('userID') ->references('id') - ->on('users'); + ->on('users') + ->cascadeOnDelete(); }); } diff --git a/database/migrations/2020_06_20_210255_create_comments_table.php b/database/migrations/2020_06_20_210255_create_comments_table.php index fc9c1d2..4cd372a 100755 --- a/database/migrations/2020_06_20_210255_create_comments_table.php +++ b/database/migrations/2020_06_20_210255_create_comments_table.php @@ -41,11 +41,13 @@ class CreateCommentsTable extends Migration $table->foreign('authorID') ->references('id') - ->on('users'); + ->on('users') + ->cascadeOnDelete(); $table->foreign('applicationID') ->references('id') - ->on('applications'); + ->on('applications') + ->cascadeOnDelete(); }); } diff --git a/database/migrations/2022_02_02_060702_create_absences_table.php b/database/migrations/2022_02_02_060702_create_absences_table.php index a00d80c..611d675 100755 --- a/database/migrations/2022_02_02_060702_create_absences_table.php +++ b/database/migrations/2022_02_02_060702_create_absences_table.php @@ -27,11 +27,13 @@ class CreateAbsencesTable extends Migration $table->foreign('requesterID') ->references('id') - ->on('users'); + ->on('users') + ->onDelete('cascade'); $table->foreign('reviewer') ->references('id') - ->on('users'); + ->on('users') + ->onDelete('set null'); }); } diff --git a/database/migrations/2022_03_07_180241_remove_account_tokens_from_user.php b/database/migrations/2022_03_07_180241_remove_account_tokens_from_user.php new file mode 100644 index 0000000..fdd2e8f --- /dev/null +++ b/database/migrations/2022_03_07_180241_remove_account_tokens_from_user.php @@ -0,0 +1,32 @@ +dropColumn('account_tokens'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->string('account_tokens')->after('password')->nullable(); + }); + } +}; diff --git a/resources/views/dashboard/user/profile/useraccount.blade.php b/resources/views/dashboard/user/profile/useraccount.blade.php index ca66911..d5aedde 100755 --- a/resources/views/dashboard/user/profile/useraccount.blade.php +++ b/resources/views/dashboard/user/profile/useraccount.blade.php @@ -48,13 +48,7 @@
  • {{ __('Server logs of your visits, including IP addresses') }}
  • - -

    {{ __('Feature temporarily unavailable') }}

    - -

    This feature has been temporarily made unavailable while we work to fix underlying issues that are causing our backoffice to crash. We apologize for the inconvenience, and any account/data deletion requests should be forwarded to our data protection officer below.

    -

    dpo@gamescluboficial.com.br

    -
    - +

    {{ __("Note: After you verify your identity, you'll receive an email with more information asking you to confirm this request.") }}

    @@ -63,7 +57,7 @@
    - +

    {{ __('For your security, your password is always required for sensitive operations.') }} {{ __('Forgot your password?') }}

    @@ -71,7 +65,7 @@
    - +

    {{ __('You cannot recover lost 2FA secrets.') }}

    @@ -81,7 +75,7 @@ - + diff --git a/resources/views/mail/deleted-account.blade.php b/resources/views/mail/deleted-account.blade.php index aea3c12..2a9fed8 100755 --- a/resources/views/mail/deleted-account.blade.php +++ b/resources/views/mail/deleted-account.blade.php @@ -3,7 +3,7 @@ - Your account has been deleted + Action required: please confirm whether you'd like to delete your account - + @@ -111,23 +111,23 @@
     
    -

    Hi {{ $name }},

    -

    Someone (hopefully you) has requested that your account at {{ config('app.name') }} be deleted.

    -

    As a security measure, an email is always sent out to make sure you really want to delete your account.

    -

    This is the IP address the request was made from: {{ $originalIP }}.

    -

    If you don't do anything, your account will automatically be permanently deleted in 30 days, and will remain unaccessible for that time period.

    -

    Click one of the buttons below to make a decision.

    +

    Hi {{ $name }},

    +

    We're sorry to see you go! Please let us know if there is any feedback you'd like to share, or if there's anything we can help you with.

    +

    To prevent any accidental loss of data, please confirm whether you really want to delete your account.

    +

    You have 7 days to make a decision, after which the links below will become invalid. You will not be able to sign in or use your account during this time period.

    +

    If you don't make any decision after 7 days, your account will remain permanently locked; Locked accounts are automatically deleted after 30 days. You may, however, contact us after this time period to unlock your account if you have changed your mind.

    +

    Do you really want to delete your account?

    - +
    - - + +
    Delete Account Cancel Deletion No, unlock and keep my account Yes, irreversibly delete my account
    @@ -150,7 +150,7 @@
    - Staff Manager + RBRecruiter | GC
    @@ -164,4 +164,4 @@
    - \ No newline at end of file + diff --git a/routes/web.php b/routes/web.php index 609d485..1168f6a 100755 --- a/routes/web.php +++ b/routes/web.php @@ -94,7 +94,7 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo Route::post('/form/contact', [ContactController::class, 'create']) ->name('sendSubmission'); - Route::get('/accounts/danger-zone/{ID}/{action}/{token}', [UserController::class, 'processDeleteConfirmation']) + Route::get('/accounts/{accountID}/dg/process-delete/{action}', [UserController::class, 'processDeleteConfirmation']) ->name('processDeleteConfirmation'); Route::group(['middleware' => ['auth', 'forcelogout', 'passwordexpiration', '2fa', 'verified']], function () {