Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion app/components/phone_input_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def strings
{
country_code_label: t('components.phone_input.country_code_label'),
invalid_phone: t('errors.messages.invalid_phone_number'),
country_constraint_usa: t('errors.messages.phone_country_constraint_usa'),
unsupported_country: unsupported_country_string,
}
end
Expand Down
21 changes: 19 additions & 2 deletions app/controllers/idv/otp_delivery_method_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class OtpDeliveryMethodController < ApplicationController

def new
analytics.idv_phone_otp_delivery_selection_visit
render :new, locals: { gpo_letter_available: gpo_letter_available }
render :new, locals: view_locals
end

def create
Expand All @@ -25,6 +25,23 @@ def create

private

attr_reader :idv_phone

def view_locals
{
gpo_letter_available: gpo_letter_available,
phone_number_capabilities: phone_number_capabilities,
}
end

def phone_number_capabilities
PhoneNumberCapabilities.new(idv_phone, phone_confirmed: user_phone?)
end

def user_phone?
MfaContext.new(current_user).phone_configurations.any? { |config| config.phone == idv_phone }
end

def confirm_phone_step_complete
redirect_to idv_phone_url if idv_session.vendor_phone_confirmation != true
end
Expand All @@ -44,7 +61,7 @@ def otp_delivery_selection_params

def render_new_with_error_message
flash[:error] = t('idv.errors.unsupported_otp_delivery_method')
render :new, locals: { gpo_letter_available: gpo_letter_available }
render :new, locals: view_locals
end

def send_phone_confirmation_otp_and_handle_result
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/idv/phone_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ def set_idv_form
@idv_form = Idv::PhoneForm.new(
user: current_user,
previous_params: idv_session.previous_phone_step_params,
allowed_countries: ['US'],
allowed_countries:
PhoneNumberCapabilities::ADDRESS_IDENTITY_PROOFING_SUPPORTED_COUNTRY_CODES,
)
end

Expand Down
13 changes: 5 additions & 8 deletions app/forms/idv/phone_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,16 @@ def initial_phone_value(input_phone)

def validate_valid_phone_for_allowed_countries
return if valid_phone_for_allowed_countries?(phone)

if allowed_countries == ['US']
errors.add(:phone, :must_have_us_country_code, type: :must_have_us_country_code)
else
errors.add(:phone, :improbable_phone, type: :improbable_phone)
end
errors.add(:phone, :improbable_phone, type: :improbable_phone)
end

def validate_phone_delivery_methods
return unless valid_phone_for_allowed_countries?(phone)

