Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
434136b
WIP: account reset disavow
slj May 20, 2020
6849f7c
Cleanup
slj May 20, 2020
e57ed10
More cleanup
slj May 20, 2020
f82b753
Updated pending controller
slj May 22, 2020
3447a51
CSS tweak
slj May 22, 2020
9c406ff
replace pending account reset cancel email with existing cancel email
jmhooper May 22, 2020
43dd744
quick cleanup
jmhooper May 22, 2020
d1389dd
Moved email to private method
slj May 22, 2020
da6b5f0
Add a service for finding pending account reset requets
jmhooper May 22, 2020
60c11e2
Use the find pending request service
jmhooper May 22, 2020
ea94b67
Add a cancel request service
jmhooper May 22, 2020
f095306
Merge branch 'slj-account-reset-disavow' of github.com:18F/identity-i…
jmhooper May 22, 2020
bcd7b8f
Move cancelled notification into a service
jmhooper May 22, 2020
af0edc3
start writing controller specs
jmhooper May 22, 2020
e21e8ed
cleanup pending presenter
jmhooper May 22, 2020
b073f94
fix lint issue
jmhooper May 22, 2020
98a100e
cleanup the template
jmhooper May 22, 2020
3a7a3d9
fix missing space
jmhooper May 22, 2020
1e727a0
add a feature spec
jmhooper May 22, 2020
bef8d7f
fix a syntax error
jmhooper May 22, 2020
cbda31d
Add controller spec
jmhooper May 22, 2020
1c2afc7
fix i18n
jmhooper May 22, 2020
3c84626
Merge branch 'master' into slj-account-reset-disavow
slj May 29, 2020
eb382b2
Default param, cleaner locales, fix typo/spec
slj May 29, 2020
d38db4d
Fix merge conflicts
slj May 29, 2020
1575839
Rubocop fixes
slj May 29, 2020
79b52d7
Fix YAML
slj May 29, 2020
055116c
Merge branch 'master' into slj-account-reset-disavow
slj Jun 3, 2020
0c95703
Added cancel confirmation interstitial
slj Jun 3, 2020
825100b
Fixed spec
slj Jun 3, 2020
5dc68e5
Fixed another spec
slj Jun 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/assets/stylesheets/email.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
}

.hr { margin: $hr-margin; }
.lead { margin-bottom: 30px; }

.lead {
hyphens: none;
margin-bottom: 30px;
}

.mr-tiny { margin-right: 4px; }

