diff --git a/.eslintrc b/.eslintrc
index a9838da95fd..d44fdbcb02b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -40,6 +40,8 @@
'app/radio-btn',
'app/print-personal-key',
'app/utils/ms-formatter',
+ 'app/phone-internationalization',
+ 'app/i18n-dropdown',
],
}
}
diff --git a/.reek b/.reek
index 80ef814f099..d3182612dcd 100644
--- a/.reek
+++ b/.reek
@@ -14,6 +14,7 @@ DuplicateMethodCall:
- UserFlowExporter#self.massage_assets
FeatureEnvy:
exclude:
+ - ActiveJob::Logging::LogSubscriber#json_for
- track_registration
- append_info_to_payload
- generate_slo_request
@@ -24,6 +25,8 @@ FeatureEnvy:
- Pii::Attributes#[]=
- OpenidConnectLogoutForm#load_identity
- Idv::ProfileMaker#pii_from_applicant
+ - Idv::Step#vendor_validator_result
+ - IdvSession#vendor_result_timed_out?
InstanceVariableAssumption:
exclude:
- User
@@ -41,6 +44,8 @@ NilCheck:
LongParameterList:
exclude:
- IdentityLinker#optional_attributes
+ - VendorValidatorJob#perform
+ - Idv::VendorResult#initialize
RepeatedConditional:
exclude:
- Users::ResetPasswordsController
@@ -53,6 +58,7 @@ TooManyInstanceVariables:
exclude:
- OpenidConnectAuthorizeForm
- OpenidConnectRedirector
+ - Idv::VendorResult
TooManyStatements:
max_statements: 6
exclude:
@@ -72,6 +78,7 @@ TooManyMethods:
- OpenidConnect::AuthorizationController
- Idv::Session
- User
+ - Verify::SessionsController
UncommunicativeMethodName:
exclude:
- PhoneConfirmationFlow
@@ -89,6 +96,7 @@ UtilityFunction:
public_methods_only: true
exclude:
- AnalyticsEventJob#perform
+ - ApplicationController#default_url_options
- ApplicationHelper#step_class
- PersonalKeyFormatter#regexp
- SessionTimeoutWarningHelper#frequency
@@ -97,6 +105,9 @@ UtilityFunction:
- SessionDecorator
- WorkerHealthChecker::Middleware#call
- UserEncryptedAttributeOverrides#create_fingerprint
+ - LocaleHelper#locale_url_param
+ - Verify::Base#mock_vendor_partial
+ - IdvSession#timed_out_vendor_error
'app/controllers':
InstanceVariableAssumption:
enabled: false
diff --git a/.rubocop.yml b/.rubocop.yml
index b2afe411f82..3adaebd80a6 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -47,6 +47,7 @@ Metrics/BlockLength:
- 'config/initializers/secure_headers.rb'
- 'config/routes.rb'
- 'spec/**/*.rb'
+ - 'config/initializers/active_job_logger_patch.rb'
Metrics/ClassLength:
Description: Avoid classes longer than 100 lines of code.
@@ -93,6 +94,9 @@ Metrics/ModuleLength:
- spec/**/*
- 'app/controllers/concerns/two_factor_authenticatable.rb'
+Metrics/ParameterLists:
+ CountKeywordArgs: false
+
# This is a Rails 5 feature, so it should be disabled until we upgrade
Rails/HttpPositionalArguments:
Description: 'Use keyword arguments instead of positional arguments in http method calls.'
diff --git a/Gemfile b/Gemfile
index d92ebf701ed..cebbb9caaeb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -24,8 +24,10 @@ gem 'http_accept_language'
gem 'httparty'
gem 'json-jwt'
gem 'lograge'
+gem 'net-sftp'
gem 'newrelic_rpm'
gem 'pg'
+gem 'phonelib'
gem 'phony_rails'
gem 'premailer-rails'
gem 'proofer', github: '18F/identity-proofer-gem', branch: 'master'
@@ -113,4 +115,5 @@ end
group :production do
gem 'equifax', git: 'git@github.com:18F/identity-equifax-api-client-gem.git', branch: 'master'
+ gem 'mandrill_dm'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 69f760e3ded..5597dc22c35 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,6 +1,6 @@
GIT
remote: git@github.com:18F/identity-equifax-api-client-gem.git
- revision: 4308a502baf7b65e8b463ecafc2d428d530b4349
+ revision: 889aad815bda2ff2a41cd2b108e2afae7f50d8b8
branch: master
specs:
equifax (1.0.0)
@@ -148,7 +148,7 @@ GEM
bullet (5.5.1)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0)
- bummr (0.2.0)
+ bummr (0.1.8)
rainbow
thor
byebug (9.0.6)
@@ -247,6 +247,7 @@ GEM
errbase (0.0.3)
erubis (2.7.0)
eventmachine (1.0.9.1)
+ excon (0.57.0)
execjs (2.7.0)
factory_girl (4.8.0)
activesupport (>= 3.0.0)
@@ -300,7 +301,7 @@ GEM
httpi (2.4.2)
rack
socksify
- i18n (0.8.4)
+ i18n (0.8.6)
i18n-tasks (0.9.15)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
@@ -314,7 +315,7 @@ GEM
ice_nine (0.11.2)
iniparse (1.4.2)
jmespath (1.3.1)
- json (2.1.0)
+ json (1.8.6)
json-jwt (1.7.2)
activesupport
bindata
@@ -348,6 +349,12 @@ GEM
skinny (~> 0.2.3)
sqlite3 (~> 1.3)
thin (~> 1.5.0)
+ mandrill-api (1.0.53)
+ excon (>= 0.16.0, < 1.0)
+ json (>= 1.7.7, < 2.0)
+ mandrill_dm (1.3.4)
+ mail (~> 2.6)
+ mandrill-api (~> 1.0.53)
memory_profiler (0.9.8)
method_source (0.8.2)
mime-types (3.1)
@@ -360,6 +367,8 @@ GEM
nenv (0.3.0)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
+ net-sftp (2.1.2)
+ net-ssh (>= 2.6.5)
net-ssh (4.1.0)
newrelic_rpm (4.2.0.334)
nokogiri (1.8.0)
@@ -376,6 +385,7 @@ GEM
parser (2.4.0.0)
ast (~> 2.2)
pg (0.21.0)
+ phonelib (0.6.12)
phony (2.15.44)
phony_rails (0.14.6)
activesupport (>= 3.0)
@@ -696,9 +706,12 @@ DEPENDENCIES
json-jwt
lograge
mailcatcher
+ mandrill_dm
+ net-sftp
newrelic_rpm
overcommit
pg
+ phonelib
phony_rails
poltergeist
premailer-rails
diff --git a/app/assets/images/globe-blue.svg b/app/assets/images/globe-blue.svg
new file mode 100644
index 00000000000..d72989b85bc
--- /dev/null
+++ b/app/assets/images/globe-blue.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/images/globe-white.svg b/app/assets/images/globe-white.svg
new file mode 100644
index 00000000000..34829ec0c2e
--- /dev/null
+++ b/app/assets/images/globe-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/images/sp-logos/cbp-ttp.png b/app/assets/images/sp-logos/cbp-ttp.png
new file mode 100644
index 00000000000..2beaf6fc93a
Binary files /dev/null and b/app/assets/images/sp-logos/cbp-ttp.png differ
diff --git a/app/assets/images/spinner.gif b/app/assets/images/spinner.gif
new file mode 100644
index 00000000000..913ce4c9165
Binary files /dev/null and b/app/assets/images/spinner.gif differ
diff --git a/app/assets/javascripts/app/components/accordion.js b/app/assets/javascripts/app/components/accordion.js
index 2724585c6e5..14f5da6239d 100644
--- a/app/assets/javascripts/app/components/accordion.js
+++ b/app/assets/javascripts/app/components/accordion.js
@@ -72,6 +72,7 @@ class Accordion extends Events {
this.content.classList.add('shown');
this.content.classList.remove('animate-out');
this.content.classList.add('animate-in');
+ this.content.setAttribute('aria-hidden', 'false');
this.emit('accordion.show');
}
@@ -81,6 +82,7 @@ class Accordion extends Events {
this.shownIcon.classList.add('display-none');
this.content.classList.remove('animate-in');
this.content.classList.add('animate-out');
+ this.content.setAttribute('aria-hidden', 'true');
this.emit('accordion.hide');
this.header.focus();
}
diff --git a/app/assets/javascripts/app/form-field-format.js b/app/assets/javascripts/app/form-field-format.js
index 308d63cf687..f00ec070241 100644
--- a/app/assets/javascripts/app/form-field-format.js
+++ b/app/assets/javascripts/app/form-field-format.js
@@ -1,5 +1,6 @@
-import { PhoneFormatter, SocialSecurityNumberFormatter, TextField } from 'field-kit';
+import { SocialSecurityNumberFormatter, TextField } from 'field-kit';
import DateFormatter from './modules/date-formatter';
+import InternationalPhoneFormatter from './modules/international-phone-formatter';
import NumericFormatter from './modules/numeric-formatter';
import PersonalKeyFormatter from './modules/personal-key-formatter';
import ZipCodeFormatter from './modules/zip-code-formatter';
@@ -13,7 +14,7 @@ function formatForm() {
['.home_equity_line', new NumericFormatter()],
['.mfa', new NumericFormatter()],
['.mortgage', new NumericFormatter()],
- ['.phone', new PhoneFormatter()],
+ ['.phone', new InternationalPhoneFormatter()],
['.personal-key', new PersonalKeyFormatter()],
['.ssn', new SocialSecurityNumberFormatter()],
['.zipcode', new ZipCodeFormatter()],
diff --git a/app/assets/javascripts/app/form-validation.js b/app/assets/javascripts/app/form-validation.js
index 7d82dec7729..7d534d6e6c0 100644
--- a/app/assets/javascripts/app/form-validation.js
+++ b/app/assets/javascripts/app/form-validation.js
@@ -10,7 +10,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (input) {
input.addEventListener('input', () => {
if (input.validity.patternMismatch) {
- input.setCustomValidity(I18n.t(`idv.errors.pattern_mismatch.${f}`));
+ input.setCustomValidity(I18n.t(`idv.errors.pattern_mismatch.${I18n.key(f)}`));
} else {
input.setCustomValidity('');
}
diff --git a/app/assets/javascripts/app/i18n-dropdown.js b/app/assets/javascripts/app/i18n-dropdown.js
new file mode 100644
index 00000000000..431765f0f47
--- /dev/null
+++ b/app/assets/javascripts/app/i18n-dropdown.js
@@ -0,0 +1,18 @@
+import 'classlist.js';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const mobileLink = document.querySelector('.i18n-mobile-toggle');
+ const mobileDropdown = document.querySelector('.i18n-mobile-dropdown');
+ const desktopLink = document.querySelector('.i18n-desktop-toggle');
+ const desktopDropdown = document.querySelector('.i18n-desktop-dropdown');
+
+ function initDropdown (trigger, dropdown) {
+ trigger.addEventListener('click', function() {
+ this.classList.toggle('focused');
+ dropdown.classList.toggle('focused');
+ });
+ }
+
+ if (mobileLink) initDropdown(mobileLink, mobileDropdown);
+ if (desktopLink) initDropdown(desktopLink, desktopDropdown);
+});
diff --git a/app/assets/javascripts/app/modules/international-phone-formatter.js b/app/assets/javascripts/app/modules/international-phone-formatter.js
new file mode 100644
index 00000000000..2b3ee0b47db
--- /dev/null
+++ b/app/assets/javascripts/app/modules/international-phone-formatter.js
@@ -0,0 +1,69 @@
+import { Formatter } from 'field-kit';
+import { asYouType as AsYouType } from 'libphonenumber-js';
+
+const fixCountryCodeSpacing = (text, countryCode) => {
+ // If the text is `+123456`, make it `+123 456`
+ if (text[countryCode.length + 1] !== ' ') {
+ return text.replace(`+${countryCode}`, `+${countryCode} `);
+ }
+ return text;
+};
+
+const getFormattedTextData = (text) => {
+ if (text === '1') {
+ text = '+1';
+ }
+
+ const asYouType = new AsYouType('US');
+ let formattedText = asYouType.input(text);
+ const countryCode = asYouType.country_phone_code;
+
+ if (asYouType.country_phone_code) {
+ formattedText = fixCountryCodeSpacing(formattedText, countryCode);
+ }
+
+ return {
+ text: formattedText,
+ template: asYouType.template,
+ countryCode,
+ };
+};
+
+const cursorPosition = (formattedTextData) => {
+ // If the text is `(23 )` the cursor goes after the 3
+ const match = formattedTextData.text.match(/\d[^\d]*$/);
+ if (match) {
+ return match.index + 1;
+ }
+ return formattedTextData.text.length + 1;
+};
+
+class InternationalPhoneFormatter extends Formatter {
+ format(text) {
+ const formattedTextData = getFormattedTextData(text);
+ return super.format(formattedTextData.text);
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ parse(text) {
+ return text.replace(/[^\d+]/g, '');
+ }
+
+ isChangeValid(change, error) {
+ const formattedTextData = getFormattedTextData(change.proposed.text);
+ const previousFormattedTextData = getFormattedTextData(change.current.text);
+
+ if (previousFormattedTextData.template &&
+ !formattedTextData.template &&
+ change.inserted.text.length === 1
+ ) {
+ return false;
+ }
+
+ change.proposed.text = formattedTextData.text;
+ change.proposed.selectedRange.start = cursorPosition(formattedTextData);
+ return super.isChangeValid(change, error);
+ }
+}
+
+export default InternationalPhoneFormatter;
diff --git a/app/assets/javascripts/app/phone-internationalization.js b/app/assets/javascripts/app/phone-internationalization.js
new file mode 100644
index 00000000000..af3e4724a33
--- /dev/null
+++ b/app/assets/javascripts/app/phone-internationalization.js
@@ -0,0 +1,136 @@
+import { PhoneFormatter } from 'field-kit';
+
+const INTERNATIONAL_CODE_REGEX = /^\+(\d+) |^1 /;
+
+const I18n = window.LoginGov.I18n;
+const phoneFormatter = new PhoneFormatter();
+
+const getPhoneUnsupportedAreaCodeCountry = (areaCode) => {
+ const form = document.querySelector('#new_two_factor_setup_form');
+ const phoneUnsupportedAreaCodes = JSON.parse(form.dataset.unsupportedAreaCodes);
+ return phoneUnsupportedAreaCodes[areaCode];
+};
+
+const areaCodeFromUSPhone = (phone) => {
+ const digits = phoneFormatter.digitsWithoutCountryCode(phone);
+ if (digits.length >= 10) {
+ return digits.slice(0, 3);
+ }
+ return null;
+};
+
+const selectedInternationCodeOption = () => {
+ const dropdown = document.querySelector('[data-international-phone-form] .international-code');
+ return dropdown.item(dropdown.selectedIndex);
+};
+
+const unsupportedUSPhoneOTPDeliveryWarningMessage = (phone) => {
+ const areaCode = areaCodeFromUSPhone(phone);
+ const country = getPhoneUnsupportedAreaCodeCountry(areaCode);
+ if (country) {
+ const messageTemplate = I18n.t('devise.two_factor_authentication.otp_delivery_preference.phone_unsupported');
+ return messageTemplate.replace('%{location}', country);
+ }
+ return null;
+};
+
+const unsupportedInternationalPhoneOTPDeliveryWarningMessage = () => {
+ const selectedOption = selectedInternationCodeOption();
+ if (selectedOption.dataset.smsOnly === 'true') {
+ const messageTemplate = I18n.t('devise.two_factor_authentication.otp_delivery_preference.phone_unsupported');
+ return messageTemplate.replace('%{location}', selectedOption.dataset.countryName);
+ }
+ return null;
+};
+
+const unsupportedPhoneOTPDeliveryWarningMessage = (phone) => {
+ const internationCodeOption = selectedInternationCodeOption();
+ if (internationCodeOption.dataset.countryCode === '1') {
+ return unsupportedUSPhoneOTPDeliveryWarningMessage(phone);
+ }
+ return unsupportedInternationalPhoneOTPDeliveryWarningMessage();
+};
+
+const updateOTPDeliveryMethods = () => {
+ const phoneRadio = document.querySelector('#two_factor_setup_form_otp_delivery_preference_voice');
+ const smsRadio = document.querySelector('#two_factor_setup_form_otp_delivery_preference_sms');
+
+ if (!phoneRadio || !smsRadio) {
+ return;
+ }
+
+ const phoneInput = document.querySelector('[data-international-phone-form] .phone');
+ const phoneLabel = phoneRadio.parentNode.parentNode;
+ const deliveryMethodHint = document.querySelector('#otp_delivery_preference_instruction');
+ const optPhoneLabelInfo = document.querySelector('#otp_phone_label_info');
+
+ const phone = phoneInput.value;
+
+ const warningMessage = unsupportedPhoneOTPDeliveryWarningMessage(phone);
+ if (warningMessage) {
+ phoneRadio.disabled = true;
+ phoneLabel.classList.add('btn-disabled');
+ smsRadio.click();
+ deliveryMethodHint.innerText = warningMessage;
+ optPhoneLabelInfo.innerText = I18n.t('devise.two_factor_authentication.otp_phone_label_info_modile_only');
+ } else {
+ phoneRadio.disabled = false;
+ phoneLabel.classList.remove('btn-disabled');
+ deliveryMethodHint.innerText = I18n.t('devise.two_factor_authentication.otp_delivery_preference.instruction');
+ optPhoneLabelInfo.innerText = I18n.t('devise.two_factor_authentication.otp_phone_label_info');
+ }
+};
+
+const internationalCodeFromPhone = (phone) => {
+ const match = phone.match(INTERNATIONAL_CODE_REGEX);
+ if (match) {
+ return match[1] || match[2];
+ }
+ return '1';
+};
+
+const updateInternationalCodeSelection = () => {
+ const phoneInput = document.querySelector('[data-international-phone-form] .phone');
+ const phone = phoneInput.value;
+ const internationalCode = internationalCodeFromPhone(phone);
+ const option = document.querySelector(`[data-country-code='${internationalCode}']`);
+ if (option) {
+ const dropdown = document.querySelector('[data-international-phone-form] .international-code');
+ dropdown.value = option.value;
+ }
+};
+
+const updateInternationalCodeInPhone = (phone, newCode) => {
+ if (phone.match(/^\+[^d+]$/)) {
+ phone = phone.replace(/^\+[^d+]$/, '');
+ }
+ if (phone.match(INTERNATIONAL_CODE_REGEX)) {
+ return phone.replace(INTERNATIONAL_CODE_REGEX, `+${newCode} `);
+ }
+ return `+${newCode} ${phone}`;
+};
+
+const updateInternationalCodeInput = () => {
+ const phoneInput = document.querySelector('[data-international-phone-form] .phone');
+ const phone = phoneInput.value;
+ const inputInternationalCode = internationalCodeFromPhone(phone);
+ const selectedInternationalCode = selectedInternationCodeOption().dataset.countryCode;
+
+ if (inputInternationalCode !== selectedInternationalCode) {
+ phoneInput.value = updateInternationalCodeInPhone(phone, selectedInternationalCode);
+ }
+};
+
+document.addEventListener('DOMContentLoaded', () => {
+ const phoneInput = document.querySelector('[data-international-phone-form] .phone');
+ const codeInput = document.querySelector('[data-international-phone-form] .international-code');
+ if (phoneInput) {
+ phoneInput.addEventListener('keyup', updateOTPDeliveryMethods);
+ phoneInput.addEventListener('keyup', updateInternationalCodeSelection);
+ }
+ if (codeInput) {
+ codeInput.addEventListener('change', updateOTPDeliveryMethods);
+ codeInput.addEventListener('change', updateInternationalCodeInput);
+ updateOTPDeliveryMethods();
+ }
+});
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 0f4ba9ccf44..64d07a2fd48 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -5,4 +5,6 @@ import 'app/form-validation';
import 'app/form-field-format';
import 'app/idv-finance-helper';
import 'app/radio-btn';
+import 'app/phone-internationalization';
import 'app/print-personal-key';
+import 'app/i18n-dropdown';
diff --git a/app/assets/javascripts/misc/i18n-strings.js.erb b/app/assets/javascripts/misc/i18n-strings.js.erb
index 4c5fa4c198d..a2bd235c4c7 100644
--- a/app/assets/javascripts/misc/i18n-strings.js.erb
+++ b/app/assets/javascripts/misc/i18n-strings.js.erb
@@ -1,11 +1,15 @@
window.LoginGov = window.LoginGov || {};
<% keys = [
+ 'devise.two_factor_authentication.otp_delivery_preference.instruction',
+ 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ 'devise.two_factor_authentication.otp_phone_label_info',
+ 'devise.two_factor_authentication.otp_phone_label_info_modile_only',
'errors.messages.format_mismatch',
'errors.messages.missing_field',
'forms.passwords.show',
'idv.errors.pattern_mismatch.dob',
- 'idv.errors.pattern_mismatch.personal-key',
+ 'idv.errors.pattern_mismatch.personal_key',
'idv.errors.pattern_mismatch.ssn',
'idv.errors.pattern_mismatch.zipcode',
'idv.modal.button.warning',
@@ -16,40 +20,45 @@ window.LoginGov = window.LoginGov || {};
'instructions.password.strength.v',
'links.remove',
'valid_email.validations.email.invalid',
- 'zxcvbn.feedback.Use a few words, avoid common phrases',
- 'zxcvbn.feedback.No need for symbols, digits, or uppercase letters',
- 'zxcvbn.feedback.Add another word or two_ Uncommon words are better_',
- 'zxcvbn.feedback.Straight rows of keys are easy to guess',
- 'zxcvbn.feedback.Short keyboard patterns are easy to guess',
- 'zxcvbn.feedback.Use a longer keyboard pattern with more turns',
- 'zxcvbn.feedback.Repeats like "aaa" are easy to guess',
- 'zxcvbn.feedback.Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
- 'zxcvbn.feedback.Avoid repeated words and characters',
- 'zxcvbn.feedback.Sequences like abc or 6543 are easy to guess',
- 'zxcvbn.feedback.Avoid sequences',
- 'zxcvbn.feedback.Recent years are easy to guess',
- 'zxcvbn.feedback.Avoid recent years',
- 'zxcvbn.feedback.Avoid years that are associated with you',
- 'zxcvbn.feedback.Dates are often easy to guess',
- 'zxcvbn.feedback.Avoid dates and years that are associated with you',
- 'zxcvbn.feedback.This is a top-10 common password',
- 'zxcvbn.feedback.This is a top-100 common password',
- 'zxcvbn.feedback.This is a very common password',
- 'zxcvbn.feedback.This is similar to a commonly used password',
- 'zxcvbn.feedback.A word by itself is easy to guess',
- 'zxcvbn.feedback.Names and surnames by themselves are easy to guess',
- 'zxcvbn.feedback.Common names and surnames are easy to guess',
- 'zxcvbn.feedback.Capitalization doesn\'t help very much',
- 'zxcvbn.feedback.All-uppercase is almost as easy to guess as all-lowercase',
- 'zxcvbn.feedback.Reversed words aren\'t much harder to guess',
- 'zxcvbn.feedback.Predictable substitutions like \'@\' instead of \'a\' don\'t help very much'
+ 'zxcvbn.feedback.a_word_by_itself_is_easy_to_guess',
+ 'zxcvbn.feedback.add_another_word_or_two_uncommon_words_are_better',
+ 'zxcvbn.feedback.all_uppercase_is_almost_as_easy_to_guess_as_all_lowercase',
+ 'zxcvbn.feedback.avoid_dates_and_years_that_are_associated_with_you',
+ 'zxcvbn.feedback.avoid_recent_years',
+ 'zxcvbn.feedback.avoid_repeated_words_and_characters',
+ 'zxcvbn.feedback.avoid_sequences',
+ 'zxcvbn.feedback.avoid_years_that_are_associated_with_you',
+ 'zxcvbn.feedback.capitalization_doesnt_help_very_much',
+ 'zxcvbn.feedback.common_names_and_surnames_are_easy_to_guess',
+ 'zxcvbn.feedback.dates_are_often_easy_to_guess',
+ 'zxcvbn.feedback.names_and_surnames_by_themselves_are_easy_to_guess',
+ 'zxcvbn.feedback.there_is_no_need_for_symbols_digits_or_uppercase_letters',
+ 'zxcvbn.feedback.predictable_substitutions_like__instead_of_a_dont_help_very_much',
+ 'zxcvbn.feedback.recent_years_are_easy_to_guess',
+ 'zxcvbn.feedback.repeats_like_aaa_are_easy_to_guess',
+ 'zxcvbn.feedback.repeats_like_abcabcabc_are_only_slightly_harder_to_guess_than_abc',
+ 'zxcvbn.feedback.reversed_words_arent_much_harder_to_guess',
+ 'zxcvbn.feedback.sequences_like_abc_or_6543_are_easy_to_guess',
+ 'zxcvbn.feedback.short_keyboard_patterns_are_easy_to_guess',
+ 'zxcvbn.feedback.straight_rows_of_keys_are_easy_to_guess',
+ 'zxcvbn.feedback.this_is_a_top_10_common_password',
+ 'zxcvbn.feedback.this_is_a_top_100_common_password',
+ 'zxcvbn.feedback.this_is_a_very_common_password',
+ 'zxcvbn.feedback.this_is_similar_to_a_commonly_used_password',
+ 'zxcvbn.feedback.for_a_stronger_password_use_a_few_words_separated_by_spaces_but_avoid_common_phrases',
+ 'zxcvbn.feedback.use_a_longer_keyboard_pattern_with_more_turns'
] %>
window.LoginGov.I18n = {
+ currentLocale: function() { return this.__currentLocale || (this.__currentLocale = document.querySelector('html').lang); },
strings: {},
- t: function(key) { return this.strings[key]; }
+ t: function(key) { return this.strings[this.currentLocale()][key]; },
+ key: function(key) { return key.replace(/[ -]/g, '_').replace(/\W/g, '').toLowerCase(); }
};
-<% keys.each do |key| %>
-window.LoginGov.I18n.strings['<%= ActionController::Base.helpers.j key %>'] = '<%= ActionController::Base.helpers.j I18n.t(key) %>';
+<% I18n.available_locales.each do |locale| %>
+ window.LoginGov.I18n.strings['<%= ActionController::Base.helpers.j locale.to_s %>'] = {};
+ <% keys.each do |key| %>
+ window.LoginGov.I18n.strings['<%= ActionController::Base.helpers.j locale.to_s %>']['<%= ActionController::Base.helpers.j key %>'] = '<%= ActionController::Base.helpers.j I18n.t(key, locale: locale) %>';
+ <% end %>
<% end %>
diff --git a/app/assets/javascripts/misc/pw-strength.js b/app/assets/javascripts/misc/pw-strength.js
index ae0b444be1b..82c0a79c92a 100644
--- a/app/assets/javascripts/misc/pw-strength.js
+++ b/app/assets/javascripts/misc/pw-strength.js
@@ -27,8 +27,7 @@ function getFeedback(z) {
const { warning, suggestions } = z.feedback;
function lookup(str) {
- const strFormatted = str.replace(/\./g, '_');
- return I18n.t(`zxcvbn.feedback.${strFormatted}`);
+ return I18n.t(`zxcvbn.feedback.${I18n.key(str)}`);
}
if (!warning && !suggestions.length) return '';
diff --git a/app/assets/stylesheets/components/_btn.scss b/app/assets/stylesheets/components/_btn.scss
index 297d879d781..5f7c5d2f972 100644
--- a/app/assets/stylesheets/components/_btn.scss
+++ b/app/assets/stylesheets/components/_btn.scss
@@ -72,3 +72,9 @@
outline: none;
}
}
+
+.btn-disabled {
+ background-color: $gray-light;
+ border-color: $gray;
+ color: $gray;
+}
diff --git a/app/assets/stylesheets/components/_footer.scss b/app/assets/stylesheets/components/_footer.scss
index f352278c144..4b9e8042f51 100644
--- a/app/assets/stylesheets/components/_footer.scss
+++ b/app/assets/stylesheets/components/_footer.scss
@@ -15,6 +15,7 @@ html {
.footer {
flex: none; // 2
+ position: relative;
}
.site-wrap {
diff --git a/app/assets/stylesheets/components/_form.scss b/app/assets/stylesheets/components/_form.scss
index cf60f39a954..71df7c2435c 100644
--- a/app/assets/stylesheets/components/_form.scss
+++ b/app/assets/stylesheets/components/_form.scss
@@ -152,6 +152,11 @@ input::-webkit-inner-spin-button {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNy4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgOCA4IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA4IDgiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTQsMUMyLjMsMSwxLDIuMywxLDRzMS4zLDMsMywzczMtMS4zLDMtM1M1LjcsMSw0LDF6Ii8+DQo8L3N2Zz4NCg==);
}
+.radio input:disabled ~ .indicator {
+ background-color: $gray-light;
+ border-color: $gray;
+}
+
.select-alt {
color: $white;
display: inline-block;
diff --git a/app/assets/stylesheets/components/_i18n-dropdown.scss b/app/assets/stylesheets/components/_i18n-dropdown.scss
new file mode 100644
index 00000000000..317a9c0face
--- /dev/null
+++ b/app/assets/stylesheets/components/_i18n-dropdown.scss
@@ -0,0 +1,47 @@
+.i18n-mobile-toggle,
+.i18n-desktop-toggle {
+ cursor: pointer;
+
+ &.focused .caret {
+ transform: rotateX(180deg) translateY(-1px);
+ }
+}
+
+.i18n-mobile-dropdown,
+.i18n-desktop-dropdown {
+ &.focused {
+ display: block;
+ }
+}
+
+.i18n-mobile-dropdown {
+ background-color: $blue-light;
+ bottom: 100%;
+ display: none;
+ left: 0;
+ position: absolute;
+ right: 0;
+}
+
+.i18n-desktop-toggle {
+ position: relative;
+ text-decoration: none;
+
+ &.focused {
+ background-color: $blue;
+ border-radius: 0;
+ margin-bottom: 0;
+ margin-top: 0;
+ padding-bottom: 12px;
+ padding-top: 12px;
+ }
+}
+
+.i18n-desktop-dropdown {
+ background-color: $blue;
+ bottom: 100%;
+ display: none;
+ left: -1px;
+ position: absolute;
+ width: 194px;
+}
diff --git a/app/assets/stylesheets/components/_loading.scss b/app/assets/stylesheets/components/_loading.scss
new file mode 100644
index 00000000000..9a2e669ae3a
--- /dev/null
+++ b/app/assets/stylesheets/components/_loading.scss
@@ -0,0 +1,4 @@
+.loading-spinner {
+ margin: auto;
+ width: 100px;
+}
diff --git a/app/assets/stylesheets/components/_space-misc.scss b/app/assets/stylesheets/components/_space-misc.scss
index 61c0bf57b8b..df019512724 100644
--- a/app/assets/stylesheets/components/_space-misc.scss
+++ b/app/assets/stylesheets/components/_space-misc.scss
@@ -8,12 +8,15 @@
.pb-tiny { padding-bottom: $space-tiny; }
.pt-tiny { padding-top: $space-tiny; }
.px-tiny { padding-left: $space-tiny; padding-right: $space-tiny; }
+.py-tiny { padding-bottom: $space-tiny; padding-top: $space-tiny; }
.mb-12p { margin-bottom: 12px; }
.mt-12p { margin-top: 12px; }
.px-12p { padding-left: 12px; padding-right: 12px; }
.py-12p { padding-bottom: 12px; padding-top: 12px; }
+.pl-24p { padding-left: 24px; }
+
.mb-40p { margin-bottom: 40px; }
.mtn1 { margin-top: -$space-1; }
diff --git a/app/assets/stylesheets/components/_typography.scss b/app/assets/stylesheets/components/_typography.scss
index 2c376505781..f1397b4070d 100644
--- a/app/assets/stylesheets/components/_typography.scss
+++ b/app/assets/stylesheets/components/_typography.scss
@@ -14,6 +14,7 @@ body { -webkit-font-smoothing: antialiased; }
.ls-5 { letter-spacing: 5px; }
.fs-12p { font-size: 12px; }
+.fs-13p { font-size: 13px; }
.fs-20p { font-size: 20px; }
.caps {
diff --git a/app/assets/stylesheets/components/all.scss b/app/assets/stylesheets/components/all.scss
index 8937fd95861..71f8a80ed93 100644
--- a/app/assets/stylesheets/components/all.scss
+++ b/app/assets/stylesheets/components/all.scss
@@ -10,6 +10,7 @@
@import 'form';
@import 'icon';
@import 'list';
+@import 'loading';
@import 'modal';
@import 'nav';
@import 'password';
@@ -25,3 +26,4 @@
@import 'space-addon';
@import 'space-misc';
@import 'typography';
+@import 'i18n-dropdown';
diff --git a/app/assets/stylesheets/variables/_web.scss b/app/assets/stylesheets/variables/_web.scss
index 7a632869bf9..6a7c2abae08 100644
--- a/app/assets/stylesheets/variables/_web.scss
+++ b/app/assets/stylesheets/variables/_web.scss
@@ -99,7 +99,7 @@ $breakpoint-xl: '(min-width: 96em)' !default;
$breakpoint-sm-md: '(min-width: 40em) and (max-width: 52em)' !default;
$breakpoint-md-lg: '(min-width: 52em) and (max-width: 64em)' !default;
-$container-width: 780px !default;
+$container-width: 940px !default;
$container-skinny-width: 620px !default;
$container-xskinny-width: 416px !default;
$container-xxskinny-width: 296px !default;
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 76da4d0180a..c623af396b6 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,6 +1,7 @@
class ApplicationController < ActionController::Base
include UserSessionContext
include VerifyProfileConcern
+ include LocaleHelper
FLASH_KEYS = %w[alert error notice success warning].freeze
@@ -53,6 +54,10 @@ def decorated_session
).call
end
+ def default_url_options
+ { locale: locale_url_param }
+ end
+
private
def disable_caching
@@ -136,9 +141,7 @@ def skip_session_expiration
end
def set_locale
- I18n.locale =
- http_accept_language.compatible_language_from(I18n.available_locales) ||
- I18n.default_locale
+ I18n.locale = LocaleChooser.new(params[:locale], request).locale
end
def sp_session
diff --git a/app/controllers/concerns/account_recovery_concern.rb b/app/controllers/concerns/account_reactivation_concern.rb
similarity index 90%
rename from app/controllers/concerns/account_recovery_concern.rb
rename to app/controllers/concerns/account_reactivation_concern.rb
index 46a36b11026..4c0140fcbcf 100644
--- a/app/controllers/concerns/account_recovery_concern.rb
+++ b/app/controllers/concerns/account_reactivation_concern.rb
@@ -1,4 +1,4 @@
-module AccountRecoveryConcern
+module AccountReactivationConcern
extend ActiveSupport::Concern
def confirm_password_reset_profile
diff --git a/app/controllers/concerns/fully_authenticatable.rb b/app/controllers/concerns/fully_authenticatable.rb
index f5e7eb0665c..a0326a0ff11 100644
--- a/app/controllers/concerns/fully_authenticatable.rb
+++ b/app/controllers/concerns/fully_authenticatable.rb
@@ -9,7 +9,6 @@ def confirm_two_factor_authenticated(id = nil)
def delete_branded_experience
ServiceProviderRequest.from_uuid(sp_session[:request_id]).delete
- session.delete(:sp)
end
def request_id
diff --git a/app/controllers/concerns/idv_failure_concern.rb b/app/controllers/concerns/idv_failure_concern.rb
index 689a3e61205..9956107d8f3 100644
--- a/app/controllers/concerns/idv_failure_concern.rb
+++ b/app/controllers/concerns/idv_failure_concern.rb
@@ -5,15 +5,28 @@ def render_failure
if step_attempts_exceeded?
@view_model = view_model(error: 'fail')
flash_message(type: :error)
- elsif step.form_valid_but_vendor_validation_failed?
- @view_model = view_model(error: 'warning')
+ elsif form_valid_but_vendor_validation_failed?
+ @view_model = view_model(error: 'warning', timed_out: step.vendor_validation_timed_out?)
flash_message(type: :warning)
else
@view_model = view_model
end
end
+ def form_valid_but_vendor_validation_failed?
+ idv_form.valid? && !step.vendor_validation_passed?
+ end
+
def flash_message(type:)
flash.now[type.to_sym] = @view_model.flash_message
end
+
+ def view_model(error: nil, timed_out: nil)
+ view_model_class.new(
+ error: error,
+ remaining_attempts: remaining_step_attempts,
+ idv_form: idv_form,
+ timed_out: timed_out
+ )
+ end
end
diff --git a/app/controllers/concerns/idv_session.rb b/app/controllers/concerns/idv_session.rb
index 271de8555f2..62241be17bb 100644
--- a/app/controllers/concerns/idv_session.rb
+++ b/app/controllers/concerns/idv_session.rb
@@ -2,6 +2,7 @@ module IdvSession
extend ActiveSupport::Concern
def confirm_idv_session_started
+ return if current_user.decorate.needs_profile_usps_verification?
redirect_to verify_session_url if idv_session.params.blank?
end
@@ -39,4 +40,32 @@ def idv_vendor
def idv_attempter
@_idv_attempter ||= Idv::Attempter.new(current_user)
end
+
+ def vendor_validator_result
+ return timed_out_vendor_error if vendor_result_timed_out?
+
+ VendorValidatorResultStorage.new.load(idv_session.async_result_id)
+ end
+
+ def vendor_result_timed_out?
+ started_at = idv_session.async_result_started_at
+ return false if started_at.blank?
+
+ expiration = started_at + Figaro.env.async_job_refresh_max_wait_seconds.to_i
+ Time.zone.now.to_i >= expiration
+ end
+
+ def timed_out_vendor_error
+ Idv::VendorResult.new(
+ success: false,
+ errors: { timed_out: ['Timed out waiting for vendor response'] },
+ timed_out: true
+ )
+ end
+
+ def refresh_if_not_ready
+ return if vendor_validator_result.present?
+
+ render 'shared/refresh'
+ end
end
diff --git a/app/controllers/concerns/phone_confirmation.rb b/app/controllers/concerns/phone_confirmation.rb
index dbc99fc1880..efb5507ed35 100644
--- a/app/controllers/concerns/phone_confirmation.rb
+++ b/app/controllers/concerns/phone_confirmation.rb
@@ -4,7 +4,17 @@ def prompt_to_confirm_phone(phone:, context: 'confirmation')
user_session[:context] = context
redirect_to otp_send_path(
- otp_delivery_selection_form: { otp_delivery_preference: current_user.otp_delivery_preference }
+ otp_delivery_selection_form: { otp_delivery_preference: otp_delivery_method(phone) }
)
end
+
+ private
+
+ def otp_delivery_method(phone)
+ if PhoneNumberCapabilities.new(phone).sms_only?
+ :sms
+ else
+ current_user.otp_delivery_preference
+ end
+ end
end
diff --git a/app/controllers/concerns/two_factor_authenticatable.rb b/app/controllers/concerns/two_factor_authenticatable.rb
index e728c786c23..02f005fd2a3 100644
--- a/app/controllers/concerns/two_factor_authenticatable.rb
+++ b/app/controllers/concerns/two_factor_authenticatable.rb
@@ -163,18 +163,23 @@ def update_phone_attributes
end
def update_idv_state
- now = Time.zone.now
if idv_context?
- Idv::Session.new(
- user_session: user_session,
- current_user: current_user,
- issuer: sp_session[:issuer]
- ).params['phone_confirmed_at'] = now
+ confirm_idv_session_phone
elsif profile_context?
Idv::ProfileActivator.new(user: current_user).call
end
end
+ def confirm_idv_session_phone
+ idv_session = Idv::Session.new(
+ user_session: user_session,
+ current_user: current_user,
+ issuer: sp_session[:issuer]
+ )
+ idv_session.user_phone_confirmation = true
+ idv_session.params['phone_confirmed_at'] = Time.zone.now
+ end
+
def reset_otp_session_data
user_session.delete(:unconfirmed_phone)
user_session[:context] = 'authentication'
@@ -232,6 +237,7 @@ def phone_view_data
phone_number: display_phone_to_deliver_to,
code_value: direct_otp_code,
otp_delivery_preference: two_factor_authentication_method,
+ voice_otp_delivery_unsupported: voice_otp_delivery_unsupported?,
reenter_phone_number_path: reenter_phone_number_path,
unconfirmed_phone: unconfirmed_phone?,
totp_enabled: current_user.totp_enabled?,
@@ -260,6 +266,15 @@ def display_phone_to_deliver_to
end
end
+ def voice_otp_delivery_unsupported?
+ phone_number = if authentication_context?
+ current_user.phone
+ else
+ user_session[:unconfirmed_phone]
+ end
+ PhoneNumberCapabilities.new(phone_number).sms_only?
+ end
+
def decorated_user
current_user.decorate
end
diff --git a/app/controllers/reactivate_account_controller.rb b/app/controllers/reactivate_account_controller.rb
index a9eff6cc5e4..dbb0eccba49 100644
--- a/app/controllers/reactivate_account_controller.rb
+++ b/app/controllers/reactivate_account_controller.rb
@@ -1,5 +1,5 @@
class ReactivateAccountController < ApplicationController
- include AccountRecoveryConcern
+ include AccountReactivationConcern
before_action :confirm_two_factor_authenticated
before_action :confirm_password_reset_profile
diff --git a/app/controllers/two_factor_authentication/totp_verification_controller.rb b/app/controllers/two_factor_authentication/totp_verification_controller.rb
index d74ac3d3a69..5006c7ea17e 100644
--- a/app/controllers/two_factor_authentication/totp_verification_controller.rb
+++ b/app/controllers/two_factor_authentication/totp_verification_controller.rb
@@ -3,6 +3,7 @@ class TotpVerificationController < ApplicationController
include TwoFactorAuthenticatable
skip_before_action :handle_two_factor_authentication
+ before_action :confirm_totp_enabled
def show
@presenter = presenter_for_two_factor_authentication_method
@@ -19,5 +20,13 @@ def create
handle_invalid_otp
end
end
+
+ private
+
+ def confirm_totp_enabled
+ return if current_user.totp_enabled?
+
+ redirect_to user_two_factor_authentication_path
+ end
end
end
diff --git a/app/controllers/users/phones_controller.rb b/app/controllers/users/phones_controller.rb
index 3fbeaadf54f..7d8c2c05be3 100644
--- a/app/controllers/users/phones_controller.rb
+++ b/app/controllers/users/phones_controller.rb
@@ -11,7 +11,7 @@ def edit
def update
@update_user_phone_form = UpdateUserPhoneForm.new(current_user)
- if @update_user_phone_form.submit(user_params)
+ if @update_user_phone_form.submit(user_params).success?
process_updates
bypass_sign_in current_user
else
@@ -22,7 +22,7 @@ def update
private
def user_params
- params.require(:update_user_phone_form).permit(:phone)
+ params.require(:update_user_phone_form).permit(:phone, :international_code)
end
def process_updates
diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb
index ae84123fdb3..078c3739a06 100644
--- a/app/controllers/users/sessions_controller.rb
+++ b/app/controllers/users/sessions_controller.rb
@@ -44,7 +44,7 @@ def timeout
def check_user_needs_redirect
if user_fully_authenticated?
- redirect_to after_sign_in_path_for(current_user)
+ redirect_to signed_in_path
elsif current_user
sign_out
end
diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb
index 90c9e42a9ea..f83ae1c6bfc 100644
--- a/app/controllers/users/two_factor_authentication_controller.rb
+++ b/app/controllers/users/two_factor_authentication_controller.rb
@@ -14,7 +14,7 @@ def show
end
def send_code
- @otp_delivery_selection_form = OtpDeliverySelectionForm.new(current_user)
+ @otp_delivery_selection_form = OtpDeliverySelectionForm.new(current_user, phone_to_deliver_to)
result = @otp_delivery_selection_form.submit(delivery_params)
diff --git a/app/controllers/users/two_factor_authentication_setup_controller.rb b/app/controllers/users/two_factor_authentication_setup_controller.rb
index 7402f09ac09..5d21cf83494 100644
--- a/app/controllers/users/two_factor_authentication_setup_controller.rb
+++ b/app/controllers/users/two_factor_authentication_setup_controller.rb
@@ -9,6 +9,7 @@ class TwoFactorAuthenticationSetupController < ApplicationController
def index
@two_factor_setup_form = TwoFactorSetupForm.new(current_user)
+ @unsupported_area_codes = PhoneNumberCapabilities::VOICE_UNSUPPORTED_US_AREA_CODES
analytics.track_event(Analytics::USER_REGISTRATION_PHONE_SETUP_VISIT)
end
diff --git a/app/controllers/users/verify_password_controller.rb b/app/controllers/users/verify_password_controller.rb
index 67bea6d230f..2dc268cb5e4 100644
--- a/app/controllers/users/verify_password_controller.rb
+++ b/app/controllers/users/verify_password_controller.rb
@@ -1,6 +1,6 @@
module Users
class VerifyPasswordController < ApplicationController
- include AccountRecoveryConcern
+ include AccountReactivationConcern
before_action :confirm_two_factor_authenticated
before_action :confirm_password_reset_profile
diff --git a/app/controllers/users/verify_personal_key_controller.rb b/app/controllers/users/verify_personal_key_controller.rb
index 6316fc9441b..0e7fbe39643 100644
--- a/app/controllers/users/verify_personal_key_controller.rb
+++ b/app/controllers/users/verify_personal_key_controller.rb
@@ -1,10 +1,10 @@
module Users
class VerifyPersonalKeyController < ApplicationController
- include AccountRecoveryConcern
+ include AccountReactivationConcern
before_action :confirm_two_factor_authenticated
before_action :confirm_password_reset_profile
- before_action :init_account_recovery, only: [:new]
+ before_action :init_account_reactivation, only: [:new]
def new
@personal_key_form = VerifyPersonalKeyForm.new(
@@ -25,10 +25,10 @@ def create
private
- def init_account_recovery
+ def init_account_reactivation
return if reactivate_account_session.started?
- flash.now[:notice] = t('notices.account_recovery')
+ flash.now[:notice] = t('notices.account_reactivation')
reactivate_account_session.start
end
diff --git a/app/controllers/verify/confirmations_controller.rb b/app/controllers/verify/confirmations_controller.rb
index 685a8f2429b..a359a255956 100644
--- a/app/controllers/verify/confirmations_controller.rb
+++ b/app/controllers/verify/confirmations_controller.rb
@@ -33,7 +33,7 @@ def confirm_profile_has_been_created
def track_final_idv_event
result = {
success: true,
- new_phone_added: idv_session.params['phone_confirmed_at'].present?,
+ new_phone_added: idv_session.params['phone'] != current_user.phone,
}
analytics.track_event(Analytics::IDV_FINAL, result)
end
diff --git a/app/controllers/verify/finance_controller.rb b/app/controllers/verify/finance_controller.rb
index e41a5af7c4f..2f4110552e9 100644
--- a/app/controllers/verify/finance_controller.rb
+++ b/app/controllers/verify/finance_controller.rb
@@ -5,6 +5,7 @@ class FinanceController < ApplicationController
before_action :confirm_step_needed
before_action :confirm_step_allowed
+ before_action :refresh_if_not_ready, only: [:show]
def new
@view_model = view_model
@@ -12,8 +13,21 @@ def new
end
def create
+ result = idv_form.submit(step_params)
+ analytics.track_event(Analytics::IDV_FINANCE_CONFIRMATION_FORM, result.to_h)
+
+ if result.success?
+ submit_idv_job
+ redirect_to verify_finance_result_path
+ else
+ @view_model = view_model
+ render_form
+ end
+ end
+
+ def show
result = step.submit
- analytics.track_event(Analytics::IDV_FINANCE_CONFIRMATION, result.to_h)
+ analytics.track_event(Analytics::IDV_FINANCE_CONFIRMATION_VENDOR, result.to_h)
increment_step_attempts
if result.success?
@@ -26,6 +40,14 @@ def create
private
+ def submit_idv_job
+ SubmitIdvJob.new(
+ vendor_validator_class: Idv::FinancialsValidator,
+ idv_session: idv_session,
+ vendor_params: vendor_params
+ ).call
+ end
+
def step_name
:financials
end
@@ -34,16 +56,12 @@ def confirm_step_needed
redirect_to verify_address_path if idv_session.financials_confirmation == true
end
- def view_model(error: nil)
- Verify::FinancialsNew.new(
- error: error,
- remaining_attempts: remaining_step_attempts,
- idv_form: idv_finance_form
- )
+ def view_model_class
+ Verify::FinancialsNew
end
- def idv_finance_form
- @_idv_finance_form ||= Idv::FinanceForm.new(idv_session.params)
+ def idv_form
+ @_idv_form ||= Idv::FinanceForm.new(idv_session.params)
end
def handle_success
@@ -53,9 +71,9 @@ def handle_success
def step
@_step ||= Idv::FinancialsStep.new(
- idv_form: idv_finance_form,
+ idv_form_params: idv_form.idv_params,
idv_session: idv_session,
- params: step_params
+ vendor_validator_result: vendor_validator_result
)
end
@@ -64,11 +82,16 @@ def step_params
end
def render_form
- if step_params[:finance_type] == 'ccn'
+ if idv_form.idv_params[:ccn].present?
render :new
else
render 'verify/finance_other/new'
end
end
+
+ def vendor_params
+ finance_type = idv_form.finance_type
+ { finance_type => idv_form.idv_params[finance_type] }
+ end
end
end
diff --git a/app/controllers/verify/phone_controller.rb b/app/controllers/verify/phone_controller.rb
index 0a941349b2f..888b08ac2b9 100644
--- a/app/controllers/verify/phone_controller.rb
+++ b/app/controllers/verify/phone_controller.rb
@@ -5,6 +5,7 @@ class PhoneController < ApplicationController
before_action :confirm_step_needed
before_action :confirm_step_allowed
+ before_action :refresh_if_not_ready, only: [:show]
def new
@view_model = view_model
@@ -12,8 +13,21 @@ def new
end
def create
+ result = idv_form.submit(step_params)
+ analytics.track_event(Analytics::IDV_PHONE_CONFIRMATION_FORM, result.to_h)
+
+ if result.success?
+ submit_idv_job
+ redirect_to verify_phone_result_path
+ else
+ @view_model = view_model
+ render :new
+ end
+ end
+
+ def show
result = step.submit
- analytics.track_event(Analytics::IDV_PHONE_CONFIRMATION, result.to_h)
+ analytics.track_event(Analytics::IDV_PHONE_CONFIRMATION_VENDOR, result.to_h)
increment_step_attempts
if result.success?
@@ -26,24 +40,28 @@ def create
private
+ def submit_idv_job
+ SubmitIdvJob.new(
+ vendor_validator_class: Idv::PhoneValidator,
+ idv_session: idv_session,
+ vendor_params: idv_session.params[:phone]
+ ).call
+ end
+
def step_name
:phone
end
def step
@_step ||= Idv::PhoneStep.new(
- idv_form: idv_phone_form,
idv_session: idv_session,
- params: step_params
+ idv_form_params: idv_form.idv_params,
+ vendor_validator_result: vendor_validator_result
)
end
- def view_model(error: nil)
- Verify::PhoneNew.new(
- error: error,
- remaining_attempts: remaining_step_attempts,
- idv_form: idv_phone_form
- )
+ def view_model_class
+ Verify::PhoneNew
end
def step_params
@@ -51,11 +69,11 @@ def step_params
end
def confirm_step_needed
- redirect_to verify_review_path if idv_session.phone_confirmation == true
+ redirect_to verify_review_path if idv_session.vendor_phone_confirmation == true
end
- def idv_phone_form
- @_idv_phone_form ||= Idv::PhoneForm.new(idv_session.params, current_user)
+ def idv_form
+ @_idv_form ||= Idv::PhoneForm.new(idv_session.params, current_user)
end
end
end
diff --git a/app/controllers/verify/review_controller.rb b/app/controllers/verify/review_controller.rb
index 7eec52cf1a7..b55e8584093 100644
--- a/app/controllers/verify/review_controller.rb
+++ b/app/controllers/verify/review_controller.rb
@@ -81,7 +81,11 @@ def idv_params
end
def phone_confirmation_required?
- idv_params[:phone] != current_user.phone &&
+ normalized_phone = idv_params[:phone]
+ return false if normalized_phone.blank?
+
+ formatted_phone = PhoneFormatter.new.format(normalized_phone)
+ formatted_phone != current_user.phone &&
idv_session.address_verification_mechanism == 'phone'
end
diff --git a/app/controllers/verify/sessions_controller.rb b/app/controllers/verify/sessions_controller.rb
index 6d944c73fea..21031c957db 100644
--- a/app/controllers/verify/sessions_controller.rb
+++ b/app/controllers/verify/sessions_controller.rb
@@ -7,6 +7,8 @@ class SessionsController < ApplicationController
before_action :confirm_idv_attempts_allowed
before_action :confirm_idv_needed
before_action :confirm_step_needed, except: [:destroy]
+ before_action :initialize_idv_session, only: [:create]
+ before_action :refresh_if_not_ready, only: [:show]
delegate :attempts_exceeded?, to: :step, prefix: true
@@ -17,8 +19,20 @@ def new
end
def create
+ result = idv_form.submit(profile_params)
+ analytics.track_event(Analytics::IDV_BASIC_INFO_SUBMITTED_FORM, result.to_h)
+
+ if result.success?
+ submit_idv_job
+ redirect_to verify_session_result_path
+ else
+ process_failure
+ end
+ end
+
+ def show
result = step.submit
- analytics.track_event(Analytics::IDV_BASIC_INFO_SUBMITTED, result.to_h)
+ analytics.track_event(Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result.to_h)
if result.success?
process_success
@@ -35,6 +49,14 @@ def destroy
private
+ def submit_idv_job
+ SubmitIdvJob.new(
+ vendor_validator_class: Idv::ProfileValidator,
+ idv_session: idv_session,
+ vendor_params: idv_session.vendor_params
+ ).call
+ end
+
def step_name
:sessions
end
@@ -45,9 +67,9 @@ def confirm_step_needed
def step
@_step ||= Idv::ProfileStep.new(
- idv_form: idv_profile_form,
+ idv_form_params: idv_session.params,
idv_session: idv_session,
- params: profile_params
+ vendor_validator_result: vendor_validator_result
)
end
@@ -68,7 +90,7 @@ def process_success
end
def process_failure
- if step.duplicate_ssn?
+ if idv_form.duplicate_ssn?
flash[:error] = t('idv.errors.duplicate_ssn')
redirect_to verify_session_dupe_path
else
@@ -77,20 +99,21 @@ def process_failure
end
end
- def view_model(error: nil)
- Verify::SessionsNew.new(
- error: error,
- remaining_attempts: remaining_idv_attempts,
- idv_form: idv_profile_form
- )
+ def view_model_class
+ Verify::SessionsNew
end
- def remaining_idv_attempts
+ def remaining_step_attempts
Idv::Attempter.idv_max_attempts - current_user.idv_attempts
end
- def idv_profile_form
- @_idv_profile_form ||= Idv::ProfileForm.new((idv_session.params || {}), current_user)
+ def idv_form
+ @_idv_form ||= Idv::ProfileForm.new((idv_session.params || {}), current_user)
+ end
+
+ def initialize_idv_session
+ idv_session.params.merge!(profile_params)
+ idv_session.applicant = idv_session.vendor_params
end
def profile_params
diff --git a/app/controllers/verify/usps_controller.rb b/app/controllers/verify/usps_controller.rb
index 5a72c9b68b5..bc850e6c921 100644
--- a/app/controllers/verify/usps_controller.rb
+++ b/app/controllers/verify/usps_controller.rb
@@ -5,10 +5,7 @@ class UspsController < ApplicationController
before_action :confirm_mail_not_spammed
def index
- @applicant = idv_session.normalized_applicant_params
- decorated_usps = UspsDecorator.new(idv_session)
- @title = decorated_usps.title
- @button = decorated_usps.button
+ @decorated_usps = UspsDecorator.new(usps_mail_service)
end
def create
diff --git a/app/controllers/verify_controller.rb b/app/controllers/verify_controller.rb
index 1f62949184a..b4ac0e88992 100644
--- a/app/controllers/verify_controller.rb
+++ b/app/controllers/verify_controller.rb
@@ -1,6 +1,6 @@
class VerifyController < ApplicationController
include IdvSession
- include AccountRecoveryConcern
+ include AccountReactivationConcern
before_action :confirm_two_factor_authenticated
before_action :confirm_idv_needed, only: %i[cancel fail]
diff --git a/app/decorators/usps_decorator.rb b/app/decorators/usps_decorator.rb
index 6777bc95cc5..31e3845a556 100644
--- a/app/decorators/usps_decorator.rb
+++ b/app/decorators/usps_decorator.rb
@@ -1,8 +1,8 @@
class UspsDecorator
- attr_reader :idv_session
+ attr_reader :usps_mail_service
- def initialize(idv_session)
- @idv_session = idv_session
+ def initialize(usps_mail_service)
+ @usps_mail_service = usps_mail_service
end
def title
@@ -16,6 +16,6 @@ def button
private
def letter_already_sent?
- @idv_session.address_verification_mechanism == 'usps'
+ @usps_mail_service.any_mail_sent?
end
end
diff --git a/app/forms/idv/finance_form.rb b/app/forms/idv/finance_form.rb
index 8746f69e46d..9becfcec15d 100644
--- a/app/forms/idv/finance_form.rb
+++ b/app/forms/idv/finance_form.rb
@@ -37,6 +37,7 @@ class FinanceForm
def initialize(idv_params)
@idv_params = idv_params
+ @params = nil
finance_type = FINANCE_TYPES.find { |param| idv_params.key? param }
update_finance_values(idv_params.merge(finance_type: finance_type))
end
@@ -44,11 +45,14 @@ def initialize(idv_params)
def submit(params)
@params = params
finance_value = update_finance_values(params)
- return false unless valid?
- clear_idv_params_finance
- idv_params[finance_type] = finance_value
- true
+ success = valid?
+ if success
+ clear_idv_params_finance
+ idv_params[finance_type] = finance_value
+ end
+
+ FormResponse.new(success: success, errors: errors.messages)
end
def self.finance_other_type_choices
@@ -71,6 +75,10 @@ def self.finance_other_type_inputs
attr_writer :finance_type, *FINANCE_TYPES
+ def params
+ @params.presence || idv_params
+ end
+
def update_finance_values(params)
type = params[:finance_type]
return false unless valid_finance_type?(type)
diff --git a/app/forms/idv/phone_form.rb b/app/forms/idv/phone_form.rb
index c6161e7947f..f59e0b4a6bf 100644
--- a/app/forms/idv/phone_form.rb
+++ b/app/forms/idv/phone_form.rb
@@ -4,27 +4,28 @@ class PhoneForm
include FormPhoneValidator
attr_reader :idv_params, :user, :phone
+ attr_accessor :international_code
def initialize(idv_params, user)
@idv_params = idv_params
@user = user
- self.phone = idv_params[:phone] || user.phone
+ self.phone = (idv_params[:phone] || user.phone).phony_formatted(
+ format: :international, normalize: :US, spaces: ' '
+ )
+ self.international_code = PhoneFormatter::DEFAULT_COUNTRY
end
def submit(params)
submitted_phone = params[:phone]
- formatted_phone = submitted_phone.phony_formatted(
- format: :international, normalize: :US, spaces: ' '
- )
+ formatted_phone = PhoneFormatter.new.format(submitted_phone, country_code: international_code)
self.phone = formatted_phone
- return false unless valid?
-
- update_idv_params(formatted_phone)
+ success = valid?
+ update_idv_params(formatted_phone) if success
- true
+ FormResponse.new(success: success, errors: errors.messages)
end
private
@@ -32,7 +33,8 @@ def submit(params)
attr_writer :phone
def update_idv_params(phone)
- idv_params[:phone] = phone
+ normalized_phone = phone.gsub(/\D/, '')[1..-1]
+ idv_params[:phone] = normalized_phone
return if phone != user.phone
diff --git a/app/forms/idv/profile_form.rb b/app/forms/idv/profile_form.rb
index 720b26f6b1b..7d2912f44b5 100644
--- a/app/forms/idv/profile_form.rb
+++ b/app/forms/idv/profile_form.rb
@@ -40,6 +40,13 @@ def pii_attributes
def submit(params)
initialize_params(params)
profile.ssn_signature = ssn_signature
+
+ FormResponse.new(success: valid?, errors: errors.messages)
+ end
+
+ def duplicate_ssn?
+ return true if any_matching_ssn_signatures?(ssn_signature)
+ return true if ssn_is_duplicate_with_old_key?
end
private
@@ -59,12 +66,7 @@ def ssn_signature(key = Pii::Fingerprinter.current_key)
end
def ssn_is_unique
- errors.add :ssn, I18n.t('idv.errors.duplicate_ssn') if ssn_is_duplicate?
- end
-
- def ssn_is_duplicate?
- return true if any_matching_ssn_signatures?(ssn_signature)
- return true if ssn_is_duplicate_with_old_key?
+ errors.add :ssn, I18n.t('idv.errors.duplicate_ssn') if duplicate_ssn?
end
def ssn_is_duplicate_with_old_key?
diff --git a/app/forms/otp_delivery_selection_form.rb b/app/forms/otp_delivery_selection_form.rb
index b62e5a0d6d9..4ed925264a5 100644
--- a/app/forms/otp_delivery_selection_form.rb
+++ b/app/forms/otp_delivery_selection_form.rb
@@ -5,8 +5,9 @@ class OtpDeliverySelectionForm
validates :otp_delivery_preference, inclusion: { in: %w[sms voice] }
- def initialize(user)
+ def initialize(user, phone_to_deliver_to)
@user = user
+ @phone_to_deliver_to = phone_to_deliver_to
end
def submit(params)
@@ -27,7 +28,7 @@ def submit(params)
attr_writer :otp_delivery_preference
attr_accessor :resend
- attr_reader :success, :user
+ attr_reader :success, :user, :phone_to_deliver_to
def otp_delivery_preference_changed?
otp_delivery_preference != user.otp_delivery_preference
@@ -37,6 +38,12 @@ def extra_analytics_attributes
{
otp_delivery_preference: otp_delivery_preference,
resend: resend,
+ country_code: parsed_phone.country_code,
+ area_code: parsed_phone.area_code,
}
end
+
+ def parsed_phone
+ @_parsed_phone ||= Phonelib.parse(phone_to_deliver_to)
+ end
end
diff --git a/app/forms/two_factor_setup_form.rb b/app/forms/two_factor_setup_form.rb
index 1d648c332b2..bc8a123dc8e 100644
--- a/app/forms/two_factor_setup_form.rb
+++ b/app/forms/two_factor_setup_form.rb
@@ -1,17 +1,16 @@
class TwoFactorSetupForm
include ActiveModel::Model
include FormPhoneValidator
+ include OtpDeliveryPreferenceValidator
- attr_accessor :phone
+ attr_accessor :phone, :international_code
def initialize(user)
@user = user
end
def submit(params)
- self.phone = params[:phone].phony_formatted(
- format: :international, normalize: :US, spaces: ' '
- )
+ process_phone_number_params(params)
self.otp_delivery_preference = params[:otp_delivery_preference]
@success = valid?
@@ -21,6 +20,11 @@ def submit(params)
FormResponse.new(success: success, errors: errors.messages, extra: extra_analytics_attributes)
end
+ def process_phone_number_params(params)
+ self.international_code = params[:international_code]
+ self.phone = PhoneFormatter.new.format(params[:phone], country_code: international_code)
+ end
+
private
attr_reader :success, :user
diff --git a/app/forms/update_user_phone_form.rb b/app/forms/update_user_phone_form.rb
index 538cf17b310..16f06558ead 100644
--- a/app/forms/update_user_phone_form.rb
+++ b/app/forms/update_user_phone_form.rb
@@ -2,7 +2,7 @@ class UpdateUserPhoneForm
include ActiveModel::Model
include FormPhoneValidator
- attr_accessor :phone
+ attr_accessor :phone, :international_code
attr_reader :user
def persisted?
@@ -12,12 +12,16 @@ def persisted?
def initialize(user)
@user = user
self.phone = @user.phone
+ self.international_code = Phonelib.parse(phone).country || PhoneFormatter::DEFAULT_COUNTRY
end
def submit(params)
- check_phone_change(params)
+ self.phone = params[:phone]
+ self.international_code = params[:international_code]
- valid?
+ check_phone_change
+
+ FormResponse.new(success: valid?, errors: errors.messages)
end
def phone_changed?
@@ -28,10 +32,8 @@ def phone_changed?
attr_reader :phone_changed
- def check_phone_change(params)
- formatted_phone = params[:phone].phony_formatted(
- format: :international, normalize: :US, spaces: ' '
- )
+ def check_phone_change
+ formatted_phone = PhoneFormatter.new.format(phone, country_code: international_code)
return unless formatted_phone != @user.phone
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index 7db2fffb178..73b8c819fed 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -66,4 +66,28 @@ def us_states_territories
]
end
# rubocop:enable MethodLength, WordArray
+
+ def international_phone_codes
+ PhoneNumberCapabilities::INTERNATIONAL_CODES.map do |key, value|
+ [
+ international_phone_code_label(value),
+ key,
+ { data: international_phone_codes_data(value) },
+ ]
+ end
+ end
+
+ private
+
+ def international_phone_code_label(code_data)
+ "#{code_data['name']} +#{code_data['country_code']}"
+ end
+
+ def international_phone_codes_data(code_data)
+ {
+ sms_only: code_data['sms_only'],
+ country_code: code_data['country_code'],
+ country_name: code_data['name'],
+ }
+ end
end
diff --git a/app/helpers/locale_helper.rb b/app/helpers/locale_helper.rb
new file mode 100644
index 00000000000..ff18d64724e
--- /dev/null
+++ b/app/helpers/locale_helper.rb
@@ -0,0 +1,6 @@
+module LocaleHelper
+ def locale_url_param
+ active_locale = I18n.locale
+ active_locale == I18n.default_locale ? nil : active_locale
+ end
+end
diff --git a/app/jobs/sms_otp_sender_job.rb b/app/jobs/sms_otp_sender_job.rb
index af203aafec0..ccfe06a0d04 100644
--- a/app/jobs/sms_otp_sender_job.rb
+++ b/app/jobs/sms_otp_sender_job.rb
@@ -15,7 +15,16 @@ def otp_valid?(otp_created_at)
def send_otp(twilio_service, code, phone)
twilio_service.send_sms(
to: phone,
- body: "#{code} is your #{APP_NAME} one-time security code."
+ body: I18n.t('jobs.sms_otp_sender_job.message', code: code, app: APP_NAME)
)
+ rescue Twilio::REST::RequestError => error
+ sanitize_phone_number(error.message)
+ raise
+ end
+
+ def sanitize_phone_number(str)
+ return unless str =~ /is not a valid phone number/
+
+ str.gsub!(/\+[\d\(\)\- ]+/) { |match| match.gsub(/\d/, '#') }
end
end
diff --git a/app/jobs/vendor_validator_job.rb b/app/jobs/vendor_validator_job.rb
new file mode 100644
index 00000000000..cf0f13ac295
--- /dev/null
+++ b/app/jobs/vendor_validator_job.rb
@@ -0,0 +1,37 @@
+class VendorValidatorJob < ActiveJob::Base
+ queue_as :idv
+
+ def perform(result_id:, vendor_validator_class:, vendor:, vendor_params:, applicant_json:,
+ vendor_session_id:)
+ vendor_validator = vendor_validator_class.constantize.new(
+ applicant: Proofer::Applicant.new(JSON.parse(applicant_json, symbolize_names: true)),
+ vendor: vendor.to_sym,
+ vendor_params: indifferent_access(vendor_params),
+ vendor_session_id: vendor_session_id
+ )
+
+ VendorValidatorResultStorage.new.store(
+ result_id: result_id,
+ result: extract_result(vendor_validator.result)
+ )
+ end
+
+ private
+
+ def extract_result(result)
+ vendor_resp = result.vendor_resp
+
+ Idv::VendorResult.new(
+ success: result.success?,
+ errors: result.errors,
+ reasons: vendor_resp.reasons,
+ normalized_applicant: vendor_resp.try(:normalized_applicant),
+ session_id: result.try(:session_id)
+ )
+ end
+
+ def indifferent_access(params)
+ return params if params.is_a?(String)
+ params.with_indifferent_access
+ end
+end
diff --git a/app/mailers/custom_devise_mailer.rb b/app/mailers/custom_devise_mailer.rb
index efed6f6b003..ca9cfb44757 100644
--- a/app/mailers/custom_devise_mailer.rb
+++ b/app/mailers/custom_devise_mailer.rb
@@ -1,14 +1,21 @@
class CustomDeviseMailer < Devise::Mailer
include Mailable
+ include LocaleHelper
before_action :attach_images
layout 'layouts/user_mailer'
default from: email_with_name(Figaro.env.email_from, Figaro.env.email_from)
+ def reset_password_instructions(*)
+ @locale = locale_url_param
+ super
+ end
+
def confirmation_instructions(record, token, options = {})
presenter = ConfirmationEmailPresenter.new(record, view_context)
@first_sentence = presenter.first_sentence
@confirmation_period = presenter.confirmation_period
@request_id = options[:request_id]
+ @locale = locale_url_param
super
end
end
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 6be2598e599..aff54835ba5 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -1,5 +1,6 @@
class UserMailer < ActionMailer::Base
include Mailable
+ include LocaleHelper
before_action :attach_images
default from: email_with_name(Figaro.env.email_from, Figaro.env.email_from)
@@ -8,8 +9,7 @@ def email_changed(old_email)
end
def signup_with_your_email(email)
- @root_url = root_url
- @new_user_password_url = new_user_password_url
+ @root_url = root_url(locale: locale_url_param)
mail(to: email, subject: t('mailer.email_reuse_notice.subject'))
end
diff --git a/app/presenters/two_factor_auth_code/authenticator_delivery_presenter.rb b/app/presenters/two_factor_auth_code/authenticator_delivery_presenter.rb
index fbf6a2736e3..f7505b5bd20 100644
--- a/app/presenters/two_factor_auth_code/authenticator_delivery_presenter.rb
+++ b/app/presenters/two_factor_auth_code/authenticator_delivery_presenter.rb
@@ -5,7 +5,7 @@ def header
end
def help_text
- t("instructions.2fa.#{two_factor_authentication_method}.confirm_code_html",
+ t("instructions.mfa.#{two_factor_authentication_method}.confirm_code_html",
email: content_tag(:strong, user_email),
app: content_tag(:strong, APP_NAME),
tooltip: view.tooltip(t('tooltips.authentication_app')))
diff --git a/app/presenters/two_factor_auth_code/phone_delivery_presenter.rb b/app/presenters/two_factor_auth_code/phone_delivery_presenter.rb
index 411e52f9019..49c729bc0cf 100644
--- a/app/presenters/two_factor_auth_code/phone_delivery_presenter.rb
+++ b/app/presenters/two_factor_auth_code/phone_delivery_presenter.rb
@@ -5,7 +5,7 @@ def header
end
def help_text
- t("instructions.2fa.#{otp_delivery_preference}.confirm_code_html",
+ t("instructions.mfa.#{otp_delivery_preference}.confirm_code_html",
number: phone_number_tag,
resend_code_link: resend_code_link)
end
@@ -34,6 +34,7 @@ def cancel_link
:phone_number,
:unconfirmed_phone,
:otp_delivery_preference,
+ :voice_otp_delivery_unsupported,
:confirmation_for_phone_change
)
@@ -42,14 +43,26 @@ def phone_number_tag
end
def otp_fallback_options
- safe_join([phone_fallback_link, auth_app_fallback_link])
+ if totp_enabled
+ otp_fallback_options_with_totp
+ elsif !voice_otp_delivery_unsupported
+ safe_join([phone_fallback_link, '.'])
+ end
+ end
+
+ def otp_fallback_options_with_totp
+ if voice_otp_delivery_unsupported
+ safe_join([auth_app_fallback_tag, '.'])
+ else
+ safe_join([phone_fallback_link, auth_app_fallback_link])
+ end
end
def update_phone_link
return unless unconfirmed_phone
link = view.link_to(t('forms.two_factor.try_again'), reenter_phone_number_path)
- t('instructions.2fa.wrong_number_html', link: link)
+ t('instructions.mfa.wrong_number_html', link: link)
end
def phone_fallback_link
@@ -64,15 +77,9 @@ def phone_link_tag
end
def auth_app_fallback_link
- return empty unless totp_enabled
-
t('links.phone_confirmation.auth_app_fallback_html', link: auth_app_fallback_tag)
end
- def empty
- '.'
- end
-
def auth_app_fallback_tag
view.link_to(
t('links.two_factor_authentication.app'),
@@ -81,7 +88,7 @@ def auth_app_fallback_tag
end
def fallback_instructions
- "instructions.2fa.#{otp_delivery_preference}.fallback_html"
+ "instructions.mfa.#{otp_delivery_preference}.fallback_html"
end
def fallback_method
diff --git a/app/services/analytics.rb b/app/services/analytics.rb
index 96811367afd..6d59735fcbe 100644
--- a/app/services/analytics.rb
+++ b/app/services/analytics.rb
@@ -38,14 +38,17 @@ def uuid
EMAIL_CONFIRMATION = 'Email Confirmation'.freeze
EMAIL_CONFIRMATION_RESEND = 'Email Confirmation requested due to invalid token'.freeze
IDV_BASIC_INFO_VISIT = 'IdV: basic info visited'.freeze
- IDV_BASIC_INFO_SUBMITTED = 'IdV: basic info submitted'.freeze
+ IDV_BASIC_INFO_SUBMITTED_FORM = 'IdV: basic info form submitted'.freeze
+ IDV_BASIC_INFO_SUBMITTED_VENDOR = 'IdV: basic info vendor submitted'.freeze
IDV_MAX_ATTEMPTS_EXCEEDED = 'IdV: max attempts exceeded'.freeze
IDV_FINAL = 'IdV: final resolution'.freeze
IDV_FINANCE_CCN_VISIT = 'IdV: finance ccn visited'.freeze
- IDV_FINANCE_CONFIRMATION = 'IdV: finance confirmation'.freeze
+ IDV_FINANCE_CONFIRMATION_FORM = 'IdV: finance confirmation form'.freeze
+ IDV_FINANCE_CONFIRMATION_VENDOR = 'IdV: finance confirmation vendor'.freeze
IDV_FINANCE_OTHER_VISIT = 'IdV: finance other visited'.freeze
IDV_INTRO_VISIT = 'IdV: intro visited'.freeze
- IDV_PHONE_CONFIRMATION = 'IdV: phone confirmation'.freeze
+ IDV_PHONE_CONFIRMATION_FORM = 'IdV: phone confirmation form'.freeze
+ IDV_PHONE_CONFIRMATION_VENDOR = 'IdV: phone confirmation vendor'.freeze
IDV_PHONE_RECORD_VISIT = 'IdV: phone of record visited'.freeze
IDV_REVIEW_COMPLETE = 'IdV: review complete'.freeze
IDV_REVIEW_VISIT = 'IdV: review info visited'.freeze
diff --git a/app/services/idv/financials_step.rb b/app/services/idv/financials_step.rb
index cffa4264dd0..0979372b0b4 100644
--- a/app/services/idv/financials_step.rb
+++ b/app/services/idv/financials_step.rb
@@ -4,7 +4,7 @@ def submit
if complete?
@success = true
idv_session.financials_confirmation = true
- idv_session.params = idv_form.idv_params
+ idv_session.params = idv_form_params
else
@success = false
idv_session.financials_confirmation = false
@@ -13,29 +13,12 @@ def submit
FormResponse.new(success: success, errors: errors)
end
- def form_valid_but_vendor_validation_failed?
- form_valid? && !vendor_validation_passed?
- end
-
private
attr_reader :success
def complete?
- form_valid? && vendor_validation_passed?
- end
-
- def vendor_validator_class
- Idv::FinancialsValidator
- end
-
- def vendor_reasons
- vendor_validator.reasons if form_valid?
- end
-
- def vendor_params
- finance_type = idv_form.finance_type
- { finance_type => idv_form.idv_params[finance_type] }
+ vendor_validation_passed?
end
end
end
diff --git a/app/services/idv/financials_validator.rb b/app/services/idv/financials_validator.rb
index d4c4746b0c2..187dcabd348 100644
--- a/app/services/idv/financials_validator.rb
+++ b/app/services/idv/financials_validator.rb
@@ -2,13 +2,9 @@ module Idv
class FinancialsValidator < VendorValidator
private
- def session_id
- idv_session.vendor_session_id
- end
-
def try_submit
try_agent_action do
- idv_agent.submit_financials(vendor_params, session_id)
+ idv_agent.submit_financials(vendor_params, vendor_session_id)
end
end
end
diff --git a/app/services/idv/phone_step.rb b/app/services/idv/phone_step.rb
index 252b6f86f5d..eb4954ff099 100644
--- a/app/services/idv/phone_step.rb
+++ b/app/services/idv/phone_step.rb
@@ -4,38 +4,23 @@ def submit
if complete?
update_idv_session
else
- idv_session.phone_confirmation = false
+ idv_session.vendor_phone_confirmation = false
end
FormResponse.new(success: complete?, errors: errors)
end
- def form_valid_but_vendor_validation_failed?
- form_valid? && !vendor_validation_passed?
- end
-
private
def complete?
- form_valid? && vendor_validation_passed?
- end
-
- def vendor_validator_class
- Idv::PhoneValidator
- end
-
- def vendor_params
- idv_form.phone
- end
-
- def vendor_reasons
- vendor_validator.reasons if form_valid?
+ vendor_validation_passed?
end
def update_idv_session
- idv_session.phone_confirmation = true
+ idv_session.vendor_phone_confirmation = true
idv_session.address_verification_mechanism = :phone
- idv_session.params = idv_form.idv_params
+ idv_session.params = idv_form_params
+ idv_session.user_phone_confirmation = idv_form_params[:phone_confirmed_at].present?
end
end
end
diff --git a/app/services/idv/phone_validator.rb b/app/services/idv/phone_validator.rb
index e6bb1d3188a..7e4b26cc009 100644
--- a/app/services/idv/phone_validator.rb
+++ b/app/services/idv/phone_validator.rb
@@ -2,13 +2,9 @@ module Idv
class PhoneValidator < VendorValidator
private
- def session_id
- idv_session.vendor_session_id
- end
-
def try_submit
try_agent_action do
- idv_agent.submit_phone(vendor_params, session_id)
+ idv_agent.submit_phone(vendor_params, vendor_session_id)
end
end
end
diff --git a/app/services/idv/profile_step.rb b/app/services/idv/profile_step.rb
index 3e92d2314e6..371e6d2a71a 100644
--- a/app/services/idv/profile_step.rb
+++ b/app/services/idv/profile_step.rb
@@ -1,12 +1,9 @@
module Idv
class ProfileStep < Step
def submit
- initialize_idv_session
- submit_idv_form
-
@success = complete?
- increment_attempts_count if form_valid?
+ increment_attempts_count
update_idv_session if success
FormResponse.new(success: success, errors: errors, extra: extra_analytics_attributes)
@@ -16,55 +13,26 @@ def attempts_exceeded?
attempter.exceeded?
end
- def duplicate_ssn?
- errors.key?(:ssn) && errors[:ssn].include?(I18n.t('idv.errors.duplicate_ssn'))
- end
-
- def form_valid_but_vendor_validation_failed?
- form_valid? && !vendor_validation_passed?
- end
-
private
attr_reader :success
- def initialize_idv_session
- idv_session.params.merge!(params)
- idv_session.applicant = vendor_params
- end
-
- def vendor_params
- idv_session.vendor_params
- end
-
- def submit_idv_form
- idv_form.submit(params)
- end
-
def complete?
- !attempts_exceeded? && form_valid? && vendor_validation_passed?
+ !attempts_exceeded? && vendor_validation_passed?
end
def attempter
- @_idv_attempter ||= Idv::Attempter.new(idv_form.user)
+ @_idv_attempter ||= Idv::Attempter.new(idv_session.current_user)
end
def increment_attempts_count
attempter.increment
end
- def form_valid?
- @_form_valid ||= idv_form.valid?
- end
-
- def vendor_validator_class
- Idv::ProfileValidator
- end
-
def update_idv_session
idv_session.profile_confirmation = true
- idv_session.vendor_session_id = vendor_validator.session_id
- idv_session.normalized_applicant_params = vendor_validator.normalized_applicant.to_hash
+ idv_session.vendor_session_id = vendor_validator_result.session_id
+ idv_session.normalized_applicant_params = vendor_validator_result.normalized_applicant.to_hash
idv_session.resolution_successful = true
end
@@ -76,7 +44,7 @@ def extra_analytics_attributes
end
def vendor_reasons
- vendor_validator.reasons if form_valid?
+ vendor_validator_result.reasons
end
end
end
diff --git a/app/services/idv/profile_validator.rb b/app/services/idv/profile_validator.rb
index eb1cd5ce051..f3c72a064e3 100644
--- a/app/services/idv/profile_validator.rb
+++ b/app/services/idv/profile_validator.rb
@@ -1,15 +1,9 @@
module Idv
class ProfileValidator < VendorValidator
- delegate :session_id, to: :result
-
def result
@_result ||= try_start
end
- def normalized_applicant
- result.vendor_resp.normalized_applicant
- end
-
private
def try_start
diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb
index 188ef878467..269517ffba9 100644
--- a/app/services/idv/session.rb
+++ b/app/services/idv/session.rb
@@ -1,12 +1,15 @@
module Idv
class Session
VALID_SESSION_ATTRIBUTES = %i[
+ async_result_id
+ async_result_started_at
address_verification_mechanism
applicant
financials_confirmation
normalized_applicant_params
params
- phone_confirmation
+ vendor_phone_confirmation
+ user_phone_confirmation
pii
profile_confirmation
profile_id
@@ -17,11 +20,13 @@ class Session
vendor_session_id
].freeze
+ attr_reader :current_user
+
def initialize(user_session:, current_user:, issuer:)
@user_session = user_session
@current_user = current_user
@issuer = issuer
- @user_session[:idv] ||= new_idv_session
+ set_idv_session
end
def method_missing(method_sym, *arguments, &block)
@@ -67,8 +72,12 @@ def clear
user_session.delete(:idv)
end
+ def phone_confirmed?
+ vendor_phone_confirmation == true && user_phone_confirmation == true
+ end
+
def complete_session
- complete_profile if phone_confirmation == true
+ complete_profile if phone_confirmed?
create_usps_entry if address_verification_mechanism == 'usps'
end
@@ -91,12 +100,17 @@ def alive?
end
def address_mechanism_chosen?
- phone_confirmation == true || address_verification_mechanism == 'usps'
+ vendor_phone_confirmation == true || address_verification_mechanism == 'usps'
end
private
- attr_accessor :user_session, :current_user, :issuer
+ attr_accessor :user_session, :issuer
+
+ def set_idv_session
+ return if session.present?
+ user_session[:idv] = new_idv_session
+ end
def new_idv_session
{ params: {}, step_attempts: { financials: 0, phone: 0 } }
@@ -108,7 +122,7 @@ def move_pii_to_user_session
end
def session
- user_session[:idv]
+ user_session.fetch(:idv, {})
end
def applicant_params
@@ -123,7 +137,7 @@ def profile_maker
@_profile_maker ||= Idv::ProfileMaker.new(
applicant: Proofer::Applicant.new(applicant_params),
normalized_applicant: Proofer::Applicant.new(normalized_applicant_params),
- phone_confirmed: phone_confirmation || false,
+ phone_confirmed: vendor_phone_confirmation || false,
user: current_user,
vendor: vendor
)
diff --git a/app/services/idv/step.rb b/app/services/idv/step.rb
index 72e93cd0924..06125e4f933 100644
--- a/app/services/idv/step.rb
+++ b/app/services/idv/step.rb
@@ -1,51 +1,31 @@
# abstract base class for Idv Steps
module Idv
class Step
- def initialize(idv_form:, idv_session:, params:)
- @idv_form = idv_form
+ def initialize(idv_session:, idv_form_params:, vendor_validator_result:)
+ @idv_form_params = idv_form_params
@idv_session = idv_session
- @params = params
+ @vendor_validator_result = vendor_validator_result
end
- def form_valid?
- form_validate(params)
+ def vendor_validation_passed?
+ vendor_validator_result.success?
+ end
+
+ def vendor_validation_timed_out?
+ vendor_validator_result.timed_out?
end
private
attr_accessor :idv_session
- attr_reader :idv_form, :params
-
- def form_validate(params)
- @form_result ||= idv_form.submit(params)
- end
-
- def vendor_validation_passed?
- vendor_validator.success?
- end
+ attr_reader :idv_form_params, :vendor_validator_result
def errors
- errors = idv_form.errors.messages.dup
- return errors unless form_valid? && vendor_errors
- merge_vendor_errors(errors)
- end
-
- def merge_vendor_errors(errors)
- vendor_errors.each_with_object(errors) do |(key, value), errs|
- value = [value] unless value.is_a?(Array)
- errs[key] = value
+ @_errors ||= begin
+ vendor_validator_result.errors.each_with_object({}) do |(key, value), errs|
+ errs[key] = Array(value)
+ end
end
end
-
- def vendor_errors
- @_vendor_errors ||= vendor_validator.errors
- end
-
- def vendor_validator
- @_vendor_validator ||= vendor_validator_class.new(
- idv_session: idv_session,
- vendor_params: vendor_params
- )
- end
end
end
diff --git a/app/services/idv/usps_mail.rb b/app/services/idv/usps_mail.rb
index dadb08f4b73..26b9193049f 100644
--- a/app/services/idv/usps_mail.rb
+++ b/app/services/idv/usps_mail.rb
@@ -12,6 +12,10 @@ def mail_spammed?
max_events? && updated_within_last_month?
end
+ def any_mail_sent?
+ user_mail_events.any?
+ end
+
private
attr_reader :current_user
diff --git a/app/services/idv/vendor_result.rb b/app/services/idv/vendor_result.rb
new file mode 100644
index 00000000000..a6975a50c21
--- /dev/null
+++ b/app/services/idv/vendor_result.rb
@@ -0,0 +1,32 @@
+module Idv
+ class VendorResult
+ attr_reader :success, :errors, :reasons, :session_id, :normalized_applicant, :timed_out
+
+ def self.new_from_json(json)
+ parsed = JSON.parse(json, symbolize_names: true)
+
+ applicant = parsed[:normalized_applicant]
+ parsed[:normalized_applicant] = Proofer::Applicant.new(applicant) if applicant
+
+ new(**parsed)
+ end
+
+ def initialize(success: nil, errors: {}, reasons: [], session_id: nil,
+ normalized_applicant: nil, timed_out: nil)
+ @success = success
+ @errors = errors
+ @reasons = reasons
+ @session_id = session_id
+ @normalized_applicant = normalized_applicant
+ @timed_out = timed_out
+ end
+
+ def success?
+ success
+ end
+
+ def timed_out?
+ timed_out
+ end
+ end
+end
diff --git a/app/services/idv/vendor_validator.rb b/app/services/idv/vendor_validator.rb
index bb0ca462b9e..8b0c6222101 100644
--- a/app/services/idv/vendor_validator.rb
+++ b/app/services/idv/vendor_validator.rb
@@ -1,35 +1,28 @@
# abstract base class for proofing vendor validation
module Idv
class VendorValidator
- delegate :success?, :errors, to: :result
- attr_reader :idv_session, :vendor_params
+ attr_reader :applicant, :vendor, :vendor_params, :vendor_session_id
- def initialize(idv_session:, vendor_params:)
- @idv_session = idv_session
+ def initialize(applicant:, vendor:, vendor_params:, vendor_session_id:)
+ @applicant = applicant
+ @vendor = vendor
@vendor_params = vendor_params
+ @vendor_session_id = vendor_session_id
end
- def reasons
- result.vendor_resp.reasons
+ def result
+ @_result ||= try_submit
end
private
- def idv_vendor
- @_idv_vendor ||= Idv::Vendor.new
- end
-
def idv_agent
@_agent ||= Idv::Agent.new(
- applicant: idv_session.applicant,
- vendor: (idv_session.vendor || idv_vendor.pick)
+ applicant: applicant,
+ vendor: vendor
)
end
- def result
- @_result ||= try_submit
- end
-
def try_agent_action
yield
rescue => err
diff --git a/app/services/locale_chooser.rb b/app/services/locale_chooser.rb
new file mode 100644
index 00000000000..8b8676e6a29
--- /dev/null
+++ b/app/services/locale_chooser.rb
@@ -0,0 +1,21 @@
+class LocaleChooser
+ include HttpAcceptLanguage::EasyAccess
+
+ def initialize(locale_param, request)
+ @locale_param = locale_param
+ @request = request
+ end
+
+ def locale
+ return locale_param if locale_valid?
+ http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale
+ end
+
+ private
+
+ attr_reader :locale_param, :request
+
+ def locale_valid?
+ LocaleValidator.new(locale_param).success?
+ end
+end
diff --git a/app/services/locale_validator.rb b/app/services/locale_validator.rb
new file mode 100644
index 00000000000..b1e6f1bb59b
--- /dev/null
+++ b/app/services/locale_validator.rb
@@ -0,0 +1,13 @@
+class LocaleValidator
+ def initialize(locale)
+ @locale = locale
+ end
+
+ def success?
+ locale.present? && I18n.available_locales.include?(locale.to_sym)
+ end
+
+ private
+
+ attr_reader :locale
+end
diff --git a/app/services/marketing_site.rb b/app/services/marketing_site.rb
index ccbfa81b344..776fdb339cd 100644
--- a/app/services/marketing_site.rb
+++ b/app/services/marketing_site.rb
@@ -1,23 +1,28 @@
class MarketingSite
BASE_URL = URI('https://www.login.gov').freeze
+ def self.locale_segment
+ active_locale = I18n.locale
+ active_locale == I18n.default_locale ? '/' : "/#{active_locale}/"
+ end
+
def self.base_url
- BASE_URL.to_s
+ URI.join(BASE_URL, locale_segment).to_s
end
def self.privacy_url
- URI.join(BASE_URL, '/policy').to_s
+ URI.join(BASE_URL, locale_segment, 'policy').to_s
end
def self.contact_url
- URI.join(BASE_URL, '/contact').to_s
+ URI.join(BASE_URL, locale_segment, 'contact').to_s
end
def self.help_url
- URI.join(BASE_URL, '/help').to_s
+ URI.join(BASE_URL, locale_segment, 'help').to_s
end
def self.help_authenticator_app_url
- URI.join(BASE_URL, '/help/signing-in/what-is-an-authenticator-app/').to_s
+ URI.join(BASE_URL, locale_segment, 'help/signing-in/what-is-an-authenticator-app/').to_s
end
end
diff --git a/app/services/null_service_provider_request.rb b/app/services/null_service_provider_request.rb
index ed799a2bbbc..6d5f732b755 100644
--- a/app/services/null_service_provider_request.rb
+++ b/app/services/null_service_provider_request.rb
@@ -4,4 +4,10 @@ def uuid; end
def issuer; end
def delete; end
+
+ def url; end
+
+ def loa; end
+
+ def requested_attributes; end
end
diff --git a/app/services/otp_rate_limiter.rb b/app/services/otp_rate_limiter.rb
index 1fb7bc29dcf..b0287467a20 100644
--- a/app/services/otp_rate_limiter.rb
+++ b/app/services/otp_rate_limiter.rb
@@ -32,10 +32,8 @@ def lock_out_user
end
def increment
- now = Time.zone.now
-
entry_for_current_phone.otp_send_count += 1
- entry_for_current_phone.otp_last_sent_at = now
+ entry_for_current_phone.otp_last_sent_at = Time.zone.now
entry_for_current_phone.save!
end
diff --git a/app/services/phone_formatter.rb b/app/services/phone_formatter.rb
new file mode 100644
index 00000000000..50f9a376116
--- /dev/null
+++ b/app/services/phone_formatter.rb
@@ -0,0 +1,12 @@
+class PhoneFormatter
+ DEFAULT_COUNTRY = 'US'.freeze
+
+ def format(phone, country_code: nil)
+ normalized_phone = if country_code
+ phone&.phony_normalized(country_code: country_code)
+ else
+ phone&.phony_normalized(default_country_code: DEFAULT_COUNTRY)
+ end
+ normalized_phone&.phony_formatted(format: :international, spaces: ' ')
+ end
+end
diff --git a/app/services/phone_number_capabilities.rb b/app/services/phone_number_capabilities.rb
new file mode 100644
index 00000000000..559bf217253
--- /dev/null
+++ b/app/services/phone_number_capabilities.rb
@@ -0,0 +1,73 @@
+class PhoneNumberCapabilities
+ VOICE_UNSUPPORTED_US_AREA_CODES = {
+ '648' => 'American Samoa',
+ '264' => 'Anguilla',
+ '268' => 'Antigua and Barbuda',
+ '246' => 'Barbados',
+ '441' => 'Bermuda',
+ '345' => 'Cayman Islands',
+ '767' => 'Dominica',
+ '809' => 'Dominican Republic',
+ '473' => 'Grenada',
+ '671' => 'Guam',
+ '876' => 'Jamaica',
+ '670' => 'Northern Mariana Islands',
+ '869' => 'Saint Kitts and Nevis',
+ '758' => 'Saint Lucia',
+ '784' => 'Saint Vincent Grenadines',
+ '868' => 'Trinidad and Tobago',
+ '649' => 'Turks and Caicos Islands',
+ '284' => 'British Virgin Islands',
+ '340' => 'United States Virgin Islands',
+ }.freeze
+
+ INTERNATIONAL_CODES = YAML.load_file(
+ Rails.root.join('config', 'country_dialing_codes.yml')
+ ).freeze
+
+ attr_reader :phone
+
+ def initialize(phone)
+ @phone = phone
+ end
+
+ def sms_only?
+ if international_code == '1'
+ VOICE_UNSUPPORTED_US_AREA_CODES[area_code].present?
+ elsif country_code_data
+ country_code_data['sms_only']
+ end
+ end
+
+ def unsupported_location
+ if international_code == '1'
+ VOICE_UNSUPPORTED_US_AREA_CODES[area_code]
+ elsif country_code_data
+ country_code_data['name']
+ end
+ end
+
+ private
+
+ def area_code
+ @area_code ||= phone_number_components.second
+ end
+
+ def country_code_data
+ @country_code_data ||= INTERNATIONAL_CODES.select do |_, value|
+ value['country_code'] == international_code
+ end.values.first
+ end
+
+ def international_code
+ @international_code ||= phone_number_components.first
+ end
+
+ def phone_number_components
+ return [] if phone.blank?
+
+ @phone_number_components ||= Phony.split(
+ PhonyRails.normalize_number(phone.to_s, default_country_code: :us).slice(1..-1)
+ )
+ end
+end
diff --git a/app/services/store_sp_metadata_in_session.rb b/app/services/store_sp_metadata_in_session.rb
index 785c2a101eb..120c9d3e044 100644
--- a/app/services/store_sp_metadata_in_session.rb
+++ b/app/services/store_sp_metadata_in_session.rb
@@ -19,7 +19,7 @@ def call
attr_reader :session, :request_id
def sp_request
- @sp_request ||= ServiceProviderRequest.find_by(uuid: request_id)
+ @sp_request ||= ServiceProviderRequest.from_uuid(request_id)
end
def loa3_requested?
diff --git a/app/services/submit_idv_job.rb b/app/services/submit_idv_job.rb
new file mode 100644
index 00000000000..17f932b0870
--- /dev/null
+++ b/app/services/submit_idv_job.rb
@@ -0,0 +1,37 @@
+class SubmitIdvJob
+ def initialize(vendor_validator_class:, idv_session:, vendor_params:)
+ @vendor_validator_class = vendor_validator_class
+ @idv_session = idv_session
+ @vendor_params = vendor_params
+ end
+
+ def call
+ update_idv_session
+
+ VendorValidatorJob.perform_later(
+ result_id: result_id,
+ vendor_validator_class: vendor_validator_class.to_s,
+ vendor: vendor.to_s,
+ vendor_params: vendor_params,
+ vendor_session_id: idv_session.vendor_session_id,
+ applicant_json: idv_session.applicant.to_json
+ )
+ end
+
+ private
+
+ attr_reader :vendor_validator_class, :idv_session, :vendor_params
+
+ def result_id
+ @_result_id ||= SecureRandom.uuid
+ end
+
+ def update_idv_session
+ idv_session.async_result_id = result_id
+ idv_session.async_result_started_at = Time.zone.now.to_i
+ end
+
+ def vendor
+ idv_session.vendor || Idv::Vendor.new.pick
+ end
+end
diff --git a/app/services/usps_confirmation_maker.rb b/app/services/usps_confirmation_maker.rb
index 1fb164a1597..e9a3ddf8af5 100644
--- a/app/services/usps_confirmation_maker.rb
+++ b/app/services/usps_confirmation_maker.rb
@@ -17,14 +17,14 @@ def perform
# This method is single statement spread across many lines for readability
def attributes
{
- address1: pii[:address1],
- address2: pii[:address2],
- city: pii[:city],
- otp: pii[:otp],
- first_name: pii[:first_name],
- last_name: pii[:last_name],
- state: pii[:state],
- zipcode: pii[:zipcode],
+ address1: pii[:address1].norm,
+ address2: pii[:address2].norm,
+ city: pii[:city].norm,
+ otp: pii[:otp].norm,
+ first_name: pii[:first_name].norm,
+ last_name: pii[:last_name].norm,
+ state: pii[:state].norm,
+ zipcode: pii[:zipcode].norm,
issuer: issuer,
}
end
diff --git a/app/services/usps_uploader.rb b/app/services/usps_uploader.rb
new file mode 100644
index 00000000000..6b0749a07ae
--- /dev/null
+++ b/app/services/usps_uploader.rb
@@ -0,0 +1,43 @@
+class UspsUploader
+ def run
+ build_file
+ upload_file
+ clear_file
+ rescue => error
+ NewRelic::Agent.notice_error(error)
+ end
+
+ # @api private
+ def local_path
+ @_local_path ||= begin
+ timestamp = Time.zone.now.strftime('%Y%m%d%H%M%S')
+ Rails.root.join('tmp', "batch-#{timestamp}.pgp")
+ end
+ end
+
+ private
+
+ def build_file
+ UspsExporter.new(local_path).run
+ end
+
+ def upload_file
+ env = Figaro.env
+
+ Net::SFTP.start(
+ env.equifax_sftp_host,
+ env.equifax_sftp_username,
+ key_data: [RequestKeyManager.equifax_ssh_key.to_pem]
+ ) do |sftp|
+ sftp.upload!(local_path.to_s, remote_path)
+ end
+ end
+
+ def clear_file
+ FileUtils.rm(local_path)
+ end
+
+ def remote_path
+ File.join(Figaro.env.equifax_sftp_directory, 'batch.pgp')
+ end
+end
diff --git a/app/services/vendor_validator_result_storage.rb b/app/services/vendor_validator_result_storage.rb
new file mode 100644
index 00000000000..098ba2422ff
--- /dev/null
+++ b/app/services/vendor_validator_result_storage.rb
@@ -0,0 +1,24 @@
+class VendorValidatorResultStorage
+ TTL = Figaro.env.session_timeout_in_minutes.to_i.minutes.seconds.to_i
+
+ def store(result_id:, result:)
+ Sidekiq.redis do |redis|
+ redis.setex(redis_key(result_id), TTL, result.to_json)
+ end
+ end
+
+ def load(result_id)
+ result_json = Sidekiq.redis do |redis|
+ redis.get(redis_key(result_id))
+ end
+
+ return unless result_json
+
+ Idv::VendorResult.new_from_json(result_json)
+ end
+
+ # @api private
+ def redis_key(result_id)
+ "vendor-validator-result-#{result_id}"
+ end
+end
diff --git a/app/validators/form_password_validator.rb b/app/validators/form_password_validator.rb
index 23c6b9a9a4f..6997842b5a5 100644
--- a/app/validators/form_password_validator.rb
+++ b/app/validators/form_password_validator.rb
@@ -40,11 +40,15 @@ def zxcvbn_feedback
feedback = @pass_score.feedback.values.flatten.reject(&:empty?)
feedback.map do |error|
- I18n.t("zxcvbn.feedback.#{error.tr('.', '_')}")
+ I18n.t("zxcvbn.feedback.#{i18n_key(error)}")
end.join('. ').gsub(/\.\s*\./, '.')
end
def password_strength_enabled?
@enabled ||= FeatureManagement.password_strength_enabled?
end
+
+ def i18n_key(key)
+ key.tr(' -', '_').gsub(/\W/, '').downcase
+ end
end
diff --git a/app/validators/form_phone_validator.rb b/app/validators/form_phone_validator.rb
index eef673377a3..164b12bfc57 100644
--- a/app/validators/form_phone_validator.rb
+++ b/app/validators/form_phone_validator.rb
@@ -3,8 +3,11 @@ module FormPhoneValidator
included do
validates_plausible_phone :phone,
- country_code: 'US',
presence: true,
- message: :improbable_phone
+ message: :improbable_phone,
+ international_code: ->(form) { form.international_code }
+ validates :international_code, inclusion: {
+ in: PhoneNumberCapabilities::INTERNATIONAL_CODES.keys,
+ }
end
end
diff --git a/app/validators/otp_delivery_preference_validator.rb b/app/validators/otp_delivery_preference_validator.rb
new file mode 100644
index 00000000000..b6f8ae643e2
--- /dev/null
+++ b/app/validators/otp_delivery_preference_validator.rb
@@ -0,0 +1,20 @@
+module OtpDeliveryPreferenceValidator
+ extend ActiveSupport::Concern
+
+ included do
+ validate :otp_delivery_preference_supported
+ end
+
+ def otp_delivery_preference_supported
+ capabilities = PhoneNumberCapabilities.new(phone)
+ return unless otp_delivery_preference == 'voice' && capabilities.sms_only?
+
+ errors.add(
+ :phone,
+ I18n.t(
+ 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ location: capabilities.unsupported_location
+ )
+ )
+ end
+end
diff --git a/app/view_models/verify/base.rb b/app/view_models/verify/base.rb
index 84e4bd669ef..46d0c291aa0 100644
--- a/app/view_models/verify/base.rb
+++ b/app/view_models/verify/base.rb
@@ -2,16 +2,17 @@ module Verify
class Base
include Rails.application.routes.url_helpers
- def initialize(error: nil, remaining_attempts:, idv_form:)
+ def initialize(error: nil, remaining_attempts:, idv_form:, timed_out: nil)
@error = error
@remaining_attempts = remaining_attempts
@idv_form = idv_form
+ @timed_out = timed_out
end
attr_reader :error, :remaining_attempts, :idv_form
def mock_vendor_partial
- if idv_vendor.pick == :mock
+ if FeatureManagement.no_pii_mode?
'verify/sessions/no_pii_warning'
else
'shared/null'
@@ -39,6 +40,7 @@ def warning_partial
end
def message
+ return html_paragraph(text: I18n.t("idv.modal.#{step_name}.timeout")) if timed_out?
html_paragraph(text: I18n.t("idv.modal.#{step_name}.#{error}")) if error
end
@@ -56,14 +58,14 @@ def flash_message
flash_heading = html_paragraph(
text: I18n.t("idv.modal.#{step_name}.heading"), css_class: 'mb2 fs-20p'
)
- flash_body = html_paragraph(text: I18n.t("idv.modal.#{step_name}.#{error}"))
+ flash_body = message
flash_heading + flash_body + attempts
end
private
- def idv_vendor
- @_idv_vendor ||= Idv::Vendor.new
+ def timed_out?
+ @timed_out
end
def button_link_text
diff --git a/app/views/devise/mailer/confirmation_instructions.html.slim b/app/views/devise/mailer/confirmation_instructions.html.slim
index f35fa928af9..e95edad6caf 100644
--- a/app/views/devise/mailer/confirmation_instructions.html.slim
+++ b/app/views/devise/mailer/confirmation_instructions.html.slim
@@ -12,15 +12,15 @@ table.button.expanded.large.radius
center
= link_to t('mailer.confirmation_instructions.link_text'), \
sign_up_create_email_confirmation_url(_request_id: \
- @request_id, confirmation_token: @token), \
+ @request_id, confirmation_token: @token, locale: @locale), \
target: '_blank', \
class: 'float-center', align: 'center'
td.expander
p = link_to sign_up_create_email_confirmation_url(_request_id: @request_id, \
- confirmation_token: @token), \
+ confirmation_token: @token, locale: @locale), \
sign_up_create_email_confirmation_url(_request_id: @request_id, \
- confirmation_token: @token), target: '_blank'
+ confirmation_token: @token, locale: @locale), target: '_blank'
table.spacer
tbody
diff --git a/app/views/devise/mailer/reset_password_instructions.html.slim b/app/views/devise/mailer/reset_password_instructions.html.slim
index a20f9ed0ede..71674e9e400 100644
--- a/app/views/devise/mailer/reset_password_instructions.html.slim
+++ b/app/views/devise/mailer/reset_password_instructions.html.slim
@@ -11,13 +11,13 @@ table.button.expanded.large.radius
td
center
= link_to t('mailer.reset_password.link_text'),
- edit_password_url(@resource, reset_password_token: @token),
+ edit_password_url(@resource, reset_password_token: @token, locale: @locale),
target: '_blank', class: 'float-center', align: 'center'
td.expander
p
- = link_to edit_password_url(@resource, reset_password_token: @token), \
- edit_password_url(@resource, reset_password_token: @token), \
+ = link_to edit_password_url(@resource, reset_password_token: @token, locale: @locale), \
+ edit_password_url(@resource, reset_password_token: @token, locale: @locale), \
target: '_blank'
table.spacer
diff --git a/app/views/devise/passwords/new.html.slim b/app/views/devise/passwords/new.html.slim
index 345c3eab860..52b74d9aaee 100644
--- a/app/views/devise/passwords/new.html.slim
+++ b/app/views/devise/passwords/new.html.slim
@@ -10,4 +10,6 @@ p.mt-tiny.mb0#email-description
= f.input :email, required: true, input_html: { 'aria-describedby': 'email-description' }
= f.button :submit, t('forms.buttons.continue'), class: 'mt2'
-= render 'shared/cancel', link: new_user_session_path
+.mt2.pt1.border-top
+ = link_to t('links.cancel'), decorated_session.cancel_link_path, class: 'h5'
+
diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim
index 89384a00d1f..ccac1c14bb4 100644
--- a/app/views/layouts/application.html.slim
+++ b/app/views/layouts/application.html.slim
@@ -8,6 +8,8 @@ html lang="#{I18n.locale}" class='no-js'
meta name='msapplication-config' content='none'
meta[name='viewport' content='width=device-width, initial-scale=1.0']
meta name="format-detection" content="telephone=no"
+ - if content_for?(:meta_refresh)
+ meta http-equiv="refresh" content="#{yield(:meta_refresh)}"
title
= content_for?(:title) ? APP_NAME + ' - ' + yield(:title) : APP_NAME
@@ -45,6 +47,7 @@ html lang="#{I18n.locale}" class='no-js'
body class="#{Rails.env}-env site sm-bg-light-blue"
.site-wrap
= render 'shared/i18n_mode' if FeatureManagement.enable_i18n_mode?
+ = render 'shared/no_pii_banner' if FeatureManagement.no_pii_mode?
= render 'shared/usa_banner'
- if content_for?(:nav)
= yield(:nav)
diff --git a/app/views/shared/_accordion.html.slim b/app/views/shared/_accordion.html.slim
index b36a57c2bab..e42dfed41b3 100644
--- a/app/views/shared/_accordion.html.slim
+++ b/app/views/shared/_accordion.html.slim
@@ -18,6 +18,7 @@
.accordion-content.clearfix.pt1(
id="#{target_id}"
role="region"
+ aria-hidden="true"
)
.px2
= yield
diff --git a/app/views/shared/_footer_lite.html.slim b/app/views/shared/_footer_lite.html.slim
index ca77f40ade4..d1a5983e6ee 100644
--- a/app/views/shared/_footer_lite.html.slim
+++ b/app/views/shared/_footer_lite.html.slim
@@ -1,16 +1,45 @@
+- show_language_dropdown = I18n.available_locales.count > 1
+
footer.footer.bg-light-blue.sm-bg-navy
- .container.h6.py1.px2.lg-px0
- .clearfix
- .col-right.caps
+ - if show_language_dropdown
+ .sm-hide.border-bottom
+ .container.py1.px2.lg-px0.h5
+ .center
+ span.i18n-mobile-toggle.block.text-decoration-none.blue.fs-13p
+ = image_tag asset_url('globe-blue.svg'), width: 12, class: 'mr1', alt: ''
+ = t('i18n.language')
+ span.caret.inline-block.ml-tiny
+ | ▾
+ .i18n-mobile-dropdown.sm-hide
+ ul.list-reset.mb0.white.center
+ - I18n.available_locales.each do |locale|
+ li.border-bottom
+ = link_to t("i18n.locale.#{locale}"), { locale: locale },
+ class: 'block py-12p px2 text-decoration-none blue fs-13p'
+ .container.py1.px2.lg-px0(class="#{'sm-py0' if show_language_dropdown}")
+ .flex.flex-center
+ .flex.flex-center
+ - if show_language_dropdown
+ .i18n-desktop-toggle.sm-show.white.my1.mr3.px1.py-tiny.border.border-blue.rounded-lg
+ = image_tag asset_url('globe-white.svg'), width: 12, class: 'mr1', alt: ''
+ = t('i18n.language')
+ span.caret.inline-block.ml-tiny
+ | ▾
+ .i18n-desktop-dropdown
+ ul.list-reset.mb0.white
+ - I18n.available_locales.each do |locale|
+ li.border-bottom.border-navy
+ = link_to t("i18n.locale.#{locale}"), { locale: locale },
+ class: 'block pl-24p py2 text-decoration-none white'
= link_to t('links.help'), MarketingSite.help_url,
- class: 'gray sm-white text-decoration-none mr3', target: '_blank'
+ class: 'caps h6 blue sm-white text-decoration-none mr3', target: '_blank'
= link_to t('links.contact'), MarketingSite.contact_url,
- class: 'gray sm-white text-decoration-none mr3', target: '_blank'
+ class: 'caps h6 blue sm-white text-decoration-none mr3', target: '_blank'
= link_to t('links.privacy_policy'), MarketingSite.privacy_url,
- class: 'gray sm-white text-decoration-none', target: '_blank'
- .col
+ class: 'caps h6 blue sm-white text-decoration-none', target: '_blank'
+ .flex.flex-auto.flex-first
= link_to('https://gsa.gov',
- class: 'flex flex-center text-decoration-none white',
+ class: 'flex flex-center text-decoration-none white h6',
target: '_blank') do
= image_tag asset_url('sp-logos/square-gsa.svg'),
width: 20, class: 'mr1 sm-show', alt: 'GSA homepage'
@@ -18,4 +47,3 @@ footer.footer.bg-light-blue.sm-bg-navy
width: 20, class: 'mr1 sm-hide', alt: 'GSA homepage'
span.sm-show
= t('shared.footer_lite.gsa')
- end
diff --git a/app/views/shared/_no_pii_banner.html.slim b/app/views/shared/_no_pii_banner.html.slim
new file mode 100644
index 00000000000..9e93f447010
--- /dev/null
+++ b/app/views/shared/_no_pii_banner.html.slim
@@ -0,0 +1,2 @@
+.py1.bg-maroon.white.fs-12p.line-height-1.center
+ = t('idv.messages.sessions.no_pii')
diff --git a/app/views/shared/_pii_review.html.slim b/app/views/shared/_pii_review.html.slim
index 13b5ae8d0fd..a058ba8531a 100644
--- a/app/views/shared/_pii_review.html.slim
+++ b/app/views/shared/_pii_review.html.slim
@@ -12,8 +12,9 @@
.mt3.h6 = t('idv.review.ssn')
.h4.bold.ico-absolute.ico-absolute-success #{pii[:ssn]}
- .h6.mt3 = t('idv.messages.phone.phone_of_record')
- .h4.bold.ico-absolute.ico-absolute-success #{pii[:phone]}
+ - if phone
+ .h6.mt3 = t('idv.messages.phone.phone_of_record')
+ .h4.bold.ico-absolute.ico-absolute-success #{phone}
.mt3.mb3
= link_to t('idv.messages.review.financial_info'), MarketingSite.help_url
diff --git a/app/views/shared/refresh.html.slim b/app/views/shared/refresh.html.slim
new file mode 100644
index 00000000000..6c7ef0103ec
--- /dev/null
+++ b/app/views/shared/refresh.html.slim
@@ -0,0 +1,7 @@
+- content_for(:meta_refresh) do
+ = Figaro.env.async_job_refresh_interval_seconds.to_s
+
+h2 = t('idv.messages.loading')
+
+.loading-spinner
+ img src="#{image_path('spinner.gif')}" height=100 width=100 alt=""
diff --git a/app/views/users/phones/edit.html.slim b/app/views/users/phones/edit.html.slim
index 3da297496fd..73ba00505a9 100644
--- a/app/views/users/phones/edit.html.slim
+++ b/app/views/users/phones/edit.html.slim
@@ -2,8 +2,15 @@
h1.h3.my0 = t('headings.edit_info.phone')
-= simple_form_for(@update_user_phone_form, url: manage_phone_path,
+= simple_form_for(@update_user_phone_form,
+ data: { unsupported_area_codes: @unsupported_area_codes,
+ international_phone_form: true },
+ url: manage_phone_path,
html: { autocomplete: 'off', method: :put, role: 'form' }) do |f|
+ = f.input :international_code,
+ collection: international_phone_codes,
+ include_blank: false,
+ input_html: { class: 'international-code' }
= f.input :phone, as: :tel, required: true, input_html: { class: 'phone', value: nil },
label: t('account.index.phone')
= f.button :submit, t('forms.buttons.submit.confirm_change'), class: 'mt2'
diff --git a/app/views/users/totp_setup/new.html.slim b/app/views/users/totp_setup/new.html.slim
index 1c5136488ee..7401123403a 100644
--- a/app/views/users/totp_setup/new.html.slim
+++ b/app/views/users/totp_setup/new.html.slim
@@ -22,8 +22,8 @@ ul.list-reset
.clipboard.ml2.right.mt-tiny class=btn_cls data-clipboard-text=@code.upcase
= t('links.copy')
.py2.center.bold
- = t('instructions.2fa.authenticator.or')
- = accordion('totp-info', t('instructions.2fa.authenticator.accordion_header'),
+ = t('instructions.mfa.authenticator.or')
+ = accordion('totp-info', t('instructions.mfa.authenticator.accordion_header'),
wrapper_css: 'mb2 col-12 fs-16p') do
.center
= image_tag @qrcode, alt: t('users.totp_setup.new.qr_img_alt')
diff --git a/app/views/users/two_factor_authentication_setup/index.html.slim b/app/views/users/two_factor_authentication_setup/index.html.slim
index 51be3136fbf..2a86ed90b15 100644
--- a/app/views/users/two_factor_authentication_setup/index.html.slim
+++ b/app/views/users/two_factor_authentication_setup/index.html.slim
@@ -5,11 +5,20 @@ p.mt-tiny.mb0
= t('devise.two_factor_authentication.otp_setup_html')
= simple_form_for(@two_factor_setup_form,
html: { autocomplete: 'off', role: 'form' },
+ data: { unsupported_area_codes: @unsupported_area_codes,
+ international_phone_form: true },
method: :patch,
url: phone_setup_path) do |f|
+ .clearfix
+ .sm-col.sm-col-8
+ = f.input :international_code,
+ collection: international_phone_codes,
+ include_blank: false,
+ input_html: { class: 'international-code' }
= f.label :phone, class: 'block'
strong.left = t('devise.two_factor_authentication.otp_phone_label')
- span.ml1.italic = t('devise.two_factor_authentication.otp_phone_label_info')
+ span#otp_phone_label_info.ml1.italic
+ = t('devise.two_factor_authentication.otp_phone_label_info')
= f.input :phone, as: :tel, label: false, required: true,
input_html: { class: 'phone sm-col-8 mb4' }
.mb3
@@ -25,7 +34,8 @@ p.mt-tiny.mb0
= radio_button_tag 'two_factor_setup_form[otp_delivery_preference]', :voice
span.indicator
= t('devise.two_factor_authentication.otp_delivery_preference.voice')
- p.mt1.mb0 = t('devise.two_factor_authentication.otp_delivery_preference.instruction')
+ p#otp_delivery_preference_instruction.mt1.mb0
+ = t('devise.two_factor_authentication.otp_delivery_preference.instruction')
= f.button :submit, t('forms.buttons.send_security_code')
= render 'shared/cancel', link: destroy_user_session_path
diff --git a/app/views/users/verify_password/new.html.slim b/app/views/users/verify_password/new.html.slim
index 20ea38eab25..a3bd8f130b0 100644
--- a/app/views/users/verify_password/new.html.slim
+++ b/app/views/users/verify_password/new.html.slim
@@ -6,4 +6,5 @@ h1.h3.my0 = t('idv.titles.session.review')
.mt4
= accordion('review-verified-info', t('idv.messages.review.intro')) do
- = render 'shared/pii_review', pii: @verify_password_form.decrypted_pii
+ - decrypted_pii = @verify_password_form.decrypted_pii
+ = render 'shared/pii_review', pii: decrypted_pii, phone: decrypted_pii[:phone].raw
diff --git a/app/views/verify/review/new.html.slim b/app/views/verify/review/new.html.slim
index 4cd7d7768d6..46e83ad6066 100644
--- a/app/views/verify/review/new.html.slim
+++ b/app/views/verify/review/new.html.slim
@@ -6,4 +6,6 @@ h1.h3.my0 = t('idv.titles.session.review')
.mt4
= accordion('review-verified-info', t('idv.messages.review.intro')) do
- = render 'shared/pii_review', pii: @idv_params
+ - phone = @idv_params[:phone]
+ - formatted_phone = PhoneFormatter.new.format(phone)
+ = render 'shared/pii_review', pii: @idv_params, phone: formatted_phone
diff --git a/app/views/verify/usps/index.html.slim b/app/views/verify/usps/index.html.slim
index 75c25ae8732..302048a8ad2 100644
--- a/app/views/verify/usps/index.html.slim
+++ b/app/views/verify/usps/index.html.slim
@@ -2,7 +2,7 @@
= image_tag(asset_url('check-email.svg'), size: '48x48', alt: 'check email',\
class: 'absolute top-n24 left-0 right-0 mx-auto')
h1.h2
- = @title
+ = @decorated_usps.title
p
= t('idv.messages.usps.byline')
@@ -10,7 +10,7 @@
strong
= t('idv.messages.usps.success')
-= button_to @button, verify_usps_path, method: 'put',
+= button_to @decorated_usps.button, verify_usps_path, method: 'put',
class: 'btn btn-primary btn-wide', form_class: 'inline-block mr2'
= link_to t('idv.messages.usps.bad_address'), verify_phone_path
diff --git a/certs/sp/cbp_goes_prod.crt b/certs/sp/cbp_goes_prod.crt
new file mode 100644
index 00000000000..5bf2246006b
--- /dev/null
+++ b/certs/sp/cbp_goes_prod.crt
@@ -0,0 +1,45 @@
+-----BEGIN CERTIFICATE-----
+MIIH8DCCBtigAwIBAgIEWRoIeDANBgkqhkiG9w0BAQsFADCBhzELMAkGA1UEBhMC
+VVMxGDAWBgNVBAoTD1UuUy4gR292ZXJubWVudDEoMCYGA1UECxMfRGVwYXJ0bWVu
+dCBvZiBIb21lbGFuZCBTZWN1cml0eTEiMCAGA1UECxMZQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdGllczEQMA4GA1UECxMHREhTIENBNDAeFw0xNzA2MjQxNDQ2MTlaFw0y
+MDA2MjQxNTE2MTlaMIGPMQswCQYDVQQGEwJVUzEYMBYGA1UEChMPVS5TLiBHb3Zl
+cm5tZW50MSgwJgYDVQQLEx9EZXBhcnRtZW50IG9mIEhvbWVsYW5kIFNlY3VyaXR5
+MQwwCgYDVQQLEwNDQlAxEDAOBgNVBAsTB0RldmljZXMxHDAaBgNVBAMTE3R0cC1h
+cHAuY2JwLmRocy5nb3YwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP
+ubSOea85FVVr9Adn+mez7LjuPBs3aeMCEK9gM9xnV2tGFJra1Mh9ef/jnnO2ANur
+jtRovws/ea/k+J54ngIv7ZXCZGUvZZoOtyFqX7Mjnj8gFvIrFCVAD4a/FySSiGNo
+Z7+X/ypX1rFb8CNFSi/SxU+zH61ZS0i+OAZ2xAk3Gv+OkZ4DHRHXsGJn8rCJ2O1N
+c/OyKpBOpkS5EjTAPw3OqD/U8CU9Hl6QbK52ovxFLgtkHGWv37dLc0Qojwa8Lqaa
+FAxjWPyND2oo4aDfGtu7YtbGk0zRf97QNvfoqjkGYAaUK0ozCTMKZEVuXknhDtvy
+a6C26kDVYA8RUn9RtFjNAgMBAAGjggRYMIIEVDAOBgNVHQ8BAf8EBAMCBaAwFwYD
+VR0gBBAwDjAMBgpghkgBZQMCAQMIMIIBGwYIKwYBBQUHAQEEggENMIIBCTA0Bggr
+BgEFBQcwAoYoaHR0cDovL3BraS5kaW1jLmRocy5nb3YvZGhzY2FfZWVfYWlhLnA3
+YzCBqgYIKwYBBQUHMAKGgZ1sZGFwOi8vbGRhcDAxLmRpbWMuZGhzLmdvdi9vdT1E
+SFMlMjBDQTQsb3U9Q2VydGlmaWNhdGlvbiUyMEF1dGhvcml0aWVzLG91PURlcGFy
+dG1lbnQlMjBvZiUyMEhvbWVsYW5kJTIwU2VjdXJpdHksbz1VLlMuJTIwR292ZXJu
+bWVudCxjPVVTP2NBQ2VydGlmaWNhdGU7YmluYXJ5MCQGCCsGAQUFBzABhhhodHRw
+Oi8vb2NzcC5kaW1jLmRocy5nb3YwEwYDVR0lBAwwCgYIKwYBBQUHAwEwggEBBgNV
+HREEgfkwgfaCE3R0cC1hcHAuY2JwLmRocy5nb3aCGHR0cC1pbnRlcm5hbC5jYnAu
+ZGhzLmdvdoIcdHRwLWludGVybmFsLWFwcC5jYnAuZGhzLmdvdoIZdHRwLXNjaGVk
+dWxlci5jYnAuZGhzLmdvdoIddHRwLXNjaGVkdWxlci1hcHAuY2JwLmRocy5nb3aC
+F3R0cC1jcmVkc3ZjLmNicC5kaHMuZ292ght0dHAtY3JlZHN2Yy1hcHAuY2JwLmRo
+cy5nb3aCF3R0cC1hZHMtYXBwLmNicC5kaHMuZ292gh50dHAtc2FwYWRhcHRlci1h
+cHAuY2JwLmRocy5nb3YwggGTBgNVHR8EggGKMIIBhjCCAVegggFToIIBT6SBnDCB
+mTELMAkGA1UEBhMCVVMxGDAWBgNVBAoTD1UuUy4gR292ZXJubWVudDEoMCYGA1UE
+CxMfRGVwYXJ0bWVudCBvZiBIb21lbGFuZCBTZWN1cml0eTEiMCAGA1UECxMZQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdGllczEQMA4GA1UECxMHREhTIENBNDEQMA4GA1UE
+AxMHQ1JMNTM5MYaBrWxkYXA6Ly9sZGFwMDEuZGltYy5kaHMuZ292L2NuPUNSTDUz
+OTEsb3U9REhTJTIwQ0E0LG91PUNlcnRpZmljYXRpb24lMjBBdXRob3JpdGllcyxv
+dT1EZXBhcnRtZW50JTIwb2YlMjBIb21lbGFuZCUyMFNlY3VyaXR5LG89VS5TLiUy
+MEdvdmVybm1lbnQsYz1VUz9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0MCmgJ6Al
+hiNodHRwOi8vcGtpLmRpbWMuZGhzLmdvdi9ESFNfQ0EyLmNybDAfBgNVHSMEGDAW
+gBR8w0pcuh82q4NRffTg5Q6QfxwTQTAdBgNVHQ4EFgQUIWVxsBQWw5oIGgPYwrvG
+o7c35EowGQYJKoZIhvZ9B0EABAwwChsEVjguMQMCA6gwDQYJKoZIhvcNAQELBQAD
+ggEBAG3uUeSo89FY4HcDGGhO9mBjXuk7HRHGLnY8fAdqJoT1gA/oTHoV/fgw8QZu
+D3X9FLgpEjQz43iHFrv/ps2tYwgSKiiovoeYyb68s5X9NuG5kNnn0dGp1TjnNlGX
+9lQSBr4CReaJpCtSAPbOtuZ+8b7MA7ODnMg/1u1qNKovIqMV6eIq5vrD/+MeSG61
++c+MI6Gita5j6J6lzZqqwtAltfMFetc1Qkl2tjqdpce9u60Ch7yuVR3Xh1SEhaAa
+jRJsbOuqNAfC038A6ASv3nxGao8AYUHt2aR9chXApKfBzfx6VYW0462UPqzeRi5l
+RCa8sud1bb1pFRJv8LWiqOC/9yw=
+-----END CERTIFICATE-----
diff --git a/config/application.rb b/config/application.rb
index b12f84bf8a2..91fbd99d570 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -16,8 +16,9 @@ class Application < Rails::Application
config.browserify_rails.force = true
config.browserify_rails.commandline_options = '-t [ babelify --presets [ es2015 ] ]'
- config.i18n.available_locales = Figaro.env.available_locales.split(' ')
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{yml}')]
+ config.i18n.available_locales = Figaro.env.available_locales.split(' ')
+ config.i18n.default_locale = :en
routes.default_url_options[:host] = Figaro.env.domain_name
diff --git a/config/application.yml.example b/config/application.yml.example
index 3d9d481d90c..753692138e3 100644
--- a/config/application.yml.example
+++ b/config/application.yml.example
@@ -49,11 +49,15 @@ use_dashboard_service_providers: 'false'
dashboard_url: 'https://dashboard.demo.login.gov'
valid_authn_contexts: '["http://idmanagement.gov/ns/assurance/loa/1", "http://idmanagement.gov/ns/assurance/loa/3"]'
+usps_mail_batch_hours: '24'
+
development:
+ async_job_refresh_interval_seconds: '5'
+ async_job_refresh_max_wait_seconds: '15'
attribute_cost: '4000$8$4$' # SCrypt::Engine.calibrate(max_time: 0.5)
attribute_encryption_key: '2086dfbd15f5b0c584f3664422a1d3409a0d2aa6084f65b6ba57d64d4257431c124158670c7655e45cabe64194f7f7b6c7970153c285bdb8287ec0c4f7553e25'
attribute_encryption_key_queue: '["old-key-one", "old-key-two"]'
- available_locales: 'en es'
+ available_locales: 'en es fr'
aws_kms_key_id: 'alias/login-dot-gov-development-keymaker'
aws_region: 'us-east-1'
dashboard_api_token: 'test_token'
@@ -69,6 +73,9 @@ development:
equifax_gpg_email: 'logs@login.gov'
equifax_password: 'sekret'
equifax_phone_username: 'sekret'
+ equifax_sftp_directory: '/directory'
+ equifax_sftp_host: 'example.com'
+ equifax_sftp_username: 'user'
equifax_ssh_passphrase: 'sekret'
hmac_fingerprinter_key: 'a2c813d4dca919340866ba58063e4072adc459b767a74cf2666d5c1eef3861db26708e7437abde1755eb24f4034386b0fea1850a1cb7e56bff8fae3cc6ade96c'
hmac_fingerprinter_key_queue: '["old-key-one", "old-key-two"]'
@@ -105,6 +112,8 @@ development:
enable_load_testing_mode: 'false'
production:
+ async_job_refresh_interval_seconds: '5'
+ async_job_refresh_max_wait_seconds: '15'
attribute_cost: '4000$8$4$' # SCrypt::Engine.calibrate(max_time: 0.5)
attribute_encryption_key: 'generate via `rake secret`'
attribute_encryption_key_queue: '["old-key-one", "old-key-two"]'
@@ -124,6 +133,9 @@ production:
equifax_gpg_email: 'sekret@example.com'
equifax_password: 'sekret'
equifax_phone_username: 'sekret'
+ equifax_sftp_directory: '/directory'
+ equifax_sftp_host: 'example.com'
+ equifax_sftp_username: 'user'
equifax_ssh_passphrase: 'sekret'
google_analytics_key: 'UA-XXXXXXXXX-YY'
hmac_fingerprinter_key: 'generate via `rake secret`'
@@ -132,6 +144,7 @@ production:
lockout_period_in_minutes: '10'
logins_per_ip_limit: '20'
logins_per_ip_period: '8'
+ mandrill_api_token: '123abc'
newrelic_license_key: 'xxx'
newrelic_browser_key: ''
newrelic_browser_app_id: ''
@@ -152,7 +165,6 @@ production:
secret_key_base: 'generate via `rake secret`'
session_encryption_key: 'generate via `rake secret`'
session_timeout_in_minutes: '8'
- smtp_settings: '{"address":"smtp.mandrillapp.com", "port":587, "authentication":"login", "enable_starttls_auto":true, "user_name":"user@gmail.com","password":"xxx"}'
twilio_accounts: '[{"sid":"sid", "auth_token":"token", "number":"9999999999"}]'
twilio_record_voice: 'false'
use_kms: 'false'
@@ -161,10 +173,12 @@ production:
enable_load_testing_mode: 'false'
test:
+ async_job_refresh_interval_seconds: '1'
+ async_job_refresh_max_wait_seconds: '15'
attribute_cost: '800$8$1$' # SCrypt::Engine.calibrate(max_time: 0.01)
attribute_encryption_key: '2086dfbd15f5b0c584f3664422a1d3409a0d2aa6084f65b6ba57d64d4257431c124158670c7655e45cabe64194f7f7b6c7970153c285bdb8287ec0c4f7553e25'
attribute_encryption_key_queue: '["old-key-one", "old-key-two"]'
- available_locales: 'en es'
+ available_locales: 'en es fr'
aws_kms_key_id: 'alias/login-dot-gov-test-keymaker'
aws_region: 'us-east-1'
domain_name: 'www.example.com'
@@ -180,6 +194,9 @@ test:
equifax_gpg_email: 'logs@login.gov'
equifax_password: 'sekret'
equifax_phone_username: 'sekret'
+ equifax_sftp_directory: '/directory'
+ equifax_sftp_host: 'example.com'
+ equifax_sftp_username: 'user'
equifax_ssh_passphrase: 'sekret'
hmac_fingerprinter_key: 'a2c813d4dca919340866ba58063e4072adc459b767a74cf2666d5c1eef3861db26708e7437abde1755eb24f4034386b0fea1850a1cb7e56bff8fae3cc6ade96c'
hmac_fingerprinter_key_queue: '["old-key-one", "old-key-two"]'
diff --git a/config/country_dialing_codes.yml b/config/country_dialing_codes.yml
new file mode 100644
index 00000000000..75013b7abdc
--- /dev/null
+++ b/config/country_dialing_codes.yml
@@ -0,0 +1,804 @@
+US:
+ name: United States of America
+ country_code: '1'
+ sms_only: false
+AF:
+ name: Afghanistan
+ country_code: '93'
+ sms_only: true
+AL:
+ name: Albania
+ country_code: '355'
+ sms_only: true
+DZ:
+ name: Algeria
+ country_code: '213'
+ sms_only: true
+AD:
+ name: Andorra
+ country_code: '376'
+ sms_only: true
+AO:
+ name: Angola
+ country_code: '244'
+ sms_only: true
+AR:
+ name: Argentina
+ country_code: '54'
+ sms_only: true
+AM:
+ name: Armenia
+ country_code: '374'
+ sms_only: true
+AW:
+ name: Aruba
+ country_code: '297'
+ sms_only: true
+AU:
+ name: Australia/Cocos/Christmas Island
+ country_code: '61'
+ sms_only: false
+AT:
+ name: Austria
+ country_code: '43'
+ sms_only: false
+AZ:
+ name: Azerbaijan
+ country_code: '994'
+ sms_only: true
+BH:
+ name: Bahrain
+ country_code: '973'
+ sms_only: false
+BD:
+ name: Bangladesh
+ country_code: '880'
+ sms_only: true
+BY:
+ name: Belarus
+ country_code: '375'
+ sms_only: true
+BE:
+ name: Belgium
+ country_code: '32'
+ sms_only: false
+BZ:
+ name: Belize
+ country_code: '501'
+ sms_only: true
+BJ:
+ name: Benin
+ country_code: '229'
+ sms_only: true
+BT:
+ name: Bhutan
+ country_code: '975'
+ sms_only: true
+BO:
+ name: Bolivia
+ country_code: '591'
+ sms_only: true
+BA:
+ name: Bosnia and Herzegovina
+ country_code: '387'
+ sms_only: true
+BW:
+ name: Botswana
+ country_code: '267'
+ sms_only: true
+BR:
+ name: Brazil
+ country_code: '55'
+ sms_only: false
+BN:
+ name: Brunei
+ country_code: '673'
+ sms_only: true
+BG:
+ name: Bulgaria
+ country_code: '359'
+ sms_only: false
+BF:
+ name: Burkina Faso
+ country_code: '226'
+ sms_only: true
+BI:
+ name: Burundi
+ country_code: '257'
+ sms_only: true
+KH:
+ name: Cambodia
+ country_code: '855'
+ sms_only: true
+CM:
+ name: Cameroon
+ country_code: '237'
+ sms_only: true
+CA:
+ name: Canada
+ country_code: '1'
+ sms_only: true
+CV:
+ name: Cape Verde
+ country_code: '238'
+ sms_only: true
+CF:
+ name: Central Africa
+ country_code: '236'
+ sms_only: true
+TD:
+ name: Chad
+ country_code: '235'
+ sms_only: true
+CL:
+ name: Chile
+ country_code: '56'
+ sms_only: true
+CN:
+ name: China
+ country_code: '86'
+ sms_only: false
+CO:
+ name: Colombia
+ country_code: '57'
+ sms_only: true
+KM:
+ name: Comoros
+ country_code: '269'
+ sms_only: true
+CG:
+ name: Congo
+ country_code: '242'
+ sms_only: true
+CD:
+ name: Congo (Democratic Republic of the)
+ country_code: '243'
+ sms_only: true
+CK:
+ name: Cook Islands
+ country_code: '682'
+ sms_only: true
+CR:
+ name: Costa Rica
+ country_code: '506'
+ sms_only: true
+HR:
+ name: Croatia
+ country_code: '385'
+ sms_only: true
+CU:
+ name: Cuba
+ country_code: '53'
+ sms_only: true
+CY:
+ name: Cyprus
+ country_code: '357'
+ sms_only: false
+CZ:
+ name: Czech Republic
+ country_code: '420'
+ sms_only: false
+DK:
+ name: Denmark
+ country_code: '45'
+ sms_only: false
+DJ:
+ name: Djibouti
+ country_code: '253'
+ sms_only: true
+TL:
+ name: East Timor
+ country_code: '670'
+ sms_only: true
+EC:
+ name: Ecuador
+ country_code: '593'
+ sms_only: true
+EG:
+ name: Egypt
+ country_code: '20'
+ sms_only: true
+SV:
+ name: El Salvador
+ country_code: '503'
+ sms_only: true
+GQ:
+ name: Equatorial Guinea
+ country_code: '240'
+ sms_only: true
+ER:
+ name: Eritrea
+ country_code: '291'
+ sms_only: true
+EE:
+ name: Estonia
+ country_code: '372'
+ sms_only: false
+ET:
+ name: Ethiopia
+ country_code: '251'
+ sms_only: true
+FK:
+ name: Falkland Islands
+ country_code: '500'
+ sms_only: true
+FO:
+ name: Faroe Islands
+ country_code: '298'
+ sms_only: true
+FJ:
+ name: Fiji
+ country_code: '679'
+ sms_only: true
+FI:
+ name: Finland/Aland Islands
+ country_code: '358'
+ sms_only: false
+FR:
+ name: France
+ country_code: '33'
+ sms_only: false
+GF:
+ name: French Guiana
+ country_code: '594'
+ sms_only: true
+PF:
+ name: French Polynesia
+ country_code: '689'
+ sms_only: true
+GA:
+ name: Gabon
+ country_code: '241'
+ sms_only: true
+GM:
+ name: Gambia
+ country_code: '220'
+ sms_only: true
+GE:
+ name: Georgia
+ country_code: '995'
+ sms_only: true
+DE:
+ name: Germany
+ country_code: '49'
+ sms_only: false
+GH:
+ name: Ghana
+ country_code: '233'
+ sms_only: true
+GI:
+ name: Gibraltar
+ country_code: '350'
+ sms_only: true
+GR:
+ name: Greece
+ country_code: '30'
+ sms_only: true
+GL:
+ name: Greenland
+ country_code: '299'
+ sms_only: true
+GP:
+ name: Guadeloupe
+ country_code: '590'
+ sms_only: true
+GT:
+ name: Guatemala
+ country_code: '502'
+ sms_only: true
+GN:
+ name: Guinea
+ country_code: '224'
+ sms_only: true
+GW:
+ name: Guinea-Bissau
+ country_code: '245'
+ sms_only: true
+GY:
+ name: Guyana
+ country_code: '592'
+ sms_only: true
+HT:
+ name: Haiti
+ country_code: '509'
+ sms_only: true
+HN:
+ name: Honduras
+ country_code: '504'
+ sms_only: true
+HK:
+ name: Hong Kong
+ country_code: '852'
+ sms_only: false
+HU:
+ name: Hungary
+ country_code: '36'
+ sms_only: true
+IS:
+ name: Iceland
+ country_code: '354'
+ sms_only: true
+IN:
+ name: India
+ country_code: '91'
+ sms_only: false
+ID:
+ name: Indonesia
+ country_code: '62'
+ sms_only: true
+IR:
+ name: Iran
+ country_code: '98'
+ sms_only: true
+IQ:
+ name: Iraq
+ country_code: '964'
+ sms_only: true
+IE:
+ name: Ireland
+ country_code: '353'
+ sms_only: false
+IL:
+ name: Israel
+ country_code: '972'
+ sms_only: false
+IT:
+ name: Italy
+ country_code: '39'
+ sms_only: false
+CI:
+ name: Ivory Coast
+ country_code: '225'
+ sms_only: true
+JP:
+ name: Japan
+ country_code: '81'
+ sms_only: false
+JO:
+ name: Jordan
+ country_code: '962'
+ sms_only: true
+KZ:
+ name: Kazakhstan
+ country_code: '7'
+ sms_only: true
+KE:
+ name: Kenya
+ country_code: '254'
+ sms_only: true
+KP:
+ name: Korea (Democratic People's Republic of)
+ country_code: '850'
+ sms_only: true
+KR:
+ name: Korea (Republic of)
+ country_code: '82'
+ sms_only: true
+KW:
+ name: Kuwait
+ country_code: '965'
+ sms_only: true
+KG:
+ name: Kyrgyzstan
+ country_code: '996'
+ sms_only: true
+LA:
+ name: Laos People's Democratic Republic
+ country_code: '856'
+ sms_only: true
+LV:
+ name: Latvia
+ country_code: '371'
+ sms_only: false
+LB:
+ name: Lebanon
+ country_code: '961'
+ sms_only: true
+LS:
+ name: Lesotho
+ country_code: '266'
+ sms_only: true
+LR:
+ name: Liberia
+ country_code: '231'
+ sms_only: true
+LY:
+ name: Libya
+ country_code: '218'
+ sms_only: true
+LI:
+ name: Liechtenstein
+ country_code: '423'
+ sms_only: true
+LT:
+ name: Lithuania
+ country_code: '370'
+ sms_only: false
+LU:
+ name: Luxembourg
+ country_code: '352'
+ sms_only: false
+MO:
+ name: Macau
+ country_code: '853'
+ sms_only: true
+MK:
+ name: Macedonia
+ country_code: '389'
+ sms_only: true
+MG:
+ name: Madagascar
+ country_code: '261'
+ sms_only: true
+MW:
+ name: Malawi
+ country_code: '265'
+ sms_only: true
+MY:
+ name: Malaysia
+ country_code: '60'
+ sms_only: true
+MV:
+ name: Maldives
+ country_code: '960'
+ sms_only: true
+ML:
+ name: Mali
+ country_code: '223'
+ sms_only: true
+MT:
+ name: Malta
+ country_code: '356'
+ sms_only: false
+MH:
+ name: Marshall Islands
+ country_code: '692'
+ sms_only: true
+MQ:
+ name: Martinique
+ country_code: '596'
+ sms_only: true
+MR:
+ name: Mauritania
+ country_code: '222'
+ sms_only: true
+MU:
+ name: Mauritius
+ country_code: '230'
+ sms_only: true
+YT:
+ name: Mayotte
+ country_code: '262'
+ sms_only: true
+MX:
+ name: Mexico
+ country_code: '52'
+ sms_only: false
+FM:
+ name: Micronesia
+ country_code: '691'
+ sms_only: true
+MD:
+ name: Moldo
+ country_code: '373'
+ sms_only: true
+MC:
+ name: Monaco
+ country_code: '377'
+ sms_only: true
+MN:
+ name: Mongolia
+ country_code: '976'
+ sms_only: true
+ME:
+ name: Montenegro
+ country_code: '382'
+ sms_only: true
+MA:
+ name: Morocco
+ country_code: '212'
+ sms_only: true
+MZ:
+ name: Mozambique
+ country_code: '258'
+ sms_only: true
+MM:
+ name: Myanmar
+ country_code: '95'
+ sms_only: true
+NA:
+ name: Namibia
+ country_code: '264'
+ sms_only: true
+NP:
+ name: Nepal
+ country_code: '977'
+ sms_only: true
+NL:
+ name: Netherlands
+ country_code: '31'
+ sms_only: false
+BQ:
+ name: Netherlands Antilles
+ country_code: '599'
+ sms_only: true
+NC:
+ name: New Caledonia
+ country_code: '687'
+ sms_only: true
+NZ:
+ name: New Zealand
+ country_code: '64'
+ sms_only: false
+NI:
+ name: Nicaragua
+ country_code: '505'
+ sms_only: true
+NE:
+ name: Niger
+ country_code: '227'
+ sms_only: true
+NG:
+ name: Nigeria
+ country_code: '234'
+ sms_only: true
+NO:
+ name: Norway
+ country_code: '47'
+ sms_only: false
+OM:
+ name: Oman
+ country_code: '968'
+ sms_only: true
+PK:
+ name: Pakistan
+ country_code: '92'
+ sms_only: true
+PW:
+ name: Palau
+ country_code: '680'
+ sms_only: true
+PS:
+ name: Palestine
+ country_code: '970'
+ sms_only: true
+PA:
+ name: Panama
+ country_code: '507'
+ sms_only: true
+PG:
+ name: Papua New Guinea
+ country_code: '675'
+ sms_only: true
+PY:
+ name: Paraguay
+ country_code: '595'
+ sms_only: true
+PE:
+ name: Peru
+ country_code: '51'
+ sms_only: false
+PH:
+ name: Philippines
+ country_code: '63'
+ sms_only: true
+PL:
+ name: Poland
+ country_code: '48'
+ sms_only: false
+PT:
+ name: Portugal
+ country_code: '351'
+ sms_only: true
+QA:
+ name: Qatar
+ country_code: '974'
+ sms_only: true
+RO:
+ name: Romania
+ country_code: '40'
+ sms_only: false
+RU:
+ name: Russia
+ country_code: '7'
+ sms_only: true
+RW:
+ name: Rwanda
+ country_code: '250'
+ sms_only: true
+RE:
+ name: Reunion
+ country_code: '262'
+ sms_only: true
+PM:
+ name: Saint Pierre and Miquelon
+ country_code: '508'
+ sms_only: true
+WS:
+ name: Samoa
+ country_code: '685'
+ sms_only: true
+SM:
+ name: San Marino
+ country_code: '378'
+ sms_only: true
+ST:
+ name: Sao Tome and Principe
+ country_code: '239'
+ sms_only: true
+SA:
+ name: Saudi Arabia
+ country_code: '966'
+ sms_only: true
+SN:
+ name: Senegal
+ country_code: '221'
+ sms_only: true
+RS:
+ name: Serbia
+ country_code: '381'
+ sms_only: true
+SC:
+ name: Seychelles
+ country_code: '248'
+ sms_only: true
+SL:
+ name: Sierra Leone
+ country_code: '232'
+ sms_only: true
+SG:
+ name: Singapore
+ country_code: '65'
+ sms_only: true
+SK:
+ name: Slovakia
+ country_code: '421'
+ sms_only: false
+SI:
+ name: Slovenia
+ country_code: '386'
+ sms_only: true
+SB:
+ name: Solomon Islands
+ country_code: '677'
+ sms_only: true
+SO:
+ name: Somalia
+ country_code: '252'
+ sms_only: true
+ZA:
+ name: South Africa
+ country_code: '27'
+ sms_only: false
+SS:
+ name: South Sudan
+ country_code: '211'
+ sms_only: true
+ES:
+ name: Spain
+ country_code: '34'
+ sms_only: false
+LK:
+ name: Sri Lanka
+ country_code: '94'
+ sms_only: true
+SD:
+ name: Sudan
+ country_code: '249'
+ sms_only: true
+SR:
+ name: Suriname
+ country_code: '597'
+ sms_only: true
+SZ:
+ name: Swaziland
+ country_code: '268'
+ sms_only: true
+SE:
+ name: Sweden
+ country_code: '46'
+ sms_only: true
+CH:
+ name: Switzerland
+ country_code: '41'
+ sms_only: false
+SY:
+ name: Syria
+ country_code: '963'
+ sms_only: true
+TW:
+ name: Taiwan
+ country_code: '886'
+ sms_only: true
+TJ:
+ name: Tajikistan
+ country_code: '992'
+ sms_only: true
+TZ:
+ name: Tanzania, United Republic of
+ country_code: '255'
+ sms_only: true
+TH:
+ name: Thailand
+ country_code: '66'
+ sms_only: true
+TG:
+ name: Togo
+ country_code: '228'
+ sms_only: true
+TO:
+ name: Tonga
+ country_code: '676'
+ sms_only: true
+TN:
+ name: Tunisia
+ country_code: '216'
+ sms_only: true
+TR:
+ name: Turkey
+ country_code: '90'
+ sms_only: true
+TM:
+ name: Turkmenistan
+ country_code: '993'
+ sms_only: true
+TV:
+ name: Tuvalu
+ country_code: '688'
+ sms_only: true
+UG:
+ name: Uganda
+ country_code: '256'
+ sms_only: true
+UA:
+ name: Ukraine
+ country_code: '380'
+ sms_only: true
+AE:
+ name: United Arab Emirates
+ country_code: '971'
+ sms_only: true
+GB:
+ name: United Kingdom
+ country_code: '44'
+ sms_only: false
+UY:
+ name: Uruguay
+ country_code: '598'
+ sms_only: true
+UZ:
+ name: Uzbekistan
+ country_code: '998'
+ sms_only: true
+VU:
+ name: Vanuatu
+ country_code: '678'
+ sms_only: true
+VE:
+ name: Venezuela
+ country_code: '58'
+ sms_only: true
+VN:
+ name: Vietnam
+ country_code: '84'
+ sms_only: true
+VG:
+ name: Virgin Islands (British)
+ country_code: '1'
+ sms_only: true
+VI:
+ name: Virgin Islands (U.S.)
+ country_code: '1'
+ sms_only: true
+YE:
+ name: Yemen
+ country_code: '967'
+ sms_only: true
+ZM:
+ name: Zambia
+ country_code: '260'
+ sms_only: true
+ZW:
+ name: Zimbabwe
+ country_code: '263'
+ sms_only: true
diff --git a/config/environments/production.rb b/config/environments/production.rb
index b16dad0505f..db18fb995e8 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -10,7 +10,6 @@
config.i18n.fallbacks = true
config.active_support.deprecation = :notify
config.active_record.dump_schema_after_migration = false
- config.action_mailer.smtp_settings = JSON.parse(Figaro.env.smtp_settings).symbolize_keys
config.action_mailer.default_url_options = {
host: Figaro.env.domain_name,
@@ -19,7 +18,11 @@
config.action_mailer.asset_host = Figaro.env.mailer_domain_name
config.action_mailer.raise_delivery_errors = false
config.action_mailer.default_options = { from: Figaro.env.email_from }
- config.action_mailer.delivery_method = :test if Figaro.env.disable_email_sending == 'true'
+ config.action_mailer.delivery_method = if Figaro.env.disable_email_sending == 'true'
+ :test
+ else
+ :mandrill
+ end
routes.default_url_options[:protocol] = :https
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index c17e130b72d..0a6637d7c31 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -101,7 +101,7 @@ ignore_unused:
- 'links.remove'
- 'valid_email.validations.email.invalid'
- 'zxcvbn.*'
- - 'instructions.2fa.{sms,voice}.fallback_html'
+ - 'instructions.mfa.{sms,voice}.fallback_html'
- 'links.phone_confirmation.fallback_to_{sms,authenticator,voice}_html'
- 'links.two_factor_authentication.resend_code.{sms,voice}_html'
- 'links.two_factor_authentication.{sms,voice}_html'
diff --git a/config/initializers/active_job_logger_patch.rb b/config/initializers/active_job_logger_patch.rb
index 9d3dcc07960..7739a1d86b5 100644
--- a/config/initializers/active_job_logger_patch.rb
+++ b/config/initializers/active_job_logger_patch.rb
@@ -5,8 +5,33 @@
module ActiveJob
module Logging
class LogSubscriber
+ def enqueue(event)
+ info { json_for(event: event, event_type: 'Enqueued') }
+ end
+
+ def perform_start(event)
+ info { json_for(event: event, event_type: 'Performing') }
+ end
+
+ def perform(event)
+ info { json_for(event: event, event_type: 'Performed') }
+ end
+
private
+ def json_for(event:, event_type:)
+ job = event.payload[:job]
+
+ {
+ timestamp: Time.zone.now,
+ event_type: event_type,
+ job_class: job.class.name,
+ job_queue: queue_name(event),
+ job_id: job.job_id,
+ duration: "#{event.duration.round(2)}ms",
+ }.to_json
+ end
+
def args_info(_job)
''
end
diff --git a/config/initializers/mandrill.rb b/config/initializers/mandrill.rb
new file mode 100644
index 00000000000..47705b72f19
--- /dev/null
+++ b/config/initializers/mandrill.rb
@@ -0,0 +1,5 @@
+if Figaro.env.mandrill_api_token.present?
+ MandrillDm.configure do |config|
+ config.api_key = Figaro.env.mandrill_api_token
+ end
+end
diff --git a/config/locales/account/en.yml b/config/locales/account/en.yml
index 598b0a31909..bb873acc6e1 100644
--- a/config/locales/account/en.yml
+++ b/config/locales/account/en.yml
@@ -3,9 +3,9 @@ en:
account:
index:
address: Current address
- authentication_app: Authentication app
- auth_app_enabled: enabled
auth_app_disabled: not enabled
+ auth_app_enabled: enabled
+ authentication_app: Authentication app
dob: Date of birth
email: Email address
full_name: Full name
diff --git a/config/locales/account/es.yml b/config/locales/account/es.yml
index 27f9b56a511..3c06f750c71 100644
--- a/config/locales/account/es.yml
+++ b/config/locales/account/es.yml
@@ -2,31 +2,31 @@
es:
account:
index:
- address: NOT TRANSLATED YET
- authentication_app: NOT TRANSLATED YET
- auth_app_enabled: NOT TRANSLATED YET
- auth_app_disabled: NOT TRANSLATED YET
- dob: NOT TRANSLATED YET
- email: NOT TRANSLATED YET
- full_name: NOT TRANSLATED YET
- login: NOT TRANSLATED YET
- password: NOT TRANSLATED YET
- phone: NOT TRANSLATED YET
- previous_address: NOT TRANSLATED YET
+ address: Dirección actual
+ auth_app_disabled: no permitido
+ auth_app_enabled: permitido
+ authentication_app: App de autenticación
+ dob: Fecha de nacimiento
+ email: Email
+ full_name: Nombre completo
+ login: Información para iniciar sesión
+ password: Contraseña
+ phone: Teléfono
+ previous_address: Dirección anterior
reactivation:
- instructions: NOT TRANSLATED YET
- link: NOT TRANSLATED YET
- ssn: NOT TRANSLATED YET
+ instructions: Su perfil ha sido desactivado debido a un cambio de contraseña.
+ link: Reactive su perfil ahora.
+ ssn: Número de Seguro Social
verification:
- instructions: NOT TRANSLATED YET
- reactivate_button: NOT TRANSLATED YET
- success: NOT TRANSLATED YET
- with_phone_button: NOT TRANSLATED YET
+ instructions: Su cuenta requiere que un código secreto sea verificado.
+ reactivate_button: Ingrese el código que recibió por correo postal.
+ success: Su cuenta ha sido verificada.
+ with_phone_button: Verifique con su teléfono.
items:
- personal_key: NOT TRANSLATED YET
+ personal_key: Clave personal
links:
- regenerate_personal_key: NOT TRANSLATED YET
+ regenerate_personal_key: Obtenga una clave nueva.
security:
- link: NOT TRANSLATED YET
- text: NOT TRANSLATED YET
- welcome: Bienvenido
+ link: Obtenga más información en el Centro de ayuda
+ text: Para su seguridad, la información de su perfil está bloqueada.
+ welcome: Bienvenido/a
diff --git a/config/locales/account/fr.yml b/config/locales/account/fr.yml
new file mode 100644
index 00000000000..650827dd753
--- /dev/null
+++ b/config/locales/account/fr.yml
@@ -0,0 +1,34 @@
+---
+fr:
+ account:
+ index:
+ address: Adresse actuelle
+ auth_app_disabled: non activée
+ auth_app_enabled: activée
+ authentication_app: Application d'authentification
+ dob: Date de naissance
+ email: Adresse courriel
+ full_name: Nom complet
+ login: Information de connexion
+ password: Mot de passe
+ phone: Numéro de téléphone
+ previous_address: Adresse précédente
+ reactivation:
+ instructions: Votre profil a été récemment désactivé en raison d'une réinitialisation
+ de mot passe. Vous pouvez utiliser votre clé personnelle pour réactiver
+ votre profil.
+ link: NOT TRANSLATED YET
+ ssn: Numéro d'assurance sociale
+ verification:
+ instructions: Votre compte requiert la vérification d'un code secret.
+ reactivate_button: Entrez le code que vous avez reçu par la poste
+ success: Votre compte a été vérifié.
+ with_phone_button: Verifiez avec votre téléphone
+ items:
+ personal_key: Clé personnelle
+ links:
+ regenerate_personal_key: Obtenez une nouvelle clé
+ security:
+ link: En apprendre davantage dans le Centre d'aide
+ text: L'information de votre profil est verrouillée pour votre sécurité.
+ welcome: Bienvenue
diff --git a/config/locales/activerecord/en.yml b/config/locales/activerecord/en.yml
index 5bffa82f950..5c8acff29cc 100644
--- a/config/locales/activerecord/en.yml
+++ b/config/locales/activerecord/en.yml
@@ -1,3 +1,4 @@
+---
en:
activerecord:
errors:
@@ -5,5 +6,6 @@ en:
app_setting:
attributes:
value:
- cannot_disable_2fa_in_prod: Two-factor authentication cannot be disabled in production
+ cannot_disable_2fa_in_prod: Two-factor authentication cannot be disabled
+ in production
invalid: Value must be '1' or '0'
diff --git a/config/locales/activerecord/es.yml b/config/locales/activerecord/es.yml
index d4dc5ed34fb..8012f7b7422 100644
--- a/config/locales/activerecord/es.yml
+++ b/config/locales/activerecord/es.yml
@@ -1,3 +1,4 @@
+---
es:
activerecord:
errors:
@@ -5,5 +6,6 @@ es:
app_setting:
attributes:
value:
- cannot_disable_2fa_in_prod: La autenticación de dos factores no se puede desactivar en la producción
- invalid: El valor tiene que ser '1' o '0'
+ cannot_disable_2fa_in_prod: La autenticación de dos factores no se puede
+ suspender en producción
+ invalid: El valor debe ser '1' o '0'
diff --git a/config/locales/activerecord/fr.yml b/config/locales/activerecord/fr.yml
new file mode 100644
index 00000000000..86bdb29d16f
--- /dev/null
+++ b/config/locales/activerecord/fr.yml
@@ -0,0 +1,11 @@
+---
+fr:
+ activerecord:
+ errors:
+ models:
+ app_setting:
+ attributes:
+ value:
+ cannot_disable_2fa_in_prod: L'authentification à deux facteurs ne peut
+ être désactivée en production
+ invalid: La valeur doit être de '1' ou de '0'
diff --git a/config/locales/devise/en.yml b/config/locales/devise/en.yml
index 94019d25c3f..967cb8e345a 100644
--- a/config/locales/devise/en.yml
+++ b/config/locales/devise/en.yml
@@ -5,12 +5,10 @@ en:
already_confirmed: Your email address has already been confirmed. %{action}
confirmed: You have confirmed your email address
confirmed_but_must_set_password: You have confirmed your email address
- send_instructions: >
- You will receive an email with instructions for how to confirm your
- email address in a few minutes.
- send_paranoid_instructions: >
- You will receive an email with instructions for how to confirm your email
- address in a few minutes.
+ send_instructions: You will receive an email with instructions for how to confirm
+ your email address in a few minutes.
+ send_paranoid_instructions: You will receive an email with instructions for
+ how to confirm your email address in a few minutes.
failure:
already_authenticated: ''
inactive: Your account is not activated yet.
@@ -18,9 +16,8 @@ en:
last_attempt: You have one more attempt before your account is locked.
locked: Your account is now locked.
not_found_in_database: Invalid email or password.
- session_limited: >
- Your login credentials were used in another browser. Please sign in
- again to continue in this browser.
+ session_limited: Your login credentials were used in another browser. Please
+ sign in again to continue in this browser.
timeout: Your session expired. Please sign in again to continue.
unauthenticated: ''
unconfirmed: You need to confirm your email address before continuing.
@@ -36,65 +33,55 @@ en:
passwords:
choose_new_password: Choose a new password.
invalid_token: The reset password token is invalid. Try again.
- no_token: >
- You can’t access this page without coming from a password reset email.
- If you do come from a password reset email, please make sure you used the full
- link provided.
- send_instructions: >
- You will receive an email with instructions on how to reset your
- password in a few minutes.
- send_paranoid_instructions: >
- You will receive an email with instructions on how to reset your
- password in a few minutes.
+ no_token: You can’t access this page without coming from a password reset email.
+ If you do come from a password reset email, please make sure you used the
+ full link provided.
+ send_instructions: You will receive an email with instructions on how to reset
+ your password in a few minutes.
+ send_paranoid_instructions: You will receive an email with instructions on how
+ to reset your password in a few minutes.
token_expired: You took too long to reset your password. Try again.
updated: Your password has been changed. You are now signed in.
- updated_not_active: >
- Your password has been changed. Please sign in with your new password.
+ updated_not_active: Your password has been changed. Please sign in with your
+ new password.
registrations:
close_window: You can close this window if you are done.
- destroy_confirm: >
- Deleting your account cannot be undone. All data associated with your
- account will be removed. Are you sure you’d like to delete your account?
+ destroy_confirm: Deleting your account cannot be undone. All data associated
+ with your account will be removed. Are you sure you’d like to delete your
+ account?
destroyed: Your account has been successfully deleted.
- email_and_phone_need_confirmation: >
- Before we finish updating your account, we need to confirm both your new
- number and new email. Please follow the instructions below to confirm your new
- number, then check for an email from us. Follow the link in the email to confirm
- your new email address.
- email_update_needs_confirmation: >
- You updated your account, but we need to confirm your new email address.
- Check for an email from us, then follow the link in the email to confirm your
- new address.
+ email_and_phone_need_confirmation: Before we finish updating your account, we
+ need to confirm both your new number and new email. Please follow the instructions
+ below to confirm your new number, then check for an email from us. Follow
+ the link in the email to confirm your new email address.
+ email_update_needs_confirmation: You updated your account, but we need to confirm
+ your new email address. Check for an email from us, then follow the link in
+ the email to confirm your new address.
enabled_twofactor: You have enabled two-factor authentication.
- phone_update_needs_confirmation: >
- Your request to update your phone number was processed, but we need to
- confirm your new number first. Please follow the instructions below. If you do
- not confirm your new number, we will keep using your old phone number.
+ phone_update_needs_confirmation: Your request to update your phone number was
+ processed, but we need to confirm your new number first. Please follow the
+ instructions below. If you do not confirm your new number, we will keep using
+ your old phone number.
signed_up: Welcome! You have created an account.
- signed_up_but_inactive: >
- You have created an account. However, we could not sign you in because
- your account is not yet activated.
- signed_up_but_locked: >
- You have created an account. However, we could not sign you in because
- your account is locked.
+ signed_up_but_inactive: You have created an account. However, we could not sign
+ you in because your account is not yet activated.
+ signed_up_but_locked: You have created an account. However, we could not sign
+ you in because your account is locked.
start:
accordion: You will need
- bullet_1_html: Provide your email address and create a
- strong password.
- bullet_2_html: >
- Enable two-step authentication. This adds a second layer of security
- to your account by requiring you to enter a new code that’s sent to your phone every time
- you log in.
- bullet_3_html: >
- Provide basic information, such as your name, address, phone number,
- and social security number.
- bullet_4_html: >
- Provide a financial account number, such as your credit card number.
- This number will only be used to verify your identity.
- bullet_5_html: >
- When you are finished with this process we’ll give you a personal key. Write
- it down and store it in a safe place; it’s important. You’ll be asked for the
- personal key every time you make changes to your account.
+ bullet_1_html: Provide your email address and create a strong
+ password.
+ bullet_2_html: Enable two-step authentication. This adds
+ a second layer of security to your account by requiring you to enter a new
+ code that’s sent to your phone every time you log in.
+ bullet_3_html: Provide basic information, such as your name,
+ address, phone number, and social security number.
+ bullet_4_html: Provide a financial account number, such as
+ your credit card number. This number will only be used to verify your identity.
+ bullet_5_html: When you are finished with this process we’ll give you a personal
+ key. Write it down and store it in a safe place; it’s important.
+ You’ll be asked for the personal key every time you make changes to your
+ account.
learn_more: Learn more about verifying your identity
updated: Your account has been updated!
sessions:
@@ -105,52 +92,48 @@ en:
buttons:
confirm_with_sms: Confirm with text message
confirm_with_voice: Confirm with voice call
- choose_delivery_confirmation: >
- How would you like to receive your confirmation security code for %{phone}?
- choose_otp_delivery_html: >
- We will send it to %{phone} immediately. Message and data rates may apply.
+ choose_delivery_confirmation: How would you like to receive your confirmation
+ security code for %{phone}?
+ choose_otp_delivery_html: We will send it to %{phone} immediately. Message and
+ data rates may apply.
contact_administrator: Please contact your system administrator.
header_text: Enter your security code
- invalid_otp: >
- That security code is invalid. You can try entering it again or request a new
- one-time security code.
+ invalid_otp: That security code is invalid. You can try entering it again or
+ request a new one-time security code.
invalid_personal_key: That personal key is invalid.
- max_generic_login_attempts_reached: >
- Your account is temporarily locked.
- max_otp_login_attempts_reached: >
- Your account is temporarily locked because you have entered the one-time
- security code incorrectly too many times.
- max_otp_requests_reached: >
- Your account is temporarily locked because you have requested a
- security code too many times.
- max_personal_key_login_attempts_reached: >
- Your account is temporarily locked because you have entered the personal key
- incorrectly too many times.
+ max_generic_login_attempts_reached: Your account is temporarily locked.
+ max_otp_login_attempts_reached: Your account is temporarily locked because you
+ have entered the one-time security code incorrectly too many times.
+ max_otp_requests_reached: Your account is temporarily locked because you have
+ requested a security code too many times.
+ max_personal_key_login_attempts_reached: Your account is temporarily locked
+ because you have entered the personal key incorrectly too many times.
otp_delivery_preference:
instruction: You can change your choice the next time you sign in
+ phone_unsupported: We're unable to make phone calls to people in %{location}
+ at this time. You will receive your security code via text message
sms: Text message (SMS)
title: How would you like to receive your security code?
voice: Phone call
otp_phone_label: Phone number
otp_phone_label_info: Mobile or landline okay
- otp_setup_html: >
- Every time you log in, we will send you a one-time
- security code via text message or phone call. This helps safeguard your account.
+ otp_phone_label_info_modile_only: Mobile only
+ otp_setup_html: "Every time you log in, we will send you a
+ one-time security code via text message or phone call. This helps safeguard
+ your account."
otp_sms_disclaimer: Message and data rates may apply.
- please_confirm: >
- Your phone number has been set. Confirm it by entering the security code below.
- please_try_again_html: Please try again in %{time_remaining}.
personal_key_fallback:
link: Use a personal key instead
text_html: Don’t have access to your phone? %{link}.
personal_key_header_text: Enter your personal key
- personal_key_prompt: >
- You can use this personal key once. If you still need a code after
- signing in, go to your account settings page to get a new one.
+ personal_key_prompt: You can use this personal key once. If you still need a
+ code after signing in, go to your account settings page to get a new one.
+ please_confirm: Your phone number has been set. Confirm it by entering the security
+ code below.
+ please_try_again_html: Please try again in %{time_remaining}.
totp_fallback:
sms_link_text: get a code via text message
- text_html: >
- If you can’t use your authenticator app right now you can %{sms_link}
+ text_html: If you can’t use your authenticator app right now you can %{sms_link}
or %{voice_link}.
voice_link_text: get a code with a phone call
totp_header_text: Enter your authentication app code
diff --git a/config/locales/devise/es.yml b/config/locales/devise/es.yml
index acbd5a27c26..03801a1189a 100644
--- a/config/locales/devise/es.yml
+++ b/config/locales/devise/es.yml
@@ -1,28 +1,26 @@
+---
es:
devise:
confirmations:
- already_confirmed: Su dirección de correo electrónico ya ha sido confirmada. %{acción}
- confirmed: Su dirección de correo electrónico esta confirmada.
- confirmed_but_must_set_password: Ha confirmado tu dirección de correo electrónico
- send_instructions:
- Recibirá un correo electrónico con instrucciones para confirmar su
- dirección de correo electrónico en breve.
- send_paranoid_instructions:
- Recibirá un correo electrónico con instrucciones para confirmar su
- dirección de correo electrónico en breve.
+ already_confirmed: Su email ha sido confirmado. %{acción}
+ confirmed: Usted ha confirmado su email.
+ confirmed_but_must_set_password: Usted ha confirmado su email.
+ send_instructions: En pocos minutos recibirá un email con instrucciones para
+ confirmar el uso de su email.
+ send_paranoid_instructions: En pocos minutos recibirá un email con instrucciones
+ para confirmar el uso de su email.
failure:
already_authenticated: ''
inactive: Su cuenta aún no está activada.
- invalid: Dirección de correo electrónico o contraseña no válidos.
+ invalid: Email o contraseña no válido.
last_attempt: Tiene un intento más antes de que su cuenta esté bloqueada.
locked: Su cuenta está bloqueada.
- not_found_in_database: Dirección de correo electrónico o contraseña no válidos.
- session_limited:
- Sus credenciales de inicio de sesión se utilizaron en otro navegador. Inicia sesión
- de nuevo para continuar en este navegador.
- timeout: Su sesión ha caducado. Vuelva a iniciar sesión para continuar.
+ not_found_in_database: Email o contraseña no válido.
+ session_limited: Sus credenciales para iniciar una sesión se utilizaron en otro
+ navegador. Inicie una sesión nueva para continuar en este navegador.
+ timeout: Su sesión ha caducado. Vuelva a iniciar la sesión para continuar.
unauthenticated: ''
- unconfirmed: Necesita confirmar su dirección de correo electrónico antes de continuar.
+ unconfirmed: Debe confirmar su email antes de continuar.
mailer:
account_locked:
subject: Su cuenta de login.gov ha sido bloqueada.
@@ -31,116 +29,122 @@ es:
password_updated:
subject: Su contraseña ha sido cambiada.
reset_password_instructions:
- subject: Restablecer su contraseña.
+ subject: Restablezca su contraseña.
passwords:
choose_new_password: Elija una contraseña nueva.
- invalid_token: El token de contraseña de restablecimiento no es válido. Inténtalo de nuevo.
- no_token:
- No puede acceder a esta página sin proceder de un correo electrónico de restablecimiento de contraseña.
- Si procede de un correo electrónico de restablecimiento de contraseña, asegúrese de utilizar el
- enlace proporcionado.
- send_instructions:
- Recibirá un correo electrónico con instrucciones sobre cómo restablecer su
- contraseña en pocos minutos.
- send_paranoid_instructions:
- Recibirá un correo electrónico con instrucciones sobre cómo restablecer su
- contraseña en pocos minutos.
- token_expired: Ha tardado demasiado en restablecer su contraseña. Inténtalo de nuevo.
- updated: Su contraseña ha sido cambiada. Ahora ha iniciado sesión.
- updated_not_active:
- Su contraseña ha sido cambiada. Inicia sesión con su nueva contraseña.
+ invalid_token: El código para restablecer la contraseña no es válido. Inténtelo
+ de nuevo.
+ no_token: Usted no puede acceder a esta página si no procede desde el email
+ para restablecer la contraseña. Asegúrese de utilizar el enlace completo que
+ está en el email.
+ send_instructions: En pocos minutos recibirá un email con instrucciones para
+ restablecer su contraseña.
+ send_paranoid_instructions: En pocos minutos recibirá un email con instrucciones
+ para restablecer su contraseña.
+ token_expired: Ha tardado demasiado en restablecer su contraseña. Inténtelo
+ de nuevo.
+ updated: Su contraseña ha sido cambiada. Ahora ha iniciado una sesión.
+ updated_not_active: Su contraseña ha sido cambiada. Inicie sesión con su contraseña
+ nueva.
registrations:
- close_window: Puede cerrar esta ventana del navegador una vez que haya confirmado su dirección de correo electrónico.
- destroy_confirm:
- No se puede deshacer la eliminación de su cuenta. Todos los datos asociados a su
- suenta se eliminará. ¿Seguro que desea eliminar su cuenta?
+ close_window: Puede cerrar esta ventana del navegador una vez que haya finalizado.
+ destroy_confirm: No se puede deshacer la eliminación de su cuenta. Todos los
+ datos asociados a su cuenta se eliminarán. ¿Confirma que desea eliminar su
+ cuenta?
destroyed: Su cuenta se ha eliminado exitosamente.
- email_and_phone_need_confirmation:
- Antes de terminar de actualizar su cuenta, debemos confirmar su nuevo
- número y el nuevo correo electrónico. Siga las instrucciones a continuación para confirmar su nuevo
- número y, a continuación, compruebe si hay un correo electrónico de nosotros. Siga el enlace en el correo electrónico para confirmar su
- nueva dirección de correo electrónico.
- email_update_needs_confirmation:
- Has actualizado su cuenta, pero debemos confirmar su nueva dirección de correo electrónico.
- Busque un correo electrónico de nosotros, luego siga el enlace en el correo electrónico para confirmar su
- nueva dirección.
+ email_and_phone_need_confirmation: Antes de terminar de actualizar su cuenta,
+ debemos confirmar su nuevo número y nuevo email. Siga las siguientes instrucciones
+ para confirmar su nuevo número y, luego, encuentre el email que le enviamos
+ nosotros. Siga el enlace en el email para confirmar su nueva dirección de
+ email.
+ email_update_needs_confirmation: Ha actualizado su cuenta, pero debemos confirmar
+ su nueva dirección de email. Encuentre el email que nosotros le enviamos y
+ siga el enlace para confirmar su nueva dirección.
enabled_twofactor: Ha habilitado la autenticación de dos factores.
- phone_update_needs_confirmation:
- Su solicitud para actualizar su número de teléfono fue procesada, pero necesitamos
- confirmar primero su número nuevo. Por favor, siga las siguientes instrucciones. Si no
- confirma su nuevo número, seguiremos usando su número de teléfono antiguo.
- signed_up: ¡Bienvenido! Usted ha creado una cuenta.
- signed_up_but_inactive:
- Ha creado una cuenta. Sin embargo, no hemos podido iniciar sesión porque
- su cuenta aún no está activada.
- signed_up_but_locked:
- Ha creado una cuenta. Sin embargo, no hemos podido iniciar sesión porque
- su cuenta está bloqueada.
+ phone_update_needs_confirmation: Su solicitud para actualizar su número de teléfono
+ fue procesada, pero necesitamos confirmar primero su número nuevo. Por favor,
+ siga las siguientes instrucciones. Si no confirma su nuevo número, seguiremos
+ usando su número de teléfono antiguo.
+ signed_up: "¡Bienvenido/a! Usted ha creado una cuenta."
+ signed_up_but_inactive: Usted ha creado una cuenta. No hemos podido iniciar
+ su sesión porque su cuenta aún no está activada.
+ signed_up_but_locked: Usted ha creado una cuenta. No hemos podido iniciar su
+ sesión porque su cuenta está bloqueada.
start:
- accordion: NOT TRANSLATED YET
- bullet_1_html: NOT TRANSLATED YET
- bullet_2_html: NOT TRANSLATED YET
- bullet_3_html: NOT TRANSLATED YET
- bullet_4_html: NOT TRANSLATED YET
- bullet_5_html: NOT TRANSLATED YET
- learn_more: NOT TRANSLATED YET
- updated: ¡Su cuenta ha sido actualizada!
+ accordion: Usted necesitará
+ bullet_1_html: Proporcione su dirección de email y establezca
+ una contraseña segura.
+ bullet_2_html: Permita la autenticación de dos factores.
+ Esto agrega una segunda capa de seguridad a su cuenta al requerirle que
+ ingrese un nuevo código que recibirá en su teléfono cada vez que inicie
+ una sesión.
+ bullet_3_html: Proporcione información básica como su nombre,
+ dirección, número de teléfono y número de Seguro Social.
+ bullet_4_html: Proporcione un número de cuenta financiera
+ como su número de tarjeta de crédito. Este número solamente será usado para
+ verificar su identidad.
+ bullet_5_html: Cuando haya terminado con este proceso, le daremos una clave
+ personal. Escríbala y guárdela en un lugar seguro; es importante.
+ Se le pedirá la clave personal cada vez que realice cambios en su cuenta.
+ learn_more: Obtenga más información sobre la verificación de su identidad.
+ updated: "¡Su cuenta ha sido actualizada!"
sessions:
- already_signed_out: Ahora se ha cerrado la sesión.
+ already_signed_out: Su sesión ha terminado ahora.
signed_in: ''
- signed_out: Ahora se ha cerrado la sesión.
+ signed_out: Su sesión ha terminado ahora.
two_factor_authentication:
buttons:
confirm_with_sms: Confirmar con mensaje de texto
confirm_with_voice: Confirmar con llamada de voz
- choose_delivery_confirmation:
- ¿Cómo le gustaría recibir su código de confirmación para %{phone}?
- choose_otp_delivery_html:
- Selecciona cómo deseas recibir su código de acceso único para %{phone}.
- contact_administrator: Por favor, póngase en contacto con el administrador del sistema.
- header_text: Ingrese su contraseña.
- invalid_otp:
- Esa contraseña no es válida. Puede intentar volver a ingresarlo o solicitar una nueva
- Código de acceso único.
- invalid_personal_key: Ese código de recuperación no es válido.
- max_generic_login_attempts_reached:
- Su cuenta ha sido bloqueada temporalmente.
- max_otp_login_attempts_reached:
- Su cuenta ha sido bloqueada temporalmente porque ha ingresado el código de acceso único
- de forma incorrecta demasiadas veces.
- max_otp_requests_reached: NOT TRANSLATED YET
- max_personal_key_login_attempts_reached:
- Su cuenta ha sido bloqueada temporalmente porque ha ingresado el código de acceso único
- de forma incorrecta demasiadas veces.
+ choose_delivery_confirmation: "¿Cómo le gustaría recibir su código de confirmación
+ para %{phone}?"
+ choose_otp_delivery_html: Lo enviaremos a %{phone} inmediatamente. Puede estar
+ sujeto a cargos de mensajería y datos.
+ contact_administrator: Por favor, póngase en contacto con el administrador del
+ sistema.
+ header_text: Ingrese su código de seguridad
+ invalid_otp: Ese código de seguridad no es válido. Puede intentar ingresarlo
+ de nuevo o solicitar un nuevo código de seguridad de sólo un uso.
+ invalid_personal_key: Esa clave personal no es válida.
+ max_generic_login_attempts_reached: Su cuenta está bloqueada temporalmente.
+ max_otp_login_attempts_reached: Su cuenta ha sido bloqueada temporalmente porque
+ ha ingresado incorrectamente el código de seguridad de sólo un uso demasiadas
+ veces.
+ max_otp_requests_reached: Su cuenta ha sido bloqueada temporalmente porque ha
+ solicitado un código de seguridad demasiadas veces más de lo permitido.
+ max_personal_key_login_attempts_reached: Su cuenta ha sido bloqueada temporalmente
+ porque ha ingresado incorrectamente la clave personal demasiadas veces.
otp_delivery_preference:
- instruction: Puede cambiar su elección la próxima vez que inicie sesión.
- sms: Mensaje de texto (SMS)
- title: ¿Cómo le gustaría recibir su código de acceso?
+ instruction: Puede cambiar su elección la próxima vez que inicie una sesión.
+ phone_unsupported: NOT TRANSLATED YET
+ sms: Mensaje de texto (SMS, sigla en inglés)
+ title: "¿Cómo le gustaría recibir su código de seguridad?"
voice: Llamada telefónica
otp_phone_label: Número de teléfono
- otp_phone_label_info: Móvil o teléfono fijo está bien
- otp_setup_html:
- Cada vez que inicies sesión, le enviaremos un código de acceso único
- a través de un mensaje de texto o una llamada telefónica. Esto ayuda a proteger su cuenta.
- otp_sms_disclaimer: Se pueden aplicar tarifas de mensajes y datos.
- please_confirm:
- Se ha establecido el número de teléfono. Confírmelo introduciendo el código de acceso siguiente.
- please_try_again_html: Inténtalo de nuevo en %{time_remaining}.
+ otp_phone_label_info: El móvil o teléfono fijo está bien.
+ otp_phone_label_info_modile_only: NOT TRANSLATED YET
+ otp_setup_html: "Cada vez que inicie una sesión, strong> le enviaremos
+ un código de seguridad de sólo un uso por mensaje de texto o llamada telefónica.
+ Esto ayuda a proteger su cuenta."
+ otp_sms_disclaimer: Puede estar sujeto a cargos de mensajería y datos.
personal_key_fallback:
- link: código de recuperación
- text_html: ¿No tiene acceso a su teléfono? Use el %{link} en su lugar.
- personal_key_header_text: Ingrese su código de recuperación.
- personal_key_prompt: >
- Puede usar este código de recuperación una vez. Si todavía necesita un código después de
- iniciar sesión, vaya a la página de configuración de su cuenta para obtener una nueva.
+ link: Use una clave personal en su lugar
+ text_html: "¿No tiene acceso a su teléfono? %{link}."
+ personal_key_header_text: Ingrese su clave personal
+ personal_key_prompt: Puede usar esta clave personal una vez. Si todavía necesita
+ un código después de iniciar una sesión, vaya a la página de configuración
+ de su cuenta para obtener una clave nueva.
+ please_confirm: Su número de teléfono ha sido establecido. Confírmelo ingresando
+ el código de seguridad a continuación.
+ please_try_again_html: Inténtelo de nuevo en %{time_remaining}.
totp_fallback:
sms_link_text: Recibir un código por mensaje de texto
- text_html: >
- Si no puede usar su aplicación de autenticador ahora, puede %{sms_link}
- O %{voice_link}.
- voice_link_text: con una llamada telefónica
- totp_header_text: Ingrese su código de autenticador
- totp_info: Use cualquier aplicación de autenticador para escanear el código de QR que aparece a continuación.
- two_factor_setup: Agregar un número de teléfono
+ text_html: Si no puede usar su app de autenticación ahora, puede %{sms_link}
+ o %{voice_link}.
+ voice_link_text: obtenga un código con una llamada telefónica
+ totp_header_text: Ingrese su código de la app de autenticación
+ totp_info: Use cualquier app de autenticación para escanear el código QR que
+ aparece a continuación.
+ two_factor_setup: Añada un número de teléfono
user:
- new_otp_sent: Le enviaremos un nuevo código de acceso único.
+ new_otp_sent: Le enviamos un nuevo código de sólo un uso
diff --git a/config/locales/devise/fr.yml b/config/locales/devise/fr.yml
new file mode 100644
index 00000000000..983a01465bd
--- /dev/null
+++ b/config/locales/devise/fr.yml
@@ -0,0 +1,155 @@
+---
+fr:
+ devise:
+ confirmations:
+ already_confirmed: Votre adresse courriel a déjà été confirmée. %{action}
+ confirmed: Vous avez confirmé votre adresse courriel
+ confirmed_but_must_set_password: Vous avez confirmé votre adresse courriel
+ send_instructions: Vous recevrez dans quelques instants un courriel avec des
+ instructions pour confirmer votre adresse courriel.
+ send_paranoid_instructions: Vous recevrez dans quelques instants un courriel
+ avec des instructions pour confirmer votre adresse courriel.
+ failure:
+ already_authenticated: ''
+ inactive: Votre compte n'est pas encore activé.
+ invalid: Adresse courriel ou mot de passe non valide.
+ last_attempt: Il vous reste un essai avant que votre compte ne soit verrouillé.
+ locked: Votre compte est maintenant verrouillé.
+ not_found_in_database: Adresse courriel ou mot de passe non valide.
+ session_limited: Vos authentifiants ont été utilisés dans un autre navigateur.
+ Veuillez vous connecter de nouveau pour continuer avec ce navigateur.
+ timeout: Votre session est expirée. Veuillez vous connecter de nouveau pour
+ continuer.
+ unauthenticated: ''
+ unconfirmed: Vous devez confirmer votre adresse courriel avant de continuer.
+ mailer:
+ account_locked:
+ subject: Votre compte login.gov a été verrouillé
+ confirmation_instructions:
+ subject: Confirmez votre adresse courriel
+ password_updated:
+ subject: Votre mot de passe a été modifié
+ reset_password_instructions:
+ subject: Réinitialisez votre mot de passe
+ passwords:
+ choose_new_password: Choisissez un nouveau mot de passe.
+ invalid_token: Le jeton de réinitialisation de mot de passe n'est pas valide.
+ Veuillez essayer de nouveau.
+ no_token: Vous ne pouvez accéder à cette page que depuis un courriel de réinitialisation
+ de mot de passe. Si vous avez été redirigé à partir d'un courriel de réinitialisation
+ de mot de passe, veuillez vous assurer que vous avez utilisé le lien fourni
+ complet.
+ send_instructions: Vous recevrez dans quelques instants un courriel avec des
+ instructions pour réinitialiser votre mot de passe.
+ send_paranoid_instructions: Vous recevrez dans quelques instants un courriel
+ avec des instructions pour réinitialiser votre mot de passe.
+ token_expired: Vous avez pris trop de temps pour réinitialiser votre mot de
+ passe. Veuillez essayer de nouveau.
+ updated: Votre mot de passe a été modifié. Vous êtes maintenant connectée(e).
+ updated_not_active: Votre mot de passe a été modifié. Veuillez vous connecter
+ avec votre nouveau mot de passe.
+ registrations:
+ close_window: Vous pouvez fermer cette fenêtre si vous avez terminé.
+ destroy_confirm: La suppression de votre compte est irréversible. Toutes les
+ données associées à votre compte seront effacées. Souhaitez-vous vraiment
+ supprimer votre compte?
+ destroyed: Votre compte a bien été supprimé.
+ email_and_phone_need_confirmation: Avant de terminer la mise-à-jour de votre
+ compte, nous devons confirmer votre nouveau numéro et votre nouvelle adresse
+ courriel. Veuillez suivre les instructions ci-dessous pour confirmer votre
+ nouveau numéro, puis surveillez la réception d'un courriel envoyé par login.gov.
+ Suivez le lien inclus dans le courriel pour confirmer votre nouvelle adresse
+ courriel.
+ email_update_needs_confirmation: Vous avez mis à jour votre compte, mais nous
+ devons confirmer votre nouvelle adresse courriel. Surveillez la réception
+ d'un courriel envoyé par nous et suivez le lien inscrit pour confirmer votre
+ nouvelle adresse courriel.
+ enabled_twofactor: Vous avez activé l'authentification à deux facteurs.
+ phone_update_needs_confirmation: Votre demande de mise à jour de votre numéro
+ de téléphone a été traitée, mais nous devons d'abord confirmer votre nouveau
+ numéro. Veuillez suivre les instructions ci-dessous. Si vous ne confirmez
+ pas votre nouveau numéro, nous continuerons d'utiliser votre ancien numéro
+ de téléphone.
+ signed_up: Bienvenue! Vous avez créé un compte.
+ signed_up_but_inactive: Vous avez créé un compte. Cependant, nous n'avons pu
+ vous connecter, car votre compte n'est pas encore activé.
+ signed_up_but_locked: Vous avez créé un compte. Cependant, nous n'avons pu vous
+ connecter, car votre compte est verrouillé.
+ start:
+ accordion: Vous devrez
+ bullet_1_html: fournir votre adresse courriel et créer un
+ mot de passe fort.
+ bullet_2_html: Activez l'authentification à deux facteurs.
+ Cela permet d'ajouter un degré de sécurité à votre compte, car vous devez
+ entrer un nouveau code envoyé à votre téléphone chaque fois que vous vous
+ connectez.
+ bullet_3_html: Fournissez de l'information de base, comme
+ votre nom, adresse et numéro de sécurité sociale.
+ bullet_4_html: Fournissez un numéro de compte bancaire, comme
+ votre numéro de carte de crédit.
+ bullet_5_html: 'Lorsque vous aurez terminé ce processus, nous vous donnerons
+ une clé personnelle. Notez-la et placez-la en lieu sûr : c''est
+ important. On vous demandera la clé personnelle chaque fois que
+ vous apporterez des changements à votre compte.'
+ learn_more: En savoir plus sur la vérification de votre identité
+ updated: Votre compte a été mis à jour!
+ sessions:
+ already_signed_out: Vous êtes maintenant connecté(e).
+ signed_in: ''
+ signed_out: Vous êtes maintenant déconnecté(e).
+ two_factor_authentication:
+ buttons:
+ confirm_with_sms: Confirmer par SMS
+ confirm_with_voice: Confirmer avec un appel vocal
+ choose_delivery_confirmation: Comment souhaitez-vous recevoir votre code de
+ sécurité de confirmation pour %{phone}?
+ choose_otp_delivery_html: Nous l'envoyons à %{phone} immédiatement. Les tarifs
+ liés aux SMS et aux données peuvent s'appliquer.
+ contact_administrator: Veuillez communiquer avec votre administrateur système.
+ header_text: Entrez votre code de sécurité
+ invalid_otp: Ce code de sécurité est non valide. Vous pouvez essayer de l'entrer
+ de nouveau ou demander un nouveau code de sécurité à utilisation unique.
+ invalid_personal_key: Cette clé personnelle est non valide.
+ max_generic_login_attempts_reached: Votre compte est temporairement verrouillé.
+ max_otp_login_attempts_reached: Votre compte est temporairement verrouillé,
+ car vous avez entré le code de sécurité à utilisation unique de façon erronée
+ à de trop nombreuses reprises.
+ max_otp_requests_reached: NOT TRANSLATED YET
+ max_personal_key_login_attempts_reached: Votre compte est temporairement verrouillé,
+ car vous avez entré le code de sécurité à utilisation unique de façon erronée
+ à de trop nombreuses reprises.
+ otp_delivery_preference:
+ instruction: Vous pourrez changer votre choix la prochaine fois que vous vous
+ connecterez
+ phone_unsupported: NOT TRANSLATED YET
+ sms: Message texte (SMS)
+ title: Comment souhaitez-vous recevoir votre code de sécurité?
+ voice: Appel téléphonique
+ otp_phone_label: Numéro de téléphone
+ otp_phone_label_info: Cellulaire ou ligne fixe est acceptable
+ otp_phone_label_info_modile_only: NOT TRANSLATED YET
+ otp_setup_html: "Chaque fois que vous vous connecterez, nous
+ vous enverrons un code de sécurité à utilisation unique par message texte
+ ou par appel téléphonique. Cela aide à protéger votre compte."
+ otp_sms_disclaimer: Les tarifs liés aux SMS et aux données peuvent s'appliquer.
+ personal_key_fallback:
+ link: Utlisez plutôt une clé personnelle
+ text_html: Vous n'avez pas accès à votre téléphone? %{link}.
+ personal_key_header_text: Entrez votre clé personnelle
+ personal_key_prompt: Vous pouvez utiliser cette clé personnelle une fois seulement.
+ Si vous avez toujours besoin d'un code après votre connexion, allez à la page
+ des réglages de votre compte pour en obtenir un nouveau.
+ please_confirm: Votre numéro de téléphone a été entré. Confirmez-le en entrant
+ le code de sécurité ci-dessous.
+ please_try_again_html: Veuillez essayer de nouveau dans %{time_remaining}.
+ totp_fallback:
+ sms_link_text: Obtenir un code via message texte
+ text_html: Si vous ne pouvez utiliser votre application d'authentification
+ maintenant, vous pouvez %{sms_link} ou %{voice_link}.
+ voice_link_text: obtenir un code par appel téléphonique
+ totp_header_text: Entrez votre code d'application d'authentification
+ totp_info: Utilisez n'importe quelle application d'authentification pour balayer
+ le code QR ci-dessous.
+ two_factor_setup: Ajoutez un numéro de téléphone
+ user:
+ new_otp_sent: Nous vous avons envoyé un code de sécurité à utilisation unique.
diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml
index 23aa459e008..38a86bf6719 100644
--- a/config/locales/errors/en.yml
+++ b/config/locales/errors/en.yml
@@ -4,23 +4,23 @@ en:
confirm_password_incorrect: Incorrect password.
invalid_authenticity_token: Oops, something went wrong. Please sign in again.
invalid_totp: Invalid code. Please try again.
- max_password_attempts_reached: >
- You've entered too many incorrect passwords. You can reset your password using the "Forgot your password?" link.
+ max_password_attempts_reached: You've entered too many incorrect passwords. You
+ can reset your password using the "Forgot your password?" link.
messages:
already_confirmed: was already confirmed, please try signing in
blank: Please fill in this field.
confirmation_code_incorrect: Incorrect code. Did you type it in correctly?
- confirmation_invalid_token: >
- Invalid confirmation link. Either the link expired or you
- already confirmed your account.
- confirmation_period_expired: >
- Expired confirmation link.
- You can click "Resend confirmation instructions" to get another one.
+ confirmation_invalid_token: Invalid confirmation link. Either the link expired
+ or you already confirmed your account.
+ confirmation_period_expired: Expired confirmation link. You can click "Resend
+ confirmation instructions" to get another one.
expired: has expired, please request a new one
format_mismatch: Please match the requested format.
- improbable_phone: Invalid phone number. Please make sure you enter a 10-digit phone number.
+ improbable_phone: Invalid phone number. Please make sure you enter a valid phone
+ number.
missing_field: Please fill in this field.
- no_password_reset_profile: No profile has been recently deactivated due to a password reset
+ no_password_reset_profile: No profile has been recently deactivated due to a
+ password reset
no_pending_profile: No profile is waiting for verification
not_found: not found
not_locked: was not locked
diff --git a/config/locales/errors/es.yml b/config/locales/errors/es.yml
index f3e3e2d66fc..bdac55909c4 100644
--- a/config/locales/errors/es.yml
+++ b/config/locales/errors/es.yml
@@ -1,39 +1,36 @@
---
es:
errors:
- confirm_password_incorrect: La contraseña proporcionada es incorrecta.
- invalid_authenticity_token: Huy! Algo salió mal. Inicia sesión de nuevo.
- invalid_totp: Se ha introducido un código no válido. Vuelve a intentarlo.
- max_password_attempts_reached: >
- Cerramos la sesión porque no proporcionó su contraseña correcta. Si
- olvidó su contraseña, haga clic en "¿Olvidó su contraseña?" enlace abajo.
+ confirm_password_incorrect: La contraseña es incorrecta.
+ invalid_authenticity_token: "¡Oops! Algo salió mal. Inicie la sesión de nuevo."
+ invalid_totp: El código es inválido. Vuelva a intentarlo.
+ max_password_attempts_reached: Ha ingresado demasiadas contraseñas incorrectas.
+ Puede restablecer su contraseña usando el enlace "¿Olvidó su contraseña?".
messages:
- already_confirmed: Ya está confirmado, por favor intenta iniciar sesión.
+ already_confirmed: ya estaba confirmado, por favor intente iniciar una sesión
blank: Por favor, rellenar este campo.
- confirmation_code_incorrect: NOT TRANSLATED YET
- confirmation_invalid_token:
- El enlace de confirmación en el que ha hecho clic ya no es válido. Esto puede haber
- sido causado al hacer clic en un enlace antiguo de su correo electrónico, o puede ser que ya haya
- confirmado su cuenta.
- confirmation_period_expired:
- Has tardado más de %{period} para confirmar su dirección de correo electrónico.
- Por favor haga clic en "Reenviar instrucciones de confirmación".
- expired: Ha caducado, por favor solicite uno nuevo
- format_mismatch: Por favor, coincidir con el formato solicitado.
- improbable_phone: El número de teléfono no es válido. Asegúrese de ingresar un número de teléfono de 10 dígitos.
- missing_field: Por favor, rellenar este campo.
- no_password_reset_profile: Ningún perfil ha sido desactivado recientemente por un restablecimiento de contraseña.
+ confirmation_code_incorrect: El código es incorrecto. ¿Lo escribió correctamente?
+ confirmation_invalid_token: El enlace de confirmación no es válido. El enlace
+ expiró o usted ya ha confirmado su cuenta.
+ confirmation_period_expired: El enlace de confirmación expiró. Puede hacer clic
+ en "Reenviar instrucciones de confirmación" para obtener otro.
+ expired: ha caducado, por favor solicite uno nuevo
+ format_mismatch: Por favor, use el formato solicitado.
+ improbable_phone: NOT TRANSLATED YET
+ missing_field: Por favor, rellene este campo.
+ no_password_reset_profile: Ningún perfil ha sido desactivado recientemente por
+ un restablecimiento de contraseña.
no_pending_profile: Ningún perfil está esperando verificación
not_found: no encontrado
not_locked: no estaba bloqueado
not_saved:
- one: '1 error prohibió este %{resource} de ser guardado:'
- other: "%{count} errores prohibieron este %{resource} de ser guardado:"
- otp_incorrect: El código secreto es incorrecto
+ one: '1 error no permitió guardar este %{resource}:'
+ other: "%{count} errores no permitieron guardar este %{resource}:"
+ otp_incorrect: El código es incorrecto. ¿Lo escribió correctamente?
password_incorrect: La contraseña es incorrecta
- personal_key_incorrect: El código de recuperación es incorrecto
+ personal_key_incorrect: La clave personal es incorrecta
requires_phone: requiere que ingrese su número de teléfono.
- unauthorized_authn_context: NOT TRANSLATED YET
- unauthorized_service_provider: NOT TRANSLATED YET
- weak_password: Su contraseña no es suficientemente fuerte. %{feedback}
+ unauthorized_authn_context: Contexto de autenticación no autorizado
+ unauthorized_service_provider: Proveedor de servicio no autorizado
+ weak_password: Su contraseña no es suficientemente segura. %{feedback}
not_authorized: No está autorizado para realizar esta acción.
diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml
new file mode 100644
index 00000000000..798dbd4f92c
--- /dev/null
+++ b/config/locales/errors/fr.yml
@@ -0,0 +1,39 @@
+---
+fr:
+ errors:
+ confirm_password_incorrect: Mot de passe incorrect.
+ invalid_authenticity_token: Oups, une erreur s'est produite. Veuillez vous connecter
+ de nouveau.
+ invalid_totp: Code non valide. Veuillez essayer de nouveau.
+ max_password_attempts_reached: Vous avez inscrit des mots de passe incorrects
+ un trop grand nombre de fois. Vous pouvez réinitialiser votre mot de passe en
+ utilisant le lien « Vous avez oublié votre mot de passe? ».
+ messages:
+ already_confirmed: a déjà été confirmé, veuillez essayer de vous connecter
+ blank: Veuillez remplir ce champ.
+ confirmation_code_incorrect: Code non valide. L'avez-vous inscrit correctement?
+ confirmation_invalid_token: Lien de confirmation non valide. Le lien est expiré
+ ou vous avez déjà confirmé votre compte.
+ confirmation_period_expired: Lien de confirmation expiré. Vous pouvez cliquer
+ sur « Envoyer les instructions de confirmation de nouveau » pour en obtenir
+ un autre.
+ expired: est expiré, veuillez en demander un nouveau
+ format_mismatch: Veuillez vous assurer de respecter le format requis.
+ improbable_phone: NOT TRANSLATED YET
+ missing_field: Veuillez remplir ce champ.
+ no_password_reset_profile: Aucun profil récemment désactivé en raison d'une
+ réinitialisation de mot de passe
+ no_pending_profile: Aucun profil en attente de vérification
+ not_found: introuvable
+ not_locked: n'a pas été verrouillé
+ not_saved:
+ one: '1 erreur a interdit la sauvegarde de cette %{resource} :'
+ other: "%{count} des erreurs ont empêché la sauvegarde de cette %{resource} :"
+ otp_incorrect: Code non valide. L'avez-vous inscrit correctement?
+ password_incorrect: Mot de passe incorrect
+ personal_key_incorrect: Clé personnelle incorrecte
+ requires_phone: vous demande d'entrer votre numéro de téléphone.
+ unauthorized_authn_context: Contexte d'authentification non autorisé
+ unauthorized_service_provider: Fournisseur de service non autorisé
+ weak_password: Votre mot de passe n'est pas assez fort. %{feedback}
+ not_authorized: Vous n'êtes pas autorisé(e) à effectuer cette action.
diff --git a/config/locales/event_types/en.yml b/config/locales/event_types/en.yml
index f996c53eb75..cecdb7006da 100644
--- a/config/locales/event_types/en.yml
+++ b/config/locales/event_types/en.yml
@@ -6,8 +6,8 @@ en:
authenticated_at: Signed in at %{service_provider}
authenticator_disabled: Authenticator app disabled
authenticator_enabled: Authenticator app enabled
+ eastern_timestamp: "%{timestamp} (Eastern)"
email_changed: Email address changed
phone_changed: Phone number changed
phone_confirmed: Phone confirmed
- eastern_timestamp: '%{timestamp} (Eastern)'
usps_mail_sent: Letter sent
diff --git a/config/locales/event_types/es.yml b/config/locales/event_types/es.yml
index 39f23a3a646..a29a2c60409 100644
--- a/config/locales/event_types/es.yml
+++ b/config/locales/event_types/es.yml
@@ -2,12 +2,12 @@
es:
event_types:
account_created: Cuenta creada
- account_verified: NOT TRANSLATED YET
- authenticated_at: Se ha iniciado sesión en %{service_provider}
- authenticator_disabled: La aplicación de autenticador está inhabilitada
- authenticator_enabled: Aplicación de autenticador activada
- email_changed: Dirección de correo electrónico cambiada
+ account_verified: Cuenta verificada
+ authenticated_at: Sesión iniciada en %{service_provider}
+ authenticator_disabled: La app de autenticación fue suspendida
+ authenticator_enabled: App de autenticación permitido
+ eastern_timestamp: "%{timestamp} (hora del Este)"
+ email_changed: Email cambiado
phone_changed: Número de teléfono cambiado
phone_confirmed: Teléfono confirmado
- eastern_timestamp: '%{timestamp} (zona horaria del Este)'
usps_mail_sent: Carta enviada
diff --git a/config/locales/event_types/fr.yml b/config/locales/event_types/fr.yml
new file mode 100644
index 00000000000..62131dacbc6
--- /dev/null
+++ b/config/locales/event_types/fr.yml
@@ -0,0 +1,13 @@
+---
+fr:
+ event_types:
+ account_created: Compte créé
+ account_verified: Compte vérifié
+ authenticated_at: Connecté à %{service_provider}
+ authenticator_disabled: Application d'authentification désactivée
+ authenticator_enabled: Application d'authentification activée
+ eastern_timestamp: "%{timestamp} (Eastern)"
+ email_changed: Adresse courriel modifiée
+ phone_changed: Numéro de téléphone modifié
+ phone_confirmed: Numéro de téléphone confirmé
+ usps_mail_sent: Lettre envoyée
diff --git a/config/locales/forms/en.yml b/config/locales/forms/en.yml
index 480bfc9b794..b736b739bdd 100644
--- a/config/locales/forms/en.yml
+++ b/config/locales/forms/en.yml
@@ -24,15 +24,16 @@ en:
show: Show password
personal_key:
alternative: Don't have your personal key?
- title: Enter your personal key
- instructions: Please confirm you have a copy of your personal key by entering it below.
confirmation_label: Personal key
+ instructions: Please confirm you have a copy of your personal key by entering
+ it below.
+ title: Enter your personal key
registration:
labels:
email: Email address
totp_setup:
- totp_intro_html: >
- When you sign in, you can get your security code from an authentication app. %{link}
+ totp_intro_html: When you sign in, you can get your security code from an authentication
+ app. %{link}
totp_step_1: Open your authentication app
totp_step_2: Enter this key in the app
totp_step_3: Enter the code from the app
@@ -41,7 +42,7 @@ en:
personal_key: Personal key
try_again: Use another phone number
verify_profile:
- name: Confirmation code
instructions: Enter the ten-character code in the letter we sent you.
+ name: Confirmation code
submit: Confirm account
title: Confirm your account
diff --git a/config/locales/forms/es.yml b/config/locales/forms/es.yml
index 6facc87038c..be37a8653f4 100644
--- a/config/locales/forms/es.yml
+++ b/config/locales/forms/es.yml
@@ -4,13 +4,13 @@ es:
buttons:
back: Atrás
continue: Continuar
- disable: Inhabilitar
+ disable: Suspender
edit: Editar
- enable: Habilitar
+ enable: Permitir
resend_confirmation: Reenviar instrucciones de confirmación
- send_security_code: Enviar contraseña
+ send_security_code: Enviar código de seguridad
submit:
- confirm_change: Confirm change
+ confirm_change: Confirmar cambio
default: Enviar
update: Actualización
confirmation:
@@ -18,29 +18,31 @@ es:
passwords:
edit:
buttons:
- submit: Cambia la contraseña
+ submit: Cambiar la contraseña
labels:
password: Nueva contraseña
- show: NOT TRANSLATED YET
+ show: Mostrar contraseña
personal_key:
- alternative: NOT TRANSLATED YET
- title: NOT TRANSLATED YET
- instructions: NOT TRANSLATED YET
- confirmation_label: NOT TRANSLATED YET
+ alternative: "¿No tiene su clave personal?"
+ confirmation_label: Clave personal
+ instructions: Confirme que tiene una copia de su clave personal ingresándola
+ a continuación.
+ title: Ingrese su clave personal
registration:
labels:
- email: Dirección de correo electrónico
+ email: Email
totp_setup:
- totp_intro_html: NOT TRANSLATED YET
- totp_step_1: NOT TRANSLATED YET
- totp_step_2: NOT TRANSLATED YET
- totp_step_3: NOT TRANSLATED YET
+ totp_intro_html: Al iniciar una sesión, puede obtener el código de seguridad
+ de una app de autenticación. %{link}
+ totp_step_1: Abra su app de autenticación.
+ totp_step_2: Ingrese esta clave en la app.
+ totp_step_3: Ingrese el código de la app.
two_factor:
- code: Código de acceso único
- personal_key: Código de recuperación
- try_again: Inténtalo de nuevo
+ code: Código de seguridad de sólo un uso
+ personal_key: Clave personal
+ try_again: Use otro número de teléfono.
verify_profile:
- name: NOT TRANSLATED YET
- title: NOT TRANSLATED YET
- submit: NOT TRANSLATED YET
- instructions: NOT TRANSLATED YET
+ instructions: Ingrese el código de 10 caracteres que le enviamos en la carta.
+ name: Código de confirmación
+ submit: Confirmar cuenta
+ title: Confirme su cuenta
diff --git a/config/locales/forms/fr.yml b/config/locales/forms/fr.yml
new file mode 100644
index 00000000000..fe721ae4650
--- /dev/null
+++ b/config/locales/forms/fr.yml
@@ -0,0 +1,49 @@
+---
+fr:
+ forms:
+ buttons:
+ back: Retour
+ continue: Continuer
+ disable: Désactiver
+ edit: Modifier
+ enable: Activer
+ resend_confirmation: Envoyer les instructions de confirmation de nouveau
+ send_security_code: Envoyer le code de sécurité
+ submit:
+ confirm_change: Confirmer le changement
+ default: Soumettre
+ update: Mettre à jour
+ confirmation:
+ show_hdr: Créez un mot de passe fort
+ passwords:
+ edit:
+ buttons:
+ submit: Changer le mot de passe
+ labels:
+ password: Nouveau mot de passe
+ show: Afficher le mot de passe
+ personal_key:
+ alternative: NOT TRANSLATED YET
+ confirmation_label: Clé personnelle
+ instructions: Veuillez confirmer que vous avez une copie de votre clé personnelle
+ en l'entrant ci-dessous.
+ title: Entrez votre clé personnelle
+ registration:
+ labels:
+ email: Adresse courriel
+ totp_setup:
+ totp_intro_html: Lorsque vous vous connectez, vous pouvez obtenir votre code
+ de sécurité d'une application d'authentification. %{link}
+ totp_step_1: Démarrez votre application d'authentification
+ totp_step_2: Entrez cette clé dans l'application
+ totp_step_3: Entrez le code à partir de l'application
+ two_factor:
+ code: Code de sécurité
+ personal_key: Clé personnelle
+ try_again: Utilisez un autre numéro de téléphone
+ verify_profile:
+ instructions: Entrez le code à dix caractères qui se trouve dans la lettre que
+ nous vous avons envoyée.
+ name: Code de confirmation
+ submit: Confirmer le compte
+ title: Confirmez votre compte
diff --git a/config/locales/headings/en.yml b/config/locales/headings/en.yml
index d1cf87dadb9..7dfa014dc14 100644
--- a/config/locales/headings/en.yml
+++ b/config/locales/headings/en.yml
@@ -1,6 +1,13 @@
---
en:
headings:
+ account:
+ account_history: Account history
+ login_info: Your account
+ profile_info: Profile information
+ reactivate: Reactivate your account
+ two_factor: Two-factor authentication
+ verified_account: Verified Account
confirmations:
new: Send another confirmation email
create_account_with_sp:
@@ -14,13 +21,6 @@ en:
change: Change your password
confirm: Confirm your current password to continue
forgot: Forgot your password?
- account:
- account_history: Account history
- login_info: Your account
- profile_info: Profile information
- reactivate: Reactivate your account
- two_factor: Two-factor authentication
- verified_account: Verified Account
personal_key: Here is your personal key
registrations:
enter_email: Start creating an account
diff --git a/config/locales/headings/es.yml b/config/locales/headings/es.yml
index dd431a09d86..76669445455 100644
--- a/config/locales/headings/es.yml
+++ b/config/locales/headings/es.yml
@@ -1,32 +1,32 @@
---
es:
headings:
+ account:
+ account_history: Historial de cuenta
+ login_info: Su cuenta
+ profile_info: Información de perfil
+ reactivate: Reactive su cuenta
+ two_factor: Autenticación de dos factores
+ verified_account: Cuenta verificada
confirmations:
- new: Enviar otro correo electrónico de confirmación
+ new: Enviar otro email de confirmación
create_account_with_sp:
- sp_text: NOT TRANSLATED YET
- create_account_without_sp: Crear una cuenta de login.gov
+ sp_text: está usando login.gov para ingresar de manera fácil y segura.
+ create_account_without_sp: Establezca una cuenta de login.gov
edit_info:
- email: Cambiar su correo electrónico
- password: Cambiar su contraseña
- phone: Ingresar su nuevo número de teléfono
+ email: Cambie su email
+ password: Cambie su contraseña
+ phone: Ingrese su nuevo número de teléfono
passwords:
- change: Cambiar su contraseña
+ change: Cambie su contraseña
confirm: Confirme la contraseña actual para continuar
- forgot: ¿Olvidó su contraseña?
- account:
- account_history: La historia de la cuenta
- login_info: Información de la cuenta
- profile_info: NOT TRANSLATED YET
- reactivate: NOT TRANSLATED YET
- two_factor: Autenticación de dos factores
- verified_account: Cuenta verificada
- personal_key: Asegúrese de que siempre puede iniciar sesión
+ forgot: "¿Olvidó su contraseña?"
+ personal_key: Aquí está su clave personal
registrations:
- enter_email: Empezar a crear una cuenta
- session_timeout_warning: ¿Necesita mas tiempo?
+ enter_email: Empiece creando una cuenta
+ session_timeout_warning: "¿Necesita más tiempo?"
sign_in_with_sp: Iniciar sesión para continuar con %{sp}
sign_in_without_sp: Iniciar sesión
totp_setup:
- new: Configurar la autenticación de dos factores
- verify_email: Revisar su correo electrónico
+ new: Permita una app de autenticación
+ verify_email: Revise su email
diff --git a/config/locales/headings/fr.yml b/config/locales/headings/fr.yml
new file mode 100644
index 00000000000..226fd4fedd5
--- /dev/null
+++ b/config/locales/headings/fr.yml
@@ -0,0 +1,32 @@
+---
+fr:
+ headings:
+ account:
+ account_history: Historique du compte
+ login_info: Votre compte
+ profile_info: Information du profil
+ reactivate: NOT TRANSLATED YET
+ two_factor: Authentification à deux facteurs
+ verified_account: Compte vérifié
+ confirmations:
+ new: Envoyer un autre courriel de confirmation
+ create_account_with_sp:
+ sp_text: utilise login.gov pour rendre la connexion plus facile et plus sécurisée.
+ create_account_without_sp: Créer un compte login.gov
+ edit_info:
+ email: Changez votre courriel
+ password: Changez votre mot de passe
+ phone: Entrez votre nouveau numéro de téléphone
+ passwords:
+ change: Changez votre mot de passe
+ confirm: Confirmez votre mot de passe actuel pour continuer
+ forgot: Vous avez oublié votre mot de passe?
+ personal_key: Voici votre clé personnelle
+ registrations:
+ enter_email: Commencer à créer le compte
+ session_timeout_warning: Vous avez besoin de plus de temps?
+ sign_in_with_sp: Connectez-vous pour continuer à %{sp}
+ sign_in_without_sp: Connexion
+ totp_setup:
+ new: Activer une application d'authentification
+ verify_email: Consultez vos courriels
diff --git a/config/locales/help_text/en.yml b/config/locales/help_text/en.yml
index 2cd0638c65f..784142c4ee3 100644
--- a/config/locales/help_text/en.yml
+++ b/config/locales/help_text/en.yml
@@ -1,14 +1,13 @@
---
en:
help_text:
- change_factor: >
- Before you're able to edit your %{factor}, you will need to confirm your
- password and receive a security code on your phone.
+ change_factor: Before you're able to edit your %{factor}, you will need to confirm
+ your password and receive a security code on your phone.
requested_attributes:
- intro_html: This is the only information %{app_name} will share with %{sp}
address: Mailing address
birthdate: Date of birth
email: Email address
full_name: Full name
+ intro_html: This is the only information %{app_name} will share with %{sp}
phone: Phone number
social_security_number: Social Security number
diff --git a/config/locales/help_text/es.yml b/config/locales/help_text/es.yml
index dc3ce1789fa..9812f0589ec 100644
--- a/config/locales/help_text/es.yml
+++ b/config/locales/help_text/es.yml
@@ -1,14 +1,13 @@
---
es:
help_text:
- change_factor: >
- NOT TRANSLATED YET
+ change_factor: Antes de poder editar su% {factor}, deberá confirmar su contraseña
+ y recibir un código de seguridad en su teléfono.
requested_attributes:
- intro_html: NOT TRANSLATED YET
- address: NOT TRANSLATED YET
- birthdate: NOT TRANSLATED YET
- email: NOT TRANSLATED YET
- full_name: NOT TRANSLATED YET
- phone: NOT TRANSLATED YET
- social_security_number: NOT TRANSLATED YET
-
+ address: Dirección de correo postal
+ birthdate: Fecha de nacimiento
+ email: Email
+ full_name: Nombre completo
+ intro_html: Esta es la única información que %{app_name} compartirá con %{sp}
+ phone: Teléfono
+ social_security_number: Número de Seguro Social
diff --git a/config/locales/help_text/fr.yml b/config/locales/help_text/fr.yml
new file mode 100644
index 00000000000..bf4426a6e57
--- /dev/null
+++ b/config/locales/help_text/fr.yml
@@ -0,0 +1,14 @@
+---
+fr:
+ help_text:
+ change_factor: Avant de pouvoir modifier votre %{factor}, vous devrez confirmer
+ votre mot de passe et recevoir un code de sécurité sur votre téléphone.
+ requested_attributes:
+ address: Adresse postale
+ birthdate: Date de naissance
+ email: Adresse courriel
+ full_name: Nom complet
+ intro_html: Il s'agit de la seule information que %{app_name} partagera avec
+ %{sp}
+ phone: Numéro de téléphone
+ social_security_number: Numéro de sécurité sociale
diff --git a/config/locales/i18n/en.yml b/config/locales/i18n/en.yml
new file mode 100644
index 00000000000..e1f2dc057ee
--- /dev/null
+++ b/config/locales/i18n/en.yml
@@ -0,0 +1,8 @@
+---
+en:
+ i18n:
+ language: Language
+ locale:
+ en: English
+ es: Español
+ fr: Français
diff --git a/config/locales/i18n/es.yml b/config/locales/i18n/es.yml
new file mode 100644
index 00000000000..788a7662875
--- /dev/null
+++ b/config/locales/i18n/es.yml
@@ -0,0 +1,8 @@
+---
+es:
+ i18n:
+ language: Idioma
+ locale:
+ en: English
+ es: Español
+ fr: Français
diff --git a/config/locales/i18n/fr.yml b/config/locales/i18n/fr.yml
new file mode 100644
index 00000000000..3f538e17e75
--- /dev/null
+++ b/config/locales/i18n/fr.yml
@@ -0,0 +1,8 @@
+---
+fr:
+ i18n:
+ language: Langue
+ locale:
+ en: English
+ es: Español
+ fr: Français
diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml
index fd3104718a8..99ad1b0168d 100644
--- a/config/locales/idv/en.yml
+++ b/config/locales/idv/en.yml
@@ -2,23 +2,24 @@
en:
idv:
buttons:
- activate_by_phone: Activate by phone
activate_by_mail: Activate by mail
- continue: Continue identity verification
+ activate_by_phone: Activate by phone
cancel: Cancel and return to your profile
+ continue: Continue identity verification
help: Continue to Help Center
mail:
- send: Send a letter
resend: Send another letter
+ send: Send a letter
cancel:
modal_header: Are you sure you want to cancel?
warning_header: If you cancel now
warning_points:
- - We won’t be able to verify your identity
- - We won't keep a record of your name, address, birth date, or Social Security number
- - You won't be able to securely access your information using login.gov
- - You’ll still have a login.gov account for your email address
- - You can manage that account on your profile page
+ - We won’t be able to verify your identity
+ - We won't keep a record of your name, address, birth date, or Social Security
+ number
+ - You won't be able to securely access your information using login.gov
+ - You’ll still have a login.gov account for your email address
+ - You can manage that account on your profile page
errors:
bad_dob: Your date of birth must be a valid date.
duplicate_ssn: An account already exists with the information you provided.
@@ -26,12 +27,11 @@ en:
hardfail: We were unable to verify your identity at this time.
incorrect_password: The password you entered is not correct.
invalid_ccn: Credit card number should be only last 8 digits.
- missing_finance: You must provide a financial account number.
mail_limit_reached: You have have requested too much mail in the last month.
+ missing_finance: You must provide a financial account number.
pattern_mismatch:
dob: Your date of birth must be entered in as mm/dd/yyyy
- "personal-key": >
- Please enter your personal key for this account. Example: ABC1-DEF2-G3HI-J456
+ personal_key: 'Please enter your personal key for this account. Example: ABC1-DEF2-G3HI-J456'
ssn: 'Your Social Security Number must be entered in as ###-##-####'
zipcode: 'Your zipcode must be entered in as #####-####'
form:
@@ -62,9 +62,8 @@ en:
zipcode: ZIP Code
index:
continue_link: Yes, continue
- paragraph_1: >
- We have partnered with a trusted third party company to verify this
- information and your identity.
+ paragraph_1: We have partnered with a trusted third party company to verify
+ this information and your identity.
prompt: Do you have all of this information available right now?
section_1:
bullet_1: Full name
@@ -78,35 +77,30 @@ en:
bullet_2: Auto loan account number
bullet_3: Mortgage loan account number
bullet_4: Home equity line of credit account number
- footnote: >
- You will never be charged any money and are not sharing any account balances or other
- financial information with us. We only check the account number to help verify you.
+ footnote: You will never be charged any money and are not sharing any account
+ balances or other financial information with us. We only check the account
+ number to help verify you.
header: Financial information
subheader: 'We need one of the following financial account numbers:'
section_3:
bullet_1: Be in your name or the name of someone in your household
- bullet_2_html: >
- Not be a virtual phone, like Google Voice or Skype
- bullet_3_html: >
- Not be a pay-as-you-go phone
+ bullet_2_html: "Not be a virtual phone, like Google Voice
+ or Skype"
+ bullet_3_html: "Not be a pay-as-you-go phone"
header: Phone line in your name
- subheader: >
- We use telephone company records to verify your identity. The phone
- number you give us should:
+ subheader: 'We use telephone company records to verify your identity. The
+ phone number you give us should:'
subheader: To verify your identity, we’ll ask you to answer a few questions
messages:
- activated_html: >
- Your identity has been verified. If you need to change your verified
- information, please %{link}.
+ activated_html: Your identity has been verified. If you need to change your
+ verified information, please %{link}.
activated_link: contact us
- cancel: >
- To continue, %{app} needs to verify your identity. We need to collect
- some basic personal information as well as some financial information from you
- to complete this process. If you don’t have the information we need at this time
- you can continue later.
+ cancel: To continue, %{app} needs to verify your identity. We need to collect
+ some basic personal information as well as some financial information from
+ you to complete this process. If you don’t have the information we need at
+ this time you can continue later.
confirm: You have encrypted your verified data
- dupe_ssn1: >
- If you are getting this error, you may have already created and verified
+ dupe_ssn1: If you are getting this error, you may have already created and verified
an account, but with a different email address.
dupe_ssn2_html: Please %{link} with the email address you originally used.
dupe_ssn2_link: sign out now and sign back in
@@ -114,54 +108,51 @@ en:
disclaimer: By continuing, you agree to let %{app_name} compare the account
information you provide with third-party sources in order to verify your
identity.
- hint: "You can use Mastercard, Visa, Diner's Club, or JCB."
- intro_ccn_html: >
- Help us verify your identity by providing the last 8 digits of a
- credit card. Learn more about %{intro_help_link}.
+ hint: You can use Mastercard, Visa, Diner's Club, or JCB.
+ intro_account: Help us verify your identity by providing an auto loan, mortgage,
+ or home equity loan number.
intro_ccn_help: how your information will be used
- intro_account: >
- Help us verify your identity by providing an auto loan, mortgage, or home equity loan
- number.
+ intro_ccn_html: Help us verify your identity by providing the last 8 digits
+ of a credit card. Learn more about %{intro_help_link}.
no_account: Want to use a credit card instead?
- no_account_info: Help us verify your identity by providing the last 8 digits of your
- credit card.
+ no_account_info: Help us verify your identity by providing the last 8 digits
+ of your credit card.
hardfail: We can’t verify your identity right now.
hardfail4: You can also go to %{sp} for more help in accessing services.
help_center: Visit our Help Center to learn more about verifying your account.
+ loading: Verifying your identity
mail_sent: Your letter is on its way
+ personal_details_verified: Personal details verified!
+ personal_key: This is your new personal key. Write it down and keep it in a
+ safe place. You will need it if you ever lose your password.
phone:
alert: This phone line must be
in_your_name: in your name or a family member's name
- intro: We can only send a confirmation code to a telephone number linked to your
- legal identity.
+ intro: We can only send a confirmation code to a telephone number linked to
+ your legal identity.
phone_of_record: Phone of record
prepaid: on a contract, not prepaid
- same_as_2fa: >
- This phone number can be the same one you used to set up your one-time
- password as long as it meets the criteria above.
- personal_key: >
- This is your new personal key. Write it down and keep it in a safe
- place. You will need it if you ever lose your password.
+ same_as_2fa: This phone number can be the same one you used to set up your
+ one-time password as long as it meets the criteria above.
review:
financial_info: Where is my financial account information?
info_verified_html: We found records matching your %{phone_message}
intro: Your verified information
- select_verification_without_sp:
- In order to protect your account from identity fraud, your profile
- will not be activated until you enter a confirmation code.
- select_verification_with_sp: To protect you from identity fraud, you can't use your
- account at %{sp_name} until you activate it by entering a confirmation code.
+ select_verification_with_sp: To protect you from identity fraud, you can't use
+ your account at %{sp_name} until you activate it by entering a confirmation
+ code.
+ select_verification_without_sp: In order to protect your account from identity
+ fraud, your profile will not be activated until you enter a confirmation code.
sessions:
+ no_pii: Do not use real personal information (demo purposes only)
pii: personal information
success: We found records matching your %{pii_message}
- no_pii: Do not use real personal information (demo purposes only)
usps:
bad_address: I can't get mail at this address
- byline: We will mail a letter with a confirmation code to your verified
- address on file.
+ byline: We will mail a letter with a confirmation code to your verified address
+ on file.
resend: Send me another letter.
success: It should arrive in 5 to 10 business days.
- personal_details_verified: Personal details verified!
modal:
attempts:
one: You have 1 attempt remaining.
@@ -170,24 +161,26 @@ en:
fail: Okay
warning: Try again
financials:
+ fail: Your account is locked for 24 hours before you can
+ try to verify again. Check our Help section for more information about identity
+ verification.
heading: We could not find records matching your financial information.
- fail: >
- Your account is locked for 24 hours before you can try to verify again.
- Check our Help section for more information about identity verification.
+ timeout: Our request to verify your information timed out.
warning: Please check the information you entered.
phone:
+ fail: Your account is locked for 24 hours before you can
+ try to verify again. Check our Help section for more information about identity
+ verification.
heading: We could not find records matching your phone information.
- fail: >
- Your account is locked for 24 hours before you can try to verify again.
- Check our Help section for more information about identity verification.
+ timeout: Our request to verify your information timed out.
warning: Please check the information you entered.
sessions:
+ fail: Your account is locked for 24 hours before you can
+ try to verify again.
heading: We could not find records matching your personal information.
- fail: >
- Your account is locked for 24 hours before you can try to verify again.
- warning: >
- Please check the information you entered. Common mistakes are an incorrect Social
- Security Number, ZIP Code, or date of birth.
+ timeout: Our request to verify your information timed out.
+ warning: Please check the information you entered. Common mistakes are an
+ incorrect Social Security Number, ZIP Code, or date of birth.
review:
dob: Date of birth
full_name: Full name
@@ -204,12 +197,12 @@ en:
hardfail: We were unable to verify your identity
intro: Help us identify you
mail:
- verify: Want a letter?
resend: Want another letter?
+ verify: Want a letter?
phone: Phone number of record
review: Review and submit
select_verification: Activate your account
- sessions: First, tell us about yourself
session:
phone: Get a code by telephone
review: Encrypt your verified data by entering your password
+ sessions: First, tell us about yourself
diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml
index efb28f903ff..e4c80d91f3d 100644
--- a/config/locales/idv/es.yml
+++ b/config/locales/idv/es.yml
@@ -2,170 +2,214 @@
es:
idv:
buttons:
- activate_by_phone: NOT TRANSLATED YET
- activate_by_mail: NOT TRANSLATED YET
- continue: NOT TRANSLATED YET
- cancel: NOT TRANSLATED YET
- help: NOT TRANSLATED YET
+ activate_by_mail: Acitive por email
+ activate_by_phone: Active por teléfono
+ cancel: Cancele y regrese a su perfil
+ continue: Continúe la verificación de identidad
+ help: Continúe con el Centro de Ayuda
mail:
- send: NOT TRANSLATED YET
- resend: NOT TRANSLATED YET
+ resend: Enviar otra carta
+ send: Enviar una carta
cancel:
- modal_header: NOT TRANSLATED YET
- warning_header: NOT TRANSLATED YET
- warning_points:
- - NOT TRANSLATED YET
+ modal_header: "¿Está seguro que desea cancelar?"
+ warning_header: Si usted cancela ahora
+ warning_points:
+ - No podremos verificar su identidad
+ - No mantendremos un registro de su nombre, dirección, fecha de nacimiento o
+ número de Seguro Social
+ - No podrá acceder a su información con seguridad usando login.gov
+ - Usted todavía tiene una cuenta de login.gov con su email
+ - Puede administrar esa cuenta en su página de perfil
errors:
- bad_dob: NOT TRANSLATED YET
- duplicate_ssn: NOT TRANSLATED YET
- finance_number_length: NOT TRANSLATED YET
- hardfail: NOT TRANSLATED YET
- incorrect_password: NOT TRANSLATED YET
- invalid_ccn: NOT TRANSLATED YET
- missing_finance: NOT TRANSLATED YET
- mail_limit_reached: NOT TRANSLATED YET
+ bad_dob: Su fecha de nacimiento debe ser una fecha válida.
+ duplicate_ssn: Ya existe una cuenta con la información que proporcionó.
+ finance_number_length: El número debe estar entre los dígitos %{mínimo} y %{máximo}.
+ hardfail: No pudimos verificar su identidad en este momento.
+ incorrect_password: La contraseña que ingresó no es correcta.
+ invalid_ccn: El número de la tarjeta de crédito debe ser de sólo los últimos
+ 8 dígitos.
+ mail_limit_reached: Usted ha solicitado demasiado correo en el último mes.
+ missing_finance: Debe proporcionar un número de cuenta financiera.
pattern_mismatch:
- dob: NOT TRANSLATED YET
- "personal-key": NOT TRANSLATED YET
- ssn: NOT TRANSLATED YET
- zipcode: NOT TRANSLATED YET
+ dob: Su fecha de nacimiento debe ser ingresada en este formato mes/día/año.
+ personal_key: 'Introduzca su clave personal para esta cuenta. Ejemplo: ABC1-DEF2-G3HI-J456'
+ ssn: 'Su número de Seguro Social debe ser ingresado como ### - ## - ####'
+ zipcode: 'Su código postal debe ser ingresado como #####-####'
form:
- activate_by_mail: NOT TRANSLATED YET
- address1: NOT TRANSLATED YET
- address2: NOT TRANSLATED YET
- auto_loan: NOT TRANSLATED YET
- ccn: NOT TRANSLATED YET
- city: NOT TRANSLATED YET
- dob: NOT TRANSLATED YET
- dob_hint: NOT TRANSLATED YET
- first_name: NOT TRANSLATED YET
- home_equity_line: NOT TRANSLATED YET
- last_name: NOT TRANSLATED YET
- mortgage: NOT TRANSLATED YET
- no_alternate_phone_html: NOT TRANSLATED YET
- password: NOT TRANSLATED YET
- personal_details: NOT TRANSLATED YET
- phone: NOT TRANSLATED YET
- phone_label_aside: NOT TRANSLATED YET
- previous_address_add: NOT TRANSLATED YET
- previous_address_html: NOT TRANSLATED YET
- select_financial_account: NOT TRANSLATED YET
- ssn_label_html: NOT TRANSLATED YET
- state: NOT TRANSLATED YET
- use_ccn: NOT TRANSLATED YET
- use_financial_account: NOT TRANSLATED YET
- zipcode: NOT TRANSLATED YET
+ activate_by_mail: Activar mi cuenta por correo.
+ address1: Dirección de calle 1
+ address2: Dirección de calle 2 (opcional)
+ auto_loan: Número de cuenta de préstamo de auto
+ ccn: Últimos 8 dígitos de una tarjeta de crédito
+ city: Ciudad
+ dob: Fecha de nacimiento
+ dob_hint: 'Ejemplo: 01/17/1964'
+ first_name: Nombre
+ home_equity_line: Número de la cuenta del préstamo sobre el valor neto de la
+ vivienda
+ last_name: Apellido
+ mortgage: Número de la cuenta de préstamo hipotecario
+ no_alternate_phone_html: No tengo otro número de teléfono. %{enlazar}
+ password: Contraseña
+ personal_details: Detalles personales
+ phone: Teléfono
+ phone_label_aside: Celular o teléfono fijo
+ previous_address_add: Añadir la dirección anterior
+ previous_address_html: "¿Se mudó en los últimos 3 meses strong>?"
+ select_financial_account: Seleccione una cuenta financiera...
+ ssn_label_html: Número de Seguro Social %{tooltip}
+ state: Estado
+ use_ccn: Proporcione el número de tarjeta de crédito >
+ use_financial_account: "¿No tiene una tarjeta de crédito?"
+ zipcode: Código postal
index:
- continue_link: NOT TRANSLATED YET
- paragraph_1: NOT TRANSLATED YET
- prompt: NOT TRANSLATED YET
+ continue_link: Sí, continuar
+ paragraph_1: Nos hemos asociado con una empresa externa confiable para verificar
+ esta información y su identidad.
+ prompt: "¿Tiene toda esta información disponible ahora mismo?"
section_1:
- bullet_1: NOT TRANSLATED YET
- bullet_2: NOT TRANSLATED YET
- bullet_3: NOT TRANSLATED YET
- bullet_4: NOT TRANSLATED YET
- header: NOT TRANSLATED YET
- subheader: NOT TRANSLATED YET
+ bullet_1: Nombre completo
+ bullet_2: Dirección postal
+ bullet_3: Fecha de nacimiento
+ bullet_4: Número de Seguro Social
+ header: Información personal
+ subheader: 'Comenzaremos con algunos datos personales como:'
section_2:
- bullet_1: NOT TRANSLATED YET
- bullet_2: NOT TRANSLATED YET
- bullet_3: NOT TRANSLATED YET
- bullet_4: NOT TRANSLATED YET
- footnote: NOT TRANSLATED YET
- header: NOT TRANSLATED YET
- subheader: NOT TRANSLATED YET
+ bullet_1: Últimos 8 dígitos de una tarjeta de crédito
+ bullet_2: Número de cuenta de préstamo de auto
+ bullet_3: Número de cuenta de préstamo hipotecario
+ bullet_4: Número de la cuenta del préstamo sobre el valor neto de la vivienda
+ footnote: Nunca se le cobrará ningún dinero y no compartirá ningún saldo de
+ cuenta u otra información financiera con nosotros. Solo verificamos el número
+ de cuenta para verificar su identidad.
+ header: Información financiera
+ subheader: 'Necesitamos uno de los siguientes números de cuenta financiera:'
section_3:
- bullet_1: NOT TRANSLATED YET
- bullet_2_html: NOT TRANSLATED YET
- bullet_3_html: NOT TRANSLATED YET
- header: NOT TRANSLATED YET
- subheader: NOT TRANSLATED YET
- subheader: NOT TRANSLATED YET
+ bullet_1: Bajo su nombre o el nombre de alguien en su hogar
+ bullet_2_html: "No es strong> un teléfono virtual, como Google Voice
+ o Skype"
+ bullet_3_html: "No es strong> un teléfono de prepago"
+ header: Línea de teléfono a su nombre
+ subheader: 'Utilizamos registros de la compañía telefónica para verificar
+ su identidad. El número de teléfono que usted nos da debe:'
+ subheader: Para verificar su identidad, le pediremos que responda algunas preguntas
messages:
- activated_html: NOT TRANSLATED YET
- activated_link: contact us
- cancel: NOT TRANSLATED YET
- confirm: NOT TRANSLATED YET
- dupe_ssn1: NOT TRANSLATED YET
- dupe_ssn2_html: NOT TRANSLATED YET
- dupe_ssn2_link: NOT TRANSLATED YET
+ activated_html: Su identidad ha sido verificada. Si necesita cambiar la información
+ verificada, por favor, %{link}.
+ activated_link: Contáctenos
+ cancel: Para continuar %{app} necesita verificar su identidad. Necesitamos recopilar
+ información personal básica, así como su información financiera para completar
+ este proceso. Si no tiene la información que necesitamos en este momento,
+ puede continuar más tarde.
+ confirm: Usted ha encriptado sus datos verificados.
+ dupe_ssn1: Si recibe este error, es posible que ya haya creado y verificado
+ una cuenta, pero con un email diferente.
+ dupe_ssn2_html: Por favor %{link} con el email que utilizó originalmente.
+ dupe_ssn2_link: Cerrar ahora y volver a iniciar sesión
finance:
- disclaimer: NOT TRANSLATED YET
- hint: NOT TRANSLATED YET
- intro_ccn_html: NOT TRANSLATED YET
- intro_ccn_help: NOT TRANSLATED YET
- intro_account: NOT TRANSLATED YET
- no_account: NOT TRANSLATED YET
- no_account_info: NOT TRANSLATED YET
- hardfail: NOT TRANSLATED YET
- hardfail4: NOT TRANSLATED YET
- help_center: NOT TRANSLATED YET
- mail_sent: NOT TRANSLATED YET
+ disclaimer: Al continuar acepta que %{app_name} compare la información de
+ la cuenta que proporciona con fuentes de terceros para verificar su identidad.
+ hint: Puede utilizar Mastercard, Visa, Diner's Club o JCB.
+ intro_account: Ayúdenos a verificar su identidad proporcionando información
+ sobre un préstamo de auto, una hipoteca o un número de préstamo sobre el
+ valor neto de su casa.
+ intro_ccn_help: cómo se utilizará su información
+ intro_ccn_html: Ayúdenos a verificar su identidad proporcionando los últimos
+ 8 dígitos de una tarjeta de crédito. Obtenga más información sobre %{intro_help_link}.
+ no_account: "¿Desea usar una tarjeta de crédito?"
+ no_account_info: Ayúdenos a verificar su identidad proporcionando los últimos
+ 8 dígitos de su tarjeta de crédito.
+ hardfail: No podemos verificar su identidad en este momento.
+ hardfail4: También puede ir a %{sp} para obtener más ayuda y acceder a los servicios.
+ help_center: Visite nuestro Centro de Ayuda para obtener más información sobre
+ la verificación de su cuenta.
+ loading: NOT TRANSLATED YET
+ mail_sent: Su carta está en camino
+ personal_details_verified: "¡Detalles personales verificados!"
+ personal_key: Esta es su nueva clave personal. Escríbala y guárdela en un lugar
+ seguro. La necesitará si pierde su contraseña.
phone:
- alert: NOT TRANSLATED YET
- in_your_name: NOT TRANSLATED YET
- intro: NOT TRANSLATED YET
- phone_of_record: NOT TRANSLATED YET
- prepaid: NOT TRANSLATED YET
- same_as_2fa: NOT TRANSLATED YET
- personal_key: NOT TRANSLATED YET
+ alert: Esta línea telefónica debe
+ in_your_name: a su nombre o el nombre de un miembro de familia
+ intro: Sólo podemos enviar el código de confirmación al número de teléfono
+ vinculado a su identidad legal.
+ phone_of_record: Teléfono del registro
+ prepaid: en un contrato, no prepago
+ same_as_2fa: Este número de teléfono puede ser el mismo que utilizó para configurar
+ su contraseña de un uso único, siempre y cuando cumpla con los criterios
+ anteriores.
review:
- financial_info: NOT TRANSLATED YET
- info_verified_html: NOT TRANSLATED YET
- intro: NOT TRANSLATED YET
- select_verification_without_sp: NOT TRANSLATED YET
- select_verification_with_sp: NOT TRANSLATED YET
+ financial_info: "¿Dónde está la información de mi cuenta financiera?"
+ info_verified_html: Encontramos registros que coinciden con su %{teléfono_mensaje}
+ intro: Su información verificada
+ select_verification_with_sp: Para protegerlo de robo de identidad, no puede
+ utilizar su cuenta en %{sp_nombre} hasta que la active ingresando un código
+ de confirmación.
+ select_verification_without_sp: Para proteger su cuenta de robo de identidad,
+ su perfil no se activará hasta que ingrese un código de confirmación.
sessions:
- pii: NOT TRANSLATED YET
- success: NOT TRANSLATED YET
- no_pii: NOT TRANSLATED YET
+ no_pii: No utilice información personal real (sólo para propósitos de demostración)
+ pii: información personal
+ success: Encontramos registros que coinciden con su %{pii_mensaje}
usps:
- bad_address: NOT TRANSLATED YET
- byline: NOT TRANSLATED YET
- resend: NOT TRANSLATED YET
- success: NOT TRANSLATED YET
- personal_details_verified: NOT TRANSLATED YET
+ bad_address: No puedo recibir correo en esta dirección
+ byline: Le enviaremos una carta con un código de confirmación a su dirección
+ verificada en el archivo.
+ resend: Envíeme otra carta.
+ success: Debe llegar en 5 a 10 días laborales.
modal:
attempts:
- one: NOT TRANSLATED YET
- other: NOT TRANSLATED YET
+ one: Tiene usted 1 intento restante. strong>
+ other: Tiene usted %{count} intentos restantes.
button:
- fail: NOT TRANSLATED YET
- warning: NOT TRANSLATED YET
+ fail: Bueno
+ warning: Inténtelo de nuevo
financials:
- fail: NOT TRANSLATED YET
- heading: NOT TRANSLATED YET
- warning: NOT TRANSLATED YET
+ fail: Su cuenta está bloqueada por 24 horas strong> antes de intentar
+ verificarla de nuevo. Consulte nuestra sección de Ayuda para obtener más
+ información sobre la verificación de identidad.
+ heading: No hemos podido encontrar registros que coincidan con su información
+ financiera.
+ timeout: NOT TRANSLATED YET
+ warning: Compruebe la información que ingresó.
phone:
- fail: NOT TRANSLATED YET
- heading: NOT TRANSLATED YET
- warning: NOT TRANSLATED YET
+ fail: Su cuenta está bloqueada por 24 horas strong> antes de intentar
+ verificarla de nuevo. Consulte nuestra sección de Ayuda para obtener más
+ información sobre la verificación de identidad.
+ heading: No hemos podido encontrar registros que coincidan con la información
+ de su teléfono.
+ timeout: NOT TRANSLATED YET
+ warning: Compruebe la información que ingresó.
sessions:
- fail: NOT TRANSLATED YET
- heading: NOT TRANSLATED YET
- warning: NOT TRANSLATED YET
+ fail: Su cuenta está bloqueada por 24 horas strong> antes de que
+ pueda intentar verificarla de nuevo.
+ heading: No hemos podido encontrar registros que coincidan con su información
+ personal.
+ timeout: NOT TRANSLATED YET
+ warning: Compruebe la información que ingresó. Los errores comunes son números
+ incorrectos de Seguro Social, código postal o fecha de nacimiento.
review:
- dob: NOT TRANSLATED YET
- full_name: NOT TRANSLATED YET
- mailing_address: NOT TRANSLATED YET
- ssn: NOT TRANSLATED YET
+ dob: Fecha de nacimiento
+ full_name: Nombre completo
+ mailing_address: Dirección postal
+ ssn: Número de Seguro Social (SSN, sigla en inglés)
titles:
- activated: NOT TRANSLATED YET
- cancel: NOT TRANSLATED YET
- complete: NOT TRANSLATED YET
- dupe: NOT TRANSLATED YET
- expectations: NOT TRANSLATED YET
- fail: NOT TRANSLATED YET
- financials: NOT TRANSLATED YET
- hardfail: NOT TRANSLATED YET
- intro: NOT TRANSLATED YET
+ activated: Su identidad ha sido verificada.
+ cancel: No podemos verificar su identidad.
+ complete: Verificación de identidad completa
+ dupe: 'Error: Una cuenta puede que ya exista.'
+ expectations: A continuación, ayúdenos a identificarlo.
+ fail: No pudimos verificar su identidad.
+ financials: Proporcione un número de cuenta financiera.
+ hardfail: No pudimos verificar su identidad.
+ intro: Ayúdenos a identificarlo
mail:
- verify: NOT TRANSLATED YET
- resend: NOT TRANSLATED YET
- phone: NOT TRANSLATED YET
- review: NOT TRANSLATED YET
- select_verification: NOT TRANSLATED YET
- sessions: NOT TRANSLATED YET
+ resend: "¿Desea otra carta?"
+ verify: "¿Desea una carta?"
+ phone: Número de teléfono del registro
+ review: Revise y envíe
+ select_verification: Active su cuenta
session:
- phone: NOT TRANSLATED YET
- review: NOT TRANSLATED YET
+ phone: Obtener un código por teléfono
+ review: Encripte sus datos verificados ingresando su contraseña.
+ sessions: Primero, cuéntenos sobre usted
diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml
new file mode 100644
index 00000000000..4391f11b268
--- /dev/null
+++ b/config/locales/idv/fr.yml
@@ -0,0 +1,227 @@
+---
+fr:
+ idv:
+ buttons:
+ activate_by_mail: Activer par la poste
+ activate_by_phone: Activer par téléphone
+ cancel: Annuler et retourner à votre profil
+ continue: Continuer la vérification d'identité
+ help: Continuer jusqu'au Centre d'aide
+ mail:
+ resend: Envoyer une autre lettre
+ send: Envoyer une lettre
+ cancel:
+ modal_header: Souhaitez-vous vraiment annuler?
+ warning_header: Si vous annulez maintenant
+ warning_points:
+ - Nous ne serons pas en mesure de vérifier votre identité
+ - Nous ne conserverons pas de dossier contenant votre nom, adresse, date de
+ naissance ou numéro de sécurité sociale
+ - Vous ne pourrez pas accéder à votre information de manière sécuritaire en
+ utilisant login.gov
+ - Vous conserverez un compte login.gov pour votre adresse courriel
+ - Vous pouvez gérer ce compte sur la page de votre profil
+ errors:
+ bad_dob: Votre date de naissance doit être une date valide.
+ duplicate_ssn: Un compte existe déjà avec l'information que vous avez fournie.
+ finance_number_length: Le nombre doit comprendre entre %{minimum} et %{maximum}
+ chiffres.
+ hardfail: Nous sommes dans l'incapacité de vérifier votre identité pour le moment.
+ incorrect_password: Le mot de passe que vous avez inscrit est incorrect.
+ invalid_ccn: Vous devez inscrire seulement les 8 derniers chiffres du numéro
+ de carte de crédit.
+ mail_limit_reached: Vous avez demandé trop de lettres au cours du dernier mois.
+ missing_finance: Vous devez fournir un numéro de compte bancaire.
+ pattern_mismatch:
+ dob: 'Votre date de naissance doit être inscrite de cette façon: mm/jj/aaaa'
+ personal_key: 'Veuillez inscrire votre clé personnelle pour ce compte, par
+ exemple : ABC1-DEF2-G3HI-J456'
+ ssn: 'Votre numéro de sécurité sociale doit être inscrit de cette façon :
+ ###-##-####'
+ zipcode: 'Votre code ZIP doit être inscrit de cette façon : #####-####'
+ form:
+ activate_by_mail: Activer mon compte par la poste.
+ address1: Adresse 1
+ address2: Adresse 2 (optional)
+ auto_loan: Numéro du prêt automobile
+ ccn: Les 8 derniers chiffres d'une carte de crédit
+ city: Ville
+ dob: Date de naissance
+ dob_hint: 'exemple : 01/17/1964'
+ first_name: Prénom
+ home_equity_line: Numéro du compte de crédit hypothécaire
+ last_name: Nom de famille
+ mortgage: Numéro du compte de prêt hypothécaire
+ no_alternate_phone_html: Je n'ai pas de numéro de téléphone. %{link}
+ password: Mot de passe
+ personal_details: Information personnelle
+ phone: Numéro de téléphone
+ phone_label_aside: Ligne mobile ou fixe
+ previous_address_add: Inscrire votre adresse précédente
+ previous_address_html: Avez-vous déménagé au cours des strong3 derniers mois/strong?
+ select_financial_account: Sélectionnez un compte bancaire…
+ ssn_label_html: Numéro de sécurité sociale %{tooltip}
+ state: État
+ use_ccn: Fournissez un numéro de carte de crédit
+ use_financial_account: Vous n'avez pas de carte de crédit?
+ zipcode: Code ZIP
+ index:
+ continue_link: Oui, continuer
+ paragraph_1: Nous avons un partenariat avec une entreprise de confiance afin
+ de vérifier cette information ainsi que votre identité.
+ prompt: Est-ce que vous avez accès à toute cette information en ce moment?
+ section_1:
+ bullet_1: Nom complet
+ bullet_2: Adresse postale
+ bullet_3: Date de naissance
+ bullet_4: Numéro de sécurité sociale
+ header: Information personnelle
+ subheader: 'Nous allons commencer avec de l''information personnelle, comme:'
+ section_2:
+ bullet_1: Les 8 derniers chiffres d'une carte de crédit
+ bullet_2: Numéro du compte de prêt automobile
+ bullet_3: Numéro du compte de crédit hypothécaire
+ bullet_4: Numéro du compte de prêt hypothécaire
+ footnote: Vous n'aurez jamais à payer quoi que ce soit avec nous, ni à partager
+ le solde de vos comptes. Nous vérifions uniquement le numéro du compte pour
+ vérifier votre identité.
+ header: Information bancaire
+ subheader: 'Nous avons besoin de l''un des numéros de compte suivants:'
+ section_3:
+ bullet_1: Votre nom ou celui d'un membre de votre famille
+ bullet_2_html: strongNot/strong un téléphone virtuel, comme Google Voice ou
+ Skype
+ bullet_3_html: strongNot/strong un téléphone prépayé
+ header: Une ligne téléphonique à votre nom
+ subheader: 'Nous utilisons les informations des compagnies de téléphone pour
+ vérifier votre identité. Le numéro de téléphone que vous nous donnez devrait: '
+ subheader: Pour vérifier votre identité, nous vous demandons de répondre à quelques
+ questions
+ messages:
+ activated_html: Votre identité a été vérifiée. Si vous souhaitez modifier votre
+ information vérifiée, veuillez %{link}.
+ activated_link: communiquer avec nous
+ cancel: Pour continuer, %{app} doit vérifier votre identité. Nous devons recueillir
+ quelques informations personnelles de base ainsi que certaines informations
+ financières pour compléter ce processus. Si vous n'avez pas cette information
+ sous la main, vous pouvez continuer plus tard.
+ confirm: Vous avez crypté vos données vérifiées
+ dupe_ssn1: Si vous recevez ce message d'erreur, il est possible que vous ayez
+ déjà créé et vérifié un compte, mais avec une adresse courriel différente.
+ dupe_ssn2_html: Veuillez %{link} avec l'adresse courriel que vous avez utilisée
+ originalement.
+ dupe_ssn2_link: déconnectez-vous puis connectez-vous à nouveau
+ finance:
+ disclaimer: En continuant, vous acceptez que %{app_name} compare l'information
+ du compte que vous avez fournie avec des sources tiers afin de vérifier
+ votre identité.
+ hint: Vous pouvez utiliser Mastercard, Visa, Diner's Club, ou JCB.
+ intro_account: Aidez-nous à vérifier votre identité en fournissant le numéro
+ de votre prêt automobile, de votre prêt hypothécaire, ou de votre crédit
+ hypothécaire.
+ intro_ccn_help: comment votre information sera utilisée
+ intro_ccn_html: Aidez-nous à vérifier votre identité en fournissant les 8
+ derniers chiffres de votre carte de crédit. Suivez ce lien pour en apprendre
+ davantage %{intro_help_link}.
+ no_account: Vous préférez utiliser une carte de crédit?
+ no_account_info: Aidez-nous à vérifier votre identité en fournissant les 8
+ derniers chiffres de votre carte de crédit.
+ hardfail: Nous ne pouvons pas vérifier votre identité pour le moment.
+ hardfail4: Vous pouvez également aller sur %{sp} où vous trouverez davantage
+ d'aide pour accéder à nos services.
+ help_center: Visitez notre Centre d'aide pour en apprendre davantage sur la
+ façon dont nous vérifions votre compte.
+ loading: NOT TRANSLATED YET
+ mail_sent: Votre lettre est en route
+ personal_details_verified: Information personnelle vérifiée!
+ personal_key: Il s'agit de votre nouvelle clé personnelle. Notez-la et conservez-la
+ dans un endroit sécuritaire. Vous en aurez besoin si vous perdez votre mot
+ de passe.
+ phone:
+ alert: Cette ligne téléphonique doit être
+ in_your_name: en votre nom ou celui d'un membre de votre famille
+ intro: Le seul numéro de téléphone auquel nous pouvons envoyer un code de
+ confirmation est celui qui est lié à votre identité légale.
+ phone_of_record: numéro de téléphone enregistré
+ prepaid: avec un contrat, et non prépayé
+ same_as_2fa: Ce numéro de téléphone peut être le même que celui que vous utilisez
+ pour configurer votre mot de passe à usage unique, tant et aussi longtemps
+ qu'il respecte les critères mentionnés plus haut.
+ review:
+ financial_info: Où se trouve l'information sur mon compte bancaire?
+ info_verified_html: Nous avons trouvé des données qui correspondent à votre
+ %{phone_message}
+ intro: Vos informations vérifiées
+ select_verification_with_sp: Afin de vous protéger des fraudes d'identité, vous
+ ne pouvez pas utiliser votre compte au %{sp_name} tant que vous ne l'aurez
+ pas activé en entrant votre code de confirmation.
+ select_verification_without_sp: Afin de protéger votre compte des fraudes liées
+ à l'identité, votre profil ne sera pas activé tant que vous n'aurez pas entré
+ votre code de confirmation.
+ sessions:
+ no_pii: N'utilisez pas de véritables données personnelles (il s'agit d'une
+ démonstration seulement)
+ pii: Information personnelle
+ success: Nous avons trouvé des données qui correspondent à %{pii_message}
+ usps:
+ bad_address: Je ne peux pas recevoir de courrier à cette adresse
+ byline: Nous posterons une lettre à l'adresse vérifiée dans nos dossiers.
+ Celle-ci contient un code de confirmation.
+ resend: Envoyez-moi une autre lettre.
+ success: Elle devrait arriver dans 5 à 10 jours ouvrables.
+ modal:
+ attempts:
+ one: Il ne vous reste qu' strongune tentative./strong
+ other: Il ne vous reste que strong%{count} tentatives./strong
+ button:
+ fail: OK
+ warning: Essayez à nouveau
+ financials:
+ fail: Votre compte est strongverrouillé pour 24 heures/strong au cours desquelles
+ vous ne pourrez pas y accéder. Consultez notre Centre d'aide pour plus d'information
+ sur la vérification de votre identité.
+ heading: Nous ne trouvons pas de données qui correspondent à vos données financières.
+ timeout: NOT TRANSLATED YET
+ warning: Veuillez vérifier l'information que vous avez fournie.
+ phone:
+ fail: Votre compte est strongverrouillé pour 24 heures/strong au cours desquelles
+ vous ne pourrez pas y accéder. Consultez notre Centre d'aide pour plus d'information
+ sur la vérification de votre identité.
+ heading: Nous ne trouvons pas de données qui correspondent à vos informations
+ téléphoniques.
+ timeout: NOT TRANSLATED YET
+ warning: Veuillez vérifier l'information que vous avez fournie.
+ sessions:
+ fail: Votre compte est strongverrouillé pour 24 heures/strong au cours desquelles
+ vous ne pourrez pas y accéder.
+ heading: Nous ne trouvons pas de données qui correspondent à vos informations
+ téléphoniques.
+ timeout: NOT TRANSLATED YET
+ warning: Veuillez vérifier l'information que vous avez fournie. Un numéro
+ de sécurité sociale, un code ZIP ou une date de naissance mal écrits sont
+ des erreurs communes.
+ review:
+ dob: Date de naissance
+ full_name: Nom complet
+ mailing_address: Adresse postale
+ ssn: Numéro de sécurité sociale (SSN)
+ titles:
+ activated: Votre identité a déjà été vérifiée
+ cancel: Nous ne pouvons pas vérifier votre identité
+ complete: Vérification de votre identité complétée
+ dupe: 'Erreur: un compte existe déjà'
+ expectations: Ensuite, aidez-vous à vous identifier
+ fail: Nous sommes dans l'incapacité de vérifier votre identité pour le moment
+ financials: Veuillez fournir un numéro de compte bancaire
+ hardfail: Nous sommes dans l'incapacité de vérifier votre identité
+ intro: Aidez-nous à vous identifier
+ mail:
+ resend: Vous voulez une autre lettre?
+ verify: Vous voulez une lettre?
+ phone: Numéro de téléphone enregistré
+ review: Réviser et soumettre
+ select_verification: Activer votre compte
+ session:
+ phone: Obtenez un code par téléphone
+ review: Encryptez vos données personnelles en entrant votre mot de passe
+ sessions: D'abord, dites-nous qui vous êtes
diff --git a/config/locales/instructions/en.yml b/config/locales/instructions/en.yml
index 6c75fcdb271..72780617456 100644
--- a/config/locales/instructions/en.yml
+++ b/config/locales/instructions/en.yml
@@ -1,50 +1,47 @@
---
en:
instructions:
- 2fa:
+ account:
+ reactivate:
+ begin: Let's get started.
+ explanation: 'When you created your account, we gave you a list of words and
+ asked you to store them in a safe place. It looked similar to this:'
+ intro: We take extra steps to keep your personal information secure and private,
+ so resetting your password takes a little extra effort.
+ modal:
+ copy: If you don't have your personal key, you will need to verify your
+ identity again.
+ heading: Don't have your personal key?
+ with_key: Do you have your personal key?
+ forgot_password:
+ close_window: You can close this browser window once you have reset your password.
+ go_back_to_mobile_app: To continue, please go back to the %{friendly_name} app
+ and sign in.
+ mfa:
authenticator:
accordion_header: Scan code with your mobile device
- confirm_code_html: Enter the code from your authenticator app. If you have several accounts
- set up in your app, enter the code corresponding to %{email} at
- %{app}.%{tooltip}
+ confirm_code_html: Enter the code from your authenticator app. If you have
+ several accounts set up in your app, enter the code corresponding to %{email}
+ at %{app}.%{tooltip}
or: or
sms:
- confirm_code_html: We sent it in a text message to %{number}. Need another code?
- %{resend_code_link}. Message rates may apply.
+ confirm_code_html: We sent it in a text message to %{number}. Need another
+ code? %{resend_code_link}. Message rates may apply.
fallback_html: If you can't get text messages right now, you can %{link}
voice:
confirm_code_html: We just called you at %{number}. Want us to call you again?
%{resend_code_link}
fallback_html: If you can't take a phone call right now, you can %{link}
wrong_number_html: Entered the wrong phone number? %{link}
- account:
- reactivate:
- begin: Let's get started.
- explanation: >
- When you created your account, we gave you a list of words and asked
- you to store them in a safe place. It looked similar to this:
- intro: >
- We take extra steps to keep your personal information secure and private,
- so resetting your password takes a little extra effort.
- modal:
- copy: If you don't have your personal key, you will need to verify your identity again.
- heading: Don't have your personal key?
- with_key: Do you have your personal key?
- forgot_password:
- close_window: You can close this browser window once you have reset your password.
- go_back_to_mobile_app: To continue, please go back to the %{friendly_name} app and sign in.
password:
- forgot: >
- Don’t know your password? Reset it after confirming your email address.
+ forgot: Don’t know your password? Reset it after confirming your email address.
+ help_text: The longer and more unusual the password, the harder it is to guess.
+ So avoid using common phrases. Also avoid repeating passwords from other online
+ accounts such as banks, email and social media.
help_text_header: Password safety tips
- help_text: >
- The longer and more unusual the password, the harder it is to guess. So avoid using common
- phrases. Also avoid repeating passwords from other online accounts such as banks, email and
- social media.
info:
- lead: >
- It must be at least %{min_length} characters long and not be a commonly used password.
- That's it!
+ lead: It must be at least %{min_length} characters long and not be a commonly
+ used password. That's it!
strength:
i: Very weak
ii: Weak
@@ -52,9 +49,8 @@ en:
intro: 'Password strength: '
iv: Good
v: Great!
- personal_key_html: >
- This is the only way to regain access to your account if you lose your password or
- phone. %{accent}
personal_key_accent: Write it down or print it out.
+ personal_key_html: This is the only way to regain access to your account if you
+ lose your password or phone. %{accent}
registration:
email: Pick an address you want to use for government communications.
diff --git a/config/locales/instructions/es.yml b/config/locales/instructions/es.yml
index a54974c4138..831da38d27c 100644
--- a/config/locales/instructions/es.yml
+++ b/config/locales/instructions/es.yml
@@ -1,50 +1,60 @@
---
es:
instructions:
- 2fa:
- authenticator:
- accordion_header: NOT TRANSLATED YET
- confirm_code_html: Ingrese el código de su aplicación de autenticador. Si tiene varias cuentas
- configuradas en su aplicación, ingrese el código correspondiente a %{email} en
- %{app}.%{tooltip}
- or: NOT TRANSLATED YET
- sms:
- confirm_code_html: Lo enviamos a %{number}. ¿No recibió un código?
- fallback_html: NOT TRANSLATED YET
- voice:
- confirm_code_html: NOT TRANSLATED YET
- fallback_html: NOT TRANSLATED YET
- wrong_number_html: ¿Ha ingresado el número de teléfono equivocado? %{link}
account:
reactivate:
- begin: NOT TRANSLATED YET
- explanation: NOT TRANSLATED YET
- intro: NOT TRANSLATED YET
+ begin: Empecemos.
+ explanation: 'Cuando creó su cuenta le dimos una lista de palabras y le pedimos
+ que las guardara en un lugar seguro. Se parecía a esto:'
+ intro: Tomamos medidas adicionales para mantener su información personal segura
+ y privada, por lo que restablecer su contraseña requiere un pequeño esfuerzo
+ adicional.
modal:
- copy: NOT TRANSLATED YET
- heading: NOT TRANSLATED YET
- with_key: NOT TRANSLATED YET
+ copy: Si no tiene su clave personal, verifique su identidad nuevamente.
+ heading: Para continuar, por favor regrese a la %{friendly_name} app e inicie
+ una sesión.
+ with_key: "¿Tiene su clave personal?"
forgot_password:
- close_window: Puede cerrar esta ventana del navegador despues que haya restablecido su contraseña.
- go_back_to_mobile_app: NOT TRANSLATED YET
+ close_window: Puede cerrar esta ventana del navegador después que haya restablecido
+ su contraseña.
+ go_back_to_mobile_app: Para continuar, por favor regrese a la %{friendly_name}
+ app e inicie una sesión.
+ mfa:
+ authenticator:
+ accordion_header: Escanear código con su dispositivo móvil
+ confirm_code_html: Ingrese el código de su app de autenticación. Si tiene
+ varias cuentas configuradas en su app, ingrese el código correspondiente
+ a %{email} en %{app}.%{tooltip}
+ or: o
+ sms:
+ confirm_code_html: Le enviamos en un mensaje de texto a %{number}. ¿Necesita
+ otro código? %{resend_code_link}. Puede estar sujeto a cargos de mensajería
+ y datos.
+ fallback_html: Si no puede recibir mensajes de texto ahora, puede %{link}
+ voice:
+ confirm_code_html: Acabamos de llamarle al %{number}. ¿Desea que le llamemos
+ de nuevo? %{resend_code_link}
+ fallback_html: Si no puede recibir una llamada de teléfono ahora, puede %{link}
+ wrong_number_html: "¿Ingresó el número de teléfono equivocado? %{link}"
password:
- forgot: >
- Si no sabe su contraseña actual, puede restablecerla. Escriba la dirección de correo electrónico
- asociada a su cuenta para recibir más instrucciones en la pantalla.
- help_text_header: NOT TRANSLATED YET
- help_text: NOT TRANSLATED YET
+ forgot: "¿No sabe su contraseña? Restablézcala después de confirmar su email."
+ help_text: Cuanto más larga y más inusual sea la contraseña, más difícil es
+ de adivinarla. Así que evite usar frases comunes. También evite repetir contraseñas
+ de otras cuentas en línea, por ejemplo de sus bancos, email y medios sociales.
+ help_text_header: Consejos de seguridad de contraseña
info:
- lead: Su contraseña debe tener al menos %{min_length} caracteres.
+ lead: Su contraseña debe tener al menos %{min_length} caracteres y no ser
+ una contraseña común. ¡Eso es todo!
strength:
i: Muy débil
ii: Débil
iii: Más o menos
- intro: 'Seguridad de la contraseña: '
- iv: Bueno
- v: Muy bueno!
- personal_key_html: >
- Escriba este código de recuperación y guárdelo en algún lugar seguro. Si no puede
- acceder a su teléfono, puede usarlo en lugar de un código de acceso único para iniciar sesión.
- personal_key_accent: NOT TRANSLATED YET
+ intro: 'Seguridad de la contraseña:'
+ iv: Buena
+ v: "¡Muy buena!"
+ personal_key_accent: Anótelo o imprímalo.
+ personal_key_html: Esta es la única manera de recuperar el acceso a su cuenta
+ si pierde su contraseña o teléfono. %{accent}
registration:
- email: Elija una dirección para usar en las comunicaciones del gobierno.
+ email: Elija una dirección que desee usar para manejar las comunicaciones del
+ Gobierno.
diff --git a/config/locales/instructions/fr.yml b/config/locales/instructions/fr.yml
new file mode 100644
index 00000000000..cad7c6b45d4
--- /dev/null
+++ b/config/locales/instructions/fr.yml
@@ -0,0 +1,59 @@
+---
+fr:
+ instructions:
+ account:
+ reactivate:
+ begin: NOT TRANSLATED YET
+ explanation: NOT TRANSLATED YET
+ intro: NOT TRANSLATED YET
+ modal:
+ copy: NOT TRANSLATED YET
+ heading: NOT TRANSLATED YET
+ with_key: NOT TRANSLATED YET
+ forgot_password:
+ close_window: Vous pourrez fermer cette fenêtre de navigateur lorsque vous aurez
+ réinitialisé votre mot de passe.
+ go_back_to_mobile_app: NOT TRANSLATED YET
+ mfa:
+ authenticator:
+ accordion_header: Balayez le code avec votre appareil mobile
+ confirm_code_html: Entrez le code à partir de votre application d'authentification.
+ Si vous avez plusieurs comptes configurés dans votre application, entrez
+ le code correspondant à %{email} à %{app}.%{tooltip}
+ or: or
+ sms:
+ confirm_code_html: Nous l'avons envoyé par message texte à %{number}. Vous
+ avez besoin d'un autre code? %{resend_code_link}. Les frais liés aux messages
+ texte peuvent s'appliquer.
+ fallback_html: Si vous ne pouvez recevoir de messages texte pour le moment,
+ vous pouvez %{link}
+ voice:
+ confirm_code_html: Nous venons de vous appeler au %{number}. Vous voulez que
+ nous vous appelions de nouveau? %{resend_code_link}
+ fallback_html: Si vous ne pouvez recevoir d'appels pour le moment, vous pouvez
+ %{link}
+ wrong_number_html: Vous avez entré un mauvais numéro de téléphone? %{link}
+ password:
+ forgot: Vous ne connaissez pas votre mot de passe? Réinitialisez-le après avoir
+ confirmé votre adresse courriel.
+ help_text: Plus long et inhabituel est le mot de passe, plus difficile il sera
+ à trouver. Évitez donc d'utiliser des phrases communes. Évitez aussi de répéter
+ des mots de passe d'autres comptes en ligne comme les comptes bancaires, les
+ comptes courriel et les comptes de médias sociaux.
+ help_text_header: Conseils sur la sécurité du mot de passe
+ info:
+ lead: Il doit avoir une longueur minimale de %{min_length} caractères et ne
+ pas être un mot de passe couramment utilisé. C'est tout!
+ strength:
+ i: Très faible
+ ii: Faible
+ iii: Correct
+ intro: 'Force du mot de passe : '
+ iv: Bonne
+ v: Excellente!
+ personal_key_accent: Notez-la ou imprimez-la.
+ personal_key_html: Il s'agit de la seule façon de récupérer l'accès à votre compte
+ si vous perdez votre mot de passe ou votre téléphone. %{accent}
+ registration:
+ email: Choisissez une adresse que vous souhaitez utiliser pour les communications
+ avec le gouvernement.
diff --git a/config/locales/jobs/en.yml b/config/locales/jobs/en.yml
index 5405f8f767d..fc312f77076 100644
--- a/config/locales/jobs/en.yml
+++ b/config/locales/jobs/en.yml
@@ -1,10 +1,10 @@
---
en:
jobs:
+ sms_otp_sender_job:
+ message: "%{code} is your %{app} one-time security code."
voice_otp_sender_job:
- message_final: >
- Hello! Your login.gov one time security code is, %{code}, again, your
- security code is, %{code}, goodbye!
- message_repeat: >
- Hello! Your login.gov one time security code is, %{code}, again, your
- security code is, %{code}. Press 1 to repeat your code.
+ message_final: Hello! Your login.gov one time security code is, %{code}, again,
+ your security code is, %{code}, goodbye!
+ message_repeat: Hello! Your login.gov one time security code is, %{code}, again,
+ your security code is, %{code}. Press 1 to repeat your code.
diff --git a/config/locales/jobs/es.yml b/config/locales/jobs/es.yml
index 6904a69ef59..2f321118986 100644
--- a/config/locales/jobs/es.yml
+++ b/config/locales/jobs/es.yml
@@ -1,6 +1,11 @@
---
es:
jobs:
+ sms_otp_sender_job:
+ message: "%{code} es su %{app} código de seguridad de sólo un uso."
voice_otp_sender_job:
- message_final: NOT TRANSLATED YET
- message_repeat: NOT TRANSLATED YET
+ message_final: "¡Hola! Su código de seguridad de login.gov para uso único es,
+ %{code}, nuevamente, su código de seguridad es, % {code}, ¡adiós!"
+ message_repeat: "¡Hola! Su código de seguridad de login.gov para uso único es
+ %{code}, nuevamente, su código de seguridad es % {code}. Presione 1 para repetir
+ su código."
diff --git a/config/locales/jobs/fr.yml b/config/locales/jobs/fr.yml
new file mode 100644
index 00000000000..fe6a39446d9
--- /dev/null
+++ b/config/locales/jobs/fr.yml
@@ -0,0 +1,11 @@
+---
+fr:
+ jobs:
+ sms_otp_sender_job:
+ message: "%{code} est votre code de sécurité à utilisation unique pour %{app}"
+ voice_otp_sender_job:
+ message_final: Bonjour! Votre code de sécurité à utilisation unique de login.gov
+ est, %{code}, de nouveau, votre code de sécurité est, %{code}, au revoir!
+ message_repeat: Bonjour! Votre code de sécurité à utilisation unique de login.gov
+ est, %{code}, de nouveau, votre code de sécurité est, %{code}. Appuyez sur 1
+ pour répéter votre code.
diff --git a/config/locales/links/en.yml b/config/locales/links/en.yml
index e70bd4206fe..16845afc752 100644
--- a/config/locales/links/en.yml
+++ b/config/locales/links/en.yml
@@ -6,6 +6,9 @@ en:
with_key: I have my key
without_key: I don't have my key
back_to_sp: Back to %{sp}
+ cancel: Cancel
+ cancel_account_creation: "‹ Cancel account creation"
+ cancel_idv: "‹ Cancel account verification"
contact: Contact
copy: Copy
create_account: Create account
@@ -14,23 +17,21 @@ en:
passwords:
forgot: Forgot your password?
phone_confirmation:
- auth_app_fallback_html: ' or %{link}.'
+ auth_app_fallback_html: " or %{link}."
fallback_to_sms_html: Send me a text message with the code instead
- fallback_to_voice_html: If you can't get text message right now, you can get a security code via %{link}
+ fallback_to_voice_html: If you can't get text message right now, you can get
+ a security code via %{link}
privacy_policy: Privacy & security
remove: Remove
resend: Resend email
reverify: Please verify your identity again.
sign_in: Sign in
sign_out: Sign out
- cancel: Cancel
- cancel_account_creation: ‹ Cancel account creation
- cancel_idv: ‹ Cancel account verification
two_factor_authentication:
app: get a security code via authentication app
- voice: get a security code via phone call
- sms: get a security code via text message
resend_code:
sms: Get another text message
voice: Get another phone call
+ sms: get a security code via text message
+ voice: get a security code via phone call
what_is_totp: What is an authentication app?
diff --git a/config/locales/links/es.yml b/config/locales/links/es.yml
index da1070539d6..1fcc5b5bf8f 100644
--- a/config/locales/links/es.yml
+++ b/config/locales/links/es.yml
@@ -3,34 +3,35 @@ es:
links:
account:
reactivate:
- with_key: NOT TRANSLATED YET
- without_key: NOT TRANSLATED YET
- back_to_sp: "Volver a %{sp}"
+ with_key: Tengo mi clave
+ without_key: No tengo mi clave
+ back_to_sp: Volver a %{sp}
+ cancel: Cancelar
+ cancel_account_creation: "< Cancelar la creación de cuenta"
+ cancel_idv: "< Cancelar la verificación de cuenta"
contact: Contactar
- copy: NOT TRANSLATED YET
+ copy: Copiar
create_account: Crear cuenta
help: Ayuda
next: Siguiente
passwords:
- forgot: ¿Olvidó su contraseña?
+ forgot: "¿Olvidó su contraseña?"
phone_confirmation:
- auth_app_fallback_html: NOT TRANSLATED YET
- fallback_to_sms_html: Envíame un mensaje de texto con el código en su lugar
- fallback_to_voice_html: Llámame con el código en su lugar
+ auth_app_fallback_html: o %{link}.
+ fallback_to_sms_html: Envíeme un mensaje de texto con el código en su lugar
+ fallback_to_voice_html: Si no puede recibir un mensaje de texto ahora mismo,
+ puede obtener un código de seguridad a través de %{link}
privacy_policy: Privacidad y seguridad
- remove: NOT TRANSLATED YET
+ remove: Retirar
resend: Enviar de nuevo
- reverify: NOT TRANSLATED YET
+ reverify: Verifique su identidad nuevamente.
sign_in: Iniciar sesión
sign_out: Cerrar sesión
- cancel: Cancelar
- cancel_account_creation: NOT TRANSLATED YET
- cancel_idv: NOT TRANSLATED YET
two_factor_authentication:
- app: NOT TRANSLATED YET
- voice: NOT TRANSLATED YET
- sms: NOT TRANSLATED YET
+ app: Obtener un código de seguridad mediante la app de autenticación
resend_code:
- sms: NOT TRANSLATED YET
- voice: NOT TRANSLATED YET
- what_is_totp: NOT TRANSLATED YET
+ sms: Obtener otro mensaje de texto
+ voice: Obtener otra llamada telefónica
+ sms: Obtener un código de seguridad a través de un mensaje de texto
+ voice: Obtener un código de seguridad a través de una llamada telefónica
+ what_is_totp: "¿Qué es una app de autenticación?"
diff --git a/config/locales/links/fr.yml b/config/locales/links/fr.yml
new file mode 100644
index 00000000000..16dc81e3b81
--- /dev/null
+++ b/config/locales/links/fr.yml
@@ -0,0 +1,37 @@
+---
+fr:
+ links:
+ account:
+ reactivate:
+ with_key: NOT TRANSLATED YET
+ without_key: NOT TRANSLATED YET
+ back_to_sp: Retour à %{sp}
+ cancel: Annuler
+ cancel_account_creation: "‹ Annuler la création du compte"
+ cancel_idv: "‹ Annuler la vérification du compte"
+ contact: Contact
+ copy: Copier
+ create_account: Créer un compte
+ help: Aide
+ next: Suivant
+ passwords:
+ forgot: Vous avez oublié votre mot de passe?
+ phone_confirmation:
+ auth_app_fallback_html: " ou %{link}."
+ fallback_to_sms_html: Envoyez-moi plutôt un SMS contenant le code
+ fallback_to_voice_html: Si vous ne pouvez recevoir de message texte pour le
+ moment, vous pouvez obtenir un code de sécurité par %{link}
+ privacy_policy: Confidentialité et sécurité
+ remove: Retirer
+ resend: Envoyer le courriel de nouveau
+ reverify: NOT TRANSLATED YET
+ sign_in: Connexion
+ sign_out: Déconnexion
+ two_factor_authentication:
+ app: obtenir un code de sécurité par l'application d'authentification
+ resend_code:
+ sms: Recevoir un autre SMS
+ voice: Recevoir un autre appel téléphonique
+ sms: obtenir un code de sécurité par SMS
+ voice: obtenir un code par un appel téléphonique
+ what_is_totp: Qu'est-ce qu'une application d'authentification?
diff --git a/config/locales/mailer/en.yml b/config/locales/mailer/en.yml
index 2091baa76ca..7bcb630ff1e 100644
--- a/config/locales/mailer/en.yml
+++ b/config/locales/mailer/en.yml
@@ -5,25 +5,23 @@ en:
confirmation_instructions:
first_sentence:
confirmed: Trying to change your email address?
- reset_requested: >
- Your %{app} account has been reset by a tech support representative.
+ reset_requested: Your %{app} account has been reset by a tech support representative.
To continue, you must confirm your email address.
- unconfirmed: Thanks for creating an account.
+ unconfirmed: Thanks for creating an account.
footer: This link will expire in %{confirmation_period}.
- header: >
- %{intro} Please click the link below or copy and paste the entire link into your browser.
+ header: "%{intro} Please click the link below or copy and paste the entire link
+ into your browser."
link_text: Confirm your email address
email_change_notice:
subject: Change your email address
email_reuse_notice:
- subject: This email address is already associated with an account.
+ subject: This email address is already associated with an account.
help: For more help, visit %{link}.
no_reply: Please do not reply to this message.
privacy_policy: Privacy policy
reset_password:
footer: This link expires in %{expires} hours.
- header: >
- To finish resetting your password, please click the link below
- or copy and paste the entire link into your browser.
+ header: To finish resetting your password, please click the link below or copy
+ and paste the entire link into your browser.
link_text: Reset your password
sent_from: Sent from %{app}
diff --git a/config/locales/mailer/es.yml b/config/locales/mailer/es.yml
index 77988d2184b..2a946221188 100644
--- a/config/locales/mailer/es.yml
+++ b/config/locales/mailer/es.yml
@@ -1,37 +1,27 @@
---
es:
mailer:
- about: Sobre %{app}
+ about: Acerca de %{app}
confirmation_instructions:
first_sentence:
- confirmed: >
- Para finalizar la actualización de tu cuenta de %{app}, debe confirmar dirección de
- correo electrónico dentro de %{confirmation_period} de recibir este mensaje.
- reset_requested: >
- Su cuenta de %{app} ha sido restablecida por un representante de soporte técnico.
- Para continuar, debe confirmar su dirección de correo electrónico.
- unconfirmed: >
- Para continuar creando su cuenta de %{app}, debe confirmar su dirección de
- correo electrónico dentro de %{confirmation_period} de recibir este mensaje.
- footer: Tome en cuenta que este enlace expira en %{período de confirmación}.
- header: >
- %{intro} Para confirmar su dirección de correo electrónico, haga clic en el enlace abajo
- o puede copiar y pegar el enlace completo en su navegador.
- link_text: Confirmar esta dirección de correo electrónico
+ confirmed: "¿Desea cambiar su email?"
+ reset_requested: Su cuenta de %{app} ha sido restablecida por un representante
+ de soporte técnico. Para continuar, debe confirmar su email.
+ unconfirmed: Gracias por crear una cuenta.
+ footer: Este enlace expira en %{confirmation_period}.
+ header: "%{intro} Haga clic en el enlace de abajo o copie y pegue el enlace
+ completo en su navegador."
+ link_text: Confirme su email
email_change_notice:
- subject: Cambiar su direccion de correo electronico
+ subject: Cambie su email
email_reuse_notice:
- subject: Confirmar su dirección de correo electrónico
- help: >
- Para obtener más ayuda, póngase en contacto con el centro de contacto de clientes de %{app} a través
- del formulario web en %{link}.
+ subject: Este email ya está asociado a una cuenta.
+ help: Para obtener más ayuda, visite %{link}.
no_reply: Por favor, no responda a este mensaje.
privacy_policy: Política de privacidad
reset_password:
- footer: >
- Tome en cuenta que este enlace caduca en %{expires} horas.
- header: >
- Ha solicitado que restablezca la contraseña de su cuenta. Para confirmar
- su solicitud, haga clic en el enlace abajo o puede copiar y pegar el enlace completo en su navegador.
- link_text: Restablecer su contraseña
+ footer: Este enlace expira en %{expires} horas.
+ header: Para terminar de restablecer su contraseña, haga clic en el enlace de
+ abajo o copie y pegue el enlace completo en su navegador.
+ link_text: Restablezca su contraseña
sent_from: Enviado desde %{app}
diff --git a/config/locales/mailer/fr.yml b/config/locales/mailer/fr.yml
new file mode 100644
index 00000000000..6dbfbf8d340
--- /dev/null
+++ b/config/locales/mailer/fr.yml
@@ -0,0 +1,28 @@
+---
+fr:
+ mailer:
+ about: À propos de %{app}
+ confirmation_instructions:
+ first_sentence:
+ confirmed: Vous tentez de changer votre adresse courriel?
+ reset_requested: Votre compte %{app} a été réinitialisé par un représentant
+ du soutien technique. Pour continuer, vous devez confirmer votre adresse
+ courriel.
+ unconfirmed: Merci d'avoir créé un compte.
+ footer: Ce lien expirera dans %{confirmation_period}.
+ header: "%{intro} Veuillez cliquer sur le lien ci-dessous ou copier et coller
+ le lien complet dans votre navigateur."
+ link_text: Confirmez votre adresse courriel
+ email_change_notice:
+ subject: Changez votre adresse courriel
+ email_reuse_notice:
+ subject: Cette adresse courriel est déjà associée à un compte.
+ help: Pour plus d'aide, visitez %{link}.
+ no_reply: Veuillez ne pas répondre à ce message.
+ privacy_policy: Politique de confidentialité
+ reset_password:
+ footer: Ce lien expire dans %{expires} heures.
+ header: Pour terminer la réinitialisation de votre mot de passe, veuillez cliquer
+ sur le lien ci-dessous ou copier et coller le lien complet dans votre navigateur.
+ link_text: Réinitialisez votre mot de passe
+ sent_from: Envoyé à partir de %{app}
diff --git a/config/locales/notices/en.yml b/config/locales/notices/en.yml
index 98188b14bbd..98ebefedb49 100644
--- a/config/locales/notices/en.yml
+++ b/config/locales/notices/en.yml
@@ -1,53 +1,47 @@
---
en:
notices:
- account_recovery: Great! You have your personal key.
- dap_html: >
-
-
+ account_reactivation: Great! You have your personal key.
+ dap_html: " "
forgot_password:
- use_diff_email:
- link: create a new account
- text_html: Or, %{link} using a different email address.
- first_paragraph_end: >
- with a link to reset your password. Follow the link to continue
- resetting your password.
+ first_paragraph_end: with a link to reset your password. Follow the link to
+ continue resetting your password.
first_paragraph_start: We sent an email to
no_email_sent_explanation_start: Didn’t receive an email?
resend_email_success: We sent another password reset email.
+ use_diff_email:
+ link: create a new account
+ text_html: Or, %{link} using a different email address.
password_changed: You changed your password.
resend_confirmation_email:
success: We sent another confirmation email.
send_code:
personal_key: You have a new personal key.
- timeout_warning:
- signed_in:
- continue: Keep me signed in
- sign_out: Sign me out
- message_html: >
- For your security, we will sign you out in %{time_left_in_session} unless
- you tell us otherwise.
- partially_signed_in:
- continue: Continue sign in
- sign_out: Cancel sign in
- message_html: >
- For your security, in %{time_left_in_session} we will
- cancel your sign in.
- session_cleared: >
- For your security, we clear what you entered if you don't move to a new page within %{minutes} minutes.
+ session_cleared: For your security, we clear what you entered if you don't move
+ to a new page within %{minutes} minutes.
signed_up_but_unconfirmed:
- first_paragraph_end: >
- with a link to confirm your email address. Follow the link to continue
- creating your account.
+ first_paragraph_end: with a link to confirm your email address. Follow the link
+ to continue creating your account.
first_paragraph_start: We sent an email to
no_email_sent_explanation_start: Didn’t receive it?
terms_of_service:
link: Security Practices and Privacy Act Statement
+ timeout_warning:
+ partially_signed_in:
+ continue: Continue sign in
+ message_html: For your security, in %{time_left_in_session} we will cancel
+ your sign in.
+ sign_out: Cancel sign in
+ signed_in:
+ continue: Keep me signed in
+ message_html: For your security, we will sign you out in %{time_left_in_session}
+ unless you tell us otherwise.
+ sign_out: Sign me out
totp_configured: You enabled an authentication app.
totp_disabled: You disabled your authentication app.
use_diff_email:
link: use a different email address
text_html: Or, %{link}
- session_timedout: >
- We signed you out. For your security, %{app} ends your session
+ session_timedout: We signed you out. For your security, %{app} ends your session
when you haven’t moved to a new page for %{minutes} minutes.
diff --git a/config/locales/notices/es.yml b/config/locales/notices/es.yml
index 46c9667b8f6..25242e0981f 100644
--- a/config/locales/notices/es.yml
+++ b/config/locales/notices/es.yml
@@ -1,41 +1,47 @@
---
es:
notices:
- account_recovery: NOT TRANSLATED YET
- dap_html: NOT TRANSLATED YET
+ account_reactivation: "¡Estupendo! Tiene su clave personal."
+ dap_html: " "
forgot_password:
+ first_paragraph_end: con un enlace para restablecer su contraseña. Siga el enlace
+ para continuar restableciendo su contraseña.
+ first_paragraph_start: Enviamos un email a
+ no_email_sent_explanation_start: "¿No recibió un email?"
+ resend_email_success: Enviamos otro email para restablecer la contraseña.
use_diff_email:
- link: NOT TRANSLATED YET
- text_html: NOT TRANSLATED YET
- first_paragraph_end: >
- NOT TRANSLATED YET
- first_paragraph_start: NOT TRANSLATED YET
- no_email_sent_explanation_start: NOT TRANSLATED YET
- resend_email_success: NOT TRANSLATED YET
- password_changed: NOT TRANSLATED YET
+ link: crear una cuenta nueva
+ text_html: O, %{link} utilizando un email diferente.
+ password_changed: Ha cambiado su contraseña.
resend_confirmation_email:
- success: NOT TRANSLATED YET
+ success: Enviamos otro email de confirmación.
send_code:
- personal_key: NOT TRANSLATED YET
- timeout_warning:
- signed_in:
- continue: NOT TRANSLATED YET
- sign_out: NOT TRANSLATED YET
- message_html: NOT TRANSLATED YET
- partially_signed_in:
- continue: NOT TRANSLATED YET
- sign_out: NOT TRANSLATED YET
- message_html: NOT TRANSLATED YET
- session_cleared: NOT TRANSLATED YET
+ personal_key: Tiene una nueva clave personal.
+ session_cleared: Para su seguridad, borramos lo que ingresó si no pasa a una página
+ nueva dentro de %{minutes} minutos.
signed_up_but_unconfirmed:
- first_paragraph_end: NOT TRANSLATED YET
- first_paragraph_start: NOT TRANSLATED YET
- no_email_sent_explanation_start: NOT TRANSLATED YET
+ first_paragraph_end: con un enlace para confirmar su email. Siga el enlace para
+ continuar creando su cuenta.
+ first_paragraph_start: Enviamos un email a
+ no_email_sent_explanation_start: "¿No lo recibió?"
terms_of_service:
- link: NOT TRANSLATED YET
- totp_configured: NOT TRANSLATED YET
- totp_disabled: NOT TRANSLATED YET
+ link: Prácticas de Seguridad y Declaración de Privacidad
+ timeout_warning:
+ partially_signed_in:
+ continue: Continuar el inicio de sesión
+ message_html: Para su seguridad, en %{time_left_in_session} cancelaremos su
+ acceso.
+ sign_out: Cancelar el inicio de sesión
+ signed_in:
+ continue: Manténgame conectado
+ message_html: Para su seguridad, terminaremos su sesión en% {time_left_in_session}
+ a menos que nos indique lo contrario.
+ sign_out: Desconécteme
+ totp_configured: Ha permitido una app de autenticación.
+ totp_disabled: Ha suspendido su app de autenticación.
use_diff_email:
- link: NOT TRANSLATED YET
- text_html: NOT TRANSLATED YET
- session_timedout: NOT TRANSLATED YET
+ link: use un email diferente
+ text_html: O %{link}
+ session_timedout: Hemos terminado su sesión. Para su seguridad, %{app} cierra su
+ sesión cuando usted no pasa a una nueva página durante %{minutes} minutos.
diff --git a/config/locales/notices/fr.yml b/config/locales/notices/fr.yml
new file mode 100644
index 00000000000..3db841b8198
--- /dev/null
+++ b/config/locales/notices/fr.yml
@@ -0,0 +1,50 @@
+---
+fr:
+ notices:
+ account_reactivation: NOT TRANSLATED YET
+ dap_html: " "
+ forgot_password:
+ first_paragraph_end: avec un lien pour réinitialiser votre mot de passe. Suivez
+ le lien pour continuer à réinitialiser votre mot de passe.
+ first_paragraph_start: Nous avons envoyé un courriel à
+ no_email_sent_explanation_start: Vous n'avez pas reçu de courriel?
+ resend_email_success: Nous avons envoyé un autre courriel de réinitialisation
+ de mot de passe.
+ use_diff_email:
+ link: Créer un nouveau compte
+ text_html: Ou, %{link} en utilisant une adresse courriel différente.
+ password_changed: Vous avez changé votre mot de passe.
+ resend_confirmation_email:
+ success: Nous avons envoyé un autre courriel de confirmation.
+ send_code:
+ personal_key: Vous avez une nouvelle clé personnelle.
+ session_cleared: Pour votre sécurité, nous effacerons l'information que vous avez
+ entrée si vous ne vous déplacez pas vers une nouvelle page dans les %{minutes}
+ prochaines minutes.
+ signed_up_but_unconfirmed:
+ first_paragraph_end: avec un lien pour confirmer votre adresse courriel. Suivez
+ le lien pour continuer à créer votre compte.
+ first_paragraph_start: Nous avons envoyé un courriel à
+ no_email_sent_explanation_start: Vous n'avez pas reçu de courriel?
+ terms_of_service:
+ link: Pratiques en matière de sécurité et énoncé concernant la Loi sur la protection
+ des renseignements personnels
+ timeout_warning:
+ partially_signed_in:
+ continue: Continuer la connexion
+ message_html: Pour votre sécurité, nous annulerons votre connexion dans %{time_left_in_session}.
+ sign_out: Annuler la connexion
+ signed_in:
+ continue: Gardez ma connexion active
+ message_html: Pour votre sécurité, nous vous déconnecterons dans %{time_left_in_session},
+ sauf en cas d'avis contraire de votre part.
+ sign_out: Déconnectez-moi
+ totp_configured: Vous avez activé l'application d'authentification.
+ totp_disabled: Vous avez désactivé l'application d'authentification.
+ use_diff_email:
+ link: utilisez une adresse courriel différente
+ text_html: Or, %{link}
+ session_timedout: Nous vous avons déconnecté. Pour votre sécurité, %{app} désactive
+ votre session lorsque vous demeurez sur une page sans vous déplacer pendant %{minutes}
+ minutes.
diff --git a/config/locales/openid_connect/en.yml b/config/locales/openid_connect/en.yml
index 760dd62e9dc..8b81ddee839 100644
--- a/config/locales/openid_connect/en.yml
+++ b/config/locales/openid_connect/en.yml
@@ -1,3 +1,4 @@
+---
en:
openid_connect:
authorization:
@@ -7,17 +8,19 @@ en:
no_valid_scope: No valid scope values found
redirect_uri_invalid: redirect_uri is invalid
redirect_uri_no_match: redirect_uri does not match registered redirect_uri
+ logout:
+ errors:
+ id_token_hint: id_token_hint was not recognized
token:
errors:
invalid_aud: Invalid audience claim, expected %{url}
- invalid_authentication: Client must authenticate via PKCE or private_key_jwt, missing either code_challenge or client_assertion
+ invalid_authentication: Client must authenticate via PKCE or private_key_jwt,
+ missing either code_challenge or client_assertion
invalid_code: invalid code
invalid_code_verifier: code_verifier did not match code_challenge
user_info:
errors:
- no_authorization: No Authorization header provided
malformed_authorization: Malformed Authorization header
- not_found: Could not find authorization for the contents of the provided access_token or it may have expired
- logout:
- errors:
- id_token_hint: id_token_hint was not recognized
+ no_authorization: No Authorization header provided
+ not_found: Could not find authorization for the contents of the provided access_token
+ or it may have expired
diff --git a/config/locales/openid_connect/es.yml b/config/locales/openid_connect/es.yml
index b49da324a96..2ae118aa458 100644
--- a/config/locales/openid_connect/es.yml
+++ b/config/locales/openid_connect/es.yml
@@ -1,23 +1,26 @@
+---
es:
openid_connect:
authorization:
errors:
- bad_client_id: NOT TRANSLATED YET
- no_valid_acr_values: NOT TRANSLATED YET
- no_valid_scope: NOT TRANSLATED YET
- redirect_uri_invalid: NOT TRANSLATED YET
- redirect_uri_no_match: NOT TRANSLATED YET
- token:
- errors:
- invalid_aud: NOT TRANSLATED YET
- invalid_authentication: NOT TRANSLATED YET
- invalid_code: NOT TRANSLATED YET
- invalid_code_verifier: NOT TRANSLATED YET
+ bad_client_id: Bad client_id
+ no_valid_acr_values: acr_valores encontrados no aceptables
+ no_valid_scope: No se han encontrado valores de magnitud válidos
+ redirect_uri_invalid: Redirect_uri no es válido
+ redirect_uri_no_match: Redirect_uri no coincide con redirect_uri registrado
logout:
errors:
- id_token_hint: NOT TRANSLATED YET
+ id_token_hint: Id_token_hint no fue reconocido
+ token:
+ errors:
+ invalid_aud: Solicitud de audiencia no válida, esperada % {url}
+ invalid_authentication: El cliente debe autenticarse a través de PKCE o private_key_jwt,
+ faltando code_challenge o client_assertion
+ invalid_code: código inválido
+ invalid_code_verifier: code_verifier no coincide con code_challenge
user_info:
errors:
- no_authorization: NOT TRANSLATED YET
- malformed_authorization: NOT TRANSLATED YET
- not_found: NOT TRANSLATED YET
+ malformed_authorization: Título de autorización mal formado
+ no_authorization: No se ha proporcionado título de autorización
+ not_found: No se pudo encontrar la autorización para el contenido del access_token
+ proporcionado o puede haber caducado
diff --git a/config/locales/openid_connect/fr.yml b/config/locales/openid_connect/fr.yml
new file mode 100644
index 00000000000..8c07af7b1ad
--- /dev/null
+++ b/config/locales/openid_connect/fr.yml
@@ -0,0 +1,26 @@
+---
+fr:
+ openid_connect:
+ authorization:
+ errors:
+ bad_client_id: Mauvaise client_id
+ no_valid_acr_values: Valeurs acr_values inacceptables trouvées
+ no_valid_scope: Aucune étendue de données valide trouvée
+ redirect_uri_invalid: redirect_uri est non valide
+ redirect_uri_no_match: redirect_uri ne correspond pas au redirect_uri enregistré
+ logout:
+ errors:
+ id_token_hint: id_token_hint n'a pas été reconnu
+ token:
+ errors:
+ invalid_aud: Affirmation liée à l'auditoire non valide, attendu %{url}
+ invalid_authentication: Le client doit s'authentifier par PKCE ou private_key_jwt,
+ code_challenge ou client_assertion manquant
+ invalid_code: code non valide
+ invalid_code_verifier: code_verifier ne correspondait pas à code_challenge
+ user_info:
+ errors:
+ malformed_authorization: Forme de l'en-tête d'autorisation non valide
+ no_authorization: Aucune en-tête d'autorisation fournie
+ not_found: L'autorisation pour le contenu du access_token fourni introuvable
+ ou il peut être expiré
diff --git a/config/locales/pages/es.yml b/config/locales/pages/es.yml
index 19aa9760f4b..8f6b9067af5 100644
--- a/config/locales/pages/es.yml
+++ b/config/locales/pages/es.yml
@@ -2,5 +2,5 @@
es:
pages:
page_not_found:
- body: NOT TRANSLATED YET
- header: NOT TRANSLATED YET
+ body: Es posible que desee comprobar su enlace e intentar nuevamente. (404)
+ header: La página que buscaba no existe.
diff --git a/config/locales/pages/fr.yml b/config/locales/pages/fr.yml
new file mode 100644
index 00000000000..15529918a62
--- /dev/null
+++ b/config/locales/pages/fr.yml
@@ -0,0 +1,6 @@
+---
+fr:
+ pages:
+ page_not_found:
+ body: Vous pouvez revérifier votre lien et essayer de nouveau. (404)
+ header: La page que vous recherchez n'existe pas.
diff --git a/config/locales/saml_idp/en.yml b/config/locales/saml_idp/en.yml
index 571661bdb8c..48733288731 100644
--- a/config/locales/saml_idp/en.yml
+++ b/config/locales/saml_idp/en.yml
@@ -1,10 +1,9 @@
+---
en:
saml_idp:
shared:
saml_post_binding:
heading: Submit to continue
- no_js: >
- JavaScript seems to be turned off in your browser. Normally this
- step happens automatically, but because you have JavaScript
- turned off, please click the submit button to continue signing
- in or signing out.
+ no_js: JavaScript seems to be turned off in your browser. Normally this step
+ happens automatically, but because you have JavaScript turned off, please
+ click the submit button to continue signing in or signing out.
diff --git a/config/locales/saml_idp/es.yml b/config/locales/saml_idp/es.yml
index ba48c1c2811..d8bb2e1156c 100644
--- a/config/locales/saml_idp/es.yml
+++ b/config/locales/saml_idp/es.yml
@@ -1,10 +1,9 @@
+---
es:
saml_idp:
shared:
saml_post_binding:
heading: Enviar para continuar
- no_js: >
- JavaScript parece estar desactivado en su navegador. Normalmente esto
- paso se produce automáticamente, pero porque usted tiene JavaScript
- desactivado, haga clic en el botón enviar para continuar iniciando o
- saliendo la sesión.
+ no_js: JavaScript parece estar desactivado en su navegador. Normalmente, este
+ paso se realiza automáticamente, pero debido a que tiene JavaScript desactivado,
+ haga clic en el botón Enviar para continuar iniciando o cerrando la sesión.
diff --git a/config/locales/saml_idp/fr.yml b/config/locales/saml_idp/fr.yml
new file mode 100644
index 00000000000..89e20d33fe0
--- /dev/null
+++ b/config/locales/saml_idp/fr.yml
@@ -0,0 +1,10 @@
+---
+fr:
+ saml_idp:
+ shared:
+ saml_post_binding:
+ heading: Soumettre pour continuer
+ no_js: JavaScript semble être désactivé dans votre navigateur. Habituellement,
+ cette étape se déroule automatiquement, mais parce que vous avez désactivé
+ le JavaScript, veuillez cliquer sur le lien « soumettre » pour continuer
+ ou pour vous déconnecter.
diff --git a/config/locales/shared/es.yml b/config/locales/shared/es.yml
index b9776c4e4de..dbbb99d678f 100644
--- a/config/locales/shared/es.yml
+++ b/config/locales/shared/es.yml
@@ -2,6 +2,6 @@
es:
shared:
footer_lite:
- gsa: Administración de Servicios Generales de EE. UU.
+ gsa: Administración General de Servicios de EE. UU.
usa_banner:
- official_site: Un sitio web oficial del gobierno de los Estados Unidos
+ official_site: Un sitio oficial del Gobierno de Estados Unidos
diff --git a/config/locales/shared/fr.yml b/config/locales/shared/fr.yml
new file mode 100644
index 00000000000..d54177fd046
--- /dev/null
+++ b/config/locales/shared/fr.yml
@@ -0,0 +1,7 @@
+---
+fr:
+ shared:
+ footer_lite:
+ gsa: Administration des services généraux des États-Unis
+ usa_banner:
+ official_site: Un site web officiel du gouvernement des États-Unis
diff --git a/config/locales/sign_up/en.yml b/config/locales/sign_up/en.yml
index 531a247d59f..413f1e3f8e9 100644
--- a/config/locales/sign_up/en.yml
+++ b/config/locales/sign_up/en.yml
@@ -1,16 +1,16 @@
---
- en:
- sign_up:
- buttons:
- continue: Continue account creation
- cancel: Cancel and delete your information
- cancel:
- modal_header: Are you sure you want to cancel?
- warning_header: If you cancel now
- success: Your account has been deleted. We did not save your information.
- warning_points:
- - You won’t have a login.gov account
- - We won't keep a record of your email address, password, and phone number
- - You won't be able to securely access your information using login.gov
- registrations:
- create_account: Create an account
+en:
+ sign_up:
+ buttons:
+ cancel: Cancel and delete your information
+ continue: Continue account creation
+ cancel:
+ modal_header: Are you sure you want to cancel?
+ success: Your account has been deleted. We did not save your information.
+ warning_header: If you cancel now
+ warning_points:
+ - You won’t have a login.gov account
+ - We won't keep a record of your email address, password, and phone number
+ - You won't be able to securely access your information using login.gov
+ registrations:
+ create_account: Create an account
diff --git a/config/locales/sign_up/es.yml b/config/locales/sign_up/es.yml
index 48d40455ef4..a5f68f42ed0 100644
--- a/config/locales/sign_up/es.yml
+++ b/config/locales/sign_up/es.yml
@@ -1,14 +1,16 @@
---
- es:
- sign_up:
- buttons:
- continue: NOT TRANSLATED YET
- cancel: NOT TRANSLATED YET
- cancel:
- modal_header: NOT TRANSLATED YET
- warning_header: NOT TRANSLATED YET
- success: NOT TRANSLATED YET
- warning_points:
- - NOT TRANSLATED YET
- registrations:
- create_account: Empezar
+es:
+ sign_up:
+ buttons:
+ cancel: Cancelar y borrar su información
+ continue: Continuar la creación de cuenta
+ cancel:
+ modal_header: "¿Está seguro de que desea cancelar?"
+ success: Su cuenta ha sido eliminada. No guardamos su información.
+ warning_header: Si cancela ahora
+ warning_points:
+ - No tendrá una cuenta de login.gov
+ - No guardaremos un registro de su email, contraseña y número de teléfono
+ - No podrá acceder de forma segura a su información a través de login.gov
+ registrations:
+ create_account: Crear una cuenta
diff --git a/config/locales/sign_up/fr.yml b/config/locales/sign_up/fr.yml
new file mode 100644
index 00000000000..7a29236c0c9
--- /dev/null
+++ b/config/locales/sign_up/fr.yml
@@ -0,0 +1,18 @@
+---
+fr:
+ sign_up:
+ buttons:
+ cancel: Annuler et supprimer votre information
+ continue: Continuer la création du compte
+ cancel:
+ modal_header: Souhaitez-vous vraiment annuler?
+ success: Le dossier contenant votre information a été effacé
+ warning_header: Si vous annulez maintenant
+ warning_points:
+ - Vous n'aurez pas de compte login.gov
+ - Nous ne conserverons pas de dossier contenant votre adresse courriel, votre
+ mot de passe et votre numéro de téléphone
+ - Vous ne serez pas en mesure d'accéder à votre information de façon sécuritaire
+ en utilisant login.gov
+ registrations:
+ create_account: Créer un compte
diff --git a/config/locales/simple_form/en.yml b/config/locales/simple_form/en.yml
index d1545816421..2b526a65b12 100644
--- a/config/locales/simple_form/en.yml
+++ b/config/locales/simple_form/en.yml
@@ -5,7 +5,7 @@ en:
default_message: 'Please review the problems below:'
'no': 'No'
required:
- html: '* '
+ html: *
mark: "*"
text: This field is required
'yes': 'Yes'
diff --git a/config/locales/simple_form/es.yml b/config/locales/simple_form/es.yml
index 7353ddc3978..8874b4a5498 100644
--- a/config/locales/simple_form/es.yml
+++ b/config/locales/simple_form/es.yml
@@ -5,7 +5,7 @@ es:
default_message: 'Por favor revise los siguientes problemas:'
'no': 'No'
required:
- html: '* '
+ html: *
mark: "*"
text: Este campo es requerido
- 'yes': 'Sí'
+ 'yes': Sí
diff --git a/config/locales/simple_form/fr.yml b/config/locales/simple_form/fr.yml
new file mode 100644
index 00000000000..ed255646c2d
--- /dev/null
+++ b/config/locales/simple_form/fr.yml
@@ -0,0 +1,11 @@
+---
+fr:
+ simple_form:
+ error_notification:
+ default_message: 'Veuillez examiner les problèmes ci-dessous :'
+ 'no': Non
+ required:
+ html: *
+ mark: "*"
+ text: Ce champ est requis
+ 'yes': Oui
diff --git a/config/locales/time/fr.yml b/config/locales/time/fr.yml
new file mode 100644
index 00000000000..32b0b06bebf
--- /dev/null
+++ b/config/locales/time/fr.yml
@@ -0,0 +1,5 @@
+---
+fr:
+ time:
+ formats:
+ event_timestamp: "%B %e, %Y à %-l:%M %p"
diff --git a/config/locales/titles/en.yml b/config/locales/titles/en.yml
index e723b867945..3a8307ef171 100644
--- a/config/locales/titles/en.yml
+++ b/config/locales/titles/en.yml
@@ -1,6 +1,7 @@
---
en:
titles:
+ account: Account
account_locked: Account locked
confirmations:
new: Resend confirmation instructions for your account
@@ -14,9 +15,8 @@ en:
change: Change the password for your account
confirm: Confirm the password for your account
forgot: Reset the password for your account
- account: Account
- reactivate_account: Reactivate your account
personal_key: Just in case
+ reactivate_account: Reactivate your account
registrations:
new: Sign up for a account
start: Get started
diff --git a/config/locales/titles/es.yml b/config/locales/titles/es.yml
index 3808dc22116..982903913fa 100644
--- a/config/locales/titles/es.yml
+++ b/config/locales/titles/es.yml
@@ -1,33 +1,33 @@
---
es:
titles:
- account_locked: NOT TRANSLATED YET
+ account: Cuenta
+ account_locked: Cuenta bloqueada
confirmations:
- new: NOT TRANSLATED YET
- show: NOT TRANSLATED YET
+ new: Reenviar instrucciones de confirmación de su cuenta
+ show: Elija una contraseña
edit_info:
- email: NOT TRANSLATED YET
- password: NOT TRANSLATED YET
- phone: NOT TRANSLATED YET
- enter_2fa_code: NOT TRANSLATED YET
+ email: Edite su email
+ password: Edite su contraseña
+ phone: Edite su número de teléfono
+ enter_2fa_code: Ingese su código de seguridad de sólo un uso
passwords:
- change: NOT TRANSLATED YET
- confirm: NOT TRANSLATED YET
- forgot: NOT TRANSLATED YET
- account: NOT TRANSLATED YET
- reactivate_account: NOT TRANSLATED YET
- personal_key: NOT TRANSLATED YET
+ change: Cambie la contraseña de su cuenta
+ confirm: Confirme la contraseña de su cuenta
+ forgot: Restablezca la contraseña de su cuenta
+ personal_key: Por si acaso
+ reactivate_account: Reactive su cuenta
registrations:
- new: NOT TRANSLATED YET
- start: NOT TRANSLATED YET
+ new: Regístrese para una cuenta
+ start: Empezar
sign_up:
- completion_html: You have %{accent} with %{app}
- loa1: created your account
- loa3: verified your identity
+ completion_html: Tiene %{accent} con %{app}
+ loa1: creó su cuenta
+ loa3: verificó su identidad
totp_setup:
- new: NOT TRANSLATED YET
- two_factor_setup: NOT TRANSLATED YET
- verify_email: NOT TRANSLATED YET
- verify_profile: NOT TRANSLATED YET
+ new: Configure la autenticación de dos factores
+ two_factor_setup: Configuración de autenticación de dos factores
+ verify_email: Revise su email
+ verify_profile: Active su cuenta
visitors:
- index: NOT TRANSLATED YET
+ index: Bienvenido/a
diff --git a/config/locales/titles/fr.yml b/config/locales/titles/fr.yml
new file mode 100644
index 00000000000..1b16899bd5c
--- /dev/null
+++ b/config/locales/titles/fr.yml
@@ -0,0 +1,33 @@
+---
+fr:
+ titles:
+ account: Compte
+ account_locked: Compte verrouillé
+ confirmations:
+ new: Envoyer les instructions de confirmation pour votre compte
+ show: Choisissez un mot de passe
+ edit_info:
+ email: Modifier votre adresse courriel
+ password: Modifier votre mot de passe
+ phone: Modifier votre numéro de téléphone
+ enter_2fa_code: Entrez le code de sécurité à utilisation unique
+ passwords:
+ change: Changez le mot de passe de votre compte
+ confirm: Confirmez le mot de passe de votre compte
+ forgot: Réinitialisez le mot de passe de votre compte
+ personal_key: Juste au cas
+ reactivate_account: Réactiver le profil
+ registrations:
+ new: S'inscrire et créer un compte
+ start: Démarrer
+ sign_up:
+ completion_html: Vous avez %{accent} avec %{app}
+ loa1: créé votre compte
+ loa3: verifié votre identité
+ totp_setup:
+ new: Configurer l'authentification à deux facteurs
+ two_factor_setup: Configuration de l'authentification à deux facteurs
+ verify_email: Consultez vos courriels
+ verify_profile: Activez votre compte
+ visitors:
+ index: Bienvenue
diff --git a/config/locales/tooltips/en.yml b/config/locales/tooltips/en.yml
index 55214520320..ff5023eb534 100644
--- a/config/locales/tooltips/en.yml
+++ b/config/locales/tooltips/en.yml
@@ -1,9 +1,8 @@
---
en:
tooltips:
- ssn: >
- We ask for your Social Security number to help prove your identity. Some
+ authentication_app: An authentication application is a mobile security app that
+ generates security codes even if you don't have an Internet connection or cellular
+ service.
+ ssn: We ask for your Social Security number to help prove your identity. Some
agencies we work with may need it to access your records.
- authentication_app: An authentication application is a mobile security app
- that generates security codes even if you don't have an Internet connection
- or cellular service.
diff --git a/config/locales/tooltips/es.yml b/config/locales/tooltips/es.yml
index a691fe14468..f5fdf0e4430 100644
--- a/config/locales/tooltips/es.yml
+++ b/config/locales/tooltips/es.yml
@@ -1,5 +1,9 @@
---
es:
tooltips:
- ssn: NOT TRANSLATED YET
- authentication_app: NOT TRANSLATED YET
+ authentication_app: Una app de autenticación es una app de seguridad móvil que
+ genera códigos de seguridad, incluso si no tiene una conexión de internet o
+ un servicio celular.
+ ssn: Le pedimos su número de Seguro Social para ayudar a demostrar su identidad.
+ Algunas agencias con las que trabajamos pueden necesitarlo para acceder a sus
+ registros.
diff --git a/config/locales/tooltips/fr.yml b/config/locales/tooltips/fr.yml
new file mode 100644
index 00000000000..6ce09fd1f9e
--- /dev/null
+++ b/config/locales/tooltips/fr.yml
@@ -0,0 +1,9 @@
+---
+fr:
+ tooltips:
+ authentication_app: Une application d'authentification est une application de
+ sécurité mobile qui génère des codes de sécurité même si vous n'avez pas de
+ connexion internet ou de service cellulaire.
+ ssn: Nous vous demandons votre numéro de sécurité sociale pour nous aider à prouver
+ votre identité. Certaines des agences avec lesquelles nous collaborons pourraient
+ devoir accéder à votre dossier.
diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml
index c3804be8068..ce320da7e90 100644
--- a/config/locales/user_mailer/en.yml
+++ b/config/locales/user_mailer/en.yml
@@ -1,31 +1,27 @@
---
en:
user_mailer:
- help_link_text: Help Center
contact_link_text: contact us
email_changed:
- help: >
- If you did not want to change your email address, please visit the %{app}
+ help: If you did not want to change your email address, please visit the %{app}
%{help_link} or %{contact_link}.
intro: The email address for your %{app} account has been changed.
+ help_link_text: Help Center
password_changed:
- help: >
- If you did not make this change, please visit the %{app}
- %{help_link} or %{contact_link}.
+ help: If you did not make this change, please visit the %{app} %{help_link}
+ or %{contact_link}.
intro: You have a new password for your %{app} account.
phone_changed:
- help: >
- If you did not want to change your phone number, please visit the %{app} %{help_link}
- or %{contact_link}.
+ help: If you did not want to change your phone number, please visit the %{app}
+ %{help_link} or %{contact_link}.
intro: The phone number associated with your %{app} account has been changed.
subject: New phone number
signup_with_your_email:
- help: >
- If you did not request a new account or suspect an error, please visit
+ help: If you did not request a new account or suspect an error, please visit
the %{app} %{help_link} or %{contact_link}.
- intro: >
- This email address is already associated with a %{app} account, so we can't use it to
- create a new account. To sign in with your existing account, follow the link below.
- If you are not trying to sign in with this email address, you can ignore this message.
+ intro: This email address is already associated with a %{app} account, so we
+ can't use it to create a new account. To sign in with your existing account,
+ follow the link below. If you are not trying to sign in with this email address,
+ you can ignore this message.
link_text: Go to %{app}
reset_password: If you can't remember your password, go to %{app} to reset it.
diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml
index 781dc005117..56d369a3bf1 100644
--- a/config/locales/user_mailer/es.yml
+++ b/config/locales/user_mailer/es.yml
@@ -1,20 +1,25 @@
---
es:
user_mailer:
- help_link_text: NOT TRANSLATED YET
- contact_link_text: NOT TRANSLATED YET
+ contact_link_text: Contáctenos
email_changed:
- help: NOT TRANSLATED YET
- intro: NOT TRANSLATED YET
+ help: Si no desea cambiar su email, visite el %{app} %{help_link} o el %{contact_link}.
+ intro: El email de su cuenta de {app} ha sido cambiado.
+ help_link_text: Centro de Ayuda
password_changed:
- help: NOT TRANSLATED YET
- intro: NOT TRANSLATED YET
+ help: Si no realizó este cambio, visite el %{app} % {help_link} o el %{contact_link}.
+ intro: Tiene una contraseña nueva para su cuenta de %{app}.
phone_changed:
- help: NOT TRANSLATED YET
- intro: NOT TRANSLATED YET
- subject: NOT TRANSLATED YET
+ help: Si no desea cambiar su número de teléfono, visite el %{app} %{help_link}
+ o el %{contact_link}.
+ intro: El número de teléfono asociado a su cuenta de %{app} ha sido cambiado.
+ subject: Nuevo número de teléfono
signup_with_your_email:
- help: NOT TRANSLATED YET
- intro: NOT TRANSLATED YET
- link_text: NOT TRANSLATED YET
- reset_password: NOT TRANSLATED YET
+ help: Si no solicitó una cuenta nueva o sospecha un error, visite el %{app}
+ %{help_link} o el %{contact_link}.
+ intro: Este email ya está asociado a una cuenta %{app}, por lo tanto no podemos
+ usarlo para crear una cuenta nueva. Para iniciar una sesión con su cuenta
+ existente, siga el siguiente enlace. Si no intenta iniciar una sesión con
+ este email, puede ignorar este mensaje.
+ link_text: Ir a %{app}
+ reset_password: Si no recuerda su contraseña, vaya a %{app} para restablecerla.
diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml
new file mode 100644
index 00000000000..44da5ec3601
--- /dev/null
+++ b/config/locales/user_mailer/fr.yml
@@ -0,0 +1,28 @@
+---
+fr:
+ user_mailer:
+ contact_link_text: communiquez avec nous
+ email_changed:
+ help: Si vous préférez ne pas changer votre adresse courriel, veuillez visiter
+ le %{help_link} de %{app} ou %{contact_link}.
+ intro: L'adresse courriel de votre compte %{app} a été changée.
+ help_link_text: Centre d'aide
+ password_changed:
+ help: Si vous n'avez pas changé votre mot de passe, veuillez visiter le %{help_link}
+ de %{app} ou %{contact_link}.
+ intro: Le mot de passe de votre compte %{app} a été changé.
+ phone_changed:
+ help: Si vous ne souhaitiez pas changer votre numéro de téléphone, veuillez
+ visiter le %{help_link} de %{app} ou %{contact_link}.
+ intro: Le numéro de téléphone associé à votre compte %{app} a été changé.
+ subject: Nouveau numéro de téléphone
+ signup_with_your_email:
+ help: Si vous n'avez pas demandé un nouveau compte ou que vous soupçonnez qu'une
+ erreur s'est produite, veuillez visiter le %{help_link} de %{app} ou %{contact_link}.
+ intro: Cette adresse courriel est déjà associée à un compte %{app}, nous ne
+ pouvons donc pas l'utiliser pour créer un nouveau compte. Pour vous connecter
+ à votre compte existant, suivez le lien ci-dessous. Si vous ne tentez pas
+ de vous connecter avec cette adresse courriel, vous pouvez ignorer ce message.
+ link_text: Allez à %{app}
+ reset_password: Si vous ne vous souvenez plus de votre mot de passe, allez à
+ %{app} pour le réinitialiser.
diff --git a/config/locales/users/en.yml b/config/locales/users/en.yml
index 4c931203869..cfe8561dda7 100644
--- a/config/locales/users/en.yml
+++ b/config/locales/users/en.yml
@@ -1,16 +1,13 @@
---
en:
users:
- totp_setup:
- new:
- qr_img_alt: QR Code for Authenticator App
personal_key:
- header: Your personal key
+ close: Close
+ confirmation_error: You've entered an incorrect personal key.
generated_on_html: Generated on %{date}
get_another: Get another key
- print: Print this page
- help_text_header: Why do I need to store my new key on paper?
- help_text: |
+ header: Your personal key
+ help_text: |-
To protect your account, you need a password and access to your telephone or authentication application at sign-in. If you can’t use your phone or app, you can sign in with your personal key instead.
For your privacy and security, login.gov does not store your password and personal key. Only you know them. Only you can access or share your personal information.
@@ -18,5 +15,8 @@ en:
We require you to store your personal key outside your computer or mobile device so that it will be safe even if your devices are stolen or your online accounts are hacked.
If you don’t have your personal key and you forget your password, the only way to keep your account safe is to verify that you are the legal owner.
- close: Close
- confirmation_error: You've entered an incorrect personal key.
+ help_text_header: Why do I need to store my new key on paper?
+ print: Print this page
+ totp_setup:
+ new:
+ qr_img_alt: QR Code for Authenticator App
diff --git a/config/locales/users/es.yml b/config/locales/users/es.yml
index 65fc4f391cf..543d5d8d4bf 100644
--- a/config/locales/users/es.yml
+++ b/config/locales/users/es.yml
@@ -1,14 +1,22 @@
+---
es:
users:
+ personal_key:
+ close: Cerrar
+ confirmation_error: Ha ingresado una clave personal incorrecta.
+ generated_on_html: Generado el %{date}
+ get_another: Obtener otra clave
+ header: Su clave personal
+ help_text: |-
+ Para proteger su cuenta, necesita una contraseña y acceso a su teléfono o app de autenticación al iniciar una sesión. Si no puede utilizar su teléfono o app, puede iniciar una sesión con su clave personal.
+
+ Para su privacidad y seguridad, login.gov no guarda su contraseña y clave personal. Sólo usted las conoce. Sólo usted puede acceder o compartir su información personal.
+
+ Le pedimos que guarde su clave personal afuera de su computadora o dispositivo móvil para que mantenerla segura en caso de que le roben sus aparatos o sus cuentas en línea sean hackeadas.
+
+ Si no tiene su clave personal y olvida su contraseña, la única manera de mantener su cuenta segura es verificando que usted es el propietario legal.
+ help_text_header: "¿Por qué necesito guardar mi nueva clave en papel?"
+ print: Imprima esta página
totp_setup:
new:
- qr_img_alt: Código de QR para la aplicación de autenticación
- personal_key:
- header: NOT TRANSLATED YET
- generated_on_html: NO TRANSLATED YET
- get_another: NOT TRANSLATED YET
- print: NOT TRANSLATED YET
- help_text_header: NOT TRANSLATED YET
- help_text: NOT TRANSLATED YET
- close: NOT TRANSLATED YET
- confirmation_error: NOT TRANSLATED YET
+ qr_img_alt: Código de QR para la app de autenticación
diff --git a/config/locales/users/fr.yml b/config/locales/users/fr.yml
new file mode 100644
index 00000000000..27baff23a54
--- /dev/null
+++ b/config/locales/users/fr.yml
@@ -0,0 +1,22 @@
+---
+fr:
+ users:
+ personal_key:
+ close: Fermer
+ confirmation_error: Vous avez entré un clé personnelle erronée.
+ generated_on_html: Générée le %{date}
+ get_another: Obtenir une autre clé
+ header: Votre clé personnelle
+ help_text: |-
+ Pour protéger votre compte, vous devez avoir un mot de passe et l'accès à votre téléphone ou application d'authentification au moment de la connexion. Si vous ne pouvez utiliser votre téléphone ou application, vous pouvez vous connecter avec votre clé personnelle.
+
+ Pour votre confidentialité et votre sécurité, login.gov ne conserve pas votre mot de passe ni votre clé personnelle. Seul(e) vous les connaissez. Seul(e) vous pouvez accéder à votre information personnelle et la partager .
+
+ Nous vous demandons de conserver votre clé personnelle à l'extérieur de votre ordinateur ou appareil mobile afin qu'elle soit en sûreté même si vos appareils sont volés ou si vos comptes en ligne sont piratés.
+
+ Si vous n'avez pas votre clé personnelle et que vous oubliez votre mot de passe, la seule façon de garder votre compte en sécurité est de vérifier que vous en êtes le(la) propriétaire légal(e).
+ help_text_header: Pourquoi dois-je conserver ma nouvelle clé sur papier?
+ print: Imprimer cette page
+ totp_setup:
+ new:
+ qr_img_alt: Code QR pour l'application d'authentification
diff --git a/config/locales/valid_email/en.yml b/config/locales/valid_email/en.yml
index 2e106d820ac..b678f98820a 100644
--- a/config/locales/valid_email/en.yml
+++ b/config/locales/valid_email/en.yml
@@ -1,5 +1,7 @@
+---
en:
valid_email:
validations:
email:
- invalid: Invalid email address format or domain entered. Correct the address and re-enter it.
+ invalid: Invalid email address format or domain entered. Correct the address
+ and re-enter it.
diff --git a/config/locales/valid_email/es.yml b/config/locales/valid_email/es.yml
index 3ead9a91c9e..7fc94c3cab7 100644
--- a/config/locales/valid_email/es.yml
+++ b/config/locales/valid_email/es.yml
@@ -1,5 +1,7 @@
+---
es:
valid_email:
validations:
email:
- invalid: El formato de dirección de correo electrónico o el dominio ingresado no es válido. Corrija la dirección y vuelva a ingresarla.
+ invalid: El formato de email o dominio ingresado no es válido. Corrija su
+ email y vuelva a intentarlo.
diff --git a/config/locales/valid_email/fr.yml b/config/locales/valid_email/fr.yml
new file mode 100644
index 00000000000..2036952ded5
--- /dev/null
+++ b/config/locales/valid_email/fr.yml
@@ -0,0 +1,7 @@
+---
+fr:
+ valid_email:
+ validations:
+ email:
+ invalid: Format d'adresse courriel ou domaine entré non valide. Corrigez l'adresse
+ et entrez-la de nouveau.
diff --git a/config/locales/zxcvbn/en.yml b/config/locales/zxcvbn/en.yml
index 05e228179f1..2e7fa4794bb 100644
--- a/config/locales/zxcvbn/en.yml
+++ b/config/locales/zxcvbn/en.yml
@@ -2,39 +2,46 @@
en:
zxcvbn:
feedback:
- "A word by itself is easy to guess": A word by itself is easy to guess
- "Add another word or two_ Uncommon words are better_": >-
- Add another word or two. Uncommon words are better
- "All-uppercase is almost as easy to guess as all-lowercase": >-
- All-uppercase is almost as easy to guess as all-lowercase
- "Avoid dates and years that are associated with you": >-
- Avoid dates and years that are associated with you
- "Avoid recent years": Avoid recent years
- "Avoid repeated words and characters": Avoid repeated words and characters
- "Avoid sequences": Avoid sequences
- "Avoid years that are associated with you": Avoid years that are associated with you
- "Capitalization doesn't help very much": Capitalization doesn’t help very much
- "Common names and surnames are easy to guess": Common names and surnames are easy to guess
- "Dates are often easy to guess": Dates are often easy to guess
- "Names and surnames by themselves are easy to guess": >-
- Names and surnames by themselves are easy to guess
- "No need for symbols, digits, or uppercase letters": >-
- There is no need for symbols, digits, or uppercase letters
- "Predictable substitutions like '@' instead of 'a' don't help very much":
- Predictable substitutions like '@' instead of 'a' don’t help very much
- "Recent years are easy to guess": Recent years are easy to guess
- "Repeats like \"aaa\" are easy to guess": Repeats like "aaa" are easy to guess
- "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": >-
- Repeats like "abcabcabc" are only slightly harder to guess than "abc"
- "Reversed words aren't much harder to guess": Reversed words aren’t much harder to guess
- "Sequences like abc or 6543 are easy to guess": Sequences like abc or 6543 are easy to guess
- "Short keyboard patterns are easy to guess": Short keyboard patterns are easy to guess
- "Straight rows of keys are easy to guess": Straight rows of keys are easy to guess
- "This is a top-10 common password": This is a top-10 common password
- "This is a top-100 common password": This is a top-100 common password
- "This is a very common password": This is a very common password
- "This is similar to a commonly used password": This is similar to a commonly used password
- "Use a few words, avoid common phrases":
- For a stronger password, use a few words separated by spaces, but avoid common phrases
- "Use a longer keyboard pattern with more turns": >-
- Use a longer keyboard pattern with more turns
+ a_word_by_itself_is_easy_to_guess: A word by itself is easy to guess
+ add_another_word_or_two_uncommon_words_are_better: Add another word or two.
+ Uncommon words are better
+ all_uppercase_is_almost_as_easy_to_guess_as_all_lowercase: All-uppercase is
+ almost as easy to guess as all-lowercase
+ avoid_dates_and_years_that_are_associated_with_you: Avoid dates and years that
+ are associated with you
+ avoid_recent_years: Avoid recent years
+ avoid_repeated_words_and_characters: Avoid repeated words and characters
+ avoid_sequences: Avoid sequences
+ avoid_years_that_are_associated_with_you: Avoid years that are associated with
+ you
+ capitalization_doesnt_help_very_much: Capitalization doesn’t help very much
+ common_names_and_surnames_are_easy_to_guess: Common names and surnames are easy
+ to guess
+ dates_are_often_easy_to_guess: Dates are often easy to guess
+ for_a_stronger_password_use_a_few_words_separated_by_spaces_but_avoid_common_phrases: For
+ a stronger password, use a few words separated by spaces, but avoid common
+ phrases
+ names_and_surnames_by_themselves_are_easy_to_guess: Names and surnames by themselves
+ are easy to guess
+ predictable_substitutions_like__instead_of_a_dont_help_very_much: Predictable
+ substitutions like '@' instead of 'a' don’t help very much
+ recent_years_are_easy_to_guess: Recent years are easy to guess
+ repeats_like_aaa_are_easy_to_guess: Repeats like "aaa" are easy to guess
+ repeats_like_abcabcabc_are_only_slightly_harder_to_guess_than_abc: Repeats like
+ "abcabcabc" are only slightly harder to guess than "abc"
+ reversed_words_arent_much_harder_to_guess: Reversed words aren’t much harder
+ to guess
+ sequences_like_abc_or_6543_are_easy_to_guess: Sequences like abc or 6543 are
+ easy to guess
+ short_keyboard_patterns_are_easy_to_guess: Short keyboard patterns are easy
+ to guess
+ straight_rows_of_keys_are_easy_to_guess: Straight rows of keys are easy to guess
+ there_is_no_need_for_symbols_digits_or_uppercase_letters: There is no need for
+ symbols, digits, or uppercase letters
+ this_is_a_top_100_common_password: This is a top-100 common password
+ this_is_a_top_10_common_password: This is a top-10 common password
+ this_is_a_very_common_password: This is a very common password
+ this_is_similar_to_a_commonly_used_password: This is similar to a commonly used
+ password
+ use_a_longer_keyboard_pattern_with_more_turns: Use a longer keyboard pattern
+ with more turns
diff --git a/config/locales/zxcvbn/es.yml b/config/locales/zxcvbn/es.yml
index 086ffa24261..268dced5c3d 100644
--- a/config/locales/zxcvbn/es.yml
+++ b/config/locales/zxcvbn/es.yml
@@ -2,30 +2,48 @@
es:
zxcvbn:
feedback:
- "A word by itself is easy to guess": NOT TRANSLATED YET
- "Add another word or two_ Uncommon words are better_": NOT TRANSLATED YET
- "All-uppercase is almost as easy to guess as all-lowercase": NOT TRANSLATED YET
- "Avoid dates and years that are associated with you": NOT TRANSLATED YET
- "Avoid recent years": NOT TRANSLATED YET
- "Avoid repeated words and characters": NOT TRANSLATED YET
- "Avoid sequences": NOT TRANSLATED YET
- "Avoid years that are associated with you": NOT TRANSLATED YET
- "Capitalization doesn't help very much": NOT TRANSLATED YET
- "Common names and surnames are easy to guess": NOT TRANSLATED YET
- "Dates are often easy to guess": NOT TRANSLATED YET
- "Names and surnames by themselves are easy to guess": NOT TRANSLATED YET
- "No need for symbols, digits, or uppercase letters": NOT TRANSLATED YET
- "Predictable substitutions like '@' instead of 'a' don't help very much": NOT TRANSLATED YET
- "Recent years are easy to guess": NOT TRANSLATED YET
- "Repeats like \"aaa\" are easy to guess": NOT TRANSLATED YET
- "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": NOT TRANSLATED YET
- "Reversed words aren't much harder to guess": NOT TRANSLATED YET
- "Sequences like abc or 6543 are easy to guess": NOT TRANSLATED YET
- "Short keyboard patterns are easy to guess": NOT TRANSLATED YET
- "Straight rows of keys are easy to guess": NOT TRANSLATED YET
- "This is a top-10 common password": NOT TRANSLATED YET
- "This is a top-100 common password": NOT TRANSLATED YET
- "This is a very common password": NOT TRANSLATED YET
- "This is similar to a commonly used password": NOT TRANSLATED YET
- "Use a few words, avoid common phrases": NOT TRANSLATED YET
- "Use a longer keyboard pattern with more turns": NOT TRANSLATED YET
+ a_word_by_itself_is_easy_to_guess: Una sola palabra es fácil de adivinar.
+ add_another_word_or_two_uncommon_words_are_better: Añada otra palabra o dos.
+ Las palabras poco comunes son mejor opción.
+ all_uppercase_is_almost_as_easy_to_guess_as_all_lowercase: Todo en mayúsculas
+ es casi igual de fácil de adivinar como todo en minúsculas.
+ avoid_dates_and_years_that_are_associated_with_you: Evite las fechas y los años
+ que están asociados con usted
+ avoid_recent_years: Evite los años recientes
+ avoid_repeated_words_and_characters: Evite palabras y caracteres repetidos
+ avoid_sequences: Evite secuencias
+ avoid_years_that_are_associated_with_you: Evite los años que están asociados
+ con usted
+ capitalization_doesnt_help_very_much: Usar mayúsculas no ayuda mucho
+ common_names_and_surnames_are_easy_to_guess: Los nombres y apellidos comunes
+ son fáciles de adivinar
+ dates_are_often_easy_to_guess: Las fechas suelen ser fáciles de adivinar
+ for_a_stronger_password_use_a_few_words_separated_by_spaces_but_avoid_common_phrases: Para
+ una contraseña más segura, use pocas palabras separadas por espacios, pero
+ evite frases comunes
+ names_and_surnames_by_themselves_are_easy_to_guess: Nombres y apellidos por
+ si solos son fáciles de adivinar
+ predictable_substitutions_like__instead_of_a_dont_help_very_much: No hay necesidad
+ de símbolos, dígitos o letras mayúsculas
+ recent_years_are_easy_to_guess: Sustituciones predecibles como '@' en lugar
+ de 'a' no ayudan mucho
+ repeats_like_aaa_are_easy_to_guess: Los años recientes son fáciles de adivinar
+ repeats_like_abcabcabc_are_only_slightly_harder_to_guess_than_abc: Las repeticiones
+ como "aaa" son fáciles de adivinar
+ reversed_words_arent_much_harder_to_guess: Las repeticiones como "abcabcabc"
+ son sólo un poco más difíciles de adivinar que "abc"
+ sequences_like_abc_or_6543_are_easy_to_guess: Las palabras invertidas no son
+ mucho más difíciles de adivinar
+ short_keyboard_patterns_are_easy_to_guess: Las secuencias como abc o 6543 son
+ fáciles de adivinar
+ straight_rows_of_keys_are_easy_to_guess: Las combinaciones cortas de teclas
+ son fáciles de adivinar
+ there_is_no_need_for_symbols_digits_or_uppercase_letters: Las líneas seguidas
+ de letras son fáciles de adivinar
+ this_is_a_top_100_common_password: Esta es una de las 10 contraseñas más comunes.
+ this_is_a_top_10_common_password: Esta es una de las 100 contraseñas más comunes.
+ this_is_a_very_common_password: Esta es una contraseña muy común
+ this_is_similar_to_a_commonly_used_password: Esto es similar a una contraseña
+ comúnmente utilizada
+ use_a_longer_keyboard_pattern_with_more_turns: Use una combinación larga de
+ teclas con más configuraciones
diff --git a/config/locales/zxcvbn/fr.yml b/config/locales/zxcvbn/fr.yml
new file mode 100644
index 00000000000..3666090103b
--- /dev/null
+++ b/config/locales/zxcvbn/fr.yml
@@ -0,0 +1,51 @@
+---
+fr:
+ zxcvbn:
+ feedback:
+ a_word_by_itself_is_easy_to_guess: Un mot seul est facile à deviner
+ add_another_word_or_two_uncommon_words_are_better: Ajoutez un ou deux autres
+ mots. Les mots non communs sont plus efficaces
+ all_uppercase_is_almost_as_easy_to_guess_as_all_lowercase: Tout en majuscules
+ est presque aussi facile à deviner que tout en minuscules
+ avoid_dates_and_years_that_are_associated_with_you: Évitez les dates et années
+ qui vous sont associées
+ avoid_recent_years: Évitez les années récentes
+ avoid_repeated_words_and_characters: Évitez les mots et caractères répétés
+ avoid_sequences: Évitez les séquences
+ avoid_years_that_are_associated_with_you: Évitez les années qui vous sont associées
+ capitalization_doesnt_help_very_much: La capitalisation n'aide pas beaucoup
+ common_names_and_surnames_are_easy_to_guess: Les prénoms et noms de famille
+ communs sont faciles à deviner
+ dates_are_often_easy_to_guess: Les dates sont souvent faciles à deviner
+ for_a_stronger_password_use_a_few_words_separated_by_spaces_but_avoid_common_phrases: Pour
+ créer un mot de passe plus fort, utilisez quelques mots séparés par des espaces,
+ mais évitez les phrases communes
+ names_and_surnames_by_themselves_are_easy_to_guess: Les prénoms et noms de famille
+ seuls sont faciles à deviner
+ predictable_substitutions_like__instead_of_a_dont_help_very_much: Les remplacements
+ prévisibles comme es « @ » au lieu de « à » n'aident pas beaucoup
+ recent_years_are_easy_to_guess: Les années récentes sont faciles à deviner
+ repeats_like_aaa_are_easy_to_guess: Les répétitions comme « aaa » sont faciles
+ à deviner
+ repeats_like_abcabcabc_are_only_slightly_harder_to_guess_than_abc: |-
+ Les répétitions comme « abcabcabc » sont à peine
+ plus difficiles à deviner que « abc »
+ reversed_words_arent_much_harder_to_guess: Les mots inversés ne sont pas très
+ difficiles à deviner
+ sequences_like_abc_or_6543_are_easy_to_guess: Les séquences comme abc ou 6543
+ sont faciles à deviner
+ short_keyboard_patterns_are_easy_to_guess: Les motifs de clavier courts sont
+ faciles à deviner
+ straight_rows_of_keys_are_easy_to_guess: Les rangées de lettres consécutives
+ sont faciles à deviner
+ there_is_no_need_for_symbols_digits_or_uppercase_letters: Les symboles, les
+ chiffres ou les lettres majuscules ne sont pas nécessaires
+ this_is_a_top_100_common_password: Il s'agit d'un des 100 mots de passe les
+ plus communs
+ this_is_a_top_10_common_password: Il s'agit d'un des 10 mots de passe les plus
+ communs
+ this_is_a_very_common_password: Il s'agit d'un mot de passe très commun
+ this_is_similar_to_a_commonly_used_password: Ceci est similaire à un mot de
+ passe souvent utilisé
+ use_a_longer_keyboard_pattern_with_more_turns: Utilisez un motif de clavier
+ plus long avec plus de tours
diff --git a/config/routes.rb b/config/routes.rb
index a21e217810d..6565bcbd982 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,60 +2,7 @@
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq', constraints: AdminConstraint.new
- # Devise handles login itself. It's first in the chain to avoid a redirect loop during
- # authentication failure.
- devise_for(
- :users,
- skip: %i[confirmations sessions registrations two_factor_authentication],
- controllers: { passwords: 'users/reset_passwords' }
- )
-
- # Additional device controller routes.
- devise_scope :user do
- get '/' => 'users/sessions#new', as: :new_user_session
- post '/' => 'users/sessions#create', as: :user_session
- get '/active' => 'users/sessions#active'
-
- get '/login/two_factor/authenticator' => 'two_factor_authentication/totp_verification#show'
- post '/login/two_factor/authenticator' => 'two_factor_authentication/totp_verification#create'
- get '/login/two_factor/personal_key' => 'two_factor_authentication/personal_key_verification#show'
- post '/login/two_factor/personal_key' => 'two_factor_authentication/personal_key_verification#create'
- get '/login/two_factor/:otp_delivery_preference' => 'two_factor_authentication/otp_verification#show',
- as: :login_two_factor
- post '/login/two_factor/:otp_delivery_preference' => 'two_factor_authentication/otp_verification#create',
- as: :login_otp
-
- get '/reauthn' => 'mfa_confirmation#new', as: :user_password_confirm
- post '/reauthn' => 'mfa_confirmation#create', as: :reauthn_user_password
- get '/timeout' => 'users/sessions#timeout'
- end
-
- if Figaro.env.enable_test_routes == 'true'
- namespace :test do
- # Assertion granting test start + return.
- get '/saml' => 'saml_test#start'
- get '/saml/decode_assertion' => 'saml_test#start'
- post '/saml/decode_assertion' => 'saml_test#decode_response'
- post '/saml/decode_slo_request' => 'saml_test#decode_slo_request'
- end
- end
-
- # Non-devise-controller routes. Alphabetically sorted.
- get '/.well-known/openid-configuration' => 'openid_connect/configuration#index',
- as: :openid_connect_configuration
-
- get '/account' => 'accounts#show'
- 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
- put '/account/reactivate/verify_password' => 'users/verify_password#update', as: :update_verify_password
- get '/account/reactivate/verify_personal_key' => 'users/verify_personal_key#new',
- as: :verify_personal_key
- post '/account/reactivate/verify_personal_key' => 'users/verify_personal_key#create',
- as: :create_verify_personal_key
- get '/account/verify_phone' => 'users/verify_profile_phone#index', as: :verify_profile_phone
- post '/account/verify_phone' => 'users/verify_profile_phone#create'
-
+ # Non i18n routes. Alphabetically sorted.
get '/api/health/workers' => 'health/workers#index'
get '/api/openid_connect/certs' => 'openid_connect/certs#index'
post '/api/openid_connect/token' => 'openid_connect/token#create'
@@ -69,86 +16,146 @@
post '/api/service_provider' => 'service_provider#update'
- delete '/authenticator_setup' => 'users/totp_setup#disable', as: :disable_totp
- get '/authenticator_setup' => 'users/totp_setup#new'
- patch '/authenticator_setup' => 'users/totp_setup#confirm'
- get '/authenticator_start' => 'users/totp_setup#start'
+ get '/openid_connect/authorize' => 'openid_connect/authorization#index'
+ get '/openid_connect/logout' => 'openid_connect/logout#index'
- get '/forgot_password' => 'forgot_password#show'
+ # i18n routes. Alphabetically sorted.
+ scope '(:locale)', locale: /#{I18n.available_locales.join('|')}/ do
+ # Devise handles login itself. It's first in the chain to avoid a redirect loop during
+ # authentication failure.
+ devise_for(
+ :users,
+ skip: %i[confirmations sessions registrations two_factor_authentication],
+ controllers: { passwords: 'users/reset_passwords' }
+ )
+
+ # Additional device controller routes.
+ devise_scope :user do
+ get '/' => 'users/sessions#new', as: :new_user_session
+ post '/' => 'users/sessions#create', as: :user_session
+ get '/active' => 'users/sessions#active'
+
+ get '/login/two_factor/authenticator' => 'two_factor_authentication/totp_verification#show'
+ post '/login/two_factor/authenticator' => 'two_factor_authentication/totp_verification#create'
+ get '/login/two_factor/personal_key' => 'two_factor_authentication/personal_key_verification#show'
+ post '/login/two_factor/personal_key' => 'two_factor_authentication/personal_key_verification#create'
+ get '/login/two_factor/:otp_delivery_preference' => 'two_factor_authentication/otp_verification#show',
+ as: :login_two_factor
+ post '/login/two_factor/:otp_delivery_preference' => 'two_factor_authentication/otp_verification#create',
+ as: :login_otp
+
+ get '/reauthn' => 'mfa_confirmation#new', as: :user_password_confirm
+ post '/reauthn' => 'mfa_confirmation#create', as: :reauthn_user_password
+ get '/timeout' => 'users/sessions#timeout'
+ end
- get '/manage/email' => 'users/emails#edit'
- match '/manage/email' => 'users/emails#update', via: %i[patch put]
- get '/manage/password' => 'users/passwords#edit'
- patch '/manage/password' => 'users/passwords#update'
- get '/manage/phone' => 'users/phones#edit'
- match '/manage/phone' => 'users/phones#update', via: %i[patch put]
- get '/manage/personal_key' => 'users/personal_keys#show', as: :manage_personal_key
- post '/manage/personal_key' => 'users/personal_keys#update'
+ if Figaro.env.enable_test_routes == 'true'
+ namespace :test do
+ # Assertion granting test start + return.
+ get '/saml' => 'saml_test#start'
+ get '/saml/decode_assertion' => 'saml_test#start'
+ post '/saml/decode_assertion' => 'saml_test#decode_response'
+ post '/saml/decode_slo_request' => 'saml_test#decode_slo_request'
+ end
+ end
- get '/openid_connect/authorize' => 'openid_connect/authorization#index'
- get '/openid_connect/logout' => 'openid_connect/logout#index'
+ # Non-devise-controller routes. Alphabetically sorted.
+ get '/.well-known/openid-configuration' => 'openid_connect/configuration#index',
+ as: :openid_connect_configuration
+
+ get '/account' => 'accounts#show'
+ 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
+ put '/account/reactivate/verify_password' => 'users/verify_password#update', as: :update_verify_password
+ get '/account/reactivate/verify_personal_key' => 'users/verify_personal_key#new',
+ as: :verify_personal_key
+ post '/account/reactivate/verify_personal_key' => 'users/verify_personal_key#create',
+ as: :create_verify_personal_key
+ get '/account/verify_phone' => 'users/verify_profile_phone#index', as: :verify_profile_phone
+ post '/account/verify_phone' => 'users/verify_profile_phone#create'
+
+ delete '/authenticator_setup' => 'users/totp_setup#disable', as: :disable_totp
+ get '/authenticator_setup' => 'users/totp_setup#new'
+ patch '/authenticator_setup' => 'users/totp_setup#confirm'
+ get '/authenticator_start' => 'users/totp_setup#start'
+
+ get '/forgot_password' => 'forgot_password#show'
+
+ get '/manage/email' => 'users/emails#edit'
+ match '/manage/email' => 'users/emails#update', via: %i[patch put]
+ get '/manage/password' => 'users/passwords#edit'
+ patch '/manage/password' => 'users/passwords#update'
+ get '/manage/phone' => 'users/phones#edit'
+ match '/manage/phone' => 'users/phones#update', via: %i[patch put]
+ get '/manage/personal_key' => 'users/personal_keys#show', as: :manage_personal_key
+ post '/manage/personal_key' => 'users/personal_keys#update'
+
+ get '/otp/send' => 'users/two_factor_authentication#send_code'
+ get '/phone_setup' => 'users/two_factor_authentication_setup#index'
+ patch '/phone_setup' => 'users/two_factor_authentication_setup#set'
+ get '/users/two_factor_authentication' => 'users/two_factor_authentication#show',
+ as: :user_two_factor_authentication # route name is used by two_factor_authentication gem
+
+ get '/profile', to: redirect('/account')
+ get '/profile/reactivate', to: redirect('/account/reactivate')
+ get '/profile/verify', to: redirect('/account/verify')
+
+ post '/sign_up/create_password' => 'sign_up/passwords#create', as: :sign_up_create_password
+ get '/sign_up/email/confirm' => 'sign_up/email_confirmations#create',
+ as: :sign_up_create_email_confirmation
+ get '/sign_up/enter_email' => 'sign_up/registrations#new', as: :sign_up_email
+ post '/sign_up/enter_email' => 'sign_up/registrations#create', as: :sign_up_register
+ get '/sign_up/enter_email/resend' => 'sign_up/email_resend#new', as: :sign_up_email_resend
+ post '/sign_up/enter_email/resend' => 'sign_up/email_resend#create',
+ as: :sign_up_create_email_resend
+ get '/sign_up/enter_password' => 'sign_up/passwords#new'
+ get '/sign_up/personal_key' => 'sign_up/personal_keys#show'
+ post '/sign_up/personal_key' => 'sign_up/personal_keys#update'
+ get '/sign_up/start' => 'sign_up/registrations#show', as: :sign_up_start
+ get '/sign_up/verify_email' => 'sign_up/emails#show', as: :sign_up_verify_email
+ get '/sign_up/completed' => 'sign_up/completions#show', as: :sign_up_completed
+ post '/sign_up/completed' => 'sign_up/completions#update'
+
+ match '/sign_out' => 'sign_out#destroy', via: %i[get post delete]
+
+ delete '/users' => 'users#destroy', as: :destroy_user
+
+ if FeatureManagement.enable_identity_verification?
+ get '/verify' => 'verify#index'
+ get '/verify/activated' => 'verify#activated'
+ get '/verify/address' => 'verify/address#index'
+ get '/verify/cancel' => 'verify#cancel'
+ get '/verify/confirmations' => 'verify/confirmations#show'
+ post '/verify/confirmations' => 'verify/confirmations#update'
+ get '/verify/fail' => 'verify#fail'
+ get '/verify/finance' => 'verify/finance#new'
+ put '/verify/finance' => 'verify/finance#create'
+ get '/verify/finance/other' => 'verify/finance_other#new'
+ get '/verify/finance/result' => 'verify/finance#show'
+ get '/verify/phone' => 'verify/phone#new'
+ put '/verify/phone' => 'verify/phone#create'
+ get '/verify/phone/result' => 'verify/phone#show'
+ get '/verify/review' => 'verify/review#new'
+ put '/verify/review' => 'verify/review#create'
+ get '/verify/session' => 'verify/sessions#new'
+ put '/verify/session' => 'verify/sessions#create'
+ get '/verify/session/result' => 'verify/sessions#show'
+ delete '/verify/session' => 'verify/sessions#destroy'
+ get '/verify/session/dupe' => 'verify/sessions#dupe'
- get '/otp/send' => 'users/two_factor_authentication#send_code'
- get '/phone_setup' => 'users/two_factor_authentication_setup#index'
- patch '/phone_setup' => 'users/two_factor_authentication_setup#set'
- get '/users/two_factor_authentication' => 'users/two_factor_authentication#show',
- as: :user_two_factor_authentication # route name is used by two_factor_authentication gem
-
- get '/profile', to: redirect('/account')
- get '/profile/reactivate', to: redirect('/account/reactivate')
- get '/profile/verify', to: redirect('/account/verify')
-
- post '/sign_up/create_password' => 'sign_up/passwords#create', as: :sign_up_create_password
- get '/sign_up/email/confirm' => 'sign_up/email_confirmations#create',
- as: :sign_up_create_email_confirmation
- get '/sign_up/enter_email' => 'sign_up/registrations#new', as: :sign_up_email
- post '/sign_up/enter_email' => 'sign_up/registrations#create', as: :sign_up_register
- get '/sign_up/enter_email/resend' => 'sign_up/email_resend#new', as: :sign_up_email_resend
- post '/sign_up/enter_email/resend' => 'sign_up/email_resend#create',
- as: :sign_up_create_email_resend
- get '/sign_up/enter_password' => 'sign_up/passwords#new'
- get '/sign_up/personal_key' => 'sign_up/personal_keys#show'
- post '/sign_up/personal_key' => 'sign_up/personal_keys#update'
- get '/sign_up/start' => 'sign_up/registrations#show', as: :sign_up_start
- get '/sign_up/verify_email' => 'sign_up/emails#show', as: :sign_up_verify_email
- get '/sign_up/completed' => 'sign_up/completions#show', as: :sign_up_completed
- post '/sign_up/completed' => 'sign_up/completions#update'
-
- match '/sign_out' => 'sign_out#destroy', via: %i[get post delete]
-
- delete '/users' => 'users#destroy', as: :destroy_user
-
- if FeatureManagement.enable_identity_verification?
- get '/verify' => 'verify#index'
- get '/verify/activated' => 'verify#activated'
- get '/verify/address' => 'verify/address#index'
- get '/verify/cancel' => 'verify#cancel'
- get '/verify/confirmations' => 'verify/confirmations#show'
- post '/verify/confirmations' => 'verify/confirmations#update'
- get '/verify/fail' => 'verify#fail'
- get '/verify/finance' => 'verify/finance#new'
- put '/verify/finance' => 'verify/finance#create'
- get '/verify/finance/other' => 'verify/finance_other#new'
- get '/verify/phone' => 'verify/phone#new'
- put '/verify/phone' => 'verify/phone#create'
- get '/verify/review' => 'verify/review#new'
- put '/verify/review' => 'verify/review#create'
- get '/verify/session' => 'verify/sessions#new'
- put '/verify/session' => 'verify/sessions#create'
- delete '/verify/session' => 'verify/sessions#destroy'
- get '/verify/session/dupe' => 'verify/sessions#dupe'
+ end
- end
+ if FeatureManagement.enable_usps_verification?
+ get '/account/verify' => 'users/verify_account#index', as: :verify_account
+ post '/account/verify' => 'users/verify_account#create'
+ get '/verify/usps' => 'verify/usps#index'
+ put '/verify/usps' => 'verify/usps#create'
+ end
- if FeatureManagement.enable_usps_verification?
- get '/account/verify' => 'users/verify_account#index', as: :verify_account
- post '/account/verify' => 'users/verify_account#create'
- get '/verify/usps' => 'verify/usps#index'
- put '/verify/usps' => 'verify/usps#create'
+ root to: 'users/sessions#new'
end
- root to: 'users/sessions#new'
-
# Make sure any new routes are added above this line!
# The line below will route all requests that aren't
# defined route to the 404 page. Therefore, anything you put after this rule
diff --git a/config/schedule.rb b/config/schedule.rb
index 7ab3c4cf593..5351f54b4e7 100644
--- a/config/schedule.rb
+++ b/config/schedule.rb
@@ -15,3 +15,11 @@
runner 'WorkerHealthChecker.check'
runner 'WorkerHealthChecker.enqueue_dummy_jobs'
end
+
+if FeatureManagement.enable_usps_verification?
+ mail_batch = Whenever.seconds(Figaro.env.usps_mail_batch_hours.to_i, :hours)
+
+ every mail_batch, roles: [:job_creator] do
+ runner 'UspsUploader.new.run'
+ end
+end
diff --git a/config/service_providers.yml b/config/service_providers.yml
index e61ac94bdde..199a8dc5e22 100644
--- a/config/service_providers.yml
+++ b/config/service_providers.yml
@@ -383,7 +383,17 @@ production:
'urn:gov:dhs.cbp.jobs:openidconnect:jenkins-pspd-credential-service':
friendly_name: 'CBP PSPD Trusted Traveler Programs'
agency: 'DHS'
- logo: 'cbp.png'
+ logo: 'cbp-ttp.png'
cert: 'cbp_goes_pre_prod'
redirect_uris:
- 'http://10.156.152.27/login'
+
+ 'urn:gov:dhs.cbp.jobs:openidconnect:aws-cbp-ttp':
+ agency: 'DHS'
+ allow_on_prod_chef_env: 'true'
+ block_encryption: 'aes256-cbc'
+ cert: 'cbp_goes_prod'
+ friendly_name: 'CBP Trusted Traveler Programs'
+ logo: 'cbp-ttp.png'
+ redirect_uris:
+ - 'https://ttp.cbp.dhs.gov'
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
index 8a0466b9b10..f72f4153bcd 100644
--- a/config/sidekiq.yml
+++ b/config/sidekiq.yml
@@ -3,4 +3,5 @@
- voice
- mailers
- analytics
+ - idv
:logfile: 'log/sidekiq.log'
diff --git a/lib/feature_management.rb b/lib/feature_management.rb
index d8ef77b268d..e88d2e3f93e 100644
--- a/lib/feature_management.rb
+++ b/lib/feature_management.rb
@@ -59,4 +59,8 @@ def self.reveal_usps_code?
def self.current_env_allowed_to_see_usps_code?
ENVS_WHERE_PREFILLING_USPS_CODE_ALLOWED.include?(Figaro.env.domain_name)
end
+
+ def self.no_pii_mode?
+ enable_identity_verification? && Idv::Vendor.new.pick == :mock
+ end
end
diff --git a/lib/yaml_normalizer.rb b/lib/yaml_normalizer.rb
new file mode 100644
index 00000000000..347a0421ebd
--- /dev/null
+++ b/lib/yaml_normalizer.rb
@@ -0,0 +1,41 @@
+require 'yaml'
+require 'active_support/core_ext/object/try'
+
+class YamlNormalizer
+ # Reads in YAML at a path, trims whitespace from each key, and writes it back to the file
+ def self.run(argv)
+ argv.each do |file|
+ $stderr.puts file
+ data = YAML.load_file(file)
+ chomp_each(data)
+ dump(file, data)
+ end
+ end
+
+ def self.chomp_each(hash)
+ hash.each do |_key, value|
+ if value.is_a?(String)
+ trim(value)
+ elsif value.is_a?(Array)
+ strip_array(value)
+ elsif value
+ chomp_each(value)
+ end
+ end
+ end
+
+ def self.dump(file, data)
+ File.open(file, 'w') { |io| io.puts YAML.dump(data) }
+ end
+
+ def self.strip_array(value)
+ value.each { |str| trim(str) if str }
+ end
+
+ def self.trim(str)
+ str.sub!(/\A\n+/, '')
+ ended_with_space_after_colon = str =~ /: \s*\Z/
+ str.rstrip!
+ str << ' ' if ended_with_space_after_colon
+ end
+end
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000000..1571492561e
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2338 @@
+{
+ "name": "upaya",
+ "version": "0.0.1",
+ "lockfileVersion": 1,
+ "dependencies": {
+ "acorn": {
+ "version": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
+ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
+ "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
+ "dev": true,
+ "dependencies": {
+ "acorn": {
+ "version": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+ "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
+ "dev": true
+ }
+ }
+ },
+ "ajv": {
+ "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+ "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+ "dev": true,
+ "dependencies": {
+ "json-stable-stringify": {
+ "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+ "dev": true
+ }
+ }
+ },
+ "ajv-keywords": {
+ "version": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz",
+ "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=",
+ "dev": true
+ },
+ "amdefine": {
+ "version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+ "dev": true,
+ "optional": true
+ },
+ "ansi-escapes": {
+ "version": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
+ "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "app": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/app/-/app-0.1.0.tgz",
+ "integrity": "sha1-eT4S9R98zgkiwjFlJhVDeGp0f+o="
+ },
+ "app-client": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/app-client/-/app-client-0.1.0.tgz",
+ "integrity": "sha1-K1pOexcCqmX92MnOZoN4o1hgcJ0="
+ },
+ "argparse": {
+ "version": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
+ "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
+ "dev": true
+ },
+ "array-filter": {
+ "version": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz",
+ "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=",
+ "dev": true
+ },
+ "array-map": {
+ "version": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
+ "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
+ "dev": true
+ },
+ "array-reduce": {
+ "version": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz",
+ "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=",
+ "dev": true
+ },
+ "array-union": {
+ "version": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+ "dev": true
+ },
+ "array-uniq": {
+ "version": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+ "dev": true
+ },
+ "arrify": {
+ "version": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+ "dev": true
+ },
+ "asn1.js": {
+ "version": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz",
+ "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=",
+ "dev": true
+ },
+ "assert": {
+ "version": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
+ "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
+ "dev": true
+ },
+ "assertion-error": {
+ "version": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
+ "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=",
+ "dev": true
+ },
+ "astw": {
+ "version": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz",
+ "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=",
+ "dev": true
+ },
+ "async": {
+ "version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+ "dev": true
+ },
+ "babel-code-frame": {
+ "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz",
+ "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=",
+ "dev": true
+ },
+ "babel-core": {
+ "version": "https://registry.npmjs.org/babel-core/-/babel-core-6.24.1.tgz",
+ "integrity": "sha1-jEKFZNzh4fQfszfsNPTDsCK1rYM=",
+ "dev": true
+ },
+ "babel-eslint": {
+ "version": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz",
+ "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=",
+ "dev": true
+ },
+ "babel-generator": {
+ "version": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.24.1.tgz",
+ "integrity": "sha1-5xX0hsWN7SVknYiJRNUqoHxdlJc=",
+ "dev": true,
+ "dependencies": {
+ "jsesc": {
+ "version": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+ "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
+ "dev": true
+ }
+ }
+ },
+ "babel-helper-call-delegate": {
+ "version": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz",
+ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=",
+ "dev": true
+ },
+ "babel-helper-define-map": {
+ "version": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz",
+ "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=",
+ "dev": true
+ },
+ "babel-helper-function-name": {
+ "version": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
+ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
+ "dev": true
+ },
+ "babel-helper-get-function-arity": {
+ "version": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
+ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
+ "dev": true
+ },
+ "babel-helper-hoist-variables": {
+ "version": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz",
+ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
+ "dev": true
+ },
+ "babel-helper-optimise-call-expression": {
+ "version": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz",
+ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
+ "dev": true
+ },
+ "babel-helper-regex": {
+ "version": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz",
+ "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=",
+ "dev": true
+ },
+ "babel-helper-replace-supers": {
+ "version": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz",
+ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=",
+ "dev": true
+ },
+ "babel-helpers": {
+ "version": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
+ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=",
+ "dev": true
+ },
+ "babel-messages": {
+ "version": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+ "dev": true
+ },
+ "babel-plugin-check-es2015-constants": {
+ "version": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
+ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-arrow-functions": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
+ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-block-scoped-functions": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz",
+ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-block-scoping": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz",
+ "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-classes": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz",
+ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-computed-properties": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz",
+ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-destructuring": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz",
+ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-duplicate-keys": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz",
+ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-for-of": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz",
+ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-function-name": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz",
+ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-literals": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz",
+ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-modules-amd": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz",
+ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-modules-commonjs": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz",
+ "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-modules-systemjs": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz",
+ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-modules-umd": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz",
+ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-object-super": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz",
+ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-parameters": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz",
+ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-shorthand-properties": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz",
+ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-spread": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz",
+ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-sticky-regex": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz",
+ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-template-literals": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz",
+ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-typeof-symbol": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz",
+ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=",
+ "dev": true
+ },
+ "babel-plugin-transform-es2015-unicode-regex": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz",
+ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=",
+ "dev": true
+ },
+ "babel-plugin-transform-regenerator": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz",
+ "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=",
+ "dev": true
+ },
+ "babel-plugin-transform-strict-mode": {
+ "version": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
+ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
+ "dev": true
+ },
+ "babel-preset-es2015": {
+ "version": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz",
+ "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=",
+ "dev": true
+ },
+ "babel-register": {
+ "version": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz",
+ "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=",
+ "dev": true
+ },
+ "babel-runtime": {
+ "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
+ "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=",
+ "dev": true
+ },
+ "babel-template": {
+ "version": "https://registry.npmjs.org/babel-template/-/babel-template-6.24.1.tgz",
+ "integrity": "sha1-BK5RTx+Ts6JTfyoPYKWkX7gwgzM=",
+ "dev": true
+ },
+ "babel-traverse": {
+ "version": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.24.1.tgz",
+ "integrity": "sha1-qzZnP9NW+aCUhlnnszjV/q2zFpU=",
+ "dev": true
+ },
+ "babel-types": {
+ "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.24.1.tgz",
+ "integrity": "sha1-oTaHncFbNga9oNkMH8dDBML/CXU=",
+ "dev": true
+ },
+ "babelify": {
+ "version": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
+ "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
+ "dev": true
+ },
+ "babylon": {
+ "version": "https://registry.npmjs.org/babylon/-/babylon-6.17.2.tgz",
+ "integrity": "sha1-IB0l71+JLEG65JSIsI2w3Udun1w=",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+ "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=",
+ "dev": true
+ },
+ "base64-js": {
+ "version": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz",
+ "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=",
+ "dev": true
+ },
+ "basscss": {
+ "version": "https://registry.npmjs.org/basscss/-/basscss-7.1.1.tgz",
+ "integrity": "sha1-e/MSAxl6Kd8O7lUfQlYXqL58d3s="
+ },
+ "basscss-align": {
+ "version": "https://registry.npmjs.org/basscss-align/-/basscss-align-1.0.2.tgz",
+ "integrity": "sha1-KUqmidb5nahuSvTFwokocIVcHDc="
+ },
+ "basscss-background-colors": {
+ "version": "https://registry.npmjs.org/basscss-background-colors/-/basscss-background-colors-1.1.3.tgz",
+ "integrity": "sha1-VKKDZRxAklZTJKTvW8JdcL52IdY="
+ },
+ "basscss-base-forms": {
+ "version": "https://registry.npmjs.org/basscss-base-forms/-/basscss-base-forms-2.0.2.tgz",
+ "integrity": "sha1-Fgi5n/SG3WuJEZLKjSXi7VdYWao="
+ },
+ "basscss-base-reset": {
+ "version": "https://registry.npmjs.org/basscss-base-reset/-/basscss-base-reset-2.0.3.tgz",
+ "integrity": "sha1-WScWF55JfgtOfJrdHxxMNAF8Vy0="
+ },
+ "basscss-base-tables": {
+ "version": "https://registry.npmjs.org/basscss-base-tables/-/basscss-base-tables-1.0.2.tgz",
+ "integrity": "sha1-uFDqHWSwb5GSK/z0AUuqoINquzA="
+ },
+ "basscss-base-typography": {
+ "version": "https://registry.npmjs.org/basscss-base-typography/-/basscss-base-typography-2.0.3.tgz",
+ "integrity": "sha1-H0vzRXEkgoII9oa8OFzTMK5+enI="
+ },
+ "basscss-border": {
+ "version": "https://registry.npmjs.org/basscss-border/-/basscss-border-3.0.4.tgz",
+ "integrity": "sha1-ZZk1aNoIZ+t12Wtwn5fjOyuQJIY="
+ },
+ "basscss-border-colors": {
+ "version": "https://registry.npmjs.org/basscss-border-colors/-/basscss-border-colors-1.1.3.tgz",
+ "integrity": "sha1-nrIya0eeqpe/m9bE7rwf2CTYCf4="
+ },
+ "basscss-borders": {
+ "version": "https://registry.npmjs.org/basscss-borders/-/basscss-borders-2.0.5.tgz",
+ "integrity": "sha1-PYRw+6kOzoknBeGlskwna3Oe644="
+ },
+ "basscss-btn": {
+ "version": "https://registry.npmjs.org/basscss-btn/-/basscss-btn-1.1.1.tgz",
+ "integrity": "sha1-xCFX8gG9lduaJRVoxULnQLKj178="
+ },
+ "basscss-btn-outline": {
+ "version": "https://registry.npmjs.org/basscss-btn-outline/-/basscss-btn-outline-1.1.0.tgz",
+ "integrity": "sha1-uEROqdPVCM0Adgqdb9/0uvXUJ1g="
+ },
+ "basscss-btn-primary": {
+ "version": "https://registry.npmjs.org/basscss-btn-primary/-/basscss-btn-primary-1.1.0.tgz",
+ "integrity": "sha1-DBJJKXHiFuQr3xNEEa/DgCw9i/c="
+ },
+ "basscss-color-base": {
+ "version": "https://registry.npmjs.org/basscss-color-base/-/basscss-color-base-2.0.2.tgz",
+ "integrity": "sha1-7YSL/OORq1NabRSnAqFPpMTzJC0="
+ },
+ "basscss-color-forms": {
+ "version": "https://registry.npmjs.org/basscss-color-forms/-/basscss-color-forms-3.0.2.tgz",
+ "integrity": "sha1-jy0dB9X8tmRVbNNUvvmM67LV4Ms="
+ },
+ "basscss-color-tables": {
+ "version": "https://registry.npmjs.org/basscss-color-tables/-/basscss-color-tables-1.0.4.tgz",
+ "integrity": "sha1-1DXsfF8hD6F959G4gT3rvKalQ+E="
+ },
+ "basscss-colors": {
+ "version": "https://registry.npmjs.org/basscss-colors/-/basscss-colors-2.2.0.tgz",
+ "integrity": "sha1-3Mt3Picu/kXfSkgJYsi9iLams+E=",
+ "dependencies": {
+ "colors.css": {
+ "version": "https://registry.npmjs.org/colors.css/-/colors.css-3.0.0.tgz",
+ "integrity": "sha1-URz0L7inGZqMvvSciKTqTx2Pnvw="
+ }
+ }
+ },
+ "basscss-defaults": {
+ "version": "https://registry.npmjs.org/basscss-defaults/-/basscss-defaults-2.1.3.tgz",
+ "integrity": "sha1-tOpjToFcaSPwx2ZbFIpMyLO0OSc="
+ },
+ "basscss-grid": {
+ "version": "https://registry.npmjs.org/basscss-grid/-/basscss-grid-1.0.6.tgz",
+ "integrity": "sha1-GlEsc7h0MwXkejanQyqtXCbMKGc="
+ },
+ "basscss-layout": {
+ "version": "https://registry.npmjs.org/basscss-layout/-/basscss-layout-3.1.0.tgz",
+ "integrity": "sha1-+fOS5IDaZmV9n+XenKTAfFecOk4="
+ },
+ "basscss-margin": {
+ "version": "https://registry.npmjs.org/basscss-margin/-/basscss-margin-1.0.7.tgz",
+ "integrity": "sha1-WpLYzamO85HHOhXt6Xs0tIiGQXw="
+ },
+ "basscss-padding": {
+ "version": "https://registry.npmjs.org/basscss-padding/-/basscss-padding-1.1.3.tgz",
+ "integrity": "sha1-adt5lBTm3Vi+2Dd2lSzCmeLmh04="
+ },
+ "basscss-position": {
+ "version": "https://registry.npmjs.org/basscss-position/-/basscss-position-2.0.3.tgz",
+ "integrity": "sha1-RnGAofjzhukHLtjQgpTSpuC6QwU="
+ },
+ "basscss-positions": {
+ "version": "https://registry.npmjs.org/basscss-positions/-/basscss-positions-1.0.5.tgz",
+ "integrity": "sha1-5P37bQMc8ljGERf5M3Hzq7uTiBo="
+ },
+ "basscss-responsive-states": {
+ "version": "https://registry.npmjs.org/basscss-responsive-states/-/basscss-responsive-states-1.0.6.tgz",
+ "integrity": "sha1-2JI0PheZiFwD5PHHAs18GrUo8AI="
+ },
+ "basscss-sass": {
+ "version": "https://registry.npmjs.org/basscss-sass/-/basscss-sass-3.0.0.tgz",
+ "integrity": "sha1-nxvoX6jqafmUQVN2ImjEavMLxM0="
+ },
+ "basscss-type-scale": {
+ "version": "https://registry.npmjs.org/basscss-type-scale/-/basscss-type-scale-1.0.5.tgz",
+ "integrity": "sha1-I79eQcnRQsgGHPmCnM8j6bMljsc="
+ },
+ "basscss-typography": {
+ "version": "https://registry.npmjs.org/basscss-typography/-/basscss-typography-3.0.3.tgz",
+ "integrity": "sha1-GCz0PffE6+0CdQ3HSAQcut/2DUM="
+ },
+ "bluebird": {
+ "version": "2.10.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz",
+ "integrity": "sha1-AkpVFylTCIV/FPkfEQb8O1VfRGs="
+ },
+ "bn.js": {
+ "version": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
+ "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
+ "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=",
+ "dev": true
+ },
+ "brorand": {
+ "version": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+ "dev": true
+ },
+ "browser-pack": {
+ "version": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.2.tgz",
+ "integrity": "sha1-+GzWzvT1MAyOY+B6TVEvZfv/RTE=",
+ "dev": true
+ },
+ "browser-resolve": {
+ "version": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz",
+ "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=",
+ "dev": true,
+ "dependencies": {
+ "resolve": {
+ "version": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+ "dev": true
+ }
+ }
+ },
+ "browserify": {
+ "version": "https://registry.npmjs.org/browserify/-/browserify-13.3.0.tgz",
+ "integrity": "sha1-tanJAgJD8McORnW+yCI7xifkFc4=",
+ "dev": true
+ },
+ "browserify-aes": {
+ "version": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.6.tgz",
+ "integrity": "sha1-Xncl297x/Vkw1OurSFZ85FHEigo=",
+ "dev": true
+ },
+ "browserify-cache-api": {
+ "version": "https://registry.npmjs.org/browserify-cache-api/-/browserify-cache-api-3.0.1.tgz",
+ "integrity": "sha1-liR+hT8Gj9bg1FzHPwuyzZd47wI=",
+ "dev": true
+ },
+ "browserify-cipher": {
+ "version": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz",
+ "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=",
+ "dev": true
+ },
+ "browserify-des": {
+ "version": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz",
+ "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=",
+ "dev": true
+ },
+ "browserify-incremental": {
+ "version": "https://registry.npmjs.org/browserify-incremental/-/browserify-incremental-3.1.1.tgz",
+ "integrity": "sha1-BxPLdYckemMqnwjPG9FpuHi2Koo=",
+ "dev": true,
+ "dependencies": {
+ "jsonparse": {
+ "version": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz",
+ "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=",
+ "dev": true
+ },
+ "JSONStream": {
+ "version": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz",
+ "integrity": "sha1-dDSdDYlSK3HzDwoD/5vSDKbxKsA=",
+ "dev": true
+ }
+ }
+ },
+ "browserify-rsa": {
+ "version": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "dev": true
+ },
+ "browserify-sign": {
+ "version": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+ "dev": true
+ },
+ "browserify-zlib": {
+ "version": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
+ "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=",
+ "dev": true
+ },
+ "bson": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz",
+ "integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw="
+ },
+ "buffer": {
+ "version": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
+ "dev": true
+ },
+ "buffer-shims": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
+ "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E="
+ },
+ "buffer-xor": {
+ "version": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+ "dev": true
+ },
+ "builtin-status-codes": {
+ "version": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+ "dev": true
+ },
+ "cached-path-relative": {
+ "version": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz",
+ "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=",
+ "dev": true
+ },
+ "caller-path": {
+ "version": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
+ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
+ "dev": true
+ },
+ "callsites": {
+ "version": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
+ "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
+ "dev": true
+ },
+ "chai": {
+ "version": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz",
+ "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true
+ },
+ "cipher-base": {
+ "version": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.3.tgz",
+ "integrity": "sha1-7qvxlEGc6QDaMBjCB9IS8qbfCgc=",
+ "dev": true
+ },
+ "circular-json": {
+ "version": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz",
+ "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=",
+ "dev": true
+ },
+ "classlist.js": {
+ "version": "https://registry.npmjs.org/classlist.js/-/classlist.js-1.1.20150312.tgz",
+ "integrity": "sha1-HXCEL3Ai8I2awIbOaeWyUPLFd4k="
+ },
+ "cli-cursor": {
+ "version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
+ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
+ "dev": true
+ },
+ "cli-width": {
+ "version": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz",
+ "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=",
+ "dev": true
+ },
+ "clipboard": {
+ "version": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
+ "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs="
+ },
+ "co": {
+ "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+ "dev": true
+ },
+ "code-point-at": {
+ "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "colors.css": {
+ "version": "https://registry.npmjs.org/colors.css/-/colors.css-2.3.0.tgz",
+ "integrity": "sha1-6JU4N1Q+GdmOKRf/C5mPbbKGITs="
+ },
+ "combine-source-map": {
+ "version": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz",
+ "integrity": "sha1-CHAxKFazB6h8xKxIbzqaYq7MwJ4=",
+ "dev": true,
+ "dependencies": {
+ "convert-source-map": {
+ "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
+ "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
+ "dev": true
+ }
+ }
+ },
+ "commander": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+ "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q="
+ },
+ "concat-map": {
+ "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "concat-stream": {
+ "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz",
+ "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": {
+ "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
+ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
+ "dev": true
+ }
+ }
+ },
+ "connect": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.2.tgz",
+ "integrity": "sha1-aU6NIGgb/kkCgsiriGvpjwn0L+c=",
+ "dependencies": {
+ "debug": {
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
+ "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+ }
+ }
+ },
+ "console-browserify": {
+ "version": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+ "dev": true
+ },
+ "constants-browserify": {
+ "version": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+ "dev": true
+ },
+ "contains-path": {
+ "version": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz",
+ "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=",
+ "dev": true
+ },
+ "core-js": {
+ "version": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
+ "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=",
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "cornerstone": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/cornerstone/-/cornerstone-0.1.1.tgz",
+ "integrity": "sha1-D4mxPUZVrgAEgUZpOWwZUqnXjt0="
+ },
+ "create-ecdh": {
+ "version": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz",
+ "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=",
+ "dev": true
+ },
+ "create-hash": {
+ "version": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
+ "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=",
+ "dev": true
+ },
+ "create-hmac": {
+ "version": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz",
+ "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=",
+ "dev": true
+ },
+ "crypto-browserify": {
+ "version": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.0.tgz",
+ "integrity": "sha1-NlKgkGq5sqfgw85mpAjpV6JIVSI=",
+ "dev": true
+ },
+ "d": {
+ "version": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
+ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
+ "dev": true
+ },
+ "date-now": {
+ "version": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+ "dev": true
+ },
+ "debug": {
+ "version": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+ "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw="
+ },
+ "deep-eql": {
+ "version": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz",
+ "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=",
+ "dev": true,
+ "dependencies": {
+ "type-detect": {
+ "version": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz",
+ "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=",
+ "dev": true
+ }
+ }
+ },
+ "deep-is": {
+ "version": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "defined": {
+ "version": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
+ "dev": true
+ },
+ "del": {
+ "version": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
+ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
+ "dev": true
+ },
+ "delegate": {
+ "version": "https://registry.npmjs.org/delegate/-/delegate-3.1.3.tgz",
+ "integrity": "sha1-moJRp3fXAl+qVXN7w7BxdCEnqf0="
+ },
+ "deps-sort": {
+ "version": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz",
+ "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=",
+ "dev": true
+ },
+ "des.js": {
+ "version": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
+ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
+ "dev": true
+ },
+ "detect-indent": {
+ "version": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
+ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
+ "dev": true
+ },
+ "detective": {
+ "version": "https://registry.npmjs.org/detective/-/detective-4.5.0.tgz",
+ "integrity": "sha1-blqMaybmx6JUsca210kNmOyR7dE=",
+ "dev": true
+ },
+ "diffie-hellman": {
+ "version": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz",
+ "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=",
+ "dev": true
+ },
+ "dirty-chai": {
+ "version": "https://registry.npmjs.org/dirty-chai/-/dirty-chai-1.2.2.tgz",
+ "integrity": "sha1-eEleYZY19/5EIZqkyDeEm/GDFC4=",
+ "dev": true
+ },
+ "doctrine": {
+ "version": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
+ "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
+ "dev": true
+ },
+ "domain-browser": {
+ "version": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz",
+ "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=",
+ "dev": true
+ },
+ "duplexer2": {
+ "version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+ "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
+ "dev": true
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ },
+ "elliptic": {
+ "version": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
+ "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
+ "dev": true
+ },
+ "encodeurl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
+ "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
+ },
+ "error-ex": {
+ "version": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
+ "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
+ "dev": true
+ },
+ "es5-ext": {
+ "version": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.22.tgz",
+ "integrity": "sha1-GHbFH5kHacESx4HqPr6J+E/TkHE=",
+ "dev": true
+ },
+ "es6-iterator": {
+ "version": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz",
+ "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
+ "dev": true
+ },
+ "es6-map": {
+ "version": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
+ "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
+ "dev": true
+ },
+ "es6-promise": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
+ "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q="
+ },
+ "es6-set": {
+ "version": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
+ "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
+ "dev": true
+ },
+ "es6-symbol": {
+ "version": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
+ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
+ "dev": true
+ },
+ "es6-weak-map": {
+ "version": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
+ "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
+ "dev": true
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+ },
+ "escape-string-regexp": {
+ "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "escodegen": {
+ "version": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz",
+ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=",
+ "dev": true,
+ "dependencies": {
+ "esprima": {
+ "version": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
+ "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
+ "dev": true
+ },
+ "estraverse": {
+ "version": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
+ "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=",
+ "dev": true
+ },
+ "source-map": {
+ "version": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+ "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "escope": {
+ "version": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
+ "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
+ "dev": true
+ },
+ "eslint": {
+ "version": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz",
+ "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=",
+ "dev": true,
+ "dependencies": {
+ "json-stable-stringify": {
+ "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+ "dev": true
+ }
+ }
+ },
+ "eslint-config-airbnb-base": {
+ "version": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.2.0.tgz",
+ "integrity": "sha1-GancRIGib3CQRUXsBAEWh2AY+FM=",
+ "dev": true
+ },
+ "eslint-import-resolver-node": {
+ "version": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz",
+ "integrity": "sha1-Wt2BBujJKNssuiMrzZ76hG49oWw=",
+ "dev": true
+ },
+ "eslint-module-utils": {
+ "version": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.0.0.tgz",
+ "integrity": "sha1-pvjCHZATWHWc3DXbrBmCrh7li84=",
+ "dev": true,
+ "dependencies": {
+ "debug": {
+ "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+ "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+ "dev": true
+ },
+ "ms": {
+ "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-import": {
+ "version": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.3.0.tgz",
+ "integrity": "sha1-N8gB4K2g4pbL3yDD85OstbUq82s=",
+ "dev": true,
+ "dependencies": {
+ "doctrine": {
+ "version": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+ "dev": true
+ }
+ }
+ },
+ "espree": {
+ "version": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
+ "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=",
+ "dev": true,
+ "dependencies": {
+ "acorn": {
+ "version": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
+ "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
+ "dev": true
+ }
+ }
+ },
+ "esprima": {
+ "version": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+ "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
+ "dev": true
+ },
+ "esquery": {
+ "version": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
+ "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
+ "dev": true
+ },
+ "esrecurse": {
+ "version": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz",
+ "integrity": "sha1-RxO2U2rffyrE8yfVWed1a/9kgiA=",
+ "dev": true,
+ "dependencies": {
+ "estraverse": {
+ "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz",
+ "integrity": "sha1-9srKcokzqFDvkGYdDheYK6RxEaI=",
+ "dev": true
+ }
+ }
+ },
+ "estraverse": {
+ "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
+ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
+ "dev": true
+ },
+ "esutils": {
+ "version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "dev": true
+ },
+ "event-emitter": {
+ "version": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
+ "dev": true
+ },
+ "events": {
+ "version": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
+ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
+ "dev": true
+ },
+ "evp_bytestokey": {
+ "version": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz",
+ "integrity": "sha1-SXtmrZ/vZc18CKYYCCS6FHa2blM=",
+ "dev": true
+ },
+ "exit-hook": {
+ "version": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
+ "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "field-kit": {
+ "version": "https://registry.npmjs.org/field-kit/-/field-kit-2.1.0.tgz",
+ "integrity": "sha1-5o7eX04wUbLcQlgQWklcVBo7LX8="
+ },
+ "figures": {
+ "version": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+ "dev": true
+ },
+ "file-entry-cache": {
+ "version": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
+ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
+ "dev": true
+ },
+ "fill-keys": {
+ "version": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz",
+ "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=",
+ "dev": true
+ },
+ "finalhandler": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz",
+ "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=",
+ "dependencies": {
+ "debug": {
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
+ "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+ }
+ }
+ },
+ "find-up": {
+ "version": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "dev": true
+ },
+ "flat-cache": {
+ "version": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz",
+ "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=",
+ "dev": true
+ },
+ "flex-object": {
+ "version": "https://registry.npmjs.org/flex-object/-/flex-object-2.0.5.tgz",
+ "integrity": "sha1-Ebm7wPT4ZOncYH6YzixnBEK9yFE="
+ },
+ "focus-trap": {
+ "version": "https://registry.npmjs.org/focus-trap/-/focus-trap-2.3.0.tgz",
+ "integrity": "sha1-B8kZZIZ9NGMV9PX434i/lkVTFuI="
+ },
+ "formatio": {
+ "version": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz",
+ "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek="
+ },
+ "fs.realpath": {
+ "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "function-bind": {
+ "version": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz",
+ "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=",
+ "dev": true
+ },
+ "generate-function": {
+ "version": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+ "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
+ "dev": true
+ },
+ "generate-object-property": {
+ "version": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+ "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+ "dev": true
+ },
+ "glob": {
+ "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
+ "dev": true
+ },
+ "globals": {
+ "version": "https://registry.npmjs.org/globals/-/globals-9.17.0.tgz",
+ "integrity": "sha1-DAymltm5u2lNLlRwvTd3fKrVAoY=",
+ "dev": true
+ },
+ "globby": {
+ "version": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
+ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
+ "dev": true
+ },
+ "good-listener": {
+ "version": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+ "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA="
+ },
+ "graceful-fs": {
+ "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+ "dev": true
+ },
+ "graceful-readlink": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
+ },
+ "has": {
+ "version": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
+ "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
+ "dev": true
+ },
+ "has-ansi": {
+ "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "dev": true
+ },
+ "has-require": {
+ "version": "https://registry.npmjs.org/has-require/-/has-require-1.2.2.tgz",
+ "integrity": "sha1-khZ1qxMNvZdo/I2o8ajiQt+kF3Q=",
+ "dev": true
+ },
+ "hash-base": {
+ "version": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
+ "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=",
+ "dev": true
+ },
+ "hash.js": {
+ "version": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
+ "integrity": "sha1-EzL/ABVsCg/92CNgE9B7d6BFFXM=",
+ "dev": true
+ },
+ "hint.css": {
+ "version": "https://registry.npmjs.org/hint.css/-/hint.css-2.5.0.tgz",
+ "integrity": "sha1-OMrjZn5C2R392+UDEAqzSTL2/WU="
+ },
+ "hmac-drbg": {
+ "version": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+ "dev": true
+ },
+ "home-or-tmp": {
+ "version": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
+ "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=",
+ "dev": true
+ },
+ "hooks-fixed": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-2.0.0.tgz",
+ "integrity": "sha1-oB2JTVKsf2WZu7H2PfycQR33DLo="
+ },
+ "hosted-git-info": {
+ "version": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz",
+ "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=",
+ "dev": true
+ },
+ "htmlescape": {
+ "version": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz",
+ "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=",
+ "dev": true
+ },
+ "https-browserify": {
+ "version": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz",
+ "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=",
+ "dev": true
+ },
+ "ieee754": {
+ "version": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
+ "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=",
+ "dev": true
+ },
+ "ignore": {
+ "version": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz",
+ "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=",
+ "dev": true
+ },
+ "imurmurhash": {
+ "version": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "indexof": {
+ "version": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true
+ },
+ "inherits": {
+ "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+ },
+ "inline-source-map": {
+ "version": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz",
+ "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=",
+ "dev": true
+ },
+ "input-sim": {
+ "version": "https://registry.npmjs.org/input-sim/-/input-sim-3.1.0.tgz",
+ "integrity": "sha1-g/nCFPTW2MjpCU00V5eVkeqIzCw="
+ },
+ "inquirer": {
+ "version": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
+ "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=",
+ "dev": true
+ },
+ "insert-module-globals": {
+ "version": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz",
+ "integrity": "sha1-wDv04BywhtW15azorQr+eInWOMM=",
+ "dev": true
+ },
+ "interpret": {
+ "version": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz",
+ "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=",
+ "dev": true
+ },
+ "invariant": {
+ "version": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
+ "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
+ "dev": true
+ },
+ "is-arrayish": {
+ "version": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-buffer": {
+ "version": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
+ "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
+ "dev": true
+ },
+ "is-builtin-module": {
+ "version": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+ "dev": true
+ },
+ "is-finite": {
+ "version": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true
+ },
+ "is-my-json-valid": {
+ "version": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz",
+ "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=",
+ "dev": true
+ },
+ "is-object": {
+ "version": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz",
+ "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=",
+ "dev": true
+ },
+ "is-path-cwd": {
+ "version": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
+ "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+ "dev": true
+ },
+ "is-path-in-cwd": {
+ "version": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
+ "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
+ "dev": true
+ },
+ "is-path-inside": {
+ "version": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
+ "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=",
+ "dev": true
+ },
+ "is-property": {
+ "version": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
+ "dev": true
+ },
+ "is-resolvable": {
+ "version": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
+ "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "js-tokens": {
+ "version": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz",
+ "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz",
+ "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=",
+ "dev": true
+ },
+ "jsesc": {
+ "version": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+ "dev": true
+ },
+ "json-stable-stringify": {
+ "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz",
+ "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=",
+ "dev": true
+ },
+ "json5": {
+ "version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
+ "dev": true
+ },
+ "jsonify": {
+ "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+ "dev": true
+ },
+ "jsonparse": {
+ "version": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
+ "dev": true
+ },
+ "jsonpointer": {
+ "version": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
+ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
+ "dev": true
+ },
+ "JSONStream": {
+ "version": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz",
+ "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=",
+ "dev": true
+ },
+ "kareem": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.4.2.tgz",
+ "integrity": "sha1-O0r12/rzrBwIuOVRj92BupDCq3I="
+ },
+ "labeled-stream-splicer": {
+ "version": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz",
+ "integrity": "sha1-pS4dE4AkwAuGscDJH2d5GLiuClk=",
+ "dev": true,
+ "dependencies": {
+ "isarray": {
+ "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ }
+ }
+ },
+ "levn": {
+ "version": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "dev": true
+ },
+ "lexical-scope": {
+ "version": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz",
+ "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=",
+ "dev": true
+ },
+ "load-json-file": {
+ "version": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+ "dev": true
+ },
+ "locate-path": {
+ "version": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "dev": true,
+ "dependencies": {
+ "path-exists": {
+ "version": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ }
+ }
+ },
+ "lodash": {
+ "version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
+ "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
+ },
+ "lodash.cond": {
+ "version": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz",
+ "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=",
+ "dev": true
+ },
+ "lodash.memoize": {
+ "version": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
+ "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=",
+ "dev": true
+ },
+ "lolex": {
+ "version": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz",
+ "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE="
+ },
+ "loose-envify": {
+ "version": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+ "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+ "dev": true
+ },
+ "merge-descriptors": {
+ "version": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
+ "dev": true
+ },
+ "miller-rabin": {
+ "version": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz",
+ "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=",
+ "dev": true
+ },
+ "mime": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz",
+ "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA="
+ },
+ "minimalistic-assert": {
+ "version": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
+ "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=",
+ "dev": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
+ "dev": true
+ },
+ "minimist": {
+ "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ },
+ "mkdirp": {
+ "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true
+ },
+ "modulator": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/modulator/-/modulator-0.1.0.tgz",
+ "integrity": "sha1-z9UVhD+R1nPxVWHZeZ33gErQMmY="
+ },
+ "module-deps": {
+ "version": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz",
+ "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=",
+ "dev": true
+ },
+ "module-not-found-error": {
+ "version": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz",
+ "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=",
+ "dev": true
+ },
+ "mongodb": {
+ "version": "2.2.27",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.27.tgz",
+ "integrity": "sha1-NBIgNNtm2YO89qta2yaiSnD+9uY=",
+ "dependencies": {
+ "readable-stream": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz",
+ "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE="
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+ },
+ "string_decoder": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ=="
+ }
+ }
+ },
+ "mongodb-core": {
+ "version": "2.1.11",
+ "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.11.tgz",
+ "integrity": "sha1-HDh3bOsXSZepnCiGDu2QKNqbPho="
+ },
+ "mongoose": {
+ "version": "4.11.3",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.11.3.tgz",
+ "integrity": "sha1-+T1CeygsLnmLD+FTL7Qafd5umNM=",
+ "dependencies": {
+ "async": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz",
+ "integrity": "sha1-LSFgx3iAMuTdbL4lAvH5osj2zeQ="
+ }
+ }
+ },
+ "mpath": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.3.0.tgz",
+ "integrity": "sha1-elj3iem1/TyUUgY0FXlg8mvV70Q="
+ },
+ "mpromise": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mpromise/-/mpromise-0.5.5.tgz",
+ "integrity": "sha1-9bJCWddjrMIlewoMjG2Gb9UXMuY="
+ },
+ "mquery": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/mquery/-/mquery-2.3.1.tgz",
+ "integrity": "sha1-mrNnSXFIAP8LtTpoHOS8TV8HyHs=",
+ "dependencies": {
+ "sliced": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz",
+ "integrity": "sha1-XtwETKTrb3gW1Qui/GPiXY/kcH8="
+ }
+ }
+ },
+ "ms": {
+ "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "muri": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/muri/-/muri-1.2.2.tgz",
+ "integrity": "sha1-YxmBMmUNsIoEzHnM0A3Tia/SYxw="
+ },
+ "mute-stream": {
+ "version": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
+ "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
+ "dev": true
+ },
+ "natural-compare": {
+ "version": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "normalize-package-data": {
+ "version": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz",
+ "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=",
+ "dev": true
+ },
+ "normalize.css": {
+ "version": "https://registry.npmjs.org/normalize.css/-/normalize.css-4.2.0.tgz",
+ "integrity": "sha1-IdZsxVcVTUN5/R4HnsfeWKN5sJk="
+ },
+ "number-is-nan": {
+ "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc="
+ },
+ "once": {
+ "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true
+ },
+ "onetime": {
+ "version": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+ "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
+ "dev": true
+ },
+ "optimist": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+ "dependencies": {
+ "wordwrap": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
+ }
+ }
+ },
+ "optionator": {
+ "version": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+ "dev": true
+ },
+ "os-browserify": {
+ "version": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz",
+ "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=",
+ "dev": true
+ },
+ "os-homedir": {
+ "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+ "dev": true
+ },
+ "os-tmpdir": {
+ "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true
+ },
+ "p-limit": {
+ "version": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz",
+ "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=",
+ "dev": true
+ },
+ "p-locate": {
+ "version": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "dev": true
+ },
+ "pako": {
+ "version": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=",
+ "dev": true
+ },
+ "parents": {
+ "version": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
+ "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=",
+ "dev": true
+ },
+ "parse-asn1": {
+ "version": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz",
+ "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=",
+ "dev": true
+ },
+ "parse-json": {
+ "version": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "dev": true
+ },
+ "parseurl": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz",
+ "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY="
+ },
+ "path-browserify": {
+ "version": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
+ "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-is-inside": {
+ "version": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
+ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
+ "dev": true
+ },
+ "path-platform": {
+ "version": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz",
+ "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=",
+ "dev": true
+ },
+ "path-type": {
+ "version": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+ "dev": true
+ },
+ "pbkdf2": {
+ "version": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz",
+ "integrity": "sha1-vjZ4XFBn6kjYBv+SMojF91C2uKI=",
+ "dev": true
+ },
+ "pff": {
+ "version": "https://registry.npmjs.org/pff/-/pff-1.0.0.tgz",
+ "integrity": "sha1-6l8J7mVxyuKSp4/CgJBaOGVmjng=",
+ "dev": true
+ },
+ "pify": {
+ "version": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ },
+ "pinkie": {
+ "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+ "dev": true
+ },
+ "pinkie-promise": {
+ "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
+ "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
+ "dev": true
+ },
+ "pluralize": {
+ "version": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz",
+ "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+ "dev": true
+ },
+ "private": {
+ "version": "https://registry.npmjs.org/private/-/private-0.1.7.tgz",
+ "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=",
+ "dev": true
+ },
+ "process": {
+ "version": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+ },
+ "progress": {
+ "version": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+ "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
+ "dev": true
+ },
+ "proxyquireify": {
+ "version": "https://registry.npmjs.org/proxyquireify/-/proxyquireify-3.2.1.tgz",
+ "integrity": "sha1-Fb7hATYKzJHc2G7k2aRF+Klx7qA=",
+ "dev": true,
+ "dependencies": {
+ "acorn": {
+ "version": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
+ "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ=",
+ "dev": true
+ },
+ "detective": {
+ "version": "https://registry.npmjs.org/detective/-/detective-4.1.1.tgz",
+ "integrity": "sha1-nEusHp+4uzT38YyuCA6h0Dr/LNo=",
+ "dev": true
+ },
+ "through": {
+ "version": "https://registry.npmjs.org/through/-/through-2.2.7.tgz",
+ "integrity": "sha1-bo4hIAGR1OtqmfbwEN9Gqhxusr0=",
+ "dev": true
+ },
+ "xtend": {
+ "version": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz",
+ "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=",
+ "dev": true
+ }
+ }
+ },
+ "public-encrypt": {
+ "version": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz",
+ "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=",
+ "dev": true
+ },
+ "punycode": {
+ "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ },
+ "querystring": {
+ "version": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "dev": true
+ },
+ "querystring-es3": {
+ "version": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+ "dev": true
+ },
+ "randombytes": {
+ "version": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.4.tgz",
+ "integrity": "sha1-lVHfIIQiyPgOtY4jJt0LhA/yLv0=",
+ "dev": true
+ },
+ "read-only-stream": {
+ "version": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
+ "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=",
+ "dev": true
+ },
+ "read-pkg": {
+ "version": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+ "dev": true
+ },
+ "read-pkg-up": {
+ "version": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+ "dev": true,
+ "dependencies": {
+ "find-up": {
+ "version": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.10.tgz",
+ "integrity": "sha1-7/5yu3yITA3TNeI3nVJhltnQEe4=",
+ "dev": true,
+ "dependencies": {
+ "string_decoder": {
+ "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz",
+ "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=",
+ "dev": true
+ }
+ }
+ },
+ "readline2": {
+ "version": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz",
+ "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=",
+ "dev": true
+ },
+ "rechoir": {
+ "version": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
+ "dev": true
+ },
+ "regenerate": {
+ "version": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz",
+ "integrity": "sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=",
+ "dev": true
+ },
+ "regenerator-runtime": {
+ "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+ "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
+ "dev": true
+ },
+ "regenerator-transform": {
+ "version": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz",
+ "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=",
+ "dev": true
+ },
+ "regexp-clone": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz",
+ "integrity": "sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk="
+ },
+ "regexpu-core": {
+ "version": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz",
+ "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=",
+ "dev": true
+ },
+ "regjsgen": {
+ "version": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+ "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+ "dev": true
+ },
+ "regjsparser": {
+ "version": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+ "dev": true
+ },
+ "repeating": {
+ "version": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+ "dev": true
+ },
+ "require_optional": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+ "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+ "dependencies": {
+ "resolve-from": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+ "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+ }
+ }
+ },
+ "require-deps": {
+ "version": "https://registry.npmjs.org/require-deps/-/require-deps-1.0.1.tgz",
+ "integrity": "sha1-JBXPScNb02pdMXc5UQjT8jcgUmM=",
+ "dev": true
+ },
+ "require-uncached": {
+ "version": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
+ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
+ "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=",
+ "dev": true
+ },
+ "resolve-from": {
+ "version": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
+ "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
+ "dev": true
+ },
+ "restore-cursor": {
+ "version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
+ "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
+ "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=",
+ "dev": true
+ },
+ "ripemd160": {
+ "version": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
+ "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=",
+ "dev": true
+ },
+ "run-async": {
+ "version": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
+ "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=",
+ "dev": true
+ },
+ "rx-lite": {
+ "version": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz",
+ "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=",
+ "dev": true
+ },
+ "safe-buffer": {
+ "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
+ "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=",
+ "dev": true
+ },
+ "samsam": {
+ "version": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz",
+ "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc="
+ },
+ "select": {
+ "version": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+ "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
+ },
+ "semver": {
+ "version": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
+ },
+ "sha.js": {
+ "version": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz",
+ "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=",
+ "dev": true
+ },
+ "shasum": {
+ "version": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
+ "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=",
+ "dev": true
+ },
+ "shell-quote": {
+ "version": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz",
+ "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=",
+ "dev": true
+ },
+ "shelljs": {
+ "version": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz",
+ "integrity": "sha1-svXHfvlxSPS09uImguELuoZnz/E=",
+ "dev": true
+ },
+ "sinon": {
+ "version": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz",
+ "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8="
+ },
+ "slash": {
+ "version": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
+ "dev": true
+ },
+ "slice-ansi": {
+ "version": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+ "dev": true
+ },
+ "sliced": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
+ "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
+ },
+ "source-map": {
+ "version": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
+ "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI="
+ },
+ "source-map-support": {
+ "version": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz",
+ "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=",
+ "dev": true
+ },
+ "spdx-correct": {
+ "version": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
+ "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz",
+ "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=",
+ "dev": true
+ },
+ "spdx-license-ids": {
+ "version": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
+ "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=",
+ "dev": true
+ },
+ "sprintf-js": {
+ "version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "statuses": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
+ "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
+ },
+ "stream-browserify": {
+ "version": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
+ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
+ "dev": true
+ },
+ "stream-combiner2": {
+ "version": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
+ "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=",
+ "dev": true
+ },
+ "stream-http": {
+ "version": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.1.tgz",
+ "integrity": "sha1-VGpRdBrVprB+njGwsQRBqRffUoo=",
+ "dev": true
+ },
+ "stream-splicer": {
+ "version": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz",
+ "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=",
+ "dev": true
+ },
+ "string_decoder": {
+ "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true
+ },
+ "strip-bom": {
+ "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true
+ },
+ "strip-json-comments": {
+ "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "dev": true
+ },
+ "stround": {
+ "version": "https://registry.npmjs.org/stround/-/stround-0.3.1.tgz",
+ "integrity": "sha1-vl8uHdf1tqFGhoJUish0YbjWZFQ="
+ },
+ "subarg": {
+ "version": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
+ "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
+ "dev": true,
+ "dependencies": {
+ "minimist": {
+ "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
+ "supports-color": {
+ "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ },
+ "syntax-error": {
+ "version": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.3.0.tgz",
+ "integrity": "sha1-HtkmbE1AvnXcVb+bsct3Biu5bKE=",
+ "dev": true
+ },
+ "tabbable": {
+ "version": "https://registry.npmjs.org/tabbable/-/tabbable-1.0.6.tgz",
+ "integrity": "sha1-fCaofqb0ol7fXtthl0WgrnQHJPw="
+ },
+ "table": {
+ "version": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
+ "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=",
+ "dev": true,
+ "dependencies": {
+ "is-fullwidth-code-point": {
+ "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz",
+ "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=",
+ "dev": true
+ }
+ }
+ },
+ "text-table": {
+ "version": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "through": {
+ "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
+ },
+ "through2": {
+ "version": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
+ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
+ "dev": true
+ },
+ "timers-browserify": {
+ "version": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
+ "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
+ "dev": true
+ },
+ "tiny-emitter": {
+ "version": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.0.tgz",
+ "integrity": "sha1-utMnrbGAS0KiMa+nQVMr2ITNCa0="
+ },
+ "to-arraybuffer": {
+ "version": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+ "dev": true
+ },
+ "to-fast-properties": {
+ "version": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
+ "dev": true
+ },
+ "trim-right": {
+ "version": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
+ "dev": true
+ },
+ "tryit": {
+ "version": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
+ "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
+ "dev": true
+ },
+ "tty-browserify": {
+ "version": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+ "dev": true
+ },
+ "type-check": {
+ "version": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "dev": true
+ },
+ "type-detect": {
+ "version": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz",
+ "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=",
+ "dev": true
+ },
+ "typedarray": {
+ "version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+ "dev": true
+ },
+ "uglify-js": {
+ "version": "3.0.25",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.25.tgz",
+ "integrity": "sha512-JO1XE0WZ9m6UpDkN7WCyPNAWI6EN3K0g40ekcoJKejViYmryJ0BaLxXjvra1IsAeIlJfq72scTbhl0jknsT2GA=="
+ },
+ "umd": {
+ "version": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz",
+ "integrity": "sha1-iuVW4RAR9jwllnCKiDclnwGz1g4=",
+ "dev": true
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ },
+ "url": {
+ "version": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dev": true,
+ "dependencies": {
+ "punycode": {
+ "version": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+ "dev": true
+ }
+ }
+ },
+ "user-home": {
+ "version": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz",
+ "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=",
+ "dev": true
+ },
+ "util": {
+ "version": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk="
+ },
+ "util-deprecate": {
+ "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "utils-merge": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
+ "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg="
+ },
+ "validate-npm-package-license": {
+ "version": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+ "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
+ "dev": true
+ },
+ "vm-browserify": {
+ "version": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
+ "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
+ "dev": true
+ },
+ "wordwrap": {
+ "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+ "dev": true
+ },
+ "wrappy": {
+ "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "write": {
+ "version": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
+ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
+ "dev": true
+ },
+ "xtend": {
+ "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+ "dev": true
+ },
+ "zxcvbn": {
+ "version": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
+ "integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA="
+ }
+ }
+}
diff --git a/package.json b/package.json
index eb1a5e66334..5e216e62d1e 100644
--- a/package.json
+++ b/package.json
@@ -7,12 +7,14 @@
"npm": "~3.8.x"
},
"dependencies": {
+ "app": "^0.1.0",
"basscss-sass": "^3.0.0",
"classlist.js": "^1.1.20150312",
"clipboard": "^1.6.1",
"field-kit": "^2.1.0",
"focus-trap": "^2.3.0",
"hint.css": "^2.3.2",
+ "libphonenumber-js": "^0.4.23",
"normalize.css": "^4.2.0",
"sinon": "^1.17.7",
"zxcvbn": "^4.4.2"
diff --git a/scripts/i18n-csv-to-yml b/scripts/i18n-csv-to-yml
new file mode 100755
index 00000000000..b92be1bffc4
--- /dev/null
+++ b/scripts/i18n-csv-to-yml
@@ -0,0 +1,78 @@
+#!/usr/bin/env ruby
+require 'optparse'
+require 'csv'
+require 'yaml'
+require 'fileutils'
+
+Config = Struct.new(:out_dir, :out_locale, :in_csv, :separate_files, :dry_run)
+
+config = Config.new
+
+parser = OptionParser.new do |opts|
+ opts.banner = "Usage: #{$PROGRAM_NAME} [OPTIONS]"
+
+ opts.on('--out-dir=OUT_DIR', 'The directory to write to') do |out_dir|
+ config.out_dir = out_dir
+ end
+
+ opts.on('--out-locale=OUT_LOCALE', 'The locale to export') do |out_locale|
+ config.out_locale = out_locale.downcase
+ end
+
+ opts.on('--in-csv=IN_CSV', 'The CSV to read translations from') do |in_csv|
+ config.in_csv = in_csv
+ end
+
+ opts.on('--separate', 'Write separate .yml files per top-level key') do
+ config.separate_files = true
+ end
+
+ opts.on('--dry-run', 'Do not write to the filesystem') do
+ config.dry_run = true
+ end
+
+ opts.on('--help', '-h', 'Show this message') do
+ puts opts
+ end
+end
+
+parser.parse(ARGV)
+
+if !config.out_dir || !config.out_locale || !config.in_csv
+ puts parser
+ exit 1
+end
+
+# hash where keys always initialize to a empty hashes (with the same property)
+hash = ->(f) { f[f] }[->(f) { Hash.new { |h, k| h[k] = f[f] } }]
+
+csv = CSV.read(config.in_csv, headers: true)
+
+def set_nested(hash, key, value)
+ *parts, last = key.split('.')
+ parts.each do |part|
+ hash = hash[part]
+ end
+ hash[last] = value
+end
+
+csv.each do |row|
+ set_nested(hash, row['Key'], row[config.out_locale.upcase])
+end
+
+if config.separate_files
+ hash.each do |top_level_key, values|
+ path = File.join(config.out_dir, top_level_key, "#{config.out_locale}.yml")
+ puts path
+
+ content = { config.out_locale => { top_level_key => values } }
+ FileUtils.mkdir_p(File.dirname(path))
+ File.open(path, 'w') { |f| f.puts content.to_yaml } unless config.dry_run
+ end
+else
+ path = File.join(config.out_dir, "#{config.out_locale}.yml")
+ puts path
+
+ content = { config.out_locale => hash }
+ File.open(path, 'w') { |f| f.puts content.to_yaml } unless config.dry_run
+end
diff --git a/scripts/normalize-yaml b/scripts/normalize-yaml
new file mode 100755
index 00000000000..8a37da93dd4
--- /dev/null
+++ b/scripts/normalize-yaml
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+$LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
+require 'yaml_normalizer'
+require 'yaml'
+
+YamlNormalizer.run(ARGV)
diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb
index f6fe4d29e6d..025cf9ed02a 100644
--- a/spec/controllers/saml_idp_controller_spec.rb
+++ b/spec/controllers/saml_idp_controller_spec.rb
@@ -306,8 +306,8 @@
saml_get_auth(saml_settings)
end
- it 'deletes SP metadata from session' do
- expect(session.key?(:sp)).to eq(false)
+ it 'does not delete SP metadata from session' do
+ expect(session.key?(:sp)).to eq(true)
end
end
end
diff --git a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb
index f7c3b1afcf2..27cd54733b8 100644
--- a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb
+++ b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb
@@ -380,6 +380,10 @@
expect(subject.user_session[:idv][:params]['phone_confirmed_at']).to_not be_nil
end
+ it 'updates idv session user_phone_confirmation attributes' do
+ expect(subject.user_session[:idv][:user_phone_confirmation]).to eq(true)
+ end
+
it 'does not update user phone attributes' do
expect(subject.current_user.reload.phone).to eq '+1 (202) 555-1212'
expect(subject.current_user.reload.phone_confirmed_at).to eq @previous_phone_confirmed_at
diff --git a/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb
index d55bb7d82bf..bed129a66c4 100644
--- a/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb
+++ b/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb
@@ -6,7 +6,7 @@
before do
sign_in_before_2fa
@secret = subject.current_user.generate_totp_secret
- subject.current_user.otp_secret_key = @secret
+ subject.current_user.update(otp_secret_key: @secret)
end
it 'redirects to the profile' do
@@ -127,5 +127,25 @@
end
end
end
+
+ context 'when the user does not have an authenticator app enabled' do
+ it 'redirects to user_two_factor_authentication_path' do
+ stub_sign_in_before_2fa
+ post :create, code: '123456'
+
+ expect(response).to redirect_to user_two_factor_authentication_path
+ end
+ end
+ end
+
+ describe '#show' do
+ context 'when the user does not have an authenticator app enabled' do
+ it 'redirects to user_two_factor_authentication_path' do
+ stub_sign_in_before_2fa
+ get :show
+
+ expect(response).to redirect_to user_two_factor_authentication_path
+ end
+ end
end
end
diff --git a/spec/controllers/users/phones_controller_spec.rb b/spec/controllers/users/phones_controller_spec.rb
index be1dc4f75ec..c4dedc1d413 100644
--- a/spec/controllers/users/phones_controller_spec.rb
+++ b/spec/controllers/users/phones_controller_spec.rb
@@ -16,7 +16,7 @@
stub_analytics
allow(@analytics).to receive(:track_event)
- put :update, update_user_phone_form: { phone: new_phone }
+ put :update, update_user_phone_form: { phone: new_phone, international_code: 'US' }
end
it 'lets user know they need to confirm their new phone' do
@@ -37,7 +37,7 @@
it 'does not delete the phone' do
stub_sign_in(user)
- put :update, update_user_phone_form: { phone: '' }
+ put :update, update_user_phone_form: { phone: '', international_code: 'US' }
expect(user.reload.phone).to be_present
expect(response).to render_template(:edit)
@@ -51,7 +51,7 @@
stub_analytics
allow(@analytics).to receive(:track_event)
- put :update, update_user_phone_form: { phone: second_user.phone }
+ put :update, update_user_phone_form: { phone: second_user.phone, international_code: 'US' }
end
it 'processes successfully and informs user' do
@@ -74,7 +74,7 @@
user = build(:user, phone: '123-123-1234')
stub_sign_in(user)
- put :update, update_user_phone_form: { phone: invalid_phone }
+ put :update, update_user_phone_form: { phone: invalid_phone, international_code: 'US' }
expect(user.phone).not_to eq invalid_phone
expect(response).to render_template(:edit)
@@ -85,7 +85,7 @@
it 'redirects to profile page without any messages' do
stub_sign_in(user)
- put :update, update_user_phone_form: { phone: user.phone }
+ put :update, update_user_phone_form: { phone: user.phone, international_code: 'US' }
expect(response).to redirect_to account_url
expect(flash.keys).to be_empty
diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb
index 9be166701c8..b981880208e 100644
--- a/spec/controllers/users/sessions_controller_spec.rb
+++ b/spec/controllers/users/sessions_controller_spec.rb
@@ -323,5 +323,22 @@
get :new
end
end
+
+ context 'with fully authenticated user who has a pending profile' do
+ it 'redirects to the verify profile page' do
+ profile = create(
+ :profile,
+ deactivation_reason: :verification_pending,
+ phone_confirmed: false,
+ pii: { otp: 'abc123', ssn: '6666', dob: '1920-01-01' }
+ )
+ user = profile.user
+
+ stub_sign_in(user)
+ get :new
+
+ expect(response).to redirect_to verify_account_path
+ end
+ end
end
end
diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb
index 26b9618fa99..0683fd5e659 100644
--- a/spec/controllers/users/two_factor_authentication_controller_spec.rb
+++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb
@@ -141,6 +141,8 @@ def index
otp_delivery_preference: 'sms',
resend: nil,
context: 'authentication',
+ country_code: '1',
+ area_code: '202',
}
expect(@analytics).to receive(:track_event).
@@ -203,6 +205,8 @@ def index
otp_delivery_preference: 'voice',
resend: nil,
context: 'authentication',
+ country_code: '1',
+ area_code: '202',
}
expect(@analytics).to receive(:track_event).
diff --git a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb
index c1700284d02..595764589f4 100644
--- a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb
+++ b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb
@@ -28,7 +28,11 @@
expect(@analytics).to receive(:track_event).
with(Analytics::MULTI_FACTOR_AUTH_PHONE_SETUP, result)
- patch :set, two_factor_setup_form: { phone: '703-555-010', otp_delivery_preference: :sms }
+ patch :set, two_factor_setup_form: {
+ phone: '703-555-010',
+ otp_delivery_preference: :sms,
+ international_code: 'US',
+ }
expect(response).to render_template(:index)
end
@@ -50,7 +54,8 @@
patch(
:set,
two_factor_setup_form: { phone: '703-555-0100',
- otp_delivery_preference: 'voice' }
+ otp_delivery_preference: 'voice',
+ international_code: 'US' }
)
expect(response).to redirect_to(
@@ -81,7 +86,8 @@
patch(
:set,
two_factor_setup_form: { phone: '703-555-0100',
- otp_delivery_preference: :sms }
+ otp_delivery_preference: :sms,
+ international_code: 'US' }
)
expect(response).to redirect_to(
@@ -110,7 +116,9 @@
patch(
:set,
- two_factor_setup_form: { phone: '703-555-0100', otp_delivery_preference: :sms }
+ two_factor_setup_form: { phone: '703-555-0100',
+ otp_delivery_preference: :sms,
+ international_code: 'US' }
)
expect(response).to redirect_to(
diff --git a/spec/controllers/users/verify_password_controller_spec.rb b/spec/controllers/users/verify_password_controller_spec.rb
index 540ce2967a7..fba5173e971 100644
--- a/spec/controllers/users/verify_password_controller_spec.rb
+++ b/spec/controllers/users/verify_password_controller_spec.rb
@@ -20,71 +20,72 @@
end
end
- context 'without personal key flag set' do
- let(:profiles) { [create(:profile, deactivation_reason: :password_reset)] }
-
- describe '#new' do
- it 'redirects to the root url' do
- get :new
- expect(response).to redirect_to(root_url)
- end
- end
-
- describe '#update' do
- it 'redirects to the root url' do
- get :new
- expect(response).to redirect_to(root_url)
- end
- end
- end
-
context 'with password reset profile' do
let(:profiles) { [create(:profile, deactivation_reason: :password_reset)] }
let(:response_ok) { FormResponse.new(success: true, errors: {}, extra: { personal_key: key }) }
let(:response_bad) { FormResponse.new(success: false, errors: {}) }
let(:key) { 'key' }
- before do
- allow(subject.reactivate_account_session).to receive(:personal_key?).and_return(personal_key)
- end
-
- describe '#new' do
- it 'renders the `new` template' do
- get :new
+ context 'without personal key flag set' do
+ describe '#new' do
+ it 'redirects to the root url' do
+ get :new
+ expect(response).to redirect_to(root_url)
+ end
+ end
- expect(response).to render_template(:new)
+ describe '#update' do
+ it 'redirects to the root url' do
+ get :new
+ expect(response).to redirect_to(root_url)
+ end
end
end
- describe '#update' do
- let(:form) { instance_double(VerifyPasswordForm) }
-
+ context 'with personal key flag set' do
before do
- expect(controller).to receive(:verify_password_form).and_return(form)
+ allow(subject.reactivate_account_session).to receive(:personal_key?).
+ and_return(personal_key)
end
- context 'with valid password' do
- before do
- allow(form).to receive(:submit).and_return(response_ok)
- put :update, user: { password: user.password }
+ describe '#new' do
+ it 'renders the `new` template' do
+ get :new
+
+ expect(response).to render_template(:new)
end
+ end
- it 'redirects to the account page' do
- expect(response).to redirect_to(account_url)
+ describe '#update' do
+ let(:form) { instance_double(VerifyPasswordForm) }
+
+ before do
+ expect(controller).to receive(:verify_password_form).and_return(form)
end
- it 'sets a new personal key as a flash message' do
- expect(flash[:personal_key]).to eq(key)
+ context 'with valid password' do
+ before do
+ allow(form).to receive(:submit).and_return(response_ok)
+ put :update, user: { password: user.password }
+ end
+
+ it 'redirects to the account page' do
+ expect(response).to redirect_to(account_url)
+ end
+
+ it 'sets a new personal key as a flash message' do
+ expect(flash[:personal_key]).to eq(key)
+ end
end
- end
- context 'without valid password' do
- it 'renders the new template' do
- allow(form).to receive(:submit).and_return(response_bad)
+ context 'without valid password' do
+ it 'renders the new template' do
+ allow(form).to receive(:submit).and_return(response_bad)
- put :update, user: { password: user.password }
+ put :update, user: { password: user.password }
- expect(response).to render_template(:new)
+ expect(response).to render_template(:new)
+ end
end
end
end
diff --git a/spec/controllers/users/verify_personal_key_controller_spec.rb b/spec/controllers/users/verify_personal_key_controller_spec.rb
index 0e16e56d7ca..d8d7698bfa3 100644
--- a/spec/controllers/users/verify_personal_key_controller_spec.rb
+++ b/spec/controllers/users/verify_personal_key_controller_spec.rb
@@ -33,7 +33,7 @@
it 'displays a flash message to the user' do
get :new
- expect(subject.flash[:notice]).to eq(t('notices.account_recovery'))
+ expect(subject.flash[:notice]).to eq(t('notices.account_reactivation'))
end
end
end
diff --git a/spec/controllers/verify/address_controller_spec.rb b/spec/controllers/verify/address_controller_spec.rb
index a590ed04d37..5c271db6326 100644
--- a/spec/controllers/verify/address_controller_spec.rb
+++ b/spec/controllers/verify/address_controller_spec.rb
@@ -9,7 +9,7 @@
describe '#index' do
it 'redirects if phone mechanism selected' do
- subject.idv_session.phone_confirmation = true
+ subject.idv_session.vendor_phone_confirmation = true
get :index
diff --git a/spec/controllers/verify/confirmations_controller_spec.rb b/spec/controllers/verify/confirmations_controller_spec.rb
index a86c74994e5..81bf90c13a3 100644
--- a/spec/controllers/verify/confirmations_controller_spec.rb
+++ b/spec/controllers/verify/confirmations_controller_spec.rb
@@ -66,7 +66,10 @@ def stub_idv_session
context 'user used 2FA phone as phone of record' do
before do
- subject.idv_session.phone_confirmation = true
+ subject.idv_session.params['phone'] = user.phone
+ subject.idv_session.params['phone_confirmed_at'] = Time.zone.now
+ subject.idv_session.vendor_phone_confirmation = true
+ subject.idv_session.user_phone_confirmation = true
end
it 'activates profile' do
@@ -148,6 +151,7 @@ def stub_idv_session
context 'user confirmed a new phone' do
it 'tracks that event' do
stub_analytics
+ subject.idv_session.params['phone'] = '+1 (202) 555-9876'
subject.idv_session.params['phone_confirmed_at'] = Time.zone.now
result = {
diff --git a/spec/controllers/verify/finance_controller_spec.rb b/spec/controllers/verify/finance_controller_spec.rb
index 4ae70d9091c..8e90712fbcc 100644
--- a/spec/controllers/verify/finance_controller_spec.rb
+++ b/spec/controllers/verify/finance_controller_spec.rb
@@ -80,12 +80,119 @@
end
end
end
+
+ it 'tracks the form errors and does not make a vendor API call' do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+
+ allow(Idv::FinancialsValidator).to receive(:new)
+
+ put :create, idv_finance_form: { finance_type: :ccn, ccn: '123' }
+
+ result = {
+ success: false,
+ errors: { ccn: ['Credit card number should be only last 8 digits.'] },
+ }
+
+ expect(@analytics).to have_received(:track_event).
+ with(Analytics::IDV_FINANCE_CONFIRMATION_FORM, result)
+ expect(subject.idv_session.financials_confirmation).to be_falsy
+ expect(Idv::FinancialsValidator).to_not have_received(:new)
+ end
end
context 'when form is valid' do
+ it 'redirects to the show page' do
+ put :create, idv_finance_form: { finance_type: :ccn, ccn: '12345678' }
+
+ expect(response).to redirect_to(verify_finance_result_path)
+ end
+
+ it 'tracks the successful submission with no errors' do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+
+ put :create, idv_finance_form: { finance_type: :ccn, ccn: '12345678' }
+
+ result = {
+ success: true,
+ errors: {},
+ }
+
+ expect(@analytics).to have_received(:track_event).with(
+ Analytics::IDV_FINANCE_CONFIRMATION_FORM, result
+ )
+ end
+ end
+ end
+
+ describe '#show' do
+ before do
+ stub_subject
+ end
+
+ context 'when the background job is not complete yet' do
+ render_views
+
+ it 'renders a spinner and has the page refresh' do
+ get :show
+
+ expect(response).to render_template('shared/refresh')
+
+ dom = Nokogiri::HTML(response.body)
+ expect(dom.css('meta[http-equiv="refresh"]')).to be_present
+ end
+ end
+
+ context 'when the background job has timed out' do
+ let(:expired_started_at) do
+ Time.zone.now.to_i - Figaro.env.async_job_refresh_max_wait_seconds.to_i
+ end
+
+ before do
+ controller.idv_session.async_result_started_at = expired_started_at
+ controller.idv_session.params = { ccn: '12345678' }
+ end
+
+ it 'displays an error' do
+ get :show
+
+ expect(response).to render_template :new
+ expect(flash[:warning]).to include(t('idv.modal.financials.timeout'))
+ end
+
+ it 'tracks the failure as a timeout' do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+
+ get :show
+
+ result = {
+ success: false,
+ errors: { timed_out: ['Timed out waiting for vendor response'] },
+ }
+
+ expect(@analytics).to have_received(:track_event).with(
+ Analytics::IDV_FINANCE_CONFIRMATION_VENDOR, result
+ )
+ end
+ end
+
+ context 'when the background job has completed' do
+ let(:result_id) { SecureRandom.uuid }
+
+ before do
+ controller.idv_session.async_result_id = result_id
+ VendorValidatorResultStorage.new.store(result_id: result_id, result: result)
+ end
+
context 'when CCN is confirmed' do
+ let(:result) { Idv::VendorResult.new(success: true) }
+
it 'redirects to phone page' do
- put :create, idv_finance_form: { finance_type: :ccn, ccn: '12345678' }
+ controller.idv_session.params = { ccn: '12345678' }
+
+ get :show
expect(flash[:success]).to eq(t('idv.messages.personal_details_verified'))
expect(response).to redirect_to verify_address_url
@@ -93,19 +200,62 @@
expected_params = { ccn: '12345678' }
expect(subject.idv_session.params).to eq expected_params
end
+
+ it 'tracks the successful submission with no errors' do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+
+ get :show
+
+ result = {
+ success: true,
+ errors: {},
+ }
+
+ expect(@analytics).to have_received(:track_event).with(
+ Analytics::IDV_FINANCE_CONFIRMATION_VENDOR, result
+ )
+ end
end
context 'when CCN is not confirmed' do
+ let(:result) do
+ Idv::VendorResult.new(
+ success: false,
+ errors: { ccn: ['The ccn could not be verified.'] }
+ )
+ end
+
+ before do
+ controller.idv_session.params = { finance_type: 'ccn', ccn: '00000000' }
+ end
+
it 'renders #new with error' do
- put :create, idv_finance_form: { finance_type: :ccn, ccn: '00000000' }
+ get :show
expect(flash[:warning]).to match t('idv.modal.financials.heading')
expect(flash[:warning]).to match t('idv.modal.attempts', count: max_attempts - 1)
expect(response).to render_template :new
end
+
+ it 'tracks the vendor error' do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+
+ get :show
+
+ result = {
+ success: false,
+ errors: { ccn: ['The ccn could not be verified.'] },
+ }
+
+ expect(@analytics).to have_received(:track_event).
+ with(Analytics::IDV_FINANCE_CONFIRMATION_VENDOR, result)
+ end
end
context 'attempt window has expired, previous attempts == max-1' do
+ let(:result) { Idv::VendorResult.new(success: true) }
let(:two_days_ago) { Time.zone.now - 2.days }
before do
@@ -114,7 +264,7 @@
end
it 'allows and does not affect attempt counter' do
- put :create, idv_finance_form: { finance_type: :ccn, ccn: '12345678' }
+ get :show
expect(response).to redirect_to verify_address_path
expect(subject.current_user.idv_attempts).to eq(max_attempts - 1)
@@ -124,61 +274,6 @@
end
end
- describe 'analytics' do
- before do
- stub_subject
- stub_analytics
- allow(@analytics).to receive(:track_event)
- end
-
- context 'when form is valid and CCN passes vendor validation' do
- it 'tracks the successful submission with no errors' do
- put :create, idv_finance_form: { finance_type: :ccn, ccn: '12345678' }
-
- result = {
- success: true,
- errors: {},
- }
-
- expect(@analytics).to have_received(:track_event).with(
- Analytics::IDV_FINANCE_CONFIRMATION, result
- )
- end
- end
-
- context 'when the form is valid but the CCN does not pass vendor validation' do
- it 'tracks the vendor error' do
- put :create, idv_finance_form: { finance_type: :ccn, ccn: '00000000' }
-
- result = {
- success: false,
- errors: { ccn: ['The ccn could not be verified.'] },
- }
-
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_FINANCE_CONFIRMATION, result)
- end
- end
-
- context 'when the form is invalid' do
- it 'tracks the form errors and does not make a vendor API call' do
- allow(Idv::FinancialsValidator).to receive(:new)
-
- put :create, idv_finance_form: { finance_type: :ccn, ccn: '123' }
-
- result = {
- success: false,
- errors: { ccn: ['Credit card number should be only last 8 digits.'] },
- }
-
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_FINANCE_CONFIRMATION, result)
- expect(subject.idv_session.financials_confirmation).to eq false
- expect(Idv::FinancialsValidator).to_not have_received(:new)
- end
- end
- end
-
def stub_subject
user = stub_sign_in
idv_session = Idv::Session.new(
diff --git a/spec/controllers/verify/phone_controller_spec.rb b/spec/controllers/verify/phone_controller_spec.rb
index 5f20d7df012..c94a9742f3f 100644
--- a/spec/controllers/verify/phone_controller_spec.rb
+++ b/spec/controllers/verify/phone_controller_spec.rb
@@ -1,9 +1,11 @@
require 'rails_helper'
-include Features::LocalizationHelper
describe Verify::PhoneController do
+ include Features::LocalizationHelper
+
let(:max_attempts) { Idv::Attempter.idv_max_attempts }
let(:good_phone) { '+1 (555) 555-0000' }
+ let(:normalized_phone) { '5555550000' }
let(:bad_phone) { '+1 (555) 555-5555' }
describe 'before_actions' do
@@ -24,7 +26,7 @@
end
it 'redirects to review when step is complete' do
- subject.idv_session.phone_confirmation = true
+ subject.idv_session.vendor_phone_confirmation = true
get :new
@@ -50,16 +52,16 @@
end
it 'renders #new' do
- put :create, idv_phone_form: { phone: '703' }
+ put :create, idv_phone_form: { phone: '703', international_code: 'US' }
expect(flash[:warning]).to be_nil
expect(subject.idv_session.params).to be_empty
end
it 'tracks form error and does not make a vendor API call' do
- allow(Idv::PhoneValidator).to receive(:new)
+ expect(Idv::PhoneValidator).to_not receive(:new)
- put :create, idv_phone_form: { phone: '703' }
+ put :create, idv_phone_form: { phone: '703', international_code: 'US' }
result = {
success: false,
@@ -69,10 +71,9 @@
}
expect(@analytics).to have_received(:track_event).with(
- Analytics::IDV_PHONE_CONFIRMATION, result
+ Analytics::IDV_PHONE_CONFIRMATION_FORM, result
)
- expect(subject.idv_session.phone_confirmation).to eq false
- expect(Idv::PhoneValidator).to_not have_received(:new)
+ expect(subject.idv_session.vendor_phone_confirmation).to be_falsy
end
end
@@ -86,46 +87,26 @@
user = build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now)
stub_verify_steps_one_and_two(user)
- put :create, idv_phone_form: { phone: good_phone }
+ put :create, idv_phone_form: { phone: good_phone, international_code: 'US' }
result = { success: true, errors: {} }
expect(@analytics).to have_received(:track_event).with(
- Analytics::IDV_PHONE_CONFIRMATION, result
- )
- end
-
- it 'tracks event with invalid phone' do
- user = build(:user, phone: bad_phone, phone_confirmed_at: Time.zone.now)
- stub_verify_steps_one_and_two(user)
-
- put :create, idv_phone_form: { phone: bad_phone }
-
- result = {
- success: false,
- errors: {
- phone: ['The phone number could not be verified.'],
- },
- }
-
- expect(flash[:warning]).to match t('idv.modal.phone.heading')
- expect(flash[:warning]).to match t('idv.modal.attempts', count: max_attempts - 1)
- expect(@analytics).to have_received(:track_event).with(
- Analytics::IDV_PHONE_CONFIRMATION, result
+ Analytics::IDV_PHONE_CONFIRMATION_FORM, result
)
end
context 'when same as user phone' do
- it 'redirects to review page and sets phone_confirmed_at' do
+ it 'redirects to result page and sets phone_confirmed_at' do
user = build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now)
stub_verify_steps_one_and_two(user)
- put :create, idv_phone_form: { phone: good_phone }
+ put :create, idv_phone_form: { phone: good_phone, international_code: 'US' }
- expect(response).to redirect_to verify_review_path
+ expect(response).to redirect_to verify_phone_result_path
expected_params = {
- phone: good_phone,
+ phone: normalized_phone,
phone_confirmed_at: user.phone_confirmed_at,
}
expect(subject.idv_session.params).to eq expected_params
@@ -133,39 +114,150 @@
end
context 'when different from user phone' do
- it 'redirects to review page and does not set phone_confirmed_at' do
+ it 'redirects to result page and does not set phone_confirmed_at' do
user = build(:user, phone: '+1 (415) 555-0130', phone_confirmed_at: Time.zone.now)
stub_verify_steps_one_and_two(user)
- put :create, idv_phone_form: { phone: good_phone }
+ put :create, idv_phone_form: { phone: good_phone, international_code: 'US' }
- expect(response).to redirect_to verify_review_path
+ expect(response).to redirect_to verify_phone_result_path
expected_params = {
- phone: good_phone,
+ phone: normalized_phone,
}
expect(subject.idv_session.params).to eq expected_params
end
end
+ end
+ end
+
+ describe '#show' do
+ let(:user) { build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now) }
+ let(:params) { { phone: good_phone } }
+
+ before do
+ stub_verify_steps_one_and_two(user)
+ controller.idv_session.params = params
+ end
+
+ context 'when the background job is not complete yet' do
+ render_views
+
+ it 'renders a spinner and has the page refresh' do
+ get :show
+
+ expect(response).to render_template('shared/refresh')
+
+ dom = Nokogiri::HTML(response.body)
+ expect(dom.css('meta[http-equiv="refresh"]')).to be_present
+ end
+ end
+
+ context 'when the background job has timed out' do
+ let(:expired_started_at) do
+ Time.zone.now.to_i - Figaro.env.async_job_refresh_max_wait_seconds.to_i
+ end
+
+ before do
+ controller.idv_session.async_result_started_at = expired_started_at
+ end
+
+ it 'displays an error' do
+ get :show
+
+ expect(response).to render_template :new
+ expect(flash[:warning]).to include(t('idv.modal.financials.timeout'))
+ end
+
+ it 'tracks the failure as a timeout' do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+
+ get :show
+
+ result = {
+ success: false,
+ errors: { timed_out: ['Timed out waiting for vendor response'] },
+ }
+
+ expect(@analytics).to have_received(:track_event).with(
+ Analytics::IDV_PHONE_CONFIRMATION_VENDOR, result
+ )
+ end
+ end
+
+ context 'when the background job has completed' do
+ let(:result_id) { SecureRandom.uuid }
+
+ before do
+ controller.idv_session.async_result_id = result_id
+ VendorValidatorResultStorage.new.store(result_id: result_id, result: result)
+ end
+
+ let(:result) { Idv::VendorResult.new(success: true) }
+
+ context 'when the phone is invalid' do
+ let(:result) do
+ Idv::VendorResult.new(
+ success: false,
+ errors: { phone: ['The phone number could not be verified.'] }
+ )
+ end
+
+ let(:params) { { phone: bad_phone } }
+ let(:user) { build(:user, phone: bad_phone, phone_confirmed_at: Time.zone.now) }
+
+ it 'tracks event with invalid phone' do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+
+ get :show
+
+ result = {
+ success: false,
+ errors: {
+ phone: ['The phone number could not be verified.'],
+ },
+ }
+
+ expect(flash[:warning]).to match t('idv.modal.phone.heading')
+ expect(flash[:warning]).to match t('idv.modal.attempts', count: max_attempts - 1)
+ expect(@analytics).to have_received(:track_event).with(
+ Analytics::IDV_PHONE_CONFIRMATION_VENDOR, result
+ )
+ end
+ end
context 'attempt window has expired, previous attempts == max-1' do
let(:two_days_ago) { Time.zone.now - 2.days }
let(:user) { build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now) }
before do
- stub_verify_steps_one_and_two(user)
user.idv_attempts = max_attempts - 1
user.idv_attempted_at = two_days_ago
end
it 'allows and does not affect attempt counter' do
- put :create, idv_phone_form: { phone: good_phone }
+ get :show
expect(response).to redirect_to verify_review_path
expect(user.idv_attempts).to eq(max_attempts - 1)
expect(user.idv_attempted_at).to eq two_days_ago
end
end
+
+ it 'passes the normalized phone to the background job' do
+ user = build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now)
+ stub_verify_steps_one_and_two(user)
+
+ expect(SubmitIdvJob).to receive(:new).with(
+ vendor_validator_class: Idv::PhoneValidator,
+ idv_session: subject.idv_session,
+ vendor_params: normalized_phone
+ ).and_call_original
+
+ put :create, idv_phone_form: { phone: good_phone }
+ end
end
end
end
diff --git a/spec/controllers/verify/review_controller_spec.rb b/spec/controllers/verify/review_controller_spec.rb
index ff31a0be5f2..1c8a2e7731e 100644
--- a/spec/controllers/verify/review_controller_spec.rb
+++ b/spec/controllers/verify/review_controller_spec.rb
@@ -36,7 +36,7 @@
issuer: nil
)
idv_session.profile_confirmation = true
- idv_session.phone_confirmation = true
+ idv_session.vendor_phone_confirmation = true
idv_session.financials_confirmation = true
idv_session.params = user_attrs
idv_session.normalized_applicant_params = user_attrs.merge(
@@ -77,7 +77,7 @@ def show
context 'user has missed address step' do
before do
- idv_session.phone_confirmation = false
+ idv_session.vendor_phone_confirmation = false
end
it 'redirects to address step' do
diff --git a/spec/controllers/verify/sessions_controller_spec.rb b/spec/controllers/verify/sessions_controller_spec.rb
index b0e90722b48..60c93db8a4d 100644
--- a/spec/controllers/verify/sessions_controller_spec.rb
+++ b/spec/controllers/verify/sessions_controller_spec.rb
@@ -28,6 +28,7 @@
let(:idv_session) do
Idv::Session.new(user_session: subject.user_session, current_user: user, issuer: nil)
end
+ let(:normalized_applicant) { Proofer::Applicant.new(user_attrs) }
describe 'before_actions' do
it 'includes before_actions from AccountStateChecker' do
@@ -104,13 +105,11 @@
result = {
success: false,
- idv_attempts_exceeded: false,
errors: { ssn: [t('idv.errors.duplicate_ssn')] },
- vendor: { reasons: nil },
}
expect(@analytics).to receive(:track_event).
- with(Analytics::IDV_BASIC_INFO_SUBMITTED, result)
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_FORM, result)
post :create, profile: user_attrs.merge(ssn: '666-66-1234')
@@ -129,139 +128,259 @@
end
context 'missing fields' do
- it 'checks for required fields' do
- partial_attrs = user_attrs.dup
- partial_attrs.delete :first_name
+ let(:partial_attrs) do
+ user_attrs.tap { |attrs| attrs.delete :first_name }
+ end
+ it 'checks for required fields' do
post :create, profile: partial_attrs
expect(response).to render_template(:new)
expect(flash[:warning]).to be_nil
end
+
+ it 'does not increment attempts count' do
+ expect { post :create, profile: partial_attrs }.
+ to_not change(user, :idv_attempts)
+ end
end
+ end
- context 'un-resolvable attributes' do
- let(:bad_attrs) { user_attrs.dup.merge(first_name: 'Bad') }
+ describe '#show' do
+ before do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+ end
- it 're-renders form' do
- post :create, profile: bad_attrs
+ context 'when the background job is not complete yet' do
+ render_views
- expect(flash[:warning]).to match t('idv.modal.sessions.heading')
- expect(flash[:warning]).to match(t('idv.modal.attempts', count: max_attempts - 1))
- expect(response).to render_template(:new)
- end
+ it 'renders a spinner and has the page refresh' do
+ get :show
- it 'creates analytics event' do
- post :create, profile: bad_attrs
+ expect(response).to render_template('shared/refresh')
- result = {
- success: false,
- idv_attempts_exceeded: false,
- errors: {
- first_name: ['Unverified first name.'],
- },
- vendor: { reasons: ['The name was suspicious'] },
- }
-
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_BASIC_INFO_SUBMITTED, result)
+ dom = Nokogiri::HTML(response.body)
+ expect(dom.css('meta[http-equiv="refresh"]')).to be_present
end
end
- context 'vendor agent throws exception' do
- let(:bad_attrs) { user_attrs.dup.merge(first_name: 'Fail') }
+ context 'when the background job has timed out' do
+ let(:expired_started_at) do
+ Time.zone.now.to_i - Figaro.env.async_job_refresh_max_wait_seconds.to_i
+ end
+
+ before do
+ controller.idv_session.async_result_started_at = expired_started_at
+ controller.idv_session.params = user_attrs
+ end
- it 'logs failure and re-renders form' do
- exception_msg = 'Failed to contact proofing vendor'
+ it 'displays an error' do
+ get :show
- expect(NewRelic::Agent).to receive(:notice_error).
- with(kind_of(StandardError))
+ expect(response).to render_template :new
+ expect(flash[:warning]).to include(t('idv.modal.sessions.timeout'))
+ end
- post :create, profile: bad_attrs
+ it 'tracks the failure as a timeout' do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+
+ get :show
result = {
success: false,
+ errors: { timed_out: ['Timed out waiting for vendor response'] },
idv_attempts_exceeded: false,
- errors: {
- agent: [exception_msg],
- },
- vendor: { reasons: [exception_msg] },
+ vendor: { reasons: [] },
}
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_BASIC_INFO_SUBMITTED, result)
- expect(response).to render_template(:new)
+ expect(@analytics).to have_received(:track_event).with(
+ Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result
+ )
end
end
- context 'success' do
- it 'creates analytics event' do
- post :create, profile: user_attrs
+ context 'when the background job has completed' do
+ let(:result_id) { SecureRandom.uuid }
+ let(:params) { user_attrs }
- result = {
- success: true,
- idv_attempts_exceeded: false,
- errors: {},
- vendor: { reasons: ['Everything looks good'] },
- }
+ before do
+ controller.idv_session.async_result_id = result_id
+ VendorValidatorResultStorage.new.store(result_id: result_id, result: result)
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_BASIC_INFO_SUBMITTED, result)
+ controller.idv_session.params = params
end
- end
- context 'max attempts exceeded' do
- before do
- user.idv_attempts = max_attempts
- user.idv_attempted_at = Time.zone.now
+ context 'un-resolvable attributes' do
+ let(:params) { user_attrs.dup.merge(first_name: 'Bad') }
+
+ let(:result) do
+ Idv::VendorResult.new(
+ success: false,
+ errors: { first_name: ['Unverified first name.'] },
+ reasons: ['The name was suspicious']
+ )
+ end
+
+ it 're-renders form' do
+ get :show
+
+ expect(flash[:warning]).to match t('idv.modal.sessions.heading')
+ expect(flash[:warning]).to match(t('idv.modal.attempts', count: max_attempts - 1))
+ expect(response).to render_template(:new)
+ end
+
+ it 'creates analytics event' do
+ get :show
+
+ result = {
+ success: false,
+ idv_attempts_exceeded: false,
+ errors: {
+ first_name: ['Unverified first name.'],
+ },
+ vendor: { reasons: ['The name was suspicious'] },
+ }
+
+ expect(@analytics).to have_received(:track_event).
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result)
+ end
end
- it 'redirects to fail' do
- post :create, profile: user_attrs
+ context 'previous address supplied' do
+ let(:bad_zipcode) { '00000' }
- result = {
- request_path: verify_session_path,
- }
+ let(:result) { Idv::VendorResult.new(success: false) }
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_MAX_ATTEMPTS_EXCEEDED, result)
- expect(response).to redirect_to verify_fail_url
- end
- end
+ context 'if previous address has a bad zipcode' do
+ let(:params) { user_attrs.merge(previous_address).merge(prev_zipcode: bad_zipcode) }
- context 'attempt window has expired, previous attempts == max-1' do
- before do
- user.idv_attempts = max_attempts - 1
- user.idv_attempted_at = Time.zone.now - 2.days
+ it 'fails' do
+ get :show
+
+ expect(idv_session.resolution_successful).to be_nil
+ end
+ end
+
+ context 'if current address has a bad zipcode' do
+ let(:params) { user_attrs.merge(previous_address).merge(zipcode: bad_zipcode) }
+
+ it 'fails' do
+ get :show
+
+ expect(idv_session.resolution_successful).to be_nil
+ end
+ end
+
+ context 'with multiple addresses' do
+ let(:result) do
+ Idv::VendorResult.new(success: true, normalized_applicant: normalized_applicant)
+ end
+ let(:params) { user_attrs.merge(previous_address) }
+
+ it 'respects both addresses' do
+ get :show
+
+ expect(idv_session.resolution_successful).to eq true
+ end
+ end
end
- it 'allows and resets attempt counter' do
- post :create, profile: user_attrs
+ context 'vendor agent throws exception' do
+ let(:params) { user_attrs.dup.merge(first_name: 'Fail') }
+ let(:exception_msg) { 'Failed to contact proofing vendor' }
+ let(:result) do
+ Idv::VendorResult.new(
+ success: false,
+ errors: { agent: [exception_msg] },
+ reasons: [exception_msg]
+ )
+ end
+
+ it 'logs failure and re-renders form' do
+ get :show
+
+ result = {
+ success: false,
+ idv_attempts_exceeded: false,
+ errors: {
+ agent: [exception_msg],
+ },
+ vendor: { reasons: [exception_msg] },
+ }
+
+ expect(@analytics).to have_received(:track_event).
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result)
+ expect(response).to render_template(:new)
+ end
+ end
- expect(response).to redirect_to verify_finance_path
- expect(user.idv_attempts).to eq 1
+ context 'success' do
+ let(:result) do
+ Idv::VendorResult.new(
+ success: true,
+ reasons: ['Everything looks good'],
+ normalized_applicant: normalized_applicant
+ )
+ end
+
+ it 'creates analytics event' do
+ get :show
+
+ result = {
+ success: true,
+ idv_attempts_exceeded: false,
+ errors: {},
+ vendor: { reasons: ['Everything looks good'] },
+ }
+
+ expect(@analytics).to have_received(:track_event).
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result)
+ end
+
+ it 'increments attempts count' do
+ expect { get :show }.to change(user, :idv_attempts).by(1)
+ end
end
- end
- context 'previous address supplied' do
- let(:bad_zipcode) { '00000' }
+ context 'max attempts exceeded' do
+ let(:result) { Idv::VendorResult.new(success: true) }
- it 'fails if previous address has bad zipcode' do
- post :create, profile: user_attrs.merge(previous_address).merge(prev_zipcode: bad_zipcode)
+ before do
+ user.idv_attempts = max_attempts
+ user.idv_attempted_at = Time.zone.now
+ end
- expect(idv_session.resolution_successful).to be_nil
- end
+ it 'redirects to fail' do
+ get :show
- it 'fails if current address has bad zipcode' do
- post :create, profile: user_attrs.merge(previous_address).merge(zipcode: bad_zipcode)
+ result = {
+ request_path: verify_session_result_path,
+ }
- expect(idv_session.resolution_successful).to be_nil
+ expect(@analytics).to have_received(:track_event).
+ with(Analytics::IDV_MAX_ATTEMPTS_EXCEEDED, result)
+ expect(response).to redirect_to verify_fail_url
+ end
end
- it 'respects both addresses' do
- post :create, profile: user_attrs.merge(previous_address)
+ context 'attempt window has expired, previous attempts == max-1' do
+ let(:result) do
+ Idv::VendorResult.new(success: true, normalized_applicant: normalized_applicant)
+ end
+
+ before do
+ user.idv_attempts = max_attempts - 1
+ user.idv_attempted_at = Time.zone.now - 2.days
+ end
+
+ it 'allows and resets attempt counter' do
+ get :show
- expect(idv_session.resolution_successful).to eq true
+ expect(response).to redirect_to verify_finance_path
+ expect(user.idv_attempts).to eq 1
+ end
end
end
end
diff --git a/spec/decorators/usps_decorator_spec.rb b/spec/decorators/usps_decorator_spec.rb
index 890182ba499..d26a29f2aaf 100644
--- a/spec/decorators/usps_decorator_spec.rb
+++ b/spec/decorators/usps_decorator_spec.rb
@@ -1,23 +1,16 @@
require 'rails_helper'
RSpec.describe UspsDecorator do
+ let(:user) { create(:user) }
subject(:decorator) do
- user = create(
- :user,
- :signed_up,
- profiles: [build(:profile, :active, :verified, pii: { first_name: 'Jane' })]
- )
-
- idv_session = Idv::Session.new(user_session: {}, current_user: user, issuer: nil)
- UspsDecorator.new(idv_session)
+ usps_mail_service = Idv::UspsMail.new(user)
+ UspsDecorator.new(usps_mail_service)
end
describe '#title' do
context 'a letter has not been sent' do
- let(:idv_session) { subject.idv_session }
-
it 'provides text to send' do
- subject.idv_session.address_verification_mechanism = nil
+ allow(subject.usps_mail_service).to receive(:any_mail_sent?).and_return(false)
expect(subject.title).to eq(
I18n.t('idv.titles.mail.verify')
)
@@ -26,7 +19,7 @@
context 'a letter has been sent' do
it 'provides text to resend' do
- subject.idv_session.address_verification_mechanism = 'usps'
+ allow(subject.usps_mail_service).to receive(:any_mail_sent?).and_return(true)
expect(subject.title).to eq(
I18n.t('idv.titles.mail.resend')
)
@@ -37,7 +30,7 @@
describe '#button' do
context 'a letter has not been sent' do
it 'provides text to send' do
- subject.idv_session.address_verification_mechanism = nil
+ allow(subject.usps_mail_service).to receive(:any_mail_sent?).and_return(false)
expect(subject.button).to eq(
I18n.t('idv.buttons.mail.send')
)
@@ -46,7 +39,7 @@
context 'a letter has been sent' do
it 'provides text to resend' do
- subject.idv_session.address_verification_mechanism = 'usps'
+ allow(subject.usps_mail_service).to receive(:any_mail_sent?).and_return(true)
expect(subject.button).to eq(
I18n.t('idv.buttons.mail.resend')
)
diff --git a/spec/factories/service_providers.rb b/spec/factories/service_providers.rb
index 58de449c4cc..434703fbc71 100644
--- a/spec/factories/service_providers.rb
+++ b/spec/factories/service_providers.rb
@@ -1,5 +1,5 @@
FactoryGirl.define do
- Faker::Config.locale = 'en-US'
+ Faker::Config.locale = :en
factory :service_provider do
cert { 'saml_test_sp' }
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 8781769339c..fac8cf7ddb1 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -1,5 +1,5 @@
FactoryGirl.define do
- Faker::Config.locale = 'en-US'
+ Faker::Config.locale = :en
factory :user do
confirmed_at Time.zone.now
diff --git a/spec/features/accessibility/idv_pages_spec.rb b/spec/features/accessibility/idv_pages_spec.rb
index 95c9f14b8a2..290b9c68025 100644
--- a/spec/features/accessibility/idv_pages_spec.rb
+++ b/spec/features/accessibility/idv_pages_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
require 'axe/rspec'
-feature 'Accessibility on IDV pages', :js do
+feature 'Accessibility on IDV pages', :js, idv_job: true do
describe 'IDV pages' do
include IdvHelper
diff --git a/spec/features/flows/sp_authentication_flows_spec.rb b/spec/features/flows/sp_authentication_flows_spec.rb
index 49fdc1dd6f8..b99eda80d5f 100644
--- a/spec/features/flows/sp_authentication_flows_spec.rb
+++ b/spec/features/flows/sp_authentication_flows_spec.rb
@@ -1,224 +1,230 @@
require 'rails_helper'
-include IdvHelper
-include SamlAuthHelper
feature 'SP-initiated authentication with login.gov', user_flow: true do
- context 'with a valid SP' do
- context 'when LOA3' do
- before do
- visit loa3_authnrequest
- end
-
- it 'prompts the user to create an account or sign in' do
- screenshot_and_save_page
- end
-
- context 'when choosing Create Account' do
- before do
- click_link t('sign_up.registrations.create_account')
- end
-
- it 'prompts for email address' do
- screenshot_and_save_page
- end
+ include IdvHelper
+ include SamlAuthHelper
- context 'with a valid email address submitted' do
+ I18n.available_locales.each do |locale|
+ context "with locale=#{locale}" do
+ context 'with a valid SP' do
+ context 'when LOA3' do
before do
- @email = Faker::Internet.safe_email
- fill_in 'Email', with: @email
- click_button t('forms.buttons.submit.default')
- @user = User.find_with_email(@email)
+ visit "#{loa3_authnrequest}&locale=#{locale}"
end
- it 'informs the user to check email' do
+ it 'prompts the user to create an account or sign in' do
screenshot_and_save_page
end
- context 'with a confirmed email address' do
+ context 'when choosing Create Account' do
before do
- confirm_last_user
+ click_link t('sign_up.registrations.create_account')
end
- it 'prompts the user for a password' do
+ it 'prompts for email address' do
screenshot_and_save_page
end
- context 'with a valid password' do
+ context 'with a valid email address submitted' do
before do
- fill_in 'password_form_password', with: Features::SessionHelper::VALID_PASSWORD
- click_button t('forms.buttons.continue')
+ @email = Faker::Internet.safe_email
+ fill_in 'user_email', with: @email
+ click_button t('forms.buttons.submit.default')
+ @user = User.find_with_email(@email)
end
- it 'prompts the user to configure 2FA' do
+ it 'informs the user to check email' do
screenshot_and_save_page
end
- context 'with a valid phone number' do
+ context 'with a confirmed email address' do
before do
- fill_in 'Phone', with: Faker::PhoneNumber.cell_phone
+ confirm_last_user
end
- context 'with SMS delivery' do
+ it 'prompts the user for a password' do
+ screenshot_and_save_page
+ end
+
+ context 'with a valid password' do
before do
- choose t('devise.two_factor_authentication.otp_delivery_preference.sms')
- click_send_security_code
+ fill_in 'password_form_password', with: Features::SessionHelper::VALID_PASSWORD
+ click_button t('forms.buttons.continue')
end
- it 'prompts for OTP' do
+ it 'prompts the user to configure 2FA' do
screenshot_and_save_page
end
- context 'with valid OTP confirmation' do
+ context 'with a valid phone number' do
before do
- fill_in 'code', with: @user.reload.direct_otp
- click_button t('forms.buttons.submit.default')
- end
-
- it 'prompts the user to verify oneself' do
- screenshot_and_save_page
+ fill_in 'two_factor_setup_form_phone', with: Faker::PhoneNumber.cell_phone
end
- context 'when choosing Yes, continue' do
+ context 'with SMS delivery' do
before do
- click_link t('idv.index.continue_link')
+ choose t('devise.two_factor_authentication.otp_delivery_preference.sms')
+ click_send_security_code
end
- it 'prompts for personal information' do
+ it 'prompts for OTP' do
screenshot_and_save_page
end
- context 'with valid personal information entered' do
+ context 'with valid OTP confirmation' do
before do
- fill_in t('idv.form.first_name'), with: Faker::Name.first_name
- fill_in t('idv.form.last_name'), with: Faker::Name.last_name
- fill_in 'profile_address1', with: '123 Main St'
- fill_in 'profile_city', with: Faker::Address.city
- find('#profile_state').find(:xpath,
- "option[#{(1..50).to_a.sample}]").
- select_option
- fill_in 'profile_zipcode', with: Faker::Address.zip_code
- fill_in t('idv.form.dob'), with: "09/09/#{(1900..2000).to_a.sample}"
- fill_in 'profile_ssn', with: "999-99-#{(1000..9999).to_a.sample}"
- click_button t('forms.buttons.continue')
+ fill_in 'code', with: @user.reload.direct_otp
+ click_button t('forms.buttons.submit.default')
end
- it 'prompts for the last 8 digits of a credit card' do
+ it 'prompts the user to verify oneself' do
screenshot_and_save_page
end
- context 'with last 8 digits of credit card' do
+ context 'when choosing Yes, continue' do
before do
- fill_out_financial_form_ok
+ click_link t('idv.index.continue_link')
end
- it 'prompts to activate account by phone or mail' do
+ it 'prompts for personal information' do
screenshot_and_save_page
end
- end
- context 'without a credit card' do
- before do
- click_link t('idv.form.use_financial_account')
- end
-
- it 'prompts user to provide a financial account number' do
- screenshot_and_save_page
- end
-
- context 'with a valid financial account' do
+ context 'with valid personal information entered' do
before do
- select t('idv.form.mortgage'), from: 'idv_finance_form_finance_type'
- fill_in 'idv_finance_form_mortgage', with: '12345678'
- # click_idv_continue doesn't work with the JavaScript on this page
- # and enabling js: true causes unexpected behavior
- form = page.find('#new_idv_finance_form')
- class << form
- def submit!
- Capybara::RackTest::Form.new(driver, native).submit({})
- end
- end
- form.submit!
+ fill_in 'profile_first_name', with: Faker::Name.first_name
+ fill_in 'profile_last_name', with: Faker::Name.last_name
+ fill_in 'profile_address1', with: '123 Main St'
+ fill_in 'profile_city', with: Faker::Address.city
+ find('#profile_state').
+ find(:xpath, "option[#{(1..50).to_a.sample}]").
+ select_option
+ fill_in 'profile_zipcode', with: Faker::Address.zip_code
+ fill_in 'profile_dob', with: "09/09/#{(1900..2000).to_a.sample}"
+ fill_in 'profile_ssn', with: "999-99-#{(1000..9999).to_a.sample}"
+ click_button t('forms.buttons.continue')
end
- it 'prompts to activate account by phone or mail' do
+ it 'prompts for the last 8 digits of a credit card' do
screenshot_and_save_page
end
- context 'when activating by phone' do
+ context 'with last 8 digits of credit card' do
before do
- click_idv_address_choose_phone
+ fill_out_financial_form_ok
end
- it 'prompts the user to confirm or enter phone number' do
+ it 'prompts to activate account by phone or mail' do
screenshot_and_save_page
end
end
- context 'when activating by mail' do
+ context 'without a credit card' do
before do
- click_idv_address_choose_usps
+ click_link t('idv.form.use_financial_account')
end
- it 'prompts the user to confirm' do
+ it 'prompts user to provide a financial account number' do
screenshot_and_save_page
end
- context 'when confirming to mail' do
+ context 'with a valid financial account' do
before do
- click_on t('idv.buttons.mail.send')
+ select t('idv.form.mortgage'),
+ from: 'idv_finance_form_finance_type'
+ fill_in 'idv_finance_form_mortgage', with: '12345678'
+ # click_idv_continue doesn't work with the JavaScript on this page
+ # and enabling js: true causes unexpected behavior
+ form = page.find('#new_idv_finance_form')
+ class << form
+ def submit!
+ Capybara::RackTest::Form.new(driver, native).submit({})
+ end
+ end
+ form.submit!
end
- it 'prompts user for password to encrypt profile' do
+ it 'prompts to activate account by phone or mail' do
screenshot_and_save_page
end
- context 'when confirming password' do
+ context 'when activating by phone' do
+ before do
+ click_idv_address_choose_phone
+ end
+
+ it 'prompts the user to confirm or enter phone number' do
+ screenshot_and_save_page
+ end
+ end
+
+ context 'when activating by mail' do
before do
- fill_in 'user_password',
- with: Features::SessionHelper::VALID_PASSWORD
- click_button t('forms.buttons.submit.default')
+ click_idv_address_choose_usps
end
- it 'provides a new personal key and prompts for verification' do
+ it 'prompts the user to confirm' do
screenshot_and_save_page
end
- context 'when clicking Continue' do
+ context 'when confirming to mail' do
before do
- click_acknowledge_personal_key
+ click_on t('idv.buttons.mail.send')
end
- it 'displays the user profile' do
+ it 'prompts user for password to encrypt profile' do
screenshot_and_save_page
end
+
+ context 'when confirming password' do
+ before do
+ fill_in 'user_password',
+ with: Features::SessionHelper::VALID_PASSWORD
+ click_button t('forms.buttons.submit.default')
+ end
+
+ it 'provides a new personal key and prompts to verify' do
+ screenshot_and_save_page
+ end
+
+ context 'when clicking Continue' do
+ before do
+ click_acknowledge_personal_key
+ end
+
+ it 'displays the user profile' do
+ screenshot_and_save_page
+ end
+ end
+ end
end
end
+
+ # Disabling this spec because of js: true issue
+ # Will re-enable this once resolved
+ # context 'when choosing to cancel' do
+ # before do
+ # click_button t('links.cancel_idv')
+ # end
+
+ # it 'prompts to continue verification or visit profile' do
+ # screenshot_and_save_page
+ # end
+ # end
end
end
-
- # Disabling this spec because of js: true issue
- # Will re-enable this once resolved
- # context 'when choosing to cancel' do
- # before do
- # click_button t('links.cancel_idv')
- # end
-
- # it 'prompts to continue verification or visit profile' do
- # screenshot_and_save_page
- # end
- # end
end
- end
- end
- context 'with invalid personal information entered' do
- before do
- fill_out_idv_form_fail
- click_button t('forms.buttons.continue')
- end
+ context 'with invalid personal information entered' do
+ before do
+ fill_out_idv_form_fail
+ click_button t('forms.buttons.continue')
+ end
- it 'presents a modal with current retries remaining' do
- screenshot_and_save_page
+ it 'presents a modal with current retries remaining' do
+ screenshot_and_save_page
+ end
+ end
end
end
end
@@ -227,147 +233,147 @@ def submit!
end
end
end
- end
- end
-
- # context 'when choosing to sign in' do
- # TODO: duplicate scenarios from Create Account here
- # end
- end
-
- context 'when LOA1' do
- before do
- visit authnrequest_get
- end
-
- it 'prompts the user to create an account or sign in' do
- screenshot_and_save_page
- end
-
- context 'when choosing Create Account' do
- before do
- click_link t('sign_up.registrations.create_account')
- end
- it 'prompts for email address' do
- screenshot_and_save_page
+ # context 'when choosing to sign in' do
+ # TODO: duplicate scenarios from Create Account here
+ # end
end
- context 'with a valid email address submitted' do
+ context 'when LOA1' do
before do
- @email = Faker::Internet.safe_email
- fill_in 'Email', with: @email
- click_button t('forms.buttons.submit.default')
- @user = User.find_with_email(@email)
+ visit "#{authnrequest_get}&locale=#{locale}"
end
- it 'informs the user to check email' do
+ it 'prompts the user to create an account or sign in' do
screenshot_and_save_page
end
- context 'with a confirmed email address' do
+ context 'when choosing Create Account' do
before do
- confirm_last_user
+ click_link t('sign_up.registrations.create_account')
end
- it 'prompts the user for a password' do
+ it 'prompts for email address' do
screenshot_and_save_page
end
- context 'with a valid password' do
+ context 'with a valid email address submitted' do
before do
- fill_in 'password_form_password', with: Features::SessionHelper::VALID_PASSWORD
- click_button t('forms.buttons.continue')
+ @email = Faker::Internet.safe_email
+ fill_in 'user_email', with: @email
+ click_button t('forms.buttons.submit.default')
+ @user = User.find_with_email(@email)
end
- it 'prompts the user to configure 2FA' do
+ it 'informs the user to check email' do
screenshot_and_save_page
end
- context 'with a valid phone number' do
+ context 'with a confirmed email address' do
before do
- fill_in 'Phone', with: Faker::PhoneNumber.cell_phone
+ confirm_last_user
end
- context 'with SMS delivery' do
+ it 'prompts the user for a password' do
+ screenshot_and_save_page
+ end
+
+ context 'with a valid password' do
before do
- choose t('devise.two_factor_authentication.otp_delivery_preference.sms')
- click_send_security_code
+ fill_in 'password_form_password', with: Features::SessionHelper::VALID_PASSWORD
+ click_button t('forms.buttons.continue')
end
- it 'prompts for OTP' do
+ it 'prompts the user to configure 2FA' do
screenshot_and_save_page
end
- end
- context 'with Voice delivery' do
- before do
- choose t('devise.two_factor_authentication.otp_delivery_preference.voice')
- click_send_security_code
- end
+ context 'with a valid phone number' do
+ before do
+ fill_in 'two_factor_setup_form_phone', with: Faker::PhoneNumber.cell_phone
+ end
- it 'prompts for OTP' do
- screenshot_and_save_page
+ context 'with SMS delivery' do
+ before do
+ choose t('devise.two_factor_authentication.otp_delivery_preference.sms')
+ click_send_security_code
+ end
+
+ it 'prompts for OTP' do
+ screenshot_and_save_page
+ end
+ end
+
+ context 'with Voice delivery' do
+ before do
+ choose t('devise.two_factor_authentication.otp_delivery_preference.voice')
+ click_send_security_code
+ end
+
+ it 'prompts for OTP' do
+ screenshot_and_save_page
+ end
+ end
end
end
end
end
end
- end
- end
-
- context 'when choosing to sign in' do
- before do
- @user = create(:user, :signed_up)
- click_link t('links.sign_in')
- end
-
- context 'with valid credentials entered' do
- before do
- fill_in_credentials_and_submit(@user.email, @user.password)
- end
- it 'prompts for 2FA delivery method' do
- screenshot_and_save_page
- end
-
- context 'with SMS OTP selected (default)' do
- it 'prompts for OTP verification' do
- screenshot_and_save_page
+ context 'when choosing to sign in' do
+ before do
+ @user = create(:user, :signed_up)
+ click_link t('links.sign_in')
end
- context 'with valid OTP confirmation' do
+ context 'with valid credentials entered' do
before do
- fill_in 'code', with: @user.reload.direct_otp
- click_button t('forms.buttons.submit.default')
+ fill_in_credentials_and_submit(@user.email, @user.password)
end
- # Skipping since we have nothing to show: this occurs on the SP
- xit 'redirects back to SP' do
+ it 'prompts for 2FA delivery method' do
screenshot_and_save_page
end
- end
- end
- end
- context 'without a valid username and password' do
- context 'when choosing "Forgot your password?"' do
- before do
- click_link t('links.passwords.forgot')
- end
+ context 'with SMS OTP selected (default)' do
+ it 'prompts for OTP verification' do
+ screenshot_and_save_page
+ end
- it 'prompts for my email address' do
- screenshot_and_save_page
- end
+ context 'with valid OTP confirmation' do
+ before do
+ fill_in 'code', with: @user.reload.direct_otp
+ click_button t('forms.buttons.submit.default')
+ end
- context 'with not_a_real_email_dot.com submitted' do
- before do
- fill_in 'password_reset_email_form_email', with: 'not_a_real_email_dot.com'
- click_button t('forms.buttons.continue')
+ # Skipping since we have nothing to show: this occurs on the SP
+ xit 'redirects back to SP' do
+ screenshot_and_save_page
+ end
+ end
end
+ end
- it 'displays a useful error' do
- screenshot_and_save_page
+ context 'without a valid username and password' do
+ context 'when choosing "Forgot your password?"' do
+ before do
+ click_link t('links.passwords.forgot')
+ end
+
+ it 'prompts for my email address' do
+ screenshot_and_save_page
+ end
+
+ context 'with not_a_real_email_dot.com submitted' do
+ before do
+ fill_in 'password_reset_email_form_email', with: 'not_a_real_email_dot.com'
+ click_button t('forms.buttons.continue')
+ end
+
+ it 'displays a useful error' do
+ screenshot_and_save_page
+ end
+ end
end
end
end
diff --git a/spec/features/flows/visitor_flows_spec.rb b/spec/features/flows/visitor_flows_spec.rb
index 089cd9c0f61..7bae6f6f0d6 100644
--- a/spec/features/flows/visitor_flows_spec.rb
+++ b/spec/features/flows/visitor_flows_spec.rb
@@ -1,187 +1,191 @@
require 'rails_helper'
feature 'Visitors requesting login.gov directly', devise: true, user_flow: true do
- context 'when visiting the homepage' do
- before do
- visit root_path
- end
-
- it 'loads the home page' do
- screenshot_and_save_page
- end
-
- describe 'showing the password' do
- it 'allows me to see my password', js: true do
- visit new_user_session_path
- fill_in 'Password', with: 'my password'
- screenshot_and_save_page
- end
- end
-
- context 'when attempting to sign in' do
- context 'with a valid account' do
+ I18n.available_locales.each do |locale|
+ context "with locale=#{locale}" do
+ context 'when visiting the homepage' do
before do
- @user = create(:user, :signed_up)
- fill_in 'Email', with: @user.email
- fill_in 'Password', with: @user.password
- page.find('.btn-primary').click
+ visit root_path(locale: locale)
end
- it 'sends OTP via previously chosen method' do
+ it 'loads the home page' do
screenshot_and_save_page
end
- context 'with a valid OTP' do
- before do
- fill_in 'code', with: @user.reload.direct_otp
- click_button t('forms.buttons.submit.default')
- end
-
- it 'redirects to profile' do
+ describe 'showing the password' do
+ it 'allows me to see my password', js: true do
+ visit new_user_session_path(locale: locale)
+ fill_in 'user_password', with: 'my password'
screenshot_and_save_page
end
end
- context 'with an invalid OTP submitted' do
- before do
- fill_in 'code', with: '123abc'
- click_button t('forms.buttons.submit.default')
- end
-
- it 'displays a useful error' do
- screenshot_and_save_page
- end
-
- context 'with a second invalid OTP submitted' do
+ context 'when attempting to sign in' do
+ context 'with a valid account' do
before do
- fill_in 'code', with: '123abc'
- click_button t('forms.buttons.submit.default')
+ @user = create(:user, :signed_up)
+ fill_in 'user_email', with: @user.email
+ fill_in 'user_password', with: @user.password
+ page.find('.btn-primary').click
end
- it 'displays a useful error' do
+ it 'sends OTP via previously chosen method' do
screenshot_and_save_page
end
- context 'with a third invalid OTP submitted' do
+ context 'with a valid OTP' do
+ before do
+ fill_in 'code', with: @user.reload.direct_otp
+ click_button t('forms.buttons.submit.default')
+ end
+
+ it 'redirects to profile' do
+ screenshot_and_save_page
+ end
+ end
+
+ context 'with an invalid OTP submitted' do
before do
fill_in 'code', with: '123abc'
click_button t('forms.buttons.submit.default')
end
- it 'displays a useful error and locks the user account' do
+ it 'displays a useful error' do
screenshot_and_save_page
end
+
+ context 'with a second invalid OTP submitted' do
+ before do
+ fill_in 'code', with: '123abc'
+ click_button t('forms.buttons.submit.default')
+ end
+
+ it 'displays a useful error' do
+ screenshot_and_save_page
+ end
+
+ context 'with a third invalid OTP submitted' do
+ before do
+ fill_in 'code', with: '123abc'
+ click_button t('forms.buttons.submit.default')
+ end
+
+ it 'displays a useful error and locks the user account' do
+ screenshot_and_save_page
+ end
+ end
+ end
end
end
- end
- end
- context 'without valid credentials' do
- before do
- fill_in 'Email', with: Faker::Internet.safe_email
- fill_in 'Password', with: 'my password'
- page.find('.btn-primary').click
- end
+ context 'without valid credentials' do
+ before do
+ fill_in 'user_email', with: Faker::Internet.safe_email
+ fill_in 'user_password', with: 'my password'
+ page.find('.btn-primary').click
+ end
- it 'displays a useful error message' do
- screenshot_and_save_page
+ it 'displays a useful error message' do
+ screenshot_and_save_page
+ end
+ end
end
- end
- end
- context 'when choosing create account' do
- before do
- click_link t('links.create_account')
- end
+ context 'when choosing create account' do
+ before do
+ click_link t('links.create_account')
+ end
- it 'informs the user about login.gov' do
- screenshot_and_save_page
- end
+ it 'informs the user about login.gov' do
+ screenshot_and_save_page
+ end
- context 'when creating account with valid email' do
- before do
- sign_up_with(Faker::Internet.safe_email)
- end
+ context 'when creating account with valid email' do
+ before do
+ sign_up_with(Faker::Internet.safe_email)
+ end
- it 'notifies user to check email' do
- screenshot_and_save_page
- end
+ it 'notifies user to check email' do
+ screenshot_and_save_page
+ end
- context 'when confirming email' do
- before do
- confirm_last_user
+ context 'when confirming email' do
+ before do
+ confirm_last_user
+ end
+
+ it 'prompts user to set password' do
+ screenshot_and_save_page
+ end
+ end
end
- it 'prompts user to set password' do
- screenshot_and_save_page
+ context 'when attempting with an invalid email' do
+ before do
+ sign_up_with('kevin@kevin')
+ end
+
+ it 'informs the user to try again' do
+ screenshot_and_save_page
+ end
end
end
end
- context 'when attempting with an invalid email' do
+ context 'when choosing \'Forgot your password?' do
before do
- sign_up_with('kevin@kevin')
+ visit new_user_password_path(locale: locale)
end
- it 'informs the user to try again' do
+ it 'prompts for email address' do
screenshot_and_save_page
end
- end
- end
- end
- context 'when choosing \'Forgot your password?' do
- before do
- visit new_user_password_path
- end
+ context 'when submitting email for an existing account' do
+ before do
+ @user = create(:user, :signed_up)
+ fill_in 'password_reset_email_form_email', with: @user.email
+ click_button t('forms.buttons.continue')
+ end
- it 'prompts for email address' do
- screenshot_and_save_page
- end
+ it 'informs the user to check their email' do
+ screenshot_and_save_page
+ end
- context 'when submitting email for an existing account' do
- before do
- @user = create(:user, :signed_up)
- fill_in 'Email', with: @user.email
- click_button t('forms.buttons.continue')
- end
+ context 'when following link in email', email: true do
+ before do
+ open_last_email
+ click_email_link_matching(/reset_password_token/)
+ end
- it 'informs the user to check their email' do
- screenshot_and_save_page
- end
+ it 'prompts the user to enter a new password' do
+ screenshot_and_save_page
+ end
- context 'when following link in email', email: true do
- before do
- open_last_email
- click_email_link_matching(/reset_password_token/)
- end
+ context 'when submitting a valid password' do
+ before do
+ fill_in t('forms.passwords.edit.labels.password'), with: 'NewVal!dPassw0rd'
+ click_button t('forms.passwords.edit.buttons.submit')
+ end
- it 'prompts the user to enter a new password' do
- screenshot_and_save_page
+ it 'redirects to the homepage with a helpful message' do
+ screenshot_and_save_page
+ end
+ end
+ end
end
- context 'when submitting a valid password' do
+ context 'when submitting email not associated with an account' do
before do
- fill_in t('forms.passwords.edit.labels.password'), with: 'NewVal!dPassw0rd'
- click_button t('forms.passwords.edit.buttons.submit')
+ fill_in 'password_reset_email_form_email', with: 'non-existent-email@example.com'
+ click_button t('forms.buttons.continue')
end
- it 'redirects to the homepage with a helpful message' do
+ it 'informs the user to check their email' do
screenshot_and_save_page
end
end
end
end
-
- context 'when submitting email not associated with an account' do
- before do
- fill_in 'Email', with: 'non-existent-email@example.com'
- click_button t('forms.buttons.continue')
- end
-
- it 'informs the user to check their email' do
- screenshot_and_save_page
- end
- end
end
end
diff --git a/spec/features/idv/flow_spec.rb b/spec/features/idv/flow_spec.rb
index 4ebe49ed1dd..0b693cef18f 100644
--- a/spec/features/idv/flow_spec.rb
+++ b/spec/features/idv/flow_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'IdV session' do
+feature 'IdV session', idv_job: true do
include IdvHelper
context 'landing page' do
@@ -65,7 +65,7 @@
fill_in 'profile_first_name', with: first_name_to_trigger_exception
click_idv_continue
- expect(current_path).to eq verify_session_path
+ expect(current_path).to eq verify_session_result_path
expect(page).to have_css('.modal-warning', text: t('idv.modal.sessions.heading'))
end
@@ -76,7 +76,7 @@
visit verify_session_path
complete_idv_profile_fail
- expect(current_path).to eq verify_session_path
+ expect(current_path).to eq verify_session_result_path
end
user.reload
@@ -109,7 +109,7 @@
fill_out_financial_form_fail
click_idv_continue
- expect(current_path).to eq verify_finance_path
+ expect(current_path).to eq verify_finance_result_path
end
fill_out_financial_form_fail
@@ -152,7 +152,7 @@
click_idv_continue
# failure reloads the form and shows warning modal
- expect(current_path).to eq verify_session_path
+ expect(current_path).to eq verify_session_result_path
expect(page).to have_css('.modal-warning', text: t('idv.modal.sessions.heading'))
click_button t('idv.modal.button.warning')
@@ -169,7 +169,7 @@
click_idv_continue
# failure reloads the form and shows warning modal
- expect(current_path).to eq verify_finance_path
+ expect(current_path).to eq verify_finance_result_path
expect(page).to have_css('.modal-warning', text: t('idv.modal.financials.heading'))
click_button t('idv.modal.button.warning')
@@ -191,7 +191,7 @@
click_idv_continue
# failure reloads the same sticky form (different path) and shows warning modal
- expect(current_path).to eq verify_finance_path
+ expect(current_path).to eq verify_finance_result_path
click_button t('idv.modal.button.warning')
expect(page).to have_selector("input[value='#{mortgage_value}']")
@@ -214,7 +214,7 @@
click_idv_continue
# failure reloads the same sticky form
- expect(current_path).to eq verify_phone_path
+ expect(current_path).to eq verify_phone_result_path
expect(page).to have_css('.modal-warning', text: t('idv.modal.phone.heading'))
click_button t('idv.modal.button.warning')
expect(page).to have_selector("input[value='#{bad_phone_formatted}']")
@@ -413,6 +413,29 @@
expect(current_path).to eq account_path
end
+
+ scenario 'being unable to verify account without OTP phone confirmation' do
+ different_phone = '555-555-9876'
+ user = sign_in_live_with_2fa
+ visit verify_session_path
+
+ fill_out_idv_form_ok
+ click_idv_continue
+ fill_out_financial_form_ok
+ click_idv_continue
+ click_idv_address_choose_phone
+ fill_out_phone_form_ok(different_phone)
+ click_idv_continue
+ fill_in :user_password, with: user_password
+ click_submit_default
+
+ visit verify_confirmations_path
+ click_acknowledge_personal_key
+
+ user.reload
+
+ expect(user.active_profile).to be_nil
+ end
end
def complete_idv_profile_fail
diff --git a/spec/features/idv/interrupted_session_spec.rb b/spec/features/idv/interrupted_session_spec.rb
index b326172ba1d..347e5891978 100644
--- a/spec/features/idv/interrupted_session_spec.rb
+++ b/spec/features/idv/interrupted_session_spec.rb
@@ -3,7 +3,7 @@
feature 'Interrupted IdV session' do
include IdvHelper
- describe 'Closing the browser while on the first form', js: true do
+ describe 'Closing the browser while on the first form', js: true, idv_job: true do
before do
sign_in_and_2fa_user
visit verify_session_path
diff --git a/spec/features/idv/phone_spec.rb b/spec/features/idv/phone_spec.rb
index 2af68727238..2d824ef3117 100644
--- a/spec/features/idv/phone_spec.rb
+++ b/spec/features/idv/phone_spec.rb
@@ -3,7 +3,7 @@
feature 'Verify phone' do
include IdvHelper
- scenario 'phone step redirects to fail after max attempts' do
+ scenario 'phone step redirects to fail after max attempts', idv_job: true do
sign_in_and_2fa_user
visit verify_session_path
fill_out_idv_form_ok
@@ -16,7 +16,7 @@
fill_out_phone_form_fail
click_idv_continue
- expect(current_path).to eq verify_phone_path
+ expect(current_path).to eq verify_phone_result_path
end
fill_out_phone_form_fail
@@ -24,7 +24,7 @@
expect(page).to have_css('.alert-error', text: t('idv.modal.phone.heading'))
end
- context 'Idv phone and user phone are different' do
+ context 'Idv phone and user phone are different', idv_job: true do
scenario 'prompts to confirm phone' do
user = create(
:user, :signed_up,
@@ -43,9 +43,31 @@
expect(current_path).to eq account_path
end
+
+ scenario 'phone number with no voice otp support only allows sms delivery' do
+ guam_phone = '671-555-5000'
+ user = create(
+ :user, :signed_up,
+ otp_delivery_preference: 'voice',
+ password: Features::SessionHelper::VALID_PASSWORD
+ )
+
+ sign_in_and_2fa_user(user)
+ visit verify_session_path
+
+ allow(VoiceOtpSenderJob).to receive(:perform_later)
+ allow(SmsOtpSenderJob).to receive(:perform_later)
+
+ complete_idv_profile_with_phone(guam_phone)
+
+ expect(current_path).to eq login_two_factor_path(otp_delivery_preference: :sms)
+ expect(VoiceOtpSenderJob).to_not have_received(:perform_later)
+ expect(SmsOtpSenderJob).to have_received(:perform_later)
+ expect(page).to_not have_content(t('links.two_factor_authentication.resend_code.phone'))
+ end
end
- scenario 'phone field only allows numbers', js: true do
+ scenario 'phone field only allows numbers', js: true, idv_job: true do
sign_in_and_2fa_user
visit verify_session_path
fill_out_idv_form_ok
@@ -57,7 +79,7 @@
fill_in 'Phone', with: ''
find('#idv_phone_form_phone').native.send_keys('abcd1234')
- expect(find('#idv_phone_form_phone').value).to eq '1 (234) '
+ expect(find('#idv_phone_form_phone').value).to eq '+1 234'
end
def complete_idv_profile_with_phone(phone)
diff --git a/spec/features/idv/previous_address_spec.rb b/spec/features/idv/previous_address_spec.rb
index 334cf48eaa3..71a018fd915 100644
--- a/spec/features/idv/previous_address_spec.rb
+++ b/spec/features/idv/previous_address_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'IdV with previous address filled in' do
+feature 'IdV with previous address filled in', idv_job: true do
include IdvHelper
let(:bad_zipcode) { '00000' }
@@ -8,7 +8,7 @@
let(:previous_address) { '456 Other Ave' }
def expect_to_stay_on_verify_session_page
- expect(current_path).to eq verify_session_path
+ expect(current_path).to eq verify_session_result_path
expect(page).to have_selector("input[value='#{bad_zipcode}']")
end
diff --git a/spec/features/openid_connect/openid_connect_spec.rb b/spec/features/openid_connect/openid_connect_spec.rb
index b548a1ff952..ab2516c478b 100644
--- a/spec/features/openid_connect/openid_connect_spec.rb
+++ b/spec/features/openid_connect/openid_connect_spec.rb
@@ -111,7 +111,7 @@
click_submit_default
expect(current_url).to start_with('http://localhost:7654/auth/result')
- expect(page.get_rack_session.keys).to_not include('sp')
+ expect(page.get_rack_session.keys).to include('sp')
end
it 'auto-allows and sets the correct CSP headers after an incorrect OTP' do
@@ -146,7 +146,7 @@
click_submit_default
expect(current_url).to start_with('http://localhost:7654/auth/result')
- expect(page.get_rack_session.keys).to_not include('sp')
+ expect(page.get_rack_session.keys).to include('sp')
end
end
@@ -238,7 +238,7 @@
click_button t('forms.buttons.continue')
redirect_uri = URI(current_url)
expect(redirect_uri.to_s).to start_with('gov.gsa.openidconnect.test://result')
- expect(page.get_rack_session.keys).to_not include('sp')
+ expect(page.get_rack_session.keys).to include('sp')
end
end
end
@@ -307,7 +307,7 @@
end
context 'LOA3 signup' do
- it 'redirects back to SP', email: true do
+ it 'redirects back to SP', email: true, idv_job: true do
allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true)
client_id = 'urn:gov:gsa:openidconnect:sp:server'
@@ -501,7 +501,7 @@
redirect_uri = URI(current_url)
expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result')
- expect(page.get_rack_session.keys).to_not include('sp')
+ expect(page.get_rack_session.keys).to include('sp')
end
perform_in_browser(:one) do
@@ -515,7 +515,7 @@
redirect_uri = URI(current_url)
expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result')
- expect(page.get_rack_session.keys).to_not include('sp')
+ expect(page.get_rack_session.keys).to include('sp')
end
end
end
diff --git a/spec/features/saml/loa1_sso_spec.rb b/spec/features/saml/loa1_sso_spec.rb
index dc974e046c7..669bb18703a 100644
--- a/spec/features/saml/loa1_sso_spec.rb
+++ b/spec/features/saml/loa1_sso_spec.rb
@@ -30,19 +30,22 @@
click_on t('forms.buttons.continue')
expect(current_url).to eq authn_request
- expect(page.get_rack_session.keys).to_not include('sp')
+ expect(page.get_rack_session.keys).to include('sp')
end
end
it 'takes user to the service provider, allows user to visit IDP' do
+ allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true)
+
user = create(:user, :signed_up)
saml_authn_request = auth_request.create(saml_settings)
visit saml_authn_request
- sign_in_live_with_2fa(user)
+ click_link t('links.sign_in')
+ fill_in_credentials_and_submit(user.email, user.password)
+ click_submit_default
expect(current_url).to eq saml_authn_request
- expect(page.get_rack_session.keys).to_not include('sp')
visit root_path
expect(current_path).to eq account_path
@@ -177,7 +180,7 @@
click_button t('forms.buttons.continue')
expect(current_url).to eq authn_request
- expect(page.get_rack_session.keys).to_not include('sp')
+ expect(page.get_rack_session.keys).to include('sp')
end
perform_in_browser(:one) do
@@ -190,7 +193,7 @@
click_button t('forms.buttons.continue')
expect(current_url).to eq authn_request
- expect(page.get_rack_session.keys).to_not include('sp')
+ expect(page.get_rack_session.keys).to include('sp')
end
end
end
diff --git a/spec/features/saml/loa3_sso_spec.rb b/spec/features/saml/loa3_sso_spec.rb
index 85785816185..f2014fb7225 100644
--- a/spec/features/saml/loa3_sso_spec.rb
+++ b/spec/features/saml/loa3_sso_spec.rb
@@ -1,9 +1,27 @@
require 'rails_helper'
-feature 'LOA3 Single Sign On' do
+feature 'LOA3 Single Sign On', idv_job: true do
include SamlAuthHelper
include IdvHelper
+ def perform_id_verification_with_usps_without_confirming_code_then_sign_out(user)
+ saml_authn_request = auth_request.create(loa3_with_bundle_saml_settings)
+ visit saml_authn_request
+ sign_in_live_with_2fa(user)
+ click_idv_begin
+ fill_out_idv_form_ok
+ click_idv_continue
+ fill_out_financial_form_ok
+ click_idv_continue
+ click_idv_address_choose_usps
+ click_on t('idv.buttons.mail.send')
+ fill_in :user_password, with: user.password
+ click_submit_default
+ click_acknowledge_personal_key
+ first(:link, t('links.sign_out')).click
+ click_submit_default
+ end
+
context 'First time registration' do
let(:email) { 'test@test.com' }
before do
@@ -67,6 +85,7 @@
click_on t('idv.buttons.mail.send')
expect(current_path).to eq verify_review_path
+ expect(page).to_not have_content t('idv.messages.phone.phone_of_record')
fill_in :user_password, with: user_password
@@ -186,6 +205,7 @@
sign_in_live_with_2fa(user)
expect(current_path).to eq verify_account_path
+ expect(page).to have_content t('idv.messages.usps.resend')
click_button t('forms.verify_profile.submit')
@@ -196,6 +216,21 @@
expect(current_url).to eq saml_authn_request
end
+
+ it 'provides an option to send another letter' do
+ user = create(:user, :signed_up)
+
+ perform_id_verification_with_usps_without_confirming_code_then_sign_out(user)
+
+ sign_in_live_with_2fa(user)
+
+ expect(current_path).to eq verify_account_path
+
+ click_link(t('idv.messages.usps.resend'))
+
+ expect(user.events.account_verified.size).to be(0)
+ expect(current_path).to eq(verify_usps_path)
+ end
end
context 'having previously cancelled phone verification' do
@@ -213,6 +248,26 @@
expect(current_url).to eq saml_authn_request
end
end
+
+ context 'returning to verify after canceling during the same session' do
+ it 'allows the user to verify' do
+ user = create(:user, :signed_up)
+ saml_authn_request = auth_request.create(loa3_with_bundle_saml_settings)
+
+ visit saml_authn_request
+ sign_in_live_with_2fa(user)
+ click_idv_begin
+ fill_out_idv_form_ok
+ click_idv_continue
+ click_idv_cancel
+ visit saml_authn_request
+ click_idv_begin
+ fill_out_idv_form_ok
+ click_idv_continue
+
+ expect(current_path).to eq verify_finance_path
+ end
+ end
end
context 'visiting sign_up_completed path before proofing' do
diff --git a/spec/features/two_factor_authentication/change_factor_spec.rb b/spec/features/two_factor_authentication/change_factor_spec.rb
index 49880e3891c..bfa396bcafb 100644
--- a/spec/features/two_factor_authentication/change_factor_spec.rb
+++ b/spec/features/two_factor_authentication/change_factor_spec.rb
@@ -54,6 +54,24 @@
expect(current_path).to eq account_path
end
+ scenario 'editing phone number with no voice otp support only allows sms delivery' do
+ user.update(otp_delivery_preference: 'voice')
+ guam_phone = '671-555-5000'
+
+ visit manage_phone_path
+ complete_2fa_confirmation
+
+ allow(VoiceOtpSenderJob).to receive(:perform_later)
+ allow(SmsOtpSenderJob).to receive(:perform_later)
+
+ update_phone_number(guam_phone)
+
+ expect(current_path).to eq login_two_factor_path(otp_delivery_preference: :sms)
+ expect(VoiceOtpSenderJob).to_not have_received(:perform_later)
+ expect(SmsOtpSenderJob).to have_received(:perform_later)
+ expect(page).to_not have_content(t('links.two_factor_authentication.resend_code.phone'))
+ end
+
scenario 'waiting too long to change phone number' do
allow(SmsOtpSenderJob).to receive(:perform_later)
@@ -154,11 +172,13 @@ def complete_2fa_confirmation_without_entering_otp
fill_in 'Password', with: Features::SessionHelper::VALID_PASSWORD
click_button t('forms.buttons.continue')
- expect(current_path).to eq login_two_factor_path(otp_delivery_preference: 'sms')
+ expect(current_path).to eq login_two_factor_path(
+ otp_delivery_preference: user.otp_delivery_preference
+ )
end
- def update_phone_number
- fill_in 'update_user_phone_form[phone]', with: '703-555-0100'
+ def update_phone_number(phone = '703-555-0100')
+ fill_in 'update_user_phone_form[phone]', with: phone
click_button t('forms.buttons.submit.confirm_change')
end
diff --git a/spec/features/two_factor_authentication/sign_in_spec.rb b/spec/features/two_factor_authentication/sign_in_spec.rb
index b3e116f5427..7ce7aff8dd1 100644
--- a/spec/features/two_factor_authentication/sign_in_spec.rb
+++ b/spec/features/two_factor_authentication/sign_in_spec.rb
@@ -32,6 +32,104 @@
expect(user.reload.phone).to_not eq '+1 (555) 555-1212'
expect(user.voice?).to eq true
end
+
+ context 'with U.S. phone that does not support phone delivery method' do
+ let(:guam_phone) { '671-555-5555' }
+
+ scenario 'renders an error if a user submits with phone selected' do
+ sign_in_before_2fa
+ fill_in 'Phone', with: guam_phone
+ choose 'Phone call'
+ click_send_security_code
+
+ expect(current_path).to eq(phone_setup_path)
+ expect(page).to have_content t(
+ 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ location: 'Guam'
+ )
+ end
+
+ scenario 'disables the phone option and displays a warning with js', :js do
+ sign_in_before_2fa
+
+ fill_in 'Phone', with: guam_phone
+ phone_radio_button = page.find(
+ '#two_factor_setup_form_otp_delivery_preference_voice',
+ visible: :all
+ )
+
+ expect(page).to have_content t(
+ 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ location: 'Guam'
+ )
+ expect(phone_radio_button).to be_disabled
+
+ fill_in 'Phone', with: '555-555-5000'
+
+ expect(page).not_to have_content t(
+ 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ location: 'Guam'
+ )
+ expect(phone_radio_button).to_not be_disabled
+ end
+ end
+
+ context 'with international phone that does not support phone delivery' do
+ scenario 'renders an error if a user submits with phone selected' do
+ sign_in_before_2fa
+ select 'Turkey +90', from: 'International code'
+ fill_in 'Phone', with: '555-555-5000'
+ choose 'Phone call'
+ click_send_security_code
+
+ expect(current_path).to eq(phone_setup_path)
+ expect(page).to have_content t(
+ 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ location: 'Turkey'
+ )
+ end
+
+ scenario 'disables the phone option and displays a warning with js', :js do
+ sign_in_before_2fa
+ select 'Turkey +90', from: 'International code'
+ fill_in 'Phone', with: '+90 312 213 29 65'
+ phone_radio_button = page.find(
+ '#two_factor_setup_form_otp_delivery_preference_voice',
+ visible: :all
+ )
+
+ expect(page).to have_content t(
+ 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ location: 'Turkey'
+ )
+ expect(phone_radio_button).to be_disabled
+
+ select 'Canada +1', from: 'International code'
+
+ expect(page).not_to have_content t(
+ 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ location: 'Turkey'
+ )
+ expect(phone_radio_button).to_not be_disabled
+ end
+
+ scenario 'updates international code as user types', :js do
+ sign_in_before_2fa
+ fill_in 'Phone', with: '+81 54 354 3643'
+
+ expect(page.find('#two_factor_setup_form_international_code').value).to eq 'JP'
+
+ fill_in 'Phone', with: '5376'
+ select 'Morocco +212', from: 'International code'
+
+ expect(find('#two_factor_setup_form_phone').value).to eq '+212 5376'
+
+ fill_in 'Phone', with: '54354'
+ select 'Japan +81', from: 'International code'
+
+ expect(find('#two_factor_setup_form_phone').value).to include '+81'
+ end
+ end
end
def attempt_to_bypass_2fa_setup
@@ -111,6 +209,25 @@ def submit_prefilled_otp_code
expect(page.evaluate_script('document.activeElement.id')).to eq 'code'
end
+ scenario 'the user changes delivery method' do
+ user = create(:user, :signed_up, otp_delivery_preference: :sms)
+ sign_in_before_2fa(user)
+
+ allow(VoiceOtpSenderJob).to receive(:perform_later)
+
+ click_on t('links.two_factor_authentication.voice')
+
+ expect(VoiceOtpSenderJob).to have_received(:perform_later)
+ end
+
+ scenario 'the user cannot change delivery method if phone is unsupported' do
+ guam_phone = '+1 (671) 555-5000'
+ user = create(:user, :signed_up, phone: guam_phone)
+ sign_in_before_2fa(user)
+
+ expect(page).to_not have_link t('links.two_factor_authentication.voice')
+ end
+
context 'user enters OTP incorrectly 3 times', js: true do
it 'locks the user out and leaves user on the page during entire lockout period' do
allow(Figaro.env).to receive(:session_check_frequency).and_return('0')
diff --git a/spec/features/users/password_recovery_via_recovery_code_spec.rb b/spec/features/users/password_recovery_via_recovery_code_spec.rb
index e3be9e3c66f..d220b1975be 100644
--- a/spec/features/users/password_recovery_via_recovery_code_spec.rb
+++ b/spec/features/users/password_recovery_via_recovery_code_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Password recovery via personal key' do
+feature 'Password recovery via personal key', idv_job: true do
include PersonalKeyHelper
include IdvHelper
diff --git a/spec/features/users/sign_in_spec.rb b/spec/features/users/sign_in_spec.rb
index a32ac94f187..7ba57d8ecb5 100644
--- a/spec/features/users/sign_in_spec.rb
+++ b/spec/features/users/sign_in_spec.rb
@@ -246,4 +246,17 @@
expect(page).to have_content t('devise.failure.invalid')
end
end
+
+ context 'invalid request_id' do
+ it 'allows the user to sign in and does not try to redirect to any SP' do
+ allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true)
+ user = create(:user, :signed_up)
+
+ visit new_user_session_path(request_id: 'invalid')
+ fill_in_credentials_and_submit(user.email, user.password)
+ click_submit_default
+
+ expect(current_path).to eq account_path
+ end
+ end
end
diff --git a/spec/features/users/user_edit_spec.rb b/spec/features/users/user_edit_spec.rb
index e579517d487..90810805be6 100644
--- a/spec/features/users/user_edit_spec.rb
+++ b/spec/features/users/user_edit_spec.rb
@@ -1,23 +1,47 @@
require 'rails_helper'
feature 'User edit' do
- scenario 'user sees error message if form is submitted without email', js: true do
- sign_in_and_2fa_user
+ context 'editing email' do
+ before do
+ sign_in_and_2fa_user
+ visit manage_email_path
+ end
- visit manage_email_path
- fill_in 'Email', with: ''
- click_button 'Update'
+ scenario 'user sees error message if form is submitted without email', :js, idv_job: true do
+ fill_in 'Email', with: ''
+ click_button 'Update'
- expect(page).to have_content t('valid_email.validations.email.invalid')
+ expect(page).to have_content t('valid_email.validations.email.invalid')
+ end
end
- scenario 'user sees error message if form is submitted without phone number', js: true do
- sign_in_and_2fa_user
+ context 'editing 2FA phone number' do
+ before do
+ sign_in_and_2fa_user
+ visit manage_phone_path
+ end
- visit manage_phone_path
- fill_in 'Phone', with: ''
- click_button t('forms.buttons.submit.confirm_change')
+ scenario 'user sees error message if form is submitted without phone number', js: true do
+ fill_in 'Phone', with: ''
+ click_button t('forms.buttons.submit.confirm_change')
- expect(page).to have_content t('errors.messages.improbable_phone')
+ expect(page).to have_content t('errors.messages.improbable_phone')
+ end
+
+ scenario 'updates international code as user types', :js do
+ fill_in 'Phone', with: '+81 54 354 3643'
+
+ expect(page.find('#update_user_phone_form_international_code').value).to eq 'JP'
+
+ fill_in 'Phone', with: '5376'
+ select 'Morocco +212', from: 'International code'
+
+ expect(find('#update_user_phone_form_phone').value).to eq '+212 5376'
+
+ fill_in 'Phone', with: '54354'
+ select 'Japan +81', from: 'International code'
+
+ expect(find('#update_user_phone_form_phone').value).to include '+81'
+ end
end
end
diff --git a/spec/features/users/user_profile_spec.rb b/spec/features/users/user_profile_spec.rb
index e04ded54ba7..89d310f47dc 100644
--- a/spec/features/users/user_profile_spec.rb
+++ b/spec/features/users/user_profile_spec.rb
@@ -66,7 +66,7 @@
expect(page).to have_content(t('idv.messages.personal_key'))
end
- it 'allows the user reactivate their profile by reverifying' do
+ it 'allows the user reactivate their profile by reverifying', idv_job: true do
profile = create(:profile, :active, :verified, pii: { ssn: '1234', dob: '1920-01-01' })
user = profile.user
diff --git a/spec/features/visitors/i18n_spec.rb b/spec/features/visitors/i18n_spec.rb
index e86cf30231c..3d28cc19c02 100644
--- a/spec/features/visitors/i18n_spec.rb
+++ b/spec/features/visitors/i18n_spec.rb
@@ -1,7 +1,14 @@
require 'rails_helper'
feature 'Internationalization' do
- context 'visit homepage' do
+ context 'visit homepage with no locale set' do
+ it 'displays a header in the default locale' do
+ visit root_path
+ expect(page).to have_content t('headings.sign_in_without_sp', locale: 'en')
+ end
+ end
+
+ context 'visit homepage with locale set in header' do
before do
page.driver.header 'Accept-Language', locale
visit root_path
@@ -22,5 +29,50 @@
expect(page).to have_content t('headings.sign_in_without_sp', locale: 'es')
end
end
+
+ context 'when the user selects an unsupported locale' do
+ let(:locale) { :es }
+
+ it 'it does not raise an exception' do
+ expect { visit root_path + '?locale=foo' }.to_not raise_exception
+ end
+
+ it 'it falls back to the locale set in header' do
+ expect(page).to have_content t('headings.sign_in_without_sp', locale: 'es')
+ end
+ end
+ end
+
+ context 'visit homepage without a locale param set' do
+ it 'displays header in the default locale' do
+ visit '/'
+
+ expect(page).to have_content t('headings.sign_in_without_sp', locale: 'en')
+ end
+
+ it 'allows user to manually toggle language from dropdown menu', js: true do
+ visit root_path
+ within(:css, '.i18n-desktop-dropdown', visible: false) do
+ find_link(t('i18n.locale.es'), visible: false).trigger('click')
+ end
+
+ expect(page).to have_content t('headings.sign_in_without_sp', locale: 'es')
+ expect(page).to have_content t('i18n.language', locale: 'es')
+
+ within(:css, '.i18n-desktop-dropdown', visible: false) do
+ find_link(t('i18n.locale.en'), visible: false).trigger('click')
+ end
+
+ expect(page).to have_content t('headings.sign_in_without_sp', locale: 'en')
+ expect(page).to have_content t('i18n.language', locale: 'en')
+ end
+ end
+
+ context 'visit homepage with locale param set to :es' do
+ it 'displays a translated header to the user' do
+ visit '/es/'
+
+ expect(page).to have_content t('headings.sign_in_without_sp', locale: 'es')
+ end
end
end
diff --git a/spec/features/visitors/phone_confirmation_spec.rb b/spec/features/visitors/phone_confirmation_spec.rb
index b32f556c6c7..6b588a49fcd 100644
--- a/spec/features/visitors/phone_confirmation_spec.rb
+++ b/spec/features/visitors/phone_confirmation_spec.rb
@@ -47,7 +47,7 @@
it 'informs the user that the OTP code is sent to the phone' do
expect(page).to have_content(
- t('instructions.2fa.sms.confirm_code_html',
+ t('instructions.mfa.sms.confirm_code_html',
number: '+1 (555) 555-5555',
resend_code_link: t('links.two_factor_authentication.resend_code.sms'))
)
@@ -65,7 +65,7 @@
it 'pretends the phone is valid and prompts to confirm the number' do
expect(current_path).to eq login_two_factor_path(otp_delivery_preference: 'sms')
expect(page).to have_content(
- t('instructions.2fa.sms.confirm_code_html',
+ t('instructions.mfa.sms.confirm_code_html',
number: @existing_user.phone,
resend_code_link: t('links.two_factor_authentication.resend_code.sms'))
)
diff --git a/spec/features/visitors/set_password_spec.rb b/spec/features/visitors/set_password_spec.rb
index 8a518521deb..ad3efba140c 100644
--- a/spec/features/visitors/set_password_spec.rb
+++ b/spec/features/visitors/set_password_spec.rb
@@ -55,7 +55,7 @@
expect(page).to have_content '...'
fill_in 'password_form_password', with: 'password'
- expect(page).to have_content 'This is a top-10 common password'
+ expect(page).to have_content t('zxcvbn.feedback.this_is_a_top_10_common_password')
end
end
@@ -92,7 +92,7 @@
click_button t('forms.buttons.continue')
- expect(page).to have_content t('zxcvbn.feedback.This is a top-10 common password')
+ expect(page).to have_content t('zxcvbn.feedback.this_is_a_top_10_common_password')
end
end
end
diff --git a/spec/forms/idv/finance_form_spec.rb b/spec/forms/idv/finance_form_spec.rb
index a2b0411a10a..2c2a027da57 100644
--- a/spec/forms/idv/finance_form_spec.rb
+++ b/spec/forms/idv/finance_form_spec.rb
@@ -25,39 +25,58 @@
end
describe '#submit' do
- it 'adds ccn key to idv_params when valid' do
- expect(subject.submit(ccn: '12345678', finance_type: :ccn)).to eq true
+ context 'when the form is valid' do
+ let(:result) { subject.submit(ccn: '12345678', finance_type: :ccn) }
- expected_params = {
- ccn: '12345678',
- }
+ it 'returns a successful form response' do
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(true)
+ expect(result.errors).to be_empty
+ end
- expect(subject.idv_params).to eq expected_params
+ it 'adds ccn key to idv_params' do
+ expected_params = {
+ ccn: '12345678',
+ }
+ subject.submit(ccn: '12345678', finance_type: :ccn)
+ expect(subject.idv_params).to eq expected_params
+ end
end
- it 'fails when missing all finance fields' do
- expect(subject.submit(foo: 'bar')).to eq false
+ context 'when the form is invalid' do
+ it 'returns an unsuccessful form response' do
+ result = subject.submit(foo: 'bar')
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to be_present
+ end
end
context 'when CCN is invalid' do
- it 'fails when alpha' do
- expect(subject.submit(ccn: '1234567a', finance_type: :ccn)).to eq false
- expect(subject.errors[:ccn]).to eq([t('idv.errors.invalid_ccn')])
+ it 'returns an unsuccessful form response when alpha' do
+ result = subject.submit(ccn: '1234567a', finance_type: :ccn)
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors[:ccn]).to eq([t('idv.errors.invalid_ccn')])
end
- it 'fails when long' do
- expect(subject.submit(ccn: '123456789', finance_type: :ccn)).to eq false
- expect(subject.errors[:ccn]).to eq([t('idv.errors.invalid_ccn')])
+ it 'returns an unsuccessful form response when long' do
+ result = subject.submit(ccn: '123456789', finance_type: :ccn)
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors[:ccn]).to eq([t('idv.errors.invalid_ccn')])
end
- it 'fails when short' do
- expect(subject.submit(ccn: '1234567', finance_type: :ccn)).to eq false
- expect(subject.errors[:ccn]).to eq([t('idv.errors.invalid_ccn')])
+ it 'returns an unsuccessful form response when short' do
+ result = subject.submit(ccn: '1234567', finance_type: :ccn)
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors[:ccn]).to eq([t('idv.errors.invalid_ccn')])
end
end
context 'any non-ccn financial value is less than the minimum allowed digits' do
- it 'fails' do
+ it 'returns an unsuccessful form response' do
finance_types = Idv::FinanceForm::FINANCE_TYPES
short_value = '1' * (FormFinanceValidator::VALID_MINIMUM_LENGTH - 1)
@@ -65,8 +84,10 @@
next if type == :ccn
params = { type => short_value, finance_type: type }
- expect(subject.submit(params)).to eq false
- expect(subject.errors[type]).to eq([t(
+ result = subject.submit(params)
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors[type]).to eq([t(
'idv.errors.finance_number_length',
minimum: FormFinanceValidator::VALID_MINIMUM_LENGTH,
maximum: FormFinanceValidator::VALID_MAXIMUM_LENGTH
@@ -76,7 +97,7 @@
end
context 'any non-ccn financial value is over the max allowed digits' do
- it 'fails' do
+ it 'returns an unsuccessful form response' do
finance_types = Idv::FinanceForm::FINANCE_TYPES
long_value = '1' * (FormFinanceValidator::VALID_MAXIMUM_LENGTH + 1)
@@ -88,8 +109,10 @@
finance_type: symbolized_type,
}
- expect(subject.submit(params)).to eq false
- expect(subject.errors[symbolized_type]).to eq([t(
+ result = subject.submit(params)
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors[symbolized_type]).to eq([t(
'idv.errors.finance_number_length',
minimum: FormFinanceValidator::VALID_MINIMUM_LENGTH,
maximum: FormFinanceValidator::VALID_MAXIMUM_LENGTH
diff --git a/spec/forms/idv/phone_form_spec.rb b/spec/forms/idv/phone_form_spec.rb
index 7eb9673c2a1..0dcf1a3bf25 100644
--- a/spec/forms/idv/phone_form_spec.rb
+++ b/spec/forms/idv/phone_form_spec.rb
@@ -7,21 +7,41 @@
it_behaves_like 'a phone form'
describe '#submit' do
- it 'adds phone key to idv_params when valid' do
- subject.submit(phone: '703-555-1212')
+ context 'when the form is valid' do
+ it 'returns a successful form response' do
+ result = subject.submit(phone: '703-555-1212', international_code: 'US')
- expected_params = {
- phone: '+1 (703) 555-1212',
- }
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(true)
+ expect(result.errors).to be_empty
+ end
- expect(subject.idv_params).to eq expected_params
+ it 'adds phone key to idv_params' do
+ subject.submit(phone: '703-555-1212', international_code: 'US')
+
+ expected_params = {
+ phone: '7035551212',
+ }
+
+ expect(subject.idv_params).to eq expected_params
+ end
+ end
+
+ context 'when the form is invalid' do
+ it 'returns an unsuccessful form response' do
+ result = subject.submit(phone: 'Im not a phone number 🙃', international_code: 'US')
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to include(:phone)
+ end
end
it 'adds phone_confirmed_at key to idv_params when submitted phone equals user phone' do
- subject.submit(phone: '+1 (202) 555-1212')
+ subject.submit(phone: '+1 (202) 555-1212', international_code: 'US')
expected_params = {
- phone: user.phone,
+ phone: '2025551212',
phone_confirmed_at: user.phone_confirmed_at,
}
diff --git a/spec/forms/idv/profile_form_spec.rb b/spec/forms/idv/profile_form_spec.rb
index 60a8b935e7e..3c0987ef2ad 100644
--- a/spec/forms/idv/profile_form_spec.rb
+++ b/spec/forms/idv/profile_form_spec.rb
@@ -19,6 +19,28 @@
}
end
+ describe '#submit' do
+ context 'when the form is valid' do
+ it 'returns a successful form response' do
+ result = subject.submit(profile_attrs)
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(true)
+ expect(result.errors).to be_empty
+ end
+ end
+
+ context 'when the form is invalid' do
+ before { profile_attrs[:dob] = nil }
+
+ it 'returns an unsuccessful form response' do
+ result = subject.submit(profile_attrs)
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to include(:dob)
+ end
+ end
+ end
+
describe 'presence validations' do
it 'is invalid when required attribute is not present' do
%i[first_name last_name ssn dob address1 city state zipcode].each do |attr|
diff --git a/spec/forms/otp_delivery_selection_form_spec.rb b/spec/forms/otp_delivery_selection_form_spec.rb
index 0441b6cf884..e50d2687ae5 100644
--- a/spec/forms/otp_delivery_selection_form_spec.rb
+++ b/spec/forms/otp_delivery_selection_form_spec.rb
@@ -1,7 +1,8 @@
require 'rails_helper'
describe OtpDeliverySelectionForm do
- subject { OtpDeliverySelectionForm.new(build_stubbed(:user)) }
+ let(:phone_to_deliver_to) { '+1 (202) 555-1234' }
+ subject { OtpDeliverySelectionForm.new(build_stubbed(:user), phone_to_deliver_to) }
describe 'otp_delivery_preference inclusion validation' do
it 'is invalid when otp_delivery_preference is neither sms nor voice' do
@@ -18,6 +19,8 @@
extra = {
otp_delivery_preference: 'sms',
resend: true,
+ country_code: '1',
+ area_code: '202',
}
result = instance_double(FormResponse)
@@ -35,6 +38,8 @@
extra = {
otp_delivery_preference: 'foo',
resend: nil,
+ country_code: '1',
+ area_code: '202',
}
result = instance_double(FormResponse)
@@ -48,7 +53,7 @@
context 'when otp_delivery_preference is the same as the user otp_delivery_preference' do
it 'does not update the user' do
user = build_stubbed(:user, otp_delivery_preference: 'sms')
- form = OtpDeliverySelectionForm.new(user)
+ form = OtpDeliverySelectionForm.new(user, phone_to_deliver_to)
expect(UpdateUser).to_not receive(:new)
@@ -59,7 +64,7 @@
context 'when otp_delivery_preference is different from the user otp_delivery_preference' do
it 'updates the user' do
user = build_stubbed(:user, otp_delivery_preference: 'voice')
- form = OtpDeliverySelectionForm.new(user)
+ form = OtpDeliverySelectionForm.new(user, phone_to_deliver_to)
attributes = { otp_delivery_preference: 'sms' }
updated_user = instance_double(UpdateUser)
diff --git a/spec/forms/two_factor_setup_form_spec.rb b/spec/forms/two_factor_setup_form_spec.rb
index 515a318f1c7..7af36cef7a6 100644
--- a/spec/forms/two_factor_setup_form_spec.rb
+++ b/spec/forms/two_factor_setup_form_spec.rb
@@ -2,9 +2,19 @@
describe TwoFactorSetupForm, type: :model do
let(:user) { build_stubbed(:user) }
- let(:valid_phone) { '+1 (202) 202-2020' }
+ let(:phone) { '+1 (202) 202-2020' }
+ let(:international_code) { 'US' }
+ let(:params) do
+ {
+ phone: phone,
+ international_code: 'US',
+ otp_delivery_preference: 'sms',
+ }
+ end
subject { TwoFactorSetupForm.new(user) }
+ it_behaves_like 'an otp delivery preference form'
+
it do
is_expected.
to validate_presence_of(:phone).
@@ -12,12 +22,28 @@
end
describe 'phone validation' do
- it 'uses the phony_rails gem with country option set to US' do
+ it 'uses the phony_rails gem' do
phone_validator = subject._validators.values.flatten.
detect { |v| v.class == PhonyPlausibleValidator }
- expect(phone_validator.options).
- to eq(country_code: 'US', presence: true, message: :improbable_phone)
+ expect(phone_validator.options[:presence]).to eq(true)
+ expect(phone_validator.options[:message]).to eq(:improbable_phone)
+ expect(phone_validator.options).to include(:international_code)
+ end
+
+ it do
+ should validate_inclusion_of(:international_code).
+ in_array(PhoneNumberCapabilities::INTERNATIONAL_CODES.keys)
+ end
+
+ it 'validates that the number matches the requested international code' do
+ params[:phone] = '123 123 1234'
+ params[:international_code] = 'MA'
+ result = subject.submit(params)
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to include(:phone)
end
end
@@ -32,14 +58,17 @@
}
result = instance_double(FormResponse)
+ params[:phone] = user.phone
+
expect(FormResponse).to receive(:new).
with(success: true, errors: {}, extra: extra).and_return(result)
- expect(form.submit(phone: user.phone, otp_delivery_preference: 'sms')).
- to eq result
+ expect(form.submit(params)).to eq result
end
end
context 'when phone is not already taken' do
+ let(:phone) { '+1 (703) 555-1212' }
+
it 'is valid' do
extra = {
otp_delivery_preference: 'sms',
@@ -48,14 +77,13 @@
expect(FormResponse).to receive(:new).
with(success: true, errors: {}, extra: extra).and_return(result)
- expect(subject.submit(phone: '+1 (703) 555-1212', otp_delivery_preference: 'sms')).
- to eq result
+ expect(subject.submit(params)).to eq result
end
end
context 'when phone is same as current user' do
it 'is valid' do
- user = build_stubbed(:user, phone: valid_phone)
+ user = build_stubbed(:user, phone: phone)
form = TwoFactorSetupForm.new(user)
extra = {
otp_delivery_preference: 'sms',
@@ -64,12 +92,13 @@
expect(FormResponse).to receive(:new).
with(success: true, errors: {}, extra: extra).and_return(result)
- expect(form.submit(phone: valid_phone, otp_delivery_preference: 'sms')).
- to eq result
+ expect(form.submit(params)).to eq result
end
end
context 'when phone is empty' do
+ let(:phone) { '' }
+
it 'does not add already taken errors' do
errors = {
phone: [t('errors.messages.improbable_phone')],
@@ -81,8 +110,7 @@
expect(FormResponse).to receive(:new).
with(success: false, errors: errors, extra: extra).and_return(result)
- expect(subject.submit(phone: '', otp_delivery_preference: 'sms')).
- to eq result
+ expect(subject.submit(params)).to eq result
end
end
end
@@ -95,7 +123,7 @@
expect(UpdateUser).to_not receive(:new)
- form.submit(phone: '+1 (703) 555-1212', otp_delivery_preference: 'sms')
+ form.submit(params)
end
end
@@ -111,7 +139,7 @@
expect(updated_user).to receive(:call)
- form.submit(phone: '+1 (703) 555-1212', otp_delivery_preference: 'sms')
+ form.submit(params)
end
end
end
diff --git a/spec/helpers/locale_helper_spec.rb b/spec/helpers/locale_helper_spec.rb
new file mode 100644
index 00000000000..de3dc7818d1
--- /dev/null
+++ b/spec/helpers/locale_helper_spec.rb
@@ -0,0 +1,31 @@
+require 'rails_helper'
+
+RSpec.describe LocaleHelper do
+ include LocaleHelper
+
+ describe '#locale_url_param' do
+ context 'in the default locale' do
+ before { I18n.locale = :en }
+
+ it 'is nil' do
+ expect(locale_url_param).to be_nil
+ end
+ end
+
+ context 'in French (a non-default locale)' do
+ before { I18n.locale = :fr }
+
+ it 'is that locale' do
+ expect(locale_url_param).to eq(:fr)
+ end
+ end
+
+ context 'in Spanish (a non-default locale)' do
+ before { I18n.locale = :es }
+
+ it 'is that locale' do
+ expect(locale_url_param).to eq(:es)
+ end
+ end
+ end
+end
diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb
index 8dd0319a723..4c753acbc9b 100644
--- a/spec/i18n_spec.rb
+++ b/spec/i18n_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'i18n/tasks'
+require 'yaml_normalizer'
RSpec.describe 'I18n' do
let(:i18n) { I18n::Tasks::BaseTask.new }
@@ -20,4 +21,59 @@
"#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
)
end
+
+ root_dir = File.expand_path(File.join(File.dirname(__FILE__), '../'))
+
+ Dir[File.join(root_dir, '/config/locales/**/*.yml')].each do |full_path|
+ i18n_file = full_path.sub("#{root_dir}/", '')
+
+ describe i18n_file do
+ it 'has only lower_snake_case keys' do
+ keys = flatten_hash(YAML.load_file(full_path)).keys
+
+ bad_keys = keys.reject { |key| key =~ /^[a-z0-9_.]+$/ }
+
+ expect(bad_keys).to be_empty
+ end
+
+ it 'has only has XML-safe identifiers (keys start with a letter)' do
+ keys = flatten_hash(YAML.load_file(full_path)).keys
+
+ bad_keys = keys.select { |key| key.split('.').any? { |part| part =~ /^[0-9]/ } }
+
+ expect(bad_keys).to be_empty
+ end
+
+ it 'has correctly-formatted interpolation values' do
+ bad_keys = flatten_hash(YAML.load_file(full_path)).select do |_key, value|
+ next unless value.is_a?(String)
+
+ interpolation_names = value.scan(/%\{([^\}]+)\}/).flatten
+
+ interpolation_names.any? { |name| name.downcase != name }
+ end
+
+ expect(bad_keys).to be_empty
+ end
+
+ it 'is formatted as normalized YAML (run scripts/normalize-yaml)' do
+ normalized_yaml = YAML.dump(YamlNormalizer.chomp_each(YAML.load_file(full_path)))
+
+ expect(File.read(full_path)).to eq(normalized_yaml)
+ end
+ end
+ end
+
+ def flatten_hash(hash, parent_keys: [], out_hash: {}, &block)
+ hash.each do |key, value|
+ if value.is_a?(Hash)
+ flatten_hash(value, parent_keys: parent_keys + [key], out_hash: out_hash, &block)
+ else
+ flat_key = [*parent_keys, key].join('.')
+ out_hash[flat_key] = value
+ end
+ end
+
+ out_hash
+ end
end
diff --git a/spec/jobs/sms_otp_sender_job_spec.rb b/spec/jobs/sms_otp_sender_job_spec.rb
index 1afc5369d23..a76fb301a4d 100644
--- a/spec/jobs/sms_otp_sender_job_spec.rb
+++ b/spec/jobs/sms_otp_sender_job_spec.rb
@@ -3,14 +3,26 @@
describe SmsOtpSenderJob do
describe '.perform' do
- it 'sends a message containing the OTP code to the mobile number', twilio: true do
+ before do
+ reset_job_queues
TwilioService.telephony_service = FakeSms
+ FakeSms.messages = []
+ end
+ subject(:perform) do
SmsOtpSenderJob.perform_now(
code: '1234',
- phone: '555-5555',
- otp_created_at: Time.zone.now.to_s
+ phone: '+1 (888) 555-5555',
+ otp_created_at: otp_created_at
)
+ end
+
+ let(:otp_created_at) { Time.zone.now.to_s }
+
+ it 'sends a message containing the OTP code to the mobile number', twilio: true do
+ TwilioService.telephony_service = FakeSms
+
+ perform
messages = FakeSms.messages
@@ -19,43 +31,49 @@
msg = messages.first
expect(msg.from).to match(/(\+19999999999|\+12222222222)/)
- expect(msg.to).to eq('555-5555')
- expect(msg.body).to include('one-time security code')
- expect(msg.body).to include('1234')
+ expect(msg.to).to eq('+1 (888) 555-5555')
+ expect(msg.body).to eq(I18n.t('jobs.sms_otp_sender_job.message', code: '1234', app: APP_NAME))
end
- it 'does not send if the OTP code is expired' do
- reset_job_queues
- TwilioService.telephony_service = FakeSms
- FakeSms.messages = []
- otp_expiration_period = Devise.direct_otp_valid_for
+ context 'if the OTP code is expired' do
+ let(:otp_created_at) do
+ otp_expiration_period = Devise.direct_otp_valid_for
+ otp_expiration_period.ago.to_s
+ end
- SmsOtpSenderJob.perform_now(
- code: '1234',
- phone: '555-5555',
- otp_created_at: otp_expiration_period.ago.to_s
- )
+ it 'does not send if the OTP code is expired' do
+ perform
- messages = FakeSms.messages
- expect(messages.size).to eq(0)
- expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to eq []
+ messages = FakeSms.messages
+ expect(messages.size).to eq(0)
+ expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to eq []
+ end
end
- it 'respects time zone' do
- reset_job_queues
- TwilioService.telephony_service = FakeSms
- FakeSms.messages = []
- otp_expiration_period = Devise.direct_otp_valid_for
+ context 'in other time zones' do
+ let(:otp_created_at) do
+ otp_expiration_period = Devise.direct_otp_valid_for
+ otp_expiration_period.ago.strftime('%F %r')
+ end
- SmsOtpSenderJob.perform_now(
- code: '1234',
- phone: '555-5555',
- otp_created_at: otp_expiration_period.ago.strftime('%F %r')
- )
+ it 'respects time zone' do
+ perform
- messages = FakeSms.messages
- expect(messages.size).to eq(0)
- expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to eq []
+ messages = FakeSms.messages
+ expect(messages.size).to eq(0)
+ expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to eq []
+ end
+ end
+
+ it 'sanitizes phone numbers embedded in error messages from Twilio' do
+ raw_message = "The 'To' number +1 (888) 555-5555 is not a valid phone number"
+ sanitized_message = "The 'To' number +# (###) ###-#### is not a valid phone number"
+
+ expect_any_instance_of(TwilioService).to receive(:send_sms).
+ and_raise(Twilio::REST::RequestError.new(raw_message))
+
+ expect { perform }.
+ to raise_error(Twilio::REST::RequestError, sanitized_message)
end
end
end
diff --git a/spec/jobs/vendor_validator_job_spec.rb b/spec/jobs/vendor_validator_job_spec.rb
new file mode 100644
index 00000000000..8e73c937a69
--- /dev/null
+++ b/spec/jobs/vendor_validator_job_spec.rb
@@ -0,0 +1,67 @@
+require 'rails_helper'
+
+RSpec.describe VendorValidatorJob do
+ let(:result_id) { SecureRandom.uuid }
+ let(:vendor_validator_class) { 'Idv::PhoneValidator' }
+ let(:vendor) { :mock }
+ let(:vendor_params) { '+1 (888) 123-4567' }
+ let(:applicant) { Proofer::Applicant.new(first_name: 'Test') }
+ let(:applicant_json) { applicant.to_json }
+ let(:vendor_session_id) { SecureRandom.uuid }
+
+ subject(:job) { VendorValidatorJob.new }
+
+ describe '#perform' do
+ subject(:perform) do
+ job.perform(
+ result_id: result_id,
+ vendor_validator_class: vendor_validator_class,
+ vendor: vendor,
+ vendor_params: vendor_params,
+ applicant_json: applicant_json,
+ vendor_session_id: vendor_session_id
+ )
+ end
+
+ it 'calls out to a vendor and serializes the result' do
+ expect(Idv::PhoneValidator).to receive(:new).
+ with(
+ applicant: kind_of(Proofer::Applicant),
+ vendor: vendor,
+ vendor_params: vendor_params,
+ vendor_session_id: vendor_session_id
+ ).and_call_original
+
+ before_result = VendorValidatorResultStorage.new.load(result_id)
+ expect(before_result).to be_nil
+
+ perform
+
+ after_result = VendorValidatorResultStorage.new.load(result_id)
+ expect(after_result).to be_a(Idv::VendorResult)
+ end
+
+ context 'when the vendor throws an exception' do
+ let(:vendor_validator_class) { 'Idv::ProfileValidator' }
+ let(:applicant) { Proofer::Applicant.new(first_name: 'Fail') }
+
+ let(:exception_msg) { 'Failed to contact proofing vendor' }
+
+ it 'notifies NewRelic and does not raise' do
+ expect(NewRelic::Agent).to receive(:notice_error).
+ with(kind_of(StandardError))
+
+ perform
+ end
+
+ it 'writes a failure result to redis' do
+ perform
+
+ result = VendorValidatorResultStorage.new.load(result_id)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to eq(agent: [exception_msg])
+ expect(result.reasons).to eq([exception_msg])
+ end
+ end
+ end
+end
diff --git a/spec/lib/feature_management_spec.rb b/spec/lib/feature_management_spec.rb
index fbdd9c8df5e..676fe6ef723 100644
--- a/spec/lib/feature_management_spec.rb
+++ b/spec/lib/feature_management_spec.rb
@@ -159,4 +159,49 @@
end
end
end
+
+ describe '.no_pii_mode?' do
+ let(:proofing_vendor) { :mock }
+ let(:enable_identity_verification) { false }
+
+ before do
+ allow_any_instance_of(Idv::Vendor).to receive(:pick).and_return(proofing_vendor)
+ allow(Figaro.env).to receive(:enable_identity_verification).
+ and_return(enable_identity_verification.to_json)
+ end
+
+ subject(:no_pii_mode?) { FeatureManagement.no_pii_mode? }
+
+ context 'with mock ID-proofing vendors' do
+ let(:proofing_vendor) { :mock }
+
+ context 'with identity verification enabled' do
+ let(:enable_identity_verification) { true }
+
+ it { expect(no_pii_mode?).to eq(true) }
+ end
+
+ context 'with identity verification disabled' do
+ let(:enable_identity_verification) { false }
+
+ it { expect(no_pii_mode?).to eq(false) }
+ end
+ end
+
+ context 'with real ID-proofing vendors' do
+ let(:proofing_vendor) { :not_mock }
+
+ context 'with identity verification enabled' do
+ let(:enable_identity_verification) { true }
+
+ it { expect(no_pii_mode?).to eq(false) }
+ end
+
+ context 'with identity verification disabled' do
+ let(:enable_identity_verification) { false }
+
+ it { expect(no_pii_mode?).to eq(false) }
+ end
+ end
+ end
end
diff --git a/spec/lib/yaml_normalizer_spec.rb b/spec/lib/yaml_normalizer_spec.rb
new file mode 100644
index 00000000000..3ee77d157be
--- /dev/null
+++ b/spec/lib/yaml_normalizer_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+require 'yaml_normalizer'
+
+RSpec.describe YamlNormalizer do
+ describe '.run' do
+ let(:tempfile) { Tempfile.new }
+
+ before do
+ File.open(tempfile.path, 'w') do |f|
+ f.puts <<~YAML
+ some:
+ key: >
+ quoted
+ value1: 'quoted'
+ value2: "quoted"
+ YAML
+ end
+ end
+
+ after { tempfile.unlink }
+
+ it 'normalizes a YAML files in-place' do
+ YamlNormalizer.run([tempfile.path])
+
+ expect(File.read(tempfile.path)).to eq <<~YAML
+ ---
+ some:
+ key: quoted
+ value1: quoted
+ value2: quoted
+ YAML
+ end
+ end
+
+ describe '.chomp_each' do
+ context 'trailing newlines' do
+ let(:original) do
+ {
+ key: 'a: ',
+ array: %W[b\n c\n],
+ nested: {
+ value: "d\n",
+ },
+ }
+ end
+
+ let(:trimmed) do
+ {
+ key: 'a: ',
+ array: %w[b c],
+ nested: { value: 'd' },
+ }
+ end
+
+ it 'in-place, recursively trims trailing newlines from all strings in a hash' do
+ YamlNormalizer.chomp_each(original)
+
+ expect(original).to eq(trimmed)
+ end
+ end
+
+ context 'trailing spaces' do
+ let(:original) { { a: 'a : ', b: 'b ', c: "c : \n" } }
+ let(:trimmed) { { a: 'a : ', b: 'b', c: 'c : ' } }
+
+ it 'trims trailing spaces, except after a colon' do
+ YamlNormalizer.chomp_each(original)
+
+ expect(original).to eq(trimmed)
+ end
+ end
+
+ context 'leading newlines' do
+ let(:original) { { a: "\n\na b c", b: "a\nb" } }
+ let(:trimmed) { { a: "a b c", b: "a\nb" } }
+
+ it 'trims leading newlines but not intermediate ones' do
+ YamlNormalizer.chomp_each(original)
+
+ expect(original).to eq(trimmed)
+ end
+ end
+
+ context 'a nil value' do
+ let(:original) { { a: nil } }
+ let(:trimmed) { { a: nil } }
+
+ it 'does not blow up' do
+ YamlNormalizer.chomp_each(original)
+
+ expect(original).to eq(trimmed)
+ end
+ end
+ end
+end
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
index cfce7ebf52d..c3b16d2cd8a 100644
--- a/spec/mailers/user_mailer_spec.rb
+++ b/spec/mailers/user_mailer_spec.rb
@@ -67,6 +67,14 @@
)
expect_email_body_to_have_help_and_contact_links
end
+
+ context 'in a non-default locale' do
+ before { I18n.locale = :fr }
+
+ it 'links to the correct locale' do
+ expect(mail.html_part.body).to include(root_url(locale: :fr))
+ end
+ end
end
describe 'phone_changed' do
diff --git a/spec/models/otp_requests_tracker_spec.rb b/spec/models/otp_requests_tracker_spec.rb
new file mode 100644
index 00000000000..21c0f8fdde1
--- /dev/null
+++ b/spec/models/otp_requests_tracker_spec.rb
@@ -0,0 +1,51 @@
+require 'rails_helper'
+
+describe OtpRequestsTracker do
+ describe '.find_or_create_with_phone' do
+ let(:phone) { '+1 703 555 1212' }
+ let(:phone_fingerprint) { Pii::Fingerprinter.fingerprint(phone) }
+
+ context 'match found' do
+ it 'returns the existing record and does not change it' do
+ OtpRequestsTracker.create(
+ phone_fingerprint: phone_fingerprint,
+ otp_send_count: 3,
+ otp_last_sent_at: Time.zone.now - 1.hour
+ )
+
+ existing = OtpRequestsTracker.where(phone_fingerprint: phone_fingerprint).first
+
+ expect { OtpRequestsTracker.find_or_create_with_phone(phone) }.
+ to_not change(OtpRequestsTracker, :count)
+ expect { OtpRequestsTracker.find_or_create_with_phone(phone) }.
+ to_not change { existing.otp_send_count }
+ expect { OtpRequestsTracker.find_or_create_with_phone(phone) }.
+ to_not change { existing.otp_last_sent_at }
+ end
+ end
+
+ context 'match not found' do
+ it 'creates new record with otp_send_count = 0 and otp_last_sent_at = current time' do
+ expect { OtpRequestsTracker.find_or_create_with_phone(phone) }.
+ to change(OtpRequestsTracker, :count).by(1)
+
+ existing = OtpRequestsTracker.where(phone_fingerprint: phone_fingerprint).first
+
+ expect(existing.otp_send_count).to eq 0
+ expect(existing.otp_last_sent_at).to be_within(2.seconds).of(Time.zone.now)
+ end
+ end
+
+ context 'race condition' do
+ it 'retries once, then raises ActiveRecord::RecordNotUnique' do
+ tracker = OtpRequestsTracker.new
+ allow(OtpRequestsTracker).to receive(:where).
+ and_raise(ActiveRecord::RecordNotUnique.new(tracker))
+
+ expect(OtpRequestsTracker).to receive(:where).exactly(:once)
+ expect { OtpRequestsTracker.find_or_create_with_phone(phone) }.
+ to raise_error ActiveRecord::RecordNotUnique
+ end
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb b/spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb
index b264973f0be..2a9aa55a07e 100644
--- a/spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb
+++ b/spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb
@@ -1,11 +1,95 @@
require 'rails_helper'
describe TwoFactorAuthCode::PhoneDeliveryPresenter do
- let(:presenter) { TwoFactorAuthCode::PhoneDeliveryPresenter.new({}) }
+ let(:data) do
+ {
+ code_value: '123abc',
+ totp_enabled: false,
+ phone_number: '***-***-5000',
+ unconfirmed_phone: false,
+ otp_delivery_preference: 'sms',
+ }
+ end
+ let(:view) { ActionController::Base.new.view_context }
+ let(:presenter) { TwoFactorAuthCode::PhoneDeliveryPresenter.new(data: data, view: view) }
it 'is a subclass of GenericDeliveryPresenter' do
expect(TwoFactorAuthCode::PhoneDeliveryPresenter.superclass).to(
be(TwoFactorAuthCode::GenericDeliveryPresenter)
)
end
+
+ describe '#fallback_links' do
+ context 'with totp enabled' do
+ before do
+ data[:totp_enabled] = true
+ end
+
+ context 'voice otp delivery supported' do
+ it 'renders an auth app fallback link' do
+ expect(presenter.fallback_links.join(' ')).to include(
+ I18n.t('links.two_factor_authentication.app')
+ )
+ end
+
+ it 'renders a voice otp link' do
+ expect(presenter.fallback_links.join(' ')).to include(
+ I18n.t('links.two_factor_authentication.voice')
+ )
+ end
+ end
+
+ context 'voice otp deliver unsupported' do
+ before do
+ data[:voice_otp_delivery_unsupported] = true
+ end
+
+ it 'renders an auth app fallback link' do
+ expect(presenter.fallback_links.join(' ')).to include(
+ I18n.t('links.two_factor_authentication.app')
+ )
+ end
+
+ it 'does not render a voice otp link' do
+ expect(presenter.fallback_links.join(' ')).to_not include(
+ I18n.t('links.two_factor_authentication.voice')
+ )
+ end
+ end
+ end
+
+ context 'without totp enabled' do
+ context 'voice otp delivery supported' do
+ it 'does not render an auth app fallback link' do
+ expect(presenter.fallback_links.join(' ')).to_not include(
+ I18n.t('links.two_factor_authentication.app')
+ )
+ end
+
+ it 'renders a voice otp link' do
+ expect(presenter.fallback_links.join(' ')).to include(
+ I18n.t('links.two_factor_authentication.voice')
+ )
+ end
+ end
+
+ context 'voice otp deliver unsupported' do
+ before do
+ data[:voice_otp_delivery_unsupported] = true
+ end
+
+ it 'does not render an auth app fallback link' do
+ expect(presenter.fallback_links.join(' ')).to_not include(
+ I18n.t('links.two_factor_authentication.app')
+ )
+ end
+
+ it 'does not render a voice otp link' do
+ expect(presenter.fallback_links.join(' ')).to_not include(
+ I18n.t('links.two_factor_authentication.voice')
+ )
+ end
+ end
+ end
+ end
end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 1ef54850cd0..5c106cb0494 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -51,6 +51,12 @@
FakeSms.messages = []
FakeVoiceCall.calls = []
end
+
+ config.before(:each, idv_job: true) do
+ allow(VendorValidatorJob).to receive(:perform_later) do |*args|
+ VendorValidatorJob.perform_now(*args)
+ end
+ end
end
Sidekiq::Testing.inline!
diff --git a/spec/services/idv/financials_step_spec.rb b/spec/services/idv/financials_step_spec.rb
index 4e2407e82cb..1445ab394f7 100644
--- a/spec/services/idv/financials_step_spec.rb
+++ b/spec/services/idv/financials_step_spec.rb
@@ -7,58 +7,49 @@
idvs.vendor = :mock
idvs
end
- let(:idv_finance_form) { Idv::FinanceForm.new(idv_session.params) }
+ let(:idv_form_params) { idv_session.params }
- def build_step(params)
+ def build_step(vendor_validator_result)
described_class.new(
- idv_form: idv_finance_form,
+ idv_form_params: idv_form_params,
idv_session: idv_session,
- params: params
+ vendor_validator_result: vendor_validator_result
)
end
describe '#submit' do
- it 'returns FormResponse with success: false for invalid params' do
- step = build_step(finance_type: :ccn, ccn: '1234')
- errors = { ccn: [t('idv.errors.invalid_ccn')] }
-
- response = instance_double(FormResponse)
- allow(FormResponse).to receive(:new).and_return(response)
- submission = step.submit
-
- expect(submission).to eq response
- expect(FormResponse).to have_received(:new).
- with(success: false, errors: errors)
- expect(idv_session.financials_confirmation).to eq false
- end
-
it 'returns FormResponse with success: true for mock-happy CCN' do
- step = build_step(finance_type: :ccn, ccn: '12345678')
+ step = build_step(
+ Idv::VendorResult.new(
+ success: true,
+ errors: {}
+ )
+ )
+
+ result = step.submit
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(true)
+ expect(result.errors).to be_empty
- response = instance_double(FormResponse)
- allow(FormResponse).to receive(:new).and_return(response)
-
- submission = step.submit
-
- expect(submission).to eq response
- expect(FormResponse).to have_received(:new).
- with(success: true, errors: {})
expect(idv_session.financials_confirmation).to eq true
- expect(idv_session.params).to eq idv_finance_form.idv_params
+ expect(idv_session.params).to eq idv_form_params
end
it 'returns FormResponse with success: false for mock-sad CCN' do
- step = build_step(finance_type: :ccn, ccn: '00000000')
-
errors = { ccn: ['The ccn could not be verified.'] }
- response = instance_double(FormResponse)
- allow(FormResponse).to receive(:new).and_return(response)
- submission = step.submit
+ step = build_step(
+ Idv::VendorResult.new(
+ success: false,
+ errors: errors
+ )
+ )
+
+ result = step.submit
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to eq(errors)
- expect(submission).to eq response
- expect(FormResponse).to have_received(:new).
- with(success: false, errors: errors)
expect(idv_session.financials_confirmation).to eq false
end
end
diff --git a/spec/services/idv/financials_validator_spec.rb b/spec/services/idv/financials_validator_spec.rb
index 0292c6b204d..2010f5bd8bb 100644
--- a/spec/services/idv/financials_validator_spec.rb
+++ b/spec/services/idv/financials_validator_spec.rb
@@ -3,13 +3,9 @@
describe Idv::FinancialsValidator do
let(:user) { build(:user) }
- let(:idv_session) do
- idvs = Idv::Session.new(user_session: {}, current_user: user, issuer: nil)
- idvs.vendor = :mock
- idvs
- end
-
- let(:session_id) { idv_session.vendor_session_id }
+ let(:applicant) { Proofer::Applicant.new({}) }
+ let(:vendor) { :mock }
+ let(:vendor_session_id) { SecureRandom.uuid }
let(:params) do
{ ccn: '123-45-6789' }
@@ -17,38 +13,42 @@
let(:confirmation) { instance_double(Proofer::Confirmation) }
- subject { Idv::FinancialsValidator.new(idv_session: idv_session, vendor_params: params) }
+ subject do
+ Idv::FinancialsValidator.new(
+ applicant: applicant,
+ vendor: vendor,
+ vendor_params: params,
+ vendor_session_id: vendor_session_id
+ )
+ end
def stub_agent_calls
agent = instance_double(Idv::Agent)
allow(Idv::Agent).to receive(:new).
- with(applicant: idv_session.applicant, vendor: :mock).
+ with(applicant: applicant, vendor: vendor).
and_return(agent)
expect(agent).to receive(:submit_financials).
- with(params, idv_session.vendor_session_id).and_return(confirmation)
+ with(params, vendor_session_id).and_return(confirmation)
end
- describe '#success?' do
- it 'returns Proofer::Confirmation#success?' do
+ describe '#result' do
+ it 'has success' do
stub_agent_calls
success_string = 'true'
-
expect(confirmation).to receive(:success?).and_return(success_string)
- expect(subject.success?).to eq success_string
+ expect(subject.result.success?).to eq success_string
end
- end
- describe '#error' do
- it 'returns Proofer::Confirmation#errors' do
+ it 'has errors' do
stub_agent_calls
error_string = 'mucho errors'
expect(confirmation).to receive(:errors).and_return(error_string)
- expect(subject.errors).to eq error_string
+ expect(subject.result.errors).to eq error_string
end
end
end
diff --git a/spec/services/idv/phone_step_spec.rb b/spec/services/idv/phone_step_spec.rb
index 3a609f56829..2ecb520ade6 100644
--- a/spec/services/idv/phone_step_spec.rb
+++ b/spec/services/idv/phone_step_spec.rb
@@ -10,53 +10,77 @@
idvs.applicant = Proofer::Applicant.new first_name: 'Some'
idvs
end
+ let(:idv_form_params) { { phone: '555-555-0000', phone_confirmed_at: nil } }
let(:idv_phone_form) { Idv::PhoneForm.new(idv_session.params, user) }
- def build_step(params)
+ def build_step(vendor_validator_result)
described_class.new(
- idv_form: idv_phone_form,
idv_session: idv_session,
- params: params
+ idv_form_params: idv_form_params,
+ vendor_validator_result: vendor_validator_result
)
end
describe '#submit' do
- it 'returns false for invalid-looking phone' do
- step = build_step(phone: '555')
-
- errors = { phone: [invalid_phone_message] }
+ it 'returns true for mock-happy phone' do
+ step = build_step(
+ Idv::VendorResult.new(
+ success: true,
+ errors: {}
+ )
+ )
- result = instance_double(FormResponse)
+ result = step.submit
- expect(FormResponse).to receive(:new).
- with(success: false, errors: errors).and_return(result)
- expect(step.submit).to eq result
- expect(idv_session.phone_confirmation).to eq false
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(true)
+ expect(result.errors).to be_empty
+ expect(idv_session.vendor_phone_confirmation).to eq true
+ expect(idv_session.params).to eq idv_phone_form.idv_params
end
- it 'returns true for mock-happy phone' do
- step = build_step(phone: '555-555-0000')
+ it 'returns false for mock-sad phone' do
+ idv_form_params[:phone] = '555-555-5555'
+ errors = { phone: ['The phone number could not be verified.'] }
- result = instance_double(FormResponse)
+ step = build_step(
+ Idv::VendorResult.new(
+ success: false,
+ errors: errors
+ )
+ )
- expect(FormResponse).to receive(:new).with(success: true, errors: {}).
- and_return(result)
- expect(step.submit).to eq result
- expect(idv_session.phone_confirmation).to eq true
- expect(idv_session.params).to eq idv_phone_form.idv_params
+ result = step.submit
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to eq(errors)
+ expect(idv_session.vendor_phone_confirmation).to eq false
end
- it 'returns false for mock-sad phone' do
- step = build_step(phone: '555-555-5555')
+ it 'marks the phone number as confirmed by user if it matches 2FA phone' do
+ idv_form_params[:phone_confirmed_at] = Time.zone.now
+ step = build_step(
+ Idv::VendorResult.new(
+ success: true,
+ errors: {}
+ )
+ )
+ step.submit
- errors = { phone: ['The phone number could not be verified.'] }
+ expect(idv_session.user_phone_confirmation).to eq(true)
+ end
- result = instance_double(FormResponse)
+ it 'does not mark the phone number as confirmed by user if it does not match 2FA phone' do
+ step = build_step(
+ Idv::VendorResult.new(
+ success: true,
+ errors: {}
+ )
+ )
+ step.submit
- expect(FormResponse).to receive(:new).
- with(success: false, errors: errors).and_return(result)
- expect(step.submit).to eq result
- expect(idv_session.phone_confirmation).to eq false
+ expect(idv_session.user_phone_confirmation).to eq(false)
end
end
end
diff --git a/spec/services/idv/phone_validator_spec.rb b/spec/services/idv/phone_validator_spec.rb
index 83b849ca116..faa6755c778 100644
--- a/spec/services/idv/phone_validator_spec.rb
+++ b/spec/services/idv/phone_validator_spec.rb
@@ -3,13 +3,9 @@
describe Idv::PhoneValidator do
let(:user) { build(:user) }
- let(:idv_session) do
- idvs = Idv::Session.new(user_session: {}, current_user: user, issuer: nil)
- idvs.vendor = :mock
- idvs
- end
-
- let(:session_id) { idv_session.vendor_session_id }
+ let(:applicant) { Proofer::Applicant.new({}) }
+ let(:vendor) { :mock }
+ let(:vendor_session_id) { SecureRandom.uuid }
let(:params) do
{ phone: '202-555-1212' }
@@ -17,38 +13,43 @@
let(:confirmation) { instance_double(Proofer::Confirmation) }
- subject { Idv::PhoneValidator.new(idv_session: idv_session, vendor_params: params) }
+ subject do
+ Idv::PhoneValidator.new(
+ applicant: applicant,
+ vendor: vendor,
+ vendor_params: params,
+ vendor_session_id: vendor_session_id
+ )
+ end
def stub_agent_calls
agent = instance_double(Idv::Agent)
allow(Idv::Agent).to receive(:new).
- with(applicant: idv_session.applicant, vendor: :mock).
+ with(applicant: applicant, vendor: vendor).
and_return(agent)
expect(agent).to receive(:submit_phone).
- with(params, idv_session.vendor_session_id).and_return(confirmation)
+ with(params, vendor_session_id).and_return(confirmation)
end
- describe '#success?' do
- it 'returns Proofer::Confirmation#success?' do
+ describe '#result' do
+ it 'has success' do
stub_agent_calls
success_string = 'true'
expect(confirmation).to receive(:success?).and_return(success_string)
- expect(subject.success?).to eq success_string
+ expect(subject.result.success?).to eq success_string
end
- end
- describe '#error' do
- it 'returns Proofer::Confirmation#errors' do
+ it 'has errors' do
stub_agent_calls
error_string = 'mucho errors'
expect(confirmation).to receive(:errors).and_return(error_string)
- expect(subject.errors).to eq error_string
+ expect(subject.result.errors).to eq error_string
end
end
end
diff --git a/spec/services/idv/profile_step_spec.rb b/spec/services/idv/profile_step_spec.rb
index e51f6f48c25..cc7655d0599 100644
--- a/spec/services/idv/profile_step_spec.rb
+++ b/spec/services/idv/profile_step_spec.rb
@@ -18,127 +18,139 @@
}
end
- def build_step(params)
+ def build_step(params, vendor_validator_result)
+ idv_session.params.merge!(params)
+ idv_session.applicant = idv_session.vendor_params
+
described_class.new(
- idv_form: idv_profile_form,
- idv_session: idv_session,
- params: params
+ idv_form_params: params,
+ vendor_validator_result: vendor_validator_result,
+ idv_session: idv_session
)
end
describe '#submit' do
it 'succeeds with good params' do
- step = build_step(user_attrs)
-
- result = instance_double(FormResponse)
+ reasons = ['Everything looks good']
extra = {
idv_attempts_exceeded: false,
- vendor: { reasons: ['Everything looks good'] },
+ vendor: { reasons: reasons },
}
- expect(FormResponse).to receive(:new).
- with(success: true, errors: {}, extra: extra).and_return(result)
- expect(step.submit).to eq result
+ step = build_step(
+ user_attrs,
+ Idv::VendorResult.new(
+ success: true,
+ errors: {},
+ reasons: reasons,
+ normalized_applicant: Proofer::Applicant.new(first_name: 'Some')
+ )
+ )
+
+ result = step.submit
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(true)
+ expect(result.errors).to be_empty
+ expect(result.extra).to eq(extra)
expect(idv_session.profile_confirmation).to eq true
end
it 'fails with invalid SSN' do
- step = build_step(user_attrs.merge(ssn: '666-66-6666'))
-
+ reasons = ['The SSN was suspicious']
errors = { ssn: ['Unverified SSN.'] }
extra = {
idv_attempts_exceeded: false,
- vendor: { reasons: ['The SSN was suspicious'] },
+ vendor: { reasons: reasons },
}
- result = instance_double(FormResponse)
+ step = build_step(
+ user_attrs.merge(ssn: '666-66-6666'),
+ Idv::VendorResult.new(success: false, errors: errors, reasons: reasons)
+ )
- expect(FormResponse).to receive(:new).
- with(success: false, errors: errors, extra: extra).and_return(result)
- expect(step.submit).to eq result
- expect(idv_session.profile_confirmation).to be_nil
- end
-
- it 'fails when form validation fails' do
- step = build_step(user_attrs.merge(ssn: '6666'))
+ result = step.submit
- errors = { ssn: [t('idv.errors.pattern_mismatch.ssn')] }
- extra = {
- idv_attempts_exceeded: false,
- vendor: { reasons: nil },
- }
-
- result = instance_double(FormResponse)
-
- expect(FormResponse).to receive(:new).
- with(success: false, errors: errors, extra: extra).and_return(result)
- expect(step.submit).to eq result
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to eq(errors)
+ expect(result.extra).to eq(extra)
expect(idv_session.profile_confirmation).to be_nil
end
it 'fails with invalid first name' do
- step = build_step(user_attrs.merge(first_name: 'Bad'))
-
errors = { first_name: ['Unverified first name.'] }
-
- result = instance_double(FormResponse)
+ reasons = ['The name was suspicious']
extra = {
idv_attempts_exceeded: false,
- vendor: { reasons: ['The name was suspicious'] },
+ vendor: { reasons: reasons },
}
- expect(FormResponse).to receive(:new).
- with(success: false, errors: errors, extra: extra).and_return(result)
- expect(step.submit).to eq result
+ step = build_step(
+ user_attrs.merge(first_name: 'Bad'),
+ Idv::VendorResult.new(success: false, errors: errors, reasons: reasons)
+ )
+
+ result = step.submit
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to eq(errors)
+ expect(result.extra).to eq(extra)
expect(idv_session.profile_confirmation).to be_nil
end
it 'fails with invalid ZIP code on current address' do
- step = build_step(user_attrs.merge(zipcode: '00000'))
-
+ reasons = ['The ZIP code was suspicious']
errors = { zipcode: ['Unverified ZIP code.'] }
-
- result = instance_double(FormResponse)
extra = {
idv_attempts_exceeded: false,
- vendor: { reasons: ['The ZIP code was suspicious'] },
+ vendor: { reasons: reasons },
}
- expect(FormResponse).to receive(:new).
- with(success: false, errors: errors, extra: extra).and_return(result)
- expect(step.submit).to eq result
+ step = build_step(
+ user_attrs.merge(zipcode: '00000'),
+ Idv::VendorResult.new(success: false, errors: errors, reasons: reasons)
+ )
+
+ result = step.submit
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to eq(errors)
+ expect(result.extra).to eq(extra)
expect(idv_session.profile_confirmation).to be_nil
end
it 'fails with invalid ZIP code on previous address' do
- step = build_step(user_attrs.merge(prev_zipcode: '00000'))
-
+ reasons = ['The ZIP code was suspicious']
errors = { zipcode: ['Unverified ZIP code.'] }
-
- result = instance_double(FormResponse)
extra = {
idv_attempts_exceeded: false,
- vendor: { reasons: ['The ZIP code was suspicious'] },
+ vendor: { reasons: reasons },
}
- expect(FormResponse).to receive(:new).
- with(success: false, errors: errors, extra: extra).and_return(result)
- expect(step.submit).to eq result
+ step = build_step(
+ user_attrs.merge(prev_zipcode: '00000'),
+ Idv::VendorResult.new(success: false, errors: errors, reasons: reasons)
+ )
+
+ result = step.submit
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to eq(errors)
+ expect(result.extra).to eq(extra)
expect(idv_session.profile_confirmation).to be_nil
end
- it 'increments attempts count if the form is valid' do
- step = build_step(user_attrs)
+ it 'increments attempts count' do
+ step = build_step(user_attrs, Idv::VendorResult.new(errors: {}))
expect { step.submit }.to change(user, :idv_attempts).by(1)
end
- it 'does not increment the attempts count if the form is not valid' do
- step = build_step(user_attrs.merge(ssn: '666'))
- expect { step.submit }.to change(user, :idv_attempts).by(0)
- end
-
it 'initializes the idv_session' do
- step = build_step(user_attrs)
+ step = build_step(user_attrs, Idv::VendorResult.new(errors: {}))
step.submit
expect(idv_session.params).to eq user_attrs
@@ -152,7 +164,7 @@ def build_step(params)
allow(Idv::Attempter).to receive(:new).with(user).and_return(attempter)
allow(attempter).to receive(:exceeded?)
- step = build_step(user_attrs)
+ step = build_step(user_attrs, Idv::VendorResult.new(errors: {}))
expect(step.attempts_exceeded?).to eq attempter.exceeded?
end
end
diff --git a/spec/services/idv/session_spec.rb b/spec/services/idv/session_spec.rb
index 7d0e6ccdea1..3e95c0cbbd5 100644
--- a/spec/services/idv/session_spec.rb
+++ b/spec/services/idv/session_spec.rb
@@ -33,4 +33,90 @@
end
end
end
+
+ describe '#complete_session' do
+ context 'with phone verifed by vendor' do
+ before do
+ subject.address_verification_mechanism = :phone
+ subject.vendor_phone_confirmation = true
+ allow(subject).to receive(:complete_profile)
+ end
+
+ it 'completes the profile if the user has completed OTP phone confirmation' do
+ subject.user_phone_confirmation = true
+ subject.complete_session
+
+ expect(subject).to have_received(:complete_profile)
+ end
+
+ it 'does not complete the profile if the user has not completed OTP phone confirmation' do
+ subject.user_phone_confirmation = nil
+ subject.complete_session
+
+ expect(subject).not_to have_received(:complete_profile)
+ end
+ end
+ end
+
+ describe '#phone_confirmed?' do
+ it 'returns true if the user and vendor have confirmed the phone' do
+ subject.user_phone_confirmation = true
+ subject.vendor_phone_confirmation = true
+
+ expect(subject.phone_confirmed?).to eq(true)
+ end
+
+ it 'returns false if the user has not confirmed the phone' do
+ subject.user_phone_confirmation = nil
+ subject.vendor_phone_confirmation = true
+
+ expect(subject.phone_confirmed?).to eq(false)
+ end
+
+ it 'returns false if the vendor has not confirmed the phone' do
+ subject.user_phone_confirmation = true
+ subject.vendor_phone_confirmation = nil
+
+ expect(subject.phone_confirmed?).to eq(false)
+ end
+
+ it 'returns false if neither the user nor the vendor has confirmed the phone' do
+ subject.user_phone_confirmation = nil
+ subject.vendor_phone_confirmation = nil
+
+ expect(subject.phone_confirmed?).to eq(false)
+ end
+ end
+
+ describe '#address_mechanism_chosen?' do
+ context 'phone verification chosen' do
+ before do
+ subject.address_verification_mechanism = 'phone'
+ end
+
+ it 'returns true if the vendor has confirmed the phone number' do
+ subject.vendor_phone_confirmation = true
+
+ expect(subject.address_mechanism_chosen?).to eq(true)
+ end
+
+ it 'returns false if the vendor has not confirmed the phone number' do
+ subject.vendor_phone_confirmation = nil
+
+ expect(subject.address_mechanism_chosen?).to eq(false)
+ end
+ end
+
+ it 'returns true if the user has selected usps address verification' do
+ subject.address_verification_mechanism = 'usps'
+
+ expect(subject.address_mechanism_chosen?).to eq(true)
+ end
+
+ it 'returns false if the user has not selected phone or usps address verification' do
+ subject.address_verification_mechanism = nil
+
+ expect(subject.address_mechanism_chosen?).to eq(false)
+ end
+ end
end
diff --git a/spec/services/idv/vendor_result_spec.rb b/spec/services/idv/vendor_result_spec.rb
new file mode 100644
index 00000000000..74d39e2809f
--- /dev/null
+++ b/spec/services/idv/vendor_result_spec.rb
@@ -0,0 +1,70 @@
+require 'rails_helper'
+
+RSpec.describe Idv::VendorResult do
+ let(:success) { true }
+ let(:errors) { { foo: ['is not valid'] } }
+ let(:reasons) { %w[foo bar baz] }
+ let(:session_id) { SecureRandom.uuid }
+ let(:normalized_applicant) do
+ Proofer::Applicant.new(
+ last_name: 'Ever',
+ first_name: 'Greatest'
+ )
+ end
+ let(:timed_out) { false }
+
+ subject(:vendor_result) do
+ Idv::VendorResult.new(
+ success: success,
+ errors: errors,
+ reasons: reasons,
+ session_id: session_id,
+ normalized_applicant: normalized_applicant,
+ timed_out: timed_out
+ )
+ end
+
+ describe '#success?' do
+ it 'is the success value' do
+ expect(vendor_result.success?).to eq(success)
+ end
+ end
+
+ describe '#timed_out?' do
+ it 'is the timed_out value' do
+ expect(vendor_result.timed_out?).to eq(timed_out)
+ end
+ end
+
+ describe '#to_json' do
+ it 'serializes normalized_applicant correctly' do
+ json = vendor_result.to_json
+
+ parsed = JSON.parse(json, symbolize_names: true)
+ expect(parsed[:normalized_applicant][:last_name]).to eq(normalized_applicant.last_name)
+ end
+ end
+
+ describe '.new_from_json' do
+ subject(:new_from_json) { Idv::VendorResult.new_from_json(vendor_result.to_json) }
+
+ it 'has simple attributes' do
+ expect(new_from_json.success?).to eq(vendor_result.success?)
+ expect(new_from_json.errors).to eq(vendor_result.errors)
+ expect(new_from_json.reasons).to eq(vendor_result.reasons)
+ expect(new_from_json.session_id).to eq(vendor_result.session_id)
+ end
+
+ it 'turns applicant into a full object' do
+ expect(new_from_json.normalized_applicant.last_name).to eq(normalized_applicant.last_name)
+ end
+
+ context 'without an applicant' do
+ let(:normalized_applicant) { nil }
+
+ it 'does not have an applicant' do
+ expect(new_from_json.normalized_applicant).to eq(nil)
+ end
+ end
+ end
+end
diff --git a/spec/services/marketing_site_spec.rb b/spec/services/marketing_site_spec.rb
index bd72aca8384..80127eac095 100644
--- a/spec/services/marketing_site_spec.rb
+++ b/spec/services/marketing_site_spec.rb
@@ -3,7 +3,15 @@
RSpec.describe MarketingSite do
describe '.base_url' do
it 'points to the base URL' do
- expect(MarketingSite.base_url).to eq('https://www.login.gov')
+ expect(MarketingSite.base_url).to eq('https://www.login.gov/')
+ end
+
+ context 'when the user has set their locale to :es' do
+ before { I18n.locale = :es }
+
+ it 'points to the base URL with the locale appended' do
+ expect(MarketingSite.base_url).to eq('https://www.login.gov/es/')
+ end
end
end
@@ -11,18 +19,42 @@
it 'points to the privacy page' do
expect(MarketingSite.privacy_url).to eq('https://www.login.gov/policy')
end
+
+ context 'when the user has set their locale to :es' do
+ before { I18n.locale = :es }
+
+ it 'points to the privacy page with the locale appended' do
+ expect(MarketingSite.privacy_url).to eq('https://www.login.gov/es/policy')
+ end
+ end
end
describe '.contact_url' do
it 'points to the contact page' do
expect(MarketingSite.contact_url).to eq('https://www.login.gov/contact')
end
+
+ context 'when the user has set their locale to :es' do
+ before { I18n.locale = :es }
+
+ it 'points to the contact page with the locale appended' do
+ expect(MarketingSite.contact_url).to eq('https://www.login.gov/es/contact')
+ end
+ end
end
describe '.help_url' do
it 'points to the help page' do
expect(MarketingSite.help_url).to eq('https://www.login.gov/help')
end
+
+ context 'when the user has set their locale to :es' do
+ before { I18n.locale = :es }
+
+ it 'points to the help page with the locale appended' do
+ expect(MarketingSite.help_url).to eq('https://www.login.gov/es/help')
+ end
+ end
end
describe '.help_authenticator_app_url' do
@@ -31,5 +63,15 @@
'https://www.login.gov/help/signing-in/what-is-an-authenticator-app/'
)
end
+
+ context 'when the user has set their locale to :es' do
+ before { I18n.locale = :es }
+
+ it 'points to the authenticator app section of the help page with the locale appended' do
+ expect(MarketingSite.help_authenticator_app_url).to eq(
+ 'https://www.login.gov/es/help/signing-in/what-is-an-authenticator-app/'
+ )
+ end
+ end
end
end
diff --git a/spec/services/otp_rate_limiter_spec.rb b/spec/services/otp_rate_limiter_spec.rb
index bdf838dcac8..f2e6b2eb349 100644
--- a/spec/services/otp_rate_limiter_spec.rb
+++ b/spec/services/otp_rate_limiter_spec.rb
@@ -23,11 +23,13 @@
end
describe '#increment' do
- it 'sets the otp_last_sent_at' do
- now = Time.zone.now
+ it 'updates otp_last_sent_at' do
+ tracker = OtpRequestsTracker.find_or_create_with_phone(current_user.phone)
+ old_otp_last_sent_at = tracker.reload.otp_last_sent_at
otp_rate_limiter.increment
+ new_otp_last_sent_at = tracker.reload.otp_last_sent_at
- expect(rate_limited_phone.otp_last_sent_at.to_i).to eq(now.to_i)
+ expect(new_otp_last_sent_at).to be > old_otp_last_sent_at
end
it 'increments the otp_send_count' do
diff --git a/spec/services/phone_formatter_spec.rb b/spec/services/phone_formatter_spec.rb
new file mode 100644
index 00000000000..8f32b271a2d
--- /dev/null
+++ b/spec/services/phone_formatter_spec.rb
@@ -0,0 +1,45 @@
+require 'rails_helper'
+
+describe PhoneFormatter do
+ describe '#format' do
+ it 'formats international numbers correctly' do
+ phone = '+404004004000'
+ formatted_phone = PhoneFormatter.new.format(phone)
+
+ expect(formatted_phone).to eq('+40 400 400 4000')
+ end
+
+ it 'formats U.S. numbers correctly' do
+ phone = '+12025005000'
+ formatted_phone = PhoneFormatter.new.format(phone)
+
+ expect(formatted_phone).to eq('+1 (202) 500-5000')
+ end
+
+ it 'uses +1 as the default international code' do
+ phone = '2025005000'
+ formatted_phone = PhoneFormatter.new.format(phone)
+
+ expect(formatted_phone).to eq('+1 (202) 500-5000')
+ end
+
+ it 'uses the international code for the country specified in the country code option' do
+ phone = '123123123'
+ formatted_phone = PhoneFormatter.new.format(phone, country_code: 'MA')
+
+ expect(formatted_phone).to eq('+212 12 3123 123')
+ end
+
+ it 'returns nil for nil' do
+ formatted_phone = PhoneFormatter.new.format(nil)
+
+ expect(formatted_phone).to be_nil
+ end
+
+ it 'returns nil for nonsense' do
+ phone = '☎️📞📱📳'
+ formatted_phone = PhoneFormatter.new.format(phone)
+ expect(formatted_phone).to be_nil
+ end
+ end
+end
diff --git a/spec/services/phone_number_capabilities_spec.rb b/spec/services/phone_number_capabilities_spec.rb
new file mode 100644
index 00000000000..ca994160040
--- /dev/null
+++ b/spec/services/phone_number_capabilities_spec.rb
@@ -0,0 +1,39 @@
+require 'rails_helper'
+
+describe PhoneNumberCapabilities do
+ let(:phone) { '+1 (555) 555-5000' }
+ subject { PhoneNumberCapabilities.new(phone) }
+
+ describe '#sms_only?' do
+ context 'voice is supported' do
+ it { expect(subject.sms_only?).to eq(false) }
+ end
+
+ context 'voice is not supported for the area code' do
+ let(:phone) { '+1 (671) 555-5000' }
+ it { expect(subject.sms_only?).to eq(true) }
+ end
+
+ context 'voice is supported for the international code' do
+ let(:phone) { '+55 (555) 555-5000' }
+ it { expect(subject.sms_only?).to eq(false) }
+ end
+
+ context 'voice is not supported for the international code' do
+ let(:phone) { '+212 1234 12345' }
+ it { expect(subject.sms_only?).to eq(true) }
+ end
+ end
+
+ describe '#unsupported_location' do
+ it 'returns the name of the unsupported area code location' do
+ locality = PhoneNumberCapabilities.new('+1 (671) 555-5000').unsupported_location
+ expect(locality).to eq('Guam')
+ end
+
+ it 'returns the name of the unsupported international code location' do
+ locality = PhoneNumberCapabilities.new('+212 1234 12345').unsupported_location
+ expect(locality).to eq('Morocco')
+ end
+ end
+end
diff --git a/spec/services/submit_idv_job_spec.rb b/spec/services/submit_idv_job_spec.rb
new file mode 100644
index 00000000000..4c72132cc14
--- /dev/null
+++ b/spec/services/submit_idv_job_spec.rb
@@ -0,0 +1,58 @@
+require 'rails_helper'
+
+RSpec.describe SubmitIdvJob do
+ subject(:service) do
+ SubmitIdvJob.new(
+ vendor_validator_class: vendor_validator_class,
+ idv_session: idv_session,
+ vendor_params: vendor_params
+ )
+ end
+
+ let(:idv_session) do
+ Idv::Session.new(
+ current_user: user,
+ issuer: nil,
+ user_session: {
+ idv: {
+ applicant: applicant,
+ vendor_session_id: vendor_session_id,
+ vendor: :mock,
+ },
+ }
+ )
+ end
+
+ let(:user) { build(:user) }
+ let(:applicant) { Proofer::Applicant.new(first_name: 'Greatest') }
+ let(:vendor_session_id) { '12345' }
+ let(:result_id) { 'abcdef' }
+ let(:vendor_params) { '+1 (888) 123-4567' }
+ let(:vendor_validator_class) { 'Idv::PhoneValidator' }
+
+ describe '#call' do
+ subject(:call) { service.call }
+
+ it 'generates a UUID and enqueues a job, and saves the UUID in the session' do
+ expect(SecureRandom).to receive(:uuid).and_return(result_id).once
+
+ expect(VendorValidatorJob).to receive(:perform_later).
+ with(
+ result_id: result_id,
+ vendor_validator_class: vendor_validator_class,
+ vendor: 'mock',
+ vendor_params: vendor_params,
+ vendor_session_id: vendor_session_id,
+ applicant_json: applicant.to_json
+ )
+
+ expect(idv_session.async_result_id).to eq(nil)
+ expect(idv_session.async_result_started_at).to eq(nil)
+
+ call
+
+ expect(idv_session.async_result_id).to eq(result_id)
+ expect(idv_session.async_result_started_at).to be_within(1).of(Time.zone.now.to_i)
+ end
+ end
+end
diff --git a/spec/services/usps_exporter_spec.rb b/spec/services/usps_exporter_spec.rb
index 2e4eba2144c..a2da4deee36 100644
--- a/spec/services/usps_exporter_spec.rb
+++ b/spec/services/usps_exporter_spec.rb
@@ -2,18 +2,17 @@
describe UspsExporter do
let(:export_file) { Tempfile.new('usps_export.psv') }
- let(:usps_entry) { UspsConfirmationEntry.new_from_hash(pii_attributes) }
let(:pii_attributes) do
- {
- first_name: 'Some',
- last_name: 'One',
- address1: '123 Any St',
- address2: 'Ste 123',
- city: 'Somewhere',
- state: 'KS',
- zipcode: '66666-1234',
- otp: 123,
- }
+ Pii::Attributes.new_from_hash(
+ first_name: { raw: 'Söme', norm: 'Some' },
+ last_name: { raw: 'Öne', norm: 'One' },
+ address1: { raw: '123 Añy St', norm: '123 Any St' },
+ address2: { raw: 'Sté 123', norm: 'Ste 123' },
+ city: { raw: 'Sömewhere', norm: 'Somewhere' },
+ state: { raw: 'KS', norm: 'KS' },
+ zipcode: { raw: '66666-1234', norm: '66666-1234' },
+ otp: { raw: 123, norm: 123 }
+ )
end
let(:service_provider) { ServiceProvider.from_issuer('http://localhost:3000') }
let(:psv_row_contents) do
@@ -23,13 +22,13 @@
due_date = due.strftime('%-B %-e')
values = [
UspsExporter::CONTENT_ROW_ID,
- usps_entry.first_name + ' ' + usps_entry.last_name,
- usps_entry.address1,
- usps_entry.address2,
- usps_entry.city,
- usps_entry.state,
- usps_entry.zipcode,
- usps_entry.otp,
+ pii_attributes.first_name.norm + ' ' + pii_attributes.last_name.norm,
+ pii_attributes.address1.norm,
+ pii_attributes.address2.norm,
+ pii_attributes.city.norm,
+ pii_attributes.state.norm,
+ pii_attributes.zipcode.norm,
+ pii_attributes.otp.norm,
"#{current_date}, #{now.year}",
"#{due_date}, #{due.year}",
service_provider.friendly_name,
diff --git a/spec/services/usps_uploader_spec.rb b/spec/services/usps_uploader_spec.rb
new file mode 100644
index 00000000000..b5f48ef530c
--- /dev/null
+++ b/spec/services/usps_uploader_spec.rb
@@ -0,0 +1,39 @@
+require 'rails_helper'
+
+RSpec.describe UspsUploader do
+ subject(:uploader) { UspsUploader.new }
+
+ describe '#run' do
+ subject(:run) { uploader.run }
+
+ let(:sftp_connection) { instance_double('Net::SFTP::Session') }
+
+ before do
+ sftp_options = [
+ Figaro.env.equifax_sftp_host,
+ Figaro.env.equifax_sftp_username,
+ { key_data: [RequestKeyManager.equifax_ssh_key.to_pem] },
+ ]
+ expect(Net::SFTP).to receive(:start).
+ with(*sftp_options).and_yield(sftp_connection)
+ end
+
+ it 'creates a PGP-encrypted file and uploads it via SFTP and deletes it after' do
+ expect(sftp_connection).to receive(:upload!).
+ with(uploader.local_path.to_s, File.join(Figaro.env.equifax_sftp_directory, 'batch.pgp'))
+
+ run
+
+ expect(File.exist?(uploader.local_path)).to eq(false)
+ end
+
+ it 'notifies NewRelic and does not delete the file if SFTP fails' do
+ expect(sftp_connection).to receive(:upload!).and_raise(StandardError)
+ expect(NewRelic::Agent).to receive(:notice_error)
+
+ expect { run }.to_not raise_error
+
+ expect(File.exist?(uploader.local_path)).to eq(true)
+ end
+ end
+end
diff --git a/spec/services/vendor_validator_result_storage_spec.rb b/spec/services/vendor_validator_result_storage_spec.rb
new file mode 100644
index 00000000000..6cd36a9270f
--- /dev/null
+++ b/spec/services/vendor_validator_result_storage_spec.rb
@@ -0,0 +1,51 @@
+require 'rails_helper'
+
+RSpec.describe VendorValidatorResultStorage do
+ subject(:service) { VendorValidatorResultStorage.new }
+
+ let(:session_id) { SecureRandom.uuid }
+ let(:result_id) { SecureRandom.uuid }
+ let(:original_result) do
+ Idv::VendorResult.new(
+ success: false,
+ session_id: session_id,
+ normalized_applicant: Proofer::Applicant.new(first_name: 'First')
+ )
+ end
+
+ describe '#store_result' do
+ it 'stores the result in redis with a TTL' do
+ key = service.redis_key(result_id)
+
+ before_redis = Sidekiq.redis { |redis| redis.get(key) }
+ expect(before_redis).to be_nil
+
+ service.store(result_id: result_id, result: original_result)
+
+ Sidekiq.redis do |redis|
+ expect(redis.get(key)).to be_present
+ expect(redis.ttl(key)).to be_within(1).of(VendorValidatorResultStorage::TTL)
+ end
+ end
+ end
+
+ describe '#vendor_validator_result' do
+ before { service.store(result_id: result_id, result: original_result) }
+
+ it 'retrieves a stored result' do
+ result = service.load(result_id)
+
+ expect(result.success?).to eq(original_result.success?)
+ expect(result.errors).to eq(original_result.errors)
+ expect(result.reasons).to eq(original_result.reasons)
+ expect(result.normalized_applicant.as_json).
+ to eq(original_result.normalized_applicant.as_json)
+ end
+
+ it 'is nil with a bad result id' do
+ result = service.load(SecureRandom.uuid)
+
+ expect(result).to be_nil
+ end
+ end
+end
diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb
index f5831cf230b..63da64d2b21 100644
--- a/spec/support/features/session_helper.rb
+++ b/spec/support/features/session_helper.rb
@@ -24,8 +24,8 @@ def signin(email, password)
end
def fill_in_credentials_and_submit(email, password)
- fill_in 'Email', with: email
- fill_in 'Password', with: password
+ fill_in 'user_email', with: email
+ fill_in 'user_password', with: password
click_button t('links.next')
end
diff --git a/spec/support/shared_examples_for_otp_delivery_preference_validation.rb b/spec/support/shared_examples_for_otp_delivery_preference_validation.rb
new file mode 100644
index 00000000000..ad2964464f6
--- /dev/null
+++ b/spec/support/shared_examples_for_otp_delivery_preference_validation.rb
@@ -0,0 +1,45 @@
+shared_examples 'an otp delivery preference form' do
+ let(:phone) { '+1 (555) 555-5000' }
+ let(:params) do
+ {
+ phone: phone,
+ otp_delivery_preference: 'voice',
+ international_code: 'US',
+ }
+ end
+
+ context 'voice' do
+ it 'is valid when supported for the phone' do
+ update_user = instance_double(UpdateUser)
+ attributes = { otp_delivery_preference: 'voice' }
+ allow(UpdateUser).to receive(:new).with(user: user, attributes: attributes).
+ and_return(update_user)
+ expect(update_user).to receive(:call)
+
+ capabilities = spy(PhoneNumberCapabilities)
+ allow(PhoneNumberCapabilities).to receive(:new).with(phone).and_return(capabilities)
+ allow(capabilities).to receive(:sms_only?).and_return(false)
+
+ result = subject.submit(params)
+
+ expect(result.success?).to eq(true)
+ end
+
+ it 'is invalid when unsupported for the phone' do
+ update_user = instance_double(UpdateUser)
+ attributes = { otp_delivery_preference: 'voice' }
+ allow(UpdateUser).to receive(:new).with(user: user, attributes: attributes).
+ and_return(update_user)
+ expect(update_user).to_not receive(:call)
+
+ capabilities = spy(PhoneNumberCapabilities)
+ allow(PhoneNumberCapabilities).to receive(:new).with(phone).and_return(capabilities)
+ allow(capabilities).to receive(:sms_only?).and_return(true)
+
+ result = subject.submit(params)
+
+ expect(result.success?).to eq(false)
+ expect(result.errors).to include(:phone)
+ end
+ end
+end
diff --git a/spec/support/shared_examples_for_phone_validation.rb b/spec/support/shared_examples_for_phone_validation.rb
index a6527b2831b..d05b00379f1 100644
--- a/spec/support/shared_examples_for_phone_validation.rb
+++ b/spec/support/shared_examples_for_phone_validation.rb
@@ -1,4 +1,8 @@
+require 'shoulda/matchers'
+
shared_examples 'a phone form' do
+ include Shoulda::Matchers::ActiveModel
+
describe 'phone presence validation' do
it 'is invalid when phone is blank' do
subject.submit(phone: '')
@@ -8,12 +12,26 @@
end
describe 'phone validation' do
- it 'uses the phony_rails gem with country option set to US' do
+ it 'uses the phony_rails gem' do
phone_validator = subject._validators.values.flatten.
detect { |v| v.class == PhonyPlausibleValidator }
- expect(phone_validator.options).
- to eq(country_code: 'US', presence: true, message: :improbable_phone)
+ expect(phone_validator.options[:presence]).to eq(true)
+ expect(phone_validator.options[:message]).to eq(:improbable_phone)
+ expect(phone_validator.options).to include(:international_code)
+ end
+
+ it do
+ should validate_inclusion_of(:international_code).
+ in_array(PhoneNumberCapabilities::INTERNATIONAL_CODES.keys)
+ end
+
+ it 'validates that the number matches the requested international code' do
+ result = subject.submit(phone: '123 123 1234', international_code: 'MA')
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to include(:phone)
end
end
@@ -24,26 +42,32 @@
allow(User).to receive(:exists?).with(email: 'new@gmail.com').and_return(false)
allow(User).to receive(:exists?).with(phone: second_user.phone).and_return(true)
- expect(subject.submit(phone: second_user.phone)).to be true
+ result = subject.submit(phone: second_user.phone, international_code: 'US')
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(true)
end
end
context 'when phone is not already taken' do
it 'is valid' do
- expect(subject.submit(phone: '+1 (703) 555-1212')).to be true
+ result = subject.submit(phone: '+1 (703) 555-1212', international_code: 'US')
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to be true
end
end
context 'when phone is same as current user' do
it 'is valid' do
- expect(subject.submit(phone: user.phone)).to be true
+ result = subject.submit(phone: user.phone, international_code: 'US')
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to be true
end
end
end
describe '#submit' do
it 'formats the phone before assigning it' do
- subject.submit(phone: '703-555-1212')
+ subject.submit(phone: '703-555-1212', international_code: 'US')
expect(subject.phone).to eq '+1 (703) 555-1212'
end
diff --git a/spec/view_models/verify/base_spec.rb b/spec/view_models/verify/base_spec.rb
index c36daff9791..a5dcd367dc8 100644
--- a/spec/view_models/verify/base_spec.rb
+++ b/spec/view_models/verify/base_spec.rb
@@ -22,4 +22,36 @@
end
end
end
+
+ describe '#message' do
+ let(:timed_out) { false }
+ let(:view_model) do
+ Verify::Base.new(
+ error: error,
+ remaining_attempts: 1,
+ idv_form: nil,
+ timed_out: timed_out
+ )
+ end
+
+ subject(:message) { view_model.message }
+
+ before { expect(view_model).to receive(:step_name).and_return(:phone) }
+
+ context 'with a warning' do
+ let(:error) { 'warning' }
+
+ it 'uses the warning copy' do
+ expect(message).to include(t('idv.modal.phone.warning'))
+ end
+
+ context 'with a timeout' do
+ let(:timed_out) { true }
+
+ it 'uses the timeout copy' do
+ expect(message).to include(t('idv.modal.phone.timeout'))
+ end
+ end
+ end
+ end
end
diff --git a/spec/views/devise/mailer/confirmation_instructions.html.slim_spec.rb b/spec/views/devise/mailer/confirmation_instructions.html.slim_spec.rb
index 5d27b671432..b5900800675 100644
--- a/spec/views/devise/mailer/confirmation_instructions.html.slim_spec.rb
+++ b/spec/views/devise/mailer/confirmation_instructions.html.slim_spec.rb
@@ -26,6 +26,21 @@
)
end
+ context 'in a non-default locale' do
+ before { assign(:locale, 'fr') }
+
+ it 'puts the locale in the URL' do
+ assign(:resource, build_stubbed(:user, confirmed_at: Time.zone.now))
+ assign(:token, 'foo')
+ render
+
+ expect(rendered).to have_link(
+ 'http://test.host/fr/sign_up/email/confirm?confirmation_token=foo',
+ href: 'http://test.host/fr/sign_up/email/confirm?confirmation_token=foo'
+ )
+ end
+ end
+
it 'mentions updating an account when user has already been confirmed' do
user = build_stubbed(:user, confirmed_at: Time.zone.now)
presenter = ConfirmationEmailPresenter.new(user, self)
diff --git a/spec/views/devise/passwords/new.html.slim_spec.rb b/spec/views/devise/passwords/new.html.slim_spec.rb
index 5cee8700ed1..d6669688339 100644
--- a/spec/views/devise/passwords/new.html.slim_spec.rb
+++ b/spec/views/devise/passwords/new.html.slim_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'devise/passwords/new.html.slim' do
- let(:user) { build_stubbed(:user) }
-
before do
@password_reset_email_form = PasswordResetEmailForm.new('')
sp = build_stubbed(
@@ -17,7 +15,6 @@
sp_session: {},
service_provider_request: ServiceProviderRequest.new
).call
- allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:decorated_session).and_return(@decorated_session)
end
@@ -38,4 +35,10 @@
expect(rendered).to have_xpath("//form[@autocomplete='off']")
end
+
+ it 'has a cancel link that points to the decorated_session cancel_link_path' do
+ render
+
+ expect(rendered).to have_link(t('links.cancel'), href: @decorated_session.cancel_link_path)
+ end
end
diff --git a/spec/views/layouts/application.html.slim_spec.rb b/spec/views/layouts/application.html.slim_spec.rb
index ed2129717fa..dd18a895ea4 100644
--- a/spec/views/layouts/application.html.slim_spec.rb
+++ b/spec/views/layouts/application.html.slim_spec.rb
@@ -15,6 +15,8 @@
)
allow(view.request).to receive(:original_url).and_return('http://test.host/foobar')
allow(view).to receive(:current_user).and_return(User.new)
+ controller.request.path_parameters[:controller] = 'users/sessions'
+ controller.request.path_parameters[:action] = 'new'
end
context 'no content for nav present' do
diff --git a/spec/views/shared/_footer_lite.html.slim_spec.rb b/spec/views/shared/_footer_lite.html.slim_spec.rb
index 567f63c4a45..78196788915 100644
--- a/spec/views/shared/_footer_lite.html.slim_spec.rb
+++ b/spec/views/shared/_footer_lite.html.slim_spec.rb
@@ -2,6 +2,11 @@
describe 'shared/_footer_lite.html.slim' do
context 'user is signed out' do
+ before do
+ controller.request.path_parameters[:controller] = 'users/sessions'
+ controller.request.path_parameters[:action] = 'new'
+ end
+
it 'contains link to help page' do
render
diff --git a/spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb b/spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb
index ff54370186c..5be1df804c5 100644
--- a/spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb
+++ b/spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb
@@ -44,28 +44,40 @@
expect(rendered).to have_selector("form[action='/users'][method='post']")
end
- it 'informs the user that an OTP has been sent to their number via #help_text' do
- build_stubbed(:user)
+ context 'OTP copy' do
+ let(:help_text) do
+ code_link = link_to(
+ t('links.two_factor_authentication.resend_code.sms'),
+ otp_send_path(
+ otp_delivery_selection_form: {
+ otp_delivery_preference: 'sms',
+ resend: true,
+ }
+ )
+ )
- code_link = link_to(
- t('links.two_factor_authentication.resend_code.sms'),
- otp_send_path(
- otp_delivery_selection_form: {
- otp_delivery_preference: 'sms',
- resend: true,
- }
+ t(
+ "instructions.mfa.#{presenter_data[:otp_delivery_preference]}.confirm_code_html",
+ number: "#{presenter_data[:phone_number]}",
+ resend_code_link: code_link
)
- )
+ end
- help_text = t(
- "instructions.2fa.#{presenter_data[:otp_delivery_preference]}.confirm_code_html",
- number: "#{presenter_data[:phone_number]}",
- resend_code_link: code_link
- )
+ it 'informs the user that an OTP has been sent to their number via #help_text' do
+ render
- render
+ expect(rendered).to include help_text
+ end
- expect(rendered).to include help_text
+ context 'in other locales' do
+ before { I18n.locale = :es }
+
+ it 'translates correctly' do
+ render
+
+ expect(rendered).to include help_text
+ end
+ end
end
context 'user signed up' do
@@ -206,7 +218,7 @@
render
expect(rendered).to include(
- t("instructions.2fa.#{otp_delivery_preference}.fallback_html", link: expected_link)
+ t("instructions.mfa.#{otp_delivery_preference}.fallback_html", link: expected_link)
)
end
@@ -224,7 +236,7 @@
render
expect(rendered).not_to include(
- t('instructions.2fa.voice.fallback_html', link: unexpected_link)
+ t('instructions.mfa.voice.fallback_html', link: unexpected_link)
)
end
end
@@ -270,7 +282,7 @@
render
expect(rendered).to include(
- t("instructions.2fa.#{otp_delivery_preference}.fallback_html", link: expected_link)
+ t("instructions.mfa.#{otp_delivery_preference}.fallback_html", link: expected_link)
)
end
@@ -288,7 +300,7 @@
render
expect(rendered).not_to include(
- t('instructions.2fa.sms.fallback_html', link: unexpected_link)
+ t('instructions.mfa.sms.fallback_html', link: unexpected_link)
)
end
end
diff --git a/spec/views/verify/review/new.html.slim_spec.rb b/spec/views/verify/review/new.html.slim_spec.rb
index 258c9d201da..3ffe21b10a1 100644
--- a/spec/views/verify/review/new.html.slim_spec.rb
+++ b/spec/views/verify/review/new.html.slim_spec.rb
@@ -48,7 +48,8 @@
end
it 'renders a link telling user why financial info is not visible' do
- expect(rendered).to have_link t('idv.messages.review.financial_info')
+ expect(rendered).
+ to have_link(t('idv.messages.review.financial_info'), href: MarketingSite.help_url)
end
end
end
diff --git a/spec/views/verify/usps/index.html.slim_spec.rb b/spec/views/verify/usps/index.html.slim_spec.rb
new file mode 100644
index 00000000000..c72e0b65701
--- /dev/null
+++ b/spec/views/verify/usps/index.html.slim_spec.rb
@@ -0,0 +1,18 @@
+require 'rails_helper'
+
+describe 'verify/usps/index.html.slim' do
+ it 'calls UspsDecorator#title and #button' do
+ user = build_stubbed(:user, :signed_up)
+ usps_mail_service = Idv::UspsMail.new(user)
+
+ usps_decorator = instance_double(UspsDecorator)
+ allow(UspsDecorator).to receive(:new).with(usps_mail_service).
+ and_return(usps_decorator)
+ @decorated_usps = usps_decorator
+
+ expect(usps_decorator).to receive(:title)
+ expect(usps_decorator).to receive(:button)
+
+ render
+ end
+end