diff --git a/app/controllers/users/verify_personal_key_controller.rb b/app/controllers/users/verify_personal_key_controller.rb index 853107688e4..c2d4341039c 100644 --- a/app/controllers/users/verify_personal_key_controller.rb +++ b/app/controllers/users/verify_personal_key_controller.rb @@ -12,15 +12,32 @@ def new user: current_user, personal_key: '', ) + + if Throttler::IsThrottled.call(current_user.id, :verify_personal_key) + render :throttled + else + render :new + end end def create - result = personal_key_form.submit - analytics.track_event(Analytics::PERSONAL_KEY_REACTIVATION_SUBMITTED, result.to_h) - if result.success? - handle_success(result) + throttled = Throttler::IsThrottledElseIncrement.call( + current_user.id, + :verify_personal_key, + analytics: analytics, + ) + + if throttled + render :throttled else - handle_failure(result) + result = personal_key_form.submit + + analytics.track_event(Analytics::PERSONAL_KEY_REACTIVATION_SUBMITTED, result.to_h) + if result.success? + handle_success(result) + else + handle_failure(result) + end end end diff --git a/app/models/throttle.rb b/app/models/throttle.rb index 4c2ec1682b9..ba500e30870 100644 --- a/app/models/throttle.rb +++ b/app/models/throttle.rb @@ -9,6 +9,7 @@ class Throttle < ApplicationRecord reset_password_email: 4, idv_resolution: 5, idv_send_link: 6, + verify_personal_key: 7, } THROTTLE_CONFIG = { @@ -36,6 +37,10 @@ class Throttle < ApplicationRecord max_attempts: AppConfig.env.idv_send_link_max_attempts.to_i, attempt_window: AppConfig.env.idv_send_link_attempt_window_in_minutes.to_i, }, + verify_personal_key: { + max_attempts: AppConfig.env.verify_personal_key_max_attempts.to_i, + attempt_window: AppConfig.env.verify_personal_key_attempt_window_in_minutes.to_i, + }, }.freeze def throttled? diff --git a/app/views/users/verify_personal_key/throttled.html.erb b/app/views/users/verify_personal_key/throttled.html.erb new file mode 100644 index 00000000000..8622cbdfe75 --- /dev/null +++ b/app/views/users/verify_personal_key/throttled.html.erb @@ -0,0 +1,9 @@ +<% title t('headings.verify_personal_key') %> + +

+ <%= t('headings.verify_personal_key') %> +

+ +

+ <%= t('errors.verify_personal_key.throttled') %> <%= link_to(t('links.go_back'), account_path) %>. +