capabilities = PhoneNumberCapabilities.new(phone, phone_confirmed: user_phone?(phone))
unsupported_delivery_methods(capabilities).each do |delivery_method|
unsupported_methods = unsupported_delivery_methods(capabilities)
return if unsupported_methods.count != delivery_methods.count
unsupported_methods.each do |delivery_method|
errors.add(
:phone,
I18n.t(
Expand All @@ -80,7 +77,7 @@ def validate_phone_delivery_methods

def valid_phone_for_allowed_countries?(phone)
if allowed_countries.present?
allowed_countries.all? { |country| Phonelib.valid_for_country?(phone, country) }
allowed_countries.any? { |country| Phonelib.valid_for_country?(phone, country) }
else
Phonelib.valid?(phone)
end
Expand Down
60 changes: 28 additions & 32 deletions app/javascript/packages/phone-input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { replaceVariables } from '@18f/identity-i18n';
*
* @prop {string=} country_code_label
* @prop {string=} invalid_phone
* @prop {string=} country_constraint_usa
* @prop {string=} unsupported_country
*/

Expand Down Expand Up @@ -46,32 +45,34 @@ export class PhoneInput extends HTMLElement {
countryCodePairs = {};

connectedCallback() {
/** @type {HTMLInputElement?} */
this.textInput = this.querySelector('.phone-input__number');
/** @type {HTMLSelectElement?} */
this.codeInput = this.querySelector('.phone-input__international-code');
this.codeWrapper = this.querySelector('.phone-input__international-code-wrapper');
this.exampleText = this.querySelector('.phone-input__example');

try {
this.deliveryMethods = JSON.parse(this.dataset.deliveryMethods || '');
this.countryCodePairs = JSON.parse(this.dataset.translatedCountryCodeNames || '');
} catch {}

if (!this.textInput || !this.codeInput) {
return;
}
if (this.isConnected) {
/** @type {HTMLInputElement?} */
this.textInput = this.querySelector('.phone-input__number');
/** @type {HTMLSelectElement?} */
this.codeInput = this.querySelector('.phone-input__international-code');
this.codeWrapper = this.querySelector('.phone-input__international-code-wrapper');
this.exampleText = this.querySelector('.phone-input__example');

try {
this.deliveryMethods = JSON.parse(this.dataset.deliveryMethods || '');
this.countryCodePairs = JSON.parse(this.dataset.translatedCountryCodeNames || '');
} catch {}

this.iti = this.initializeIntlTelInput();
if (!this.textInput || !this.codeInput) {
return;
}

this.iti = this.initializeIntlTelInput();

this.textInput.addEventListener('countrychange', () => this.syncCountryChangeToCodeInput());
this.textInput.addEventListener('input', () => this.validate());
this.codeInput.addEventListener('change', () => this.formatTextInput());
this.codeInput.addEventListener('change', () => this.setExampleNumber());
this.codeInput.addEventListener('change', () => this.validate());
this.textInput.addEventListener('countrychange', () => this.syncCountryChangeToCodeInput());
this.textInput.addEventListener('input', () => this.validate());
this.codeInput.addEventListener('change', () => this.formatTextInput());
this.codeInput.addEventListener('change', () => this.setExampleNumber());
this.codeInput.addEventListener('change', () => this.validate());

this.setExampleNumber();
this.validate();
this.setExampleNumber();
this.validate();
}
}

get selectedOption() {
Expand Down Expand Up @@ -160,7 +161,7 @@ export class PhoneInput extends HTMLElement {
}

validate() {
const { textInput, codeInput, supportedCountryCodes, selectedOption } = this;
const { textInput, codeInput, selectedOption } = this;
if (!textInput || !codeInput || !selectedOption) {
return;
}
Expand All @@ -173,14 +174,9 @@ export class PhoneInput extends HTMLElement {
return;
}

const isInvalidCountry =
supportedCountryCodes?.length === 1 && !isValidNumberForRegion(phoneNumber, countryCode);
const isInvalidCountry = !isValidNumberForRegion(phoneNumber, countryCode);
if (isInvalidCountry) {
if (countryCode === 'US') {
textInput.setCustomValidity(this.strings.country_constraint_usa || '');
} else {
textInput.setCustomValidity(this.strings.invalid_phone || '');
}
textInput.setCustomValidity(this.strings.invalid_phone || '');
}

const isInvalidPhoneNumber = !isPhoneValid(phoneNumber, countryCode);
Expand Down
19 changes: 3 additions & 16 deletions app/javascript/packages/phone-input/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ describe('PhoneInput', () => {
{
"country_code_label": "Country code",
"invalid_phone": "Phone number is not valid",
"country_constraint_usa": "Must be a U.S. phone number",
"unsupported_country": "We are unable to verify phone numbers from %{location}"
}
</script>
Expand Down Expand Up @@ -133,25 +132,13 @@ describe('PhoneInput', () => {
});

it('validates phone from region', async () => {
const input = createAndConnectElement({ isSingleOption: true });
const input = createAndConnectElement({ isNonUSSingleOption: true });

/** @type {HTMLInputElement} */
const phoneNumber = getByLabelText(input, 'Phone number');

await userEvent.type(phoneNumber, '306-555-1234');
expect(phoneNumber.validationMessage).to.equal('Must be a U.S. phone number');
});

context('with non-U.S. single option', () => {
it('validates phone from region', async () => {
const input = createAndConnectElement({ isNonUSSingleOption: true });

/** @type {HTMLInputElement} */
const phoneNumber = getByLabelText(input, 'Phone number');

await userEvent.type(phoneNumber, '513-555-1234');
expect(phoneNumber.validationMessage).to.equal('Phone number is not valid');
});
await userEvent.type(phoneNumber, '513-555-1234');
expect(phoneNumber.validationMessage).to.equal('Phone number is not valid');
});
});

Expand Down
5 changes: 5 additions & 0 deletions app/services/idv/phone_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,13 @@ def start_phone_confirmation_session
end

def extra_analytics_attributes
parsed_phone = Phonelib.parse(applicant[:phone])

{
vendor: idv_result.except(:errors, :success),
area_code: parsed_phone.area_code,
country_code: parsed_phone.country,
phone_fingerprint: Pii::Fingerprinter.fingerprint(parsed_phone.e164),
}
end

Expand Down
2 changes: 2 additions & 0 deletions app/services/phone_number_capabilities.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ def self.load_config
end

INTERNATIONAL_CODES = load_config.freeze
ADDRESS_IDENTITY_PROOFING_SUPPORTED_COUNTRY_CODES =
IdentityConfig.store.address_identity_proofing_supported_country_codes

attr_reader :phone, :phone_confirmed

Expand Down
56 changes: 30 additions & 26 deletions app/views/idv/otp_delivery_method/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,37 @@
) do |f| %>

<fieldset class="margin-bottom-4 padding-0 border-0">
<%= radio_button_tag(
'otp_delivery_preference',
:sms,
false,
disabled: VendorStatus.new.vendor_outage?(:sms),
class: 'usa-radio__input usa-radio__input--tile',
) %>
<label for="otp_delivery_preference_sms" class="usa-radio__label">
<%= t('two_factor_authentication.otp_delivery_preference.sms') %>
<span class="usa-radio__label-description">
<%= t('two_factor_authentication.two_factor_choice_options.sms_info') %>
</span>
</label>
<% if phone_number_capabilities.supports_sms? %>
<%= radio_button_tag(
'otp_delivery_preference',
:sms,
false,
disabled: VendorStatus.new.vendor_outage?(:sms),
class: 'usa-radio__input usa-radio__input--tile',
) %>
<label for="otp_delivery_preference_sms" class="usa-radio__label">
<%= t('two_factor_authentication.otp_delivery_preference.sms') %>
<span class="usa-radio__label-description">
<%= t('two_factor_authentication.two_factor_choice_options.sms_info') %>
</span>
</label>
<% end %>

<%= radio_button_tag(
'otp_delivery_preference',
:voice,
false,
disabled: VendorStatus.new.vendor_outage?(:voice),
class: 'usa-radio__input usa-radio__input--tile',
) %>
<label for="otp_delivery_preference_voice" class="usa-radio__label">
<%= t('two_factor_authentication.otp_delivery_preference.voice') %>
<span class="usa-radio__label-description">
<%= t('two_factor_authentication.two_factor_choice_options.voice_info') %>
</span>
</label>
<% if phone_number_capabilities.supports_voice? %>
<%= radio_button_tag(
'otp_delivery_preference',
:voice,
false,
disabled: VendorStatus.new.vendor_outage?(:voice),
class: 'usa-radio__input usa-radio__input--tile',
) %>
<label for="otp_delivery_preference_voice" class="usa-radio__label">
<%= t('two_factor_authentication.otp_delivery_preference.voice') %>
<span class="usa-radio__label-description">
<%= t('two_factor_authentication.two_factor_choice_options.voice_info') %>
</span>
</label>
<% end %>
</fieldset>
<div class="margin-y-5">
<%= submit_tag(t('idv.buttons.send_confirmation_code'), class: 'usa-button usa-button--big usa-button--wide') %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/idv/phone/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
) do |f| %>
<%= render PhoneInputComponent.new(
form: f,
allowed_countries: ['US'],
allowed_countries: @idv_form.allowed_countries,
required: true,
class: 'margin-bottom-4',
) %>
Expand Down
1 change: 1 addition & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ acuant_upload_image_timeout: 1.0
acuant_get_results_timeout: 1.0
acuant_create_document_timeout: 1.0
add_email_link_valid_for_hours: 24
address_identity_proofing_supported_country_codes: '["AS", "GU", "MP", "PR", "US", "VI"]'
asset_host: ''
async_wait_timeout_seconds: 60
async_stale_job_timeout_seconds: 300
Expand Down
3 changes: 0 additions & 3 deletions config/locales/errors/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,12 @@ en:
invalid_voice_number: Invalid phone number. Check that you’ve entered the
correct country code or area code.
missing_field: Please fill in this field.
must_have_us_country_code: Invalid phone number. Please make sure you enter a
phone number with a U.S. country code.
no_pending_profile: No profile is waiting for verification
not_a_number: is not a number
otp_failed: Your security code failed to send.
password_incorrect: Incorrect password
personal_key_incorrect: Incorrect personal key
phone_confirmation_throttled: You tried too many times, please try again in %{timeout}.
phone_country_constraint_usa: Must be a U.S. phone number
phone_duplicate: This account is already using the phone number you entered as
an authenticator. Please use a different phone number.
phone_required: Phone number is required
Expand Down
3 changes: 0 additions & 3 deletions config/locales/errors/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,12 @@ es:
invalid_voice_number: Numero de telefono invalido. Verifique que haya ingresado
el código de país o de área correcto.
missing_field: Por favor, rellene este campo.
must_have_us_country_code: Número de teléfono no válido. Asegúrate de introducir
un número de teléfono con un código país de EE. UU.
no_pending_profile: Ningún perfil está esperando verificación
not_a_number: no es un número
otp_failed: Se produjo un error al enviar el código de seguridad.
password_incorrect: La contraseña es incorrecta
personal_key_incorrect: La clave personal es incorrecta
phone_confirmation_throttled: Lo intentaste muchas veces, vuelve a intentarlo en %{timeout}.
phone_country_constraint_usa: Debe ser un número telefónico de los Estados Unidos
phone_duplicate: Esta cuenta ya está utilizando el número de teléfono que
ingresó como autenticador. Por favor, use un número de teléfono
diferente.
Expand Down
3 changes: 0 additions & 3 deletions config/locales/errors/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,12 @@ fr:
invalid_voice_number: Numéro de téléphone invalide. Vérifiez que vous avez entré
le bon indicatif international ou régional.
missing_field: Veuillez remplir ce champ.
must_have_us_country_code: Numéro de téléphone non valide. Veuillez vous assurer
de saisir un numéro de téléphone avec un code pays des États-Unis.
no_pending_profile: Aucun profil en attente de vérification
not_a_number: N’est pas un nombre
otp_failed: Échec de l’envoi de votre code de sécurité.
password_incorrect: Mot de passe incorrect
personal_key_incorrect: Clé personnelle incorrecte
phone_confirmation_throttled: Vous avez essayé plusieurs fois, essayez à nouveau dans %{timeout}.
phone_country_constraint_usa: Dois être un numéro de téléphone américain
phone_duplicate: Ce compte utilise déjà le numéro de téléphone que vous avez
entré en tant qu’authentificateur. Veuillez utiliser un numéro de
téléphone différent.
Expand Down
1 change: 1 addition & 0 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def self.build_store(config_map)
config.add(:acuant_get_results_timeout, type: :float)
config.add(:acuant_create_document_timeout, type: :float)
config.add(:add_email_link_valid_for_hours, type: :integer)
config.add(:address_identity_proofing_supported_country_codes, type: :json)
config.add(:asset_host, type: :string)
config.add(:async_wait_timeout_seconds, type: :integer)
config.add(:async_stale_job_timeout_seconds, type: :integer)
Expand Down
Loading