diff --git a/.mocharc.js b/.mocharc.js index 6a156b6e61d..1806a14ce37 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -4,6 +4,5 @@ module.exports = /** @type {import('mocha').MochaOptions} */ ({ require: ['./spec/javascript/support/mocha.js'], file: 'spec/javascript/spec_helper.js', extension: ['js', 'jsx', 'ts', 'tsx'], - loader: ['quibble'], conditions: ['source'], }); diff --git a/Gemfile.lock b/Gemfile.lock index 2cb32e99b65..eea82ac68fa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -321,8 +321,8 @@ GEM factory_bot_rails (6.4.3) factory_bot (~> 6.4) railties (>= 5.0.0) - faker (2.19.0) - i18n (>= 1.6, < 2) + faker (3.4.2) + i18n (>= 1.8.11, < 2) faraday (2.10.0) faraday-net_http (>= 2.0, < 3.2) logger diff --git a/app/controllers/idv/agreement_controller.rb b/app/controllers/idv/agreement_controller.rb index 3a4b052d757..64ced91aba2 100644 --- a/app/controllers/idv/agreement_controller.rb +++ b/app/controllers/idv/agreement_controller.rb @@ -36,7 +36,6 @@ def update ) if result.success? - idv_session.idv_consent_given = true idv_session.idv_consent_given_at = Time.zone.now if IdentityConfig.store.in_person_proofing_opt_in_enabled && diff --git a/app/controllers/idv/in_person/state_id_controller.rb b/app/controllers/idv/in_person/state_id_controller.rb index ea073a12fd0..ad9bc723cee 100644 --- a/app/controllers/idv/in_person/state_id_controller.rb +++ b/app/controllers/idv/in_person/state_id_controller.rb @@ -6,7 +6,6 @@ class StateIdController < ApplicationController include Idv::AvailabilityConcern include IdvStepConcern - before_action :render_404_if_controller_not_enabled before_action :redirect_unless_enrollment # confirm previous step is complete before_action :set_usps_form_presenter @@ -92,11 +91,6 @@ def self.step_info private - def render_404_if_controller_not_enabled - render_not_found unless - IdentityConfig.store.in_person_state_id_controller_enabled - end - def redirect_unless_enrollment redirect_to idv_document_capture_url unless current_user.establishing_in_person_enrollment end diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index d4f9d4cdbfa..5284d5e6881 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -75,7 +75,10 @@ def prev_url def pii pii_from_user = user_session.dig('idv/in_person', :pii_from_user) || {} - pii_from_user.merge(ssn: idv_session.ssn) + pii_from_user.merge( + consent_given_at: idv_session.idv_consent_given_at, + ssn: idv_session.ssn, + ) end # override IdvSessionConcern diff --git a/app/controllers/idv/verify_info_controller.rb b/app/controllers/idv/verify_info_controller.rb index c69563bb272..16d0a51d01f 100644 --- a/app/controllers/idv/verify_info_controller.rb +++ b/app/controllers/idv/verify_info_controller.rb @@ -79,6 +79,7 @@ def analytics_arguments def pii idv_session.pii_from_doc.to_h.merge( ssn: idv_session.ssn, + consent_given_at: idv_session.idv_consent_given_at, **idv_session.updated_user_address.to_h, ).with_indifferent_access end diff --git a/app/controllers/saml_idp_controller.rb b/app/controllers/saml_idp_controller.rb index 362106b8e35..012dd21a40e 100644 --- a/app/controllers/saml_idp_controller.rb +++ b/app/controllers/saml_idp_controller.rb @@ -129,7 +129,7 @@ def capture_analytics finish_profile: user_has_pending_profile?, requested_ial: requested_ial, request_signed: saml_request.signed?, - matching_cert_serial: saml_request.service_provider.matching_cert&.serial&.to_s, + matching_cert_serial:, requested_nameid_format: saml_request.name_id_format, ) @@ -140,6 +140,12 @@ def capture_analytics analytics.saml_auth(**analytics_payload) end + def matching_cert_serial + saml_request.matching_cert&.serial&.to_s + rescue SamlIdp::XMLSecurity::SignedDocument::ValidationError + nil + end + def log_external_saml_auth_request return unless external_saml_request? diff --git a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb index 20b912feda6..c58a1967315 100644 --- a/app/controllers/two_factor_authentication/webauthn_verification_controller.rb +++ b/app/controllers/two_factor_authentication/webauthn_verification_controller.rb @@ -7,7 +7,6 @@ class WebauthnVerificationController < ApplicationController include NewDeviceConcern before_action :check_sp_required_mfa - before_action :check_if_device_supports_platform_auth, only: :show before_action :confirm_webauthn_enabled, only: :show def show @@ -23,17 +22,6 @@ def confirm private - def check_if_device_supports_platform_auth - return unless user_session.has_key?(:platform_authenticator_available) - if platform_authenticator? && !device_supports_webauthn_platform? - redirect_to login_two_factor_options_url - end - end - - def device_supports_webauthn_platform? - user_session.delete(:platform_authenticator_available) == true - end - def handle_webauthn_result(result) handle_verification_for_authentication_context( result:, diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb index b56ccf6524b..57a5444489e 100644 --- a/app/controllers/users/two_factor_authentication_controller.rb +++ b/app/controllers/users/two_factor_authentication_controller.rb @@ -338,17 +338,19 @@ def otp_rate_limiter def redirect_url if !mobile? && TwoFactorAuthentication::PivCacPolicy.new(current_user).enabled? login_two_factor_piv_cac_url + elsif TwoFactorAuthentication::WebauthnPolicy.new(current_user).platform_enabled? + if user_session[:platform_authenticator_available] == false + login_two_factor_options_url + else + login_two_factor_webauthn_url(platform: true) + end elsif TwoFactorAuthentication::WebauthnPolicy.new(current_user).enabled? - login_two_factor_webauthn_url(webauthn_params) + login_two_factor_webauthn_url elsif TwoFactorAuthentication::AuthAppPolicy.new(current_user).enabled? login_two_factor_authenticator_url end end - def webauthn_params - { platform: current_user.webauthn_configurations.platform_authenticators.present? } - end - def handle_too_many_short_term_otp_sends(method:, default:) analytics.rate_limit_reached( limiter_type: short_term_otp_rate_limiter.rate_limit_type, diff --git a/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx b/app/javascript/packages/document-capture/components/document-capture-review-issues.spec.tsx similarity index 62% rename from spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx rename to app/javascript/packages/document-capture/components/document-capture-review-issues.spec.tsx index 729e574c145..cc37fdf45aa 100644 --- a/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx +++ b/app/javascript/packages/document-capture/components/document-capture-review-issues.spec.tsx @@ -2,6 +2,8 @@ import { render, screen, within } from '@testing-library/react'; import DocumentCaptureReviewIssues from '@18f/identity-document-capture/components/document-capture-review-issues'; import { InPersonContext } from '@18f/identity-document-capture/context'; import { toFormEntryError } from '@18f/identity-document-capture/services/upload'; +import { I18nContext } from '@18f/identity-react-i18n'; +import { I18n } from '@18f/identity-i18n'; import { expect } from 'chai'; import { composeComponents } from '@18f/identity-compose-components'; @@ -9,19 +11,48 @@ describe('DocumentCaptureReviewIssues', () => { const DEFAULT_OPTIONS = { registerField: () => undefined, captureHints: true, - remainingAttempts: 2, value: {}, onChange: () => undefined, onError: () => undefined, + isFailedSelfie: false, + isFailedDocType: false, + isFailedSelfieLivenessOrQuality: false, + remainingSubmitAttempts: Infinity, + unknownFieldErrors: [], + errors: [], + hasDismissed: false, + toPreviousStep: () => undefined, }; + + context('with default props', () => { + it('does not display infinity remaining attempts', () => { + const { queryByText } = render( + + + , + ); + + expect(queryByText('You have Infinity attempts remaining.')).to.equal(null); + }); + }); + context('with doc error', () => { it('renders for non doc type failure', () => { const props = { isFailedDocType: false, + remainingSubmitAttempts: 2, unknownFieldErrors: [ { field: 'general', - error: toFormEntryError({ field: 'general', message: 'general error' }), + error: toFormEntryError({ field: 'network', message: 'general error' }), }, ], errors: [ @@ -44,6 +75,16 @@ describe('DocumentCaptureReviewIssues', () => { }, }, ], + [ + I18nContext.Provider, + { + value: new I18n({ + strings: { + 'idv.failure.attempts_html': 'You have %{count} attempts remaining.', + }, + }), + }, + ], [ DocumentCaptureReviewIssues, { @@ -58,6 +99,8 @@ describe('DocumentCaptureReviewIssues', () => { expect(getByText('general error')).to.be.ok(); + expect(getByText('You have 2 attempts remaining.')).to.be.ok(); + // tips header expect(getByText('doc_auth.tips.review_issues_id_header_text')).to.be.ok(); const lists = getAllByRole('list'); @@ -80,31 +123,37 @@ describe('DocumentCaptureReviewIssues', () => { }); it('renders for a doc type failure', () => { - const props = { - isFailedDocType: true, - unknownFieldErrors: [ - { - field: 'general', - error: toFormEntryError({ field: 'general', message: 'general error' }), - }, - ], - errors: [ - { - field: 'front', - error: toFormEntryError({ field: 'front', message: 'front side doc type error' }), - }, - { - field: 'back', - error: toFormEntryError({ field: 'back', message: 'back side doc type error' }), - }, - ], - }; const { getByText, getByLabelText, getByRole } = render( - + , diff --git a/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx b/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx index 221b5f88fa5..2b7366b0b5a 100644 --- a/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx +++ b/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx @@ -60,11 +60,13 @@ function DocumentCaptureReviewIssues({ altFailedDocTypeMsg={isFailedDocType ? t('doc_auth.errors.doc.wrong_id_type_html') : null} hasDismissed={hasDismissed} /> -

