diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 31f338017b9..207557da566 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -93,18 +93,16 @@ install: # Build a container image async, and don't block CI tests # Cache intermediate images for 1 week (168 hours) -build-review-image: +build-idp-image: stage: review needs: [] - environment: - name: review/$CI_COMMIT_REF_NAME interruptible: true variables: BRANCH_TAGGING_STRING: '' rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH variables: - BRANCH_TAGGING_STRING: '--destination ${ECR_REGISTRY}/identity-idp/review:main' + BRANCH_TAGGING_STRING: '--destination ${ECR_REGISTRY}/identity-idp/idp:main' - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - if: $CI_PIPELINE_SOURCE != "merge_request_event" when: never @@ -129,12 +127,13 @@ build-review-image: - >- /kaniko/executor --context "${CI_PROJECT_DIR}" - --dockerfile "${CI_PROJECT_DIR}/dockerfiles/idp_review_app.Dockerfile" - --destination "${ECR_REGISTRY}/identity-idp/review:${CI_COMMIT_SHA}" + --dockerfile "${CI_PROJECT_DIR}/dockerfiles/idp_deploy.Dockerfile" + --destination "${ECR_REGISTRY}/identity-idp/idp:${CI_COMMIT_SHA}" ${BRANCH_TAGGING_STRING} - --cache-repo="${ECR_REGISTRY}/identity-idp/review/cache" + --cache-repo="${ECR_REGISTRY}/identity-idp/idp/cache" --cache-ttl=168h --cache=true + --snapshot-mode=redo --compressed-caching=false --build-arg "http_proxy=${http_proxy}" --build-arg "https_proxy=${https_proxy}" @@ -142,8 +141,11 @@ build-review-image: --build-arg "ARG_CI_ENVIRONMENT_SLUG=${CI_ENVIRONMENT_SLUG}" --build-arg "ARG_CI_COMMIT_BRANCH=${CI_COMMIT_BRANCH}" --build-arg "ARG_CI_COMMIT_SHA=${CI_COMMIT_SHA}" + --build-arg "LARGE_FILES_TOKEN=${LARGE_FILES_TOKEN}" + --build-arg "LARGE_FILES_USER=${LARGE_FILES_USER}" + --build-arg "SERVICE_PROVIDERS_KEY=${SERVICE_PROVIDERS_KEY}" -build-idp-image: +build-nginx-image: stage: review needs: [] interruptible: true @@ -152,7 +154,7 @@ build-idp-image: rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH variables: - BRANCH_TAGGING_STRING: '--destination ${ECR_REGISTRY}/identity-idp/idp:main' + BRANCH_TAGGING_STRING: '--destination ${ECR_REGISTRY}/identity-idp/nginx:main' - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - if: $CI_PIPELINE_SOURCE != "merge_request_event" when: never @@ -177,8 +179,8 @@ build-idp-image: - >- /kaniko/executor --context "${CI_PROJECT_DIR}" - --dockerfile "${CI_PROJECT_DIR}/dockerfiles/idp_deploy.Dockerfile" - --destination "${ECR_REGISTRY}/identity-idp/idp:${CI_COMMIT_SHA}" + --dockerfile "${CI_PROJECT_DIR}/dockerfiles/nginx.Dockerfile" + --destination "${ECR_REGISTRY}/identity-idp/nginx:${CI_COMMIT_SHA}" ${BRANCH_TAGGING_STRING} --cache-repo="${ECR_REGISTRY}/identity-idp/idp/cache" --cache-ttl=168h @@ -195,7 +197,6 @@ build-idp-image: --build-arg "LARGE_FILES_USER=${LARGE_FILES_USER}" --build-arg "SERVICE_PROVIDERS_KEY=${SERVICE_PROVIDERS_KEY}" - check_changelog: stage: test variables: @@ -672,19 +673,6 @@ secret_detection: # Export the automated ECR scan results into a format Gitlab can use # Report schema https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/container-scanning-report-format.json -ecr-scan-review-app: - extends: .container_scan_template - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - - if: $CI_PIPELINE_SOURCE != "merge_request_event" - when: never - needs: - - job: build-review-image - stage: scan - variables: - ecr_repo: identity-idp/review - ecr-scan-ci: extends: .container_scan_template rules: diff --git a/.rubocop.yml b/.rubocop.yml index 52c97e23501..00d3129d03b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,6 +7,7 @@ require: - rubocop-rails - rubocop-rspec - rubocop-performance + - ./lib/linters/i18n_helper_html_linter.rb - ./lib/linters/analytics_event_name_linter.rb - ./lib/linters/localized_validation_message_linter.rb - ./lib/linters/image_size_linter.rb @@ -45,6 +46,13 @@ Bundler/InsecureProtocolSource: Gemspec/DuplicatedAssignment: Enabled: true +IdentityIdp/I18nHelperHtmlLinter: + Enabled: true + Include: + - app/views/**/*.erb + - app/components/**/*.erb + - app/controllers/**/*.rb + IdentityIdp/AnalyticsEventNameLinter: Enabled: true Include: diff --git a/Gemfile b/Gemfile index db1a2b773d9..17262773376 100644 --- a/Gemfile +++ b/Gemfile @@ -36,7 +36,7 @@ gem 'fugit' gem 'foundation_emails' gem 'good_job', '~> 4.0' gem 'http_accept_language' -gem 'identity-hostdata', github: '18F/identity-hostdata', tag: 'v4.0.0' +gem 'identity-hostdata', github: '18F/identity-hostdata', tag: 'v4.4.1' gem 'identity-logging', github: '18F/identity-logging', tag: 'v0.1.1' gem 'identity_validations', github: '18F/identity-validations', tag: 'v0.7.2' gem 'jsbundling-rails', '~> 1.1.2' @@ -66,7 +66,7 @@ gem 'rack-headers_filter' gem 'rack-timeout', require: false gem 'redacted_struct' gem 'redis', '>= 3.2.0' -gem 'redis-session-store', github: '18F/redis-session-store', tag: 'v1.0.1-18f' +gem 'redis-session-store', github: '18F/redis-session-store', tag: 'v1.0.2-18f' gem 'retries' gem 'rexml', '~> 3.3' gem 'rotp', '~> 6.3', '>= 6.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 26f1b15d422..9fe38995467 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,11 +1,12 @@ GIT remote: https://github.com/18F/identity-hostdata.git - revision: 9574e05398833c531f450c3da99a6afde4ce68fc - tag: v4.0.0 + revision: 67a19c577b8fa9305350cf9cefa572cef4a80310 + tag: v4.4.1 specs: - identity-hostdata (4.0.0) - activesupport (>= 6.1, < 8) + identity-hostdata (4.4.1) + activesupport (>= 6.1, < 9) aws-sdk-s3 (~> 1.8) + aws-sdk-secretsmanager (>= 1.91) redacted_struct (>= 2.0) GIT @@ -26,11 +27,11 @@ GIT GIT remote: https://github.com/18F/redis-session-store.git - revision: 9e3f8a22a1b5d1e835e5cba20c51e38b8965b836 - tag: v1.0.1-18f + revision: 905c146bbc1c09ce411edd036eac266c53f5b153 + tag: v1.0.2-18f specs: - redis-session-store (1.0.1.pre.18f) - actionpack (>= 6, < 8) + redis-session-store (1.0.2.pre.18f) + actionpack (>= 6, < 9) redis (>= 4.3, < 6) GIT @@ -182,6 +183,9 @@ GEM aws-sdk-core (~> 3, >= 3.179.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.6) + aws-sdk-secretsmanager (1.102.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) aws-sdk-ses (1.44.0) aws-sdk-core (~> 3, >= 3.122.0) aws-sigv4 (~> 1.1) diff --git a/Makefile b/Makefile index 1fbbab9a121..f10d272378a 100644 --- a/Makefile +++ b/Makefile @@ -120,9 +120,11 @@ lint_yaml: normalize_yaml ## Lints YAML files lint_font_glyphs: ## Lints to validate content glyphs match expectations from fonts scripts/yaml_characters \ --exclude-locale=zh \ + --exclude-path=config/locales/telephony \ --exclude-gem-path=faker \ --exclude-gem-path=good_job \ --exclude-gem-path=i18n-tasks \ + --exclude-key-scope=user_mailer \ > app/assets/fonts/glyphs.txt (! git diff --name-only | grep "glyphs\.txt$$") || (echo "Error: New character data found. Follow 'Fonts' instructions in 'docs/frontend.md' to regenerate fonts."; exit 1) diff --git a/Procfile b/Procfile index 5bcd9cc496a..b62e7746a6d 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,4 @@ web: WEBPACK_PORT=${WEBPACK_PORT:-3035} bundle exec rackup config.ru --port ${PORT:-3000} --host ${FOREMAN_HOST:-${HOST:-localhost}} worker: bundle exec good_job start -js: WEBPACK_PORT=${WEBPACK_PORT:-3035} yarn webpack $([ -n "$HTTPS" ] && echo "--watch" || echo "serve") +js: WEBPACK_PORT=${WEBPACK_PORT:-3035} yarn webpack --watch css: yarn build:css --watch diff --git a/app/assets/stylesheets/components/_btn.scss b/app/assets/stylesheets/components/_btn.scss index 452ac960179..6500169b809 100644 --- a/app/assets/stylesheets/components/_btn.scss +++ b/app/assets/stylesheets/components/_btn.scss @@ -8,7 +8,7 @@ } .usa-button:disabled.usa-button--active, -[aria-disabled='true'].usa-button--active { +.usa-button[aria-disabled='true'].usa-button--active { &:not( .usa-button--unstyled, .usa-button--secondary, diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index 78add678c47..297ac4a2a95 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -79,12 +79,27 @@ def ssn_rate_limiter def idv_failure(result) proofing_results_exception = result.extra.dig(:proofing_results, :exception) + has_exception = proofing_results_exception.present? is_mva_exception = result.extra.dig( :proofing_results, :context, :stages, :state_id, :mva_exception, + ).present? + is_threatmetrix_exception = result.extra.dig( + :proofing_results, + :context, + :stages, + :threatmetrix, + :exception, + ).present? + resolution_failed = !result.extra.dig( + :proofing_results, + :context, + :stages, + :resolution, + :success, ) if ssn_rate_limiter.limited? @@ -93,10 +108,14 @@ def idv_failure(result) elsif resolution_rate_limiter.limited? idv_failure_log_rate_limited(:idv_resolution) redirect_to rate_limited_url - elsif proofing_results_exception.present? && is_mva_exception + elsif has_exception && is_mva_exception idv_failure_log_warning redirect_to state_id_warning_url - elsif proofing_results_exception.present? + elsif (has_exception && is_threatmetrix_exception) || + (!has_exception && resolution_failed) + idv_failure_log_warning + redirect_to warning_url + elsif has_exception idv_failure_log_error redirect_to exception_url else diff --git a/app/controllers/idv/hybrid_handoff_controller.rb b/app/controllers/idv/hybrid_handoff_controller.rb index 0702bf9b829..8d41ee040c9 100644 --- a/app/controllers/idv/hybrid_handoff_controller.rb +++ b/app/controllers/idv/hybrid_handoff_controller.rb @@ -5,6 +5,7 @@ class HybridHandoffController < ApplicationController include Idv::AvailabilityConcern include ActionView::Helpers::DateHelper include IdvStepConcern + include DocAuthVendorConcern include StepIndicatorConcern before_action :confirm_not_rate_limited @@ -12,8 +13,7 @@ class HybridHandoffController < ApplicationController before_action :confirm_hybrid_handoff_needed, only: :show def show - @upload_disabled = idv_session.selfie_check_required && - !idv_session.desktop_selfie_test_mode_enabled? + @upload_disabled = upload_disabled? @direct_ipp_with_selfie_enabled = IdentityConfig.store.in_person_doc_auth_button_enabled && Idv::InPersonConfig.enabled_for_issuer?( @@ -74,6 +74,8 @@ def self.step_info ) end + private + def handle_phone_submission return rate_limited_failure if rate_limiter.limited? rate_limiter.increment! @@ -120,6 +122,11 @@ def sp_or_app_name current_sp&.friendly_name.presence || APP_NAME end + def upload_disabled? + (doc_auth_vendor == Idp::Constants::Vendors::SOCURE || idv_session.selfie_check_required) && + !idv_session.desktop_selfie_test_mode_enabled? + end + def build_telephony_form_response(telephony_result) FormResponse.new( success: telephony_result.success?, diff --git a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb index 4cbf8f44fdf..5368b620ca8 100644 --- a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb @@ -19,7 +19,6 @@ def show # document request document_request = DocAuth::Socure::Requests::DocumentRequest.new( - document_capture_session_uuid: document_capture_session_uuid, redirect_url: idv_hybrid_mobile_socure_document_capture_url, language: I18n.locale, ) diff --git a/app/controllers/idv/in_person/public/usps_locations_controller.rb b/app/controllers/idv/in_person/public/usps_locations_controller.rb index b8f20a7f96d..9531141600c 100644 --- a/app/controllers/idv/in_person/public/usps_locations_controller.rb +++ b/app/controllers/idv/in_person/public/usps_locations_controller.rb @@ -3,15 +3,34 @@ module Idv module InPerson module Public + class UspsLocationsError < StandardError + def initialize + super('Unsupported characters in address field.') + end + end + class UspsLocationsController < ApplicationController skip_forgery_protection + include IppHelper + + rescue_from Faraday::Error, + StandardError, + UspsLocationsError, + Faraday::BadRequestError, + with: :handle_error + def index candidate = UspsInPersonProofing::Applicant.new( address: search_params['street_address'], city: search_params['city'], state: search_params['state'], zip_code: search_params['zip_code'] ) + + unless candidate.has_valid_address? + raise UspsLocationsError + end + locations = proofer.request_facilities(candidate, false) render json: localized_locations(locations).to_json @@ -34,6 +53,27 @@ def localized_locations(locations) end end + def handle_error(err) + remapped_error = case err + when ActionController::InvalidAuthenticityToken, + Faraday::Error, + UspsLocationsError + :unprocessable_entity + else + :internal_server_error + end + + analytics.idv_in_person_locations_request_failure( + api_status_code: Rack::Utils.status_code(remapped_error), + exception_class: err.class, + exception_message: scrub_message(err.message), + response_body_present: err.respond_to?(:response_body) && err.response_body.present?, + response_body: err.respond_to?(:response_body) && scrub_body(err.response_body), + response_status_code: err.respond_to?(:response_status) && err.response_status, + ) + render json: {}, status: remapped_error + end + def search_params params.require(:address).permit( :street_address, diff --git a/app/controllers/idv/socure/document_capture_controller.rb b/app/controllers/idv/socure/document_capture_controller.rb index 805b92c3851..acf6191b133 100644 --- a/app/controllers/idv/socure/document_capture_controller.rb +++ b/app/controllers/idv/socure/document_capture_controller.rb @@ -27,7 +27,6 @@ def show # document request document_request = DocAuth::Socure::Requests::DocumentRequest.new( - document_capture_session_uuid: document_capture_session_uuid, redirect_url: idv_socure_document_capture_update_url, language: I18n.locale, ) diff --git a/app/controllers/socure_webhook_controller.rb b/app/controllers/socure_webhook_controller.rb index a4ba596cae3..212f01e02e8 100644 --- a/app/controllers/socure_webhook_controller.rb +++ b/app/controllers/socure_webhook_controller.rb @@ -26,6 +26,8 @@ def process_webhook_event when 'DOCUMENTS_UPLOADED' increment_rate_limiter fetch_results + when 'SESSION_EXPIRED', 'SESSION_COMPLETE' + reset_docv_url end end @@ -80,9 +82,10 @@ def log_webhook_receipt analytics.idv_doc_auth_socure_webhook_received( created_at: event[:created], customer_user_id: event[:customerUserId], + docv_transaction_token: event[:docvTransactionToken], event_type: event[:eventType], reference_id: event[:referenceId], - user_id: event[:customerUserId], + user_id: user&.uuid, ) end @@ -93,10 +96,16 @@ def increment_rate_limiter # Logic to throw an error when no DocumentCaptureSession found will be done in ticket LG-14905 end + def reset_docv_url + if document_capture_session.present? + document_capture_session.socure_docv_capture_app_url = nil + document_capture_session.save + end + end + def document_capture_session - token = event[:docvTransactionToken] || event[:docVTransactionToken] @document_capture_session ||= DocumentCaptureSession.find_by( - socure_docv_transaction_token: token, + socure_docv_transaction_token: docv_transaction_token, ) end @@ -117,4 +126,12 @@ def socure_params :docvTransactionToken, :docVTransactionToken], ) end + + def user + @user ||= document_capture_session&.user + end + + def docv_transaction_token + @docv_transaction_token ||= event[:docvTransactionToken] || event[:docVTransactionToken] + end end diff --git a/app/controllers/users/emails_controller.rb b/app/controllers/users/emails_controller.rb index 0f8681fa729..09e24410d3e 100644 --- a/app/controllers/users/emails_controller.rb +++ b/app/controllers/users/emails_controller.rb @@ -99,6 +99,7 @@ def email_address def handle_successful_delete send_delete_email_notification + user_session.delete(:selected_email_id_for_linked_identity) flash[:success] = t('email_addresses.delete.success') create_user_event(:email_deleted) end diff --git a/app/forms/frontend_error_form.rb b/app/forms/frontend_error_form.rb index e7fcd196efc..f9ed8046632 100644 --- a/app/forms/frontend_error_form.rb +++ b/app/forms/frontend_error_form.rb @@ -7,10 +7,11 @@ class FrontendErrorForm validate :validate_filename_extension validate :validate_filename_host - attr_reader :filename + attr_reader :filename, :error_id - def submit(filename:) + def submit(filename:, error_id:) @filename = filename + @error_id = error_id FormResponse.new(success: valid?, errors:, serialize_error_details_only: true) end @@ -18,11 +19,13 @@ def submit(filename:) private def validate_filename_extension - return if File.extname(filename.to_s) == '.js' + return if error_id || File.extname(filename.to_s) == '.js' errors.add(:filename, :invalid_extension, message: t('errors.general')) end def validate_filename_host + return if error_id + begin return if URI(filename.to_s).host == IdentityConfig.store.domain_name rescue URI::InvalidURIError; end diff --git a/app/javascript/packages/address-search/CHANGELOG.md b/app/javascript/packages/address-search/CHANGELOG.md index ee448b8734e..805793d16b8 100644 --- a/app/javascript/packages/address-search/CHANGELOG.md +++ b/app/javascript/packages/address-search/CHANGELOG.md @@ -1,5 +1,9 @@ # `Change Log` +## v3.4.0 (2024-11-07) + +- Added new optional resultsSectionHeading component prop to FullAddressSearch + ## v3.3.0 (2024-11-05) - Remove obsolete `is_pilot` field from PostOffice and FormattedLocation types diff --git a/app/javascript/packages/address-search/components/full-address-search.spec.tsx b/app/javascript/packages/address-search/components/full-address-search.spec.tsx index a3a7ff018de..0ec986da2c6 100644 --- a/app/javascript/packages/address-search/components/full-address-search.spec.tsx +++ b/app/javascript/packages/address-search/components/full-address-search.spec.tsx @@ -320,4 +320,57 @@ describe('FullAddressSearch', () => { await expect(handleLocationsFound).to.eventually.be.called(); }); }); + + context('Address Search with Results Section Heading', () => { + let server: SetupServer; + before(() => { + server = setupServer( + http.post(locationsURL, () => HttpResponse.json([{ name: 'Baltimore' }])), + ); + server.listen(); + }); + + after(() => { + server.close(); + }); + + it('renders the results section heading when passed in', async () => { + const handleLocationsFound = sandbox.stub(); + const onSelect = sinon.stub(); + const resultsSectionHeadingText = 'Mock Heading'; + const { findByText, getByLabelText, getByText } = render( + new Map() }}> + undefined} + handleLocationSelect={onSelect} + disabled={false} + resultsSectionHeading={() =>