.s10 {
Expand Down
31 changes: 31 additions & 0 deletions app/controllers/account_reset/pending_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module AccountReset
class PendingController < ApplicationController
include UserAuthenticator

before_action :authenticate_user
before_action :confirm_account_reset_request_exists

def show
analytics.track_event(Analytics::PENDING_ACCOUNT_RESET_VISITED)
@pending_presenter = AccountReset::PendingPresenter.new(pending_account_reset_request)
end

def cancel
analytics.track_event(Analytics::PENDING_ACCOUNT_RESET_CANCELLED)
AccountReset::CancelRequestForUser.new(current_user).call
redirect_to user_two_factor_authentication_url
end

private

def confirm_account_reset_request_exists
render_not_found if pending_account_reset_request.blank?
end

def pending_account_reset_request
@account_reset_request ||= AccountReset::FindPendingRequestForUser.new(
current_user,
).call
end
end
end
16 changes: 15 additions & 1 deletion app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def handle_valid_authentication
add_sp_cost(:digest)
create_user_event(:sign_in_before_2fa)
update_last_sign_in_at_on_email
redirect_to user_two_factor_authentication_url
redirect_to_2fa_or_pending_reset
end

def now
Expand Down Expand Up @@ -161,5 +161,19 @@ def request_id
def sp_session_ial
sp_session[:ial2] ? 2 : 1
end

def redirect_to_2fa_or_pending_reset
if pending_account_reset_request.present?
redirect_to account_reset_pending_url
else
redirect_to user_two_factor_authentication_url
end
end

def pending_account_reset_request
AccountReset::FindPendingRequestForUser.new(
current_user,
).call
end
end
end
21 changes: 21 additions & 0 deletions app/presenters/account_reset/pending_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module AccountReset
class PendingPresenter
include ActionView::Helpers::DateHelper

attr_reader :account_reset_request

def initialize(account_reset_request)
@account_reset_request = account_reset_request
end

def time_remaining_until_granted(now: Time.zone.now)
distance_of_time_in_words(
now,
account_reset_request.requested_at + Figaro.env.account_reset_wait_period_days.to_i.days,
true,
highest_measures: 2,
two_words_connector: " #{I18n.t('datetime.dotiw.two_words_connector')} ",
)
end
end
end
17 changes: 1 addition & 16 deletions app/services/account_reset/cancel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ def call
@success = valid?

if success
notify_user_via_email_of_account_reset_cancellation
notify_user_via_phone_of_account_reset_cancellation if phone.present?
AccountReset::NotifyUserOfRequestCancellation.new(user).call
update_account_reset_request
end

Expand All @@ -23,16 +22,6 @@ def call

attr_reader :success, :token

def notify_user_via_email_of_account_reset_cancellation
user.confirmed_email_addresses.each do |email_address|
UserMailer.account_reset_cancel(email_address).deliver_later
end
end

def notify_user_via_phone_of_account_reset_cancellation
Telephony.send_account_reset_cancellation_notice(to: phone)
end

def update_account_reset_request
account_reset_request.update!(cancelled_at: Time.zone.now,
request_token: nil,
Expand All @@ -43,10 +32,6 @@ def user
account_reset_request&.user || AnonymousUser.new
end

def phone
MfaContext.new(user).phone_configurations.take&.phone
end

def extra_analytics_attributes
{
event: 'cancel',
Expand Down
20 changes: 20 additions & 0 deletions app/services/account_reset/cancel_request_for_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module AccountReset
class CancelRequestForUser
attr_reader :user

def initialize(user)
@user = user
end

def call(now: Time.zone.now)
account_reset_request.update!(cancelled_at: now)
NotifyUserOfRequestCancellation.new(user).call
end

private

def account_reset_request
FindPendingRequestForUser.new(user).call
end
end
end
20 changes: 20 additions & 0 deletions app/services/account_reset/find_pending_request_for_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module AccountReset
class FindPendingRequestForUser
attr_reader :user

def initialize(user)
@user = user
end

def call
AccountResetRequest.where(
user: user,
granted_at: nil,
cancelled_at: nil,
).where(
'requested_at > ?',
Figaro.env.account_reset_wait_period_days.to_i.days.ago,
).order(requested_at: :asc).first
end
end
end
28 changes: 28 additions & 0 deletions app/services/account_reset/notify_user_of_request_cancellation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module AccountReset
class NotifyUserOfRequestCancellation
attr_reader :user

def initialize(user)
@user = user
end

def call
notify_user_via_email_of_account_reset_cancellation
notify_user_via_phone_of_account_reset_cancellation
end

private

def notify_user_via_email_of_account_reset_cancellation
user.confirmed_email_addresses.each do |email_address|
UserMailer.account_reset_cancel(email_address).deliver_later
end
end

def notify_user_via_phone_of_account_reset_cancellation
MfaContext.new(user).phone_configurations.each do |phone_configuration|
Telephony.send_account_reset_cancellation_notice(to: phone_configuration.phone)
end
end
end
end
2 changes: 2 additions & 0 deletions app/services/analytics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ def browser_attributes
PASSWORD_RESET_PASSWORD = 'Password Reset: Password Submitted'.freeze
PASSWORD_RESET_TOKEN = 'Password Reset: Token Submitted'.freeze
PASSWORD_RESET_VISIT = 'Password Reset: Email Form Visited'.freeze
PENDING_ACCOUNT_RESET_CANCELLED = 'Pending account reset cancelled'.freeze
PENDING_ACCOUNT_RESET_VISITED = 'Pending account reset visited'.freeze
PERSONAL_KEY_REACTIVATION = 'Personal key reactivation: Account reactivated with personal key'.freeze
PERSONAL_KEY_REACTIVATION_SIGN_IN = 'Personal key reactivation: Account reactivated with personal key as MFA'.freeze
PERSONAL_KEY_REACTIVATION_SUBMITTED = 'Personal key reactivation: Personal key form submitted'.freeze
Expand Down
22 changes: 22 additions & 0 deletions app/views/account_reset/pending/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<h1 class="h3 my0">
<%= t('account_reset.pending.header') %>
</h1>

<p>
<%= t(
'account_reset.pending.delete_and_recreate_html',
interval: @pending_presenter.time_remaining_until_granted,
) %>
</p>

<p>
<%= t(
'account_reset.pending.cancel_html',
click_here: button_to(
t('account_reset.pending.click_here'),
account_reset_pending_cancel_path,
class: 'btn btn-link',
form_class: 'display-inline',
),
) %>
</p>
6 changes: 6 additions & 0 deletions config/locales/account_reset/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ en:
your account. Once your account is deleted, you can create a new one using
the same email address.
title: Deleting your account should be your last resort
pending:
cancel_html: "%{click_here} to cancel your account reset request."
click_here: Click here
delete_and_recreate_html: In <strong>%{interval}</strong> you will be able to
delete and recreate your account.
header: You have requested an account reset
request:
access_your_account: access your account
are_you_sure: Are you sure you don't have access to any of your authentication
Expand Down
27 changes: 17 additions & 10 deletions config/locales/account_reset/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,28 @@ es:
a su registro número de teléfono.
delete_account:
are_you_sure: "¿Seguro que quieres eliminar tu cuenta?"
info: Eliminar su cuenta debe ser su último recurso si está bloqueado         
de tu cuenta No podrá recuperar ninguna información vinculada a su cuenta.
Una vez que se elimine su cuenta, puede crear una nueva usando la misma dirección
info: Eliminar su cuenta debe ser su último recurso si está bloqueado de tu
cuenta No podrá recuperar ninguna información vinculada a su cuenta. Una vez
que se elimine su cuenta, puede crear una nueva usando la misma dirección
de correo electrónico.
title: Eliminar tu cuenta debería ser tu último recurso
pending:
cancel_html: "%{click_here} para cancelar su solicitud de restablecimiento de
cuenta."
click_here: Haga clic aquí
delete_and_recreate_html: En <strong>%{interval}</strong> podrá eliminar y volver
a crear su cuenta.
header: Has solicitado un restablecimiento de cuenta
request:
access_your_account: acceder a tu cuenta
are_you_sure: "¿Estás seguro de que no tienes acceso a ninguno de tus métodos
de seguridad?"
delete_account: Eliminar su cuenta
delete_account_info: Eliminar su cuenta existente y crear una nueva         
le permite usar la misma dirección de correo electrónico y configurar nuevas
opciones de seguridad. Sin embargo, eliminar eliminará cualquier aplicación
de agencia que haya vinculado a su cuenta y deberá restaurar cada conexión.
<br><br> Si continúas, tú primero recibirá una confirmación por correo electrónico.
delete_account_info: Eliminar su cuenta existente y crear una nueva le permite
usar la misma dirección de correo electrónico y configurar nuevas opciones
de seguridad. Sin embargo, eliminar eliminará cualquier aplicación de agencia
que haya vinculado a su cuenta y deberá restaurar cada conexión. <br><br>
Si continúas, tú primero recibirá una confirmación por correo electrónico.
Como medida de seguridad, lo hará reciba otro correo electrónico con el enlace
para seguir eliminando su cuenta las 24 horas después del correo electrónico
de confirmación inicial llega.
Expand All @@ -49,7 +56,7 @@ es:
personal_key_info: Su clave personal es un código de 16 caracteres que se le
dio a en la creación de la cuenta como método de recuperación; consulte el
ejemplo a continuación.
personal_key_trailer: Si tiene su clave personal, puede usarla en %{link}         
en lugar de restablecer.
personal_key_trailer: Si tiene su clave personal, puede usarla en %{link} en
lugar de restablecer.
title: Eliminación y restablecimiento de cuenta
yes_continue: Sí, continúa la eliminación.
7 changes: 7 additions & 0 deletions config/locales/account_reset/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ fr:
liées à ton compte. Une fois votre compte supprimé, vous pouvez en créer un
nouveau en utilisant la même adresse e-mail.
title: La suppression de votre compte devrait être votre dernier recours
pending:
cancel_html: "%{click_here} pour annuler votre demande de réinitialisation de
compte."
click_here: Cliquez ici
delete_and_recreate_html: En <strong>%{interval}</strong> vous pourrez supprimer
et recréer votre compte.
header: Vous avez demandé une réinitialisation de compte
request:
access_your_account: accéder à votre compte
are_you_sure: Êtes-vous sûr de n'avoir accès à aucune de vos méthodes de sécurité?
Expand Down
2 changes: 1 addition & 1 deletion config/locales/dotiw/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
en:
datetime:
dotiw:
two_words_connector: and
two_words_connector: "and"
2 changes: 1 addition & 1 deletion config/locales/dotiw/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
es:
datetime:
dotiw:
two_words_connector: y
two_words_connector: "y"
2 changes: 1 addition & 1 deletion config/locales/dotiw/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
fr:
datetime:
dotiw:
two_words_connector: et
two_words_connector: "et"
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
get '/account_reset/delete_account' => 'account_reset/delete_account#show'
delete '/account_reset/delete_account' => 'account_reset/delete_account#delete'
get '/account_reset/confirm_delete_account' => 'account_reset/confirm_delete_account#show'
get '/account_reset/pending' => 'account_reset/pending#show'
post '/account_reset/pending/cancel' => 'account_reset/pending#cancel'

get '/login/two_factor/options' => 'two_factor_authentication/options#index'
post '/login/two_factor/options' => 'two_factor_authentication/options#create'
Expand Down
38 changes: 38 additions & 0 deletions spec/controllers/account_reset/pending_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require 'rails_helper'

describe AccountReset::PendingController do
let(:user) { create(:user) }

before do
sign_in user
end

describe '#show' do
context 'when the account reset request does not exist' do
it 'renders a 404' do
get :show

expect(response).to render_template('pages/page_not_found')
end
end
end

describe '#cancel' do
it 'cancels the account reset request' do
account_reset_request = AccountResetRequest.create(user: user, requested_at: 1.hour.ago)

post :cancel

expect(account_reset_request.reload.cancelled_at).to_not be_nil
expect(response).to redirect_to(user_two_factor_authentication_url)
end

context 'when the account reset request does not exist' do
it 'renders a 404' do
post :cancel

expect(response).to render_template('pages/page_not_found')
end
end
end
end
Loading