diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b31986a963..6cbf62c3ffa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,6 +9,7 @@ variables: JUNIT_OUTPUT: 'true' ECR_REGISTRY: '${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com' IDP_CI_SHA: 'sha256:6915b54a913ebcb9066fdfaa88c3d42bda1f4505cfb59b9d5848576705954621' + PKI_IMAGE_TAG: 'main' default: image: '${ECR_REGISTRY}/idp/ci@${IDP_CI_SHA}' @@ -95,8 +96,12 @@ build-idp-image: stage: review needs: [] interruptible: true + variables: + BRANCH_TAGGING_STRING: "" rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + variables: + BRANCH_TAGGING_STRING: "--destination ${ECR_REGISTRY}/identity-idp/review:${$CI_DEFAULT_BRANCH}" - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - if: $CI_PIPELINE_SOURCE != "merge_request_event" when: never @@ -119,6 +124,7 @@ build-idp-image: --context "${CI_PROJECT_DIR}" --dockerfile "${CI_PROJECT_DIR}/Dockerfile" --destination "${ECR_REGISTRY}/identity-idp/review:${CI_COMMIT_SHA}" + ${BRANCH_TAGGING_STRING} --cache-repo="${ECR_REGISTRY}/identity-idp/review/cache" --cache-ttl=168h --cache=true @@ -312,25 +318,27 @@ review-app: [ {"name": "POSTGRES_SSLMODE", "value": "prefer"}, {"name": "POSTGRES_NAME", "value": "idp"}, - {"name": "POSTGRES_HOST","value": "$CI_ENVIRONMENT_SLUG-identity-idp-chart-postgres.review-apps"}, + {"name": "POSTGRES_HOST","value": "$CI_ENVIRONMENT_SLUG-login-chart-pg.review-apps"}, {"name": "POSTGRES_USERNAME", "value": "postgres"}, {"name": "POSTGRES_PASSWORD", "value": "postgres"}, {"name": "POSTGRES_WORKER_SSLMODE", "value": "prefer"}, {"name": "POSTGRES_WORKER_NAME", "value": "idp-worker-jobs"}, - {"name": "POSTGRES_WORKER_HOST", "value": "$CI_ENVIRONMENT_SLUG-identity-idp-chart-postgres.review-apps"}, + {"name": "POSTGRES_WORKER_HOST", "value": "$CI_ENVIRONMENT_SLUG-login-chart-pg.review-apps"}, {"name": "POSTGRES_WORKER_USERNAME", "value": "postgres"}, {"name": "POSTGRES_WORKER_PASSWORD", "value": "postgres"}, {"name": "RAILS_OFFLINE", "value": "true"}, - {"name": "REDIS_IRS_ATTEMPTS_API_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-identity-idp-chart-redis.review-apps:6379/2"}, - {"name": "REDIS_THROTTLE_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-identity-idp-chart-redis.review-apps:6379/1"}, - {"name": "REDIS_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-identity-idp-chart-redis.review-apps:6379"}, + {"name": "REDIS_IRS_ATTEMPTS_API_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379/2"}, + {"name": "REDIS_THROTTLE_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379/1"}, + {"name": "REDIS_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379"}, {"name": "ASSET_HOST", "value": "https://$CI_ENVIRONMENT_SLUG.review-app.identitysandbox.gov"}, {"name": "DOMAIN_NAME", "value": "$CI_ENVIRONMENT_SLUG.review-app.identitysandbox.gov"}, {"name": "LOGIN_DATACENTER", "value": "true" }, {"name": "LOGIN_DOMAIN", "value": "identitysandbox.gov"}, {"name": "LOGIN_ENV", "value": "$CI_ENVIRONMENT_SLUG" }, {"name": "LOGIN_HOST_ROLE", "value": "idp" }, - {"name": "LOGIN_SKIP_REMOTE_CONFIG", "value": "true" } + {"name": "LOGIN_SKIP_REMOTE_CONFIG", "value": "true" }, + {"name": "PIV_CAC_SERVICE_URL", "value": "https://$CI_ENVIRONMENT_SLUG-review-app.pivcac.identitysandbox.gov/"}, + {"name": "PIV_CAC_VERIFY_TOKEN_URL", "value": "https://$CI_ENVIRONMENT_SLUG-review-app.pivcac.identitysandbox.gov/"} ] EOF ) @@ -339,28 +347,45 @@ review-app: [ {"name": "POSTGRES_SSLMODE", "value": "prefer"}, {"name": "POSTGRES_NAME", "value": "idp"}, - {"name": "POSTGRES_HOST", "value": "$CI_ENVIRONMENT_SLUG-identity-idp-chart-postgres.review-apps"}, + {"name": "POSTGRES_HOST", "value": "$CI_ENVIRONMENT_SLUG-login-chart-pg.review-apps"}, {"name": "POSTGRES_USERNAME", "value": "postgres"}, {"name": "POSTGRES_PASSWORD", "value": "postgres"}, {"name": "POSTGRES_WORKER_SSLMODE", "value": "prefer"}, {"name": "POSTGRES_WORKER_NAME", "value": "idp-worker-jobs"}, - {"name": "POSTGRES_WORKER_HOST", "value": "$CI_ENVIRONMENT_SLUG-identity-idp-chart-postgres.review-apps"}, + {"name": "POSTGRES_WORKER_HOST", "value": "$CI_ENVIRONMENT_SLUG-login-chart-pg.review-apps"}, {"name": "POSTGRES_WORKER_USERNAME", "value": "postgres"}, {"name": "POSTGRES_WORKER_PASSWORD", "value": "postgres"}, {"name": "RAILS_OFFLINE", "value": "true"}, - {"name": "REDIS_IRS_ATTEMPTS_API_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-identity-idp-chart-redis.review-apps:6379/2"}, - {"name": "REDIS_THROTTLE_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-identity-idp-chart-redis.review-apps:6379/1"}, - {"name": "REDIS_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-identity-idp-chart-redis.review-apps:6379"}, + {"name": "REDIS_IRS_ATTEMPTS_API_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379/2"}, + {"name": "REDIS_THROTTLE_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379/1"}, + {"name": "REDIS_URL", "value": "redis://$CI_ENVIRONMENT_SLUG-login-chart-redis.review-apps:6379"}, {"name": "ASSET_HOST", "value": "https://$CI_ENVIRONMENT_SLUG.review-app.identitysandbox.gov"}, {"name": "DOMAIN_NAME", "value": "$CI_ENVIRONMENT_SLUG.review-app.identitysandbox.gov"}, {"name": "LOGIN_DATACENTER", "value": "true" }, {"name": "LOGIN_DOMAIN", "value": "identitysandbox.gov"}, {"name": "LOGIN_ENV", "value": "$CI_ENVIRONMENT_SLUG" }, {"name": "LOGIN_HOST_ROLE", "value": "worker" }, - {"name": "LOGIN_SKIP_REMOTE_CONFIG", "value": "true" } + {"name": "LOGIN_SKIP_REMOTE_CONFIG", "value": "true" }, + {"name": "PIV_CAC_SERVICE_URL", "value": "https://$CI_ENVIRONMENT_SLUG-review-app.pivcac.identitysandbox.gov/"}, + {"name": "PIV_CAC_VERIFY_TOKEN_URL", "value": "https://$CI_ENVIRONMENT_SLUG-review-app.pivcac.identitysandbox.gov/"} + ] + EOF + ) + - |- + export PIVCAC_ENV=$(cat <- helm upgrade --install --namespace review-apps --debug @@ -368,14 +393,21 @@ review-app: --set idp.image.tag="${CI_COMMIT_SHA}" --set worker.image.repository="${ECR_REGISTRY}/identity-idp/review" --set worker.image.tag="${CI_COMMIT_SHA}" - --set idp.ingress.enabled=true + --set pivcac.image.repository="${ECR_REGISTRY}/identity-pivcac/review" + --set pivcac.image.tag="${PKI_IMAGE_TAG}" --set-json idp.env="$IDP_ENV" --set-json worker.env="$WORKER_ENV" + --set-json pivcac.env="$PIVCAC_ENV" --set-json idp.ingress.hosts="[{\"host\": \"$CI_ENVIRONMENT_SLUG.review-app.identitysandbox.gov\", \"paths\": [{\"path\": \"/\", \"pathType\": \"Prefix\"}]}]" - $CI_ENVIRONMENT_SLUG ./charts + --set-json pivcac.ingress.hosts="[{\"host\": \"$CI_ENVIRONMENT_SLUG-review-app.pivcac.identitysandbox.gov\", \"paths\": [{\"path\": \"/\", \"pathType\": \"Prefix\"}]}]" + $CI_ENVIRONMENT_SLUG ./identity-idp-helm-chart - echo "DNS may take a while to propagate, so be patient if it doesn't show up right away" - echo "To access the rails console, first run 'aws-vault exec sandbox-power -- aws eks update-kubeconfig --name review_app'" - - echo "Then run aws-vault exec sandbox-power -- kubectl exec -it service/$CI_ENVIRONMENT_SLUG-identity-idp-chart-idp -n review-apps -- /app/bin/rails console" + - echo "Then run aws-vault exec sandbox-power -- kubectl exec -it service/$CI_ENVIRONMENT_SLUG-login-chart-idp -n review-apps -- /app/bin/rails console" + - echo "Address of IDP review app:" + - echo https://$CI_ENVIRONMENT_SLUG.review-app.identitysandbox.gov + - echo "Address of PIVCAC review app:" + - echo https://$CI_ENVIRONMENT_SLUG-review-app.pivcac.identitysandbox.gov environment: name: review/$CI_COMMIT_REF_NAME url: https://$CI_ENVIRONMENT_SLUG.review-app.identitysandbox.gov @@ -548,4 +580,4 @@ ecr-scan: paths: - gl-container-scanning-report.json reports: - container_scanning: gl-container-scanning-report.json \ No newline at end of file + container_scanning: gl-container-scanning-report.json diff --git a/Dockerfile b/Dockerfile index 7d23137ef17..a9ad34bf643 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,8 @@ ENV REDIS_THROTTLE_URL redis://redis:6379/1 ENV REDIS_URL redis://redis:6379 ENV ASSET_HOST http://localhost:3000 ENV DOMAIN_NAME localhost:3000 +ENV PIV_CAC_SERVICE_URL https://localhost:8443/ +ENV PIV_CAC_VERIFY_TOKEN_URL https://localhost:8443/ # Prevent documentation installation RUN echo 'path-exclude=/usr/share/doc/*' > /etc/dpkg/dpkg.cfg.d/00_nodoc && \ diff --git a/Gemfile b/Gemfile index 02a4f9018f7..b6defce7400 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,7 @@ gem 'rails', '~> 7.0.0' gem 'activerecord-postgis-adapter' gem 'ahoy_matey', '~> 3.0' gem 'aws-sdk-kms', '~> 1.4' +gem 'aws-sdk-cloudwatchlogs', require: false gem 'aws-sdk-pinpoint' gem 'aws-sdk-pinpointsmsvoice' gem 'aws-sdk-ses', '~> 1.6' @@ -17,6 +18,7 @@ gem 'barby', '~> 0.6.8' gem 'base32-crockford' gem 'bootsnap', '~> 1.0', require: false gem 'browser' +gem 'concurrent-ruby' gem 'connection_pool' gem 'cssbundling-rails' gem 'devise', '~> 4.8' @@ -92,7 +94,6 @@ group :development do end group :development, :test do - gem 'aws-sdk-cloudwatchlogs', require: false gem 'brakeman', require: false gem 'bullet', '~> 7.0' gem 'capybara-webmock', git: 'https://github.com/hashrocket/capybara-webmock.git', ref: 'd3f3b7c' diff --git a/Gemfile.lock b/Gemfile.lock index cc016fe2adb..7d2825e26d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -734,6 +734,7 @@ DEPENDENCIES bullet (~> 7.0) bundler-audit capybara-webmock! + concurrent-ruby connection_pool cssbundling-rails derailed_benchmarks diff --git a/app/controllers/concerns/idv/document_capture_concern.rb b/app/controllers/concerns/idv/document_capture_concern.rb index ce853df084b..79f3b77a48b 100644 --- a/app/controllers/concerns/idv/document_capture_concern.rb +++ b/app/controllers/concerns/idv/document_capture_concern.rb @@ -23,7 +23,7 @@ def successful_response # copied from Flow::Failure module def failure(message, extra = nil) - flow_session[:error_message] = message if defined?(flow_session) + flash[:error] = message form_response_params = { success: false, errors: { message: message } } form_response_params[:extra] = extra unless extra.nil? FormResponse.new(**form_response_params) diff --git a/app/controllers/concerns/idv/phone_otp_rate_limitable.rb b/app/controllers/concerns/idv/phone_otp_rate_limitable.rb index 598d0f74ad8..791ea9b4580 100644 --- a/app/controllers/concerns/idv/phone_otp_rate_limitable.rb +++ b/app/controllers/concerns/idv/phone_otp_rate_limitable.rb @@ -3,7 +3,6 @@ module PhoneOtpRateLimitable extend ActiveSupport::Concern included do - before_action :confirm_two_factor_authenticated before_action :handle_locked_out_user end diff --git a/app/controllers/concerns/idv/phone_otp_sendable.rb b/app/controllers/concerns/idv/phone_otp_sendable.rb index 8a657ed53d1..0d6c5c77b4f 100644 --- a/app/controllers/concerns/idv/phone_otp_sendable.rb +++ b/app/controllers/concerns/idv/phone_otp_sendable.rb @@ -3,7 +3,6 @@ module PhoneOtpSendable extend ActiveSupport::Concern included do - before_action :confirm_two_factor_authenticated before_action :handle_locked_out_user end diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index be6c37b50e2..752eb89c0fa 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -191,7 +191,7 @@ def async_state_done(current_async_state) state_id_number: pii[:state_id_number], # todo: add other edited fields? extra: { - address_edited: !!(idv_session.address_edited || flow_session['address_edited']), + address_edited: !!idv_session.address_edited, address_line2_present: !pii[:address2].blank?, pii_like_keypaths: [[:errors, :ssn], [:response_body, :first_name], [:same_address_as_id], diff --git a/app/controllers/idv/address_controller.rb b/app/controllers/idv/address_controller.rb index bfd31f9bec3..12d04526022 100644 --- a/app/controllers/idv/address_controller.rb +++ b/app/controllers/idv/address_controller.rb @@ -44,7 +44,6 @@ def profile_params def capture_address_edited(result) address_edited = result.to_h[:address_edited] - flow_session['address_edited'] = address_edited if address_edited idv_session.address_edited = true if address_edited end end diff --git a/app/controllers/idv/agreement_controller.rb b/app/controllers/idv/agreement_controller.rb index c244177a8f6..2e49b15dad3 100644 --- a/app/controllers/idv/agreement_controller.rb +++ b/app/controllers/idv/agreement_controller.rb @@ -13,8 +13,6 @@ def show 'agreement', :view, true ) - - render :show, locals: { flow_session: flow_session } end def update diff --git a/app/controllers/idv/getting_started_controller.rb b/app/controllers/idv/getting_started_controller.rb index f622b68aad5..f52905b1c30 100644 --- a/app/controllers/idv/getting_started_controller.rb +++ b/app/controllers/idv/getting_started_controller.rb @@ -15,8 +15,6 @@ def show @sp_name = decorated_session.sp_name || APP_NAME @title = t('doc_auth.headings.getting_started', sp_name: @sp_name) - - render :show, locals: { flow_session: flow_session } end def update diff --git a/app/controllers/idv/hybrid_handoff_controller.rb b/app/controllers/idv/hybrid_handoff_controller.rb index d75ec9427a8..83799c82e15 100644 --- a/app/controllers/idv/hybrid_handoff_controller.rb +++ b/app/controllers/idv/hybrid_handoff_controller.rb @@ -183,7 +183,7 @@ def rate_limited_failure # copied from Flow::Failure module def failure(message, extra = nil) - flow_session[:error_message] = message + flash[:error] = message form_response_params = { success: false, errors: { message: message } } form_response_params[:extra] = extra unless extra.nil? FormResponse.new(**form_response_params) diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index e7988c084a4..37f3fb6be1d 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -48,7 +48,7 @@ def invalid_state? end def prev_url - idv_in_person_proofing_ssn_url + idv_in_person_ssn_url end def pii diff --git a/app/controllers/idv/in_person_controller.rb b/app/controllers/idv/in_person_controller.rb index ab28eb607f5..21c191c1855 100644 --- a/app/controllers/idv/in_person_controller.rb +++ b/app/controllers/idv/in_person_controller.rb @@ -16,7 +16,7 @@ class InPersonController < ApplicationController FLOW_STATE_MACHINE_SETTINGS = { step_url: :idv_in_person_step_url, - final_url: :idv_in_person_proofing_ssn_url, + final_url: :idv_in_person_ssn_url, flow: Idv::Flows::InPersonFlow, analytics_id: 'In Person Proofing', }.freeze diff --git a/app/controllers/idv/otp_verification_controller.rb b/app/controllers/idv/otp_verification_controller.rb index 57f80b68968..9f690b6bb62 100644 --- a/app/controllers/idv/otp_verification_controller.rb +++ b/app/controllers/idv/otp_verification_controller.rb @@ -4,7 +4,7 @@ class OtpVerificationController < ApplicationController include StepIndicatorConcern include PhoneOtpRateLimitable - # confirm_two_factor_authenticated before action is in PhoneOtpRateLimitable + before_action :confirm_two_factor_authenticated before_action :confirm_step_needed before_action :confirm_otp_sent before_action :set_code diff --git a/app/controllers/idv/resend_otp_controller.rb b/app/controllers/idv/resend_otp_controller.rb index 917f12d3a2b..febd4b4a1ef 100644 --- a/app/controllers/idv/resend_otp_controller.rb +++ b/app/controllers/idv/resend_otp_controller.rb @@ -4,7 +4,7 @@ class ResendOtpController < ApplicationController include PhoneOtpRateLimitable include PhoneOtpSendable - # confirm_two_factor_authenticated before action is in PhoneOtpRateLimitable + before_action :confirm_two_factor_authenticated before_action :confirm_user_phone_confirmation_needed before_action :confirm_user_phone_confirmation_session_started diff --git a/app/controllers/service_provider_controller.rb b/app/controllers/service_provider_controller.rb index 1b4c5a95456..cf96be3258f 100644 --- a/app/controllers/service_provider_controller.rb +++ b/app/controllers/service_provider_controller.rb @@ -3,8 +3,12 @@ class ServiceProviderController < ApplicationController def update authorize do - ServiceProviderUpdater.new.run(sp_params[:service_provider]) if - FeatureManagement.use_dashboard_service_providers? + if !FeatureManagement.use_dashboard_service_providers? + render json: { status: 'Service providers updater has not been enabled.' } + return + end + + ServiceProviderUpdater.new.run(sp_params['service_provider']) render json: { status: 'If the feature is enabled, service providers have been updated.' } end @@ -32,6 +36,15 @@ def authorization_token end def sp_params - params.permit(service_provider: {}) + if request.headers['Content-Type'] == 'gzip/json' + body = request.body.read + if body.present? + JSON.parse(Zlib.gunzip(body)) + else + {} + end + else + params.permit(service_provider: {}) + end end end diff --git a/app/controllers/users/backup_code_setup_controller.rb b/app/controllers/users/backup_code_setup_controller.rb index 94da6c649d9..da144db7e68 100644 --- a/app/controllers/users/backup_code_setup_controller.rb +++ b/app/controllers/users/backup_code_setup_controller.rb @@ -22,7 +22,7 @@ def index def create generate_codes result = BackupCodeSetupForm.new(current_user).submit - analytics_properties = result.to_h + analytics_properties = result.to_h.merge(analytics_properties_for_visit) analytics.backup_code_setup_visit(**analytics_properties) irs_attempts_api_tracker.mfa_enroll_backup_code(success: result.success?) @@ -65,7 +65,7 @@ def confirm_backup_codes; end private def analytics_properties_for_visit - ParseControllerFromReferer.new(request.referer).call + { in_multi_mfa_selection_flow: in_multi_mfa_selection_flow? } end def track_backup_codes_created @@ -82,6 +82,7 @@ def mfa_user def track_backup_codes_confirmation_setup_visit analytics.multi_factor_auth_enter_backup_code_confirmation_visit( enabled_mfa_methods_count: mfa_user.enabled_mfa_methods_count, + in_multi_mfa_selection_flow: in_multi_mfa_selection_flow?, ) end diff --git a/app/controllers/users/mfa_selection_controller.rb b/app/controllers/users/mfa_selection_controller.rb deleted file mode 100644 index 0fc7013d2e5..00000000000 --- a/app/controllers/users/mfa_selection_controller.rb +++ /dev/null @@ -1,71 +0,0 @@ -module Users - class MfaSelectionController < ApplicationController - include UserAuthenticator - include SecureHeadersConcern - include MfaSetupConcern - - before_action :authenticate_user - before_action :confirm_user_authenticated_for_2fa_setup - - def index - two_factor_options_form - @after_setup_path = after_mfa_setup_path - @presenter = two_factor_options_presenter - analytics.user_registration_2fa_additional_setup_visit - end - - def update - result = submit_form - analytics_hash = result.to_h - analytics.user_registration_2fa_additional_setup(**analytics_hash) - - if result.success? - process_valid_form - else - flash[:error] = result.first_error_message - redirect_to second_mfa_setup_path - end - end - - # @api private - def two_factor_options_form - @two_factor_options_form ||= TwoFactorOptionsForm.new( - user: current_user, - phishing_resistant_required: service_provider_mfa_policy.phishing_resistant_required?, - piv_cac_required: service_provider_mfa_policy.piv_cac_required?, - ) - end - - private - - def submit_form - two_factor_options_form.submit(two_factor_options_form_params) - end - - def two_factor_options_presenter - TwoFactorOptionsPresenter.new( - user_agent: request.user_agent, - user: current_user, - phishing_resistant_required: service_provider_mfa_policy.phishing_resistant_required?, - piv_cac_required: service_provider_mfa_policy.piv_cac_required?, - show_skip_additional_mfa_link: show_skip_additional_mfa_link?, - ) - end - - def process_valid_form - user_session[:mfa_selections] = @two_factor_options_form.selection - - if user_session[:mfa_selections].first.present? - redirect_to confirmation_path(user_session[:mfa_selections].first) - else - redirect_to after_mfa_setup_path - end - end - - def two_factor_options_form_params - params.require(:two_factor_options_form).permit(:selection, selection: []) - rescue ActionController::ParameterMissing - ActionController::Parameters.new(selection: []) - end - end -end diff --git a/app/controllers/users/phone_setup_controller.rb b/app/controllers/users/phone_setup_controller.rb index 6344fd8e60b..579e68457fd 100644 --- a/app/controllers/users/phone_setup_controller.rb +++ b/app/controllers/users/phone_setup_controller.rb @@ -35,7 +35,7 @@ def create if result.success? handle_create_success(@new_phone_form.phone) elsif recoverable_recaptcha_error?(result) - render :spam_protection, locals: { authentication_methods_setup_path: } + render :spam_protection else render :index end diff --git a/app/controllers/users/phones_controller.rb b/app/controllers/users/phones_controller.rb index d2813f92eec..b5bd2834ef8 100644 --- a/app/controllers/users/phones_controller.rb +++ b/app/controllers/users/phones_controller.rb @@ -11,6 +11,8 @@ class PhonesController < ApplicationController before_action :allow_csp_recaptcha_src, if: :recaptcha_enabled? before_action :confirm_recently_authenticated_2fa + helper_method :in_multi_mfa_selection_flow? + def add user_session[:phone_id] = nil @new_phone_form = NewPhoneForm.new(user: current_user, analytics: analytics) diff --git a/app/controllers/users/piv_cac_authentication_setup_controller.rb b/app/controllers/users/piv_cac_authentication_setup_controller.rb index 5599e4b57cd..f56bbc33e57 100644 --- a/app/controllers/users/piv_cac_authentication_setup_controller.rb +++ b/app/controllers/users/piv_cac_authentication_setup_controller.rb @@ -34,7 +34,7 @@ def error end def delete - analytics.user_registration_piv_cac_disabled + analytics.piv_cac_disabled remove_piv_cac clear_piv_cac_information create_user_event(:piv_cac_disabled) @@ -57,8 +57,9 @@ def submit_new_piv_cac def track_piv_cac_setup_visit mfa_user = MfaContext.new(current_user) - analytics.user_registration_piv_cac_setup_visit( + analytics.piv_cac_setup_visit( enabled_mfa_methods_count: mfa_user.enabled_mfa_methods_count, + in_multi_mfa_selection_flow: in_multi_mfa_selection_flow?, ) end @@ -131,6 +132,7 @@ def track_mfa_method_added mfa_user = MfaContext.new(current_user) analytics.multi_factor_auth_added_piv_cac( enabled_mfa_methods_count: mfa_user.enabled_mfa_methods_count, + in_multi_mfa_selection_flow: in_multi_mfa_selection_flow?, ) Funnel::Registration::AddMfa.call(current_user.id, 'piv_cac', analytics) end diff --git a/app/controllers/users/piv_cac_login_controller.rb b/app/controllers/users/piv_cac_login_controller.rb index ffb15acef79..98e88fe6a87 100644 --- a/app/controllers/users/piv_cac_login_controller.rb +++ b/app/controllers/users/piv_cac_login_controller.rb @@ -37,7 +37,7 @@ def error private def render_prompt - analytics.user_registration_piv_cac_setup_visit + analytics.piv_cac_setup_visit(in_multi_mfa_selection_flow: false) @presenter = PivCacAuthenticationLoginPresenter.new(piv_cac_login_form, url_options) render :new end diff --git a/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb b/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb index 25d91b27bed..abc1705c9e7 100644 --- a/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb +++ b/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb @@ -32,7 +32,7 @@ def decline private def render_prompt - analytics.user_registration_piv_cac_setup_visit + analytics.piv_cac_setup_visit(in_multi_mfa_selection_flow: false) render :prompt end diff --git a/app/controllers/users/totp_setup_controller.rb b/app/controllers/users/totp_setup_controller.rb index e4c5a499a7c..61dae61c13b 100644 --- a/app/controllers/users/totp_setup_controller.rb +++ b/app/controllers/users/totp_setup_controller.rb @@ -73,6 +73,7 @@ def track_event user_signed_up: MfaPolicy.new(current_user).two_factor_enabled?, totp_secret_present: new_totp_secret.present?, enabled_mfa_methods_count: mfa_user.enabled_mfa_methods_count, + in_multi_mfa_selection_flow: in_multi_mfa_selection_flow?, ) end @@ -96,6 +97,7 @@ def create_events mfa_user = MfaContext.new(current_user) analytics.multi_factor_auth_added_totp( enabled_mfa_methods_count: mfa_user.enabled_mfa_methods_count, + in_multi_mfa_selection_flow: in_multi_mfa_selection_flow?, ) Funnel::Registration::AddMfa.call(current_user.id, 'auth_app', analytics) end diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb index 41bb97bf43c..7925d98f223 100644 --- a/app/forms/idv/api_image_upload_form.rb +++ b/app/forms/idv/api_image_upload_form.rb @@ -32,7 +32,6 @@ def submit if client_response.success? doc_pii_response = validate_pii_from_doc(client_response) - rate_limiter.reset! end end @@ -114,7 +113,10 @@ def validate_pii_from_doc(client_response) analytics.idv_doc_auth_submitted_pii_validation(**response.to_h) - store_pii(client_response) if client_response.success? && response.success? + if client_response.success? && response.success? + store_pii(client_response) + rate_limiter.reset! + end response end @@ -139,7 +141,7 @@ def extra_attributes Digest::SHA256.urlsafe_base64digest(back_image_bytes) end - @extra_attributes + @extra_attributes.merge!(getting_started_ab_test_analytics_bucket) end def remaining_attempts @@ -216,7 +218,9 @@ def document_capture_session_uuid def doc_auth_client @doc_auth_client ||= DocAuthRouter.client( vendor_discriminator: document_capture_session_uuid, - warn_notifier: proc { |attrs| analytics&.doc_auth_warning(**attrs) }, + warn_notifier: proc do |attrs| + analytics&.doc_auth_warning(**attrs.merge(getting_started_ab_test_analytics_bucket)) + end, ) end @@ -248,7 +252,8 @@ def update_analytics(client_response:, vendor_request_time_in_ms:) async: false, flow_path: params[:flow_path], vendor_request_time_in_ms: vendor_request_time_in_ms, - ).merge(acuant_sdk_upgrade_ab_test_data), + ).merge(acuant_sdk_upgrade_ab_test_data). + merge(getting_started_ab_test_analytics_bucket), ) end @@ -279,6 +284,13 @@ def acuant_sdk_upgrade_ab_test_data } end + def getting_started_ab_test_analytics_bucket + { + getting_started_ab_test_bucket: + AbTests::IDV_GETTING_STARTED.bucket(user_uuid), + } + end + def acuant_sdk_capture? image_metadata.dig(:front, :source) == Idp::Constants::Vendors::ACUANT && image_metadata.dig(:back, :source) == Idp::Constants::Vendors::ACUANT diff --git a/app/forms/idv/doc_pii_form.rb b/app/forms/idv/doc_pii_form.rb index ed8179fa2df..2c340884b65 100644 --- a/app/forms/idv/doc_pii_form.rb +++ b/app/forms/idv/doc_pii_form.rb @@ -4,7 +4,7 @@ class DocPiiForm validate :validate_pii - attr_reader :first_name, :last_name, :dob, :state, :zipcode, :attention_with_barcode, + attr_reader :first_name, :last_name, :dob, :address1, :state, :zipcode, :attention_with_barcode, :jurisdiction alias_method :attention_with_barcode?, :attention_with_barcode @@ -13,6 +13,7 @@ def initialize(pii:, attention_with_barcode: false) @first_name = pii[:first_name] @last_name = pii[:last_name] @dob = pii[:dob] + @address1 = pii[:address1] @state = pii[:state] @zipcode = pii[:zipcode] @jurisdiction = pii[:state_id_jurisdiction] @@ -45,6 +46,8 @@ def validate_pii errors.add(:pii, dob_error, type: :dob_error) elsif !dob_meets_min_age? errors.add(:pii, dob_min_age_error, type: :dob_min_age_error) + elsif address1.blank? + errors.add(:pii, address_error, type: :address_error) elsif !state_valid? errors.add(:pii, generic_error, type: :generic_error) elsif !zipcode_valid? @@ -101,5 +104,9 @@ def dob_error def dob_min_age_error I18n.t('doc_auth.errors.pii.birth_date_min_age') end + + def address_error + I18n.t('doc_auth.errors.alerts.address_check') + end end end diff --git a/app/javascript/packages/address-search/index-spec.tsx b/app/javascript/packages/address-search/components/address-input.spec.tsx similarity index 95% rename from app/javascript/packages/address-search/index-spec.tsx rename to app/javascript/packages/address-search/components/address-input.spec.tsx index 73600472e1f..a46ad448622 100644 --- a/app/javascript/packages/address-search/index-spec.tsx +++ b/app/javascript/packages/address-search/components/address-input.spec.tsx @@ -5,7 +5,7 @@ import { setupServer } from 'msw/node'; import { rest } from 'msw'; import type { SetupServer } from 'msw/node'; import { SWRConfig } from 'swr'; -import AddressSearch from '.'; +import AddressInput from './address-input'; const DEFAULT_RESPONSE = [ { @@ -24,7 +24,7 @@ const DEFAULT_RESPONSE = [ const LOCATIONS_URL = 'https://login.gov/api/locations'; const ADDRESSES_URL = 'https://login.gov/api/addresses'; -describe('AddressSearch', () => { +describe('AddressInput', () => { const sandbox = useSandbox(); context('when an address is found', () => { let server: SetupServer; @@ -45,7 +45,7 @@ describe('AddressSearch', () => { const handleLocationsFound = sandbox.stub(); const { findByText, findByLabelText } = render( new Map() }}> - undefined, + onFoundAddress = () => undefined, + onFoundLocations = () => undefined, + onLoadingLocations = () => undefined, + onError = () => undefined, + disabled = false, + addressSearchURL, + locationsURL, +}: AddressInputProps) { + const spinnerButtonRef = useRef(null); + const [textInput, setTextInput] = useState(''); + const { + locationResults, + uspsError, + addressError, + isLoading, + handleAddressSearch: onSearch, + foundAddress, + validatedFieldRef, + } = useUspsLocations({ locationsURL, addressSearchURL }); + + const onTextInputChange = (event: React.ChangeEvent) => { + const { target } = event; + setTextInput(target.value); + }; + + useEffect(() => { + spinnerButtonRef.current?.toggleSpinner(isLoading); + onLoadingLocations(isLoading); + }, [isLoading]); + + useEffect(() => { + addressError && onError(addressError); + uspsError && onError(uspsError); + }, [uspsError, addressError]); + + useDidUpdateEffect(() => { + onFoundLocations(locationResults); + + foundAddress && onFoundAddress(foundAddress); + }, [locationResults]); + + const handleSearch = useCallback( + (event) => { + onError(null); + onSearch(event, textInput); + }, + [textInput], + ); + + return ( + <> + + + +
+ + {t('in_person_proofing.body.location.po_search.search_button')} + +
+ + ); +} + +export default AddressInput; diff --git a/app/javascript/packages/address-search/components/address-search.tsx b/app/javascript/packages/address-search/components/address-search.tsx new file mode 100644 index 00000000000..dcf6232319d --- /dev/null +++ b/app/javascript/packages/address-search/components/address-search.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; +import { Alert, PageHeading } from '@18f/identity-components'; +import { useI18n } from '@18f/identity-react-i18n'; +import InPersonLocations from './in-person-locations'; +import AddressInput from './address-input'; +import type { LocationQuery, FormattedLocation } from '../types'; + +function AddressSearch({ + registerField, + locationsURL, + addressSearchURL, + handleLocationSelect, + disabled, + onFoundLocations, +}) { + const [apiError, setApiError] = useState(null); + const [foundAddress, setFoundAddress] = useState(null); + const [locationResults, setLocationResults] = useState( + null, + ); + const [isLoadingLocations, setLoadingLocations] = useState(false); + const { t } = useI18n(); + + return ( + <> + {apiError && ( + + {t('idv.failure.exceptions.post_office_search_error')} + + )} + {t('in_person_proofing.headings.po_search.location')} +

{t('in_person_proofing.body.location.po_search.po_search_about')}

+ { + setLocationResults(locations); + onFoundLocations(locations); + }} + onLoadingLocations={setLoadingLocations} + onError={setApiError} + disabled={disabled} + locationsURL={locationsURL} + addressSearchURL={addressSearchURL} + /> + {locationResults && foundAddress && !isLoadingLocations && ( + + )} + + ); +} + +export default AddressSearch; diff --git a/app/javascript/packages/document-capture/components/in-person-locations.tsx b/app/javascript/packages/address-search/components/in-person-locations.tsx similarity index 100% rename from app/javascript/packages/document-capture/components/in-person-locations.tsx rename to app/javascript/packages/address-search/components/in-person-locations.tsx diff --git a/app/javascript/packages/document-capture/components/location-collection-item.spec.tsx b/app/javascript/packages/address-search/components/location-collection-item.spec.tsx similarity index 100% rename from app/javascript/packages/document-capture/components/location-collection-item.spec.tsx rename to app/javascript/packages/address-search/components/location-collection-item.spec.tsx diff --git a/app/javascript/packages/document-capture/components/location-collection-item.tsx b/app/javascript/packages/address-search/components/location-collection-item.tsx similarity index 100% rename from app/javascript/packages/document-capture/components/location-collection-item.tsx rename to app/javascript/packages/address-search/components/location-collection-item.tsx diff --git a/app/javascript/packages/document-capture/components/location-collection.spec.tsx b/app/javascript/packages/address-search/components/location-collection.spec.tsx similarity index 100% rename from app/javascript/packages/document-capture/components/location-collection.spec.tsx rename to app/javascript/packages/address-search/components/location-collection.spec.tsx diff --git a/app/javascript/packages/document-capture/components/location-collection.tsx b/app/javascript/packages/address-search/components/location-collection.tsx similarity index 100% rename from app/javascript/packages/document-capture/components/location-collection.tsx rename to app/javascript/packages/address-search/components/location-collection.tsx diff --git a/app/javascript/packages/address-search/hooks/use-usps-locations.ts b/app/javascript/packages/address-search/hooks/use-usps-locations.ts new file mode 100644 index 00000000000..af022401d3c --- /dev/null +++ b/app/javascript/packages/address-search/hooks/use-usps-locations.ts @@ -0,0 +1,108 @@ +import { useState, useRef, useEffect, useCallback } from 'react'; +import { request } from '@18f/identity-request'; +import { t } from '@18f/identity-i18n'; +import useSWR from 'swr/immutable'; +import type { Location, FormattedLocation, LocationQuery, PostOffice } from '../types'; +import { formatLocations, snakeCase, transformKeys } from '../utils'; + +const requestUspsLocations = async ({ + locationsURL, + address, +}: { + locationsURL: string; + address: LocationQuery; +}): Promise => { + const response = await request(locationsURL, { + method: 'post', + json: { address: transformKeys(address, snakeCase) }, + }); + + return formatLocations(response); +}; + +function requestAddressCandidates({ + unvalidatedAddressInput, + addressSearchURL, +}: { + unvalidatedAddressInput: string; + addressSearchURL: string; +}): Promise { + return request(addressSearchURL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + json: { address: unvalidatedAddressInput }, + }); +} + +export default function useUspsLocations({ + locationsURL, + addressSearchURL, +}: { + locationsURL: string; + addressSearchURL: string; +}) { + // raw text input that is set when user clicks search + const [addressQuery, setAddressQuery] = useState(''); + const validatedFieldRef = useRef(null); + const handleAddressSearch = useCallback((event, unvalidatedAddressInput) => { + event.preventDefault(); + validatedFieldRef.current?.setCustomValidity(''); + validatedFieldRef.current?.reportValidity(); + + if (unvalidatedAddressInput === '') { + return; + } + + setAddressQuery(unvalidatedAddressInput); + }, []); + + // sends the raw text query to arcgis + const { + data: addressCandidates, + isLoading: isLoadingCandidates, + error: addressError, + } = useSWR([addressQuery], () => + addressQuery + ? requestAddressCandidates({ unvalidatedAddressInput: addressQuery, addressSearchURL }) + : null, + ); + + const [foundAddress, setFoundAddress] = useState(null); + + useEffect(() => { + if (addressCandidates?.[0]) { + const bestMatchedAddress = addressCandidates[0]; + setFoundAddress({ + streetAddress: bestMatchedAddress.street_address, + city: bestMatchedAddress.city, + state: bestMatchedAddress.state, + zipCode: bestMatchedAddress.zip_code, + address: bestMatchedAddress.address, + }); + } else if (addressCandidates) { + validatedFieldRef?.current?.setCustomValidity( + t('in_person_proofing.body.location.inline_error'), + ); + validatedFieldRef?.current?.reportValidity(); + setFoundAddress(null); + } + }, [addressCandidates]); + + const { + data: locationResults, + isLoading: isLoadingLocations, + error: uspsError, + } = useSWR([foundAddress], ([address]) => + address ? requestUspsLocations({ locationsURL, address }) : null, + ); + + return { + foundAddress, + locationResults, + uspsError, + addressError, + isLoading: isLoadingLocations || isLoadingCandidates, + handleAddressSearch, + validatedFieldRef, + }; +} diff --git a/app/javascript/packages/address-search/index.tsx b/app/javascript/packages/address-search/index.tsx index 4e97db5de05..f5d9054738a 100644 --- a/app/javascript/packages/address-search/index.tsx +++ b/app/javascript/packages/address-search/index.tsx @@ -1,282 +1,8 @@ -import { TextInput } from '@18f/identity-components'; -import { useState, useRef, useEffect, useCallback } from 'react'; -import { t } from '@18f/identity-i18n'; -import { request } from '@18f/identity-request'; -import ValidatedField from '@18f/identity-validated-field/validated-field'; -import SpinnerButton, { SpinnerButtonRefHandle } from '@18f/identity-spinner-button/spinner-button'; -import type { RegisterFieldCallback } from '@18f/identity-form-steps'; -import useSWR from 'swr/immutable'; -import { useDidUpdateEffect } from '@18f/identity-react-hooks'; +import { snakeCase, formatLocations, transformKeys } from './utils'; +import InPersonLocations from './components/in-person-locations'; +import AddressInput from './components/address-input'; +import AddressSearch from './components/address-search'; -export interface FormattedLocation { - formattedCityStateZip: string; - distance: string; - id: number; - name: string; - saturdayHours: string; - streetAddress: string; - sundayHours: string; - weekdayHours: string; - isPilot: boolean; -} - -export interface PostOffice { - address: string; - city: string; - distance: string; - name: string; - saturday_hours: string; - state: string; - sunday_hours: string; - weekday_hours: string; - zip_code_4: string; - zip_code_5: string; - is_pilot: boolean; -} - -export interface LocationQuery { - streetAddress: string; - city: string; - state: string; - zipCode: string; - address: string; -} - -interface Location { - street_address: string; - city: string; - state: string; - zip_code: string; - address: string; -} - -const formatLocations = (postOffices: PostOffice[]): FormattedLocation[] => - postOffices.map((po: PostOffice, index) => ({ - formattedCityStateZip: `${po.city}, ${po.state}, ${po.zip_code_5}-${po.zip_code_4}`, - id: index, - distance: po.distance, - name: po.name, - saturdayHours: po.saturday_hours, - streetAddress: po.address, - sundayHours: po.sunday_hours, - weekdayHours: po.weekday_hours, - isPilot: !!po.is_pilot, - })); - -export const snakeCase = (value: string) => - value - .split(/(?=[A-Z])/) - .join('_') - .toLowerCase(); - -// snake case the keys of the location -export const transformKeys = (location: object, predicate: (key: string) => string) => - Object.keys(location).reduce( - (acc, key) => ({ - [predicate(key)]: location[key], - ...acc, - }), - {}, - ); - -const requestUspsLocations = async ({ - locationsURL, - address, -}: { - locationsURL: string; - address: LocationQuery; -}): Promise => { - const response = await request(locationsURL, { - method: 'post', - json: { address: transformKeys(address, snakeCase) }, - }); - - return formatLocations(response); -}; - -function requestAddressCandidates({ - unvalidatedAddressInput, - addressSearchURL, -}: { - unvalidatedAddressInput: string; - addressSearchURL: string; -}): Promise { - return request(addressSearchURL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - json: { address: unvalidatedAddressInput }, - }); -} - -function useUspsLocations({ - locationsURL, - addressSearchURL, -}: { - locationsURL: string; - addressSearchURL: string; -}) { - // raw text input that is set when user clicks search - const [addressQuery, setAddressQuery] = useState(''); - const validatedFieldRef = useRef(null); - const handleAddressSearch = useCallback((event, unvalidatedAddressInput) => { - event.preventDefault(); - validatedFieldRef.current?.setCustomValidity(''); - validatedFieldRef.current?.reportValidity(); - - if (unvalidatedAddressInput === '') { - return; - } - - setAddressQuery(unvalidatedAddressInput); - }, []); - - // sends the raw text query to arcgis - const { - data: addressCandidates, - isLoading: isLoadingCandidates, - error: addressError, - } = useSWR([addressQuery], () => - addressQuery - ? requestAddressCandidates({ unvalidatedAddressInput: addressQuery, addressSearchURL }) - : null, - ); - - const [foundAddress, setFoundAddress] = useState(null); - - useEffect(() => { - if (addressCandidates?.[0]) { - const bestMatchedAddress = addressCandidates[0]; - setFoundAddress({ - streetAddress: bestMatchedAddress.street_address, - city: bestMatchedAddress.city, - state: bestMatchedAddress.state, - zipCode: bestMatchedAddress.zip_code, - address: bestMatchedAddress.address, - }); - } else if (addressCandidates) { - validatedFieldRef?.current?.setCustomValidity( - t('in_person_proofing.body.location.inline_error'), - ); - validatedFieldRef?.current?.reportValidity(); - setFoundAddress(null); - } - }, [addressCandidates]); - - const { - data: locationResults, - isLoading: isLoadingLocations, - error: uspsError, - } = useSWR([foundAddress], ([address]) => - address ? requestUspsLocations({ locationsURL, address }) : null, - ); - - return { - foundAddress, - locationResults, - uspsError, - addressError, - isLoading: isLoadingLocations || isLoadingCandidates, - handleAddressSearch, - validatedFieldRef, - }; -} - -interface AddressSearchProps { - registerField?: RegisterFieldCallback; - onFoundAddress?: (address: LocationQuery | null) => void; - onFoundLocations?: (locations: FormattedLocation[] | null | undefined) => void; - onLoadingLocations?: (isLoading: boolean) => void; - onError?: (error: Error | null) => void; - disabled?: boolean; - addressSearchURL: string; - locationsURL: string; -} - -function AddressSearch({ - registerField = () => undefined, - onFoundAddress = () => undefined, - onFoundLocations = () => undefined, - onLoadingLocations = () => undefined, - onError = () => undefined, - disabled = false, - addressSearchURL, - locationsURL, -}: AddressSearchProps) { - const spinnerButtonRef = useRef(null); - const [textInput, setTextInput] = useState(''); - const { - locationResults, - uspsError, - addressError, - isLoading, - handleAddressSearch: onSearch, - foundAddress, - validatedFieldRef, - } = useUspsLocations({ locationsURL, addressSearchURL }); - - const onTextInputChange = (event: React.ChangeEvent) => { - const { target } = event; - setTextInput(target.value); - }; - - useEffect(() => { - spinnerButtonRef.current?.toggleSpinner(isLoading); - onLoadingLocations(isLoading); - }, [isLoading]); - - useEffect(() => { - addressError && onError(addressError); - uspsError && onError(uspsError); - }, [uspsError, addressError]); - - useDidUpdateEffect(() => { - onFoundLocations(locationResults); - - foundAddress && onFoundAddress(foundAddress); - }, [locationResults]); - - const handleSearch = useCallback( - (event) => { - onError(null); - onSearch(event, textInput); - }, - [textInput], - ); - - return ( - <> - - - -
- - {t('in_person_proofing.body.location.po_search.search_button')} - -
- - ); -} +export { snakeCase, formatLocations, transformKeys, InPersonLocations, AddressInput }; export default AddressSearch; diff --git a/app/javascript/packages/address-search/package.json b/app/javascript/packages/address-search/package.json index c0118198d48..f68b6a85b7b 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": "1.0.5", + "version": "2.0.0", "type": "module", "private": false, "files": [ diff --git a/app/javascript/packages/address-search/types.d.ts b/app/javascript/packages/address-search/types.d.ts new file mode 100644 index 00000000000..587b78e4203 --- /dev/null +++ b/app/javascript/packages/address-search/types.d.ts @@ -0,0 +1,79 @@ +import type { RegisterFieldCallback } from '@18f/identity-form-steps'; +import type { ReactNode } from 'react'; + +interface FormattedLocation { + formattedCityStateZip: string; + distance: string; + id: number; + name: string; + saturdayHours: string; + streetAddress: string; + sundayHours: string; + weekdayHours: string; + isPilot: boolean; +} + +interface PostOffice { + address: string; + city: string; + distance: string; + name: string; + saturday_hours: string; + state: string; + sunday_hours: string; + weekday_hours: string; + zip_code_4: string; + zip_code_5: string; + is_pilot: boolean; +} + +interface LocationQuery { + streetAddress: string; + city: string; + state: string; + zipCode: string; + address: string; +} + +interface Location { + street_address: string; + city: string; + state: string; + zip_code: string; + address: string; +} + +interface AddressInputProps { + registerField?: RegisterFieldCallback; + onFoundAddress?: (address: LocationQuery | null) => void; + onFoundLocations?: (locations: FormattedLocation[] | null | undefined) => void; + onLoadingLocations?: (isLoading: boolean) => void; + onError?: (error: Error | null) => void; + disabled?: boolean; + addressSearchURL: string; + locationsURL: string; +} + +interface InPersonLocationsProps { + locations: FormattedLocation[] | null | undefined; + onSelect; + address: string; +} + +interface LocationCollectionItemProps { + distance?: string; + formattedCityStateZip: string; + handleSelect?: (event: React.MouseEvent, selection: number) => void; + name?: string; + saturdayHours: string; + selectId: number; + streetAddress: string; + sundayHours: string; + weekdayHours: string; +} + +interface LocationCollectionProps { + className?: string; + + children?: ReactNode; +} diff --git a/app/javascript/packages/address-search/utils.ts b/app/javascript/packages/address-search/utils.ts new file mode 100644 index 00000000000..79d895ee6fb --- /dev/null +++ b/app/javascript/packages/address-search/utils.ts @@ -0,0 +1,30 @@ +import type { PostOffice, FormattedLocation } from './types'; + +export const formatLocations = (postOffices: PostOffice[]): FormattedLocation[] => + postOffices.map((po: PostOffice, index) => ({ + formattedCityStateZip: `${po.city}, ${po.state}, ${po.zip_code_5}-${po.zip_code_4}`, + id: index, + distance: po.distance, + name: po.name, + saturdayHours: po.saturday_hours, + streetAddress: po.address, + sundayHours: po.sunday_hours, + weekdayHours: po.weekday_hours, + isPilot: !!po.is_pilot, + })); + +export const snakeCase = (value: string) => + value + .split(/(?=[A-Z])/) + .join('_') + .toLowerCase(); + +// snake case the keys of the location +export const transformKeys = (location: object, predicate: (key: string) => string) => + Object.keys(location).reduce( + (acc, key) => ({ + [predicate(key)]: location[key], + ...acc, + }), + {}, + ); diff --git a/app/javascript/packages/document-capture/components/acuant-capture.tsx b/app/javascript/packages/document-capture/components/acuant-capture.tsx index 5ddcf36b6cd..5d4d17228c3 100644 --- a/app/javascript/packages/document-capture/components/acuant-capture.tsx +++ b/app/javascript/packages/document-capture/components/acuant-capture.tsx @@ -490,44 +490,51 @@ function AcuantCapture( setIsCapturingEnvironment(false); } + function onAcuantImageCaptureFailure(error: AcuantCaptureFailureError, code: string | undefined) { + const { SEQUENCE_BREAK_CODE } = window.AcuantJavascriptWebSdk; + if (isAcuantCameraAccessFailure(error)) { + if (fullScreenRef.current?.focusTrap) { + suspendFocusTrapForAnticipatedFocus(fullScreenRef.current.focusTrap); + } + + // Internally, Acuant sets a cookie to bail on guided capture if initialization had + // previously failed for any reason, including declined permission. Since the cookie + // never expires, and since we want to re-prompt even if the user had previously + // declined, unset the cookie value when failure occurs for permissions. + setAcuantFailureCookie(null); + + onCameraAccessDeclined(); + } else if (code === SEQUENCE_BREAK_CODE) { + setOwnErrorMessage( + `${t('doc_auth.errors.upload_error')} ${t('errors.messages.try_again') + .split(' ') + .join(NBSP_UNICODE)}`, + ); + + refreshAcuantFailureCookie(); + } else if (error === undefined) { + // Show a more generic error message when there's a cropping error. + // Errors with a value of `undefined` are cropping errors. + setOwnErrorMessage(t('errors.general')); + } else { + setOwnErrorMessage(t('doc_auth.errors.camera.failed')); + } + + setIsCapturingEnvironment(false); + trackEvent('IdV: Image capture failed', { + field: name, + acuantCaptureMode, + error: getNormalizedAcuantCaptureFailureMessage(error, code), + }); + } + return (
{isCapturingEnvironment && ( setHasStartedCropping(true)} onImageCaptureSuccess={onAcuantImageCaptureSuccess} - onImageCaptureFailure={(error, code) => { - const { SEQUENCE_BREAK_CODE } = window.AcuantJavascriptWebSdk; - if (isAcuantCameraAccessFailure(error)) { - if (fullScreenRef.current?.focusTrap) { - suspendFocusTrapForAnticipatedFocus(fullScreenRef.current.focusTrap); - } - - // Internally, Acuant sets a cookie to bail on guided capture if initialization had - // previously failed for any reason, including declined permission. Since the cookie - // never expires, and since we want to re-prompt even if the user had previously - // declined, unset the cookie value when failure occurs for permissions. - setAcuantFailureCookie(null); - - onCameraAccessDeclined(); - } else if (code === SEQUENCE_BREAK_CODE) { - setOwnErrorMessage( - `${t('doc_auth.errors.upload_error')} ${t('errors.messages.try_again') - .split(' ') - .join(NBSP_UNICODE)}`, - ); - - refreshAcuantFailureCookie(); - } else { - setOwnErrorMessage(t('doc_auth.errors.camera.failed')); - } - - setIsCapturingEnvironment(false); - trackEvent('IdV: Image capture failed', { - field: name, - error: getNormalizedAcuantCaptureFailureMessage(error, code), - }); - }} + onImageCaptureFailure={onAcuantImageCaptureFailure} > {!hasStartedCropping && ( { const sandbox = useSandbox(); + + context('validates form', () => { + it('displays an error for all required fields when input is empty', async () => { + const handleLocationsFound = sandbox.stub(); + const { findByText, findAllByText } = render( + new Map() }}> + + , + ); + + await userEvent.click( + await findByText('in_person_proofing.body.location.po_search.search_button'), + ); + + const errors = await findAllByText('simple_form.required.text'); + expect(errors).to.have.lengthOf(4); + }); + + it('displays an error for an invalid ZIP code length (length = 1)', async () => { + const handleLocationsFound = sandbox.stub(); + const { findByText, findByLabelText, findAllByText } = render( + new Map() }}> + + , + ); + + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.address_label'), + '200 main', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.city_label'), + 'Endeavor', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.state_label'), + 'DE', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.zipcode_label'), + '1', + ); + await userEvent.click( + await findByText('in_person_proofing.body.location.po_search.search_button'), + ); + + const errors = await findAllByText('idv.errors.pattern_mismatch.zipcode_five'); + expect(errors).to.have.lengthOf(1); + }); + + it('does not display an error for a valid ZIP code length (length = 5)', async () => { + const handleLocationsFound = sandbox.stub(); + const { findByText, findByLabelText, queryByText } = render( + new Map() }}> + + , + ); + + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.address_label'), + '200 main', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.city_label'), + 'Endeavor', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.state_label'), + 'DE', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.zipcode_label'), + '17201', + ); + await userEvent.click( + await findByText('in_person_proofing.body.location.po_search.search_button'), + ); + + expect(queryByText('idv.errors.pattern_mismatch.zipcode')).to.be.null; + }); + }); + context('when an address is found', () => { let server: SetupServer; before(() => { diff --git a/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx b/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx index 5e2c7cad5e8..83c91d7c93b 100644 --- a/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx +++ b/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx @@ -7,13 +7,12 @@ import SpinnerButton, { SpinnerButtonRefHandle } from '@18f/identity-spinner-but import type { RegisterFieldCallback } from '@18f/identity-form-steps'; import useSWR from 'swr/immutable'; import { useDidUpdateEffect } from '@18f/identity-react-hooks'; -import { - FormattedLocation, - transformKeys, - snakeCase, +import { transformKeys, snakeCase } from '@18f/identity-address-search'; +import type { LocationQuery, PostOffice, -} from '@18f/identity-address-search'; + FormattedLocation, +} from '@18f/identity-address-search/types'; import { InPersonContext } from '../context'; const formatLocations = (postOffices: PostOffice[]): FormattedLocation[] => @@ -51,33 +50,65 @@ function useUspsLocations(locationsURL: string) { const validatedStateFieldRef = useRef(null); const validatedZipCodeFieldRef = useRef(null); - const handleLocationSearch = useCallback( - (event, addressValue, cityValue, stateValue, zipCodeValue) => { - event.preventDefault(); + const checkValidityAndDisplayErrors = (address, city, state, zipCode) => { + let formIsValid = true; + const zipCodeIsValid = zipCode.length === 5 && !!zipCode.match(/\d{5}/); + + if (address.length === 0) { + validatedAddressFieldRef.current?.setCustomValidity(t('simple_form.required.text')); + formIsValid = false; + } else { validatedAddressFieldRef.current?.setCustomValidity(''); - validatedAddressFieldRef.current?.reportValidity(); + } + + if (city.length === 0) { + formIsValid = false; + validatedCityFieldRef.current?.setCustomValidity(t('simple_form.required.text')); + } else { validatedCityFieldRef.current?.setCustomValidity(''); - validatedCityFieldRef.current?.reportValidity(); + } + + if (state.length === 0) { + formIsValid = false; + validatedStateFieldRef.current?.setCustomValidity(t('simple_form.required.text')); + } else { validatedStateFieldRef.current?.setCustomValidity(''); - validatedStateFieldRef.current?.reportValidity(); + } + + if (zipCode.length === 0) { + formIsValid = false; + validatedZipCodeFieldRef.current?.setCustomValidity(t('simple_form.required.text')); + } else { validatedZipCodeFieldRef.current?.setCustomValidity(''); - validatedZipCodeFieldRef.current?.reportValidity(); - - if ( - addressValue.trim().length === 0 || - cityValue.trim().length === 0 || - stateValue.trim().length === 0 || - zipCodeValue.trim().length === 0 - ) { + } + + validatedAddressFieldRef.current?.reportValidity(); + validatedCityFieldRef.current?.reportValidity(); + validatedStateFieldRef.current?.reportValidity(); + validatedZipCodeFieldRef.current?.reportValidity(); + + return formIsValid && zipCodeIsValid; + }; + + const handleLocationSearch = useCallback( + (event, addressValue, cityValue, stateValue, zipCodeValue) => { + event.preventDefault(); + const address = addressValue.trim(); + const city = cityValue.trim(); + const zipCode = zipCodeValue.trim(); + + const formIsValid = checkValidityAndDisplayErrors(address, city, stateValue, zipCode); + + if (!formIsValid) { return; } setLocationQuery({ - address: `${addressValue}, ${cityValue}, ${stateValue} ${zipCodeValue}`, - streetAddress: addressValue, - city: cityValue, + address: `${address}, ${city}, ${stateValue} ${zipCode}`, + streetAddress: address, + city, state: stateValue, - zipCode: zipCodeValue, + zipCode, }); }, [], @@ -148,9 +179,11 @@ function FullAddressSearch({ input(target.value); }; + type SelectChangeEvent = React.ChangeEvent; + const onAddressChange = inputChangeHandler(setAddressValue); const onCityChange = inputChangeHandler(setCityValue); - const onStateChange = inputChangeHandler(setStateValue); + const onStateChange = (e: SelectChangeEvent) => setStateValue(e.target.value); const onZipCodeChange = inputChangeHandler(setZipCodeValue); useEffect(() => { @@ -178,7 +211,12 @@ function FullAddressSearch({ return ( <> - + - + @@ -217,7 +264,12 @@ function FullAddressSearch({ ))} - +
diff --git a/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx b/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx index d6ee119a0f0..4b952423185 100644 --- a/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx +++ b/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx @@ -3,11 +3,11 @@ import { useI18n } from '@18f/identity-react-i18n'; import { Alert, PageHeading } from '@18f/identity-components'; import { request } from '@18f/identity-request'; import { forceRedirect } from '@18f/identity-url'; -import { transformKeys, snakeCase, LocationQuery } from '@18f/identity-address-search'; +import { transformKeys, snakeCase, InPersonLocations } from '@18f/identity-address-search'; +import type { LocationQuery, FormattedLocation } from '@18f/identity-address-search/types'; import FullAddressSearch from './in-person-full-address-search'; import BackButton from './back-button'; import AnalyticsContext from '../context/analytics'; -import InPersonLocations, { FormattedLocation } from './in-person-locations'; import { InPersonContext } from '../context'; import UploadContext from '../context/upload'; import { LOCATIONS_URL } from './in-person-location-post-office-search-step'; diff --git a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx index 127ffee8351..c256eb18f25 100644 --- a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx +++ b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.tsx @@ -1,16 +1,10 @@ import { useState, useEffect, useCallback, useRef, useContext } from 'react'; -import { useI18n } from '@18f/identity-react-i18n'; -import { Alert, PageHeading } from '@18f/identity-components'; import { request } from '@18f/identity-request'; import { forceRedirect } from '@18f/identity-url'; -import AddressSearch, { - transformKeys, - snakeCase, - LocationQuery, -} from '@18f/identity-address-search'; +import AddressSearch, { transformKeys, snakeCase } from '@18f/identity-address-search'; +import type { FormattedLocation } from '@18f/identity-address-search/types'; import BackButton from './back-button'; import AnalyticsContext from '../context/analytics'; -import InPersonLocations, { FormattedLocation } from './in-person-locations'; import { InPersonContext } from '../context'; import UploadContext from '../context/upload'; @@ -22,16 +16,13 @@ export const ADDRESSES_URL = new URL('/api/addresses', window.location.href).toS function InPersonLocationPostOfficeSearchStep({ onChange, toPreviousStep, registerField }) { const { inPersonURL } = useContext(InPersonContext); - const { t } = useI18n(); const [inProgress, setInProgress] = useState(false); - const [isLoadingLocations, setLoadingLocations] = useState(false); const [autoSubmit, setAutoSubmit] = useState(false); const { trackEvent } = useContext(AnalyticsContext); const [locationResults, setLocationResults] = useState( null, ); - const [foundAddress, setFoundAddress] = useState(null); - const [apiError, setApiError] = useState(null); + const [disabledAddressSearch, setDisabledAddressSearch] = useState(false); const { flowPath } = useContext(UploadContext); @@ -102,30 +93,14 @@ function InPersonLocationPostOfficeSearchStep({ onChange, toPreviousStep, regist return ( <> - {apiError && ( - - {t('idv.failure.exceptions.post_office_search_error')} - - )} - {t('in_person_proofing.headings.po_search.location')} -

{t('in_person_proofing.body.location.po_search.po_search_about')}

- {locationResults && foundAddress && !isLoadingLocations && ( - - )} ); diff --git a/app/javascript/packages/phone-input/package.json b/app/javascript/packages/phone-input/package.json index e0d1e27b6eb..fd9fa08f565 100644 --- a/app/javascript/packages/phone-input/package.json +++ b/app/javascript/packages/phone-input/package.json @@ -4,6 +4,6 @@ "version": "1.0.0", "dependencies": { "intl-tel-input": "^17.0.19", - "libphonenumber-js": "^1.10.41" + "libphonenumber-js": "^1.10.43" } } diff --git a/app/jobs/reports/identity_verification_report.rb b/app/jobs/reports/identity_verification_report.rb new file mode 100644 index 00000000000..8943374b7c8 --- /dev/null +++ b/app/jobs/reports/identity_verification_report.rb @@ -0,0 +1,25 @@ +require 'reporting/identity_verification_report' + +module Reports + class IdentityVerificationReport < BaseReport + REPORT_NAME = 'identity-verification-report' + + attr_accessor :report_date + + def perform(report_date) + self.report_date = report_date + + csv = report_maker.to_csv + + save_report(REPORT_NAME, csv, extension: 'csv') + end + + def report_maker + Reporting::IdentityVerificationReport.new( + issuer: nil, + time_range: report_date.all_day, + slice: 4.hours, + ) + end + end +end diff --git a/app/models/concerns/user_access_key_overrides.rb b/app/models/concerns/user_access_key_overrides.rb index 286f6df7416..54ba8e86e46 100644 --- a/app/models/concerns/user_access_key_overrides.rb +++ b/app/models/concerns/user_access_key_overrides.rb @@ -14,7 +14,6 @@ def valid_password?(password) user_uuid: uuid, ) @password = password if result - log_password_verification_failure unless result result end @@ -82,15 +81,4 @@ def authenticatable_salt encrypted_password_digest, ).password_salt end - - private - - def log_password_verification_failure - metadata = { - event: 'Failure to validate password', - uuid: uuid, - timestamp: Time.zone.now, - } - Rails.logger.info(metadata.to_json) - end end diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 1cebaeccaeb..63a34fc3ebd 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -226,9 +226,9 @@ def backup_code_created(enabled_mfa_methods_count:, **extra) end # Tracks when the user visits the Backup Code Regenerate page. - # @param [String] request_came_from the controller/action the request came from - def backup_code_regenerate_visit(request_came_from:, **extra) - track_event('Backup Code Regenerate Visited', request_came_from:, **extra) + # @param [Boolean] in_multi_mfa_selection_flow whether user is going through MFA selection Flow + def backup_code_regenerate_visit(in_multi_mfa_selection_flow:, **extra) + track_event('Backup Code Regenerate Visited', in_multi_mfa_selection_flow:, **extra) end # Track user creating new BackupCodeSetupForm, record form submission Hash @@ -297,11 +297,17 @@ def doc_auth_async(error: nil, uuid: nil, result_id: nil, **extra) track_event('Doc Auth Async', error: error, uuid: uuid, result_id: result_id, **extra) end - # @param [String] message the warining + # @param [String] message the warning + # @param [String] getting_started_ab_test_bucket Which initial IdV screen the user saw # Logged when there is a non-user-facing error in the doc auth process, such as an unrecognized # field from a vendor - def doc_auth_warning(message: nil, **extra) - track_event('Doc Auth Warning', message: message, **extra) + def doc_auth_warning(message: nil, getting_started_ab_test_bucket: nil, **extra) + track_event( + 'Doc Auth Warning', + message: message, + getting_started_ab_test_bucket: getting_started_ab_test_bucket, + **extra, + ) end # When a user views the edit password page @@ -759,6 +765,7 @@ def idv_doc_auth_ssn_visited(**extra) # @param [String] flow_path # @param [String] front_image_fingerprint Fingerprint of front image data # @param [String] back_image_fingerprint Fingerprint of back image data + # @param [String] getting_started_ab_test_bucket Which initial IdV screen the user saw # The document capture image uploaded was locally validated during the IDV process def idv_doc_auth_submitted_image_upload_form( success:, @@ -769,6 +776,7 @@ def idv_doc_auth_submitted_image_upload_form( user_id: nil, front_image_fingerprint: nil, back_image_fingerprint: nil, + getting_started_ab_test_bucket: nil, **extra ) track_event( @@ -781,6 +789,7 @@ def idv_doc_auth_submitted_image_upload_form( flow_path: flow_path, front_image_fingerprint: front_image_fingerprint, back_image_fingerprint: back_image_fingerprint, + getting_started_ab_test_bucket: getting_started_ab_test_bucket, **extra, ) end @@ -800,6 +809,7 @@ def idv_doc_auth_submitted_image_upload_form( # @param [Float] vendor_request_time_in_ms Time it took to upload images & get a response. # @param [String] front_image_fingerprint Fingerprint of front image data # @param [String] back_image_fingerprint Fingerprint of back image data + # @param [String] getting_started_ab_test_bucket Which initial IdV screen the user saw # The document capture image was uploaded to vendor during the IDV process def idv_doc_auth_submitted_image_upload_vendor( success:, @@ -816,6 +826,7 @@ def idv_doc_auth_submitted_image_upload_vendor( vendor_request_time_in_ms: nil, front_image_fingerprint: nil, back_image_fingerprint: nil, + getting_started_ab_test_bucket: nil, **extra ) track_event( @@ -835,6 +846,7 @@ def idv_doc_auth_submitted_image_upload_vendor( vendor_request_time_in_ms: vendor_request_time_in_ms, front_image_fingerprint: front_image_fingerprint, back_image_fingerprint: back_image_fingerprint, + getting_started_ab_test_bucket: getting_started_ab_test_bucket, **extra, ) end @@ -847,6 +859,7 @@ def idv_doc_auth_submitted_image_upload_vendor( # @param [String] flow_path # @param [String] front_image_fingerprint Fingerprint of front image data # @param [String] back_image_fingerprint Fingerprint of back image data + # @param [String] getting_started_ab_test_bucket Which initial IdV screen the user saw # The PII that came back from the document capture vendor was validated def idv_doc_auth_submitted_pii_validation( success:, @@ -857,6 +870,7 @@ def idv_doc_auth_submitted_pii_validation( user_id: nil, front_image_fingerprint: nil, back_image_fingerprint: nil, + getting_started_ab_test_bucket: nil, **extra ) track_event( @@ -869,6 +883,7 @@ def idv_doc_auth_submitted_pii_validation( flow_path: flow_path, front_image_fingerprint: front_image_fingerprint, back_image_fingerprint: back_image_fingerprint, + getting_started_ab_test_bucket: getting_started_ab_test_bucket, **extra, ) end @@ -2602,12 +2617,15 @@ def multi_factor_auth_added_phone(enabled_mfa_methods_count:, **extra) # Tracks when the user has added the MFA method piv_cac to their account # @param [Integer] enabled_mfa_methods_count number of registered mfa methods for the user - def multi_factor_auth_added_piv_cac(enabled_mfa_methods_count:, **extra) + # @param [Boolean] in_multi_mfa_selection_flow whether user is going through MFA selection Flow + def multi_factor_auth_added_piv_cac(enabled_mfa_methods_count:, in_multi_mfa_selection_flow:, + **extra) track_event( 'Multi-Factor Authentication: Added PIV_CAC', { method_name: :piv_cac, - enabled_mfa_methods_count: enabled_mfa_methods_count, + enabled_mfa_methods_count:, + in_multi_mfa_selection_flow:, **extra, }.compact, ) @@ -2615,12 +2633,15 @@ def multi_factor_auth_added_piv_cac(enabled_mfa_methods_count:, **extra) # Tracks when the user has added the MFA method TOTP to their account # @param [Integer] enabled_mfa_methods_count number of registered mfa methods for the user - def multi_factor_auth_added_totp(enabled_mfa_methods_count:, **extra) + # @param [Boolean] in_multi_mfa_selection_flow whether user is going through MFA selection Flow + def multi_factor_auth_added_totp(enabled_mfa_methods_count:, in_multi_mfa_selection_flow:, + **extra) track_event( 'Multi-Factor Authentication: Added TOTP', { method_name: :totp, - enabled_mfa_methods_count: enabled_mfa_methods_count, + in_multi_mfa_selection_flow:, + enabled_mfa_methods_count:, **extra, }.compact, ) @@ -2651,13 +2672,17 @@ def multi_factor_auth_backup_code_download # Tracks when the user visits the backup code confirmation setup page # @param [Integer] enabled_mfa_methods_count number of registered mfa methods for the user + # @param [Boolean] in_multi_mfa_selection_flow tell whether its in MFA selection flow or not def multi_factor_auth_enter_backup_code_confirmation_visit( - enabled_mfa_methods_count:, **extra + enabled_mfa_methods_count:, + in_multi_mfa_selection_flow:, + **extra ) track_event( 'Multi-Factor Authentication: enter backup code confirmation visited', { - enabled_mfa_methods_count: enabled_mfa_methods_count, + enabled_mfa_methods_count:, + in_multi_mfa_selection_flow:, **extra, }.compact, ) @@ -3247,6 +3272,12 @@ def phone_deletion(success:, phone_configuration_id:, **extra) ) end + # @identity.idp.previous_event_name User Registration: piv cac disabled + # Tracks when user's piv cac is disabled + def piv_cac_disabled + track_event('PIV CAC disabled') + end + # @param [Boolean] success # @param [Hash] errors # tracks piv cac login event @@ -3259,6 +3290,17 @@ def piv_cac_login(success:, errors:, **extra) ) end + # @identity.idp.previous_event_name User Registration: piv cac setup visited + # Tracks when user's piv cac setup + # @param [Boolean] in_multi_mfa_selection_flow + def piv_cac_setup_visit(in_multi_mfa_selection_flow:, **extra) + track_event( + 'PIV CAC setup visited', + in_multi_mfa_selection_flow:, + **extra, + ) + end + # @param [String] redirect_url URL user was directed to # @param [String, nil] step which step # @param [String, nil] location which part of a step, if applicable @@ -3751,17 +3793,20 @@ def telephony_otp_sent( # @param [Boolean] user_signed_up # @param [Boolean] totp_secret_present # @param [Integer] enabled_mfa_methods_count + # @param [Boolean] in_multi_mfa_selection_flow def totp_setup_visit( user_signed_up:, totp_secret_present:, enabled_mfa_methods_count:, + in_multi_mfa_selection_flow:, **extra ) track_event( 'TOTP Setup Visited', - user_signed_up: user_signed_up, - totp_secret_present: totp_secret_present, - enabled_mfa_methods_count: enabled_mfa_methods_count, + user_signed_up:, + totp_secret_present:, + enabled_mfa_methods_count:, + in_multi_mfa_selection_flow:, **extra, ) end @@ -3836,29 +3881,6 @@ def user_prompted_before_navigation_and_still_on_page(path:, seconds:, **extra) ) end - # @param [Boolean] success - # @param [Hash] errors - # Tracks when the the user has selected and submitted additional MFA methods on user registration - def user_registration_2fa_additional_setup(success:, - errors: nil, - **extra) - track_event( - 'User Registration: Additional 2FA Setup', - { - success: success, - errors: errors, - **extra, - }.compact, - ) - end - - # Tracks when user visits additional MFA selection page - def user_registration_2fa_additional_setup_visit - track_event( - 'User Registration: Additional 2FA Setup visited', - ) - end - # @param [Boolean] success # @param [Hash] errors # @param [Integer] enabled_mfa_methods_count @@ -4059,19 +4081,6 @@ def user_registration_phone_setup_visit(enabled_mfa_methods_count:, **extra) ) end - # Tracks when user's piv cac is disabled - def user_registration_piv_cac_disabled - track_event('User Registration: piv cac disabled') - end - - # Tracks when user's piv cac setup - def user_registration_piv_cac_setup_visit(**extra) - track_event( - 'User Registration: piv cac setup visited', - **extra, - ) - end - # Tracks when user skips Suggest Another MFA Page def user_registration_suggest_another_mfa_notice_skipped track_event('User Registration: Suggest Another MFA Notice Skipped') diff --git a/app/services/idv/steps/in_person/address_step.rb b/app/services/idv/steps/in_person/address_step.rb index 6b304143082..7b53f4943f7 100644 --- a/app/services/idv/steps/in_person/address_step.rb +++ b/app/services/idv/steps/in_person/address_step.rb @@ -30,7 +30,7 @@ def call redirect_to idv_in_person_verify_info_url if updating_address? - redirect_to idv_in_person_proofing_ssn_url + redirect_to idv_in_person_ssn_url end def extra_view_variables diff --git a/app/services/idv/steps/in_person/state_id_step.rb b/app/services/idv/steps/in_person/state_id_step.rb index 1645b0d931d..f87327898c1 100644 --- a/app/services/idv/steps/in_person/state_id_step.rb +++ b/app/services/idv/steps/in_person/state_id_step.rb @@ -27,7 +27,7 @@ def call if pii_from_user[:same_address_as_id] == 'true' copy_state_id_address_to_residential_address(pii_from_user) mark_step_complete(:address) - redirect_to idv_in_person_proofing_ssn_url + redirect_to idv_in_person_ssn_url end if initial_state_of_same_address_as_id == 'true' && diff --git a/app/services/proofing/aamva/proofer.rb b/app/services/proofing/aamva/proofer.rb index 9a20caf983a..ac20f64b969 100644 --- a/app/services/proofing/aamva/proofer.rb +++ b/app/services/proofing/aamva/proofer.rb @@ -98,13 +98,8 @@ def address_verified?(results) end def send_to_new_relic(result) - case result.exception - when Proofing::TimeoutError + if result.mva_timeout? return # noop - when Proofing::Aamva::VerificationError - if result.mva_timeout? - return # noop - end end NewRelic::Agent.notice_error(result.exception) end diff --git a/app/views/idv/agreement/show.html.erb b/app/views/idv/agreement/show.html.erb index a4e29f194dd..d246c2b409f 100644 --- a/app/views/idv/agreement/show.html.erb +++ b/app/views/idv/agreement/show.html.erb @@ -14,9 +14,9 @@ class: [ 'js-consent-form-alert', 'margin-bottom-4', - flow_session[:error_message].blank? && 'display-none', + 'display-none', ].select(&:present?), - message: flow_session[:error_message].presence || t('errors.doc_auth.consent_form'), + message: t('errors.doc_auth.consent_form'), ) %> <%= render PageHeadingComponent.new.with_content(t('doc_auth.headings.lets_go')) %> diff --git a/app/views/idv/getting_started/show.html.erb b/app/views/idv/getting_started/show.html.erb index ddc9be31c11..934eae6c721 100644 --- a/app/views/idv/getting_started/show.html.erb +++ b/app/views/idv/getting_started/show.html.erb @@ -10,9 +10,9 @@ class: [ 'js-consent-form-alert', 'margin-bottom-4', - flow_session[:error_message].blank? && 'display-none', + 'display-none', ].select(&:present?), - message: flow_session[:error_message].presence || t('errors.doc_auth.consent_form'), + message: t('errors.doc_auth.consent_form'), ) %> <%= render PageHeadingComponent.new.with_content(@title) %> diff --git a/app/views/idv/in_person/verify_info/show.html.erb b/app/views/idv/in_person/verify_info/show.html.erb index a4e80dff376..6aff91ed815 100644 --- a/app/views/idv/in_person/verify_info/show.html.erb +++ b/app/views/idv/in_person/verify_info/show.html.erb @@ -145,7 +145,7 @@ locals:
<%= link_to( t('idv.buttons.change_label'), - idv_in_person_proofing_ssn_url, + idv_in_person_ssn_url, 'aria-label': t('idv.buttons.change_ssn_label'), ) %>
diff --git a/app/views/users/emails/show.html.erb b/app/views/users/emails/show.html.erb index 38fa5bf8e8f..cb2ebc6adba 100644 --- a/app/views/users/emails/show.html.erb +++ b/app/views/users/emails/show.html.erb @@ -12,6 +12,7 @@ form: f, name: :email, label: t('forms.registration.labels.email'), + input_html: { autocomplete: 'off' }, required: true, ) %> <%= f.submit t('forms.buttons.submit.default') %> diff --git a/app/views/users/mfa_selection/index.html.erb b/app/views/users/mfa_selection/index.html.erb deleted file mode 100644 index f5f42b1c5ab..00000000000 --- a/app/views/users/mfa_selection/index.html.erb +++ /dev/null @@ -1,41 +0,0 @@ -<%= title t('mfa.additional_mfa_required.heading') %> - -<%= render PageHeadingComponent.new.with_content(t('mfa.additional_mfa_required.heading')) %> - -

<%= @presenter.intro %>

- -

- <%= t('headings.account.two_factor') %> -

- -
    - <% @presenter.options.each do |option| %> - <% if option.mfa_configuration_count > 0 %> - <%= render partial: 'partials/multi_factor_authentication/selected_mfa_option', locals: { option: option } %> - <% end %> - <% end %> -
-<%= simple_form_for @two_factor_options_form, - html: { autocomplete: 'off' }, - method: :patch, - url: second_mfa_setup_path do |f| %> -
-
- <%= @presenter.intro %> - <% @presenter.options.each do |option| %> - <%= render(option) do %> - <%= render partial: 'partials/multi_factor_authentication/mfa_selection', - locals: { option: option } %> - <% end %> - <% end %> -
-
- - <%= f.submit t('forms.buttons.continue'), class: 'margin-bottom-1' %> -<% end %> - -<% if @presenter.show_skip_additional_mfa_link? %> - <%= render PageFooterComponent.new do %> - <%= link_to t('mfa.skip'), @after_setup_path, method: :get %> - <% end %> -<% end %> diff --git a/app/views/users/phone_setup/spam_protection.html.erb b/app/views/users/phone_setup/spam_protection.html.erb index 620c13936a1..85cd48d5d7c 100644 --- a/app/views/users/phone_setup/spam_protection.html.erb +++ b/app/views/users/phone_setup/spam_protection.html.erb @@ -43,7 +43,7 @@ <%= render TroubleshootingOptionsComponent.new do |c| %> <% c.with_header { t('components.troubleshooting_options.default_heading') } %> - <% if local_assigns[:authentication_methods_setup_path].present? %> + <% if in_multi_mfa_selection_flow? %> <% c.with_option( url: authentication_methods_setup_path, ).with_content(t('two_factor_authentication.login_options_link_text')) %> @@ -59,7 +59,7 @@ ).with_content(t('two_factor_authentication.learn_more')) %> <% end %> -<% unless local_assigns[:authentication_methods_setup_path].present? %> +<% unless in_multi_mfa_selection_flow? %> <%= render PageFooterComponent.new do %> <%= link_to t('links.cancel'), account_path %> <% end %> diff --git a/charts/Chart.yaml b/charts/Chart.yaml deleted file mode 100644 index a8e4602c88d..00000000000 --- a/charts/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v2 -name: identity-idp-chart -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.0.8 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/charts/templates/NOTES.txt b/charts/templates/NOTES.txt deleted file mode 100644 index ddd91ed4fc2..00000000000 --- a/charts/templates/NOTES.txt +++ /dev/null @@ -1,22 +0,0 @@ -1. The application is running at the following URL: -{{- if .Values.idp.ingress.enabled }} -{{- range $host := .Values.idp.ingress.hosts }} - {{- range .paths }} - https://{{ $host.host }}/ - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "identity-idp-chart.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo https://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "identity-idp-chart.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "identity-idp-chart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo https://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "identity-idp-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit https://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT -{{- end }} diff --git a/charts/templates/_helpers.tpl b/charts/templates/_helpers.tpl deleted file mode 100644 index e42075e0000..00000000000 --- a/charts/templates/_helpers.tpl +++ /dev/null @@ -1,159 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "identity-idp-chart.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "identity-idp-chart.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "identity-idp-chart.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "identity-idp-chart.idp.labels" -}} -helm.sh/chart: {{ include "identity-idp-chart.chart" . }} -{{ include "identity-idp-chart.idp.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "identity-idp-chart.idp.selectorLabels" -}} -app.kubernetes.io/name: {{ include "identity-idp-chart.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "identity-idp-chart.idp.serviceAccountName" -}} -{{- if .Values.worker.serviceAccount.create }} -{{- default (printf "%s-idp" (include "identity-idp-chart.fullname" .)) .Values.worker.serviceAccount.name }} -{{- else }} -{{- default (printf "%s-idp" "default") .Values.worker.serviceAccount.name }} -{{- end }} -{{- end }} - -# templates/_helpers.tpl -{{/* -idp fullname -*/}} -{{- define "identity-idp-chart.idp.fullname" -}} -{{- printf "%s-idp" (include "identity-idp-chart.fullname" .) -}} -{{- end -}} - -# templates/_helpers.tpl -{{/* -Redis fullname -*/}} -{{- define "identity-idp-chart.redis.fullname" -}} -{{- printf "%s-redis" (include "identity-idp-chart.fullname" .) -}} -{{- end -}} - -{{/* -Redis labels -*/}} -{{- define "identity-idp-chart.redis.labels" -}} -helm.sh/chart: {{ include "identity-idp-chart.chart" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -app.kubernetes.io/name: redis -{{- end -}} - -{{/* -Redis selector labels -*/}} -{{- define "identity-idp-chart.redis.selectorLabels" -}} -app.kubernetes.io/instance: {{ .Release.Name }} -app.kubernetes.io/name: redis -{{- end -}} - -{{/* -Postgres fullname -*/}} -{{- define "identity-idp-chart.postgres.fullname" -}} -{{- printf "%s-postgres" (include "identity-idp-chart.fullname" .) -}} -{{- end -}} - -{{/* -Postgres labels -*/}} -{{- define "identity-idp-chart.postgres.labels" -}} -helm.sh/chart: {{ include "identity-idp-chart.chart" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -app.kubernetes.io/name: postgres -{{- end -}} - -{{/* -Postgres selector labels -*/}} -{{- define "identity-idp-chart.postgres.selectorLabels" -}} -app: {{ include "identity-idp-chart.postgres.fullname" . }} -{{- end -}} - -{{/* -Common labels -*/}} -{{- define "identity-idp-chart.worker.labels" -}} -helm.sh/chart: {{ include "identity-idp-chart.chart" . }} -{{ include "identity-idp-chart.worker.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "identity-idp-chart.worker.selectorLabels" -}} -app.kubernetes.io/name: {{ include "identity-idp-chart.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "identity-idp-chart.worker.serviceAccountName" -}} -{{- if .Values.worker.serviceAccount.create }} -{{- default (printf "%s-worker" (include "identity-idp-chart.fullname" .)) .Values.worker.serviceAccount.name }} -{{- else }} -{{- default (printf "%s-worker" "default") .Values.worker.serviceAccount.name }} -{{- end }} -{{- end }} - -# templates/_helpers.tpl -{{/* -worker fullname -*/}} -{{- define "identity-idp-chart.worker.fullname" -}} -{{- printf "%s-worker" (include "identity-idp-chart.fullname" .) -}} -{{- end -}} \ No newline at end of file diff --git a/charts/templates/idp/create-db-job.yaml b/charts/templates/idp/create-db-job.yaml deleted file mode 100644 index 5c624fdb19e..00000000000 --- a/charts/templates/idp/create-db-job.yaml +++ /dev/null @@ -1,48 +0,0 @@ -# Job runs until the command does not fail -# attempts to intialize the database that will -# be used by the rails-app deployment -{{- if and .Values.idp.enabled .Values.idp.hooks.create }} -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ include "identity-idp-chart.idp.fullname" . }}-create-database - labels: - {{- include "identity-idp-chart.idp.labels" . | nindent 4 }} - annotations: - {{- toYaml .Values.idp.hooks.annotations | nindent 4 }} -spec: - backoffLimit: {{ .Values.idp.hooks.backoffLimit }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "identity-idp-chart.idp.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.idp.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "identity-idp-chart.idp.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.idp.podSecurityContext | nindent 8 }} - restartPolicy: OnFailure - containers: - - name: {{ include "identity-idp-chart.fullname" . }}-create-database - securityContext: - {{- toYaml .Values.idp.securityContext | nindent 12 }} - image: "{{ .Values.idp.image.repository }}:{{ .Values.idp.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.idp.image.pullPolicy }} - env: - {{- toYaml .Values.idp.env | nindent 12 }} - command: [ bundle ] - args: - - exec - - rake - - db:create - resources: - {{- toYaml .Values.idp.resources | nindent 12 }} - backoffLimit: 4 -{{- end }} diff --git a/charts/templates/idp/deployment.yaml b/charts/templates/idp/deployment.yaml deleted file mode 100644 index 5df498ca55c..00000000000 --- a/charts/templates/idp/deployment.yaml +++ /dev/null @@ -1,79 +0,0 @@ -{{- if .Values.idp.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "identity-idp-chart.idp.fullname" . }} - labels: - {{- include "identity-idp-chart.idp.labels" . | nindent 4 }} -spec: - {{- if not .Values.idp.autoscaling.enabled }} - replicas: {{ .Values.idp.replicaCount }} - {{- end }} - selector: - matchLabels: - {{- include "identity-idp-chart.idp.selectorLabels" . | nindent 6 }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "identity-idp-chart.idp.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "identity-idp-chart.idp.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.idp.image.repository }}:{{ .Values.idp.image.tag | default .Chart.AppVersion }}" - env: - {{- toYaml .Values.idp.env | nindent 12 }} - imagePullPolicy: {{ .Values.idp.image.pullPolicy }} - ports: - - name: https - containerPort: {{ .Values.idp.service.port }} - protocol: TCP - livenessProbe: - exec: - command: - - sh - - -c - - curl -k -f https://localhost:{{ .Values.idp.service.port }}/api/health # Need custom exec command to ignore self-signed cert - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 - readinessProbe: - exec: - command: - - sh - - -c - - curl -k -f https://localhost:{{ .Values.idp.service.port }}/api/health # Need custom exec command to ignore self-signed cert - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 - resources: - {{- toYaml .Values.idp.resources | nindent 12 }} - {{- with .Values.idp.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.idp.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.idp.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} diff --git a/charts/templates/idp/ingress.yaml b/charts/templates/idp/ingress.yaml deleted file mode 100644 index 74d9b8e7b8c..00000000000 --- a/charts/templates/idp/ingress.yaml +++ /dev/null @@ -1,61 +0,0 @@ -{{- if and .Values.idp.ingress.enabled .Values.idp.enabled -}} -{{- $fullName := include "identity-idp-chart.idp.fullname" . -}} -{{- $svcPort := .Values.idp.service.port -}} -{{- if and .Values.idp.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.idp.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.idp.ingress.annotations "kubernetes.io/ingress.class" .Values.idp.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "identity-idp-chart.idp.labels" . | nindent 4 }} - {{- with .Values.idp.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if and .Values.idp.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ .Values.idp.ingress.className }} - {{- end }} - {{- if .Values.idp.ingress.tls }} - tls: - {{- range .Values.idp.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.idp.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullName }} - port: - number: {{ $svcPort }} - {{- else }} - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/templates/idp/migrate-db-job.yaml b/charts/templates/idp/migrate-db-job.yaml deleted file mode 100644 index a74d1f3f3b4..00000000000 --- a/charts/templates/idp/migrate-db-job.yaml +++ /dev/null @@ -1,45 +0,0 @@ -{{- if and .Values.idp.enabled .Values.idp.hooks.migrate }} -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ include "identity-idp-chart.fullname" . }}-migration-database - labels: - {{- include "identity-idp-chart.idp.labels" . | nindent 4 }} - annotations: - {{- toYaml .Values.idp.hooks.annotations | nindent 4 }} -spec: - backoffLimit: {{ .Values.idp.hooks.backoffLimit }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "identity-idp-chart.idp.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.idp.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "identity-idp-chart.idp.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.idp.podSecurityContext | nindent 8 }} - restartPolicy: OnFailure - containers: - - name: {{ include "identity-idp-chart.fullname" . }}-migrate-database - securityContext: - {{- toYaml .Values.idp.securityContext | nindent 12 }} - image: "{{ .Values.idp.image.repository }}:{{ .Values.idp.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.idp.image.pullPolicy }} - env: - {{- toYaml .Values.idp.env | nindent 12 }} - command: [ bundle ] - args: - - exec - - rake - - db:migrate - resources: - {{- toYaml .Values.idp.resources | nindent 12 }} - backoffLimit: 4 -{{- end }} diff --git a/charts/templates/idp/seed-db-job.yaml b/charts/templates/idp/seed-db-job.yaml deleted file mode 100644 index 154f65990ac..00000000000 --- a/charts/templates/idp/seed-db-job.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# Seeds data into database on first install -{{- if and .Values.idp.enabled .Values.idp.hooks.seed }} -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ include "identity-idp-chart.idp.fullname" . }}-seed-database - labels: - {{- include "identity-idp-chart.idp.labels" . | nindent 4 }} - annotations: - {{- toYaml .Values.idp.hooks.annotations | nindent 4 }} -spec: - backoffLimit: {{ .Values.idp.hooks.backoffLimit }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "identity-idp-chart.idp.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.idp.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "identity-idp-chart.idp.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.idp.podSecurityContext | nindent 8 }} - restartPolicy: OnFailure - containers: - - name: {{ include "identity-idp-chart.fullname" . }}-seed-database - securityContext: - {{- toYaml .Values.idp.securityContext | nindent 12 }} - image: "{{ .Values.idp.image.repository }}:{{ .Values.idp.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.idp.image.pullPolicy }} - env: - {{- toYaml .Values.idp.env | nindent 12 }} - command: [ bundle ] - args: - - exec - - rake - - db:seed - resources: - {{- toYaml .Values.idp.resources | nindent 12 }} - backoffLimit: 4 -{{- end }} diff --git a/charts/templates/idp/service.yaml b/charts/templates/idp/service.yaml deleted file mode 100644 index 5bef9e70e12..00000000000 --- a/charts/templates/idp/service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if .Values.idp.enabled -}} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "identity-idp-chart.idp.fullname" . }} - labels: - {{- include "identity-idp-chart.idp.labels" . | nindent 4 }} -spec: - type: {{ .Values.idp.service.type }} - ports: - - port: {{ .Values.idp.service.port }} - targetPort: {{ .Values.idp.service.port }} - protocol: TCP - name: https - selector: - {{- include "identity-idp-chart.idp.selectorLabels" . | nindent 4 }} -{{- end }} diff --git a/charts/templates/idp/serviceaccount.yaml b/charts/templates/idp/serviceaccount.yaml deleted file mode 100644 index 97032a47ef3..00000000000 --- a/charts/templates/idp/serviceaccount.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if and .Values.idp.serviceAccount.create .Values.idp.enabled -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "identity-idp-chart.idp.serviceAccountName" . }} - labels: - {{- include "identity-idp-chart.idp.labels" . | nindent 4 }} - {{- with .Values.idp.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/charts/templates/postgres/deployment.yaml b/charts/templates/postgres/deployment.yaml deleted file mode 100644 index ad6056312a5..00000000000 --- a/charts/templates/postgres/deployment.yaml +++ /dev/null @@ -1,40 +0,0 @@ -{{- if .Values.postgresql.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "identity-idp-chart.postgres.fullname" . }} -spec: - replicas: 1 - selector: - matchLabels: - {{- include "identity-idp-chart.postgres.selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - {{- include "identity-idp-chart.postgres.selectorLabels" . | nindent 8 }} - spec: - containers: - - name: {{ include "identity-idp-chart.postgres.fullname" . }} - image: {{ .Values.postgresql.image }}:{{ .Values.postgresql.tag }} - env: - - name: POSTGRES_USER - value: {{ .Values.postgresql.username }} - - name: POSTGRES_PASSWORD - value: {{ .Values.postgresql.password }} - - name: POSTGRES_DB - value: {{ .Values.postgresql.database }} - ports: - - containerPort: {{ .Values.postgresql.service.port }} - volumeMounts: - - name: {{ include "identity-idp-chart.postgres.fullname" . }}-data - mountPath: /var/lib/postgresql/data - volumes: - {{- if .Values.postgresql.persistence.enabled }} - - name: {{ include "identity-idp-chart.postgres.fullname" . }}-data - persistentVolumeClaim: - claimName: {{ include "identity-idp-chart.postgres.fullname" . }}-pvc - {{- else }} - - name: {{ include "identity-idp-chart.postgres.fullname" . }}-data - emptyDir: {} - {{- end }} -{{- end }} diff --git a/charts/templates/postgres/service.yaml b/charts/templates/postgres/service.yaml deleted file mode 100644 index 9e16bae4dee..00000000000 --- a/charts/templates/postgres/service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{- if .Values.postgresql.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "identity-idp-chart.postgres.fullname" . }} -spec: - type: ClusterIP - ports: - - port: {{ .Values.postgresql.service.port }} - targetPort: {{ .Values.postgresql.service.port }} - protocol: TCP - selector: - {{- include "identity-idp-chart.postgres.selectorLabels" . | nindent 4 }} -{{- end }} diff --git a/charts/templates/redis/deployment.yaml b/charts/templates/redis/deployment.yaml deleted file mode 100644 index ef88cd09fab..00000000000 --- a/charts/templates/redis/deployment.yaml +++ /dev/null @@ -1,36 +0,0 @@ -{{- if .Values.redis.enabled }} -# templates/redis/deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "identity-idp-chart.redis.fullname" . }} - labels: - {{- include "identity-idp-chart.redis.labels" . | nindent 4 }} -spec: - selector: - matchLabels: - {{- include "identity-idp-chart.redis.selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - {{- include "identity-idp-chart.redis.selectorLabels" . | nindent 8 }} - spec: - containers: - - name: redis - image: "{{ .Values.redis.image }}:{{ .Values.redis.tag }}" - imagePullPolicy: IfNotPresent - env: - - name: REDIS_USERNAME - value: {{ .Values.redis.username }} - - name: REDIS_PASSWORD - value: {{ .Values.redis.password }} - ports: - - name: redis - containerPort: {{ .Values.redis.service.port }} - volumeMounts: - - name: redis-data - mountPath: /data - volumes: - - name: redis-data - emptyDir: {} -{{- end }} diff --git a/charts/templates/redis/service.yaml b/charts/templates/redis/service.yaml deleted file mode 100644 index f7af9d1a39b..00000000000 --- a/charts/templates/redis/service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Values.redis.enabled }} -# templates/redis/service.yaml -apiVersion: v1 -kind: Service -metadata: - name: {{ include "identity-idp-chart.redis.fullname" . }} - labels: - {{- include "identity-idp-chart.redis.labels" . | nindent 4 }} -spec: - type: ClusterIP - ports: - - port: {{ .Values.redis.service.port }} - targetPort: redis - protocol: TCP - name: redis - selector: - {{- include "identity-idp-chart.redis.selectorLabels" . | nindent 4 }} -{{- end }} diff --git a/charts/templates/tests/test-connection.yaml b/charts/templates/tests/test-connection.yaml deleted file mode 100644 index 78ad282c016..00000000000 --- a/charts/templates/tests/test-connection.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "identity-idp-chart.idp.fullname" . }}-test-connection" - labels: - {{- include "identity-idp-chart.idp.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ include "identity-idp-chart.idp.fullname" . }}:{{ .Values.idp.service.port }}'] - restartPolicy: Never diff --git a/charts/templates/worker/deployment.yaml b/charts/templates/worker/deployment.yaml deleted file mode 100644 index 8c4bb151544..00000000000 --- a/charts/templates/worker/deployment.yaml +++ /dev/null @@ -1,66 +0,0 @@ -{{- if .Values.worker.enabled -}} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "identity-idp-chart.worker.fullname" . }} - labels: - {{- include "identity-idp-chart.worker.labels" . | nindent 4 }} -spec: - {{- if not .Values.worker.autoscaling.enabled }} - replicas: {{ .Values.worker.replicaCount }} - {{- end }} - selector: - matchLabels: - {{- include "identity-idp-chart.worker.selectorLabels" . | nindent 6 }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "identity-idp-chart.worker.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "identity-idp-chart.worker.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.worker.image.repository }}:{{ .Values.worker.image.tag | default .Chart.AppVersion }}" - command: ["bundle", "exec", "good_job", "start", "--probe-port={{ .Values.worker.service.port }}"] - env: - {{- toYaml .Values.worker.env | nindent 12 }} - imagePullPolicy: {{ .Values.worker.image.pullPolicy }} - ports: - - name: http - containerPort: {{ .Values.worker.service.port }} - protocol: TCP - livenessProbe: - httpGet: - path: / - port: {{ .Values.worker.service.port }} - readinessProbe: - httpGet: - path: / - port: {{ .Values.worker.service.port }} - resources: - {{- toYaml .Values.worker.resources | nindent 12 }} - {{- with .Values.worker.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.worker.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.worker.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} diff --git a/charts/templates/worker/serviceaccount.yaml b/charts/templates/worker/serviceaccount.yaml deleted file mode 100644 index a801465375f..00000000000 --- a/charts/templates/worker/serviceaccount.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if and .Values.worker.serviceAccount.create .Values.worker.enabled -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "identity-idp-chart.worker.serviceAccountName" . }} - labels: - {{- include "identity-idp-chart.worker.labels" . | nindent 4 }} - {{- with .Values.worker.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/charts/values.yaml b/charts/values.yaml deleted file mode 100644 index 10d00309b22..00000000000 --- a/charts/values.yaml +++ /dev/null @@ -1,235 +0,0 @@ -# Default values for identity-idp-chart. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -idp: - enabled: true - env: - - name: POSTGRES_SSLMODE - value: "prefer" - - name: POSTGRES_NAME - value: "idp" - - name: POSTGRES_HOST - value: "rails-app-test-identity-idp-chart-postgres.review-apps" - - name: POSTGRES_USERNAME - value: "postgres" - - name: POSTGRES_PASSWORD - value: "postgres" - - name: POSTGRES_WORKER_SSLMODE - value: "prefer" - - name: POSTGRES_WORKER_NAME - value: "idp-worker-jobs" - - name: POSTGRES_WORKER_HOST - value: "rails-app-test-identity-idp-chart-postgres.review-apps" - - name: POSTGRES_WORKER_USERNAME - value: "postgres" - - name: POSTGRES_WORKER_PASSWORD - value: "postgres" - - name: LOGIN_ENV - value: "dev" - - name: RAILS_OFFLINE - value: "true" - - name: REDIS_IRS_ATTEMPTS_API_URL - value: redis://rails-app-test-identity-idp-chart-redis.review-apps:6379/2 - - name: REDIS_THROTTLE_URL - value: redis://rails-app-test-identity-idp-chart-redis.review-apps:6379/1 - - name: REDIS_URL - value: redis://rails-app-test-identity-idp-chart-redis.review-apps:6379 - - name: ASSET_HOST - value: https://test.review-app.identitysandbox.gov - - name: DOMAIN_NAME - value: test.review-app.identitysandbox.gov - hooks: - # Enable or disable post install rake tasks - create: true # db:create - migrate: true # db:migrate - seed: false # db:seed - backoffLimit: 5 - annotations: # Annotations to add to the job - helm.sh/hook: post-install,post-upgrade - helm.sh/hook-delete-policy: hook-succeeded,hook-failed - replicaCount: 1 - image: - repository: 894947205914.dkr.ecr.us-west-2.amazonaws.com/review_app/idp - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: latest - imagePullSecrets: [] - nameOverride: "" - nodeSelector: {} - tolerations: [] - affinity: {} - fullnameOverride: "" - serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: - eks.amazonaws.com/role-arn: "arn:aws:iam::894947205914:role/reviewapp_idp_iam_role" - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" - podAnnotations: {} - podSecurityContext: {} - # fsGroup: 2000 - securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - service: - type: ClusterIP - port: 3000 - ingress: - enabled: true - className: "alb" - annotations: - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/group.name: review-app - alb.ingress.kubernetes.io/backend-protocol: HTTPS - alb.ingress.kubernetes.io/healthcheck-path: "/api/health" - alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' - alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:894947205914:certificate/a696b1c7-00cf-4188-8361-bca780b7fc49 - hosts: - - host: test.review-app.identitysandbox.gov - paths: - - path: /* - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - - resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - - autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -worker: - enabled: true - env: - - name: POSTGRES_SSLMODE - value: "prefer" - - name: POSTGRES_NAME - value: "idp" - - name: POSTGRES_HOST - value: "rails-app-test-identity-idp-chart-postgres.review-apps" - - name: POSTGRES_USERNAME - value: "postgres" - - name: POSTGRES_PASSWORD - value: "postgres" - - name: POSTGRES_WORKER_SSLMODE - value: "prefer" - - name: POSTGRES_WORKER_NAME - value: "idp-worker-jobs" - - name: POSTGRES_WORKER_HOST - value: "rails-app-test-identity-idp-chart-postgres.review-apps" - - name: POSTGRES_WORKER_USERNAME - value: "postgres" - - name: POSTGRES_WORKER_PASSWORD - value: "postgres" - - name: LOGIN_ENV - value: "dev" - - name: RAILS_OFFLINE - value: "true" - - name: REDIS_IRS_ATTEMPTS_API_URL - value: redis://rails-app-test-identity-idp-chart-redis.review-apps:6379/2 - - name: REDIS_THROTTLE_URL - value: redis://rails-app-test-identity-idp-chart-redis.review-apps:6379/1 - - name: REDIS_URL - value: redis://rails-app-test-identity-idp-chart-redis.review-apps:6379 - replicaCount: 1 - image: - #repository: stephenshelton/identity-idp - repository: 894947205914.dkr.ecr.us-west-2.amazonaws.com/review_app/idp - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: latest - imagePullSecrets: [] - nameOverride: "" - nodeSelector: {} - tolerations: [] - affinity: {} - fullnameOverride: "" - serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: - eks.amazonaws.com/role-arn: "arn:aws:iam::894947205914:role/reviewapp_worker_iam_role" - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" - podAnnotations: {} - podSecurityContext: {} - # fsGroup: 2000 - securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - service: - type: ClusterIP - port: 7001 - - resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - - autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -postgresql: - image: postgis/postgis - tag: "14-3.3" - enabled: true - username: postgres - password: postgres - database: idp - persistence: - enabled: false - size: 8Gi - service: - port: 5432 - -redis: - enabled: true - image: redis - tag: "7.0" - username: redis - password: password - persistence: - enabled: false - size: 8Gi - service: - port: 6379 diff --git a/config/application.yml.default b/config/application.yml.default index 71aeeb71819..e695a2ca798 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -228,7 +228,7 @@ phone_confirmation_max_attempt_window_in_minutes: 1_440 phone_service_check: true phone_recaptcha_mock_validator: true phone_recaptcha_score_threshold: 0.0 -phone_recaptcha_country_score_overrides: '{"AS":0.0,"GU":0.0,"MP":0.0,"PR":0.0,"US":0.0,"VI":0.0}' +phone_recaptcha_country_score_overrides: '{"AS":0.0,"GU":0.0,"MP":0.0,"PR":0.0,"US":0.0,"VI":0.0,"CA":0.0,"MX":0.0}' phone_setups_per_ip_limit: 25 phone_setups_per_ip_period: 300 phone_setups_per_ip_track_only_mode: false diff --git a/config/application.yml.default.docker b/config/application.yml.default.docker index 2243c87febe..196f6af58cb 100644 --- a/config/application.yml.default.docker +++ b/config/application.yml.default.docker @@ -17,7 +17,9 @@ production: redis_url: ['env', 'REDIS_URL'] password_pepper: f22d4b2cafac9066fe2f4416f5b7a32c session_encryption_key: 27bad3c25711099429c1afdfd1890910f3b59f5a4faec1c85e945cb8b02b02f261ba501d99cfbb4fab394e0102de6fecf8ffe260f322f610db3e96b2a775c120 - piv_cac_verify_token_secret: ee7f20f44cdc2ba0c6830f70470d1d1d059e1279cdb58134db92b35947b1528ef5525ece5910cf4f2321ab989a618feea12ef95711dbc62b9601e8520a34ee12 + piv_cac_service_url: ['env', 'PIV_CAC_SERVICE_URL'] + piv_cac_verify_token_secret: "a6ed2fb16320ae85a7a8e48f4b0eeb6afca5f1ac64af2a05a0c486df1c20b693987832a11f0910729f199b3ce5c7609fe6d580bed428d035ea8460990e38a382" + piv_cac_verify_token_url: ['env', 'PIV_CAC_VERIFY_TOKEN_URL'] secret_key_base: development_secret_key_base domain_name: ['env', 'DOMAIN_NAME'] use_kms: false diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml index 9a0ed82f110..3385fe7c07c 100644 --- a/config/locales/doc_auth/en.yml +++ b/config/locales/doc_auth/en.yml @@ -15,6 +15,7 @@ en: upload_picture: Upload photo errors: alerts: + address_check: We couldn’t read the address on your ID. Try taking new pictures. barcode_content_check: We couldn’t read the barcode on the back of your ID. It could be because of a problem with the barcode, or the barcode is a new type that we don’t recognize yet. Use another state‑issued ID if diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml index 37e96066ac6..1f1f3407c73 100644 --- a/config/locales/doc_auth/es.yml +++ b/config/locales/doc_auth/es.yml @@ -15,6 +15,8 @@ es: upload_picture: Subir foto errors: alerts: + address_check: No pudimos leer la dirección en su documento de identidad. + Intente de tomar nuevas fotos. barcode_content_check: No pudimos leer el código de barras en el reverso de su documento de identidad. Puede ser debido a un problema con el código de barras, o bien el código de barras es un nuevo tipo que aún no diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml index 1a2c9ce03a2..47fb28b8d34 100644 --- a/config/locales/doc_auth/fr.yml +++ b/config/locales/doc_auth/fr.yml @@ -15,6 +15,8 @@ fr: upload_picture: Télécharger une photo errors: alerts: + address_check: Nous n’avons pas pu lire l’adresse sur votre pièce d’identité. + Essayez de prendre de nouvelles photos. barcode_content_check: Nous n’avons pas pu lire le code-barres au verso de votre carte d’identité. Cela pourrait être dû à un problème avec le code-barres, ou le code-barres est d’un type nouveau que nous ne diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index 4f4146cc122..d331b10ef3d 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -56,6 +56,7 @@ en: pattern_mismatch: ssn: 'Enter a nine-digit Social Security number' zipcode: Enter a 5 or 9 digit ZIP Code + zipcode_five: Enter a 5 digit ZIP Code failure: attempts_html: one: For security reasons, you have one attempt remaining to diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index 753352e6088..6056fcee9f2 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -57,6 +57,7 @@ es: pattern_mismatch: ssn: 'Ingrese un número de Seguro Social de nueve dígitos' zipcode: Ingresa un código postal de 5 o 9 dígitos + zipcode_five: Ingresa un código postal de 5 dígitos failure: attempts_html: one: Por motivos de seguridad, le quedan un intento para añadir diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index 89989952b94..5de4838c0e0 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -60,6 +60,7 @@ fr: pattern_mismatch: ssn: 'Entrez un numéro de sécurité sociale à neuf chiffres' zipcode: Entrez un code postal à 5 ou 9 chiffres + zipcode_five: Entrez un code postal à 5 chiffres failure: attempts_html: one: Pour des raisons de sécurité, il vous reste une tentative @@ -168,9 +169,9 @@ fr: - Vous devrez ressaisir vos informations personnelles, comme votre nom, pièce d’identité officielle, etc. form: - address1: Adresse Ligne 1 - address2: Adresse Ligne 2 - address2_optional: Adresse Ligne 2 (optional) + address1: Adresse ligne 1 + address2: Adresse ligne 2 + address2_optional: Adresse ligne 2 (optional) city: Ville dob: Date de naissance first_name: Prénom diff --git a/config/locales/mfa/en.yml b/config/locales/mfa/en.yml index 24351543e5f..fb04d5701b1 100644 --- a/config/locales/mfa/en.yml +++ b/config/locales/mfa/en.yml @@ -4,8 +4,6 @@ en: account_info: Adding another authentication method prevents you from getting locked out of your account if you lose one of your methods. add: Add another method - additional_mfa_required: - heading: Authentication method setup info: Add another layer of security by selecting a multi-factor authentication method. We recommend you select at least two different options in case you lose one of your methods. diff --git a/config/locales/mfa/es.yml b/config/locales/mfa/es.yml index ba3298114f0..957e29248f9 100644 --- a/config/locales/mfa/es.yml +++ b/config/locales/mfa/es.yml @@ -4,8 +4,6 @@ es: account_info: Agregar otro método de autenticación evita que se le bloquee el acceso a su cuenta si pierde uno de sus métodos. add: Agregar otro método - additional_mfa_required: - heading: Configuración del método de autenticación info: Agregue otro nivel de seguridad seleccionando un método de autentificación de varios factores. Le recomendamos seleccionar al menos dos opciones diferentes en caso de que pierda uno de los métodos. diff --git a/config/locales/mfa/fr.yml b/config/locales/mfa/fr.yml index bd60f2734b6..5ed4e0872e5 100644 --- a/config/locales/mfa/fr.yml +++ b/config/locales/mfa/fr.yml @@ -4,8 +4,6 @@ fr: account_info: L’ajout d’une autre méthode d’authentification vous empêche d’être bloqué sur votre compte si vous perdez l’une de vos méthodes. add: Agregar otro método - additional_mfa_required: - heading: Configuration de la méthode d’authentification info: Ajoutez une autre couche de sécurité en sélectionnant une méthode d’authentification multi-facteurs. Nous vous recommandons de sélectionner au moins deux options différentes au cas où vous perdriez l’une de vos diff --git a/config/routes.rb b/config/routes.rb index deae3c521a9..3ba43611fb6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -255,8 +255,6 @@ get '/authentication_methods_setup' => 'users/two_factor_authentication_setup#index' patch '/authentication_methods_setup' => 'users/two_factor_authentication_setup#create' - get '/second_mfa_setup' => 'users/mfa_selection#index' - patch '/second_mfa_setup' => 'users/mfa_selection#update' get '/phone_setup' => 'users/phone_setup#index' post '/phone_setup' => 'users/phone_setup#create' get '/users/two_factor_authentication' => 'users/two_factor_authentication#show', @@ -375,8 +373,14 @@ # sometimes underscores get messed up when linked to via SMS as: :capture_doc_dashes - get '/in_person_proofing/ssn' => 'in_person/ssn#show' - put '/in_person_proofing/ssn' => 'in_person/ssn#update' + # DEPRECATION NOTICE + # Usage of the /in_person_proofing/ssn routes is deprecated. + # Use the /in_person/ssn routes instead. + # + # These have been left in temporarily to prevent any impact to users + # during the deprecation process. + get '/in_person_proofing/ssn' => redirect('/verify/in_person/ssn', status: 307) + put '/in_person_proofing/ssn' => redirect('/verify/in_person/ssn', status: 307) get '/in_person' => 'in_person#index' get '/in_person/ready_to_verify' => 'in_person/ready_to_verify#show', @@ -384,6 +388,8 @@ post '/in_person/usps_locations' => 'in_person/usps_locations#index' put '/in_person/usps_locations' => 'in_person/usps_locations#update' post '/in_person/addresses' => 'in_person/address_search#index' + get '/in_person/ssn' => 'in_person/ssn#show' + put '/in_person/ssn' => 'in_person/ssn#update' get '/in_person/verify_info' => 'in_person/verify_info#show' put '/in_person/verify_info' => 'in_person/verify_info#update' get '/in_person/:step' => 'in_person#show', as: :in_person_step diff --git a/lib/cleanup/destroy_unused_providers.rb b/lib/cleanup/destroy_unused_providers.rb index e319a6e72bd..29849d80d66 100644 --- a/lib/cleanup/destroy_unused_providers.rb +++ b/lib/cleanup/destroy_unused_providers.rb @@ -28,6 +28,8 @@ def run records.print_data + break if records.service_provider.in_person_enrollments.size > 0 + stdout.puts "Type 'yes' and hit enter to continue and " \ "destroy this service provider and associated records:\n" diff --git a/lib/cleanup/destroyable_records.rb b/lib/cleanup/destroyable_records.rb index 31228211269..9ba55009c45 100644 --- a/lib/cleanup/destroyable_records.rb +++ b/lib/cleanup/destroyable_records.rb @@ -28,9 +28,14 @@ def print_data stdout.puts "\n" stdout.puts '********' - stdout.puts "This provider has #{in_person_enrollments.size} in person enrollments " \ + if in_person_enrollments.size == 0 + stdout.puts "This provider has #{in_person_enrollments.size} in person enrollments " \ + "that will be destroyed" + else + stdout.puts "\e[31mThis provider has #{in_person_enrollments.size} in person enrollments " \ "that will be destroyed - Please handle these removals manually. " \ - "For more details check https://cm-jira.usa.gov/browse/LG-10679" + "For more details check https://cm-jira.usa.gov/browse/LG-10679\e[0m" + end stdout.puts "\n" stdout.puts '*******' diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 9397b94b108..eccbe357b4f 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -357,9 +357,9 @@ def self.build_store(config_map) config.add(:pinpoint_voice_configs, type: :json) config.add(:pinpoint_voice_pool_size, type: :integer) config.add(:piv_cac_service_timeout, type: :float) - config.add(:piv_cac_service_url) + config.add(:piv_cac_service_url, type: :string) config.add(:piv_cac_verify_token_secret) - config.add(:piv_cac_verify_token_url) + config.add(:piv_cac_verify_token_url, type: :string) config.add(:platform_auth_set_up_enabled, type: :boolean) config.add(:poll_rate_for_verify_in_seconds, type: :integer) config.add(:proof_address_max_attempt_window_in_minutes, type: :integer) diff --git a/lib/reporting/cloudwatch_client.rb b/lib/reporting/cloudwatch_client.rb index 0a15c24033f..15e54c9df6c 100644 --- a/lib/reporting/cloudwatch_client.rb +++ b/lib/reporting/cloudwatch_client.rb @@ -1,6 +1,4 @@ require 'aws-sdk-cloudwatchlogs' -require 'concurrent-ruby' -require 'ruby-progressbar' module Reporting class CloudwatchClient @@ -27,7 +25,7 @@ def initialize( slice_interval: 1.day, logger: nil, progress: true, - log_group_name: 'prod_/srv/idp/shared/log/events.log' + log_group_name: default_events_log ) @ensure_complete_logs = ensure_complete_logs @num_threads = num_threads @@ -182,6 +180,13 @@ def show_progress? !!@progress end + # The prod events log ('prod_/srv/idp/shared/log/events.log') or equivalent in lower + # environments + def default_events_log + env = Identity::Hostdata.in_datacenter? ? Identity::Hostdata.env : 'prod' + "#{env}_/srv/idp/shared/log/events.log" + end + private def log(level, message) diff --git a/lib/reporting/identity_verification_report.rb b/lib/reporting/identity_verification_report.rb index 2953a0a5fd7..bf9684ac159 100644 --- a/lib/reporting/identity_verification_report.rb +++ b/lib/reporting/identity_verification_report.rb @@ -17,9 +17,9 @@ class IdentityVerificationReport attr_reader :issuer, :time_range module Events + IDV_DOC_AUTH_WELCOME = 'IdV: doc auth welcome visited' + IDV_DOC_AUTH_GETTING_STARTED = 'IdV: doc auth getting_started visited' IDV_DOC_AUTH_IMAGE_UPLOAD = 'IdV: doc auth image upload vendor submitted' - IDV_GPO_ADDRESS_LETTER_REQUESTED = 'IdV: USPS address letter requested' - USPS_IPP_ENROLLMENT_CREATED = 'USPS IPPaaS enrollment created' IDV_FINAL_RESOLUTION = 'IdV: final resolution' GPO_VERIFICATION_SUBMITTED = 'IdV: GPO verification submitted' USPS_ENROLLMENT_STATUS_UPDATED = 'GetUspsProofingResultsJob: Enrollment status updated' @@ -29,6 +29,13 @@ def self.all_events end end + module Results + IDV_FINAL_RESOLUTION_VERIFIED = 'IdV: final resolution - Verified' + IDV_FINAL_RESOLUTION_FRAUD_REVIEW = 'IdV: final resolution - Fraud Review Pending' + IDV_FINAL_RESOLUTION_GPO = 'IdV: final resolution - GPO Pending' + IDV_FINAL_RESOLUTION_IN_PERSON = 'IdV: final resolution - In Person Proofing' + end + # @param [String] isssuer # @param [Range