diff --git a/app/javascript/packages/document-capture/components/document-capture.tsx b/app/javascript/packages/document-capture/components/document-capture.tsx
index 26b2287b441..42e6f2bacc1 100644
--- a/app/javascript/packages/document-capture/components/document-capture.tsx
+++ b/app/javascript/packages/document-capture/components/document-capture.tsx
@@ -5,6 +5,7 @@ import { FormSteps, PromptOnNavigate } from '@18f/identity-form-steps';
import { VerifyFlowStepIndicator, VerifyFlowPath } from '@18f/identity-verify-flow';
import { useDidUpdateEffect } from '@18f/identity-react-hooks';
import type { FormStep } from '@18f/identity-form-steps';
+import { getConfigValue } from '@18f/identity-config';
import { UploadFormEntriesError } from '../services/upload';
import DocumentsStep from './documents-step';
import InPersonPrepareStep from './in-person-prepare-step';
@@ -60,6 +61,8 @@ function DocumentCapture({ isAsyncForm = false, onStepChange = () => {} }: Docum
const { flowPath } = useContext(UploadContext);
const { trackSubmitEvent, trackVisitEvent } = useContext(AnalyticsContext);
const { inPersonURL, arcgisSearchEnabled } = useContext(InPersonContext);
+ const appName = getConfigValue('appName');
+
useDidUpdateEffect(onStepChange, [stepName]);
useEffect(() => {
if (stepName) {
@@ -112,14 +115,17 @@ function DocumentCapture({ isAsyncForm = false, onStepChange = () => {} }: Docum
{
name: 'location',
form: arcgisSearchEnabled ? InPersonLocationPostOfficeSearchStep : InPersonLocationStep,
+ title: t('in_person_proofing.headings.po_search.location'),
},
{
name: 'prepare',
form: InPersonPrepareStep,
+ title: t('in_person_proofing.headings.prepare'),
},
flowPath === 'hybrid' && {
name: 'switch_back',
form: InPersonSwitchBackStep,
+ title: t('in_person_proofing.headings.switch_back'),
},
].filter(Boolean) as FormStep[]);
@@ -137,6 +143,7 @@ function DocumentCapture({ isAsyncForm = false, onStepChange = () => {} }: Docum
pii: submissionError.pii,
})(ReviewIssuesStep)
: ReviewIssuesStep,
+ title: t('errors.doc_auth.throttled_heading'),
},
] as FormStep[]
).concat(inPersonSteps)
@@ -144,6 +151,7 @@ function DocumentCapture({ isAsyncForm = false, onStepChange = () => {} }: Docum
{
name: 'documents',
form: DocumentsStep,
+ title: t('doc_auth.headings.document_capture'),
},
].filter(Boolean) as FormStep[]);
@@ -186,6 +194,7 @@ function DocumentCapture({ isAsyncForm = false, onStepChange = () => {} }: Docum
onStepChange={setStepName}
onStepSubmit={trackSubmitEvent}
autoFocus={!!submissionError}
+ titleFormat={`%{step} - ${appName}`}
/>
>
)}
diff --git a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx
index 5983aa4587c..1d645eb35cf 100644
--- a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx
+++ b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx
@@ -368,4 +368,38 @@ describe('InPersonLocationStep', () => {
expect(moreResults).to.be.empty();
});
});
+
+ context('user deletes text from searchbox after location results load', () => {
+ beforeEach(() => {
+ server.use(
+ rest.post(ADDRESS_SEARCH_URL, (_req, res, ctx) =>
+ res(ctx.json(DEFAULT_RESPONSE), ctx.status(200)),
+ ),
+ rest.post(LOCATIONS_URL, (_req, res, ctx) => res(ctx.json([{ name: 'Baltimore' }]))),
+ );
+ });
+
+ it('allows user to select a location', async () => {
+ const { findAllByText, findByLabelText, findByText, queryByText } = render(
+
,
+ { wrapper },
+ );
+ await userEvent.type(
+ await findByLabelText('in_person_proofing.body.location.po_search.address_search_label'),
+ 'Evergreen Terrace Springfield',
+ );
+
+ await userEvent.click(
+ await findByText('in_person_proofing.body.location.po_search.search_button'),
+ );
+
+ await userEvent.clear(
+ await findByLabelText('in_person_proofing.body.location.po_search.address_search_label'),
+ );
+
+ await userEvent.click(findAllByText('in_person_proofing.body.location.location_button')[0]);
+
+ expect(await queryByText('in_person_proofing.body.location.inline_error')).to.be.null();
+ });
+ });
});
diff --git a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx
index e16593bc54c..a309c93f5ef 100644
--- a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx
+++ b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx
@@ -14,15 +14,16 @@ import InPersonLocations, { FormattedLocation } from './in-person-locations';
function InPersonLocationPostOfficeSearchStep({ onChange, toPreviousStep, registerField }) {
const { t } = useI18n();
- const [inProgress, setInProgress] = useState(false);
- const [isLoadingLocations, setLoadingLocations] = useState(false);
- const [autoSubmit, setAutoSubmit] = useState(false);
+ const [inProgress, setInProgress] = useState
(false);
+ const [isLoadingLocations, setLoadingLocations] = useState(false);
+ const [autoSubmit, setAutoSubmit] = useState(false);
const { setSubmitEventMetadata } = useContext(AnalyticsContext);
const [locationResults, setLocationResults] = useState(
null,
);
const [foundAddress, setFoundAddress] = useState(null);
const [apiError, setApiError] = useState(null);
+ const [disabledAddressSearch, setDisabledAddressSearch] = useState(false);
// ref allows us to avoid a memory leak
const mountedRef = useRef(false);
@@ -43,6 +44,12 @@ function InPersonLocationPostOfficeSearchStep({ onChange, toPreviousStep, regist
setSubmitEventMetadata({ selected_location: selectedLocationAddress });
onChange({ selectedLocationAddress });
if (autoSubmit) {
+ setDisabledAddressSearch(true);
+ setTimeout(() => {
+ if (mountedRef.current) {
+ setDisabledAddressSearch(false);
+ }
+ }, 250);
return;
}
// prevent navigation from continuing
@@ -52,14 +59,12 @@ function InPersonLocationPostOfficeSearchStep({ onChange, toPreviousStep, regist
}
const selected = transformKeys(selectedLocation, snakeCase);
setInProgress(true);
- await request(LOCATIONS_URL, {
- json: selected,
- method: 'PUT',
- })
- .then(() => {
- if (!mountedRef.current) {
- return;
- }
+ try {
+ await request(LOCATIONS_URL, {
+ json: selected,
+ method: 'PUT',
+ });
+ if (mountedRef.current) {
setAutoSubmit(true);
setImmediate(() => {
// continue with navigation
@@ -67,13 +72,12 @@ function InPersonLocationPostOfficeSearchStep({ onChange, toPreviousStep, regist
// allow process to be re-triggered in case submission did not work as expected
setAutoSubmit(false);
});
- })
- .finally(() => {
- if (!mountedRef.current) {
- return;
- }
+ }
+ } finally {
+ if (mountedRef.current) {
setInProgress(false);
- });
+ }
+ }
},
[locationResults, inProgress],
);
@@ -93,6 +97,7 @@ function InPersonLocationPostOfficeSearchStep({ onChange, toPreviousStep, regist
onFoundLocations={setLocationResults}
onLoadingLocations={setLoadingLocations}
onError={setApiError}
+ disabled={disabledAddressSearch}
/>
{locationResults && foundAddress && !isLoadingLocations && (
{
- const { clock } = useSandbox({ useFakeTimers: true });
+ const sandbox = useSandbox({ useFakeTimers: true });
+ const { clock } = sandbox;
const userEvent = baseUserEvent.setup({ advanceTimers: clock.tick });
const longWaitDurationMs = 1000;
@@ -118,4 +120,18 @@ describe('SpinnerButtonElement', () => {
expect(wrapper.classList.contains('spinner-button--spinner-active')).to.be.false();
});
+
+ it('removes action message timeout when disconnected from the page', async () => {
+ const wrapper = createWrapper({ actionMessage: 'Verifying...' });
+ const button = screen.getByRole('link', { name: 'Click Me' });
+
+ sandbox.spy(window, 'setTimeout');
+ sandbox.spy(window, 'clearTimeout');
+
+ await userEvent.click(button);
+ wrapper.parentNode!.removeChild(wrapper);
+
+ const timeoutId = (window.setTimeout as unknown as SinonStub).getCall(0).returnValue;
+ expect(window.clearTimeout).to.have.been.calledWith(timeoutId);
+ });
});
diff --git a/app/javascript/packages/spinner-button/spinner-button-element.ts b/app/javascript/packages/spinner-button/spinner-button-element.ts
index 62571c8bd96..903fbd9ce28 100644
--- a/app/javascript/packages/spinner-button/spinner-button-element.ts
+++ b/app/javascript/packages/spinner-button/spinner-button-element.ts
@@ -38,6 +38,10 @@ export class SpinnerButtonElement extends HTMLElement {
this.addEventListener('spinner.stop', () => this.toggleSpinner(false));
}
+ disconnectedCallback() {
+ window.clearTimeout(this.#longWaitTimeout);
+ }
+
toggleSpinner(isVisible: boolean) {
const { button, actionMessage } = this.elements;
this.classList.toggle('spinner-button--spinner-active', isVisible);
diff --git a/app/javascript/packs/application.ts b/app/javascript/packs/application.ts
index 5ec39aefa75..227b3ade7f2 100644
--- a/app/javascript/packs/application.ts
+++ b/app/javascript/packs/application.ts
@@ -2,3 +2,8 @@ import { accordion, banner, skipnav } from 'identity-style-guide';
const components = [accordion, banner, skipnav];
components.forEach((component) => component.on());
+const mainContent = document.getElementById('main-content');
+document.querySelector('.usa-skipnav')?.addEventListener('click', (event) => {
+ event.preventDefault();
+ mainContent?.scrollIntoView();
+});
diff --git a/app/jobs/get_usps_proofing_results_job.rb b/app/jobs/get_usps_proofing_results_job.rb
index 90e50e5130e..a6d158d863a 100644
--- a/app/jobs/get_usps_proofing_results_job.rb
+++ b/app/jobs/get_usps_proofing_results_job.rb
@@ -28,6 +28,7 @@ def enrollment_analytics_attributes(enrollment, complete:)
enrollment_id: enrollment.id,
minutes_since_last_status_check: enrollment.minutes_since_last_status_check,
minutes_since_last_status_update: enrollment.minutes_since_last_status_update,
+ minutes_since_established: enrollment.minutes_since_established,
minutes_to_completion: complete ? enrollment.minutes_since_established : nil,
issuer: enrollment.issuer,
}
@@ -146,6 +147,11 @@ def handle_bad_request_error(err, enrollment)
if response_message == IPP_INCOMPLETE_ERROR_MESSAGE
# Customer has not been to post office for IPP
enrollment_outcomes[:enrollments_in_progress] += 1
+ analytics(user: enrollment.user).
+ idv_in_person_usps_proofing_results_job_enrollment_incomplete(
+ **enrollment_analytics_attributes(enrollment, complete: false),
+ response_message: response_message,
+ )
elsif response_message&.match(IPP_EXPIRED_ERROR_MESSAGE)
handle_expired_status_update(enrollment, err.response, response_message)
elsif response_message == IPP_INVALID_ENROLLMENT_CODE_MESSAGE % enrollment.enrollment_code
@@ -288,8 +294,7 @@ def handle_unexpected_response(enrollment, response_message, reason:, cancel: tr
analytics(user: enrollment.user).
idv_in_person_usps_proofing_results_job_unexpected_response(
- enrollment_code: enrollment.enrollment_code,
- enrollment_id: enrollment.id,
+ **enrollment_analytics_attributes(enrollment, complete: cancel),
response_message: response_message,
reason: reason,
)
diff --git a/app/jobs/reports/daily_dropoffs_report.rb b/app/jobs/reports/daily_dropoffs_report.rb
index e756723bc66..cca30f71748 100644
--- a/app/jobs/reports/daily_dropoffs_report.rb
+++ b/app/jobs/reports/daily_dropoffs_report.rb
@@ -117,7 +117,9 @@ def query_results
COALESCE(CASE WHEN doc_auth_logs.verify_submit_count > 0 THEN 1 else null END)
) AS verify_submit
, COUNT(doc_auth_logs.verify_phone_view_at) AS phone
- , COUNT(doc_auth_logs.verify_phone_submit_at) AS phone_submit
+ , COUNT(
+ COALESCE(CASE WHEN doc_auth_logs.verify_phone_submit_count > 0 THEN 1 else null END)
+ ) AS phone_submit
, COUNT(doc_auth_logs.encrypt_view_at) AS encrypt
, COUNT(doc_auth_logs.verified_view_at) AS personal_key
, COUNT(profiles.id) AS verified
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index b7db25bc899..c70358fb14f 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -85,10 +85,11 @@ def signup_with_your_email
end
end
- def reset_password_instructions(token:)
+ def reset_password_instructions(token:, request_id:)
with_user_locale(user) do
@locale = locale_url_param
@token = token
+ @request_id = request_id
@pending_profile_requires_verification = user.decorate.pending_profile_requires_verification?
@hide_title = @pending_profile_requires_verification
mail(to: email_address.email, subject: t('user_mailer.reset_password_instructions.subject'))
diff --git a/app/models/otp_requests_tracker.rb b/app/models/otp_requests_tracker.rb
deleted file mode 100644
index e95ca79b878..00000000000
--- a/app/models/otp_requests_tracker.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-class OtpRequestsTracker < ApplicationRecord
- def self.find_or_create_with_phone_and_confirmed(phone, phone_confirmed)
- create_or_find_by(
- phone_fingerprint: Pii::Fingerprinter.fingerprint(phone.strip),
- phone_confirmed: phone_confirmed,
- )
- end
-
- def self.atomic_increment(id)
- now = Time.zone.now
- # The following sql offers superior db performance with one write and no locking overhead
- query = sanitize_sql_array(
- ['UPDATE otp_requests_trackers ' \
- 'SET otp_send_count = otp_send_count + 1,' \
- 'otp_last_sent_at = ?, updated_at = ? ' \
- 'WHERE id = ?', now, now, id],
- )
- OtpRequestsTracker.connection.execute(query)
- OtpRequestsTracker.find(id)
- end
-end
diff --git a/app/presenters/openid_connect_user_info_presenter.rb b/app/presenters/openid_connect_user_info_presenter.rb
index edd9e71cb15..7f52a4aebc1 100644
--- a/app/presenters/openid_connect_user_info_presenter.rb
+++ b/app/presenters/openid_connect_user_info_presenter.rb
@@ -21,8 +21,6 @@ def user_info
info.merge!(ial2_attributes) if scoper.ial2_scopes_requested?
info.merge!(x509_attributes) if scoper.x509_scopes_requested?
info[:verified_at] = verified_at if scoper.verified_at_requested?
- info[:ial] = identity.ial if identity.ial.present?
- info[:aal] = identity.aal if identity.aal.present?
scoper.filter(info)
end
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 d69595a65bd..335e4ce294c 100644
--- a/app/presenters/two_factor_auth_code/phone_delivery_presenter.rb
+++ b/app/presenters/two_factor_auth_code/phone_delivery_presenter.rb
@@ -45,6 +45,33 @@ def help_text
''
end
+ def troubleshooting_header
+ t('components.troubleshooting_options.default_heading')
+ end
+
+ def troubleshooting_options
+ [
+ troubleshoot_change_phone_or_method_option,
+ {
+ url: MarketingSite.help_center_article_url(
+ category: 'get-started',
+ article: 'authentication-options',
+ anchor: 'didn-t-receive-your-one-time-code',
+ ),
+ text: t('two_factor_authentication.phone_verification.troubleshooting.code_not_received'),
+ new_tab: true,
+ },
+ {
+ url: MarketingSite.help_center_article_url(
+ category: 'get-started',
+ article: 'authentication-options',
+ ),
+ text: t('two_factor_authentication.phone_verification.troubleshooting.learn_more'),
+ new_tab: true,
+ },
+ ]
+ end
+
def cancel_link
locale = LinkLocaleResolver.locale
if confirmation_for_add_phone || reauthn
@@ -56,6 +83,20 @@ def cancel_link
private
+ def troubleshoot_change_phone_or_method_option
+ if unconfirmed_phone
+ {
+ url: phone_setup_path,
+ text: t('two_factor_authentication.phone_verification.troubleshooting.change_number'),
+ }
+ else
+ {
+ url: login_two_factor_options_path,
+ text: t('two_factor_authentication.login_options_link_text'),
+ }
+ end
+ end
+
attr_reader(
:phone_number,
:account_reset_token,
diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb
index 21a348deb16..a5fa8d1d7bf 100644
--- a/app/services/analytics_events.rb
+++ b/app/services/analytics_events.rb
@@ -3125,6 +3125,7 @@ def idv_in_person_usps_proofing_results_job_completed(
# @param [String] exception_class
# @param [String] exception_message
# @param [String] enrollment_code
+ # @param [Float] minutes_since_established
# @param [Float] minutes_since_last_status_check
# @param [Float] minutes_since_last_status_update
# @param [Float] minutes_to_completion
@@ -3145,6 +3146,7 @@ def idv_in_person_usps_proofing_results_job_completed(
def idv_in_person_usps_proofing_results_job_exception(
reason:,
enrollment_id:,
+ minutes_since_established:,
exception_class: nil,
exception_message: nil,
enrollment_code: nil,
@@ -3174,6 +3176,7 @@ def idv_in_person_usps_proofing_results_job_exception(
exception_class: exception_class,
exception_message: exception_message,
enrollment_code: enrollment_code,
+ minutes_since_established: minutes_since_established,
minutes_since_last_status_check: minutes_since_last_status_check,
minutes_since_last_status_update: minutes_since_last_status_update,
minutes_to_completion: minutes_to_completion,
@@ -3249,12 +3252,14 @@ def idv_in_person_email_reminder_job_exception(
# Tracks individual enrollments that are updated during GetUspsProofingResultsJob
# @param [String] enrollment_code
# @param [String] enrollment_id
+ # @param [Float] minutes_since_established
# @param [Boolean] fraud_suspected
# @param [Boolean] passed did this enrollment pass or fail?
# @param [String] reason why did this enrollment pass or fail?
def idv_in_person_usps_proofing_results_job_enrollment_updated(
enrollment_code:,
enrollment_id:,
+ minutes_since_established:,
fraud_suspected:,
passed:,
reason:,
@@ -3264,6 +3269,7 @@ def idv_in_person_usps_proofing_results_job_enrollment_updated(
'GetUspsProofingResultsJob: Enrollment status updated',
enrollment_code: enrollment_code,
enrollment_id: enrollment_id,
+ minutes_since_established: minutes_since_established,
fraud_suspected: fraud_suspected,
passed: passed,
reason: reason,
@@ -3300,14 +3306,38 @@ def idv_in_person_email_reminder_job_email_initiated(
)
end
+ # Tracks incomplete enrollments checked via the USPS API
+ # @param [String] enrollment_code
+ # @param [String] enrollment_id
+ # @param [Float] minutes_since_established
+ # @param [String] response_message
+ def idv_in_person_usps_proofing_results_job_enrollment_incomplete(
+ enrollment_code:,
+ enrollment_id:,
+ minutes_since_established:,
+ response_message:,
+ **extra
+ )
+ track_event(
+ 'GetUspsProofingResultsJob: Enrollment incomplete',
+ enrollment_code: enrollment_code,
+ enrollment_id: enrollment_id,
+ minutes_since_established: minutes_since_established,
+ response_message: response_message,
+ **extra,
+ )
+ end
+
# Tracks unexpected responses from the USPS API
# @param [String] enrollment_code
# @param [String] enrollment_id
+ # @param [Float] minutes_since_established
# @param [String] response_message
# @param [String] reason why was this error unexpected?
def idv_in_person_usps_proofing_results_job_unexpected_response(
enrollment_code:,
enrollment_id:,
+ minutes_since_established:,
response_message:,
reason:,
**extra
@@ -3316,6 +3346,7 @@ def idv_in_person_usps_proofing_results_job_unexpected_response(
'GetUspsProofingResultsJob: Unexpected response received',
enrollment_code: enrollment_code,
enrollment_id: enrollment_id,
+ minutes_since_established: minutes_since_established,
response_message: response_message,
reason: reason,
**extra,
diff --git a/app/services/arcgis_api/geocoder.rb b/app/services/arcgis_api/geocoder.rb
index 0b80b6b8b7a..421cab6b34e 100644
--- a/app/services/arcgis_api/geocoder.rb
+++ b/app/services/arcgis_api/geocoder.rb
@@ -6,7 +6,8 @@ class Geocoder
keyword_init: true
)
Location = Struct.new(:latitude, :longitude, keyword_init: true)
- API_TOKEN_CACHE_KEY = :arcgis_api_token
+ API_TOKEN_HOST = URI(IdentityConfig.store.arcgis_api_generate_token_url).host
+ API_TOKEN_CACHE_KEY = "arcgis_api_token:#{API_TOKEN_HOST}"
# These are option URL params that tend to apply to multiple endpoints
# https://developers.arcgis.com/rest/geocode/api-reference/geocoding-find-address-candidates.htm#ESRI_SECTION2_38613C3FCB12462CAADD55B2905140BF
@@ -35,12 +36,6 @@ class Geocoder
].join(','),
}.freeze
- ROOT_URL = IdentityConfig.store.arcgis_api_root_url
- SUGGEST_ENDPOINT = "#{ROOT_URL}/servernh/rest/services/GSA/USA/GeocodeServer/suggest"
- ADDRESS_CANDIDATES_ENDPOINT =
- "#{ROOT_URL}/servernh/rest/services/GSA/USA/GeocodeServer/findAddressCandidates"
- GENERATE_TOKEN_ENDPOINT = "#{ROOT_URL}/portal/sharing/rest/generateToken"
-
KNOWN_FIND_ADDRESS_CANDIDATES_PARAMETERS = [
:magicKey, # Generated from /suggest; identifier used to retrieve full address record
:SingleLine, # Unvalidated address-like text string used to search for geocoded addresses
@@ -60,7 +55,7 @@ def suggest(text)
}
parse_suggestions(
- faraday.get(SUGGEST_ENDPOINT, params, dynamic_headers) do |req|
+ faraday.get(IdentityConfig.store.arcgis_api_suggest_url, params, dynamic_headers) do |req|
req.options.context = { service_name: 'arcgis_geocoder_suggest' }
end.body,
)
@@ -88,7 +83,10 @@ def find_address_candidates(**options)
}
parse_address_candidates(
- faraday.get(ADDRESS_CANDIDATES_ENDPOINT, params, dynamic_headers) do |req|
+ faraday.get(
+ IdentityConfig.store.arcgis_api_find_address_candidates_url, params,
+ dynamic_headers
+ ) do |req|
req.options.context = { service_name: 'arcgis_geocoder_find_address_candidates' }
end.body,
)
@@ -195,7 +193,10 @@ def request_token
f: 'json',
}
- faraday.post(GENERATE_TOKEN_ENDPOINT, URI.encode_www_form(body)) do |req|
+ faraday.post(
+ IdentityConfig.store.arcgis_api_generate_token_url,
+ URI.encode_www_form(body),
+ ) do |req|
req.options.context = { service_name: 'usps_token' }
end.body
end
diff --git a/app/services/arcgis_api/mock/geocoder.rb b/app/services/arcgis_api/mock/geocoder.rb
index 6e869388e2d..d27b5a6f610 100644
--- a/app/services/arcgis_api/mock/geocoder.rb
+++ b/app/services/arcgis_api/mock/geocoder.rb
@@ -14,7 +14,7 @@ def faraday
private
def stub_generate_token(stub)
- stub.post(GENERATE_TOKEN_ENDPOINT) do |env|
+ stub.post(IdentityConfig.store.arcgis_api_generate_token_url) do |env|
[
200,
{ 'Content-Type': 'application/json' },
@@ -28,7 +28,7 @@ def stub_generate_token(stub)
end
def stub_suggestions(stub)
- stub.get(SUGGEST_ENDPOINT) do |env|
+ stub.get(IdentityConfig.store.arcgis_api_suggest_url) do |env|
[
200,
{ 'Content-Type': 'application/json' },
@@ -38,7 +38,7 @@ def stub_suggestions(stub)
end
def stub_address_candidates(stub)
- stub.get(ADDRESS_CANDIDATES_ENDPOINT) do |env|
+ stub.get(IdentityConfig.store.arcgis_api_find_address_candidates_url) do |env|
[
200,
{ 'Content-Type': 'application/json' },
diff --git a/app/services/idv/flows/doc_auth_flow.rb b/app/services/idv/flows/doc_auth_flow.rb
index e84d9a819f3..4a67c4d96bd 100644
--- a/app/services/idv/flows/doc_auth_flow.rb
+++ b/app/services/idv/flows/doc_auth_flow.rb
@@ -26,8 +26,9 @@ class DocAuthFlow < Flow::BaseFlow
{ name: :getting_started },
{ name: :verify_id },
{ name: :verify_info },
- { name: :secure_account },
+ *([name: :secure_account] if !IdentityConfig.store.gpo_personal_key_after_otp),
{ name: :get_a_letter },
+ *([name: :secure_account] if IdentityConfig.store.gpo_personal_key_after_otp),
].freeze
OPTIONAL_SHOW_STEPS = {
diff --git a/app/services/idv/steps/send_link_step.rb b/app/services/idv/steps/send_link_step.rb
index 06e99aa333f..1d1e29a5c5c 100644
--- a/app/services/idv/steps/send_link_step.rb
+++ b/app/services/idv/steps/send_link_step.rb
@@ -29,8 +29,22 @@ def call
build_telephony_form_response(telephony_result)
end
+ def extra_view_variables
+ {
+ idv_phone_form: build_form,
+ }
+ end
+
private
+ def build_form
+ Idv::PhoneForm.new(
+ previous_params: {},
+ user: current_user,
+ delivery_methods: [:sms],
+ )
+ end
+
def build_telephony_form_response(telephony_result)
FormResponse.new(
success: telephony_result.success?,
@@ -77,11 +91,7 @@ def sp_or_app_name
def form_submit
params = permit(:phone)
params[:otp_delivery_preference] = 'sms'
- Idv::PhoneForm.new(
- previous_params: {},
- user: current_user,
- delivery_methods: [:sms],
- ).submit(params)
+ build_form.submit(params)
end
def formatted_destination_phone
diff --git a/app/services/marketing_site.rb b/app/services/marketing_site.rb
index c3a29b9a2b2..55313387e1a 100644
--- a/app/services/marketing_site.rb
+++ b/app/services/marketing_site.rb
@@ -81,12 +81,12 @@ def self.security_url
URI.join(BASE_URL, locale_segment, 'security/').to_s
end
- def self.help_center_article_url(category:, article:)
+ def self.help_center_article_url(category:, article:, anchor: '')
if !valid_help_center_article?(category: category, article: article)
raise ArgumentError.new("Unknown help center article category #{category}/#{article}")
end
-
- URI.join(BASE_URL, locale_segment, "help/#{category}/#{article}/").to_s
+ anchor_text = anchor.present? ? "##{anchor}" : ''
+ URI.join(BASE_URL, locale_segment, "help/#{category}/#{article}/#{anchor_text}").to_s
end
def self.valid_help_center_article?(category:, article:)
diff --git a/app/services/otp_rate_limiter.rb b/app/services/otp_rate_limiter.rb
index 4afde203a54..b0aa75d31d7 100644
--- a/app/services/otp_rate_limiter.rb
+++ b/app/services/otp_rate_limiter.rb
@@ -15,19 +15,14 @@ def exceeded_otp_send_limit?
end
def max_requests_reached?
- return throttle.throttled? if IdentityConfig.store.redis_throttle_otp_rate_limiter_read_enabled
-
- entry_for_current_phone.otp_send_count > otp_maxretry_times
+ throttle.throttled?
end
def rate_limit_period_expired?
- return throttle.expired? if IdentityConfig.store.redis_throttle_otp_rate_limiter_read_enabled
- otp_last_sent_at.present? && (otp_last_sent_at + otp_findtime) < Time.zone.now
+ throttle.expired?
end
def reset_count_and_otp_last_sent_at
- entry_for_current_phone.update(otp_last_sent_at: Time.zone.now, otp_send_count: 0)
-
throttle.reset!
end
@@ -36,30 +31,21 @@ def lock_out_user
end
def increment
- # DO NOT MEMOIZE
- @entry = OtpRequestsTracker.atomic_increment(entry_for_current_phone.id)
throttle.increment!
- nil
end
def otp_last_sent_at
- if IdentityConfig.store.redis_throttle_otp_rate_limiter_read_enabled
- throttle.attempted_at
- else
- entry_for_current_phone.otp_last_sent_at
- end
+ throttle.attempted_at
+ end
+
+ def throttle
+ @throttle ||= Throttle.new(throttle_type: :phone_otp, target: throttle_key)
end
private
attr_reader :phone, :user, :phone_confirmed
- # rubocop:disable Naming/MemoizedInstanceVariableName
- def entry_for_current_phone
- @entry ||= OtpRequestsTracker.find_or_create_with_phone_and_confirmed(phone, phone_confirmed)
- end
- # rubocop:enable Naming/MemoizedInstanceVariableName
-
def otp_findtime
IdentityConfig.store.otp_delivery_blocklist_findtime.minutes
end
@@ -72,10 +58,6 @@ def phone_fingerprint
@phone_fingerprint ||= Pii::Fingerprinter.fingerprint(PhoneFormatter.format(phone))
end
- def throttle
- @throttle ||= Throttle.new(throttle_type: :phone_otp, target: throttle_key)
- end
-
def throttle_key
"#{phone_fingerprint}:#{phone_confirmed}"
end
diff --git a/app/services/request_password_reset.rb b/app/services/request_password_reset.rb
index 330b459a960..aa2b588d369 100644
--- a/app/services/request_password_reset.rb
+++ b/app/services/request_password_reset.rb
@@ -30,6 +30,7 @@ def send_reset_password_instructions
token = user.set_reset_password_token
UserMailer.with(user: user, email_address: email_address_record).reset_password_instructions(
token: token,
+ request_id: request_id,
).deliver_now_or_later
event = PushNotification::RecoveryActivatedEvent.new(user: user)
diff --git a/app/services/usps_in_person_proofing/mock/fixtures.rb b/app/services/usps_in_person_proofing/mock/fixtures.rb
index da574339eb4..51ca638275c 100644
--- a/app/services/usps_in_person_proofing/mock/fixtures.rb
+++ b/app/services/usps_in_person_proofing/mock/fixtures.rb
@@ -13,6 +13,10 @@ def self.request_facilities_response
load_response_fixture('request_facilities_response.json')
end
+ def self.request_facilities_response_with_duplicates
+ load_response_fixture('request_facilities_response_with_duplicates.json')
+ end
+
def self.request_show_usps_location_response
load_response_fixture('request_show_usps_location_response.json')
end
diff --git a/app/services/usps_in_person_proofing/mock/responses/request_facilities_response_with_duplicates.json b/app/services/usps_in_person_proofing/mock/responses/request_facilities_response_with_duplicates.json
new file mode 100644
index 00000000000..563c13e4c6d
--- /dev/null
+++ b/app/services/usps_in_person_proofing/mock/responses/request_facilities_response_with_duplicates.json
@@ -0,0 +1,224 @@
+{
+ "postOffices": [
+ {
+ "parking": "Street",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 6:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 3:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "0.05 mi",
+ "streetAddress": "3775 INDUSTRIAL BLVD",
+ "city": "WEST SACRAMENTO",
+ "phone": "916-556-3406",
+ "name": "INDUSTRIAL WEST SACRAMENTO",
+ "zip4": "9998",
+ "state": "CA",
+ "zip5": "95799"
+ },
+ {
+ "parking": "Lot",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 6:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 3:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "0.05 mi",
+ "streetAddress": "3775 INDUSTRIAL BLVD",
+ "city": "WEST SACRAMENTO",
+ "phone": "916-373-8157",
+ "name": "WEST SACRAMENTO P\u0026DC",
+ "zip4": "0102",
+ "state": "CA",
+ "zip5": "95799"
+ },
+ {
+ "parking": "Lot",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 5:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 5:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "2.22 mi",
+ "streetAddress": "1601 MERKLEY AVE",
+ "city": "WEST SACRAMENTO",
+ "name": "WEST SACRAMENTO",
+ "phone": "916-556-3406",
+ "state": "CA",
+ "zip4": "9998",
+ "zip5": "95691"
+ },
+ {
+ "parking": "Street",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 6:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 3:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "2.77 mi",
+ "streetAddress": "900 SACRAMENTO AVE",
+ "city": "WEST SACRAMENTO",
+ "phone": "916-556-3406",
+ "name": "BRODERICK",
+ "zip4": "9998",
+ "state": "CA",
+ "zip5": "95605"
+ },
+ {
+ "parking": "Lot",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 6:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 3:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "3.87 mi",
+ "streetAddress": "660 J ST STE 170",
+ "city": "SACRAMENTO",
+ "phone": "916-498-9145",
+ "name": "DOWNTOWN PLAZA",
+ "zip4": "9996",
+ "state": "CA",
+ "zip5": "95814"
+ },
+ {
+ "parking": "Lot",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 6:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 3:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "4.53 mi",
+ "streetAddress": "2121 BROADWAY",
+ "city": "SACRAMENTO",
+ "name": "BROADWAY",
+ "phone": "916-227-6503",
+ "zip4": "9998",
+ "state": "CA",
+ "zip5": "95818"
+ },
+ {
+ "parking": "Lot",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 6:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 3:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "4.55 mi",
+ "streetAddress": "5930 S LAND PARK DR",
+ "city": "SACRAMENTO",
+ "phone": "916-262-3107",
+ "name": "LAND PARK",
+ "zip4": "9998",
+ "state": "CA",
+ "zip5": "95822"
+ },
+ {
+ "parking": "Lot",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 6:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 3:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "5.33 mi",
+ "streetAddress": "1618 ALHAMBRA BLVD",
+ "city": "SACRAMENTO",
+ "phone": "916-227-6503",
+ "name": "FORT SUTTER",
+ "zip4": "9998",
+ "state": "CA",
+ "zip5": "95816"
+ },
+ {
+ "parking": "Lot",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 6:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 3:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "5.49 mi",
+ "streetAddress": "2929 35TH ST",
+ "city": "SACRAMENTO",
+ "phone": "916-227-6509",
+ "name": "OAK PARK",
+ "zip4": "9998",
+ "state": "CA",
+ "zip5": "95817"
+ },
+ {
+ "parking": "Lot",
+ "hours": [
+ {
+ "weekdayHours": "9:00 AM - 6:00 PM"
+ },
+ {
+ "saturdayHours": "9:00 AM - 3:00 PM"
+ },
+ {
+ "sundayHours": "Closed"
+ }
+ ],
+ "distance": "6.63 mi",
+ "streetAddress": "4750 J ST",
+ "city": "SACRAMENTO",
+ "phone": "916-227-6503",
+ "name": "CAMELLIA",
+ "zip4": "9998",
+ "state": "CA",
+ "zip5": "95819"
+ }
+ ]
+}
diff --git a/app/services/usps_in_person_proofing/proofer.rb b/app/services/usps_in_person_proofing/proofer.rb
index 2153fb7f271..a2d6a0a61bd 100644
--- a/app/services/usps_in_person_proofing/proofer.rb
+++ b/app/services/usps_in_person_proofing/proofer.rb
@@ -18,11 +18,12 @@ def request_facilities(location)
zipCode: location.zip_code,
}.to_json
- parse_facilities(
+ facilities = parse_facilities(
faraday.post(url, body, dynamic_headers) do |req|
req.options.context = { service_name: 'usps_facilities' }
end.body,
)
+ dedupe_facilities(facilities)
end
# Temporary function to return a static set of facilities
@@ -218,5 +219,11 @@ def parse_facilities(facilities)
)
end
end
+
+ def dedupe_facilities(facilities)
+ facilities.uniq do |facility|
+ [facility.address, facility.city, facility.state, facility.zip_code_5]
+ end
+ end
end
end
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb
index c23be3588e6..f57684bf32d 100644
--- a/app/views/devise/passwords/edit.html.erb
+++ b/app/views/devise/passwords/edit.html.erb
@@ -1,5 +1,7 @@
<% title t('titles.passwords.change') %>
+<% request_id = params[:request_id] || sp_session[:request_id] %>
+
<%= render PageHeadingComponent.new.with_content(t('headings.passwords.change')) %>
<%= t('instructions.password.password_key') %>
@@ -19,6 +21,7 @@
required: true,
},
) %>
+ <%= hidden_field_tag('request_id', request_id) %>
<%= render 'devise/shared/password_strength', forbidden_passwords: @forbidden_passwords %>
<%= f.submit t('forms.passwords.edit.buttons.submit'), class: 'display-block margin-y-5' %>
<% end %>
diff --git a/app/views/idv/come_back_later/show.html.erb b/app/views/idv/come_back_later/show.html.erb
index fc6a469223d..4f3436972c4 100644
--- a/app/views/idv/come_back_later/show.html.erb
+++ b/app/views/idv/come_back_later/show.html.erb
@@ -17,18 +17,25 @@
<%= render PageHeadingComponent.new(class: 'text-center').with_content(t('idv.titles.come_back_later')) %>
+
+ <%= t('idv.messages.come_back_later_html', app_name: APP_NAME) %>
+
+
- <%= t('idv.messages.come_back_later', app_name: APP_NAME) %>
+ <%= t('idv.messages.come_back_later_password_html') %>
+
+
<% if decorated_session.sp_name.present? %>
<%= t('idv.messages.come_back_later_sp_html', sp: decorated_session.sp_name) %>
<% else %>
<%= t('idv.messages.come_back_later_no_sp_html', app_name: APP_NAME) %>
<% end %>
+
<% if decorated_session.sp_name.present? %>
<%= link_to(
- t('idv.buttons.continue_plain'),
+ t('idv.cancel.actions.exit', app_name: APP_NAME),
return_to_sp_cancel_path(location: :come_back_later),
class: 'usa-button usa-button--big usa-button--wide',
) %>
@@ -39,4 +46,5 @@
class: 'usa-button usa-button--big usa-button--wide',
) %>
<% end %>
+
diff --git a/app/views/idv/doc_auth/send_link.html.erb b/app/views/idv/doc_auth/send_link.html.erb
index 2ad9512e160..1e49331ba97 100644
--- a/app/views/idv/doc_auth/send_link.html.erb
+++ b/app/views/idv/doc_auth/send_link.html.erb
@@ -16,7 +16,8 @@
<%= t('doc_auth.instructions.send_sms') %>
<%= simple_form_for(
- :doc_auth,
+ idv_phone_form,
+ as: :doc_auth,
url: url_for,
method: 'PUT',
html: { autocomplete: 'off' },
diff --git a/app/views/two_factor_authentication/otp_verification/show.html.erb b/app/views/two_factor_authentication/otp_verification/show.html.erb
index 71925e45e2b..9569dc12a94 100644
--- a/app/views/two_factor_authentication/otp_verification/show.html.erb
+++ b/app/views/two_factor_authentication/otp_verification/show.html.erb
@@ -56,14 +56,12 @@
).with_content(t('links.two_factor_authentication.send_another_code')) %>
<% end %>
-<% if @presenter.unconfirmed_phone? %>
-
- <%= t('instructions.mfa.wrong_number') %>
- <%= link_to(t('forms.two_factor.try_again'), phone_setup_path) %>
-
-<% else %>
- <%= render 'shared/fallback_links', presenter: @presenter %>
-<% end %>
+
+<%= render(
+ 'shared/troubleshooting_options',
+ heading: @presenter.troubleshooting_header,
+ options: @presenter.troubleshooting_options,
+ ) %>
<% if MfaPolicy.new(current_user).two_factor_enabled? %>
<%= render 'shared/cancel', link: @presenter.cancel_link %>
diff --git a/app/views/user_mailer/reset_password_instructions.html.erb b/app/views/user_mailer/reset_password_instructions.html.erb
index c109b8090b7..89826627c92 100644
--- a/app/views/user_mailer/reset_password_instructions.html.erb
+++ b/app/views/user_mailer/reset_password_instructions.html.erb
@@ -33,7 +33,7 @@
<%= link_to t('user_mailer.reset_password_instructions.link_text'),
- edit_user_password_url(reset_password_token: @token, locale: @locale),
+ edit_user_password_url(reset_password_token: @token, locale: @locale, request_id: @request_id),
target: '_blank', class: 'float-center', align: 'center', rel: 'noopener' %>
|
@@ -48,8 +48,8 @@
- <%= link_to edit_user_password_url(reset_password_token: @token, locale: @locale),
- edit_user_password_url(reset_password_token: @token, locale: @locale),
+ <%= link_to edit_user_password_url(reset_password_token: @token, locale: @locale, request_id: @request_id),
+ edit_user_password_url(reset_password_token: @token, locale: @locale, request_id: @request_id),
target: '_blank', rel: 'noopener' %>
diff --git a/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb b/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb
index 99624795266..fbecd121669 100644
--- a/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb
+++ b/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb
@@ -84,7 +84,7 @@
<%= t('date.day_names')[6] %>: <%= @presenter.selected_location_hours(:sunday) %>
- <%= @presenter.selected_location_details[:phone] %>
+ <%= @presenter.selected_location_details['phone'] %>
<% end %>
diff --git a/config/application.rb b/config/application.rb
index 25fdd4bb518..a1bfbecfdc8 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -14,6 +14,7 @@
require_relative '../lib/identity_job_log_subscriber'
require_relative '../lib/email_delivery_observer'
require_relative '../lib/good_job_connection_pool_size'
+require_relative '../lib/identity_cors'
Bundler.require(*Rails.groups)
@@ -110,25 +111,10 @@ class Application < Rails::Application
require 'secure_cookies'
config.middleware.insert_after ActionDispatch::Static, SecureCookies
- # rubocop:disable Metrics/BlockLength
config.middleware.insert_before 0, Rack::Cors do
allow do
origins do |source, _env|
- next if source == IdentityConfig.store.domain_name
-
- redirect_uris = Rails.cache.fetch(
- 'all_service_provider_redirect_uris',
- expires_in: IdentityConfig.store.all_redirect_uris_cache_duration_minutes.minutes,
- ) do
- ServiceProvider.pluck(:redirect_uris).flatten.compact
- end
-
- redirect_uris.find do |uri|
- split_uri = uri.split('//')
- protocol = split_uri[0]
- domain = split_uri[1].split('/')[0] if split_uri.size > 1
- source == "#{protocol}//#{domain}"
- end.present?
+ IdentityCors.allowed_redirect_uri?(source)
end
resource '/.well-known/openid-configuration', headers: :any, methods: [:get]
resource '/api/openid_connect/certs', headers: :any, methods: [:get]
@@ -140,24 +126,11 @@ class Application < Rails::Application
end
allow do
- allowed_origins = [
- 'https://www.login.gov',
- 'https://login.gov',
- 'https://handbook.login.gov',
- %r{^https://federalist-[0-9a-f-]+\.app\.cloud\.gov$},
- ]
-
- if Rails.env.development? || Rails.env.test?
- allowed_origins << %r{https?://localhost(:\d+)?$}
- allowed_origins << %r{https?://127\.0\.0\.1(:\d+)?$}
- end
-
- origins allowed_origins
+ origins IdentityCors.allowed_origins_static_sites
resource '/api/analytics-events', headers: :any, methods: [:get]
resource '/api/country-support', headers: :any, methods: [:get]
end
end
- # rubocop:enable Metrics/BlockLength
if !IdentityConfig.store.enable_rate_limiting
# Rack::Attack auto-includes itself as a Railtie, so we need to
diff --git a/config/application.yml.default b/config/application.yml.default
index 1d4ce53eb64..341df2666b3 100644
--- a/config/application.yml.default
+++ b/config/application.yml.default
@@ -41,11 +41,13 @@ acuant_get_results_timeout: 1.0
acuant_create_document_timeout: 1.0
add_email_link_valid_for_hours: 24
address_identity_proofing_supported_country_codes: '["AS", "GU", "MP", "PR", "US", "VI"]'
-arcgis_api_root_url: 'https://gis.gsa.gov'
arcgis_api_username: ''
arcgis_api_password: ''
-arcgis_search_enabled: false
+arcgis_search_enabled: true
arcgis_mock_fallback: true
+arcgis_api_generate_token_url: 'https://gis.gsa.gov/portal/sharing/rest/generateToken'
+arcgis_api_suggest_url: 'https://gis.gsa.gov/servernh/rest/services/GSA/USA/GeocodeServer/suggest'
+arcgis_api_find_address_candidates_url: 'https://gis.gsa.gov/servernh/rest/services/GSA/USA/GeocodeServer/findAddressCandidates'
asset_host: ''
async_wait_timeout_seconds: 60
async_stale_job_timeout_seconds: 300
@@ -110,6 +112,7 @@ good_job_max_threads: 5
good_job_queues: 'default:5;low:1;*'
good_job_queue_select_limit: 5_000
gpo_designated_receiver_pii: '{}'
+gpo_personal_key_after_otp: false
hide_phone_mfa_signup: false
identity_pki_disabled: false
identity_pki_local_dev: false
@@ -189,7 +192,7 @@ logins_per_ip_period: 60
logo_upload_enabled: false
mailer_domain_name: http://localhost:3000
max_auth_apps_per_account: 2
-max_bad_passwords: 100
+max_bad_passwords: 5
max_bad_passwords_window_in_seconds: 60
max_emails_per_account: 12
max_mail_events: 4
@@ -243,7 +246,6 @@ rails_mailer_previews_enabled: false
reauthn_window: 120
recovery_code_length: 4
redis_irs_attempt_api_url: redis://localhost:6379/2
-redis_throttle_otp_rate_limiter_read_enabled: false
redis_throttle_url: redis://localhost:6379/1
redis_url: redis://localhost:6379/0
redis_pool_size: 10
@@ -378,7 +380,6 @@ development:
rails_mailer_previews_enabled: true
rack_timeout_service_timeout_seconds: 9_999_999_999
recurring_jobs_disabled_names: "[]"
- redis_throttle_otp_rate_limiter_read_enabled: true
s3_report_bucket_prefix: ''
s3_report_public_bucket_prefix: ''
saml_endpoint_configs: '[{"suffix":"2021","secret_key_passphrase":"trust-but-verify"},{"suffix":"2022","secret_key_passphrase":"trust-but-verify"}]'
@@ -539,7 +540,6 @@ test:
piv_cac_verify_token_secret: 3ac13bfa23e22adae321194c083e783faf89469f6f85dcc0802b27475c94b5c3891b5657bd87d0c1ad65de459166440512f2311018db90d57b15d8ab6660748f
poll_rate_for_verify_in_seconds: 0
recurring_jobs_disabled_names: '["disabled job"]'
- redis_throttle_otp_rate_limiter_read_enabled: true
reg_confirmed_email_max_attempts: 3
reg_unconfirmed_email_max_attempts: 4
reg_unconfirmed_email_window_in_minutes: 70
diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml
index 44c0fc3317b..1ed6b750a6c 100644
--- a/config/locales/idv/en.yml
+++ b/config/locales/idv/en.yml
@@ -131,9 +131,14 @@ en:
verified information, please %{link}.
activated_link: contact us
clear_and_start_over: Clear my information and start over
- come_back_later: Once your letter arrives, sign into %{app_name}, and enter your
- one-time code when prompted.
+ come_back_later_html: Once your
+ letter arrives, sign into %{app_name}, and enter your one-time code when
+ prompted.
come_back_later_no_sp_html: You can return to your Una
+ vez que reciba la carta, inicie sesión en %{app_name} e introduzca el
+ código único cuando se le solicite.
come_back_later_no_sp_html: Ahora puedes volver a tu Une fois votre lettre arrivée,
+ connectez-vous à %{app_name} et entrez votre code à usage unique lorsque
+ vous y êtes invité.
come_back_later_no_sp_html: Vous pouvez revenir à votre