- -

+ {Number.isFinite(remainingSubmitAttempts) && ( +

+ +

+ )} {!isFailedDocType && captureHints && ( { user, challenge, excludeCredentials, - authenticatorAttachment: 'cross-platform', - hints: ['security-key'], }); expect(navigator.credentials.create).to.have.been.calledWith({ @@ -130,16 +128,15 @@ describe('enrollWebauthnDevice', () => { context('platform authenticator', () => { it('enrolls a device with correct authenticatorAttachment', async () => { await enrollWebauthnDevice({ + platformAuthenticator: true, user, challenge, excludeCredentials, - authenticatorAttachment: 'platform', - hints: ['client-device'], }); expect(navigator.credentials.create).to.have.been.calledWithMatch({ publicKey: { - hints: ['client-device'], + hints: undefined, authenticatorSelection: { authenticatorAttachment: 'platform', }, @@ -162,7 +159,6 @@ describe('enrollWebauthnDevice', () => { user, challenge, excludeCredentials, - authenticatorAttachment: 'cross-platform', }); expect(result.transports).to.equal(undefined); @@ -182,7 +178,6 @@ describe('enrollWebauthnDevice', () => { user, challenge, excludeCredentials, - authenticatorAttachment: 'cross-platform', }); expect(result.authenticatorDataFlagsValue).to.equal(undefined); diff --git a/app/javascript/packages/webauthn/enroll-webauthn-device.ts b/app/javascript/packages/webauthn/enroll-webauthn-device.ts index bf19d2a75db..f853488173e 100644 --- a/app/javascript/packages/webauthn/enroll-webauthn-device.ts +++ b/app/javascript/packages/webauthn/enroll-webauthn-device.ts @@ -18,15 +18,13 @@ interface AuthenticatorAttestationResponseBrowserSupport type PublicKeyCredentialHintType = 'client-device' | 'security-key' | 'hybrid'; interface EnrollOptions { + platformAuthenticator?: boolean; + user: PublicKeyCredentialUserEntity; challenge: BufferSource; excludeCredentials: PublicKeyCredentialDescriptor[]; - - authenticatorAttachment?: AuthenticatorAttachment; - - hints?: Array; } interface EnrollResult { @@ -80,11 +78,10 @@ const SUPPORTED_ALGORITHMS: COSEAlgorithm[] = [ ]; async function enrollWebauthnDevice({ + platformAuthenticator = false, user, challenge, excludeCredentials, - authenticatorAttachment, - hints, }: EnrollOptions): Promise { const credential = (await navigator.credentials.create({ publicKey: { @@ -94,11 +91,16 @@ async function enrollWebauthnDevice({ pubKeyCredParams: SUPPORTED_ALGORITHMS.map((alg) => ({ alg, type: 'public-key' })), timeout: 800000, attestation: 'none', - hints, + hints: platformAuthenticator ? undefined : ['security-key'], authenticatorSelection: { - // Prevents user from needing to use PIN with Security Key + // A user is assumed to be AAL2 recently authenticated before being permitted to add an + // authentication method to their account. Additionally, unless explicitly discouraged, + // Windows devices will prompt the user to add a PIN to their security key. When used as a + // single-factor authenticator in combination with a memorized secret (password), proving + // possession is sufficient, and a PIN ("something you know") is unnecessary friction that + // contributes to abandonment or loss of access. userVerification: 'discouraged', - authenticatorAttachment, + authenticatorAttachment: platformAuthenticator ? 'platform' : 'cross-platform', }, excludeCredentials, } as PublicKeyCredentialCreationOptionsWithHints, diff --git a/app/javascript/packs/webauthn-setup.ts b/app/javascript/packs/webauthn-setup.ts index 743b99c827c..b0c2d0c9eff 100644 --- a/app/javascript/packs/webauthn-setup.ts +++ b/app/javascript/packs/webauthn-setup.ts @@ -42,6 +42,7 @@ function webauthn() { (document.getElementById('platform_authenticator') as HTMLInputElement).value === 'true'; enrollWebauthnDevice({ + platformAuthenticator, user: { id: longToByteArray(Number((document.getElementById('user_id') as HTMLInputElement).value)), name: (document.getElementById('user_email') as HTMLInputElement).value, @@ -55,8 +56,6 @@ function webauthn() { .split(',') .filter(Boolean), ), - authenticatorAttachment: platformAuthenticator ? 'platform' : 'cross-platform', - hints: platformAuthenticator ? ['client-device', 'hybrid'] : ['security-key'], }) .then((result) => { (document.getElementById('webauthn_id') as HTMLInputElement).value = result.webauthnId; diff --git a/app/jobs/reports/mfa_report.rb b/app/jobs/reports/mfa_report.rb new file mode 100644 index 00000000000..af416981a67 --- /dev/null +++ b/app/jobs/reports/mfa_report.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'reporting/mfa_report' + +module Reports + class MfaReport < BaseReport + REPORT_NAME = 'mfa-report' + + attr_accessor :report_date + + def perform(report_date) + return unless IdentityConfig.store.s3_reports_enabled + + self.report_date = report_date + message = "Report: #{REPORT_NAME} #{report_date}" + subject = "Monthly MFA Report - #{report_date}" + + report_configs.each do |report_hash| + reports = monthly_mfa_emailable_reports(report_hash['issuers']) + + report_hash['emails'].each do |email| + ReportMailer.tables_report( + email:, + subject:, + message:, + reports:, + attachment_format: :csv, + ).deliver_now + end + end + end + + private + + def monthly_mfa_emailable_reports(issuers) + Reporting::MfaReport.new( + issuers:, + time_range: report_date.all_month, + ).as_emailable_reports + end + + def report_configs + IdentityConfig.store.mfa_report_config + end + end +end diff --git a/app/jobs/socure_shadow_mode_proofing_job.rb b/app/jobs/socure_shadow_mode_proofing_job.rb index fc39a3280c2..399f8440f22 100644 --- a/app/jobs/socure_shadow_mode_proofing_job.rb +++ b/app/jobs/socure_shadow_mode_proofing_job.rb @@ -100,6 +100,7 @@ def build_applicant( :phone, :dob, :ssn, + :consent_given_at, ), email: user_email, } diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index f01ca2b86a9..da60ab14e2c 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -1925,9 +1925,9 @@ def idv_doc_auth_verify_polling_wait_visited(**extra) # @option proofing_results [String,nil] context.stages.threatmetrix.exception If an exception was encountered making the request to the vendor, its message is provided here. # @option proofing_results [Boolean] context.stages.threatmetrix.timed_out Whether the request to the vendor timed out. # @option proofing_results [String] context.stages.threatmetrix.transaction_id Vendor-specific transaction ID for the request. + # @option proofing_results [String] context.stages.threatmetrix.session_id Session ID associated with the response. + # @option proofing_results [String] context.stages.threatmetrix.account_lex_id LexID associated with the response. # @option proofing_results [Hash] context.stages.threatmetrix.response_body JSON body of the response returned from the vendor. PII has been redacted. - # @option proofing_results [String] context.stages.threatmetrix.response_body.account_lex_id LexID associated with the response. - # @option proofing_results [String] context.stages.threatmetrix.response_body.session_id Session ID associated with the response. # @option proofing_results [String] context.stages.threatmetrix.review_status One of "pass", "review", "reject". # @param skip_hybrid_handoff [Boolean] Whether the user should skip hybrid handoff (i.e. because they are already on a mobile device) # @param ssn_is_unique [Boolean] Whether another Profile existed with the same SSN at the time the profile associated with the current IdV session was minted. diff --git a/app/services/proofing/ddp_result.rb b/app/services/proofing/ddp_result.rb index 9670d570078..7c5dddc3ebd 100644 --- a/app/services/proofing/ddp_result.rb +++ b/app/services/proofing/ddp_result.rb @@ -7,6 +7,8 @@ class DdpResult :success, :transaction_id, :review_status, + :account_lex_id, + :session_id, :response_body, :client @@ -16,6 +18,8 @@ def initialize( context: {}, exception: nil, transaction_id: nil, + account_lex_id: nil, + session_id: nil, review_status: nil, response_body: nil, client: nil @@ -25,6 +29,8 @@ def initialize( @context = context @exception = exception @transaction_id = transaction_id + @account_lex_id = account_lex_id + @session_id = session_id @response_body = response_body @review_status = review_status @client = client @@ -70,6 +76,8 @@ def to_h timed_out: timed_out?, transaction_id: transaction_id, review_status: review_status, + account_lex_id: account_lex_id, + session_id: session_id, response_body: redacted_response_body, } end diff --git a/app/services/proofing/lexis_nexis/ddp/proofer.rb b/app/services/proofing/lexis_nexis/ddp/proofer.rb index 6b7d296c312..e1a7da5f904 100644 --- a/app/services/proofing/lexis_nexis/ddp/proofer.rb +++ b/app/services/proofing/lexis_nexis/ddp/proofer.rb @@ -36,6 +36,8 @@ def build_result_from_response(verification_response) result.review_status = review_status result.add_error(:request_result, request_result) unless request_result == 'success' result.add_error(:review_status, review_status) unless review_status == 'pass' + result.account_lex_id = body['account_lex_id'] + result.session_id = body['session_id'] result.success = !result.errors? result.client = 'lexisnexis' diff --git a/app/services/proofing/mock/ddp_mock_client.rb b/app/services/proofing/mock/ddp_mock_client.rb index 772443a48b6..a0118e5e60c 100644 --- a/app/services/proofing/mock/ddp_mock_client.rb +++ b/app/services/proofing/mock/ddp_mock_client.rb @@ -39,6 +39,8 @@ def stage 'ddp', ).freeze TRANSACTION_ID = 'ddp-mock-transaction-id-123' + ACCOUNT_LEX_ID = 'super-cool-test-lex-id' + SESSION_ID = 'super-cool-test-session-id' def initialize(response_fixture_file: 'successful_response.json') @response_fixture_file = File.expand_path(response_fixture_file, FIXTURES_DIR) @@ -55,6 +57,8 @@ def proof(applicant) return exception_result if review_status.nil? result.review_status = review_status + result.account_lex_id = ACCOUNT_LEX_ID + result.session_id = SESSION_ID result.response_body = response_body result.add_error(:review_status, review_status) unless review_status == 'pass' diff --git a/app/services/proofing/socure/id_plus/input.rb b/app/services/proofing/socure/id_plus/input.rb index 28070fc51f2..7fd095ee36e 100644 --- a/app/services/proofing/socure/id_plus/input.rb +++ b/app/services/proofing/socure/id_plus/input.rb @@ -16,7 +16,9 @@ module IdPlus :phone, :email, :ssn, + :consent_given_at, keyword_init: true, + allowed_members: [:consent_given_at], ).freeze end end diff --git a/app/services/proofing/socure/id_plus/request.rb b/app/services/proofing/socure/id_plus/request.rb index d15591eb2aa..881c495d63d 100644 --- a/app/services/proofing/socure/id_plus/request.rb +++ b/app/services/proofing/socure/id_plus/request.rb @@ -98,7 +98,7 @@ def body dob: input.dob&.to_date&.to_s, userConsent: true, - consentTimestamp: 5.minutes.ago.iso8601, + consentTimestamp: input.consent_given_at&.to_time&.iso8601, email: input.email, mobileNumber: input.phone, diff --git a/config/application.yml.default b/config/application.yml.default index 4cece14a740..63d0fd90c9b 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -228,6 +228,7 @@ max_mail_events: 4 max_mail_events_window_in_days: 30 max_phone_numbers_per_account: 5 max_piv_cac_per_account: 2 +mfa_report_config: '[]' min_password_score: 3 minimum_wait_before_another_usps_letter_in_hours: 24 mx_timeout: 3 diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index 9d74b63cecd..ac97907c86e 100644 --- a/config/initializers/job_configurations.rb +++ b/config/initializers/job_configurations.rb @@ -9,6 +9,7 @@ cron_every_monday = 'every Monday at 0:25 UTC' # equivalent to '25 0 * * 1' cron_every_monday_1am = 'every Monday at 1:00 UTC' # equivalent to '0 1 * * 1' cron_every_monday_2am = 'every Monday at 2:00 UTC' # equivalent to '0 2 * * 1' +cron_monthly = '30 0 1 * *' # monthly, 0:30 UTC to not overlap with jobs running at 0000 if defined?(Rails::Console) Rails.logger.info 'job_configurations: console detected, skipping schedule' @@ -217,7 +218,7 @@ cron: cron_24h_and_a_bit, args: -> { [Time.zone.yesterday.end_of_day] }, }, - # Previous week's drop of report + # Previous week's drop off report weekly_drop_off_report: { class: 'Reports::DropOffReport', cron: cron_every_monday_1am, @@ -229,6 +230,12 @@ cron: cron_every_monday_2am, args: -> { [Time.zone.yesterday.end_of_day] }, }, + # Previous months's mfa report + monthly_mfa_report: { + class: 'Reports::MfaReport', + cron: cron_monthly, + args: -> { [Time.zone.yesterday.end_of_day] }, + }, }.compact end # rubocop:enable Metrics/BlockLength diff --git a/config/service_providers.localdev.yml b/config/service_providers.localdev.yml index 362e0111672..9b905e8ad0f 100644 --- a/config/service_providers.localdev.yml +++ b/config/service_providers.localdev.yml @@ -450,6 +450,21 @@ development: friendly_name: 'Example Sinatra App' in_person_proofing_enabled: true + 'urn:gov:gsa:openidconnect:sp:sinatra_pkce': + agency_id: 1 + ial: 2 + push_notification_url: http://localhost:9292/api/push_notifications + return_to_sp_url: 'http://localhost:9292' + redirect_uris: + - 'http://localhost:9292/' + - 'http://localhost:9292/auth/result' + - 'http://localhost:9292/logout' + certs: + - 'sp_sinatra_demo' + friendly_name: 'Example Sinatra App with PKCE' + in_person_proofing_enabled: true + pkce: true + 'urn:gov:gsa:openidconnect:sp:expressjs': agency: 'GSA' certs: diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 8f9c0909de1..3dd9c8f1f98 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -247,6 +247,7 @@ def self.store config.add(:max_mail_events_window_in_days, type: :integer) config.add(:max_phone_numbers_per_account, type: :integer) config.add(:max_piv_cac_per_account, type: :integer) + config.add(:mfa_report_config, type: :json) config.add(:min_password_score, type: :integer) config.add(:minimum_wait_before_another_usps_letter_in_hours, type: :integer) config.add(:mx_timeout, type: :integer) diff --git a/scripts/enforce-typescript-files.mjs b/scripts/enforce-typescript-files.mjs index f77f6d396a0..90341fe763f 100755 --- a/scripts/enforce-typescript-files.mjs +++ b/scripts/enforce-typescript-files.mjs @@ -44,7 +44,6 @@ const LEGACY_FILE_EXCEPTIONS = [ 'spec/javascript/packages/document-capture/components/acuant-selfie-camera-spec.jsx', 'spec/javascript/packages/document-capture/components/acuant-selfie-capture-canvas-spec.jsx', 'spec/javascript/packages/document-capture/components/callback-on-mount-spec.jsx', - 'spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx', 'spec/javascript/packages/document-capture/components/document-capture-spec.jsx', 'spec/javascript/packages/document-capture/components/document-capture-warning-spec.jsx', 'spec/javascript/packages/document-capture/components/file-image-spec.jsx', diff --git a/spec/controllers/idv/in_person/state_id_controller_spec.rb b/spec/controllers/idv/in_person/state_id_controller_spec.rb index 1b195113fca..f977cdf27fc 100644 --- a/spec/controllers/idv/in_person/state_id_controller_spec.rb +++ b/spec/controllers/idv/in_person/state_id_controller_spec.rb @@ -28,32 +28,6 @@ ) end - context '#render_404_if_controller_not_enabled' do - context 'flag not set' do - before do - allow(IdentityConfig.store).to receive(:in_person_state_id_controller_enabled). - and_return(nil) - end - it 'renders a 404' do - get :show - - expect(response).to be_not_found - end - end - - context 'flag not enabled' do - before do - allow(IdentityConfig.store).to receive(:in_person_state_id_controller_enabled). - and_return(false) - end - it 'renders a 404' do - get :show - - expect(response).to be_not_found - end - end - end - context '#confirm_establishing_enrollment' do let(:enrollment) { nil } it 'redirects to document capture if not complete' do diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index ebea673e80c..22cb5cbe811 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -13,6 +13,7 @@ stub_sign_in(user) subject.idv_session.flow_path = 'standard' subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID[:ssn] + subject.idv_session.idv_consent_given_at = Time.zone.now.to_s subject.user_session['idv/in_person'] = flow_session end @@ -216,6 +217,7 @@ hash_including( state_id_type: 'drivers_license', ssn: Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID[:ssn], + consent_given_at: subject.idv_session.idv_consent_given_at, ), ).and_call_original @@ -264,7 +266,9 @@ it 'captures state id address fields in the pii' do expect(Idv::Agent).to receive(:new).with( - Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS, + Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS.merge( + consent_given_at: subject.idv_session.idv_consent_given_at, + ), ).and_call_original put :update end diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index 67107ec790e..1be8cdbe1d0 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -397,6 +397,7 @@ expect(Idv::Agent).to receive(:new).with( hash_including( ssn: Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn], + consent_given_at: controller.idv_session.idv_consent_given_at, **Idp::Constants::MOCK_IDV_APPLICANT, ), ).and_call_original diff --git a/spec/controllers/users/email_confirmations_controller_spec.rb b/spec/controllers/users/email_confirmations_controller_spec.rb index b6495043c2b..8c984d65584 100644 --- a/spec/controllers/users/email_confirmations_controller_spec.rb +++ b/spec/controllers/users/email_confirmations_controller_spec.rb @@ -5,7 +5,7 @@ describe 'Valid email confirmation tokens' do it 'tracks a valid email confirmation token event' do user = create(:user) - new_email = Faker::Internet.safe_email + new_email = Faker::Internet.email expect(PushNotification::HttpPush).to receive(:deliver).once. with(PushNotification::EmailChangedEvent.new( @@ -27,7 +27,7 @@ it 'rejects an otherwise valid token for unconfirmed users' do user = create(:user, :unconfirmed, email_addresses: []) - new_email = Faker::Internet.safe_email + new_email = Faker::Internet.email add_email_form = AddUserEmailForm.new add_email_form.submit(user, email: new_email) @@ -41,7 +41,7 @@ it 'rejects expired tokens' do user = create(:user) - new_email = Faker::Internet.safe_email + new_email = Faker::Internet.email add_email_form = AddUserEmailForm.new add_email_form.submit(user, email: new_email) diff --git a/spec/controllers/users/emails_controller_spec.rb b/spec/controllers/users/emails_controller_spec.rb index 05bb2201d48..2bc857706c5 100644 --- a/spec/controllers/users/emails_controller_spec.rb +++ b/spec/controllers/users/emails_controller_spec.rb @@ -30,7 +30,7 @@ stub_sign_in(user) while EmailPolicy.new(user).can_add_email? - email = Faker::Internet.safe_email + email = Faker::Internet.email user.email_addresses.create(email: email, confirmed_at: Time.zone.now) end end @@ -53,7 +53,7 @@ context 'valid email exists in session' do it 'sends email' do - email = Faker::Internet.safe_email + email = Faker::Internet.email post :add, params: { user: { email: email } } diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb index 6d1588d5bcb..be407001213 100644 --- a/spec/controllers/users/sessions_controller_spec.rb +++ b/spec/controllers/users/sessions_controller_spec.rb @@ -906,7 +906,7 @@ render_views it 'does not prefill the form' do - email = Faker::Internet.safe_email + email = Faker::Internet.email password = SecureRandom.uuid get :new, params: { user: { email: email, password: password } } diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb index 0c1cd63d7ab..2b4eeb055ce 100644 --- a/spec/controllers/users/two_factor_authentication_controller_spec.rb +++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb @@ -176,28 +176,39 @@ def index end end - context 'when user is webauthn enabled' do + context 'when user has webauthn' do + let(:user) { create(:user, :with_webauthn) } + before do - stub_sign_in_before_2fa(create(:user, :with_webauthn)) + stub_sign_in_before_2fa(user) end - it 'renders the :webauthn view' do + it 'redirects to webauthn verification' do get :show - expect(response).to redirect_to login_two_factor_webauthn_path(platform: false) + expect(response).to redirect_to login_two_factor_webauthn_path end - context 'when platform_authenticator' do - before do - controller.current_user.webauthn_configurations. - first.update!(platform_authenticator: true) - end + context 'when user has platform webauthn' do + let(:user) { create(:user, :with_webauthn_platform) } - it 'passes the platform parameter if the user has a platform autheticator' do + it 'redirects to webauthn verification with the platform parameter' do get :show expect(response).to redirect_to login_two_factor_webauthn_path(platform: true) end + + context 'when session value indicates no device platform support available' do + before do + controller.user_session[:platform_authenticator_available] = false + end + + it 'redirects to mfa options page' do + get :show + + expect(response).to redirect_to login_two_factor_options_path + end + end end end diff --git a/spec/factories/email_addresses.rb b/spec/factories/email_addresses.rb index 620d7b04cd4..975f2e8b883 100644 --- a/spec/factories/email_addresses.rb +++ b/spec/factories/email_addresses.rb @@ -4,7 +4,7 @@ factory :email_address do confirmed_at { Time.zone.now } confirmation_sent_at { Time.zone.now - 5.minutes } - email { Faker::Internet.safe_email } + email { Faker::Internet.email } user { association :user } trait :unconfirmed do diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 4659097e929..4e0cec5aadd 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -47,7 +47,7 @@ until user.email_addresses.many? user.email_addresses << build( :email_address, - email: Faker::Internet.safe_email, + email: Faker::Internet.email, confirmed_at: user.confirmed_at, user_id: -1, ) @@ -57,7 +57,7 @@ until user.email_addresses.many? user.email_addresses << build( :email_address, - email: Faker::Internet.safe_email, + email: Faker::Internet.email, confirmed_at: user.confirmed_at, user_id: -1, ) diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index c704ffd21d8..c4f5f26f714 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -11,21 +11,27 @@ let(:threatmetrix) { true } let(:idv_level) { 'in_person' } let(:threatmetrix_response) do - { client: nil, + { + client: nil, errors: {}, exception: nil, response_body: { "fraudpoint.score": '500', request_id: '1234', request_result: 'success', + account_lex_id: 'super-cool-test-lex-id', + session_id: 'super-cool-test-session-id', review_status: 'pass', risk_rating: 'trusted', summary_risk_score: '-6', tmx_risk_rating: 'neutral', tmx_summary_reason_code: ['Identity_Negative_History'] }, review_status: 'pass', + account_lex_id: 'super-cool-test-lex-id', + session_id: 'super-cool-test-session-id', success: true, timed_out: false, - transaction_id: 'ddp-mock-transaction-id-123' } + transaction_id: 'ddp-mock-transaction-id-123', + } end let(:base_proofing_components) do { @@ -767,14 +773,18 @@ let(:proofing_device_profiling) { :disabled } let(:threatmetrix) { false } let(:threatmetrix_response) do - { client: 'tmx_disabled', + { + client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', - response_body: nil } + account_lex_id: nil, + session_id: nil, + response_body: nil, + } end it 'records all of the events' do @@ -843,14 +853,18 @@ let(:proofing_device_profiling) { :disabled } let(:threatmetrix) { false } let(:threatmetrix_response) do - { client: 'tmx_disabled', + { + client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', - response_body: nil } + account_lex_id: nil, + session_id: nil, + response_body: nil, + } end it 'records all of the events' do @@ -888,14 +902,18 @@ let(:proofing_device_profiling) { :disabled } let(:threatmetrix) { false } let(:threatmetrix_response) do - { client: 'tmx_disabled', + { + client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', - response_body: nil } + account_lex_id: nil, + session_id: nil, + response_body: nil, + } end it 'records all of the events' do @@ -945,14 +963,18 @@ let(:idv_level) { 'legacy_in_person' } let(:threatmetrix) { false } let(:threatmetrix_response) do - { client: 'tmx_disabled', + { + client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', - response_body: nil } + account_lex_id: nil, + session_id: nil, + response_body: nil, + } end it 'records all of the events', allow_browser_log: true do @@ -1012,14 +1034,18 @@ def wait_for_event(event, wait) let(:proofing_device_profiling) { :disabled } let(:threatmetrix) { false } let(:threatmetrix_response) do - { client: 'tmx_disabled', + { + client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', - response_body: nil } + account_lex_id: nil, + session_id: nil, + response_body: nil, + } end it 'records all of the events' do @@ -1069,14 +1095,18 @@ def wait_for_event(event, wait) let(:proofing_device_profiling) { :disabled } let(:threatmetrix) { false } let(:threatmetrix_response) do - { client: 'tmx_disabled', + { + client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', - response_body: nil } + account_lex_id: nil, + session_id: nil, + response_body: nil, + } end it 'records all of the events' do diff --git a/spec/features/idv/steps/in_person/state_id_50_50_spec.rb b/spec/features/idv/steps/in_person/state_id_50_50_spec.rb index d08dd7c2a71..61862486724 100644 --- a/spec/features/idv/steps/in_person/state_id_50_50_spec.rb +++ b/spec/features/idv/steps/in_person/state_id_50_50_spec.rb @@ -71,11 +71,8 @@ page.refresh end - it 'renders the 404 page' do - expect(page).to have_content( - "The page you were looking for doesn’t exist.\nYou might want to double-check your link" \ - " and try again. (404)", - ) + it 'renders the state ID controller page without error' do + expect(page).to have_current_path(idv_in_person_proofing_state_id_path, wait: 10) end end diff --git a/spec/features/remember_device/revocation_spec.rb b/spec/features/remember_device/revocation_spec.rb index 1c83a366484..51a4073f2a0 100644 --- a/spec/features/remember_device/revocation_spec.rb +++ b/spec/features/remember_device/revocation_spec.rb @@ -62,7 +62,7 @@ def expect_mfa_to_be_required_for_user(user) elsif TwoFactorAuthentication::WebauthnPolicy.new(user).platform_enabled? login_two_factor_webauthn_path(platform: true) elsif TwoFactorAuthentication::WebauthnPolicy.new(user).enabled? - login_two_factor_webauthn_path(platform: false) + login_two_factor_webauthn_path elsif TwoFactorAuthentication::AuthAppPolicy.new(user).enabled? login_two_factor_authenticator_path elsif TwoFactorAuthentication::PhonePolicy.new(user).enabled? diff --git a/spec/features/users/sign_up_spec.rb b/spec/features/users/sign_up_spec.rb index b71f4ce0a78..b6fef7c4ab1 100644 --- a/spec/features/users/sign_up_spec.rb +++ b/spec/features/users/sign_up_spec.rb @@ -37,7 +37,7 @@ end context 'picking a preferred email language on signup' do - let(:email) { Faker::Internet.safe_email } + let(:email) { Faker::Internet.email } it 'allows a user to pick a language when entering email' do visit sign_up_email_path diff --git a/spec/features/webauthn/hidden_spec.rb b/spec/features/webauthn/hidden_spec.rb index 1ad7393b430..57b52cbb92b 100644 --- a/spec/features/webauthn/hidden_spec.rb +++ b/spec/features/webauthn/hidden_spec.rb @@ -93,15 +93,40 @@ end context 'with device that doesnt support authenticator' do - it 'redirects to options page on sign in and shows the option' do - email ||= user.email_addresses.first.email - password = user.password + it 'redirects to options page and allows them to choose authenticator' do visit new_user_session_path set_hidden_field('platform_authenticator_available', 'false') - fill_in_credentials_and_submit(email, password) - continue_as(email, password) + fill_in_credentials_and_submit(user.email, user.password) + + # Redirected to options page expect(current_path).to eq(login_two_factor_options_path) + + # Can choose authenticator expect(webauthn_option_hidden?).to eq(false) + choose t('two_factor_authentication.login_options.webauthn_platform') + click_continue + expect(current_url).to eq(login_two_factor_webauthn_url(platform: true)) + end + + context 'if the webauthn credential is not their default mfa method when signing in' do + let(:user) do + create(:user, :fully_registered, :with_piv_or_cac, :with_webauthn_platform) + end + + it 'allows them to choose authenticator if they change from their default method' do + visit new_user_session_path + set_hidden_field('platform_authenticator_available', 'false') + fill_in_credentials_and_submit(user.email, user.password) + + # Redirected to default MFA method + expect(current_path).to eq(login_two_factor_piv_cac_path) + + # Can change to authenticator if they choose + click_on t('two_factor_authentication.login_options_link_text') + choose t('two_factor_authentication.login_options.webauthn_platform') + click_continue + expect(current_url).to eq(login_two_factor_webauthn_url(platform: true)) + end end end end diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/successful_redacted_response.json b/spec/fixtures/proofing/lexis_nexis/ddp/successful_redacted_response.json index a58c4d1569b..5ee88beed85 100644 --- a/spec/fixtures/proofing/lexis_nexis/ddp/successful_redacted_response.json +++ b/spec/fixtures/proofing/lexis_nexis/ddp/successful_redacted_response.json @@ -6,5 +6,7 @@ "summary_risk_score": "-6", "tmx_risk_rating": "neutral", "fraudpoint.score": "500", - "tmx_summary_reason_code": ["Identity_Negative_History"] + "tmx_summary_reason_code": ["Identity_Negative_History"], + "session_id": "super-cool-test-session-id", + "account_lex_id": "super-cool-test-lex-id" } diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json b/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json index a408987796b..2de77c0ff61 100644 --- a/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json +++ b/spec/fixtures/proofing/lexis_nexis/ddp/successful_response.json @@ -7,5 +7,7 @@ "tmx_risk_rating": "neutral", "fraudpoint.score": "500", "first_name": "WARNING! YOU SHOULD NEVER SEE THIS PII FIELD IN THE LOGS", - "tmx_summary_reason_code": ["Identity_Negative_History"] + "tmx_summary_reason_code": ["Identity_Negative_History"], + "session_id": "super-cool-test-session-id", + "account_lex_id": "super-cool-test-lex-id" } diff --git a/spec/jobs/reports/mfa_report_spec.rb b/spec/jobs/reports/mfa_report_spec.rb new file mode 100644 index 00000000000..7c688ce2e5f --- /dev/null +++ b/spec/jobs/reports/mfa_report_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +RSpec.describe Reports::MfaReport do + let(:issuer) { 'issuer1' } + let(:issuers) { [issuer] } + let(:report_date) { Date.new(2023, 12, 0o1).in_time_zone('UTC') } + let(:email) { 'partner.name@example.com' } + let(:name) { 'Partner Name' } + + let(:report_configs) do + [ + { + 'name' => name, + 'issuers' => issuers, + 'emails' => [email], + }, + ] + end + + before do + allow(IdentityConfig.store).to receive(:s3_reports_enabled).and_return(true) + allow(IdentityConfig.store).to receive(:mfa_report_config) { report_configs } + end + + describe '#perform' do + let(:reports) { double('emailable_reports').as_null_object } + + let(:monthly_report) { double(Reporting::MfaReport, as_emailable_reports: reports) } + + before do + expect(Reporting::MfaReport).to receive(:new).with( + issuers:, + time_range: report_date.all_month, + ) { monthly_report } + + allow(ReportMailer).to receive(:tables_report).and_call_original + end + + it 'emails the csv' do + expect(ReportMailer).to receive(:tables_report).with( + email:, + subject: "Monthly MFA Report - #{report_date}", + message: "Report: mfa-report #{report_date}", + reports:, + attachment_format: :csv, + ) + + subject.perform(report_date) + end + end + + describe 'with empty logs' do + before do + stub_cloudwatch_logs([]) + end + + it 'sends an email with at least 1 attachment' do + subject.perform(report_date) + sent_mail = ActionMailer::Base.deliveries.last + expect(sent_mail.parts.attachments.count).to be >= 1 + end + end +end diff --git a/spec/presenters/two_factor_options_presenter_spec.rb b/spec/presenters/two_factor_options_presenter_spec.rb index 5816a8da51d..a86ec05b8d3 100644 --- a/spec/presenters/two_factor_options_presenter_spec.rb +++ b/spec/presenters/two_factor_options_presenter_spec.rb @@ -116,23 +116,6 @@ ] end end - context 'when platform_auth_set_up_enabled is enabled' do - before do - allow(IdentityConfig.store).to receive(:platform_auth_set_up_enabled). - and_return(true) - end - - it 'supplies all the options except webauthn' do - expect(presenter.options.map(&:class)).to eq [ - TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter, - TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, - TwoFactorAuthentication::SetUpPhoneSelectionPresenter, - TwoFactorAuthentication::SetUpWebauthnSelectionPresenter, - TwoFactorAuthentication::SetUpPivCacSelectionPresenter, - TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, - ] - end - end end describe '#all_options_sorted' do diff --git a/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb b/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb index df0ca8d7955..c57e331ba28 100644 --- a/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb +++ b/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb @@ -99,6 +99,9 @@ expect(result.success?).to eq(true) expect(result.errors).to be_empty + expect(result.review_status).to eq('pass') + expect(result.session_id).to eq('super-cool-test-session-id') + expect(result.account_lex_id).to eq('super-cool-test-lex-id') end end diff --git a/spec/services/proofing/socure/id_plus/input_spec.rb b/spec/services/proofing/socure/id_plus/input_spec.rb index ba709a98f5b..2aaeadefa49 100644 --- a/spec/services/proofing/socure/id_plus/input_spec.rb +++ b/spec/services/proofing/socure/id_plus/input_spec.rb @@ -4,7 +4,7 @@ let(:user) { build(:user) } let(:state_id) do - Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE + Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE.merge(consent_given_at: '2024-09-01T00:00:00Z') end subject do @@ -32,6 +32,8 @@ phone: '12025551212', ssn: '900-66-1234', email: user.email, + + consent_given_at: '2024-09-01T00:00:00Z', }, ) end diff --git a/spec/services/proofing/socure/id_plus/request_spec.rb b/spec/services/proofing/socure/id_plus/request_spec.rb index 4c0fd4ec867..7ff73f5d5e8 100644 --- a/spec/services/proofing/socure/id_plus/request_spec.rb +++ b/spec/services/proofing/socure/id_plus/request_spec.rb @@ -15,7 +15,9 @@ let(:input) do Proofing::Socure::IdPlus::Input.new( email: user.email, - **Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE.slice( + **Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE.merge( + consent_given_at: '2024-09-01T00:00:00Z', + ).slice( *Proofing::Socure::IdPlus::Input.members, ), ) @@ -27,37 +29,30 @@ describe '#body' do it 'contains all expected values' do - freeze_time do - expect(JSON.parse(request.body, symbolize_names: true)).to eql( - { - modules: [ - 'kyc', - ], - firstName: 'FAKEY', - surName: 'MCFAKERSON', - dob: '1938-10-06', - physicalAddress: '1 FAKE RD', - physicalAddress2: nil, - city: 'GREAT FALLS', - state: 'MT', - zip: '59010-1234', - country: 'US', - nationalId: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:ssn], - countryOfOrigin: 'US', - - email: user.email, - mobileNumber: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:phone], - - userConsent: true, - - # XXX: This should be set to the time the user submitted agreement, - # which we are not currently tracking. The "5.minutes.ago" is - # because Socure will reject times "in the future", so we avoid - # our clocks being out of sync with theirs. - consentTimestamp: 5.minutes.ago.iso8601, - }, - ) - end + expect(JSON.parse(request.body, symbolize_names: true)).to eql( + { + modules: [ + 'kyc', + ], + firstName: 'FAKEY', + surName: 'MCFAKERSON', + dob: '1938-10-06', + physicalAddress: '1 FAKE RD', + physicalAddress2: nil, + city: 'GREAT FALLS', + state: 'MT', + zip: '59010-1234', + country: 'US', + nationalId: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:ssn], + countryOfOrigin: 'US', + + email: user.email, + mobileNumber: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:phone], + + userConsent: true, + consentTimestamp: '2024-09-01T00:00:00+00:00', + }, + ) end end diff --git a/spec/services/push_notification/email_changed_event_spec.rb b/spec/services/push_notification/email_changed_event_spec.rb index 20bfd52fdab..c673a3e4015 100644 --- a/spec/services/push_notification/email_changed_event_spec.rb +++ b/spec/services/push_notification/email_changed_event_spec.rb @@ -9,7 +9,7 @@ end let(:user) { build(:user) } - let(:email) { Faker::Internet.safe_email } + let(:email) { Faker::Internet.email } describe '#event_type' do it 'is the RISC event type' do diff --git a/spec/services/push_notification/http_push_spec.rb b/spec/services/push_notification/http_push_spec.rb index 6a854c044b4..1cd1e71bb13 100644 --- a/spec/services/push_notification/http_push_spec.rb +++ b/spec/services/push_notification/http_push_spec.rb @@ -18,7 +18,7 @@ let(:event) do PushNotification::IdentifierRecycledEvent.new( user: user, - email: Faker::Internet.safe_email, + email: Faker::Internet.email, ) end let(:now) { Time.zone.now } diff --git a/spec/services/push_notification/identifier_recycled_event_spec.rb b/spec/services/push_notification/identifier_recycled_event_spec.rb index 548fbc57eea..a99664d4492 100644 --- a/spec/services/push_notification/identifier_recycled_event_spec.rb +++ b/spec/services/push_notification/identifier_recycled_event_spec.rb @@ -9,7 +9,7 @@ end let(:user) { build(:user) } - let(:email) { Faker::Internet.safe_email } + let(:email) { Faker::Internet.email } describe '#event_type' do it 'is the RISC event type' do diff --git a/spec/services/reset_user_password_spec.rb b/spec/services/reset_user_password_spec.rb index f7aafe5f929..f0017af9340 100644 --- a/spec/services/reset_user_password_spec.rb +++ b/spec/services/reset_user_password_spec.rb @@ -20,7 +20,7 @@ end it 'notifies the user via email to each of their confirmed email addresses' do - create(:email_address, user:, email: Faker::Internet.safe_email, confirmed_at: nil) + create(:email_address, user:, email: Faker::Internet.email, confirmed_at: nil) expect { call }. to(change { ActionMailer::Base.deliveries.count }.by(2)) diff --git a/spec/services/usps_in_person_proofing/proofer_spec.rb b/spec/services/usps_in_person_proofing/proofer_spec.rb index e8fe2c16a9a..a31e7fe7d88 100644 --- a/spec/services/usps_in_person_proofing/proofer_spec.rb +++ b/spec/services/usps_in_person_proofing/proofer_spec.rb @@ -95,7 +95,7 @@ def expect_facility_fields_to_be_present(facility) zip_code: Faker::Address.zip_code, first_name: Faker::Name.first_name, last_name: Faker::Name.last_name, - email: Faker::Internet.safe_email, + email: Faker::Internet.email, unique_id: '123456789', ) stub_request_token @@ -291,7 +291,7 @@ def expect_facility_fields_to_be_present(facility) zip_code: Faker::Address.zip_code, first_name: Faker::Name.first_name, last_name: Faker::Name.last_name, - email: Faker::Internet.safe_email, + email: Faker::Internet.email, unique_id: '123456789', ) end diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb index d2ffb80101a..ea92023dd69 100644 --- a/spec/support/features/session_helper.rb +++ b/spec/support/features/session_helper.rb @@ -122,7 +122,7 @@ def fill_in_password_and_submit(password) end def sign_up - email = Faker::Internet.safe_email + email = Faker::Internet.email sign_up_with(email) confirm_last_user end diff --git a/spec/support/flow_policy_helper.rb b/spec/support/flow_policy_helper.rb index 67a24a8a6d6..ecda95a489e 100644 --- a/spec/support/flow_policy_helper.rb +++ b/spec/support/flow_policy_helper.rb @@ -12,7 +12,7 @@ def stub_step(key:, idv_session:) when :welcome idv_session.welcome_visited = true when :agreement - idv_session.idv_consent_given_at = Time.zone.now + idv_session.idv_consent_given_at = Time.zone.now.to_s when :how_to_verify idv_session.skip_doc_auth = false idv_session.skip_doc_auth_from_how_to_verify = false diff --git a/spec/support/shared_examples/remember_device.rb b/spec/support/shared_examples/remember_device.rb index 79719d93e92..8cadc69f64f 100644 --- a/spec/support/shared_examples/remember_device.rb +++ b/spec/support/shared_examples/remember_device.rb @@ -70,7 +70,7 @@ def expect_mfa_to_be_required_for_user(user) elsif TwoFactorAuthentication::WebauthnPolicy.new(user).platform_enabled? login_two_factor_webauthn_path(platform: true) elsif TwoFactorAuthentication::WebauthnPolicy.new(user).enabled? - login_two_factor_webauthn_path(platform: false) + login_two_factor_webauthn_path elsif TwoFactorAuthentication::AuthAppPolicy.new(user).enabled? login_two_factor_authenticator_path elsif TwoFactorAuthentication::PhonePolicy.new(user).enabled?