{resultsSectionHeadingText}

} + /> +
, + ); + + await userEvent.type( + getByLabelText('in_person_proofing.body.location.po_search.address_label'), + '200 main', + ); + await userEvent.type( + getByLabelText('in_person_proofing.body.location.po_search.city_label'), + 'Endeavor', + ); + await userEvent.selectOptions( + getByLabelText('in_person_proofing.body.location.po_search.state_label'), + 'DE', + ); + await userEvent.type( + getByLabelText('in_person_proofing.body.location.po_search.zipcode_label'), + '17201', + ); + await userEvent.click(getByText('in_person_proofing.body.location.po_search.search_button')); + + expect(await findByText(resultsSectionHeadingText)).to.exist(); + }); + }); }); diff --git a/app/javascript/packages/address-search/components/full-address-search.tsx b/app/javascript/packages/address-search/components/full-address-search.tsx index ab2dc67b402..0a6f07bd4f5 100644 --- a/app/javascript/packages/address-search/components/full-address-search.tsx +++ b/app/javascript/packages/address-search/components/full-address-search.tsx @@ -15,6 +15,7 @@ function FullAddressSearch({ registerField, resultsHeaderComponent, usStatesTerritories, + resultsSectionHeading, }: FullAddressSearchProps) { const [apiError, setApiError] = useState(null); const [foundAddress, setFoundAddress] = useState(null); @@ -61,6 +62,7 @@ function FullAddressSearch({ address={foundAddress.address || ''} noInPersonLocationsDisplay={noInPersonLocationsDisplay} resultsHeaderComponent={resultsHeaderComponent} + resultsSectionHeading={resultsSectionHeading} /> )} diff --git a/app/javascript/packages/address-search/components/in-person-locations.spec.tsx b/app/javascript/packages/address-search/components/in-person-locations.spec.tsx index 195206b9f81..7dfdbbbd828 100644 --- a/app/javascript/packages/address-search/components/in-person-locations.spec.tsx +++ b/app/javascript/packages/address-search/components/in-person-locations.spec.tsx @@ -88,6 +88,23 @@ describe('InPersonLocations', () => { ).to.not.exist(); }); + it('renders a header at the top of the results', () => { + const headingText = 'mock heading'; + const testId = 'mock-heading'; + const { getByTestId } = render( +

{headingText}

} + />, + ); + + expect(getByTestId(testId)).to.exist(); + expect(getByTestId(testId).textContent).to.equal(headingText); + }); + context('when no locations are found', () => { it('renders the passed in noLocations component w/ address', () => { const onClick = sinon.stub(); diff --git a/app/javascript/packages/address-search/components/in-person-locations.tsx b/app/javascript/packages/address-search/components/in-person-locations.tsx index af2f807cbe7..36b65f63922 100644 --- a/app/javascript/packages/address-search/components/in-person-locations.tsx +++ b/app/javascript/packages/address-search/components/in-person-locations.tsx @@ -20,6 +20,7 @@ function InPersonLocations({ address, noInPersonLocationsDisplay: NoInPersonLocationsDisplay, resultsHeaderComponent: HeaderComponent, + resultsSectionHeading: ResultsSectionHeading, }: InPersonLocationsProps) { if (locations?.length === 0) { return ; @@ -27,6 +28,7 @@ function InPersonLocations({ return ( <> + {ResultsSectionHeading && }

{t('in_person_proofing.body.location.po_search.results_description', { address, diff --git a/app/javascript/packages/address-search/package.json b/app/javascript/packages/address-search/package.json index a02eebaee2d..1812b0240a0 100644 --- a/app/javascript/packages/address-search/package.json +++ b/app/javascript/packages/address-search/package.json @@ -1,6 +1,6 @@ { "name": "@18f/identity-address-search", - "version": "3.3.0", + "version": "3.4.0", "type": "module", "private": false, "files": [ @@ -34,4 +34,4 @@ "directory": "app/javascript/packages/address-search" }, "sideEffects": false -} +} \ No newline at end of file diff --git a/app/javascript/packages/address-search/types.d.ts b/app/javascript/packages/address-search/types.d.ts index d75c0b971be..b2b4f89d8cd 100644 --- a/app/javascript/packages/address-search/types.d.ts +++ b/app/javascript/packages/address-search/types.d.ts @@ -69,6 +69,7 @@ interface InPersonLocationsProps { noInPersonLocationsDisplay: ComponentType<{ address: string }>; onSelect; resultsHeaderComponent?: ComponentType; + resultsSectionHeading?: ComponentType; } interface LocationCollectionItemProps { @@ -98,4 +99,5 @@ interface FullAddressSearchProps { registerField: RegisterFieldCallback; resultsHeaderComponent?: ComponentType; usStatesTerritories: string[][]; + resultsSectionHeading?: ComponentType; } diff --git a/app/javascript/packages/analytics/README.md b/app/javascript/packages/analytics/README.md index 752f2f17633..e33fb2b669a 100644 --- a/app/javascript/packages/analytics/README.md +++ b/app/javascript/packages/analytics/README.md @@ -8,6 +8,9 @@ Utilities and custom elements for logging events and errors in the application. Track an event or error from your code using exported function members. +Since JavaScript may be bundled and minified in production environments, including an `errorId` is +required to uniquely identify the source of the error. + ```ts import { trackEvent, trackError } from '@18f/identity-analytics'; @@ -18,7 +21,7 @@ button.addEventListener('click', () => { try { doSomethingRisky(); } catch (error) { - trackError(error); + trackError(error, { errorId: 'exampleId' }); } ``` diff --git a/app/javascript/packages/analytics/index.spec.ts b/app/javascript/packages/analytics/index.spec.ts index 6a3a374c125..5253d73ed68 100644 --- a/app/javascript/packages/analytics/index.spec.ts +++ b/app/javascript/packages/analytics/index.spec.ts @@ -93,7 +93,7 @@ describe('trackError', () => { }); it('tracks event', async () => { - trackError(new Error('Oops!')); + trackError(new Error('Oops!'), { errorId: 'exampleId' }); expect(global.navigator.sendBeacon).to.have.been.calledOnce(); @@ -101,12 +101,13 @@ describe('trackError', () => { expect(actualEndpoint).to.eql(endpoint); const { event, payload } = JSON.parse(await data.text()); - const { name, message, stack } = payload; + const { name, message, stack, error_id: errorId } = payload; expect(event).to.equal('Frontend Error'); expect(name).to.equal('Error'); expect(message).to.equal('Oops!'); expect(stack).to.be.a('string'); + expect(errorId).to.equal('exampleId'); }); context('with event parameter', () => { diff --git a/app/javascript/packages/analytics/index.ts b/app/javascript/packages/analytics/index.ts index 22fa2d91308..45e546195a4 100644 --- a/app/javascript/packages/analytics/index.ts +++ b/app/javascript/packages/analytics/index.ts @@ -2,6 +2,11 @@ import { getConfigValue } from '@18f/identity-config'; export { default as isTrackableErrorEvent } from './is-trackable-error-event'; +/** + * Metadata used to identify the source of an error. + */ +type ErrorMetadata = { errorId?: never; filename: string } | { errorId: string; filename?: never }; + /** * Logs an event. * @@ -24,8 +29,8 @@ export function trackEvent(event: string, payload?: object) { * Logs an error. * * @param error Error object. - * @param event Error event, if error is caught using an `error` event handler. Including this can - * add additional resolution to the logged error, notably the filename where the error occurred. + * @param metadata Metadata used to identify the source of an error, including either the filename + * from an ErrorEvent object, or a unique identifier. */ -export const trackError = ({ name, message, stack }: Error, event?: ErrorEvent) => - trackEvent('Frontend Error', { name, message, stack, filename: event?.filename }); +export const trackError = ({ name, message, stack }: Error, { filename, errorId }: ErrorMetadata) => + trackEvent('Frontend Error', { name, message, stack, filename, error_id: errorId }); diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts index f0bdece5bb7..657a7548e4a 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts @@ -1,17 +1,20 @@ import quibble from 'quibble'; import type { SinonStub } from 'sinon'; -import userEvent from '@testing-library/user-event'; +import baseUserEvent from '@testing-library/user-event'; import { screen, waitFor, fireEvent } from '@testing-library/dom'; import { useSandbox, useDefineProperty } from '@18f/identity-test-helpers'; import '@18f/identity-spinner-button/spinner-button-element'; describe('CaptchaSubmitButtonElement', () => { - const sandbox = useSandbox(); + let FAILED_LOAD_DELAY_MS: number; + const sandbox = useSandbox({ useFakeTimers: true }); + const { clock } = sandbox; + const userEvent = baseUserEvent.setup({ advanceTimers: clock.tick }); const trackError = sandbox.stub(); before(async () => { quibble('@18f/identity-analytics', { trackError }); - await import('./captcha-submit-button-element'); + ({ FAILED_LOAD_DELAY_MS } = await import('./captcha-submit-button-element')); }); afterEach(() => { @@ -117,7 +120,6 @@ describe('CaptchaSubmitButtonElement', () => { await userEvent.click(button); await waitFor(() => expect((form.submit as SinonStub).called).to.be.true()); - expect(grecaptcha.ready).to.have.been.called(); expect(grecaptcha.execute).to.have.been.calledWith(RECAPTCHA_SITE_KEY, { action: RECAPTCHA_ACTION_NAME, }); @@ -126,6 +128,57 @@ describe('CaptchaSubmitButtonElement', () => { }); }); + context('with recaptcha not loaded by time of submission', () => { + beforeEach(() => { + delete (global as any).grecaptcha; + }); + + it('enqueues the challenge callback to be run once recaptcha loads', async () => { + const button = screen.getByRole('button', { name: 'Submit' }); + const form = document.querySelector('form')!; + sandbox.stub(form, 'submit'); + + await userEvent.click(button); + + expect(form.submit).not.to.have.been.called(); + /* eslint-disable no-underscore-dangle */ + expect((globalThis as any).___grecaptcha_cfg).to.have.keys('fns'); + expect((globalThis as any).___grecaptcha_cfg.fns) + .to.be.an('array') + .with.lengthOf.greaterThan(0); + (globalThis as any).___grecaptcha_cfg.fns.forEach((callback) => callback()); + /* eslint-enable no-underscore-dangle */ + + await expect(form.submit).to.eventually.be.called(); + }); + }); + + context('with only recaptcha loader script loaded by time of submission', () => { + // The loader script will define the `grecaptcha` global and `ready` function, but it will + // not define `execute`. + beforeEach(() => { + delete (global as any).grecaptcha.execute; + delete (global as any).grecaptcha.enterprise.execute; + }); + + it('enqueues the challenge callback to be run once recaptcha loads', async () => { + const button = screen.getByRole('button', { name: 'Submit' }); + const form = document.querySelector('form')!; + sandbox.stub(form, 'submit'); + + await userEvent.click(button); + + expect(grecaptcha.ready).to.have.been.called(); + + // Simulate reCAPTCHA full script loaded + (global as any).grecaptcha.execute = sandbox.stub().resolves(RECAPTCHA_TOKEN_VALUE); + const callback = (grecaptcha.ready as SinonStub).getCall(0).args[0]; + callback(); + + await expect(form.submit).to.eventually.be.called(); + }); + }); + context('with recaptcha enterprise', () => { beforeEach(() => { const element = document.querySelector('lg-captcha-submit-button')!; @@ -141,7 +194,6 @@ describe('CaptchaSubmitButtonElement', () => { await userEvent.click(button); await waitFor(() => expect((form.submit as SinonStub).called).to.be.true()); - expect(grecaptcha.enterprise.ready).to.have.been.called(); expect(grecaptcha.enterprise.execute).to.have.been.calledWith(RECAPTCHA_SITE_KEY, { action: RECAPTCHA_ACTION_NAME, }); @@ -156,19 +208,18 @@ describe('CaptchaSubmitButtonElement', () => { delete (global as any).grecaptcha; }); - it('does not prevent default form submission', async () => { + it('submits the form if recaptcha is still not loaded after reasonable delay', async () => { const button = screen.getByRole('button', { name: 'Submit' }); const form = document.querySelector('form')!; - - let didSubmit = false; - form.addEventListener('submit', (event) => { - expect(event.defaultPrevented).to.equal(false); - event.preventDefault(); - didSubmit = true; - }); + sandbox.stub(form, 'submit'); await userEvent.click(button); - await waitFor(() => expect(didSubmit).to.be.true()); + + expect(form.submit).not.to.have.been.called(); + clock.tick(FAILED_LOAD_DELAY_MS - 1); + expect(form.submit).not.to.have.been.called(); + clock.tick(1); + expect(form.submit).to.have.been.called(); }); }); @@ -188,6 +239,9 @@ describe('CaptchaSubmitButtonElement', () => { await userEvent.click(button); await expect(form.submit).to.eventually.be.called(); + expect(Object.fromEntries(new window.FormData(form))).to.deep.equal({ + recaptcha_token: '', + }); }); it('tracks error', async () => { diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts index 45f6e33afeb..5c97855f1cd 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts @@ -1,5 +1,11 @@ import { trackError } from '@18f/identity-analytics'; +/** + * Maximum time (in milliseconds) to wait on reCAPTCHA to finish loading once a form is submitted + * before considering reCAPTCHA as having failed to load. + */ +export const FAILED_LOAD_DELAY_MS = 5_000; + class CaptchaSubmitButtonElement extends HTMLElement { form: HTMLFormElement | null; @@ -46,23 +52,23 @@ class CaptchaSubmitButtonElement extends HTMLElement { } invokeChallenge() { - this.recaptchaClient!.ready(async () => { + this.#onReady(async () => { const { recaptchaSiteKey: siteKey, recaptchaAction: action } = this; - let token; + let token: string | undefined; try { token = await this.recaptchaClient!.execute(siteKey!, { action }); + this.tokenInput.value = token; } catch (error) { - trackError(error); + trackError(error, { errorId: 'recaptchaExecute' }); } - this.tokenInput.value = token; this.submit(); }); } shouldInvokeChallenge(): boolean { - return !!(this.recaptchaSiteKey && this.recaptchaClient); + return !!this.recaptchaSiteKey; } handleFormSubmit = (event: SubmitEvent) => { @@ -71,6 +77,25 @@ class CaptchaSubmitButtonElement extends HTMLElement { this.invokeChallenge(); } }; + + #onReady(callback: Parameters[0]) { + if (this.recaptchaClient) { + this.recaptchaClient.ready(callback); + } else { + // If reCAPTCHA hasn't finished loading by the time the form is submitted, we can enqueue the + // callback to be invoked once loaded by appending a callback to the ___grecaptcha_cfg global. + // + // See: https://developers.google.com/recaptcha/docs/loading + + const failedLoadTimeoutId = setTimeout(() => this.submit(), FAILED_LOAD_DELAY_MS); + const clearFailedLoadTimeout = () => clearTimeout(failedLoadTimeoutId); + + /* eslint-disable no-underscore-dangle */ + globalThis.___grecaptcha_cfg ??= { fns: [] }; + globalThis.___grecaptcha_cfg.fns.push(clearFailedLoadTimeout, callback); + /* eslint-enable no-underscore-dangle */ + } + } } declare global { diff --git a/app/javascript/packages/lite-webpack-dev-server/README.md b/app/javascript/packages/lite-webpack-dev-server/README.md new file mode 100644 index 00000000000..d942f744663 --- /dev/null +++ b/app/javascript/packages/lite-webpack-dev-server/README.md @@ -0,0 +1,46 @@ +# `@18f/identity-lite-webpack-dev-server` + +Minimal, zero-dependency alternative to Webpack's default development web server. + +**What does it do the same as `webpack-dev-server`?** + +- Serves static assets from the built output directory +- Pauses page loads during compilation to guarantee that a page loads with the latest JavaScript + +**What doesn't it do that `webpack-dev-server` does?** + +Most everything else! Notably, it does not: + +- Automatically reload the page when compilation finishes +- Handle anything other than JavaScript + +## Usage + +If migrating from `webpack-dev-server`: + +- Remove your `devServer` configuration from `webpack.config.js` + +Add an instance of `LiteWebpackDevServerPlugin` to your Webpack `plugins` array. The example below +shows how you might conditionally include the plugin in local development only, to avoid the server +being run in production environments. + +```ts +// webpack.config.js + +const { DEV_SERVER_PORT } = process.env; + +export default { + // ... + plugins: [ + // ... + DEV_SERVER_PORT && + new LiteWebpackDevServerPlugin({ publicPath: './public', port: Number(DEV_SERVER_PORT) }), + ] +}; +``` + +Supported options: + +- `publicPath` (`string`): Relative path to the root of the static file server +- `port` (`number`): Port on which the static file server should listen +- `headers` (`object`): Additional headers to include in every response diff --git a/app/javascript/packages/lite-webpack-dev-server/package.json b/app/javascript/packages/lite-webpack-dev-server/package.json new file mode 100644 index 00000000000..c769131bdac --- /dev/null +++ b/app/javascript/packages/lite-webpack-dev-server/package.json @@ -0,0 +1,7 @@ +{ + "name": "@18f/identity-lite-webpack-dev-server", + "version": "1.0.0", + "private": true, + "sideEffects": false, + "main": "./webpack-plugin.js" +} diff --git a/app/javascript/packages/lite-webpack-dev-server/webpack-plugin.js b/app/javascript/packages/lite-webpack-dev-server/webpack-plugin.js new file mode 100644 index 00000000000..8932db838a6 --- /dev/null +++ b/app/javascript/packages/lite-webpack-dev-server/webpack-plugin.js @@ -0,0 +1,85 @@ +const http = require('node:http'); +const { join } = require('node:path'); +const { createReadStream } = require('node:fs'); + +/** + * @typedef PluginOptions + * @prop {string} [publicPath] + * @prop {number} [port] + * @prop {Record} [headers] + */ + +/** + * Webpack plugin name. + * + * @type {string} + */ +const PLUGIN = 'LiteWebpackDevServerPlugin'; + +class LiteWebpackDevServerPlugin { + /** + * @type {string} + */ + publicPath; + + /** + * @type {number} + */ + port; + + /** + * @type {Record} + */ + headers; + + /** + * @param {PluginOptions} options + */ + constructor(options) { + Object.assign(this, { + publicPath: '.', + port: 3035, + headers: { + 'content-type': 'text/javascript', + ...options.headers, + }, + ...options, + }); + } + + /** + * @param {import('webpack').Compiler} compiler + */ + apply(compiler) { + /** @type {Promise} */ + let build; + + /** @type {() => void} */ + let onCompileFinished; + + const server = http.createServer(async (request, response) => { + for (const [key, value] of Object.entries(this.headers)) { + response.setHeader(key, value); + } + + await build; + const url = new URL(request.url ?? '', 'file:///'); + const filePath = join(process.cwd(), this.publicPath, url.pathname); + createReadStream(filePath).pipe(response); + }); + + server.listen(this.port); + + compiler.hooks.beforeCompile.tap(PLUGIN, () => { + build = new Promise((resolve) => { + onCompileFinished = resolve; + }); + }); + + compiler.hooks.afterCompile.tap(PLUGIN, () => onCompileFinished()); + + compiler.hooks.shutdown.tap(PLUGIN, () => server.close()); + } +} + +module.exports = LiteWebpackDevServerPlugin; diff --git a/app/javascript/packages/spinner-button/spinner-button-element.spec.ts b/app/javascript/packages/spinner-button/spinner-button-element.spec.ts index 816b81d69f1..8a45fad0911 100644 --- a/app/javascript/packages/spinner-button/spinner-button-element.spec.ts +++ b/app/javascript/packages/spinner-button/spinner-button-element.spec.ts @@ -75,18 +75,32 @@ describe('SpinnerButtonElement', () => { context('inside form', () => { it('disables button without preventing form handlers', async () => { const wrapper = createWrapper({ inForm: true }); - let didSubmit = false; - wrapper.form!.addEventListener('submit', (event) => { - didSubmit = true; - event.preventDefault(); - }); + const onSubmit = sandbox.stub().callsFake((event: SubmitEvent) => event.preventDefault()); + wrapper.form!.addEventListener('submit', onSubmit); const button = screen.getByRole('button', { name: 'Click Me' }); await userEvent.type(button, '{Enter}'); clock.tick(0); - expect(didSubmit).to.be.true(); - expect(button.hasAttribute('disabled')).to.be.true(); + expect(onSubmit).to.have.been.calledOnce(); + expect(button.ariaDisabled).to.equal('true'); + }); + + it('prevents duplicate submission', async () => { + const wrapper = createWrapper({ inForm: true }); + const onSubmit = sandbox.stub().callsFake((event: SubmitEvent) => event.preventDefault()); + wrapper.form!.addEventListener('submit', onSubmit); + const button = screen.getByRole('button', { name: 'Click Me' }); + + await userEvent.type(button, '{Enter}'); + clock.tick(0); + + expect(onSubmit).to.have.been.calledOnce(); + + await userEvent.type(button, '{Enter}'); + clock.tick(0); + + expect(onSubmit).to.have.been.calledOnce(); }); it('unbinds events when disconnected', () => { @@ -104,18 +118,32 @@ describe('SpinnerButtonElement', () => { context('with form inside (button_to)', () => { it('disables button without preventing form handlers', async () => { const wrapper = createWrapper({ isButtonTo: true }); - let didSubmit = false; - wrapper.form!.addEventListener('submit', (event) => { - didSubmit = true; - event.preventDefault(); - }); + const onSubmit = sandbox.stub().callsFake((event: SubmitEvent) => event.preventDefault()); + wrapper.form!.addEventListener('submit', onSubmit); const button = screen.getByRole('button', { name: 'Click Me' }); await userEvent.type(button, '{Enter}'); clock.tick(0); - expect(didSubmit).to.be.true(); - expect(button.hasAttribute('disabled')).to.be.true(); + expect(onSubmit).to.have.been.calledOnce(); + expect(button.ariaDisabled).to.equal('true'); + }); + + it('prevents duplicate submission', async () => { + const wrapper = createWrapper({ isButtonTo: true }); + const onSubmit = sandbox.stub().callsFake((event: SubmitEvent) => event.preventDefault()); + wrapper.form!.addEventListener('submit', onSubmit); + const button = screen.getByRole('button', { name: 'Click Me' }); + + await userEvent.type(button, '{Enter}'); + clock.tick(0); + + expect(onSubmit.callCount).equal(1); + + await userEvent.type(button, '{Enter}'); + clock.tick(0); + + expect(onSubmit.callCount).equal(1); }); }); diff --git a/app/javascript/packages/spinner-button/spinner-button-element.ts b/app/javascript/packages/spinner-button/spinner-button-element.ts index b91b052e938..3b9c06486be 100644 --- a/app/javascript/packages/spinner-button/spinner-button-element.ts +++ b/app/javascript/packages/spinner-button/spinner-button-element.ts @@ -13,6 +13,7 @@ export class SpinnerButtonElement extends HTMLElement { connectedCallback() { this.form = this.querySelector('form') || this.closest('form'); + this.button.addEventListener('click', this.#preventDefaultIfSpinning); this.addEventListener('spinner.start', () => this.toggleSpinner(true)); this.addEventListener('spinner.stop', () => this.toggleSpinner(false)); @@ -40,6 +41,10 @@ export class SpinnerButtonElement extends HTMLElement { return this.querySelector('.usa-button')!; } + get isSpinning(): boolean { + return this.classList.contains('spinner-button--spinner-active'); + } + get actionMessage(): HTMLElement { return this.querySelector('.spinner-button__action-message')!; } @@ -62,9 +67,9 @@ export class SpinnerButtonElement extends HTMLElement { this.button.classList.toggle('usa-button--active', isVisible); if (isVisible) { - this.button.setAttribute('disabled', ''); + this.button.setAttribute('aria-disabled', 'true'); } else { - this.button.removeAttribute('disabled'); + this.button.removeAttribute('aria-disabled'); } if (this.actionMessage) { @@ -73,16 +78,19 @@ export class SpinnerButtonElement extends HTMLElement { window.clearTimeout(this.#longWaitTimeout); if (isVisible && Number.isFinite(this.longWaitDurationMs)) { - this.#longWaitTimeout = window.setTimeout( - () => this.handleLongWait(), - this.longWaitDurationMs, - ); + this.#longWaitTimeout = window.setTimeout(this.#handleLongWait, this.longWaitDurationMs); } } - handleLongWait() { + #handleLongWait = () => { this.actionMessage?.classList.remove('usa-sr-only'); - } + }; + + #preventDefaultIfSpinning = (event: MouseEvent) => { + if (this.isSpinning) { + event.preventDefault(); + } + }; } declare global { diff --git a/app/javascript/packages/submit-button/submit-button-element.spec.ts b/app/javascript/packages/submit-button/submit-button-element.spec.ts index 54e3dc11f4c..49416c704cb 100644 --- a/app/javascript/packages/submit-button/submit-button-element.spec.ts +++ b/app/javascript/packages/submit-button/submit-button-element.spec.ts @@ -1,3 +1,4 @@ +import { mock } from 'node:test'; import { screen } from '@testing-library/dom'; import userEvent from '@testing-library/user-event'; import './submit-button-element'; @@ -24,10 +25,29 @@ describe('SubmitButtonElement', () => { await userEvent.click(button); - expect(button.disabled).to.be.true(); + expect(button.ariaDisabled).to.equal('true'); expect(button.classList.contains('usa-button--active')).to.be.true(); }); + it('prevents duplicate submissions', async () => { + document.body.innerHTML = ` +
+ + + +
`; + + const button = screen.getByRole('button') as HTMLButtonElement; + const form = button.closest('form') as HTMLFormElement; + const onSubmit = mock.fn((event: SubmitEvent) => event.preventDefault()); + form.addEventListener('submit', onSubmit); + + await userEvent.click(button); + expect(onSubmit.mock.callCount()).to.equal(1); + await userEvent.click(button); + expect(onSubmit.mock.callCount()).to.equal(1); + }); + it('does not activate if form validation prevents submission', async () => { document.body.innerHTML = `
diff --git a/app/javascript/packages/submit-button/submit-button-element.ts b/app/javascript/packages/submit-button/submit-button-element.ts index 0a685687267..badac160eb1 100644 --- a/app/javascript/packages/submit-button/submit-button-element.ts +++ b/app/javascript/packages/submit-button/submit-button-element.ts @@ -1,6 +1,7 @@ class SubmitButtonElement extends HTMLElement { connectedCallback() { - this.form?.addEventListener('submit', () => this.activate()); + this.button.addEventListener('click', this.#preventDefaultIfSubmitting); + this.form?.addEventListener('submit', this.#activate); } get form(): HTMLFormElement | null { @@ -11,10 +12,20 @@ class SubmitButtonElement extends HTMLElement { return this.querySelector('button')!; } - activate() { - this.button.classList.add('usa-button--active'); - this.button.disabled = true; + get isSubmitting(): boolean { + return this.button.getAttribute('aria-disabled') === 'true'; } + + #activate = () => { + this.button.classList.add('usa-button--active'); + this.button.setAttribute('aria-disabled', 'true'); + }; + + #preventDefaultIfSubmitting = (event: MouseEvent) => { + if (this.isSubmitting) { + event.preventDefault(); + } + }; } declare global { diff --git a/app/javascript/packages/webauthn/webauthn-verify-button-element.spec.ts b/app/javascript/packages/webauthn/webauthn-verify-button-element.spec.ts index 5101427babd..04a4e83d6e0 100644 --- a/app/javascript/packages/webauthn/webauthn-verify-button-element.spec.ts +++ b/app/javascript/packages/webauthn/webauthn-verify-button-element.spec.ts @@ -50,15 +50,16 @@ describe('WebauthnVerifyButtonElement', () => { Object.assign(element.dataset, { credentials: '[]', userChallenge: '[]' }, data); const form = document.querySelector('form')!; sinon.stub(form, 'submit'); - return { form, element }; + return element; } - it('assigns button type to avoid default form submission', () => { + it('prevents default form submission', async () => { createElement(); + const button = screen.getByRole('button'); + await userEvent.click(button); - const button = screen.getByRole('button') as HTMLButtonElement; - - expect(button.type).to.equal('button'); + // This test relies on the fact that JSDOM will throw an error about not implementing form + // submission if the form submission was left unhandled. }); it('shows spinner on click', async () => { @@ -94,6 +95,10 @@ describe('WebauthnVerifyButtonElement', () => { await userEvent.click(button); expect(verifyWebauthnDevice).to.have.been.calledOnce(); + + // This test also implicitly verifies that the form would not submit on a second button click, + // since JSDOM will throw an error about not implementing form submission if the form submission + // was left unhandled. }); it('submits with error name as input on thrown expected error', async () => { diff --git a/app/javascript/packages/webauthn/webauthn-verify-button-element.ts b/app/javascript/packages/webauthn/webauthn-verify-button-element.ts index d937c879f49..9acca615c1a 100644 --- a/app/javascript/packages/webauthn/webauthn-verify-button-element.ts +++ b/app/javascript/packages/webauthn/webauthn-verify-button-element.ts @@ -14,8 +14,11 @@ class WebauthnVerifyButtonElement extends HTMLElement { dataset: WebauthnVerifyButtonDataset; connectedCallback() { - this.setButtonAttributes(); - this.bindEvents(); + this.form.addEventListener('submit', this.#handleSubmit, { once: true }); + } + + get form(): HTMLFormElement { + return this.closest('form')!; } get button(): HTMLButtonElement { @@ -38,17 +41,8 @@ class WebauthnVerifyButtonElement extends HTMLElement { return this.dataset.userChallenge; } - setButtonAttributes() { - this.button.type = 'button'; - } - - bindEvents() { - this.button.addEventListener('click', () => this.verify()); - } - async verify() { this.spinner.hidden = false; - this.submitButton.activate(); const { userChallenge, credentials } = this; @@ -60,7 +54,7 @@ class WebauthnVerifyButtonElement extends HTMLElement { this.setInputValue('signature', result.signature); } catch (error) { if (!isExpectedWebauthnError(error, { isVerifying: true })) { - trackError(error); + trackError(error, { errorId: 'webauthnVerify' }); } if (isUserVerificationScreenLockError(error)) { @@ -70,13 +64,18 @@ class WebauthnVerifyButtonElement extends HTMLElement { this.setInputValue('webauthn_error', error.name); } - this.closest('form')?.submit(); + this.form.submit(); } setInputValue(name: string, value: string) { const input = this.querySelector(`[name="${name}"]`)!; input.value = value; } + + #handleSubmit = (event: SubmitEvent) => { + event.preventDefault(); + this.verify(); + }; } declare global { diff --git a/app/javascript/packs/track-errors.ts b/app/javascript/packs/track-errors.ts index fb4582900ad..c0686e2ec0c 100644 --- a/app/javascript/packs/track-errors.ts +++ b/app/javascript/packs/track-errors.ts @@ -9,6 +9,6 @@ declare let window: WindowWithInitialErrors; const { _e: initialErrors } = window; const handleErrorEvent = (event: ErrorEvent) => - isTrackableErrorEvent(event) && trackError(event.error, event); + isTrackableErrorEvent(event) && trackError(event.error, { filename: event.filename }); initialErrors.forEach(handleErrorEvent); window.addEventListener('error', handleErrorEvent); diff --git a/app/javascript/packs/webauthn-setup.ts b/app/javascript/packs/webauthn-setup.ts index b0c2d0c9eff..2e28f36d8b6 100644 --- a/app/javascript/packs/webauthn-setup.ts +++ b/app/javascript/packs/webauthn-setup.ts @@ -75,7 +75,7 @@ function webauthn() { }) .catch((error: Error) => { if (!isExpectedWebauthnError(error)) { - trackError(error); + trackError(error, { errorId: 'webauthnSetup' }); } reloadWithError(error.name, { force: true }); diff --git a/app/jobs/data_warehouse/table_summary_stats_export_job.rb b/app/jobs/data_warehouse/table_summary_stats_export_job.rb index 3f74bb85198..7fef9cabee5 100644 --- a/app/jobs/data_warehouse/table_summary_stats_export_job.rb +++ b/app/jobs/data_warehouse/table_summary_stats_export_job.rb @@ -29,7 +29,9 @@ def max_ids_and_counts(timestamp) end def table_has_id_column?(table) - ActiveRecord::Base.connection.columns(table).map(&:name).include?('id') + ActiveRecord::Base.connection.columns(table).any? do |column| + column.name == 'id' && column.type == :integer + end end def fetch_max_id_and_count(table, timestamp) diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index f9a49854a66..240bb5b40ad 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -1566,13 +1566,15 @@ def idv_doc_auth_redo_ssn_submitted( # @param [String] created_at The created timestamp received from Socure # @param [String] customer_user_id The customerUserId received from Socure + # @param [String] docv_transaction_token The docvTransactionToken received from Socure # @param [String] event_type The eventType received from Socure # @param [String] reference_id The referenceId received from Socure - # @param [String] user_id The customerUserId, repackaged as user_id + # @param [String] user_id The uuid of the user using Socure def idv_doc_auth_socure_webhook_received( created_at:, customer_user_id:, event_type:, + docv_transaction_token:, reference_id:, user_id:, **extra @@ -1581,6 +1583,7 @@ def idv_doc_auth_socure_webhook_received( :idv_doc_auth_socure_webhook_received, created_at:, customer_user_id:, + docv_transaction_token:, event_type:, reference_id:, user_id:, diff --git a/app/services/doc_auth/socure/requests/document_request.rb b/app/services/doc_auth/socure/requests/document_request.rb index 4a6f5b0920f..5121e72c577 100644 --- a/app/services/doc_auth/socure/requests/document_request.rb +++ b/app/services/doc_auth/socure/requests/document_request.rb @@ -4,15 +4,13 @@ module DocAuth module Socure module Requests class DocumentRequest < DocAuth::Socure::Request - attr_reader :document_type, :redirect_url, :document_capture_session_uuid, :language + attr_reader :document_type, :redirect_url, :language def initialize( - document_capture_session_uuid:, redirect_url:, language:, document_type: 'license' ) - @document_capture_session_uuid = document_capture_session_uuid @redirect_url = redirect_url @document_type = document_type @language = language @@ -39,7 +37,6 @@ def body redirect: redirect, language: lang(language), }, - customerUserId: document_capture_session_uuid, }.to_json end diff --git a/app/services/frontend_error_logger.rb b/app/services/frontend_error_logger.rb index 3037954aa51..226dd49aff5 100644 --- a/app/services/frontend_error_logger.rb +++ b/app/services/frontend_error_logger.rb @@ -3,13 +3,13 @@ class FrontendErrorLogger class FrontendError < StandardError; end - def self.track_error(name:, message:, stack:, filename:) - return unless FrontendErrorForm.new.submit(filename:).success? + def self.track_error(name:, message:, stack:, filename: nil, error_id: nil) + return unless FrontendErrorForm.new.submit(filename:, error_id:).success? NewRelic::Agent.notice_error( FrontendError.new, expected: true, - custom_params: { frontend_error: { name:, message:, stack:, filename: } }, + custom_params: { frontend_error: { name:, message:, stack:, filename:, error_id: } }, ) end end diff --git a/app/views/sign_up/select_email/show.html.erb b/app/views/sign_up/select_email/show.html.erb index 00a5134e774..ed505209855 100644 --- a/app/views/sign_up/select_email/show.html.erb +++ b/app/views/sign_up/select_email/show.html.erb @@ -4,7 +4,7 @@ <% c.with_header(id: 'select-email-heading') { t('titles.select_email') } %>

- <%= I18n.t('help_text.select_preferred_email_html', sp: @sp_name) %> + <%= t('help_text.select_preferred_email_html', sp: @sp_name) %>

<%= simple_form_for(@select_email_form, url: sign_up_select_email_path) do |f| %> diff --git a/config/initializers/ab_tests.rb b/config/initializers/ab_tests.rb index 969842c2a51..952a7071214 100644 --- a/config/initializers/ab_tests.rb +++ b/config/initializers/ab_tests.rb @@ -91,9 +91,9 @@ def self.all RECOMMEND_WEBAUTHN_PLATFORM_FOR_SMS_USER = AbTest.new( experiment_name: 'Recommend Face or Touch Unlock for SMS users', should_log: [ - 'Multi-Factor Authentication', - 'User Registration: MFA Setup Complete', - 'User Registration: 2FA Setup', + :webauthn_platform_recommended_visited, + :webauthn_platform_recommended_submitted, + :webauthn_setup_submitted, ].to_set, buckets: { recommend_for_account_creation: diff --git a/config/locales/en.yml b/config/locales/en.yml index c0a8740407b..e6c366a31ce 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1208,7 +1208,7 @@ in_person_proofing.body.barcode.retail_hours_closed: Closed in_person_proofing.body.barcode.return_to_partner_link: Return to %{sp_name} in_person_proofing.body.barcode.what_to_expect: What to expect at the Post Office in_person_proofing.body.cta.button: Try in person -in_person_proofing.body.cta.prompt_detail: You may be able to verify your identity at a participating Post Office near you. +in_person_proofing.body.cta.prompt_detail: Most people who are unable to complete this step online are successful in verifying their identity at a participating Post Office. No appointment needed. Locations are available nationwide. in_person_proofing.body.expect.heading: What to expect after your visit in_person_proofing.body.expect.info: We’ll send you an email to let you know if your identity verification was successful or unsuccessful within 24 hours of your visit to the Post Office. in_person_proofing.body.location.distance.one: '%{count} mile away' diff --git a/config/locales/es.yml b/config/locales/es.yml index ac4c70204ad..c8dd2efd5f0 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1219,7 +1219,7 @@ in_person_proofing.body.barcode.retail_hours_closed: Cerrado in_person_proofing.body.barcode.return_to_partner_link: Volver a %{sp_name} in_person_proofing.body.barcode.what_to_expect: Qué esperar en la oficina de correos in_person_proofing.body.cta.button: Intentar en persona -in_person_proofing.body.cta.prompt_detail: Es posible que pueda verificar su identidad en una oficina de correos participante cercana. +in_person_proofing.body.cta.prompt_detail: La mayoría de las personas que no pueden hacer la verificación de su identidad en línea logran verificarla en una oficina de correos participante. No es necesario hacer cita para ello, y hay oficinas en todo el país. in_person_proofing.body.expect.heading: Qué esperar después de la visita in_person_proofing.body.expect.info: En las 24 horas siguientes a su visita a la oficina de correos, recibirá un correo electrónico para informarle si se logró o no su verificación de identidad. in_person_proofing.body.location.distance.one: A %{count} milla de distancia @@ -1448,7 +1448,7 @@ notices.privacy.security_and_privacy_practices: Prácticas de seguridad y declar notices.resend_confirmation_email.success: Enviamos otro correo electrónico de confirmación. notices.session_cleared: Por su seguridad, borramos lo que usted ingresó si usted no pasa a una página nueva en %{minutes} minutos. notices.session_timedout: Cerramos su sesión. Por su seguridad, %{app_name} cierra su sesión cuando usted no pasa a una página nueva en %{minutes} minutos. -notices.sign_in.recaptcha.disclosure_statement_html: Este sitio está protegido por reCAPTCHA y se aplican %{google_policy_link_html} y %{google_tos_link_html} de Google. +notices.sign_in.recaptcha.disclosure_statement_html: Este sitio está protegido por reCAPTCHA y se aplican la %{google_policy_link_html} y las %{google_tos_link_html} de Google. notices.signed_up_and_confirmed.first_paragraph_end: con un vínculo para confirmar su dirección de correo electrónico. Siga el vínculo para continuar agregando este correo electrónico a su cuenta. notices.signed_up_and_confirmed.first_paragraph_start: Enviamos un correo electrónico a notices.signed_up_and_confirmed.no_email_sent_explanation_start: '¿No recibió un correo electrónico?' @@ -1755,9 +1755,9 @@ two_factor_authentication.piv_cac.nickname: Apodo two_factor_authentication.piv_cac.renamed: Se ha cambiado correctamente el nombre de su método PIV/CAC two_factor_authentication.please_try_again_html: Inténtelo de nuevo en %{countdown}. two_factor_authentication.read_about_two_factor_authentication: Lea sobre la autenticación de dos factores -two_factor_authentication.recaptcha.disclosure_statement_html: Este sitio está protegido por reCAPTCHA y se aplican %{google_policy_link_html} y %{google_tos_link_html} de Google. Lea %{login_tos_link_html} de %{app_name}. -two_factor_authentication.recaptcha.google_policy_link: la Política de privacidad -two_factor_authentication.recaptcha.google_tos_link: las Condiciones de servicio +two_factor_authentication.recaptcha.disclosure_statement_html: Este sitio está protegido por reCAPTCHA y se aplican la %{google_policy_link_html} y las %{google_tos_link_html} de Google. Lea %{login_tos_link_html} de %{app_name}. +two_factor_authentication.recaptcha.google_policy_link: Política de privacidad +two_factor_authentication.recaptcha.google_tos_link: Condiciones de servicio two_factor_authentication.recaptcha.login_tos_link: Condiciones de uso del servicio móvil two_factor_authentication.recommended: Recomendado two_factor_authentication.totp_header_text: Ingrese su código de la aplicación de autenticación diff --git a/config/locales/fr.yml b/config/locales/fr.yml index f419b9ae3d0..c078e9c4862 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1208,7 +1208,7 @@ in_person_proofing.body.barcode.retail_hours_closed: Fermé in_person_proofing.body.barcode.return_to_partner_link: Retourner à %{sp_name} in_person_proofing.body.barcode.what_to_expect: À quoi s’attendre au bureau de poste in_person_proofing.body.cta.button: Essayer en personne -in_person_proofing.body.cta.prompt_detail: Vous pourrez peut-être confirmer votre identité dans un bureau de poste participant près de chez vous. +in_person_proofing.body.cta.prompt_detail: La plupart des personnes qui ne parviennent pas à effectuer cette étape en ligne réussissent à confirmer leur identité dans un bureau de poste participant. Sans rendez-vous. Il existe des sites dans l’ensemble du pays. in_person_proofing.body.expect.heading: Que faire après votre visite in_person_proofing.body.expect.info: Nous vous enverrons un e-mail pour vous informer de la réussite ou de l’échec de la vérification de votre identité dans les 24 heures suivant votre visite au bureau de poste. in_person_proofing.body.location.distance.one: à %{count} mile diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 1530d4741cc..0349e3166ef 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -1221,7 +1221,7 @@ in_person_proofing.body.barcode.retail_hours_closed: 关闭 in_person_proofing.body.barcode.return_to_partner_link: 返回 %{sp_name} in_person_proofing.body.barcode.what_to_expect: 在邮局会发生什么 in_person_proofing.body.cta.button: 尝试亲身去 -in_person_proofing.body.cta.prompt_detail: 你也许可以到附近一个参与本项目的邮局去亲身验证你的身份证件。 +in_person_proofing.body.cta.prompt_detail: 无法在网上完成这一步骤的大多数人都能在一个参与本项目的邮局成功地证实身份。去邮局无需预约。全国各地都有参与本项目的邮局。 in_person_proofing.body.expect.heading: 去邮局后会发生什么 in_person_proofing.body.expect.info: 你去邮局后 24 小时内,我们会给你发电邮,告诉你是否成功验证了身份。 in_person_proofing.body.location.distance.one: 距离你 %{count} 英里 diff --git a/dockerfiles/application.yaml b/dockerfiles/application.yaml index 9803a90beb8..67ad0b394b4 100644 --- a/dockerfiles/application.yaml +++ b/dockerfiles/application.yaml @@ -489,6 +489,9 @@ spec: patch: |- - op: replace path: /spec/template/spec/containers/0/image + value: {{ECR_REGISTRY}}/identity-idp/nginx:{{IDP_CONTAINER_TAG}} + - op: replace + path: /spec/template/spec/containers/1/image value: {{ECR_REGISTRY}}/identity-idp/idp:{{IDP_CONTAINER_TAG}} - op: replace path: /spec/template/spec/containers/0/imagePullPolicy diff --git a/dockerfiles/nginx-prod.conf b/dockerfiles/nginx-prod.conf new file mode 100644 index 00000000000..e5b041464ac --- /dev/null +++ b/dockerfiles/nginx-prod.conf @@ -0,0 +1,235 @@ +# user nginx; +worker_processes 2; +worker_rlimit_nofile 2048; +pid /var/run/nginx.pid; +daemon off; +load_module /usr/lib/nginx/modules/ngx_http_headers_more_filter_module.so; + + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + + sendfile on; + tcp_nopush off; + keepalive_timeout 60 50; + gzip on; + gzip_types text/plain text/css application/xml application/javascript application/json image/jpg image/jpeg image/png image/gif image/svg+xml font/woff2 woff2; + + # Timeouts definition + client_body_timeout 10; + client_header_timeout 10; + send_timeout 10; + # Set buffer size limits + client_body_buffer_size 1k; + client_header_buffer_size 1k; + client_max_body_size 20k; + large_client_header_buffers 2 20k; + # Limit connections + limit_conn addr 20; + limit_conn_status 429; + limit_conn_zone $binary_remote_addr zone=addr:5m; + # Disable sending server info and versions + server_tokens off; + more_clear_headers Server; + more_clear_headers X-Powered-By; + # Prevent clickJacking attack + add_header X-Frame-Options SAMEORIGIN; + # Disable content-type sniffing + add_header X-Content-Type-Options nosniff; + # Enable XSS filter + add_header X-XSS-Protection "1; mode=block"; + + # Enables nginx to check multiple set_real_ip_from lines + real_ip_recursive on; + + real_ip_header X-Forwarded-For; + + # Exclude all private IPv4 space from client source calculation when + # processing the X-Forewarded-For header + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 100.64.0.0/10; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + # TODO - IPv6 CIDR for VPCs will require autoconfiguration + + # Add CloudFront source address ranges to trusted CIDR range for real ip computation + include /etc/nginx/cloudfront-ips.conf; + + # logging + access_log /dev/stdout; + error_log /dev/stdout info; + + # Specify a key=value format useful for machine parsing + log_format kv escape=json + '{' + '"time": "$time_local", ' + '"hostname": "$host", ' + '"dest_port": "$server_port", ' + '"dest_ip": "$server_addr", ' + '"src": "$remote_addr", ' + '"src_ip": "$realip_remote_addr", ' + '"user": "$remote_user", ' + '"protocol": "$server_protocol", ' + '"http_method": "$request_method", ' + '"status": "$status", ' + '"bytes_out": "$body_bytes_sent", ' + '"bytes_in": "$request_length", ' + '"http_referer": "$http_referer", ' + '"http_user_agent": "$http_user_agent", ' + '"nginx_version": "$nginx_version", ' + '"http_cloudfront_viewer_address": "$http_cloudfront_viewer_address", ' + '"http_cloudfront_viewer_http_version": "$http_cloudfront_viewer_http_version", ' + '"http_cloudfront_viewer_tls": "$http_cloudfront_viewer_tls", ' + '"http_cloudfront_viewer_country": "$http_cloudfront_viewer_country", ' + '"http_cloudfront_viewer_country_region": "$http_cloudfront_viewer_country_region", ' + '"http_x_forwarded_for": "$http_x_forwarded_for", ' + '"http_x_amzn_trace_id": "$http_x_amzn_trace_id", ' + '"response_time": "$upstream_response_time", ' + '"request_time": "$request_time", ' + '"request": "$request", ' + '"tls_protocol": "$ssl_protocol", ' + '"tls_cipher": "$ssl_cipher", ' + '"uri_path": "$uri", ' + '"uri_query": "$query_string",' + '"log_filename": "nginx_access.log"' + '}'; + + # Get $status_reason variable, a human readable version of $status + include status-map.conf; + + # Set HSTS header only if not already set by app. Some clients get unhappy if + # you set multiple Strict-Transport-Security headers. + # https://serverfault.com/a/598106 + map $upstream_http_strict_transport_security $sts_value { + '' "max-age=31536000; preload"; + } + + # Always add a HSTS header - This is still inside the http block, so will not + # conflict with headers set in nginx.conf + add_header Strict-Transport-Security $sts_value always; + + server { + listen 8443 ssl; + server_name _; + access_log /dev/stdout kv; + + ssl_certificate /keys/localhost.crt; + ssl_certificate_key /keys/localhost.key; + ssl_verify_client optional_no_ca; # on; + ssl_verify_depth 10; + + ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; + ssl_prefer_server_ciphers on; + ssl_protocols TLSv1.2; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 5m; + ssl_stapling on; + ssl_stapling_verify on; + resolver_timeout 5s; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Host $host; + proxy_buffer_size 32k; + proxy_buffers 8 32k; + proxy_busy_buffers_size 64k; + + if ($request_method !~ ^(DELETE|GET|HEAD|OPTIONS|POST|PUT)$ ) { + return 405; + } + + # Content expiry rules + # Content pages set to no-cache + location ~* \.(?:manifest|appcache|html?|xml|json)$ { + expires -1; + proxy_pass https://0.0.0.0:3000; + } + + # Mutable assets set to no-cache + location ~* /AcuantImageProcessingWorker\.min\.js$ { + expires -1; + add_header Strict-Transport-Security $sts_value; + proxy_pass https://0.0.0.0:3000; + } + + # Media: images, icons, video, audio, HTC + location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ { + expires 1M; + add_header Strict-Transport-Security $sts_value; + add_header Cache-Control "public"; + proxy_pass https://0.0.0.0:3000; + } + + # CSS and Javascript + location ~* \.(?:css|js)$ { + expires 1M; + add_header Strict-Transport-Security $sts_value; + add_header Cache-Control "public"; + proxy_pass https://0.0.0.0:3000; + } + + # WebFonts + location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ { + types {font/woff2 woff2;} + expires 1M; + add_header Strict-Transport-Security $sts_value; + add_header Access-Control-Allow-Origin https://$host; + add_header Access-Control-Allow-Methods 'GET'; + proxy_pass https://0.0.0.0:3000; + } + + # TODO - The following matches result in inconsistent headers! Should refactor to use include blocks + + ## Very large upload limits + + # Combined async image uploads - Require very large cap + location /api/verify/images { + add_header Strict-Transport-Security $sts_value; + client_max_body_size 30M; + proxy_pass https://0.0.0.0:3000; + } + + # Fallback (noscript) combined uploads + location ~ ^(/(en|es|fr))?/verify/(capture_doc|doc_auth)/document_capture { + add_header Strict-Transport-Security $sts_value; + client_max_body_size 30M; + proxy_pass https://0.0.0.0:3000; + } + + ## Large upload limits + + location ~ /api/v1/(facematch|liveness) { + add_header Strict-Transport-Security $sts_value; + client_max_body_size 10M; + proxy_pass https://0.0.0.0:3000; + } + + location ^~ /AssureIDService/Document/ { + add_header Strict-Transport-Security $sts_value; + client_max_body_size 10M; + proxy_pass https://0.0.0.0:3000; + } + + location ^~ /api/service_provider { + add_header Strict-Transport-Security $sts_value; + client_max_body_size 30k; + proxy_pass https://0.0.0.0:3000; + } + + location /service_providers { + add_header Strict-Transport-Security $sts_value; + client_max_body_size 2M; + error_page 413 =303 https://$host/413_logo.html; + proxy_pass https://0.0.0.0:3000; + } + + location / { + # Avoid ssl stripping attack + add_header Strict-Transport-Security $sts_value; + proxy_pass https://0.0.0.0:3000; + } + } +} diff --git a/dockerfiles/nginx.Dockerfile b/dockerfiles/nginx.Dockerfile new file mode 100644 index 00000000000..c96304c1ffa --- /dev/null +++ b/dockerfiles/nginx.Dockerfile @@ -0,0 +1,19 @@ +FROM public.ecr.aws/docker/library/alpine:3 + +RUN apk upgrade --no-cache +RUN apk add --no-cache jq curl nginx nginx-mod-http-headers-more openssl + +COPY ./dockerfiles/update-ips.sh /update-ips.sh +COPY ./dockerfiles/nginx-prod.conf /etc/nginx/nginx.conf +COPY ./dockerfiles/status-map.conf /etc/nginx/ +RUN /update-ips.sh + +# Generate and place SSL certificates for nginx (used only by ALB) +RUN mkdir /keys +RUN openssl req -x509 -sha256 -nodes -newkey rsa:2048 -days 1825 \ + -keyout /keys/localhost.key \ + -out /keys/localhost.crt \ + -subj "/C=US/ST=Fake/L=Fakerton/O=Dis/CN=localhost" && \ + chmod 644 /keys/localhost.key /keys/localhost.crt + +ENTRYPOINT ["/usr/sbin/nginx"] diff --git a/dockerfiles/status-map.conf b/dockerfiles/status-map.conf new file mode 100644 index 00000000000..2d08bec5ee6 --- /dev/null +++ b/dockerfiles/status-map.conf @@ -0,0 +1,80 @@ +# Create $status_reason, a human-friendly version of $status. +# This file must be included from inside an http { } block. +map $status $status_reason { + default "-"; + 100 "Continue"; + 101 "Switching Protocols"; + 102 "Processing"; + + 200 "OK"; + 201 "Created"; + 202 "Accepted"; + 203 "Non-Authoritative Information"; + 204 "No Content"; + 205 "Reset Content"; + 206 "Partial Content"; + 207 "Multi-Status"; + 208 "Already Reported"; + 226 "IM Used"; + + 300 "Multiple Choices"; + 301 "Moved Permanently"; + 302 "Found"; + 303 "See Other"; + 304 "Not Modified"; + 305 "Use Proxy"; + 306 "Switch Proxy"; + 307 "Temporary Redirect"; + 308 "Permanent Redirect"; + + 400 "Bad Request"; + 401 "Unauthorized"; + 402 "Payment Required"; + 403 "Forbidden"; + 404 "Not Found"; + 405 "Method Not Allowed"; + 406 "Not Acceptable"; + 407 "Proxy Authentication Required"; + 408 "Request Timeout"; + 409 "Conflict"; + 410 "Gone"; + 411 "Length Required"; + 412 "Precondition Failed"; + 413 "Payload Too Large"; + 414 "URI Too Long"; + 415 "Unsupported Media Type"; + 416 "Range Not Satisfiable"; + 417 "Expectation Failed"; + 418 "I'm A Teapot"; + 421 "Too Many Connections From This IP"; + 422 "Unprocessable Entity"; + 423 "Locked"; + 424 "Failed Dependency"; + 425 "Unordered Collection"; + 426 "Upgrade Required"; + 428 "Precondition Required"; + 429 "Too Many Requests"; + 431 "Request Header Fields Too Large"; + 449 "Retry With"; + 450 "Blocked By Windows Parental Controls"; + + # nginx + 444 "No Response"; + 495 "SSL Certificate Error"; + 496 "SSL Certificate Required"; + 497 "HTTP Request Sent to HTTPS Port"; + 499 "Client Closed Request"; + + 500 "Internal Server Error"; + 501 "Not Implemented"; + 502 "Bad Gateway"; + 503 "Service Unavailable"; + 504 "Gateway Timeout"; + 505 "HTTP Version Not Supported"; + 506 "Variant Also Negotiates"; + 507 "Insufficient Storage"; + 508 "Loop Detected"; + 509 "Bandwidth Limit Exceeded"; + 510 "Not Extended"; + 511 "Network Authentication Required"; +} diff --git a/dockerfiles/update-ips.sh b/dockerfiles/update-ips.sh new file mode 100755 index 00000000000..102c2ee8665 --- /dev/null +++ b/dockerfiles/update-ips.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# +# This script updates the ips.conf file so that we have +# up-to-date cloudfront IP information. +# +set -e + +IPS_CONF="/etc/nginx/cloudfront-ips.conf" +echo "Updating $IPS_CONF" + +rm -f "$IPS_CONF" +echo '# cloudfront IP ranges' > $IPS_CONF +echo '# ' >> $IPS_CONF + +curl -s https://ip-ranges.amazonaws.com/ip-ranges.json | jq -r '.prefixes[] | select(.service=="CLOUDFRONT_ORIGIN_FACING") | .ip_prefix' | while read i ; do + echo "set_real_ip_from $i;" >> $IPS_CONF +done + +curl -s https://ip-ranges.amazonaws.com/ip-ranges.json | jq -r '.ipv6_prefixes[] | select(.service=="CLOUDFRONT") | .ipv6_prefix' | while read i ; do + echo "set_real_ip_from $i;" >> $IPS_CONF +done diff --git a/docs/frontend.md b/docs/frontend.md index 61486c65a5e..50ea1687641 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -373,10 +373,14 @@ Each error includes a few details to help you debug: - `message`: Corresponds to [`Error#message`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/message), and is usually a good summary to group by - `name`: The subclass of the error (e.g. `TypeError`) - `stack`: A stacktrace of the individual error instance +- `filename`: The URL of the script where the error was raised, if it's an uncaught error +- `error_id`: A unique identifier for tracing caught errors explicitly tracked Note that NewRelic creates links in stack traces which are invalid, since they include the line and column number. If you encounter an "AccessDenied" error when clicking a stacktrace link, make sure to remove those details after the `.js` in your browser URL. -Debugging these stack traces can be difficult, since files in production are minified, and the stack traces include line numbers and columns for minified files. With the following steps, you can find a reference to the original code: +If an error includes `error_id`, you can use this to search in code for the corresponding call to `trackError` including that value as its `errorId` to trace where the error occurred. + +Otherwise, debugging these stack traces can be difficult, since files in production are minified, and the stack traces include line numbers and columns for minified files. With the following steps, you can find a reference to the original code: 1. Download the minified JavaScript file referenced in the stack trace - Example: https://secure.login.gov/packs/document-capture-e41c853e.digested.js diff --git a/docs/images/port-forwarding-settings.png b/docs/images/port-forwarding-settings.png new file mode 100644 index 00000000000..9263b0f9e59 Binary files /dev/null and b/docs/images/port-forwarding-settings.png differ diff --git a/docs/mobile.md b/docs/mobile.md index b75ed8e580d..206f86e6b45 100644 --- a/docs/mobile.md +++ b/docs/mobile.md @@ -22,21 +22,13 @@ development: enable_load_testing_mode: true ``` -3. In the Chrome web browser of your development computer, visit `chrome://inspect` - -4. Click on **Port forwarding**. For port `8234` enter `0.0.0.0:3000`. Check **Enable port forwarding** and click **Done**. These screenshots illustrates enabling port forwarding on a MacBook: - -| Click on Port forwarding | Enter IP and enable | -| :----------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | -| ![port-forwarding-button](https://user-images.githubusercontent.com/546123/231608927-5f577e1a-bc82-47c6-b69a-d592c551a99f.png) | ![port-forwarding-settings](https://user-images.githubusercontent.com/546123/231608489-f09f281e-305d-4200-9f21-9d772773a113.png) | - -5. Start your app's local web server with: +3. Start your app's local web server with: ```bash HOST=0.0.0.0 make run-https ``` -6. In the Chrome browser on your phone, open a new incognito tab. In the address bar, type in `https://` (don't forget the `s`) followed by your LAN IP and port number (like `https://192.168.x.x:3000`). When you visit this page, you may see a **Your connection is not private** message. Click **Advanced** and **Proceed** to continue. You should then see the sign in screen of the identity-idp app. +4. On your phone's browser, open a new tab. In the address bar, type in `https://` (don't forget the `s`) followed by your LAN IP and port number (like `https://192.168.x.x:3000`). When you visit this page, you may see a **Your connection is not private** message. Click **Advanced** and **Proceed** to continue. You should then see the sign in screen of the identity-idp app. After you complete these steps, pages from the app are served from your development machine to your mobile device, where you may now use the identity-idp app. For front-end development, you may now want to turn on browser development tools per the next section of these instructions. @@ -56,11 +48,25 @@ These instructions will allow you to debug your phone browser with Chrome DevToo 2. Plug your Android phone into your development computer with a USB cable. (A USB hub may or may not work.) If you see a message on your phone asking you to **Allow USB debugging** click to allow it. -3. Visit `chrome://inspect` in the Chrome browser of your development computer. (It may already be open from the previous set of instructions.) Below the "Remote Target" heading, you should see a listing of all the tabs open on your phone. Find the item on the list that represents the sign in screen of the identity-idp app. It should be at the top of the list. +3. In the Chrome web browser of your development computer, visit `chrome://inspect` + +4. Click on **Port forwarding**. Create/ensure the following entries: + - Port 3000 forwarded to localhost:3000 + - Port 3035 forwarded to localhost:3035 (This is needed to ensure that JavaScript resources can be accessed through webpack) + +Check **Enable port forwarding** and click **Done**. These screenshots illustrates enabling port forwarding on a MacBook: + +| Click on Port forwarding | Enter IP and enable | +| :----------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | +| ![port-forwarding-button](https://user-images.githubusercontent.com/546123/231608927-5f577e1a-bc82-47c6-b69a-d592c551a99f.png) | ![port-forwarding-settings](/docs/images/port-forwarding-settings.png) | + +5. You should now be able to access the application by navigating to http://localhost:3000 on your device. + +6. Back on your development computer, in the `chrome://inspect` tab, below the "Remote Target" heading, you should see a listing of all the tabs open on your phone. Find the item on the list that represents the sign in screen of the identity-idp app. It should be at the top of the list. If you don't see any tabs under the "Remote Target" heading, you may need to try a different method of connecting your phone to your computer. In your terminal, you can run the command `ioreg -p IOUSB` to see what is connected to your USB ports. If your phone is connected to a USB hub but is not listed in the output, try connecting your phone directly to the computer. You could also try using a different USB cable. -4. Click to **inspect** this tab. You should see browser DevTools and a representation of your phone's screen on your development computer, as in this illustration: +7. Click to **inspect** this tab. You should see browser DevTools and a representation of your phone's screen on your development computer, as in this illustration: inspect-androd-chrome-tab diff --git a/lib/linters/i18n_helper_html_linter.rb b/lib/linters/i18n_helper_html_linter.rb new file mode 100644 index 00000000000..53251a13b05 --- /dev/null +++ b/lib/linters/i18n_helper_html_linter.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module IdentityIdp + # This linter checks to ensure that strings which include HTML are rendered using Rails `t` + # view helper, rather than through the I18n class. Only the Rails view helper will mark the + # content as HTML-safe. + # + # @see https://guides.rubyonrails.org/i18n.html#using-safe-html-translations + # + # @example + # # bad + # I18n.t('errors.message_html') + # + # # good + # t('errors.message_html') + # + class I18nHelperHtmlLinter < RuboCop::Cop::Base + MSG = 'Use the Rails `t` view helper for HTML-safe strings' + + RESTRICT_ON_SEND = [:t].freeze + + def_node_matcher :i18n_class_send?, <<~PATTERN + (send (const nil? :I18n) :t $...) + PATTERN + + def on_send(node) + return if !i18n_class_send?(node) || !i18n_key(node)&.end_with?('_html') + add_offense(node) + end + + private + + def i18n_key(node) + first_argument = node.arguments.first + return if first_argument.nil? + return if !first_argument.respond_to?(:value) + first_argument.value.to_s + end + end + end + end +end diff --git a/package.json b/package.json index d712d452ada..08749c75bb7 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "eslint-plugin-react": "^7.31.8", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-testing-library": "^6.2.0", - "jsdom": "^22.1.0", + "jsdom": "^25.0.1", "mocha": "^10.0.0", "mq-polyfill": "^1.1.8", "msw": "^2.2.1", @@ -91,7 +91,6 @@ "svgo": "^3.2.0", "swr": "^2.0.0", "typescript": "^5.2.2", - "webpack-dev-server": "^5.0.4", "yarn-deduplicate": "^6.0.2" }, "resolutions": { diff --git a/scripts/yaml_characters b/scripts/yaml_characters index 9be80fe72b1..ba7bca65a5e 100755 --- a/scripts/yaml_characters +++ b/scripts/yaml_characters @@ -5,7 +5,9 @@ require 'pathname' require_relative '../config/environment' excluded_locales = [] -excluded_gem_paths = [] +excluded_paths = [] +excluded_key_scopes = [] + OptionParser.new do |opts| opts.banner = <<~TXT Usage @@ -22,8 +24,16 @@ OptionParser.new do |opts| excluded_locales << locale.to_sym end + opts.on('--exclude-path=PATH', 'Disregard characters from the given relative path') do |path| + excluded_paths << File.join(Dir.pwd, path, '') + end + opts.on('--exclude-gem-path=GEM', 'Disregard characters loaded by the given gem') do |gem| - excluded_gem_paths << Gem.loaded_specs[gem].full_gem_path + excluded_paths << File.join(Gem.loaded_specs[gem].full_gem_path, '') + end + + opts.on('--exclude-key-scope=SCOPE', 'Exclude keys in the given top-level key scope') do |scope| + excluded_key_scopes << scope.to_sym end opts.on('-h', '--help', 'Prints this help') do @@ -50,12 +60,13 @@ def hash_values(hash) end I18n.load_path.reject! do |load_path| - excluded_gem_paths.any? { |gem_path| load_path.start_with?(gem_path) } + excluded_paths.any? { |path| load_path.start_with?(path) } end I18n.backend.eager_load! data = I18n.backend.translations.slice(*I18n.available_locales - excluded_locales) +excluded_key_scopes.each { |scope| data.each_key { |locale| data[locale].delete(scope) } } strings = hash_values(data) joined_string = strings.join('') sanitized_string = sanitize(joined_string) diff --git a/spec/controllers/frontend_log_controller_spec.rb b/spec/controllers/frontend_log_controller_spec.rb index a717ac1ecd5..a891ce398b2 100644 --- a/spec/controllers/frontend_log_controller_spec.rb +++ b/spec/controllers/frontend_log_controller_spec.rb @@ -60,9 +60,11 @@ let(:flow_path) { 'standard' } let(:event) { 'IdV: location submitted' } let(:payload) do - { 'selected_location' => selected_location, + { + 'selected_location' => selected_location, 'flow_path' => flow_path, - 'opted_in_to_in_person_proofing' => nil } + 'opted_in_to_in_person_proofing' => nil, + } end it 'succeeds' do @@ -94,9 +96,11 @@ { opt_in_analytics_properties: true } end let(:payload) do - { 'selected_location' => selected_location, + { + 'selected_location' => selected_location, 'flow_path' => flow_path, - 'opted_in_to_in_person_proofing' => true } + 'opted_in_to_in_person_proofing' => true, + } end before do @@ -207,6 +211,7 @@ message: 'message', stack: 'stack', filename: 'filename', + error_id: nil, }, }, expected: true, diff --git a/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb index d87e5061368..427b42a7a7f 100644 --- a/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb +++ b/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb @@ -80,7 +80,6 @@ referenceId: '123ab45d-2e34-46f3-8d17-6f540ae90303', data: { eventId: 'zoYgIxEZUbXBoocYAnbb5DrT', - customerUserId: document_capture_session_uuid, docvTransactionToken: docv_transaction_token, qrCode: 'data:image/png;base64,iVBO......K5CYII=', url: socure_capture_app_url, @@ -98,7 +97,6 @@ it 'creates a DocumentRequest' do expect(request_class).to have_received(:new). with( - document_capture_session_uuid: document_capture_session_uuid, redirect_url: idv_hybrid_mobile_socure_document_capture_url, language: expected_language, ) @@ -125,7 +123,6 @@ }, language: expected_language, }, - customerUserId: document_capture_session_uuid, }, ), ) @@ -148,7 +145,6 @@ }, language: 'zh-cn', }, - customerUserId: document_capture_session_uuid, }, ), ) diff --git a/spec/controllers/idv/in_person/public/usps_locations_controller_spec.rb b/spec/controllers/idv/in_person/public/usps_locations_controller_spec.rb index 3559670d7b4..57b97ba4755 100644 --- a/spec/controllers/idv/in_person/public/usps_locations_controller_spec.rb +++ b/spec/controllers/idv/in_person/public/usps_locations_controller_spec.rb @@ -3,6 +3,10 @@ RSpec.describe Idv::InPerson::Public::UspsLocationsController do include Rails.application.routes.url_helpers + before do + stub_analytics + end + describe '#index' do subject(:action) do post :index, @@ -21,5 +25,59 @@ action expect(response).to be_ok end + + context 'with a 500 error from USPS' do + let(:server_error) { Faraday::ServerError.new } + let(:proofer) { double('Proofer') } + + before do + allow(UspsInPersonProofing::EnrollmentHelper).to receive(:usps_proofer).and_return(proofer) + allow(proofer).to receive(:request_facilities).and_raise(server_error) + end + + it 'returns an unprocessible entity client error' do + subject + expect(@analytics).to have_logged_event( + 'Request USPS IPP locations: request failed', + api_status_code: 422, + exception_class: server_error.class, + exception_message: server_error.message, + response_body_present: + server_error.response_body.present?, + ) + + status = response.status + expect(status).to eq 422 + end + end + + context 'address has unsupported characters' do + let(:locale) { nil } + let(:usps_locations_error) { Idv::InPerson::Public::UspsLocationsError.new } + + subject(:response) do + post :index, params: { locale: locale, + address: { street_address: '1600, Pennsylvania Ave', + city: 'Washington', + state: 'DC', + zip_code: '20500' } } + end + + it 'returns unprocessable entity' do + subject + + expect(@analytics).to have_logged_event( + 'Request USPS IPP locations: request failed', + api_status_code: 422, + exception_class: usps_locations_error.class, + exception_message: usps_locations_error.message, + response_body_present: false, + response_body: false, + response_status_code: false, + ) + + expect(response.status).to eq 422 + end + end end end diff --git a/spec/controllers/idv/socure/document_capture_controller_spec.rb b/spec/controllers/idv/socure/document_capture_controller_spec.rb index 6ecf9d88f05..93db87d1ca9 100644 --- a/spec/controllers/idv/socure/document_capture_controller_spec.rb +++ b/spec/controllers/idv/socure/document_capture_controller_spec.rb @@ -93,7 +93,6 @@ referenceId: '123ab45d-2e34-46f3-8d17-6f540ae90303', data: { eventId: 'zoYgIxEZUbXBoocYAnbb5DrT', - customerUserId: '121212', docvTransactionToken: docv_transaction_token, qrCode: 'data:image/png;base64,iVBO......K5CYII=', url: socure_capture_app_url, @@ -111,7 +110,6 @@ it 'creates a DocumentRequest' do expect(request_class).to have_received(:new). with( - document_capture_session_uuid: expected_uuid, redirect_url: idv_socure_document_capture_update_url, language: expected_language, ) @@ -138,7 +136,6 @@ }, language: :en, }, - customerUserId: expected_uuid, }, ), ) @@ -161,7 +158,6 @@ }, language: 'zh-cn', }, - customerUserId: expected_uuid, }, ), ) diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index 50351d84b11..5b8e6e758c0 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -4,6 +4,7 @@ include FlowPolicyHelper let(:user) { create(:user) } + let(:analytics_hash) do { analytics_id: 'Doc Auth', @@ -144,6 +145,7 @@ context 'when proofing_device_profiling is enabled' do let(:threatmetrix_client_id) { 'threatmetrix_client' } let(:review_status) { 'pass' } + let(:idv_result) do { context: { @@ -253,6 +255,69 @@ end end + context 'when there is a threatmetrix exception' do + let(:review_status) { nil } + + let(:idv_result) do + { + context: { + device_profiling_adjudication_reason: 'device_profiling_exception', + errors: {}, + stages: { + threatmetrix: { + client: nil, + errors: {}, + exception: "Unexpected ThreatMetrix review_status value: #{review_status}", + response_body: nil, + review_status:, + success: false, + transaction_id: nil, + }, + }, + }, + success: false, + } + end + + it 'sets the review status in the idv session' do + get :show + expect(controller.idv_session.threatmetrix_review_status).to be_nil + end + + it 'redirects to warning_url' do + get :show + + expect(response).to redirect_to idv_session_errors_warning_url + + expect(@analytics).to have_logged_event( + 'IdV: doc auth warning visited', + step_name: 'verify_info', + remaining_submit_attempts: kind_of(Integer), + ) + end + + it 'logs the analytics event with the device profiling exception' do + get :show + + expect(@analytics).to have_logged_event( + 'IdV: doc auth verify proofing results', + hash_including( + success: false, + proofing_results: hash_including( + context: hash_including( + device_profiling_adjudication_reason: 'device_profiling_exception', + stages: hash_including( + threatmetrix: hash_including( + exception: match(/\S+/), + ), + ), + ), + ), + ), + ) + end + end + context 'when threatmetrix response is Reject' do let(:review_status) { 'reject' } @@ -427,6 +492,65 @@ end end + context 'when the resolution proofing job fails and there is no exception' do + before do + allow(controller).to receive(:load_async_state).and_return(async_state) + end + + let(:document_capture_session) do + DocumentCaptureSession.create(user:) + end + + let(:async_state) do + # Here we're trying to match the store to redis -> read from redis flow this data travels + adjudicated_result = Proofing::Resolution::ResultAdjudicator.new( + state_id_result: Proofing::StateIdResult.new( + success: true, + errors: {}, + exception: nil, + vendor_name: :aamva, + transaction_id: 'abc123', + verified_attributes: [], + ), + device_profiling_result: Proofing::DdpResult.new(success: true), + ipp_enrollment_in_progress: true, + residential_resolution_result: Proofing::Resolution::Result.new(success: true), + resolution_result: Proofing::Resolution::Result.new( + success: false, + errors: { + base: [ + "Verification failed with code: 'priority.scoring.model.verification.fail'", + ], + }, + ), + same_address_as_id: true, + should_proof_state_id: true, + applicant_pii: Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN, + ).adjudicated_result.to_h + + document_capture_session.create_proofing_session + + document_capture_session.store_proofing_result(adjudicated_result) + + document_capture_session.load_proofing_result + end + + it 'renders the warning page' do + get :show + expect(response).to redirect_to(idv_session_errors_warning_url) + end + + it 'logs an event' do + get :show + + expect(@analytics).to have_logged_event( + 'IdV: doc auth warning visited', + step_name: 'verify_info', + remaining_submit_attempts: kind_of(Numeric), + ) + end + end + context 'when the resolution proofing job has not completed' do let(:async_state) do ProofingSessionAsyncResult.new(status: ProofingSessionAsyncResult::IN_PROGRESS) diff --git a/spec/controllers/socure_webhook_controller_spec.rb b/spec/controllers/socure_webhook_controller_spec.rb index 479441dea9e..ac6ea6373fc 100644 --- a/spec/controllers/socure_webhook_controller_spec.rb +++ b/spec/controllers/socure_webhook_controller_spec.rb @@ -4,24 +4,21 @@ RSpec.describe SocureWebhookController do describe 'POST /api/webhooks/socure/event' do - let(:user) { create(:user) } - let(:socure_docv_transaction_token) { 'dummy_docv_transaction_token' } - let(:document_capture_session) do - DocumentCaptureSession.create(user:).tap do |dcs| - dcs.socure_docv_transaction_token = socure_docv_transaction_token - end - end - let(:rate_limiter) { RateLimiter.new(rate_limit_type: :idv_doc_auth, user: user) } let(:socure_secret_key) { 'this-is-a-secret' } let(:socure_secret_key_queue) { ['this-is-an-old-secret', 'this-is-an-older-secret'] } let(:socure_enabled) { true } + let(:event_type) { 'TEST_WEBHOOK' } + let(:event_docv_transaction_token) { 'TEST_WEBHOOK_TOKEN' } + let(:customer_user_id) { '#1-customer' } + let(:reference_id) { 'the-ref-id' } let(:webhook_body) do { event: { created: '2020-01-01T00:00:00Z', - customerUserId: '123', - eventType: 'TEST_WEBHOOK', - referenceId: 'abc', + customerUserId: customer_user_id, + eventType: event_type, + docvTransactionToken: event_docv_transaction_token, + referenceId: reference_id, data: { documentData: { dob: '2000-01-01', @@ -33,19 +30,6 @@ }, } end - let(:document_uploaded_webhook_body) do - { - eventGroup: 'DocvNotification', - reason: 'DOCUMENTS_UPLOADED', - event: { - created: '2020-01-01T00:00:00Z', - docvTransactionToken: socure_docv_transaction_token, - eventType: 'DOCUMENTS_UPLOADED', - message: 'Documents Upload Successful', - referenceId: user.id, - }, - } - end before do allow(IdentityConfig.store).to receive(:socure_webhook_secret_key). @@ -54,202 +38,234 @@ and_return(socure_secret_key_queue) allow(IdentityConfig.store).to receive(:socure_enabled). and_return(socure_enabled) + allow(SocureDocvResultsJob).to receive(:perform_later) stub_analytics end context 'webhook authentication' do - it 'returns OK and logs an event with a correct secret key and body' do - request.headers['Authorization'] = socure_secret_key - post :create, params: webhook_body - - expect(response).to have_http_status(:ok) - expect(@analytics).to have_logged_event( - :idv_doc_auth_socure_webhook_received, - created_at: '2020-01-01T00:00:00Z', - customer_user_id: '123', - event_type: 'TEST_WEBHOOK', - reference_id: 'abc', - user_id: '123', - ) - end - - it 'returns OK with an older secret key' do - request.headers['Authorization'] = socure_secret_key_queue.last - post :create, params: webhook_body - - expect(response).to have_http_status(:ok) - end - - it 'returns unauthorized with a bad secret key' do - request.headers['Authorization'] = 'ABC123' - post :create, params: webhook_body - - expect(response).to have_http_status(:unauthorized) - end - - it 'returns unauthorized with no secret key' do - post :create, params: webhook_body - - expect(response).to have_http_status(:unauthorized) - end - - it 'returns bad request with no event in the body' do - request.headers['Authorization'] = socure_secret_key - post :create, params: {} - - expect(response).to have_http_status(:bad_request) - end - end + context 'received with invalid webhook key' do + it 'returns unauthorized with a bad secret key' do + request.headers['Authorization'] = 'ABC123' + post :create, params: webhook_body - context 'when DOCUMENTS_UPLOADED event received' do - let(:webhook_body) do - { - id: 'a8202f22-7331-483b-a76a-546f68da062d', - origId: '45ac9531-60ae-4bc7-805e-f7823e4e5545', - eventGroup: 'DocvNotification', - reason: 'DOCUMENTS_UPLOADED', - environmentName: 'Production', - event: { - created: '2024-08-07T21:18:19.949Z', - customerUserId: '111-222-333', - docVTransactionToken: '45ac9531-60ae-4bc7-805e-f7823e4e5545', - eventType: 'DOCUMENTS_UPLOADED', - message: 'Documents Upload Successful', - referenceId: '45ac9531-60ae-4bc7-805e-f7823e4e5545', - userId: '444-555-666', - }, - } - end + expect(response).to have_http_status(:unauthorized) + end - it 'returns OK and logs an event with a correct secret key and body' do - request.headers['Authorization'] = socure_secret_key - post :create, params: document_uploaded_webhook_body - expect(response).to have_http_status(:ok) - expect(@analytics).to have_logged_event( - :idv_doc_auth_socure_webhook_received, - created_at: document_uploaded_webhook_body[:event][:created], - event_type: document_uploaded_webhook_body[:event][:eventType], - reference_id: document_uploaded_webhook_body[:event][:referenceId].to_s, - ) - end + it 'returns unauthorized with no secret key' do + post :create, params: webhook_body - context 'when document capture session exists' do - let(:user) { create(:user) } - let(:document_capture_session) do - DocumentCaptureSession.create(user:).tap do |dcs| - dcs.socure_docv_transaction_token = '45ac9531-60ae-4bc7-805e-f7823e4e5545' - end + expect(response).to have_http_status(:unauthorized) end + end + context 'with a valid webhook key' do before do request.headers['Authorization'] = socure_secret_key - allow(DocumentCaptureSession).to receive(:find_by). - and_return(document_capture_session) - allow(SocureDocvResultsJob).to receive(:perform_later) - allow(RateLimiter).to receive(:new).with( - { - user: user, - rate_limit_type: :idv_doc_auth, - }, - ).and_return(rate_limiter) - end - - it 'increments rate limiter of correct user' do - expect(rate_limiter.attempts).to eq 0 - post :create, params: document_uploaded_webhook_body - expect(rate_limiter.attempts).to eq 1 - post :create, params: document_uploaded_webhook_body - expect(rate_limiter.attempts).to eq 2 end - - it 'enqueues a SocureDocvResultsJob' do + it 'returns OK and logs an event with a correct secret key and body' do post :create, params: webhook_body - expect(SocureDocvResultsJob).to have_received(:perform_later). - with(document_capture_session_uuid: document_capture_session.uuid) - end - end - - context 'when document capture session does not exist' do - before do - allow(NewRelic::Agent).to receive(:notice_error) + expect(response).to have_http_status(:ok) + expect(@analytics).to have_logged_event( + :idv_doc_auth_socure_webhook_received, + created_at: '2020-01-01T00:00:00Z', + customer_user_id:, + docv_transaction_token: event_docv_transaction_token, + event_type:, + reference_id:, + ) end - it 'logs an error with NewRelic' do + it 'returns OK with an older secret key' do request.headers['Authorization'] = socure_secret_key_queue.last post :create, params: webhook_body - expect(NewRelic::Agent).to have_received(:notice_error) + expect(response).to have_http_status(:ok) end - end - end - context 'when socure webhook disabled' do - let(:socure_enabled) { false } + context 'when an event does not exist in the body' do + it 'returns bad request' do + post :create, params: {} - it 'the webhook route does not exist' do - request.headers['Authorization'] = socure_secret_key - post :create, params: webhook_body + expect(response).to have_http_status(:bad_request) + end + end - expect(response).to be_not_found - end - end + context 'when document capture session exists' do + it 'logs the user\'s uuid' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + post :create, params: webhook_body + + expect(response).to have_http_status(:ok) + expect(@analytics).to have_logged_event( + :idv_doc_auth_socure_webhook_received, + created_at: '2020-01-01T00:00:00Z', + customer_user_id:, + docv_transaction_token: dcs.socure_docv_transaction_token, + event_type:, + reference_id:, + user_id: dcs.user.uuid, + ) + end - context 'when SESSION_COMPLETE event received' do - let(:docv_transaction_token) { '45ac9531-60ae-4bc7-805e-f7823e4e5547' } - let(:webhook_body) do - { - id: 'a8202f22-7331-483b-a76a-546f68da062d', - origId: '45ac9531-60ae-4bc7-805e-f7823e4e5545', - eventGroup: 'DocvNotification', - reason: 'SESSION_COMPLETE', - environmentName: 'Production', - event: { - created: '2024-08-07T21:18:19.949Z', - customerUserId: '111-222-333', - docVTransactionToken: docv_transaction_token, - eventType: 'SESSION_COMPLETE', - message: 'Session Complete', - referenceId: '45ac9531-60ae-4bc7-805e-f7823e4e5545', - }, - } - end + context 'when DOCUMENTS_UPLOADED event received' do + let(:event_type) { 'DOCUMENTS_UPLOADED' } + + it 'returns OK and logs an event with a correct secret key and body' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + + post :create, params: webhook_body + expect(response).to have_http_status(:ok) + expect(@analytics).to have_logged_event( + :idv_doc_auth_socure_webhook_received, + created_at: webhook_body[:event][:created], + customer_user_id:, + docv_transaction_token: dcs.socure_docv_transaction_token, + event_type:, + reference_id:, + user_id: dcs.user.uuid, + ) + end + + it 'increments rate limiter of correct user' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + + i = 0 + while i < 4 + rate_limiter = RateLimiter.new( + user: dcs.user, + rate_limit_type: :idv_doc_auth, + ) + expect(rate_limiter.attempts).to eq i + i += 1 + post :create, params: webhook_body + end + end + + it 'enqueues a SocureDocvResultsJob' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + + post :create, params: webhook_body + + expect(SocureDocvResultsJob).to have_received(:perform_later). + with(document_capture_session_uuid: dcs.uuid) + end + + it 'does not reset socure_docv_capture_app_url value' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + post :create, params: webhook_body + dcs.reload + expect(dcs.socure_docv_capture_app_url).not_to be_nil + end + + context 'when document capture session does not exist' do + before do + allow(NewRelic::Agent).to receive(:notice_error) + end + + it 'logs an error with NewRelic' do + request.headers['Authorization'] = socure_secret_key_queue.last + post :create, params: webhook_body + + expect(NewRelic::Agent).to have_received(:notice_error) + end + end + end - context 'when document capture session exists' do - let(:user) { create(:user) } - let(:document_capture_session) do - DocumentCaptureSession.create(user:).tap do |dcs| - dcs.socure_docv_transaction_token = docv_transaction_token + context 'when SESSION_COMPLETE event received' do + let(:event_type) { 'SESSION_COMPLETE' } + + it 'does not increment rate limiter of user' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + + i = 0 + while i < 4 + post :create, params: webhook_body + rate_limiter = RateLimiter.new( + user: dcs.user, + rate_limit_type: :idv_doc_auth, + ) + expect(rate_limiter.attempts).to eq 0 + i += 1 + end + end + + it 'does not enqueue a SocureDocvResultsJob' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + + post :create, params: webhook_body + + expect(SocureDocvResultsJob).not_to have_received(:perform_later) + end + + it 'resets socure_docv_capture_app_url to nil' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + expect(dcs.socure_docv_capture_app_url). + not_to be_nil + post :create, params: webhook_body + dcs.reload + expect(dcs.socure_docv_capture_app_url).to be_nil + end end - end - before do - request.headers['Authorization'] = socure_secret_key - allow(DocumentCaptureSession).to receive(:find_by). - and_return(document_capture_session) - allow(SocureDocvResultsJob).to receive(:perform_later) - allow(RateLimiter).to receive(:new).with( - { - user: user, - rate_limit_type: :idv_doc_auth, - }, - ).and_return(rate_limiter) - end + context 'when SESSION_EXPIRED event received' do + let(:event_type) { 'SESSION_EXPIRED' } + + it 'does not increment rate limiter of user' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + + i = 0 + while i < 4 + post :create, params: webhook_body + + rate_limiter = RateLimiter.new( + user: dcs.user, + rate_limit_type: :idv_doc_auth, + ) + expect(rate_limiter.attempts).to eq 0 + i += 1 + end + end + + it 'does not enqueue a SocureDocvResultsJob' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + + post :create, params: webhook_body + + expect(SocureDocvResultsJob).not_to have_received(:perform_later) + end + + it 'resets socure_docv_capture_app_url to nil' do + dcs = create(:document_capture_session, :socure) + webhook_body[:event][:docvTransactionToken] = dcs.socure_docv_transaction_token + expect(dcs.socure_docv_capture_app_url). + not_to be_nil + post :create, params: webhook_body + dcs.reload + expect(dcs.socure_docv_capture_app_url).to be_nil + end + end - it 'does not increment rate limiter of user' do - expect(rate_limiter.attempts).to eq 0 - post :create, params: webhook_body - expect(rate_limiter.attempts).to eq 0 - post :create, params: webhook_body - expect(rate_limiter.attempts).to eq 0 - end + context 'when socure webhook disabled' do + let(:socure_enabled) { false } - it 'does not enqueue a SocureDocvResultsJob' do - post :create, params: webhook_body + it 'the webhook route does not exist' do + post :create, params: webhook_body - expect(SocureDocvResultsJob).not_to have_received(:perform_later). - with(document_capture_session_uuid: document_capture_session.uuid) + expect(response).to be_not_found + end + end end end end diff --git a/spec/controllers/users/emails_controller_spec.rb b/spec/controllers/users/emails_controller_spec.rb index 30b667bfc62..bfc142718af 100644 --- a/spec/controllers/users/emails_controller_spec.rb +++ b/spec/controllers/users/emails_controller_spec.rb @@ -120,4 +120,95 @@ end end end + + describe '#delete' do + subject(:response) { delete :delete, params: params } + let(:user) { create(:user, :fully_registered, :with_multiple_emails) } + let(:params) { { id: user.email_addresses.take.id } } + + before do + stub_sign_in(user) + end + + it 'redirects to account page' do + expect(response).to redirect_to(account_url) + end + + context 'with invalid submisson' do + let(:user) { create(:user, :fully_registered) } + + it 'logs analytics' do + stub_analytics + + response + + expect(@analytics).to have_logged_event( + 'Email Deletion Requested', + success: false, + errors: {}, + ) + end + + it 'flashes error' do + response + + expect(flash[:error]).to eq(t('email_addresses.delete.failure')) + end + end + + context 'with valid submission' do + it 'logs analytics' do + stub_analytics + + response + + expect(@analytics).to have_logged_event( + 'Email Deletion Requested', + success: true, + errors: {}, + ) + end + + it 'notifies all confirmed email addresses, including the deleted' do + email_addresses = user.confirmed_email_addresses.to_a + + response + + expect_delivered_email_count(email_addresses.count) + email_addresses.each do |email_address| + expect_delivered_email( + to: [email_address.email], + subject: t('user_mailer.email_deleted.subject'), + ) + end + end + + it 'flashes success' do + response + + expect(flash[:success]).to eq(t('email_addresses.delete.success')) + end + + it 'tracks user event' do + expect { response }.to change { user.events.count }.by(1) + expect(user.events.last.event_type).to eq('email_deleted') + end + + it 'deletes the email address' do + expect { response }.to change { user.email_addresses.count }.by(-1) + end + + context 'with selected email for linked identity in session' do + before do + controller.user_session[:selected_email_id_for_linked_identity] = params[:id] + end + + it 'resets session value' do + response + + expect(controller.user_session[:selected_email_id_for_linked_identity]).to be_nil + end + end + end + end end diff --git a/spec/factories/document_capture_sessions.rb b/spec/factories/document_capture_sessions.rb new file mode 100644 index 00000000000..679cc9283fd --- /dev/null +++ b/spec/factories/document_capture_sessions.rb @@ -0,0 +1,11 @@ +FactoryBot.define do + factory :document_capture_session do + uuid { SecureRandom.uuid } + user { association :user, :fully_registered } + end + + trait :socure do + socure_docv_transaction_token { SecureRandom.uuid } + socure_docv_capture_app_url { 'https://capture-app.test' } + end +end diff --git a/spec/features/idv/doc_auth/hybrid_handoff_spec.rb b/spec/features/idv/doc_auth/hybrid_handoff_spec.rb index 437db0408f3..ed52c7441fa 100644 --- a/spec/features/idv/doc_auth/hybrid_handoff_spec.rb +++ b/spec/features/idv/doc_auth/hybrid_handoff_spec.rb @@ -315,11 +315,14 @@ def verify_no_upload_photos_section_and_link(page) expect(page).to_not have_content(t('doc_auth.headings.upload_from_computer')) end - context 'on a desktop device with various ipp and selfie configuration' do + context 'on a desktop device with various ipp, socure, and selfie configuration' do let(:in_person_proofing_enabled) { true } let(:sp_ipp_enabled) { true } let(:in_person_proofing_opt_in_enabled) { true } let(:facial_match_required) { true } + let(:socure_enabled) { false } + let(:doc_auth_vendor) { Idp::Constants::Vendors::MOCK } + let(:desktop_test_mode_enabled) { false } let(:user) { user_with_2fa } before do @@ -328,7 +331,10 @@ def verify_no_upload_photos_section_and_link(page) service_provider.in_person_proofing_enabled = false service_provider.save! end - allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode).and_return(false) + allow(IdentityConfig.store).to receive(:socure_enabled).and_return(socure_enabled) + allow(IdentityConfig.store).to receive(:doc_auth_vendor_default).and_return(doc_auth_vendor) + allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode). + and_return(desktop_test_mode_enabled) allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return( in_person_proofing_enabled, ) @@ -351,6 +357,31 @@ def verify_no_upload_photos_section_and_link(page) complete_doc_auth_steps_before_agreement_step complete_agreement_step end + + context 'when socure is the doc auth vendor' do + let(:facial_match_required) { false } + let(:in_person_proofing_opt_in_enabled) { false } + let(:sp_ipp_enabled) { false } + let(:socure_enabled) { true } + let(:doc_auth_vendor) { Idp::Constants::Vendors::SOCURE } + + context 'when socure desktop test mode is not enabled' do + it 'shows phone only top content no upload section' do + verify_handoff_page_non_selfie_version_content(page) + verify_no_upload_photos_section_and_link(page) + end + end + + context 'when socure desktop test mode is enabled' do + let(:desktop_test_mode_enabled) { true } + + it 'shows phone top content and desktop upload content' do + verify_handoff_page_non_selfie_version_content(page) + expect(page).to have_content(t('doc_auth.headings.upload_from_computer')) + end + end + end + context 'when ipp is available system wide' do context 'when in person proofing opt in enabled' do context 'when sp ipp is available' do diff --git a/spec/features/idv/threat_metrix_pending_spec.rb b/spec/features/idv/threat_metrix_pending_spec.rb index 43686859d25..3bd790c6fd0 100644 --- a/spec/features/idv/threat_metrix_pending_spec.rb +++ b/spec/features/idv/threat_metrix_pending_spec.rb @@ -104,7 +104,7 @@ end end - scenario 'users pending ThreatMetrix No Result, it results in an error', :js do + scenario 'users pending ThreatMetrix No Result, it results in an error but shows warning', :js do freeze_time do user = create(:user, :fully_registered) visit_idp_from_ial1_oidc_sp( @@ -117,8 +117,8 @@ complete_ssn_step complete_verify_step - expect(page).to have_content(t('idv.failure.sessions.exception')) - expect(page).to have_current_path(idv_session_errors_exception_path) + expect(page).to have_content(t('idv.failure.sessions.warning')) + expect(page).to have_current_path(idv_session_errors_warning_path) end end diff --git a/spec/forms/frontend_error_form_spec.rb b/spec/forms/frontend_error_form_spec.rb index d800d1fe0df..189bcb1ef5b 100644 --- a/spec/forms/frontend_error_form_spec.rb +++ b/spec/forms/frontend_error_form_spec.rb @@ -1,8 +1,6 @@ require 'rails_helper' RSpec.describe FrontendErrorForm do - let(:filename) { 'https://example.com/foo.js' } - subject(:form) { described_class.new } before do @@ -10,7 +8,9 @@ end describe '#submit' do - subject(:result) { form.submit(filename:) } + subject(:result) { form.submit(filename:, error_id:) } + let(:error_id) { nil } + let(:filename) { 'https://example.com/foo.js' } context 'with valid filename' do let(:filename) { 'https://example.com/foo.js' } @@ -24,9 +24,20 @@ context 'without filename' do let(:filename) { nil } - it 'is unsuccessful' do - expect(result.success?).to eq(false) - expect(result.errors).to eq(filename: [t('errors.general'), t('errors.general')]) + context 'without error id' do + it 'is unsuccessful' do + expect(result.success?).to eq(false) + expect(result.errors).to eq(filename: [t('errors.general'), t('errors.general')]) + end + end + + context 'with error id' do + let(:error_id) { 'exampleId' } + + it 'is successful' do + expect(result.success?).to eq(true) + expect(result.errors).to eq({}) + end end end diff --git a/spec/lib/linters/i18n_helper_html_linter_spec.rb b/spec/lib/linters/i18n_helper_html_linter_spec.rb new file mode 100644 index 00000000000..7cd2f0ec4a0 --- /dev/null +++ b/spec/lib/linters/i18n_helper_html_linter_spec.rb @@ -0,0 +1,51 @@ +require 'rubocop' +require 'rubocop/rspec/cop_helper' +require 'rubocop/rspec/expect_offense' + +require_relative '../../../lib/linters/i18n_helper_html_linter' + +RSpec.describe RuboCop::Cop::IdentityIdp::I18nHelperHtmlLinter do + include CopHelper + include RuboCop::RSpec::ExpectOffense + + let(:config) { RuboCop::Config.new } + let(:cop) { RuboCop::Cop::IdentityIdp::I18nHelperHtmlLinter.new(config) } + + it 'registers offense when calling `t` from i18n class with key suffixed by "_html"' do + expect_offense(<<~RUBY) + I18n.t('errors.message_html') + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ IdentityIdp/I18nHelperHtmlLinter: Use the Rails `t` view helper for HTML-safe strings + RUBY + end + + it 'registers offense when calling `t` from i18n class with symbol key suffixed by "_html"' do + expect_offense(<<~RUBY) + I18n.t(:message_html) + ^^^^^^^^^^^^^^^^^^^^^ IdentityIdp/I18nHelperHtmlLinter: Use the Rails `t` view helper for HTML-safe strings + RUBY + end + + it 'gracefully handles `I18n.t` without arguments' do + expect_no_offenses(<<~RUBY) + I18n.t + RUBY + end + + it 'gracefully handles `I18n.t` with variable key' do + expect_no_offenses(<<~RUBY) + I18n.t(key) + RUBY + end + + it 'registers no offense when calling `t` from i18n class with key not suffixed by "_html"' do + expect_no_offenses(<<~RUBY) + I18n.t('errors.message') + RUBY + end + + it 'registers no offense when calling `t` from Rails view helper' do + expect_no_offenses(<<~RUBY) + t('errors.message_html') + RUBY + end +end diff --git a/spec/services/doc_auth/socure/requests/document_request_spec.rb b/spec/services/doc_auth/socure/requests/document_request_spec.rb index 17c8032118b..6635c856550 100644 --- a/spec/services/doc_auth/socure/requests/document_request_spec.rb +++ b/spec/services/doc_auth/socure/requests/document_request_spec.rb @@ -7,7 +7,6 @@ subject(:document_request) do described_class.new( - document_capture_session_uuid:, redirect_url: redirect_url, language:, ) @@ -43,7 +42,6 @@ }, language: language, }, - customerUserId: document_capture_session_uuid, } end let(:fake_socure_status) { 200 } diff --git a/spec/services/frontend_error_logger_spec.rb b/spec/services/frontend_error_logger_spec.rb index 7f1922e35eb..2e23ba28f1d 100644 --- a/spec/services/frontend_error_logger_spec.rb +++ b/spec/services/frontend_error_logger_spec.rb @@ -9,26 +9,51 @@ end describe '.track_event' do - it 'notices an expected error to NewRelic with custom parameters' do - expect(NewRelic::Agent).to receive(:notice_error).with( - kind_of(FrontendErrorLogger::FrontendError), - expected: true, - custom_params: { - frontend_error: { - name: 'name', - message: 'message', - stack: 'stack', - filename: 'filename.js', + let(:payload) { { name: 'name', message: 'message', stack: 'stack' } } + subject(:result) { FrontendErrorLogger.track_error(**payload) } + + context 'with filename payload' do + let(:payload) { super().merge(filename: 'filename.js') } + + it 'notices an expected error to NewRelic with custom parameters' do + expect(NewRelic::Agent).to receive(:notice_error).with( + kind_of(FrontendErrorLogger::FrontendError), + expected: true, + custom_params: { + frontend_error: { + name: 'name', + message: 'message', + stack: 'stack', + filename: 'filename.js', + error_id: nil, + }, + }, + ) + + result + end + end + + context 'with error id payload' do + let(:payload) { super().merge(error_id: 'exampleId') } + + it 'notices an expected error to NewRelic with custom parameters' do + expect(NewRelic::Agent).to receive(:notice_error).with( + kind_of(FrontendErrorLogger::FrontendError), + expected: true, + custom_params: { + frontend_error: { + name: 'name', + message: 'message', + stack: 'stack', + filename: nil, + error_id: 'exampleId', + }, }, - }, - ) - - FrontendErrorLogger.track_error( - name: 'name', - message: 'message', - stack: 'stack', - filename: 'filename.js', - ) + ) + + result + end end context 'with unsuccessful validation of request parameters' do diff --git a/spec/views/accounts/connected_accounts/selected_email/edit.html.erb_spec.rb b/spec/views/accounts/connected_accounts/selected_email/edit.html.erb_spec.rb index 4645965a8ed..454ddb9144b 100644 --- a/spec/views/accounts/connected_accounts/selected_email/edit.html.erb_spec.rb +++ b/spec/views/accounts/connected_accounts/selected_email/edit.html.erb_spec.rb @@ -22,6 +22,12 @@ @select_email_form = SelectEmailForm.new(user:, identity:) end + it 'renders introduction text' do + expect(rendered).to have_content( + strip_tags(t('help_text.select_preferred_email_html', sp: identity.display_name)), + ) + end + it 'renders a list of the users email addresses as radio options' do allow(self).to receive(:page).and_return(Capybara.string(rendered)) inputs = page.find_all('[type="radio"]') diff --git a/spec/views/sign_up/select_email/show.html.erb_spec.rb b/spec/views/sign_up/select_email/show.html.erb_spec.rb index 9f360eddf30..30d16c34b92 100644 --- a/spec/views/sign_up/select_email/show.html.erb_spec.rb +++ b/spec/views/sign_up/select_email/show.html.erb_spec.rb @@ -1,6 +1,7 @@ require 'rails_helper' RSpec.describe 'sign_up/select_email/show.html.erb' do + subject(:rendered) { render } let(:email) { 'michael.motorist@email.com' } let(:email2) { 'michael.motorist2@email.com' } let(:user) { create(:user) } @@ -10,11 +11,16 @@ create(:email_address, email: email2, user:) @user_emails = user.confirmed_email_addresses @select_email_form = SelectEmailForm.new(user:) + @sp_name = 'Test Service Provider' end - it 'shows all of the user\'s emails' do - render + it 'renders introduction text' do + expect(rendered).to have_content( + strip_tags(t('help_text.select_preferred_email_html', sp: @sp_name)), + ) + end + it 'shows all of the emails' do expect(rendered).to include('michael.motorist@email.com') expect(rendered).to include('michael.motorist2@email.com') end diff --git a/webpack.config.js b/webpack.config.js index fdec2e0ed53..d0492128dd9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,7 @@ const WebpackAssetsManifest = require('webpack-assets-manifest'); const RailsI18nWebpackPlugin = require('@18f/identity-rails-i18n-webpack-plugin'); const RailsAssetsWebpackPlugin = require('@18f/identity-assets/webpack-plugin'); const UnpolyfillWebpackPlugin = require('@18f/identity-unpolyfill-webpack-plugin'); +const LiteWebpackDevServerPlugin = require('@18f/identity-lite-webpack-dev-server'); const env = process.env.NODE_ENV || process.env.RAILS_ENV || 'development'; const host = process.env.HOST || 'localhost'; @@ -22,24 +23,6 @@ module.exports = /** @type {import('webpack').Configuration} */ ({ mode, devtool, target: ['web'], - devServer: { - static: { - directory: './public', - watch: false, - }, - port: devServerPort, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Cache-Control': 'no-store', - Vary: '*', - }, - client: { - overlay: { - runtimeErrors: false, - }, - }, - hot: false, - }, entry: entries.reduce((result, path) => { result[parse(path).name] = resolve(path); return result; @@ -112,5 +95,15 @@ module.exports = /** @type {import('webpack').Configuration} */ ({ }), new RailsAssetsWebpackPlugin(), new UnpolyfillWebpackPlugin(), + devServerPort && + new LiteWebpackDevServerPlugin({ + publicPath: './public', + port: Number(devServerPort), + headers: { + 'Access-Control-Allow-Origin': '*', + 'Cache-Control': 'no-store', + Vary: '*', + }, + }), ], }); diff --git a/yarn.lock b/yarn.lock index dbd737fdad5..e993b73813d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1218,31 +1218,6 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jsonjoy.com/base64@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" - integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== - -"@jsonjoy.com/json-pack@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz#ab59c642a2e5368e8bcfd815d817143d4f3035d0" - integrity sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg== - dependencies: - "@jsonjoy.com/base64" "^1.1.1" - "@jsonjoy.com/util" "^1.1.2" - hyperdyperid "^1.2.0" - thingies "^1.20.0" - -"@jsonjoy.com/util@^1.1.2": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.2.0.tgz#0fe9a92de72308c566ebcebe8b5a3f01d3149df2" - integrity sha512-4B8B+3vFsY4eo33DMKyJPlQ3sBMpPFUZK2dr3O3rXrOGKKbYG44J0XSFkDo1VOQiri5HFEhIeVvItjR2xcazmg== - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" - integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== - "@mswjs/cookies@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-1.1.0.tgz#1528eb43630caf83a1d75d5332b30e75e9bb1b5b" @@ -1398,11 +1373,6 @@ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591" integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q== -"@tootallnate/once@2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== - "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" @@ -1413,21 +1383,6 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A== -"@types/body-parser@*": - version "1.19.5" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" - integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.13": - version "3.5.13" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" - integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== - dependencies: - "@types/node" "*" - "@types/chai-as-promised@*", "@types/chai-as-promised@^7.1.5": version "7.1.5" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz#6e016811f6c7a64f2eed823191c3a6955094e255" @@ -1447,21 +1402,6 @@ dependencies: "@types/react" "*" -"@types/connect-history-api-fallback@^1.5.4": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" - integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - "@types/cookie@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" @@ -1480,43 +1420,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.19.5" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" - integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*", "@types/express@^4.17.21": - version "4.17.21" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" - integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - "@types/grecaptcha@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/grecaptcha/-/grecaptcha-3.0.4.tgz#3de601f3b0cd0298faf052dd5bd62aff64c2be2e" integrity sha512-7l1Y8DTGXkx/r4pwU1nMVAR+yD/QC+MCHKXAyEX/7JZhwcN1IED09aZ9vCjjkcGdhSQiu/eJqcXInpl6eEEEwg== -"@types/http-errors@*": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== - -"@types/http-proxy@^1.17.8": - version "1.17.14" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec" - integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== - dependencies: - "@types/node" "*" - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -1546,11 +1454,6 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - "@types/mocha@^10.0.0": version "10.0.0" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.0.tgz#3d9018c575f0e3f7386c1de80ee66cc21fbb7a52" @@ -1563,13 +1466,6 @@ dependencies: "@types/node" "*" -"@types/node-forge@^1.3.0": - version "1.3.11" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" - integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== - dependencies: - "@types/node" "*" - "@types/node@*", "@types/node@^20.11.16", "@types/node@^20.2.5": version "20.14.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.7.tgz#342cada27f97509eb8eb2dbc003edf21ce8ab5a8" @@ -1582,16 +1478,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/qs@*": - version "6.9.15" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" - integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - "@types/react-dom@^17.0.11": version "17.0.15" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.15.tgz#f2c8efde11521a4b7991e076cb9c70ba3bb0d156" @@ -1615,11 +1501,6 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/retry@0.12.2": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" - integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== - "@types/scheduler@*": version "0.16.2" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" @@ -1630,30 +1511,6 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== -"@types/send@*": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-index@^1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" - integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== - dependencies: - "@types/express" "*" - -"@types/serve-static@*", "@types/serve-static@^1.15.5": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" - integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "*" - "@types/sinon-chai@^3.2.8": version "3.2.8" resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.8.tgz#5871d09ab50d671d8e6dd72e9073f8e738ac61dc" @@ -1674,13 +1531,6 @@ resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== -"@types/sockjs@^0.3.36": - version "0.3.36" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" - integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== - dependencies: - "@types/node" "*" - "@types/statuses@^2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.4.tgz#041143ba4a918e8f080f8b0ffbe3d4cb514e2315" @@ -1703,13 +1553,6 @@ resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== -"@types/ws@^8.5.10": - version "8.5.10" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" - integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== - dependencies: - "@types/node" "*" - "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -2025,14 +1868,6 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - acorn-import-attributes@^1.9.5: version "1.9.5" resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" @@ -2048,12 +1883,12 @@ acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== +agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== dependencies: - debug "4" + debug "^4.3.4" ajv-formats@^2.1.1: version "2.1.1" @@ -2106,11 +1941,6 @@ ansi-escapes@^4.3.2: dependencies: type-fest "^0.21.3" -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -2179,11 +2009,6 @@ array-buffer-byte-length@^1.0.0: call-bind "^1.0.2" is-array-buffer "^3.0.1" -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - array-includes@^3.1.5, array-includes@^3.1.6: version "3.1.6" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" @@ -2312,11 +2137,6 @@ balanced-match@^2.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - big-integer@^1.6.44: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -2327,32 +2147,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour-service@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" - integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw== - dependencies: - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -2419,23 +2213,6 @@ bundle-name@^3.0.0: dependencies: run-applescript "^5.0.0" -bundle-name@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" - integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== - dependencies: - run-applescript "^7.0.0" - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - call-bind@^1.0.2, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -2617,7 +2394,7 @@ colord@^2.9.3: resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== -colorette@^2.0.10, colorette@^2.0.14: +colorette@^2.0.14: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== @@ -2654,26 +2431,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2694,38 +2451,11 @@ concurrently@^8.2.2: tree-kill "^1.2.2" yargs "^17.7.2" -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== - cookie@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" @@ -2748,11 +2478,6 @@ core-js@^3.21.1: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94" integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - cosmiconfig@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" @@ -2821,12 +2546,12 @@ csso@^5.0.5: dependencies: css-tree "~2.2.0" -cssstyle@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a" - integrity sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg== +cssstyle@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.1.0.tgz#161faee382af1bafadb6d3867a92a19bcb4aea70" + integrity sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA== dependencies: - rrweb-cssom "^0.6.0" + rrweb-cssom "^0.7.1" csstype@^3.0.2: version "3.0.2" @@ -2838,14 +2563,13 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== -data-urls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-4.0.0.tgz#333a454eca6f9a5b7b0f1013ff89074c3f522dd4" - integrity sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g== +data-urls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" + integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== dependencies: - abab "^2.0.6" - whatwg-mimetype "^3.0.0" - whatwg-url "^12.0.0" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" date-fns@^2.30.0: version "2.30.0" @@ -2854,13 +2578,6 @@ date-fns@^2.30.0: dependencies: "@babel/runtime" "^7.21.0" -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" @@ -2917,11 +2634,6 @@ default-browser-id@^3.0.0: bplist-parser "^0.2.0" untildify "^4.0.0" -default-browser-id@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" - integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== - default-browser@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" @@ -2932,21 +2644,6 @@ default-browser@^4.0.0: execa "^7.1.1" titleize "^3.0.0" -default-browser@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" - integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== - dependencies: - bundle-name "^4.1.0" - default-browser-id "^5.0.0" - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -2974,31 +2671,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" @@ -3021,13 +2698,6 @@ dirty-chai@^2.0.1: resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3" integrity sha512-ys79pWKvDMowIDEPC6Fig8d5THiC0DJ2gmTeGzVAoEH18J8OzLud0Jh7I9IWg3NSk8x2UocznUuFmfHCXYZx9w== -dns-packet@^5.2.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" - integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -3061,13 +2731,6 @@ domelementtype@^2.3.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domexception@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" - integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== - dependencies: - webidl-conversions "^7.0.0" - domhandler@^5.0.2, domhandler@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" @@ -3089,11 +2752,6 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - electron-to-chromium@^1.4.668: version "1.4.724" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.724.tgz#e0a86fe4d3d0e05a4d7b032549d79608078f830d" @@ -3114,11 +2772,6 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - enhanced-resolve@^5.17.1: version "5.17.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" @@ -3236,11 +2889,6 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -3471,16 +3119,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3516,43 +3154,6 @@ execa@^7.1.1: signal-exit "^3.0.7" strip-final-newline "^3.0.0" -express@^4.17.3: - version "4.19.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.2" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.6.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -3596,13 +3197,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -3631,19 +3225,6 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - find-cache-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -3719,11 +3300,6 @@ focus-trap@^6.7.1: dependencies: tabbable "^5.2.1" -follow-redirects@^1.0.0: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== - for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -3748,21 +3324,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - foundation-emails@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/foundation-emails/-/foundation-emails-2.3.1.tgz#80d77707d825966cbbe8111ddb4c790976555ff9" integrity sha512-omqS9jEaM4mehUMPDGnp9BmzZ+vWBpkt/cgvTHCNu1w5JWtvaBQop+Ds7c6Gmt77hUIvmo78Oi5N4u2x7K0Utw== -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -3950,7 +3516,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -3965,11 +3531,6 @@ graphql@^16.8.1: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -4031,99 +3592,32 @@ headers-polyfill@^4.0.2: resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.2.tgz#9115a76eee3ce8fbf95b6e3c6bf82d936785b44a" integrity sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw== -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -html-encoding-sniffer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" - integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== +html-encoding-sniffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" + integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== dependencies: - whatwg-encoding "^2.0.0" - -html-entities@^2.4.0: - version "2.5.2" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" - integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== + whatwg-encoding "^3.1.1" html-tags@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== - dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" - -http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== +http-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" + agent-base "^7.1.0" + debug "^4.3.4" -https-proxy-agent@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== +https-proxy-agent@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== dependencies: - agent-base "6" + agent-base "^7.0.2" debug "4" human-signals@^2.1.0: @@ -4136,18 +3630,6 @@ human-signals@^4.3.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== -hyperdyperid@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" - integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -4194,16 +3676,11 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - ini@^1.3.5: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" @@ -4228,16 +3705,6 @@ intl-tel-input@^24.5.0: resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-24.5.0.tgz#1a7589f046b72a97f8cd30ebae4c0615635f542d" integrity sha512-utSW+7a2JpUFdRRwo6CAiEQuAMwWxnCurPr2VDkfnW7W9g9ZprvPJPj00vxkiGV8h3GMRAD7aUtX9+oYwD/8OQ== -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" - integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== - is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" @@ -4332,11 +3799,6 @@ is-negative-zero@^2.0.2: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== -is-network-error@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997" - integrity sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g== - is-node-process@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134" @@ -4364,11 +3826,6 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -4455,23 +3912,11 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -is-wsl@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" - integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== - dependencies: - is-inside-container "^1.0.0" - isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -4512,34 +3957,32 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsdom@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8" - integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw== +jsdom@^25.0.1: + version "25.0.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" + integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw== dependencies: - abab "^2.0.6" - cssstyle "^3.0.0" - data-urls "^4.0.0" + cssstyle "^4.1.0" + data-urls "^5.0.0" decimal.js "^10.4.3" - domexception "^4.0.0" form-data "^4.0.0" - html-encoding-sniffer "^3.0.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.1" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.5" is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.4" + nwsapi "^2.2.12" parse5 "^7.1.2" - rrweb-cssom "^0.6.0" + rrweb-cssom "^0.7.1" saxes "^6.0.0" symbol-tree "^3.2.4" - tough-cookie "^4.1.2" - w3c-xmlserializer "^4.0.0" + tough-cookie "^5.0.0" + w3c-xmlserializer "^5.0.0" webidl-conversions "^7.0.0" - whatwg-encoding "^2.0.0" - whatwg-mimetype "^3.0.0" - whatwg-url "^12.0.1" - ws "^8.13.0" - xml-name-validator "^4.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + ws "^8.18.0" + xml-name-validator "^5.0.0" jsesc@^2.5.1: version "2.5.2" @@ -4635,14 +4078,6 @@ language-tags@^1.0.5: dependencies: language-subtag-registry "~0.3.2" -launch-editor@^2.6.1: - version "2.8.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.8.0.tgz#7255d90bdba414448e2138faa770a74f28451305" - integrity sha512-vJranOAJrI/llyWGRQqiDM+adrw+k83fvmmx3+nV47g3+36xM15jE+zyZ6Ffel02+xSvuM0b2GDRosXZkbb6wA== - dependencies: - picocolors "^1.0.0" - shell-quote "^1.8.1" - levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -4861,31 +4296,11 @@ mdn-data@2.0.30: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^4.6.0: - version "4.9.3" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.9.3.tgz#41a3218065fe3911d9eba836250c8f4e43f816bc" - integrity sha512-bsYSSnirtYTWi1+OPMFb0M048evMKyUYe0EbtuGQgq6BVQM1g1W8/KIUJCCvjgI/El0j6Q4WsmMiBwLUBSw8LA== - dependencies: - "@jsonjoy.com/json-pack" "^1.0.3" - "@jsonjoy.com/util" "^1.1.2" - tree-dump "^1.0.1" - tslib "^2.0.0" - meow@^13.1.0: version "13.2.0" resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f" integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -4896,12 +4311,7 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -4909,23 +4319,18 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": +mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -4936,11 +4341,6 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - minimatch@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" @@ -5010,11 +4410,6 @@ mq-polyfill@^1.1.8: resolved "https://registry.yarnpkg.com/mq-polyfill/-/mq-polyfill-1.1.8.tgz#c144190b21214bf8d8b099e7e34e6ca2b888dc14" integrity sha1-wUQZCyEhS/jYsJnn405soriI3BQ= -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -5048,14 +4443,6 @@ msw@^2.2.1: type-fest "^4.9.0" yargs "^17.7.2" -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - mute-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" @@ -5071,11 +4458,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" @@ -5092,11 +4474,6 @@ nise@^5.1.1: just-extend "^4.0.2" path-to-regexp "^1.7.0" -node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - node-modules-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" @@ -5133,10 +4510,10 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -nwsapi@^2.2.4: - version "2.2.10" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.10.tgz#0b77a68e21a0b483db70b11fad055906e867cda8" - integrity sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ== +nwsapi@^2.2.12: + version "2.2.13" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.13.tgz#e56b4e98960e7a040e5474536587e599c4ff4655" + integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ== object-assign@4.1.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" @@ -5198,23 +4575,6 @@ object.values@^1.1.5, object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1, on-finished@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -5236,16 +4596,6 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" -open@^10.0.3: - version "10.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-10.1.0.tgz#a7795e6e5d519abe4286d9937bb24b51122598e1" - integrity sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw== - dependencies: - default-browser "^5.2.1" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^3.1.0" - open@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" @@ -5308,15 +4658,6 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-retry@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.0.tgz#8d6df01af298750009691ce2f9b3ad2d5968f3bd" - integrity sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA== - dependencies: - "@types/retry" "0.12.2" - is-network-error "^1.0.0" - retry "^0.13.1" - p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -5351,11 +4692,6 @@ parse5@^7.1.2: dependencies: entities "^4.4.0" -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -5394,11 +4730,6 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - path-to-regexp@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" @@ -5535,11 +4866,6 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -5549,36 +4875,11 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -psl@^1.1.33: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== - -punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: +punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -5604,21 +4905,6 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -5664,28 +4950,6 @@ react@^17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" -readable-stream@^2.0.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -5782,11 +5046,6 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -5826,11 +5085,6 @@ resolve@^2.0.0-next.3: is-core-module "^2.2.0" path-parse "^1.0.6" -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -5850,10 +5104,10 @@ rimraf@^5.0.5: dependencies: glob "^10.3.7" -rrweb-cssom@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" - integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== +rrweb-cssom@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" + integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== run-applescript@^5.0.0: version "5.0.0" @@ -5862,11 +5116,6 @@ run-applescript@^5.0.0: dependencies: execa "^5.0.0" -run-applescript@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" - integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== - run-async@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad" @@ -5886,12 +5135,7 @@ rxjs@^7.4.0, rxjs@^7.8.1: dependencies: tslib "^2.1.0" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -5905,7 +5149,7 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -6043,7 +5287,7 @@ schema-utils@^3.1.1, schema-utils@^3.2.0, schema-utils@^3.3.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0, schema-utils@^4.2.0: +schema-utils@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== @@ -6053,19 +5297,6 @@ schema-utils@^4.0.0, schema-utils@^4.2.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" - integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== - dependencies: - "@types/node-forge" "^1.3.0" - node-forge "^1" - semver@^5.6.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" @@ -6081,25 +5312,6 @@ semver@^7.3.7, semver@^7.5.0, semver@^7.5.4: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -6114,29 +5326,6 @@ serialize-javascript@^6.0.1: dependencies: randombytes "^2.1.0" -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - set-function-length@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -6149,16 +5338,6 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -6244,15 +5423,6 @@ smartquotes@^2.3.2: resolved "https://registry.yarnpkg.com/smartquotes/-/smartquotes-2.3.2.tgz#fb1630c49ba04e57446e1a97dc10d590072af4a6" integrity sha512-0R6YJ5hLpDH4mZR7N5eZ12oCMLspvGOHL9A9SEm2e3b/CQmQidekW4SWSKEmor/3x6m3NCBBEqLzikcZC9VJNQ== -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -6290,39 +5460,11 @@ spawn-command@0.0.2: resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -statuses@2.0.1, statuses@^2.0.1: +statuses@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - strict-event-emitter@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" @@ -6396,20 +5538,6 @@ string.prototype.trimstart@^1.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -6661,21 +5789,23 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -thingies@^1.20.0: - version "1.21.0" - resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" - integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - titleize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== +tldts-core@^6.1.59: + version "6.1.59" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.59.tgz#95d1076ed9ea36f81493be515ad9d3e916440126" + integrity sha512-EiYgNf275AQyVORl8HQYYe7rTVnmLb4hkWK7wAk/12Ksy5EiHpmUmTICa4GojookBPC8qkLMBKKwCmzNA47ZPQ== + +tldts@^6.1.32: + version "6.1.59" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.59.tgz#aa903f542a69429bcdf4bcd63f4f1fb4cf689312" + integrity sha512-472ilPxsRuqBBpn+KuRBHJvZhk6tTo4yTVsmODrLBNLwRYJPkDfMEHivgNwp5iEl+cbrZzzRtLKRxZs7+QKkRg== + dependencies: + tldts-core "^6.1.59" + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -6688,32 +5818,19 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -tough-cookie@^4.1.2: - version "4.1.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" - integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== +tough-cookie@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.0.0.tgz#6b6518e2b5c070cf742d872ee0f4f92d69eac1af" + integrity sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q== dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" + tldts "^6.1.32" -tr46@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" - integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== +tr46@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" + integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== dependencies: - punycode "^2.3.0" - -tree-dump@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.1.tgz#b448758da7495580e6b7830d6b7834fca4c45b96" - integrity sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA== + punycode "^2.3.1" tree-kill@^1.2.2: version "1.2.2" @@ -6740,7 +5857,7 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.0: +tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.0: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== @@ -6779,14 +5896,6 @@ type-fest@^4.9.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.10.2.tgz#3abdb144d93c5750432aac0d73d3e85fcab45738" integrity sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" @@ -6839,16 +5948,6 @@ unicode-property-aliases-ecmascript@^1.0.4: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== -universalify@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" - integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -6869,50 +5968,27 @@ uri-js@^4.2.2, uri-js@^4.4.1: dependencies: punycode "^2.1.0" -url-parse@^1.5.3: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - varint@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -w3c-xmlserializer@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" - integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== dependencies: - xml-name-validator "^4.0.0" + xml-name-validator "^5.0.0" watchpack@^2.4.1: version "2.4.1" @@ -6922,13 +5998,6 @@ watchpack@^2.4.1: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -6966,54 +6035,6 @@ webpack-cli@^5.1.4: rechoir "^0.8.0" webpack-merge "^5.7.3" -webpack-dev-middleware@^7.1.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz#2af00538b6e4eda05f5afdd5d711dbebc05958f7" - integrity sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA== - dependencies: - colorette "^2.0.10" - memfs "^4.6.0" - mime-types "^2.1.31" - on-finished "^2.4.1" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz#cb6ea47ff796b9251ec49a94f24a425e12e3c9b8" - integrity sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA== - dependencies: - "@types/bonjour" "^3.5.13" - "@types/connect-history-api-fallback" "^1.5.4" - "@types/express" "^4.17.21" - "@types/serve-index" "^1.9.4" - "@types/serve-static" "^1.15.5" - "@types/sockjs" "^0.3.36" - "@types/ws" "^8.5.10" - ansi-html-community "^0.0.8" - bonjour-service "^1.2.1" - chokidar "^3.6.0" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.4.0" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.1.0" - launch-editor "^2.6.1" - open "^10.0.3" - p-retry "^6.2.0" - rimraf "^5.0.5" - schema-utils "^4.2.0" - selfsigned "^2.4.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^7.1.0" - ws "^8.16.0" - webpack-merge@^5.7.3: version "5.8.0" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" @@ -7064,38 +6085,24 @@ webpack@^5.94.0: watchpack "^2.4.1" webpack-sources "^3.2.3" -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -whatwg-encoding@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" - integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== dependencies: iconv-lite "0.6.3" -whatwg-mimetype@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" - integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== -whatwg-url@^12.0.0, whatwg-url@^12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-12.0.1.tgz#fd7bcc71192e7c3a2a97b9a8d6b094853ed8773c" - integrity sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ== +whatwg-url@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" + integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== dependencies: - tr46 "^4.1.1" + tr46 "^5.0.0" webidl-conversions "^7.0.0" which-boxed-primitive@^1.0.2: @@ -7194,15 +6201,15 @@ write-file-atomic@^5.0.1: imurmurhash "^0.1.4" signal-exit "^4.0.1" -ws@^8.13.0, ws@^8.16.0: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" - integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== +ws@^8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== -xml-name-validator@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" - integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== xmlchars@^2.2.0: version "2.2.0"