diff --git a/app/controllers/account_reset/pending_controller.rb b/app/controllers/account_reset/pending_controller.rb
index 855a6e12452..d1a713ea934 100644
--- a/app/controllers/account_reset/pending_controller.rb
+++ b/app/controllers/account_reset/pending_controller.rb
@@ -1,7 +1,7 @@
module AccountReset
class PendingController < ApplicationController
include UserAuthenticator
- include ActionView::Helpers::DateHelper
+ include AccountResetConcern
before_action :authenticate_user
before_action :confirm_account_reset_request_exists
@@ -12,7 +12,7 @@ def show
end
def confirm
- @account_reset_deletion_period_interval = account_reset_deletion_period_interval
+ @account_reset_deletion_period_interval = account_reset_deletion_period_interval(current_user)
end
def cancel
@@ -32,16 +32,5 @@ def pending_account_reset_request
current_user,
).call
end
-
- def account_reset_deletion_period_interval
- current_time = Time.zone.now
-
- distance_of_time_in_words(
- current_time,
- current_time + IdentityConfig.store.account_reset_wait_period_days.days,
- true,
- accumulate_on: :hours,
- )
- end
end
end
diff --git a/app/controllers/account_reset/request_controller.rb b/app/controllers/account_reset/request_controller.rb
index afc55e4df98..7f51aea7740 100644
--- a/app/controllers/account_reset/request_controller.rb
+++ b/app/controllers/account_reset/request_controller.rb
@@ -1,13 +1,13 @@
module AccountReset
class RequestController < ApplicationController
include TwoFactorAuthenticatable
- include ActionView::Helpers::DateHelper
+ include AccountResetConcern
before_action :confirm_two_factor_enabled
def show
analytics.account_reset_visit
- @account_reset_deletion_period_interval = account_reset_deletion_period_interval
+ @account_reset_deletion_period_interval = account_reset_deletion_period_interval(current_user)
end
def create
@@ -41,16 +41,5 @@ def analytics_attributes
email_addresses: current_user.email_addresses.count,
}
end
-
- def account_reset_deletion_period_interval
- current_time = Time.zone.now
-
- distance_of_time_in_words(
- current_time,
- current_time + IdentityConfig.store.account_reset_wait_period_days.days,
- true,
- accumulate_on: :hours,
- )
- end
end
end
diff --git a/app/controllers/concerns/account_reset_concern.rb b/app/controllers/concerns/account_reset_concern.rb
new file mode 100644
index 00000000000..15457c8fb87
--- /dev/null
+++ b/app/controllers/concerns/account_reset_concern.rb
@@ -0,0 +1,38 @@
+module AccountResetConcern
+ include ActionView::Helpers::DateHelper
+ def account_reset_deletion_period_interval(user)
+ current_time = Time.zone.now
+
+ distance_of_time_in_words(
+ current_time,
+ current_time + account_reset_wait_period_days(user),
+ true,
+ accumulate_on: reset_accumulation_type(user),
+ )
+ end
+
+ def account_reset_wait_period_days(user)
+ if supports_fraud_account_reset?(user)
+ IdentityConfig.store.account_reset_fraud_user_wait_period_days.days
+ else
+ IdentityConfig.store.account_reset_wait_period_days.days
+ end
+ end
+
+ def supports_fraud_account_reset?(user)
+ IdentityConfig.store.account_reset_fraud_user_wait_period_days.present? &&
+ fraud_state?(user)
+ end
+
+ def fraud_state?(user)
+ user.fraud_review_pending? || user.fraud_rejection?
+ end
+
+ def reset_accumulation_type(user)
+ if account_reset_wait_period_days(user) > 3.days
+ :days
+ else
+ :hours
+ end
+ end
+end
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 19ff2a46794..5027083feb7 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -15,6 +15,7 @@
class UserMailer < ActionMailer::Base
include Mailable
include LocaleHelper
+ include AccountResetConcern
include ActionView::Helpers::DateHelper
class UserEmailAddressMismatchError < StandardError; end
@@ -144,10 +145,10 @@ def personal_key_regenerated
def account_reset_request(account_reset)
with_user_locale(user) do
@token = account_reset&.request_token
- @account_reset_deletion_period_hours = account_reset_deletion_period_hours
+ @account_reset_deletion_period_interval = account_reset_deletion_period_interval(user)
@header = t(
'user_mailer.account_reset_request.header',
- interval: account_reset_deletion_period_interval,
+ interval: @account_reset_deletion_period_interval,
)
mail(
to: email_address.email,
@@ -160,7 +161,7 @@ def account_reset_granted(account_reset)
with_user_locale(user) do
@token = account_reset&.request_token
@granted_token = account_reset&.granted_token
- @account_reset_deletion_period_hours = account_reset_deletion_period_hours
+ @account_reset_deletion_period_interval = account_reset_deletion_period_interval(user)
@account_reset_token_valid_period = account_reset_token_valid_period
mail(
to: email_address.email,
@@ -431,21 +432,6 @@ def account_reinstated
private
- def account_reset_deletion_period_interval
- current_time = Time.zone.now
-
- distance_of_time_in_words(
- current_time,
- current_time + IdentityConfig.store.account_reset_wait_period_days.days,
- true,
- accumulate_on: :hours,
- )
- end
-
- def account_reset_deletion_period_hours
- IdentityConfig.store.account_reset_wait_period_days.days.in_hours.to_i
- end
-
def account_reset_token_valid_period
current_time = Time.zone.now
diff --git a/app/presenters/account_reset/pending_presenter.rb b/app/presenters/account_reset/pending_presenter.rb
index 636b7bcd081..3325adec06d 100644
--- a/app/presenters/account_reset/pending_presenter.rb
+++ b/app/presenters/account_reset/pending_presenter.rb
@@ -1,5 +1,6 @@
module AccountReset
class PendingPresenter
+ include AccountResetConcern
include ActionView::Helpers::DateHelper
attr_reader :account_reset_request
@@ -9,7 +10,7 @@ def initialize(account_reset_request)
end
def time_remaining_until_granted(now: Time.zone.now)
- wait_time = IdentityConfig.store.account_reset_wait_period_days.days
+ wait_time = account_reset_wait_period_days(user)
distance_of_time_in_words(
now,
@@ -19,8 +20,12 @@ def time_remaining_until_granted(now: Time.zone.now)
)
end
- def account_reset_deletion_period_hours
- IdentityConfig.store.account_reset_wait_period_days.days.in_hours.to_i
+ def account_reset_deletion_period
+ account_reset_deletion_period_interval(user)
+ end
+
+ def user
+ account_reset_request.user
end
end
end
diff --git a/app/presenters/two_factor_login_options_presenter.rb b/app/presenters/two_factor_login_options_presenter.rb
index 0d9556a4168..cff2c81bfb0 100644
--- a/app/presenters/two_factor_login_options_presenter.rb
+++ b/app/presenters/two_factor_login_options_presenter.rb
@@ -1,4 +1,5 @@
class TwoFactorLoginOptionsPresenter < TwoFactorAuthCode::GenericDeliveryPresenter
+ include AccountResetConcern
include ActionView::Helpers::TranslationHelper
attr_reader :user, :reauthentication_context, :phishing_resistant_required, :piv_cac_required
@@ -117,7 +118,7 @@ def account_reset_cancel_link
[
t(
'two_factor_authentication.account_reset.pending',
- interval: account_reset_deletion_period_interval,
+ interval: account_reset_deletion_period_interval(user),
),
@view.link_to(
t('two_factor_authentication.account_reset.cancel_link'),
@@ -143,15 +144,4 @@ def sp_name
APP_NAME
end
end
-
- def account_reset_deletion_period_interval
- current_time = Time.zone.now
-
- view.distance_of_time_in_words(
- current_time,
- current_time + IdentityConfig.store.account_reset_wait_period_days.days,
- true,
- accumulate_on: :hours,
- )
- end
end
diff --git a/app/services/account_reset/create_request.rb b/app/services/account_reset/create_request.rb
index bbe2d05de2b..61da2996239 100644
--- a/app/services/account_reset/create_request.rb
+++ b/app/services/account_reset/create_request.rb
@@ -1,5 +1,6 @@
module AccountReset
class CreateRequest
+ include AccountResetConcern
include ActionView::Helpers::DateHelper
def initialize(user, requesting_issuer)
@@ -48,23 +49,12 @@ def notify_user_by_sms_if_applicable
@telephony_response = Telephony.send_account_reset_notice(
to: phone,
country_code: Phonelib.parse(phone).country,
- interval: account_reset_wait_period,
+ interval: account_reset_deletion_period_interval(user),
)
end
def extra_analytics_attributes
@telephony_response&.extra&.slice(:request_id, :message_id) || {}
end
-
- def account_reset_wait_period
- current_time = Time.zone.now
-
- distance_of_time_in_words(
- current_time,
- current_time + IdentityConfig.store.account_reset_wait_period_days,
- true,
- accumulate_on: :hours,
- )
- end
end
end
diff --git a/app/services/account_reset/find_pending_request_for_user.rb b/app/services/account_reset/find_pending_request_for_user.rb
index e8d27db2f04..79ca655beba 100644
--- a/app/services/account_reset/find_pending_request_for_user.rb
+++ b/app/services/account_reset/find_pending_request_for_user.rb
@@ -1,5 +1,6 @@
module AccountReset
class FindPendingRequestForUser
+ include AccountResetConcern
attr_reader :user
def initialize(user)
@@ -13,7 +14,7 @@ def call
cancelled_at: nil,
).where(
'requested_at > ?',
- IdentityConfig.store.account_reset_wait_period_days.days.ago,
+ account_reset_wait_period_days(user).ago,
).order(requested_at: :asc).first
end
end
diff --git a/app/services/account_reset/grant_request.rb b/app/services/account_reset/grant_request.rb
index 0598458378b..0dec3f3d27f 100644
--- a/app/services/account_reset/grant_request.rb
+++ b/app/services/account_reset/grant_request.rb
@@ -7,9 +7,10 @@ def initialize(user)
def call
token = SecureRandom.uuid
arr = AccountResetRequest.find_by(user_id: @user_id)
+ return false if fraud_user?(arr) && fraud_wait_period_not_met?(arr)
result = arr.with_lock do
if !arr.granted_token_valid?
- account_reset_request.update(
+ arr.update(
granted_at: Time.zone.now,
granted_token: token,
)
@@ -21,8 +22,21 @@ def call
private
- def account_reset_request
- AccountResetRequest.create_or_find_by(user_id: @user_id)
+ def fraud_user?(arr)
+ arr.user.fraud_review_pending? ||
+ arr.user.fraud_rejection?
+ end
+
+ def fraud_wait_period_not_met?(arr)
+ if fraud_wait_period_days.present?
+ return arr.requested_at > (Time.zone.now - fraud_wait_period_days.days)
+ else
+ false
+ end
+ end
+
+ def fraud_wait_period_days
+ IdentityConfig.store.account_reset_fraud_user_wait_period_days
end
end
end
diff --git a/app/views/account_reset/pending/show.html.erb b/app/views/account_reset/pending/show.html.erb
index ceb730fe1fc..970283d4574 100644
--- a/app/views/account_reset/pending/show.html.erb
+++ b/app/views/account_reset/pending/show.html.erb
@@ -5,7 +5,7 @@
<%= t(
'account_reset.pending.wait_html',
- hours: @pending_presenter.account_reset_deletion_period_hours,
+ waiting_period: @pending_presenter.account_reset_deletion_period,
interval: @pending_presenter.time_remaining_until_granted,
) %>
diff --git a/app/views/user_mailer/account_reset_granted.html.erb b/app/views/user_mailer/account_reset_granted.html.erb
index 70d608be1d9..4da0fcc5ec8 100644
--- a/app/views/user_mailer/account_reset_granted.html.erb
+++ b/app/views/user_mailer/account_reset_granted.html.erb
@@ -1,5 +1,5 @@
- <%= t('user_mailer.account_reset_granted.intro_html', hours: @account_reset_deletion_period_hours, app_name: link_to(APP_NAME, IdentityConfig.store.mailer_domain_name, class: 'gray')) %>
+ <%= t('user_mailer.account_reset_granted.intro_html', waiting_period: @account_reset_deletion_period_interval, app_name: link_to(APP_NAME, IdentityConfig.store.mailer_domain_name, class: 'gray')) %>
diff --git a/app/views/user_mailer/account_reset_request.html.erb b/app/views/user_mailer/account_reset_request.html.erb
index d486d654efc..932fcfad3a5 100644
--- a/app/views/user_mailer/account_reset_request.html.erb
+++ b/app/views/user_mailer/account_reset_request.html.erb
@@ -1,5 +1,5 @@
- <%= t('user_mailer.account_reset_request.intro_html', app_name: link_to(APP_NAME, IdentityConfig.store.mailer_domain_name, class: 'gray'), hours: @account_reset_deletion_period_hours) %>
+ <%= t('user_mailer.account_reset_request.intro_html', app_name: link_to(APP_NAME, IdentityConfig.store.mailer_domain_name, class: 'gray'), waiting_period: @account_reset_deletion_period_interval) %>
diff --git a/config/application.yml.default b/config/application.yml.default
index 865bba115a4..b188461db5c 100644
--- a/config/application.yml.default
+++ b/config/application.yml.default
@@ -24,6 +24,7 @@ all_redirect_uris_cache_duration_minutes: 2
allowed_ialmax_providers: '[]'
allowed_verified_within_providers: '[]'
account_reset_token_valid_for_days: 1
+account_reset_fraud_user_wait_period_days:
account_reset_wait_period_days: 1
account_suspended_support_code: EFGHI
acuant_assure_id_password: ''
@@ -509,6 +510,7 @@ production:
test:
aamva_private_key: 123abc
aamva_public_key: 123abc
+ account_reset_fraud_user_wait_period_days: 30
acuant_assure_id_url: https://example.com
acuant_facial_match_url: https://facial_match.example.com
acuant_passlive_url: https://liveness.example.com
diff --git a/config/locales/account_reset/en.yml b/config/locales/account_reset/en.yml
index f3fe9cf0d60..ea9493777fd 100644
--- a/config/locales/account_reset/en.yml
+++ b/config/locales/account_reset/en.yml
@@ -33,8 +33,8 @@ en:
confirm: If you cancel now, you must create a new request and wait another
%{interval} to delete your account.
header: You requested to delete your account
- wait_html: There is a %{hours}-hour waiting period to delete your account. In
- %{interval}, you will receive an email with
+ wait_html: There is a waiting period of %{waiting_period} to delete your
+ account. In %{interval}, you will receive an email with
instructions to complete the deletion.
recovery_options:
check_saved_credential: See if you have a saved credential
diff --git a/config/locales/account_reset/es.yml b/config/locales/account_reset/es.yml
index 9f8f1dc4317..3cd6eb93a1d 100644
--- a/config/locales/account_reset/es.yml
+++ b/config/locales/account_reset/es.yml
@@ -34,9 +34,9 @@ es:
confirm: Si cancela ahora, debe crear una nueva solicitud y esperar otras
%{interval} para eliminar su cuenta.
header: Solicitaste eliminar tu cuenta
- wait_html: Hay un período de espera de %{hours} horas para eliminar su cuenta.
- En %{interval}, recibirá un correo electrónico con
- instrucciones para completar la eliminación.
+ wait_html: Hay un período de espera de %{waiting_period} para eliminar su
+ cuenta. En %{interval}, recibirá un correo electrónico
+ con instrucciones para completar la eliminación.
recovery_options:
check_saved_credential: Verifica si tienes una credencial almacenada
check_webauthn_platform_info: Si has habilitado el desbloqueo facial o táctil,
diff --git a/config/locales/account_reset/fr.yml b/config/locales/account_reset/fr.yml
index cd52a50fa08..f26afe7d5e0 100644
--- a/config/locales/account_reset/fr.yml
+++ b/config/locales/account_reset/fr.yml
@@ -34,7 +34,7 @@ fr:
confirm: Si vous annulez maintenant, vous devez créer une nouvelle demande et
attendre encore %{interval} pour supprimer votre compte.
header: Vous avez demandé de supprimer votre compte
- wait_html: Il y a un délai d’attente de %{hours} heures pour supprimer votre
+ wait_html: Il y a un délai d’attente de %{waiting_period} pour supprimer votre
compte. Dans %{interval}, vous recevrez un e-mail avec
des instructions pour terminer la suppression.
recovery_options:
diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml
index 430d6552c71..16585668655 100644
--- a/config/locales/user_mailer/en.yml
+++ b/config/locales/user_mailer/en.yml
@@ -20,8 +20,8 @@ en:
button: Yes, continue deleting
cancel_link_text: please cancel
help_html: If you don’t want to delete your account, %{cancel_account_reset_html}.
- intro_html: Your %{hours} hour waiting period has ended. Please complete step 2
- of the process.
If you’ve been unable to locate your
+ intro_html: Your waiting period of %{waiting_period} has ended. Please complete
+ step 2 of the process.
If you’ve been unable to locate your
authentication methods, select “confirm deletion” to delete your
%{app_name} account.
In the future, if you need to access
participating government websites who use %{app_name}, you can create a
@@ -33,13 +33,14 @@ en:
to cancel.
header: Your account will be deleted in %{interval}
intro_html: 'As a security measure, %{app_name} requires a two-step process to
- delete your account:
Step One: There is a %{hours} hour waiting
- period if you have lost access to your authentication methods and need
- to delete your account. If you locate your authentication methods, you
- can sign in to your %{app_name} account to cancel this request.
- Step Two: After your %{hours} hour waiting period, you will receive an
- email that will ask you to confirm the deletion of your %{app_name}
- account. Your account will not be deleted until you confirm.'
+ delete your account:
Step One: There is a waiting period of
+ %{waiting_period} if you have lost access to your authentication methods
+ and need to delete your account. If you locate your authentication
+ methods, you can sign in to your %{app_name} account to cancel this
+ request.
Step Two: After the waiting period of
+ %{waiting_period}, you will receive an email that will ask you to
+ confirm the deletion of your %{app_name} account. Your account will not
+ be deleted until you confirm.'
subject: How to delete your %{app_name} account
account_verified:
change_password_link: change your password
diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml
index e71f33c9287..b7c372edafb 100644
--- a/config/locales/user_mailer/es.yml
+++ b/config/locales/user_mailer/es.yml
@@ -22,8 +22,8 @@ es:
button: Sí, continúa eliminando
cancel_link_text: por favor cancele
help_html: Si no desea eliminar su cuenta, %{cancel_account_reset_html}.
- intro_html: Su período de espera de %{hours} horas ha finalizado. Complete el
- paso 2 del proceso.
Si no ha podido localizar sus métodos de
+ intro_html: Su período de espera de %{waiting_period} finalizó. Complete el paso
+ 2 del proceso.
Si no ha podido localizar sus métodos de
autenticación, seleccione “confirmar eliminación” para eliminar su
cuenta de %{app_name}.
En el futuro, si necesita acceder a los
sitios web gubernamentales participantes que utilizan %{app_name}, puede
@@ -35,14 +35,14 @@ es:
para cancelar.'
header: Su cuenta será eliminada en %{interval}
intro_html: 'Como medida de seguridad, %{app_name} requiere un proceso de dos
- pasos para eliminar su cuenta:
Paso uno: hay un período de
- espera de %{hours} horas si ha perdido el acceso a sus métodos de
+ pasos para eliminar su cuenta:
Paso uno: Hay un período de
+ espera de %{waiting_period} si perdió el acceso a sus métodos de
autenticación y necesita eliminar su cuenta. Si encuentra sus métodos de
autenticación, puede iniciar sesión en su cuenta %{app_name} para
- cancelar esta solicitud.
Paso dos: Después de su período de
- espera de %{hours} horas, recibirá un correo electrónico que le pedirá
- que confirme la eliminación de su cuenta %{app_name}. Su cuenta no se
- eliminará hasta que confirme.'
+ cancelar esta solicitud.
Paso dos: Tras el período de espera de
+ %{waiting_period}, recibirás un correo electrónico en el que te
+ pediremos que confirmes la eliminación de tu cuenta %{app_name}. Tu
+ cuenta no se eliminará hasta que lo confirmes.'
subject: Cómo eliminar su cuenta de %{app_name}
account_verified:
change_password_link: cambiar tu contraseña
diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml
index cf220fa3213..b52f7806e2d 100644
--- a/config/locales/user_mailer/fr.yml
+++ b/config/locales/user_mailer/fr.yml
@@ -22,7 +22,7 @@ fr:
cancel_link_text: veuillez annuler
help_html: Si vous ne souhaitez pas supprimer votre compte,
%{cancel_account_reset_html}.
- intro_html: Votre période d’attente de %{hours} heures est terminée. Veuillez
+ intro_html: Votre délai d’attente de %{waiting_period} est terminé. Veuillez
terminer l’étape 2 du processus.
Si vous ne parvenez pas à
localiser vos méthodes d’authentification, sélectionnez “confirmer la
suppression” pour supprimer votre compte %{app_name}.
À
@@ -36,15 +36,14 @@ fr:
%{app_name} pour annuler.
header: Votre compte sera supprimé dans %{interval}
intro_html: 'Par mesure de sécurité, %{app_name} nécessite un processus en deux
- étapes pour supprimer votre compte:
Étape 1: Il y a une
- période d’attente de %{hours} heures si vous avez perdu l’accès à vos
- méthodes d’authentification et devez supprimer votre compte. Si vous
- trouvez vos méthodes d’authentification, vous pouvez vous connecter à
- votre compte %{app_name} pour annuler cette demande.
Deuxième
- étape: après votre période d’attente de %{hours} heures, vous recevrez
- un e-mail qui vous demandera de confirmer la suppression de votre compte
- %{app_name}. Votre compte ne sera pas supprimé tant que vous ne l’aurez
- pas confirmé.'
+ étapes pour supprimer votre compte:
Étape 1: Il y a un delai
+ d’attente de %{waiting_period} si vous avez perdu l’accès à vos méthodes
+ d’authentification et devez supprimer votre compte. Si vous trouvez vos
+ méthodes d’authentification, vous pouvez vous connecter à votre compte
+ %{app_name} pour annuler cette demande.
Deuxième étape: après
+ la période d’attente de %{waiting_period}, vous recevrez un e-mail qui
+ vous demandera de confirmer la suppression de votre compte %{app_name}.
+ Votre compte ne sera pas supprimé tant que vous n’aurez pas confirmé.'
subject: Comment supprimer votre compte %{app_name}
account_verified:
change_password_link: changer votre mot de passe
diff --git a/lib/identity_config.rb b/lib/identity_config.rb
index 45029c9bd20..40436cf5024 100644
--- a/lib/identity_config.rb
+++ b/lib/identity_config.rb
@@ -108,6 +108,7 @@ def self.build_store(config_map)
config.add(:aamva_verification_url)
config.add(:account_reset_token_valid_for_days, type: :integer)
config.add(:account_reset_wait_period_days, type: :integer)
+ config.add(:account_reset_fraud_user_wait_period_days, type: :integer, allow_nil: true)
config.add(:account_suspended_support_code, type: :string)
config.add(:acuant_assure_id_password)
config.add(:acuant_assure_id_subscription_id)
diff --git a/spec/controllers/account_reset/pending_controller_spec.rb b/spec/controllers/account_reset/pending_controller_spec.rb
index 3348bb4cf70..3155f6f56a2 100644
--- a/spec/controllers/account_reset/pending_controller_spec.rb
+++ b/spec/controllers/account_reset/pending_controller_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
RSpec.describe AccountReset::PendingController do
+ include ActionView::Helpers::DateHelper
+ include AccountResetHelper
let(:user) { create(:user) }
before do
@@ -17,6 +19,67 @@
end
end
+ describe '#confirm' do
+ before do
+ end
+ context 'non-fraud user' do
+ it 'should have @account_reset_deletion_period_interval to match regular wait period' do
+ create_account_reset_request_for(user)
+
+ get :confirm
+ current_time = Time.zone.now
+ time_in_hours = distance_of_time_in_words(
+ current_time,
+ current_time + IdentityConfig.store.account_reset_wait_period_days.days,
+ true,
+ accumulate_on: :hours,
+ )
+ expect(controller.view_assigns['account_reset_deletion_period_interval']).
+ to eq(time_in_hours)
+ end
+ end
+
+ context 'fraud user' do
+ let(:user) { create(:user, :fraud_review_pending) }
+ context 'fraud wait period not set' do
+ before do
+ allow(IdentityConfig.store).to receive(:account_reset_fraud_user_wait_period_days).
+ and_return(nil)
+ end
+
+ it 'should have @account_reset_deletion_period to match regular wait period' do
+ create_account_reset_request_for(user)
+
+ get :confirm
+ current_time = Time.zone.now
+ time_in_hours = distance_of_time_in_words(
+ current_time,
+ current_time + IdentityConfig.store.account_reset_wait_period_days.days,
+ true,
+ accumulate_on: :hours,
+ )
+ expect(controller.view_assigns['account_reset_deletion_period_interval']).
+ to eq(time_in_hours)
+ end
+ end
+
+ it 'should have @account_reset_deletion_period_interval to match fraud wait period' do
+ create_account_reset_request_for(user)
+
+ get :confirm
+ current_time = Time.zone.now
+ time_in_hours = distance_of_time_in_words(
+ current_time,
+ current_time + IdentityConfig.store.account_reset_fraud_user_wait_period_days.days,
+ true,
+ accumulate_on: :days,
+ )
+ expect(controller.view_assigns['account_reset_deletion_period_interval']).
+ to eq(time_in_hours)
+ end
+ end
+ end
+
describe '#cancel' do
it 'cancels the account reset request and logs the cancellation event' do
stub_attempts_tracker
diff --git a/spec/controllers/account_reset/request_controller_spec.rb b/spec/controllers/account_reset/request_controller_spec.rb
index 09c7484d905..5077481222c 100644
--- a/spec/controllers/account_reset/request_controller_spec.rb
+++ b/spec/controllers/account_reset/request_controller_spec.rb
@@ -1,6 +1,7 @@
require 'rails_helper'
RSpec.describe AccountReset::RequestController, allowed_extra_analytics: [:*] do
+ include ActionView::Helpers::DateHelper
let(:user) { create(:user, :with_authentication_app) }
describe '#show' do
it 'renders the page' do
@@ -32,6 +33,64 @@
get :show
end
+
+ context 'non-fraud user' do
+ it 'should have @account_reset_deletion_period_interval to match regular wait period' do
+ stub_sign_in_before_2fa(user)
+
+ get :show
+ current_time = Time.zone.now
+ time_in_hours = distance_of_time_in_words(
+ current_time,
+ current_time + IdentityConfig.store.account_reset_wait_period_days.days,
+ true,
+ accumulate_on: :hours,
+ )
+ expect(controller.view_assigns['account_reset_deletion_period_interval']).
+ to eq(time_in_hours)
+ end
+ end
+
+ context 'fraud user' do
+ let(:user) { create(:user, :fraud_review_pending) }
+
+ context 'fraud wait period not set' do
+ before do
+ allow(IdentityConfig.store).to receive(:account_reset_fraud_user_wait_period_days).
+ and_return(nil)
+ end
+
+ it 'should have @account_reset_deletion_period to match regular wait period' do
+ stub_sign_in_before_2fa(user)
+
+ get :show
+ current_time = Time.zone.now
+ time_in_hours = distance_of_time_in_words(
+ current_time,
+ current_time + IdentityConfig.store.account_reset_wait_period_days.days,
+ true,
+ accumulate_on: :hours,
+ )
+ expect(controller.view_assigns['account_reset_deletion_period_interval']).
+ to eq(time_in_hours)
+ end
+ end
+
+ it 'should have @account_reset_deletion_period_interval to match fraud wait period' do
+ stub_sign_in_before_2fa(user)
+
+ get :show
+ current_time = Time.zone.now
+ time_in_hours = distance_of_time_in_words(
+ current_time,
+ current_time + IdentityConfig.store.account_reset_fraud_user_wait_period_days.days,
+ true,
+ accumulate_on: :days,
+ )
+ expect(controller.view_assigns['account_reset_deletion_period_interval']).
+ to eq(time_in_hours)
+ end
+ end
end
describe '#create' do
diff --git a/spec/controllers/concerns/account_reset_concern_spec.rb b/spec/controllers/concerns/account_reset_concern_spec.rb
new file mode 100644
index 00000000000..f19a2c97ad1
--- /dev/null
+++ b/spec/controllers/concerns/account_reset_concern_spec.rb
@@ -0,0 +1,68 @@
+require 'rails_helper'
+
+RSpec.describe AccountResetConcern do
+ include ActionView::Helpers::DateHelper
+ let(:test_class) do
+ Class.new do
+ include AccountResetConcern
+
+ attr_reader :current_user
+
+ def initialize(current_user:)
+ @current_user = current_user
+ end
+ end
+ end
+ let(:user) { build(:user) }
+ let(:instance) { test_class.new(current_user: user) }
+
+ describe '#account_reset_deletion_period_interval' do
+ context 'non fraud user' do
+ it 'should return regular wait time' do
+ current_time = Time.zone.now
+ time_in_hours = distance_of_time_in_words(
+ current_time,
+ current_time + IdentityConfig.store.account_reset_wait_period_days.days,
+ true,
+ accumulate_on: :hours,
+ )
+ expect(instance.account_reset_deletion_period_interval(user)).
+ to eq(time_in_hours)
+ end
+ end
+
+ context 'fraud user' do
+ let(:user) { create(:user, :fraud_review_pending) }
+ it 'should return fraud wait time' do
+ current_time = Time.zone.now
+ time_in_hours = distance_of_time_in_words(
+ current_time,
+ current_time + IdentityConfig.store.account_reset_fraud_user_wait_period_days.days,
+ true,
+ accumulate_on: :days,
+ )
+ expect(instance.account_reset_deletion_period_interval(user)).
+ to eq(time_in_hours)
+ end
+
+ context 'when account_reset_fraud_user_wait_period_days is nil' do
+ before do
+ allow(IdentityConfig.store).to receive(:account_reset_fraud_user_wait_period_days).
+ and_return(nil)
+ end
+
+ it 'should return standard reset wait time' do
+ current_time = Time.zone.now
+ time_in_hours = distance_of_time_in_words(
+ current_time,
+ current_time + IdentityConfig.store.account_reset_wait_period_days.days,
+ true,
+ accumulate_on: :hours,
+ )
+ expect(instance.account_reset_deletion_period_interval(user)).
+ to eq(time_in_hours)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
index 20d86af472f..aaac96febaf 100644
--- a/spec/mailers/user_mailer_spec.rb
+++ b/spec/mailers/user_mailer_spec.rb
@@ -304,7 +304,7 @@ def expect_email_body_to_have_help_and_contact_links
let(:account_reset) { user.account_reset_request }
let(:interval) { '24 hours' }
- let(:account_reset_deletion_period_hours) { 24 }
+ let(:account_reset_deletion_period_hours) { '24 hours' }
it_behaves_like 'a system email'
it_behaves_like 'an email that respects user email locale preference'
@@ -323,7 +323,7 @@ def expect_email_body_to_have_help_and_contact_links
t(
'user_mailer.account_reset_request.intro_html', app_name: APP_NAME,
interval: interval,
- hours:
+ waiting_period:
account_reset_deletion_period_hours
),
),
@@ -355,7 +355,7 @@ def expect_email_body_to_have_help_and_contact_links
UserMailer.with(user: user, email_address: email_address).
account_reset_granted(user.account_reset_request)
end
- let(:account_reset_deletion_period_hours) { 24 }
+ let(:account_reset_deletion_period_hours) { '24 hours' }
let(:token_expiration_interval) { '24 hours' }
it_behaves_like 'a system email'
@@ -377,7 +377,7 @@ def expect_email_body_to_have_help_and_contact_links
strip_tags(
t(
'user_mailer.account_reset_granted.intro_html', app_name: APP_NAME,
- hours:
+ waiting_period:
account_reset_deletion_period_hours
),
),
diff --git a/spec/presenters/account_reset/pending_presenter_spec.rb b/spec/presenters/account_reset/pending_presenter_spec.rb
index b4bb776a0aa..631d6976dcc 100644
--- a/spec/presenters/account_reset/pending_presenter_spec.rb
+++ b/spec/presenters/account_reset/pending_presenter_spec.rb
@@ -20,6 +20,36 @@
describe '#time_remaining_until_granted' do
before { I18n.locale = :en }
+ before do
+ allow(IdentityConfig.store).to receive(:account_reset_fraud_user_wait_period_days).
+ and_return(10)
+ end
+
+ context 'fraud user' do
+ let(:user) { create(:user, :fraud_review_pending) }
+ context 'when the remaining time is greater than 1 week' do
+ let(:requested_at) { 10.days.ago - (9.days + 21.minutes) }
+
+ it 'returns its description in week and days' do
+ expect(subject.time_remaining_until_granted).to eq '1 week and 2 days'
+ end
+ end
+ context 'when the remaining time is greater than 3 days and less than 1 weeks' do
+ let(:requested_at) { 10.days.ago - (5.days + 21.hours) }
+
+ it 'returns its description in hours and minutes' do
+ expect(subject.time_remaining_until_granted).to eq '5 days and 21 hours'
+ end
+ end
+
+ context 'when the remaining time is less than 1 day' do
+ let(:requested_at) { 10.days.ago - (10.hours + 21.minutes) }
+
+ it 'returns its description in hours and minutes' do
+ expect(subject.time_remaining_until_granted).to eq '10 hours and 21 minutes'
+ end
+ end
+ end
context 'when the remaining time is greater than 1 hour' do
let(:requested_at) { 24.hours.ago - (2.hours + 21.minutes) }
diff --git a/spec/services/account_reset/find_prending_request_for_user_spec.rb b/spec/services/account_reset/find_pending_request_for_user_spec.rb
similarity index 70%
rename from spec/services/account_reset/find_prending_request_for_user_spec.rb
rename to spec/services/account_reset/find_pending_request_for_user_spec.rb
index d2a5fb7ba38..bad22dc2f96 100644
--- a/spec/services/account_reset/find_prending_request_for_user_spec.rb
+++ b/spec/services/account_reset/find_pending_request_for_user_spec.rb
@@ -45,5 +45,20 @@
it { expect(subject.call).to be_nil }
end
+
+ context 'fraud user' do
+ let(:user) { create(:user, :fraud_review_pending) }
+ let(:user2) { create(:user, :fraud_rejection) }
+ context 'when a request exists, and it hasnt been granted yet but over a day' do
+ let(:requested_at) { 30.hours.ago }
+
+ it { expect(subject.call).to eq(account_reset_request) }
+ end
+ context 'when a request has expired' do
+ let(:requested_at) { 1.year.ago }
+
+ it { expect(subject.call).to be_nil }
+ end
+ end
end
end
diff --git a/spec/services/account_reset/grant_request_spec.rb b/spec/services/account_reset/grant_request_spec.rb
index be3fafbeda7..f87bd5803ea 100644
--- a/spec/services/account_reset/grant_request_spec.rb
+++ b/spec/services/account_reset/grant_request_spec.rb
@@ -29,5 +29,81 @@
expect(arr.granted_token).to eq(arr.reload.granted_token)
end
end
+
+ context 'with a fraud user' do
+ let(:user) { create(:user, :fraud_review_pending) }
+ let(:user2) { create(:user, :fraud_rejection) }
+ context 'with nil being set for fraud time' do
+ before do
+ allow(IdentityConfig.store).to receive(:account_reset_fraud_user_wait_period_days).
+ and_return(nil)
+ end
+
+ it 'grants request for all users' do
+ before_waiting_the_full_wait_period(Time.zone.now) do
+ create_account_reset_request_for(user)
+ create_account_reset_request_for(user2)
+ end
+
+ result = AccountReset::GrantRequest.new(user).call
+ arr = AccountResetRequest.find_by(user_id: user.id)
+ expect(arr.granted_at).to be_present
+ expect(arr.granted_token).to be_present
+ expect(result).to eq true
+
+ result2 = AccountReset::GrantRequest.new(user2).call
+ arr2 = AccountResetRequest.find_by(user_id: user2.id)
+ expect(arr2.granted_at).to be_present
+ expect(arr2.granted_token).to be_present
+ expect(result2).to eq true
+ end
+ end
+ context 'with deletion period not met' do
+ it 'does not grant request' do
+ create_account_reset_request_for(user)
+ result = AccountReset::GrantRequest.new(user).call
+
+ arr = AccountResetRequest.find_by(user_id: user.id)
+ expect(arr.granted_at).to_not be_present
+ expect(arr.granted_token).to_not be_present
+ expect(result).to eq false
+ end
+ end
+
+ context 'with deletion period met' do
+ it 'grants request for all users' do
+ before_waiting_the_full_fraud_wait_period(Time.zone.now) do
+ create_account_reset_request_for(user)
+ create_account_reset_request_for(user2)
+ end
+
+ result = AccountReset::GrantRequest.new(user).call
+ arr = AccountResetRequest.find_by(user_id: user.id)
+ expect(arr.granted_at).to be_present
+ expect(arr.granted_token).to be_present
+ expect(result).to eq true
+
+ result2 = AccountReset::GrantRequest.new(user2).call
+ arr2 = AccountResetRequest.find_by(user_id: user2.id)
+ expect(arr2.granted_at).to be_present
+ expect(arr2.granted_token).to be_present
+ expect(result2).to eq true
+ end
+ end
+ end
+ end
+
+ def before_waiting_the_full_wait_period(now)
+ days = IdentityConfig.store.account_reset_wait_period_days.days
+ travel_to(now - 1 - days) do
+ yield
+ end
+ end
+
+ def before_waiting_the_full_fraud_wait_period(now)
+ days = IdentityConfig.store.account_reset_fraud_user_wait_period_days.days
+ travel_to(now - 1 - days) do
+ yield
+ end
end
end
diff --git a/spec/services/account_reset/grant_requests_and_send_emails_spec.rb b/spec/services/account_reset/grant_requests_and_send_emails_spec.rb
index 54a8aab1093..5311dc5e3c3 100644
--- a/spec/services/account_reset/grant_requests_and_send_emails_spec.rb
+++ b/spec/services/account_reset/grant_requests_and_send_emails_spec.rb
@@ -10,62 +10,179 @@
let(:now) { Time.zone.now }
context 'after waiting the full wait period' do
- it 'does not send notifications when the notifications were already sent' do
- before_waiting_the_full_wait_period(now) do
- create_account_reset_request_for(user)
+ context 'standard user' do
+ it 'does not send notifications when the notifications were already sent' do
+ before_waiting_the_full_wait_period(now) do
+ create_account_reset_request_for(user)
+ end
+
+ AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
end
- AccountReset::GrantRequestsAndSendEmails.new.perform(now)
- notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
- expect(notifications_sent).to eq(0)
- end
+ it 'does not send notifications when the request was cancelled' do
+ before_waiting_the_full_wait_period(now) do
+ create_account_reset_request_for(user)
+ cancel_request_for(user)
+ end
- it 'does not send notifications when the request was cancelled' do
- before_waiting_the_full_wait_period(now) do
- create_account_reset_request_for(user)
- cancel_request_for(user)
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
end
- notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
- expect(notifications_sent).to eq(0)
- end
+ it 'sends notifications after a request is granted' do
+ before_waiting_the_full_wait_period(now) do
+ create_account_reset_request_for(user)
+ end
- it 'sends notifications after a request is granted' do
- before_waiting_the_full_wait_period(now) do
- create_account_reset_request_for(user)
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+
+ expect(notifications_sent).to eq(1)
end
- notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ it 'sends 2 notifications after 2 requests are granted' do
+ before_waiting_the_full_wait_period(now) do
+ create_account_reset_request_for(user)
+ create_account_reset_request_for(user2)
+ end
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
- expect(notifications_sent).to eq(1)
+ expect(notifications_sent).to eq(2)
+ end
end
- it 'sends 2 notifications after 2 requests are granted' do
- before_waiting_the_full_wait_period(now) do
- create_account_reset_request_for(user)
- create_account_reset_request_for(user2)
+ context 'possible fraud user' do
+ let(:user) { create(:user, :fraud_review_pending) }
+ let(:user2) { create(:user, :fraud_rejection) }
+ before do
+ allow(IdentityConfig.store).to receive(:account_reset_fraud_user_wait_period_days).
+ and_return(10)
+ end
+ it 'does not send notifications when the notifications were already sent' do
+ before_waiting_the_full_fraud_wait_period(now) do
+ create_account_reset_request_for(user)
+ end
+
+ AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
end
- notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ it 'does not send notifications when the request was cancelled' do
+ before_waiting_the_full_fraud_wait_period(now) do
+ create_account_reset_request_for(user)
+ cancel_request_for(user)
+ end
- expect(notifications_sent).to eq(2)
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
+ end
+
+ it 'sends notifications after a request is granted' do
+ before_waiting_the_full_fraud_wait_period(now) do
+ create_account_reset_request_for(user)
+ end
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+
+ expect(notifications_sent).to eq(1)
+ end
+
+ it 'sends 2 notifications after 2 requests are granted' do
+ before_waiting_the_full_fraud_wait_period(now) do
+ create_account_reset_request_for(user)
+ create_account_reset_request_for(user2)
+ end
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+
+ expect(notifications_sent).to eq(2)
+ end
end
end
context 'after not waiting the full wait period' do
- it 'does not send notifications after a request' do
- create_account_reset_request_for(user)
+ context 'standard user' do
+ it 'does not send notifications before a request wait period is done' do
+ create_account_reset_request_for(user)
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
+ end
- notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
- expect(notifications_sent).to eq(0)
+ it 'does not send notifications when the request was cancelled' do
+ create_account_reset_request_for(user)
+ cancel_request_for(user)
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
+ end
end
- it 'does not send notifications when the request was cancelled' do
- create_account_reset_request_for(user)
- cancel_request_for(user)
+ context 'possible fraud user' do
+ let(:user) { create(:user, :fraud_review_pending) }
+ let(:user2) { create(:user, :fraud_rejection) }
+ context 'with fraud wait period set' do
+ it 'does not send notifications before a request wait period is done' do
+ create_account_reset_request_for(user)
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
+ end
+
+ it 'does not send notifications when the request was cancelled' do
+ create_account_reset_request_for(user)
+ cancel_request_for(user)
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
+ end
+
+ it 'should not send if its in between regular wait period and fraud wait period' do
+ before_waiting_the_full_wait_period(now) do
+ create_account_reset_request_for(user)
+ create_account_reset_request_for(user2)
+ end
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+
+ expect(notifications_sent).to eq(0)
+ end
+ end
+
+ context 'with fraud wait period not set' do
+ before do
+ allow(IdentityConfig.store).to receive(:account_reset_fraud_user_wait_period_days).
+ and_return(nil)
+ end
+ it 'does not send notifications before a request wait period is done' do
+ create_account_reset_request_for(user)
- notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
- expect(notifications_sent).to eq(0)
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
+ end
+
+ it 'does not send notifications when the request was cancelled' do
+ create_account_reset_request_for(user)
+ cancel_request_for(user)
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+ expect(notifications_sent).to eq(0)
+ end
+
+ it 'should send if its after regular wait period' do
+ before_waiting_the_full_wait_period(now) do
+ create_account_reset_request_for(user)
+ create_account_reset_request_for(user2)
+ end
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.perform(now)
+
+ expect(notifications_sent).to eq(2)
+ end
+ end
end
end
end
@@ -76,4 +193,11 @@ def before_waiting_the_full_wait_period(now)
yield
end
end
+
+ def before_waiting_the_full_fraud_wait_period(now)
+ days = IdentityConfig.store.account_reset_fraud_user_wait_period_days.days
+ travel_to(now - 1 - days) do
+ yield
+ end
+ end
end