diff --git a/config/application.yml.default b/config/application.yml.default index 18107037eb9..e573b9b7ba0 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -141,6 +141,8 @@ usps_download_sftp_timeout: '5' usps_upload_enabled: 'false' usps_upload_sftp_timeout: '5' valid_authn_contexts: '["http://idmanagement.gov/ns/assurance/loa/1", "http://idmanagement.gov/ns/assurance/loa/3", "http://idmanagement.gov/ns/assurance/ial/1", "http://idmanagement.gov/ns/assurance/ial/2", "http://idmanagement.gov/ns/assurance/ial/0", "http://idmanagement.gov/ns/assurance/ial/2?strict=true", "urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo", "http://idmanagement.gov/ns/assurance/aal/2", "http://idmanagement.gov/ns/assurance/aal/3", "http://idmanagement.gov/ns/assurance/aal/3?hspd12=true"]' +verify_personal_key_attempt_window_in_minutes: '15' +verify_personal_key_max_attempts: '5' usps_ipp_password: '' usps_ipp_root_url: '' usps_ipp_sponsor_id: '' @@ -452,6 +454,8 @@ test: session_encryption_key: 27bad3c25711099429c1afdfd1890910f3b59f5a4faec1c85e945cb8b02b02f261ba501d99cfbb4fab394e0102de6fecf8ffe260f322f610db3e96b2a775c120 sps_over_quota_limit_notify_email_list: '["test1@test.com"]' telephony_adapter: test + verify_personal_key_attempt_window_in_minutes: '3' + verify_personal_key_max_attempts: '1' use_dashboard_service_providers: 'false' use_kms: 'false' usps_confirmation_max_days: '10' diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml index f4b59bbd235..6c59af05090 100644 --- a/config/locales/errors/en.yml +++ b/config/locales/errors/en.yml @@ -118,6 +118,8 @@ en: unique_name: That name is already taken. Please choose a different name. two_factor_auth_setup: must_select_option: Select an authentication method. + verify_personal_key: + throttled: You tried too many times, please try again in 15 minutes. webauthn_setup: already_registered: Security key already registered. Please try a different security key. diff --git a/config/locales/errors/es.yml b/config/locales/errors/es.yml index a8e4a0d352c..c645021030d 100644 --- a/config/locales/errors/es.yml +++ b/config/locales/errors/es.yml @@ -121,6 +121,8 @@ es: unique_name: El nombre ya fue escogido. Por favor, elija un nombre diferente. two_factor_auth_setup: must_select_option: Seleccione un método de autenticación. + verify_personal_key: + throttled: Lo intentaste muchas veces, vuelve a intentarlo en 15 minutos. webauthn_setup: already_registered: Clave de seguridad ya registrada. Por favor, intente una clave de seguridad diferente. diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml index e8758d5e196..f4069a0cc27 100644 --- a/config/locales/errors/fr.yml +++ b/config/locales/errors/fr.yml @@ -128,6 +128,8 @@ fr: unique_name: Ce nom est déjà pris. Veuillez choisir un autre nom. two_factor_auth_setup: must_select_option: Sélectionnez une méthode d'authentification. + verify_personal_key: + throttled: Vous avez essayé plusieurs fois, essayez à nouveau dans 15 minutes. webauthn_setup: already_registered: Clé de sécurité déjà enregistrée. Veuillez essayer une clé de sécurité différente. diff --git a/config/locales/headings/en.yml b/config/locales/headings/en.yml index 83756e5fd0e..50d39bd3a5c 100644 --- a/config/locales/headings/en.yml +++ b/config/locales/headings/en.yml @@ -68,5 +68,6 @@ en: totp_setup: new: Add an authentication app verify_email: Check your email + verify_personal_key: Verify your personal key webauthn_setup: new: Add your security key diff --git a/config/locales/headings/es.yml b/config/locales/headings/es.yml index cb1a02ac233..a63c23b0951 100644 --- a/config/locales/headings/es.yml +++ b/config/locales/headings/es.yml @@ -68,5 +68,6 @@ es: totp_setup: new: Agregar una aplicación de autenticación verify_email: Revise su email + verify_personal_key: Verifica tu clave personal webauthn_setup: new: Añade tu clave de seguridad diff --git a/config/locales/headings/fr.yml b/config/locales/headings/fr.yml index c71ca574c1c..22eddb88b30 100644 --- a/config/locales/headings/fr.yml +++ b/config/locales/headings/fr.yml @@ -70,5 +70,6 @@ fr: totp_setup: new: Ajouter une application d'authentification verify_email: Consultez vos courriels + verify_personal_key: Vérifier votre clé personnelle webauthn_setup: new: Ajoutez votre clé de sécurité diff --git a/spec/controllers/users/verify_personal_key_controller_spec.rb b/spec/controllers/users/verify_personal_key_controller_spec.rb index 9a4d7e2f328..7a7f8a1bd7b 100644 --- a/spec/controllers/users/verify_personal_key_controller_spec.rb +++ b/spec/controllers/users/verify_personal_key_controller_spec.rb @@ -35,6 +35,13 @@ expect(subject.flash[:info]).to eq(t('notices.account_reactivation')) end + + it 'shows throttled page after being throttled' do + allow(Throttler::IsThrottled).to receive(:call).once.and_return(true) + get :new + + expect(response).to render_template(:throttled) + end end end @@ -86,5 +93,32 @@ expect(response).to render_template(:new) end end + + context 'with throttle reached' do + let(:bad_key) { 'baaad' } + before do + allow(VerifyPersonalKeyForm).to receive(:new). + with(user: subject.current_user, personal_key: bad_key). + and_return(form) + allow(form).to receive(:submit).and_return(response_bad) + end + + it 'renders throttled page' do + stub_analytics + expect(@analytics).to receive(:track_event).with( + Analytics::PERSONAL_KEY_REACTIVATION_SUBMITTED, + { errors: { personal_key: ['bad_key'] }, success: false }, + ).once + expect(@analytics).to receive(:track_event).with( + Analytics::THROTTLER_RATE_LIMIT_TRIGGERED, + throttle_type: 'verify_personal_key', + ).once + + post :create, params: { personal_key: bad_key } + post :create, params: { personal_key: bad_key } + + expect(response).to render_template(:throttled) + end + end end end