diff --git a/app/controllers/users/delete_controller.rb b/app/controllers/users/delete_controller.rb index 5559dfbf982..5daaabe5042 100644 --- a/app/controllers/users/delete_controller.rb +++ b/app/controllers/users/delete_controller.rb @@ -1,19 +1,39 @@ module Users - class DeleteController < ReauthnRequiredController + class DeleteController < ApplicationController before_action :confirm_two_factor_authenticated + before_action :confirm_current_password, only: [:delete] - def show; end + def show + analytics.track_event(Analytics::ACCOUNT_DELETE_VISITED) + end def delete send_push_notifications current_user.destroy! sign_out flash[:success] = t('devise.registrations.destroyed') + analytics.track_event(Analytics::ACCOUNT_DELETE_SUBMITTED, success: true) redirect_to root_url end private + def confirm_current_password + return if valid_password? + + flash[:error] = t('idv.errors.incorrect_password') + analytics.track_event(Analytics::ACCOUNT_DELETE_SUBMITTED, success: false) + render :show + end + + def valid_password? + current_user.valid_password?(password) + end + + def password + params.fetch(:user, {})[:password].presence + end + def send_push_notifications return if Figaro.env.push_notifications_enabled != 'true' PushNotification::AccountDelete.new.call(current_user.id) diff --git a/app/services/analytics.rb b/app/services/analytics.rb index cf9e4697cf1..5f473bf9d53 100644 --- a/app/services/analytics.rb +++ b/app/services/analytics.rb @@ -65,6 +65,8 @@ def browser_attributes # rubocop:disable Metrics/LineLength ACCOUNT_RESET = 'Account Reset'.freeze + ACCOUNT_DELETE_SUBMITTED = 'Account Delete submitted'.freeze + ACCOUNT_DELETE_VISITED = 'Account Delete visited'.freeze ACCOUNT_DELETION = 'Account Deletion Requested'.freeze ACCOUNT_RESET_VISIT = 'Account deletion and reset visited'.freeze ACCOUNT_VISIT = 'Account Page Visited'.freeze diff --git a/app/views/users/delete/show.html.erb b/app/views/users/delete/show.html.erb new file mode 100644 index 00000000000..06070167938 --- /dev/null +++ b/app/views/users/delete/show.html.erb @@ -0,0 +1,30 @@ + diff --git a/app/views/users/delete/show.html.slim b/app/views/users/delete/show.html.slim deleted file mode 100644 index 49cd4bdb39a..00000000000 --- a/app/views/users/delete/show.html.slim +++ /dev/null @@ -1,18 +0,0 @@ -.p0.cntnr-xxskinny.border-box.bg-white.rounded-xxl.modal-warning - h2.my2.fs-20p.sans-serif.regular.center - = t('users.delete.heading') - hr.mb3.bw4.rounded - .mb1.bold - = t('users.delete.subheading') - - ul.px2.yellow-dots - li.mb1 = t('users.delete.bullet_1', app: APP_NAME) - li.mb1 = current_user.decorate.delete_account_bullet_key - li.mb1 = t('users.delete.bullet_3', app: APP_NAME) - .center - = button_to t('users.delete.actions.delete'), account_delete_path, - class: 'btn btn-primary col-12 mb2 p2 rounded-lg', method: 'delete' - - = link_to t('users.delete.actions.cancel'), account_path, - role: 'button', - class: 'btn col-12 p2 rounded-lg border border-blue blue border-box' diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml index 29f25e08c0a..ce6f9b7068c 100644 --- a/config/locales/doc_auth/en.yml +++ b/config/locales/doc_auth/en.yml @@ -8,6 +8,8 @@ en: start_over: Start over take_picture: Take photo take_picture_retry: Retake photo + upload_picture: Upload photo + upload_picture_retry: Re-upload photo use_phone: Use your phone forms: address1: Address diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml index fa31b60c9bf..4797ea2a45c 100644 --- a/config/locales/doc_auth/es.yml +++ b/config/locales/doc_auth/es.yml @@ -8,6 +8,8 @@ es: start_over: Comenzar de nuevo take_picture: Tomar la foto take_picture_retry: Retomar foto + upload_picture: Subir foto + upload_picture_retry: Volver a subir la foto use_phone: Usa tu telefono forms: address1: Dirección diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml index f4c68e77c47..9bacb3b1c09 100644 --- a/config/locales/doc_auth/fr.yml +++ b/config/locales/doc_auth/fr.yml @@ -8,6 +8,8 @@ fr: start_over: Recommencer take_picture: Prendre une photo take_picture_retry: Reprendre la photo + upload_picture: Envoyer la photo + upload_picture_retry: Re-télécharger la photo use_phone: Utilisez votre téléphone forms: address1: Adresse diff --git a/config/locales/users/en.yml b/config/locales/users/en.yml index 47f05ae286a..95640baeb6a 100644 --- a/config/locales/users/en.yml +++ b/config/locales/users/en.yml @@ -13,6 +13,7 @@ en: name, address, date of birth and Social Security number from our system." bullet_3: You won't be able to securely access your information using %{app}. heading: Are you sure you want to delete your account? + instructions: Enter your password to confirm that you want to delete your account. subheading: If you delete your account personal_key: close: Close diff --git a/config/locales/users/es.yml b/config/locales/users/es.yml index 680a520e8df..6a1674cfefc 100644 --- a/config/locales/users/es.yml +++ b/config/locales/users/es.yml @@ -14,6 +14,7 @@ es: dirección, fecha de nacimiento y número de Seguro Social de nuestro sistema." bullet_3: Usted no podrá tener acceso seguro a su información usando %{app} heading: "¿Está seguro que desea eliminar su cuenta?" + instructions: Ingrese su contraseña para confirmar que desea eliminar su cuenta. subheading: Si elimina su cuenta personal_key: close: Cerrar diff --git a/config/locales/users/fr.yml b/config/locales/users/fr.yml index 42dc3968c74..53dbcd73a58 100644 --- a/config/locales/users/fr.yml +++ b/config/locales/users/fr.yml @@ -16,6 +16,8 @@ fr: bullet_3: Vous ne pourrez pas accéder en toute sécurité à vos informations en utilisant %{app}. heading: Êtes-vous sûr de vouloir supprimer votre compte? + instructions: Saisissez votre mot de passe pour confirmer que vous souhaitez + supprimer votre compte. subheading: Si vous supprimez votre compte personal_key: close: Fermer diff --git a/config/routes.rb b/config/routes.rb index 9620716cd2f..ba200a6f61e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -146,7 +146,7 @@ get '/account' => 'accounts#show' get '/account/devices/:id/events' => 'events#show', as: :account_events get '/account/delete' => 'users/delete#show', as: :account_delete - delete '/account/delete' => 'users/delete#delete' + post '/account/delete' => 'users/delete#delete' get '/account/reactivate/start' => 'reactivate_account#index', as: :reactivate_account put '/account/reactivate/start' => 'reactivate_account#update' get '/account/reactivate/verify_password' => 'users/verify_password#new', as: :verify_password diff --git a/spec/controllers/users/delete_controller_spec.rb b/spec/controllers/users/delete_controller_spec.rb index 5cfa25fbd1e..3f64933108d 100644 --- a/spec/controllers/users/delete_controller_spec.rb +++ b/spec/controllers/users/delete_controller_spec.rb @@ -4,8 +4,13 @@ include Features::MailerHelper describe '#show' do - it 'shows' do + it 'shows and logs a visit' do + stub_analytics stub_signed_in_user + + expect(@analytics).to receive(:track_event). + with(Analytics::ACCOUNT_DELETE_VISITED) + get :show expect(response).to render_template(:show) @@ -13,19 +18,58 @@ end describe '#delete' do + let(:password) { ControllerHelper::VALID_PASSWORD } + subject(:delete) { post :delete, params: { user: { password: password } } } + + context 'with an incorrect password' do + let(:password) { 'wrong' } + + it 'flashes a banner and renders the form' do + stub_signed_in_user + delete + expect(response).to render_template(:show) + expect(flash[:error]).to eq(t('idv.errors.incorrect_password')) + end + + it 'does not delete the user' do + stub_signed_in_user + expect { delete }.to_not change(User, :count) + end + + it 'logs a failed submit' do + stub_analytics + stub_signed_in_user + + expect(@analytics).to receive(:track_event). + with(Analytics::ACCOUNT_DELETE_SUBMITTED, success: false) + + delete + end + end + it 'redirects to the root path' do stub_signed_in_user - post :delete + delete expect(response).to redirect_to root_url end it 'deletes user' do user = stub_signed_in_user expect(User.where(id: user.id).length).to eq(1) - post :delete + delete expect(User.where(id: user.id).length).to eq(0) end + it 'logs a succesful submit' do + stub_analytics + stub_signed_in_user + + expect(@analytics).to receive(:track_event). + with(Analytics::ACCOUNT_DELETE_SUBMITTED, success: true) + + delete + end + it 'does not delete identities to prevent uuid reuse' do user = stub_signed_in_user user.identities << Identity.create( @@ -33,21 +77,24 @@ last_authenticated_at: Time.zone.now, ) expect(Identity.where(user_id: user.id).length).to eq(1) - post :delete + delete expect(Identity.where(user_id: user.id).length).to eq(1) end it 'deletes profile information for ial2' do - profile = create(:profile, :active, :verified, pii: { ssn: '1234', dob: '1920-01-01' }) - stub_sign_in(profile.user) + user = stub_sign_in + create(:profile, :active, :verified, user: user, pii: { ssn: '1234', dob: '1920-01-01' }) expect(Profile.count).to eq(1) - post :delete + delete expect(Profile.count).to eq(0) end end def stub_signed_in_user - user = create(:user, :signed_up, email: 'old_email@example.com') + user = create(:user, + :signed_up, + email: 'old_email@example.com', + password: ControllerHelper::VALID_PASSWORD) stub_sign_in(user) end end diff --git a/spec/features/two_factor_authentication/change_factor_spec.rb b/spec/features/two_factor_authentication/change_factor_spec.rb index 73bee409704..fd35eb55fc6 100644 --- a/spec/features/two_factor_authentication/change_factor_spec.rb +++ b/spec/features/two_factor_authentication/change_factor_spec.rb @@ -49,15 +49,6 @@ end end end - - scenario 'deleting account' do - visit account_delete_path - - expect(page).to have_content t('help_text.no_factor.delete_account') - complete_2fa_confirmation - - expect(current_path).to eq account_delete_path - end end def complete_2fa_confirmation diff --git a/spec/features/users/user_profile_spec.rb b/spec/features/users/user_profile_spec.rb index bfb99b42b71..0b00385f4c7 100644 --- a/spec/features/users/user_profile_spec.rb +++ b/spec/features/users/user_profile_spec.rb @@ -31,6 +31,7 @@ expect(User.count).to eq 1 expect(AgencyIdentity.count).to eq 1 + fill_in(t('idv.form.password'), with: Features::SessionHelper::VALID_PASSWORD) click_button t('users.delete.actions.delete') expect(page).to have_content t('devise.registrations.destroyed') expect(current_path).to eq root_path @@ -59,6 +60,7 @@ }, ) + fill_in(t('idv.form.password'), with: Features::SessionHelper::VALID_PASSWORD) click_button t('users.delete.actions.delete') expect(request).to have_been_requested end @@ -74,6 +76,7 @@ expect(User.count).to eq 1 expect(Profile.count).to eq 1 + fill_in(t('idv.form.password'), with: profile.user.password) click_button t('users.delete.actions.delete') expect(page).to have_content t('devise.registrations.destroyed') expect(current_path).to eq root_path @@ -89,6 +92,7 @@ sign_in_live_with_2fa(profile.user) visit account_path click_link(t('account.links.delete_account'), href: account_delete_path) + fill_in(t('idv.form.password'), with: profile.user.password) click_button t('users.delete.actions.delete') expect(User.count).to eq 0 diff --git a/spec/views/users/delete/show.html.slim_spec.rb b/spec/views/users/delete/show.html.erb_spec.rb similarity index 97% rename from spec/views/users/delete/show.html.slim_spec.rb rename to spec/views/users/delete/show.html.erb_spec.rb index aa37b1b4480..46f8f24d671 100644 --- a/spec/views/users/delete/show.html.slim_spec.rb +++ b/spec/views/users/delete/show.html.erb_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'users/delete/show.html.slim' do +describe 'users/delete/show.html.erb' do let(:user) { build_stubbed(:user, :signed_up) } let(:decorated_user) { user.decorate }