From 5918b6a6dfc12b4f25072fcae28e545f55c24a6a Mon Sep 17 00:00:00 2001 From: A Shukla Date: Thu, 13 Feb 2025 11:39:56 -0600 Subject: [PATCH 01/14] Lg-15491 refactor allow direct ipp (#11871) * changelog: Internal, allow_direct_ipp, refactor allow direct ipp to not use skip_doc_auth_from_handoff as it is not an accurate representation * fixing some tests * setting skip_doc_auth_from_handoff based on params * changing approach to not use new variable * adding method to improve code readability --- app/controllers/idv/document_capture_controller.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb index 834c1ac6730..2abb39d3002 100644 --- a/app/controllers/idv/document_capture_controller.rb +++ b/app/controllers/idv/document_capture_controller.rb @@ -125,14 +125,19 @@ def allow_direct_ipp? # Only allow direct access to document capture if IPP available return false unless IdentityConfig.store.in_person_doc_auth_button_enabled && Idv::InPersonConfig.enabled_for_issuer?(decorated_sp_session.sp_issuer) - @previous_step_url = params[:step] == 'hybrid_handoff' ? idv_hybrid_handoff_path : nil + @previous_step_url = step_is_handoff? ? idv_hybrid_handoff_path : nil # allow idv_session.flow_path = 'standard' - idv_session.skip_doc_auth_from_handoff = true + idv_session.skip_doc_auth_from_handoff = step_is_handoff? + idv_session.skip_doc_auth_from_how_to_verify = params[:step] == 'how_to_verify' idv_session.skip_hybrid_handoff = nil true end + def step_is_handoff? + params[:step] == 'hybrid_handoff' + end + def set_usps_form_presenter @presenter = Idv::InPerson::UspsFormPresenter.new end From 5f0152dc877ed6bcfb1ad43ce57ece6cc665fa38 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:45:24 -0500 Subject: [PATCH 02/14] LG-15753: Update translations for Face/Touch user events (#11877) changelog: User-Facing Improvements, Translations, Improve translations for Face or Touch Unlock account history actions --- config/locales/es.yml | 4 ++-- config/locales/zh.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/locales/es.yml b/config/locales/es.yml index 970c1d6f6aa..28e65e5800c 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -856,8 +856,8 @@ event_types.sign_in_notification_timeframe_expired: Venció el tiempo del mensaj event_types.sign_in_unsuccessful_2fa: No se pudo autenticar event_types.webauthn_key_added: Clave de seguridad de hardware añadida event_types.webauthn_key_removed: Clave de seguridad de hardware eliminada -event_types.webauthn_platform_added: Desbloqueo facial o táctil añadido -event_types.webauthn_platform_removed: Desbloqueo facial o táctil eliminado +event_types.webauthn_platform_added: Se agregó el desbloqueo facial o táctil +event_types.webauthn_platform_removed: Se eliminó el desbloqueo facial o táctil forms.backup_code_regenerate.caution: Si vuelve a generar sus códigos de recuperación, recibirá un conjunto nuevo de códigos. Sus códigos de recuperación originales ya no serán válidos. forms.backup_code_regenerate.confirm: '¿Está seguro de que desea volver a generar sus códigos de recuperación?' forms.backup_code_reminder.body_info: Si no puede acceder a su método de autenticación principal, puede usar códigos de recuperación para acceder a su cuenta. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index e1d5133e35f..b356c72123c 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -856,8 +856,8 @@ event_types.sign_in_notification_timeframe_expired: 从新设备登录的通知 event_types.sign_in_unsuccessful_2fa: 身份证实失败 event_types.webauthn_key_added: 硬件安全密钥已添加 event_types.webauthn_key_removed: 硬件安全密钥已去掉 -event_types.webauthn_platform_added: 人脸或触摸解锁已添加。 -event_types.webauthn_platform_removed: 人脸或触摸解锁已去掉。 +event_types.webauthn_platform_added: 添加了人脸或触摸解锁 +event_types.webauthn_platform_removed: 取消了人脸或触摸解锁 forms.backup_code_regenerate.caution: 如果你重新生成备用代码,会收到新的一套备用代码。你原来的备用代码就会失效。 forms.backup_code_regenerate.confirm: 你确定要重新生成备用代码吗? forms.backup_code_reminder.body_info: 如果你无法使用自己的主要身份证实方法,可以使用备用代码重新获得对账户的访问权。 From 9f23fa5638703e90eec56e1321362137b3019fbc Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 13 Feb 2025 16:36:40 -0500 Subject: [PATCH 03/14] LG-15652 add transaction reference id to analytics (#11851) * Added parameters to :idv_socure_verification_data_requested event. * Added parameters to idv_socure_document_request submitted event. changelog: Internal,Socure,Add Socure's error status and message to analytics when present. --- .../concerns/idv/document_capture_concern.rb | 2 + app/jobs/socure_docv_results_job.rb | 2 +- app/services/analytics_events.rb | 40 ++- app/services/doc_auth/socure/request.rb | 3 +- .../socure/requests/document_request.rb | 3 +- .../socure/requests/docv_result_request.rb | 3 +- .../socure/responses/docv_result_response.rb | 6 +- .../doc_auth/socure_internal_error_spec.rb | 66 ++++ spec/jobs/socure_docv_results_job_spec.rb | 309 +++++++++++------- .../requests/docv_result_request_spec.rb | 7 +- 10 files changed, 303 insertions(+), 138 deletions(-) create mode 100644 spec/features/idv/doc_auth/socure_internal_error_spec.rb diff --git a/app/controllers/concerns/idv/document_capture_concern.rb b/app/controllers/concerns/idv/document_capture_concern.rb index d6731425af0..0c71c24c7f3 100644 --- a/app/controllers/concerns/idv/document_capture_concern.rb +++ b/app/controllers/concerns/idv/document_capture_concern.rb @@ -106,6 +106,8 @@ def track_document_request_event(document_request:, document_response:, timer:) success: @url.present?, document_type: document_request_body[:documentType], docv_transaction_token: response_hash.dig(:data, :docvTransactionToken), + socure_status: response_hash[:status], + socure_msg: response_hash[:msg], } analytics_hash = log_extras .merge(analytics_arguments) diff --git a/app/jobs/socure_docv_results_job.rb b/app/jobs/socure_docv_results_job.rb index 21a46b73dfc..cd26425d7e9 100644 --- a/app/jobs/socure_docv_results_job.rb +++ b/app/jobs/socure_docv_results_job.rb @@ -18,6 +18,7 @@ def perform(document_capture_session_uuid:, async: true, docv_transaction_token_ docv_result_response = timer.time('vendor_request') do socure_document_verification_result end + log_verification_request( docv_result_response:, vendor_request_time_in_ms: timer.results['vendor_request'], @@ -56,7 +57,6 @@ def analytics def log_verification_request(docv_result_response:, vendor_request_time_in_ms:) analytics.idv_socure_verification_data_requested( **docv_result_response.to_h.merge( - docv_transaction_token: document_capture_session.socure_docv_transaction_token, submit_attempts: rate_limiter&.attempts, remaining_submit_attempts: rate_limiter&.remaining_count, vendor_request_time_in_ms:, diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index b3b5d1ef085..dfcd57a6e3b 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -4992,6 +4992,8 @@ def idv_session_error_visited( # @param [Boolean] liveness_enabled Whether or not the selfie result is included in response # @param [String] vendor which 2rd party we are using for doc auth # @param [Hash] document_type type of socument submitted (Drivers Licenese, etc.) + # @param [String] socure_status Socure's status value for internal errors on their side. + # @param [String] socure_msg Socure's status message for interal errors on their side. # The request for socure verification was sent def idv_socure_document_request_submitted( success:, @@ -5014,6 +5016,8 @@ def idv_socure_document_request_submitted( document_type: nil, docv_transaction_token: nil, flow_path: nil, + socure_status: nil, + socure_msg: nil, **extra ) track_event( @@ -5038,6 +5042,8 @@ def idv_socure_document_request_submitted( document_type:, docv_transaction_token:, flow_path:, + socure_status:, + socure_msg:, **extra, ) end @@ -5121,36 +5127,40 @@ def idv_socure_shadow_mode_proofing_result_missing(**extra) # @param [String] birth_year Birth year from document # @param [Integer] issue_year Year document was issued # @param [Boolean] biometric_comparison_required does doc auth require biometirc + # @param [String] vendor_status Socure's request status (used for errors) + # @param [String] vendor_status_message socure's error message (used for errors) # The request for socure verification was sent def idv_socure_verification_data_requested( success:, errors:, async:, - reference_id:, - reason_codes:, - document_type:, - decision:, - state:, - state_id_type:, submit_attempts:, - remaining_submit_attempts:, - liveness_checking_required:, - issue_year:, vendor_request_time_in_ms:, doc_type_supported:, doc_auth_success:, vendor:, - address_line2_present:, - zip_code:, - birth_year:, - liveness_enabled:, - biometric_comparison_required:, + remaining_submit_attempts:, + reference_id: nil, + reason_codes: nil, + document_type: nil, + decision: nil, + state: nil, + state_id_type: nil, + liveness_checking_required: nil, + issue_year: nil, + address_line2_present: nil, + zip_code: nil, + birth_year: nil, + liveness_enabled: nil, + biometric_comparison_required: nil, customer_profile: nil, docv_transaction_token: nil, user_id: nil, exception: nil, flow_path: nil, billed: nil, + vendor_status: nil, + vendor_status_message: nil, **extra ) track_event( @@ -5183,6 +5193,8 @@ def idv_socure_verification_data_requested( issue_year:, liveness_enabled:, biometric_comparison_required:, + vendor_status:, + vendor_status_message:, **extra, ) end diff --git a/app/services/doc_auth/socure/request.rb b/app/services/doc_auth/socure/request.rb index e1754634417..9a6417ce497 100644 --- a/app/services/doc_auth/socure/request.rb +++ b/app/services/doc_auth/socure/request.rb @@ -51,10 +51,11 @@ def handle_invalid_response(http_response) exception: exception, status: response_body.dig('status'), status_message: response_body.dig('msg'), + reference_id: response_body.dig('referenceId'), ) end - def handle_connection_error(exception:, status: nil, status_message: nil) + def handle_connection_error(exception:, status: nil, status_message: nil, reference_id: nil) raise NotImplementedError end diff --git a/app/services/doc_auth/socure/requests/document_request.rb b/app/services/doc_auth/socure/requests/document_request.rb index 3fa0a99b10d..6bf9a93234e 100644 --- a/app/services/doc_auth/socure/requests/document_request.rb +++ b/app/services/doc_auth/socure/requests/document_request.rb @@ -44,7 +44,7 @@ def handle_http_response(http_response) JSON.parse(http_response.body, symbolize_names: true) end - def handle_connection_error(exception:, status: nil, status_message: nil) + def handle_connection_error(exception:, status: nil, status_message: nil, reference_id: nil) NewRelic::Agent.notice_error(exception) { success: false, @@ -54,6 +54,7 @@ def handle_connection_error(exception:, status: nil, status_message: nil) vendor: 'Socure', vendor_status: status, vendor_status_message: status_message, + reference_id:, }.compact, } end diff --git a/app/services/doc_auth/socure/requests/docv_result_request.rb b/app/services/doc_auth/socure/requests/docv_result_request.rb index 5be48264e33..6724c95f961 100644 --- a/app/services/doc_auth/socure/requests/docv_result_request.rb +++ b/app/services/doc_auth/socure/requests/docv_result_request.rb @@ -32,7 +32,7 @@ def handle_http_response(http_response) ) end - def handle_connection_error(exception:, status: nil, status_message: nil) + def handle_connection_error(exception:, status: nil, status_message: nil, reference_id: nil) NewRelic::Agent.notice_error(exception) DocAuth::Response.new( success: false, @@ -42,6 +42,7 @@ def handle_connection_error(exception:, status: nil, status_message: nil) vendor: 'Socure', vendor_status: status, vendor_status_message: status_message, + reference_id:, }.compact, ) end diff --git a/app/services/doc_auth/socure/responses/docv_result_response.rb b/app/services/doc_auth/socure/responses/docv_result_response.rb index 017b65f0b89..2f62a05fce5 100644 --- a/app/services/doc_auth/socure/responses/docv_result_response.rb +++ b/app/services/doc_auth/socure/responses/docv_result_response.rb @@ -8,6 +8,8 @@ class DocvResultResponse < DocAuth::Response DATA_PATHS = { reference_id: %w[referenceId], + status: %w[status], + msg: %w[msg], document_verification: %w[documentVerification], reason_codes: %w[documentVerification reasonCodes], document_type: %w[documentVerification documentType], @@ -55,7 +57,7 @@ def initialize(http_response:, exception: e, extra: { backtrace: e.backtrace, - }, + } ) end @@ -70,6 +72,8 @@ def selfie_status def extra_attributes { reference_id: get_data(DATA_PATHS[:reference_id]), + vendor_status: get_data(DATA_PATHS[:status]), + vendor_status_message: get_data(DATA_PATHS[:msg]), decision: get_data(DATA_PATHS[:decision]), biometric_comparison_required: biometric_comparison_required, customer_profile: get_data(DATA_PATHS[:customer_profile]), diff --git a/spec/features/idv/doc_auth/socure_internal_error_spec.rb b/spec/features/idv/doc_auth/socure_internal_error_spec.rb new file mode 100644 index 00000000000..22a7f2147af --- /dev/null +++ b/spec/features/idv/doc_auth/socure_internal_error_spec.rb @@ -0,0 +1,66 @@ +require 'rails_helper' + +RSpec.describe 'when Socure throws an internal error' do + include IdvStepHelper + + let(:fake_analytics) { FakeAnalytics.new } + + let(:socure_status) { 'Error' } + let(:reference_id) { '360ae43f-123f-47ab-8e05-6af79752e76c' } + let(:socure_msg) { 'InternalServerException' } + + before do + allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) + + allow(IdentityConfig.store).to receive(:socure_docv_enabled).and_return(true) + allow(DocAuthRouter).to receive(:doc_auth_vendor_for_bucket) + .and_return(Idp::Constants::Vendors::SOCURE) + + stub_docv_document_request( + body: { status: socure_status, referenceId: reference_id, msg: socure_msg }, + ) + end + + context 'in normal flow' do + before do + visit_idp_from_oidc_sp_with_ial2 + @user = sign_in_and_2fa_user + complete_doc_auth_steps_before_document_capture_step + visit idv_socure_document_capture_path + end + + it 'correctly logs a document capture request submitted event', js: true do + expect(fake_analytics).to have_logged_event( + :idv_socure_document_request_submitted, + hash_including(socure_status:, reference_id:, socure_msg:), + ) + end + end + + context 'in hybrid handoff' do + before do + allow(Telephony).to receive(:send_doc_auth_link).and_wrap_original do |impl, config| + @sms_link = config[:link] + impl.call(**config) + end + end + + it 'correctly logs a document capture request submitted event', js: true do + perform_in_browser(:desktop) do + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user + complete_doc_auth_steps_before_hybrid_handoff_step + click_send_link + end + + perform_in_browser(:mobile) do + visit @sms_link + end + + expect(fake_analytics).to have_logged_event( + :idv_socure_document_request_submitted, + hash_including(socure_status:, reference_id:, socure_msg:), + ) + end + end +end diff --git a/spec/jobs/socure_docv_results_job_spec.rb b/spec/jobs/socure_docv_results_job_spec.rb index a50cf8421b8..8ad61700fe3 100644 --- a/spec/jobs/socure_docv_results_job_spec.rb +++ b/spec/jobs/socure_docv_results_job_spec.rb @@ -26,149 +26,224 @@ subject(:perform) do job.perform(document_capture_session_uuid: document_capture_session_uuid) end + subject(:perform_now) do job.perform(document_capture_session_uuid: document_capture_session_uuid, async: false) end - let(:socure_response_body) do - # ID+ v3.0 API Predictive Document Verification response - { - referenceId: 'a1234b56-e789-0123-4fga-56b7c890d123', - previousReferenceId: 'e9c170f2-b3e4-423b-a373-5d6e1e9b23f8', - documentVerification: { - reasonCodes: %w[I831 R810], - documentType: { - type: 'Drivers License', - country: 'USA', - state: 'NY', - }, - decision: { - name: 'lenient', - value: decision_value, - }, - documentData: { - firstName: 'Dwayne', - surName: 'Denver', - fullName: 'Dwayne Denver', - address: '123 Example Street, New York City, NY 10001', - parsedAddress: { - physicalAddress: '123 Example Street', - physicalAddress2: 'New York City NY 10001', - city: 'New York City', + context 'when we get a 200 OK back from Socure' do + let(:socure_response_body) do + # ID+ v3.0 API Predictive Document Verification response + { + referenceId: 'a1234b56-e789-0123-4fga-56b7c890d123', + previousReferenceId: 'e9c170f2-b3e4-423b-a373-5d6e1e9b23f8', + documentVerification: { + reasonCodes: %w[I831 R810], + documentType: { + type: 'Drivers License', + country: 'USA', state: 'NY', - country: 'US', - zip: '10001', }, - documentNumber: '000000000', - dob: '2000-01-01', - issueDate: '2020-01-01', - expirationDate: expiration_date, + decision: { + name: 'lenient', + value: decision_value, + }, + documentData: { + firstName: 'Dwayne', + surName: 'Denver', + fullName: 'Dwayne Denver', + address: '123 Example Street, New York City, NY 10001', + parsedAddress: { + physicalAddress: '123 Example Street', + physicalAddress2: 'New York City NY 10001', + city: 'New York City', + state: 'NY', + country: 'US', + zip: '10001', + }, + documentNumber: '000000000', + dob: '2000-01-01', + issueDate: '2020-01-01', + expirationDate: expiration_date, + }, }, - }, - customerProfile: { - customerUserId: document_capture_session.uuid, - userId: 'u8JpWn4QsF3R7tA2', - }, - } - end + customerProfile: { + customerUserId: document_capture_session.uuid, + userId: 'u8JpWn4QsF3R7tA2', + }, + } + end - let(:expected_socure_log) do - { - success: true, - issue_year: 2020, - vendor: 'Socure', - submit_attempts: 0, - remaining_submit_attempts: 4, - state: 'NY', - zip_code: '10001', - doc_auth_success: true, - document_type: { - type: 'Drivers License', - country: 'USA', + let(:expected_socure_log) do + { + success: true, + issue_year: 2020, + vendor: 'Socure', + submit_attempts: 0, + remaining_submit_attempts: 4, state: 'NY', - }, - } - end - - before do - stub_request(:post, 'https://example.com/api/3.0/EmailAuthScore') - .to_return( - headers: { - 'Content-Type' => 'application/json', + zip_code: '10001', + doc_auth_success: true, + document_type: { + type: 'Drivers License', + country: 'USA', + state: 'NY', }, - body: JSON.generate(socure_response_body), - ) - end - - it 'stores the result from the Socure DocV request' do - perform - - document_capture_session.reload - document_capture_session_result = document_capture_session.load_result - expect(document_capture_session_result.success).to eq(true) - expect(document_capture_session_result.pii[:first_name]).to eq('Dwayne') - expect(document_capture_session_result.attention_with_barcode).to eq(false) - expect(document_capture_session_result.doc_auth_success).to eq(true) - expect(document_capture_session_result.selfie_status).to eq(:not_processed) - end + } + end - context 'Pii validation fails' do before do - allow_any_instance_of(Idv::DocPiiForm).to receive(:zipcode).and_return(:invalid_junk) + stub_request(:post, 'https://example.com/api/3.0/EmailAuthScore') + .to_return( + headers: { + 'Content-Type' => 'application/json', + }, + body: JSON.generate(socure_response_body), + ) end - it 'stores a failed result' do + it 'stores the result from the Socure DocV request' do perform document_capture_session.reload document_capture_session_result = document_capture_session.load_result - expect(document_capture_session_result.success).to eq(false) + expect(document_capture_session_result.success).to eq(true) + expect(document_capture_session_result.pii[:first_name]).to eq('Dwayne') + expect(document_capture_session_result.attention_with_barcode).to eq(false) expect(document_capture_session_result.doc_auth_success).to eq(true) - expect(document_capture_session_result.errors).to eq({ pii_validation: 'failed' }) + expect(document_capture_session_result.selfie_status).to eq(:not_processed) end - end - it 'logs an idv_doc_auth_submitted_pii_validation event' do - perform - expect(fake_analytics).to have_logged_event( - 'IdV: doc auth image upload vendor pii validation', - hash_including( - :submit_attempts, - :remaining_submit_attempts, - ), - ) - end + context 'Socure returns an error' do + let(:status) { 'Error' } + let(:referenceId) { '360ae43f-123f-47ab-8e05-6af79752e76c' } + let(:msg) { 'InternalServerException' } + let(:socure_response_body) { { status:, referenceId:, msg: } } + + it 'logs the status, reference_id, and message' do + perform + + expect(fake_analytics).to have_logged_event( + :idv_socure_verification_data_requested, + hash_including( + :vendor_status, + :reference_id, + :vendor_status_message, + ), + ) + end + end - it 'logs an idv_socure_verification_data_requested event' do - perform - expect(fake_analytics).to have_logged_event( - :idv_socure_verification_data_requested, - hash_including( - expected_socure_log.merge({ async: true }), - ), - ) - end + context 'Pii validation fails' do + before do + allow_any_instance_of(Idv::DocPiiForm).to receive(:zipcode).and_return(:invalid_junk) + end - it 'expect log with perform_now to have async eq false' do - perform_now - expect(fake_analytics).to have_logged_event( - :idv_socure_verification_data_requested, - hash_including( - expected_socure_log.merge({ async: false }), - ), - ) - end + it 'stores a failed result' do + perform - context 'when the document capture session does not exist' do - let(:document_capture_session_uuid) { '1234' } + document_capture_session.reload + document_capture_session_result = document_capture_session.load_result + expect(document_capture_session_result.success).to eq(false) + expect(document_capture_session_result.doc_auth_success).to eq(true) + expect(document_capture_session_result.errors).to eq({ pii_validation: 'failed' }) + end + end - it 'raises an error and fails to store the result from the Socure DocV request' do - expect { perform }.to raise_error( - RuntimeError, - "DocumentCaptureSession not found: #{document_capture_session_uuid}", + it 'logs an idv_doc_auth_submitted_pii_validation event' do + perform + expect(fake_analytics).to have_logged_event( + 'IdV: doc auth image upload vendor pii validation', + hash_including( + :submit_attempts, + :remaining_submit_attempts, + ), ) - document_capture_session.reload - expect(document_capture_session.load_result).to be_nil + end + + it 'logs an idv_socure_verification_data_requested event' do + perform + expect(fake_analytics).to have_logged_event( + :idv_socure_verification_data_requested, + hash_including( + expected_socure_log.merge({ async: true }), + ), + ) + end + + it 'expect log with perform_now to have async eq false' do + perform_now + expect(fake_analytics).to have_logged_event( + :idv_socure_verification_data_requested, + hash_including( + expected_socure_log.merge({ async: false }), + ), + ) + end + + context 'when the document capture session does not exist' do + let(:document_capture_session_uuid) { '1234' } + + it 'raises an error and fails to store the result from the Socure DocV request' do + expect { perform }.to raise_error( + RuntimeError, + "DocumentCaptureSession not found: #{document_capture_session_uuid}", + ) + document_capture_session.reload + expect(document_capture_session.load_result).to be_nil + end + end + end + + context 'when we get a non-200 HTTP response back from Socure' do + %w[400 403 404 500].each do |http_status| + context "Socure returns HTTP #{http_status} with an error body" do + let(:status) { 'Error' } + let(:referenceId) { '360ae43f-123f-47ab-8e05-6af79752e76c' } + let(:msg) { 'InternalServerException' } + let(:socure_response_body) { { status:, referenceId:, msg: } } + + before do + stub_request(:post, 'https://example.com/api/3.0/EmailAuthScore') + .to_return( + status: http_status, + headers: { + 'Content-Type' => 'application/json', + }, + body: JSON.generate(socure_response_body), + ) + end + + it 'logs the status, reference_id, and message' do + perform + + expect(fake_analytics).to have_logged_event( + :idv_socure_verification_data_requested, + hash_including( + { + vendor_status: 'Error', + reference_id: referenceId, + vendor_status_message: msg, + }, + ), + ) + end + end + + context "Socure returns HTTP #{http_status} with no error body" do + before do + stub_request(:post, 'https://example.com/api/3.0/EmailAuthScore') + .to_return(status: http_status) + end + + it 'logs the event' do + perform + + expect(fake_analytics).to have_logged_event( + :idv_socure_verification_data_requested, + ) + end + end end end end diff --git a/spec/services/doc_auth/socure/requests/docv_result_request_spec.rb b/spec/services/doc_auth/socure/requests/docv_result_request_spec.rb index dc9e19cc6cc..0ef5e6ee9a9 100644 --- a/spec/services/doc_auth/socure/requests/docv_result_request_spec.rb +++ b/spec/services/doc_auth/socure/requests/docv_result_request_spec.rb @@ -30,7 +30,10 @@ end context 'with socure failures' do - let(:fake_socure_response) { {} } + let(:status) { 'Error' } + let(:referenceId) { '360ae43f-123f-47ab-8e05-6af79752e76c' } + let(:msg) { 'InternalServerException' } + let(:fake_socure_response) { { status:, referenceId:, msg: } } let(:fake_socure_status) { 500 } it 'expect correct doc auth response during a connection failure' do @@ -51,8 +54,8 @@ response_hash = docv_result_request.fetch.to_h expect(response_hash[:success]).to eq(false) expect(response_hash[:errors]).to eq({ network: true }) - expect(response_hash[:errors]).to eq({ network: true }) expect(response_hash[:vendor]).to eq('Socure') + expect(response_hash[:reference_id]).to eq(referenceId) expect(response_hash[:exception]).to be_a(DocAuth::RequestError) expect(response_hash[:exception].message).to include('Unexpected HTTP response 500') end From 878cda3332140c9ce27e243f526b2476e97ce5b0 Mon Sep 17 00:00:00 2001 From: "Davi (she/they)" Date: Thu, 13 Feb 2025 16:57:46 -0500 Subject: [PATCH 04/14] OpenAPI Spec (#11689) changelog: Upcoming Features, Attempts API, Adds initial draft of API spec --- Makefile | 6 ++ docs/attempts-api/openapi.yml | 39 ++++++++ docs/attempts-api/paths/poll.yml | 78 ++++++++++++++++ docs/attempts-api/paths/status.yml | 37 ++++++++ .../paths/transmitter-configuration.yml | 22 +++++ docs/attempts-api/paths/verification.yml | 27 ++++++ docs/attempts-api/schemas/AllEvents.yml | 6 ++ .../schemas/DecodedJWTPayload.yml | 26 ++++++ docs/attempts-api/schemas/PollParameters.yml | 11 +++ docs/attempts-api/schemas/StreamStatus.yml | 31 +++++++ .../schemas/TransmitterConfiguration.yml | 66 +++++++++++++ .../schemas/events/AccountRecoveryEvents.yml | 19 ++++ .../schemas/events/IdentityProofingEvents.yml | 43 +++++++++ .../schemas/events/RegistrationEvents.yml | 31 +++++++ .../schemas/events/SignInEvents.yml | 33 +++++++ .../AccountResetAccountDeleted.yml | 23 +++++ .../AccountResetCancelRequest.yml | 5 + .../AccountResetRequestSubmitted.yml | 10 ++ .../ForgotPasswordEmailConfirmed.yml | 25 +++++ .../ForgotPasswordEmailRateLimited.yml | 9 ++ .../ForgotPasswordEmailSent.yml | 10 ++ .../ForgotPasswordNewPasswordSubmitted.yml | 32 +++++++ .../PersonalKeyReactivationRateLimited.yml | 5 + .../PersonalKeyReactivationSubmitted.yml | 24 +++++ .../IdvDocumentUploadMethodSelected.yml | 11 +++ .../IdvDocumentUploadRateLimited.yml | 5 + .../IdvDocumentUploadSubmitted.yml | 92 +++++++++++++++++++ .../IdvGpoLetterRequested.yml | 11 +++ .../IdvGpoVerificationRateLimited.yml | 5 + .../IdvGpoVerificationSubmitted.yml | 20 ++++ .../identity-proofing/IdvPasswordEntered.yml | 10 ++ .../IdvPersonalKeyGenerated.yml | 5 + .../identity-proofing/IdvPhoneOtpSent.yml | 41 +++++++++ .../IdvPhoneOtpSentRateLimited.yml | 8 ++ .../IdvPhoneOtpSubmitted.yml | 30 ++++++ .../IdvPhoneOtpSubmittedRateLimited.yml | 8 ++ .../IdvPhoneSendLinkRateLimited.yml | 8 ++ .../identity-proofing/IdvPhoneSubmitted.yml | 26 ++++++ .../IdvPhoneUploadLinkSent.yml | 36 ++++++++ .../IdvPhoneUploadLinkUsed.yml | 6 ++ .../events/identity-proofing/IdvReproof.yml | 5 + .../identity-proofing/IdvSsnSubmitted.yml | 9 ++ .../identity-proofing/IdvTmxFraudCheck.yml | 41 +++++++++ .../IdvVerificationRateLimited.yml | 14 +++ .../IdvVerificationSubmitted.yml | 40 ++++++++ .../registration/MfaEnrollBackupCode.yml | 9 ++ .../registration/MfaEnrollOptionsSelected.yml | 22 +++++ .../MfaEnrollPhoneOtpCodeRateLimited.yml | 10 ++ .../registration/MfaEnrollPhoneOtpSent.yml | 18 ++++ .../MfaEnrollPhoneOtpSentRateLimited.yml | 9 ++ .../MfaEnrollPhoneOtpSubmitted.yml | 10 ++ .../events/registration/MfaEnrollPivCac.yml | 58 ++++++++++++ .../events/registration/MfaEnrollTotp.yml | 9 ++ .../MfaEnrollWebauthnPlatform.yml | 10 ++ .../registration/MfaEnrollWebauthnRoaming.yml | 10 ++ .../UserRegistrationEmailConfirmed.yml | 32 +++++++ ...RegistrationEmailSubmissionRateLimited.yml | 15 +++ .../UserRegistrationEmailSubmitted.yml | 24 +++++ .../UserRegistrationPasswordSubmitted.yml | 22 +++++ .../schemas/events/shared/EventProperties.yml | 35 +++++++ .../schemas/events/shared/Subject.yml | 10 ++ .../events/sign-in/LoggedInAccountPurged.yml | 9 ++ .../events/sign-in/LoggedInPasswordChange.yml | 22 +++++ ...ofileChangeReauthenticationRateLimited.yml | 5 + ...ProfileChangeReauthenticationSubmitted.yml | 10 ++ .../sign-in/LoginEmailAndPasswordAuth.yml | 10 ++ .../events/sign-in/LoginRateLimited.yml | 9 ++ .../events/sign-in/LogoutInitiated.yml | 4 + .../sign-in/MfaLoginBackupCodeSubmitted.yml | 12 +++ .../events/sign-in/MfaLoginPhoneOtpSent.yml | 35 +++++++ .../MfaLoginPhoneOtpSentRateLimited.yml | 9 ++ .../sign-in/MfaLoginPhoneOtpSubmitted.yml | 13 +++ .../sign-in/MfaLoginPivCacSubmitted.yml | 53 +++++++++++ .../events/sign-in/MfaLoginTotpSubmitted.yml | 10 ++ .../MfaLoginWebauthnPlatformSubmitted.yml | 11 +++ .../MfaLoginWebauthnRoamingSubmitted.yml | 12 +++ .../sign-in/MfaSubmissionRateLimited.yml | 14 +++ package.json | 3 + scripts/lint-openapi-spec.mjs | 15 +++ yarn.lock | 76 ++++++++++++++- 80 files changed, 1680 insertions(+), 4 deletions(-) create mode 100644 docs/attempts-api/openapi.yml create mode 100644 docs/attempts-api/paths/poll.yml create mode 100644 docs/attempts-api/paths/status.yml create mode 100644 docs/attempts-api/paths/transmitter-configuration.yml create mode 100644 docs/attempts-api/paths/verification.yml create mode 100644 docs/attempts-api/schemas/AllEvents.yml create mode 100644 docs/attempts-api/schemas/DecodedJWTPayload.yml create mode 100644 docs/attempts-api/schemas/PollParameters.yml create mode 100644 docs/attempts-api/schemas/StreamStatus.yml create mode 100644 docs/attempts-api/schemas/TransmitterConfiguration.yml create mode 100644 docs/attempts-api/schemas/events/AccountRecoveryEvents.yml create mode 100644 docs/attempts-api/schemas/events/IdentityProofingEvents.yml create mode 100644 docs/attempts-api/schemas/events/RegistrationEvents.yml create mode 100644 docs/attempts-api/schemas/events/SignInEvents.yml create mode 100644 docs/attempts-api/schemas/events/account-recovery/AccountResetAccountDeleted.yml create mode 100644 docs/attempts-api/schemas/events/account-recovery/AccountResetCancelRequest.yml create mode 100644 docs/attempts-api/schemas/events/account-recovery/AccountResetRequestSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailConfirmed.yml create mode 100644 docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailSent.yml create mode 100644 docs/attempts-api/schemas/events/account-recovery/ForgotPasswordNewPasswordSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/account-recovery/PersonalKeyReactivationRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/account-recovery/PersonalKeyReactivationSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadMethodSelected.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvGpoLetterRequested.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvGpoVerificationRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvGpoVerificationSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPasswordEntered.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPersonalKeyGenerated.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSent.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSentRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmittedRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSendLinkRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPhoneUploadLinkSent.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvPhoneUploadLinkUsed.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvReproof.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvSsnSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvTmxFraudCheck.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvVerificationRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/identity-proofing/IdvVerificationSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollBackupCode.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollOptionsSelected.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpCodeRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSent.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSentRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollPivCac.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollTotp.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollWebauthnPlatform.yml create mode 100644 docs/attempts-api/schemas/events/registration/MfaEnrollWebauthnRoaming.yml create mode 100644 docs/attempts-api/schemas/events/registration/UserRegistrationEmailConfirmed.yml create mode 100644 docs/attempts-api/schemas/events/registration/UserRegistrationEmailSubmissionRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/registration/UserRegistrationEmailSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/registration/UserRegistrationPasswordSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/shared/EventProperties.yml create mode 100644 docs/attempts-api/schemas/events/shared/Subject.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/LoggedInAccountPurged.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/LoggedInPasswordChange.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/LoggedInProfileChangeReauthenticationRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/LoggedInProfileChangeReauthenticationSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/LoginEmailAndPasswordAuth.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/LoginRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/LogoutInitiated.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/MfaLoginBackupCodeSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSent.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSentRateLimited.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/MfaLoginPivCacSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/MfaLoginTotpSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/MfaLoginWebauthnPlatformSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/MfaLoginWebauthnRoamingSubmitted.yml create mode 100644 docs/attempts-api/schemas/events/sign-in/MfaSubmissionRateLimited.yml create mode 100755 scripts/lint-openapi-spec.mjs diff --git a/Makefile b/Makefile index 76f6e640dcf..737b203ee78 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,7 @@ ARTIFACT_DESTINATION_FILE ?= ./tmp/idp.tar.gz lint_readme \ lint_spec_file_name \ lintfix \ + lint_openapi \ normalize_yaml \ optimize_assets \ optimize_svg \ @@ -107,6 +108,8 @@ endif make lint_spec_file_name @echo "--- lint migrations ---" make lint_migrations + @echo "--- lint openapi spec ---" + make lint_openapi audit: ## Checks packages for vulnerabilities @echo "--- bundler-audit ---" @@ -181,6 +184,9 @@ lint_spec_file_name: -exec false {} + \ -exec echo "Error: Spec files named incorrectly, should end in '.spec.(js|ts|jsx|tsx)':" {} + +lint_openapi: + @yarn lint:openapi + lintfix: ## Try to automatically fix any Ruby, ERB, JavaScript, YAML, or CSS lint errors @echo "--- rubocop fix ---" bundle exec rubocop -a diff --git a/docs/attempts-api/openapi.yml b/docs/attempts-api/openapi.yml new file mode 100644 index 00000000000..04ce4811d6d --- /dev/null +++ b/docs/attempts-api/openapi.yml @@ -0,0 +1,39 @@ +openapi: '3.0.3' +info: + version: 1.0.0 + title: Attempts Stream API for Login.gov + license: + name: CC0 1.0 Universal + url: https://creativecommons.org/publicdomain/zero/1.0/legalcode + description: | + This document contains specifications for the Login.gov Attempts API, a new standards-based polling API based on the Shared Signals Framework (SSF) and the RISC poll-based SET delivery specification (RFC 8936). + + The Attempts API securely transmits integration-specific identity verification event summary data to Relying Parties (RP) for the purposes of fraud prevention and mitigation. + + To ensure the security of data transmission, all endpoints will utilize Bearer Tokens. The format of these Bearer Tokens will be . To ensure the security of individual Security Event Tokens (SETs), each one will be encrypted with a public key provided from the RP. +servers: + - url: https://secure.login.gov + description: Production server (uses live data) + - url: https://idp.int.identitysandbox.gov + description: Sandbox server (uses test data) +paths: + /.well-known/ssf-configuration: + $ref: './paths/transmitter-configuration.yml' + /api/attempts/poll: + $ref: './paths/poll.yml' + /api/attempts/status: + $ref: './paths/status.yml' + /api/attempts/verification: + $ref: './paths/verification.yml' + + +components: + schemas: + DecodedJWTPayload: + $ref: './schemas/DecodedJWTPayload.yml' + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: rp-issuer JWT + description: \ No newline at end of file diff --git a/docs/attempts-api/paths/poll.yml b/docs/attempts-api/paths/poll.yml new file mode 100644 index 00000000000..c01a81953aa --- /dev/null +++ b/docs/attempts-api/paths/poll.yml @@ -0,0 +1,78 @@ +post: + summary: Request for queued events to be returned. + description: |- + A Relying Party will POST this endpoint in order to receive Poll-Based Security Event Tokens (SET). These Sets will be the queued identity verification events. + + The structure of this endpoint is based on the [RISC Specification](https://www.rfc-editor.org/rfc/rfc8936.html#name-polling-http-request) for polling + operationId: poll_events + security: + - BearerAuth: [ ] + requestBody: + description: | + The request is detailed in the RISC spec (https://www.rfc-editor.org/rfc/rfc8936.html#name-polling-http-request): + + When initiating a poll request, the SET Recipient constructs a JSON document that consists of + polling request parameters and SET acknowledgement parameters in the form of JSON objects. + required: false + content: + application/json: + schema: + $ref: "../schemas/PollParameters.yml" + responses: + 200: + description: | + As detailed in the [RISC Spec](https://www.rfc-editor.org/rfc/rfc8936.html#name-polling-http-response): + In response to a poll request, the SET Transmitter checks for available SETs and responds with a JSON document containing the list of SETs. + + A decoded SET looks like: + + ``` + "6454bc0b-00fc-4404-885c-7f07bef243ce": { + "aud": "https://serviceprovider.com", + "iat": 1657920726, + "iss": "https://secure.login.gov", + "jti": "6454bc0b-00fc-4404-885c-7f07bef243ce", + "events": { + "https://schemas.login.gov/secevent/attempts-api/event-type/mfa-enroll-backup-code": { + "application_url": "string", + "occurred_at": 1657920726, + "subject": { + "subject_type": "session", + "session_id": "12345" + }, + "user_ip_address": "string", + "user_uuid": "string", + "unique_session_id": "string", + "success": true + } + } + ``` + content: + application/json: + schema: + type: object + properties: + sets: + type: object + description: The key is the event JTI; the value is an encoded JWT + additionalProperties: + type: object + $ref: '../schemas/DecodedJWTPayload.yml' + 400: + description: | + The 400 (Bad Request) status code indicates that the server cannot or + will not process the request due to something that is perceived to be + a client error (e.g., malformed request syntax, invalid request + message framing, or deceptive request routing). + 401: + description: | + The 401 (Unauthorized) status code indicates that the request has not + been applied because it lacks valid authentication credentials for + the target resource. Please ensure that the request includes a valid Bearer token. + 403: + description: | + The 403 (Forbidden) status code indicates that the server understood + the request but refuses to authorize it. Please reach out to your account manager + if you think this is a mistake. + '5XX': + description: Unexpected error. diff --git a/docs/attempts-api/paths/status.yml b/docs/attempts-api/paths/status.yml new file mode 100644 index 00000000000..e03776d7464 --- /dev/null +++ b/docs/attempts-api/paths/status.yml @@ -0,0 +1,37 @@ +get: + summary: Request to get the status of an Event Stream + description: |- + [Spec](https://openid.net/specs/openid-sharedsignals-framework-1_0-ID3.html#name-reading-a-streams-status) + + An Event Receiver checks the current status of an event stream by making an + HTTP GET request to the stream’s Status Endpoint. + operationId: get_status + security: + - BearerAuth: [ ] + responses: + 200: + description: |- + On receiving a valid request the Event Transmitter responds with a 200 + OK response containing a JSON object with an attribute status + content: + application/json: + schema: + $ref: '../schemas/StreamStatus.yml' + 400: + description: | + The 400 (Bad Request) status code indicates that the server cannot or + will not process the request due to something that is perceived to be + a client error (e.g., malformed request syntax, invalid request + message framing, or deceptive request routing). + 401: + description: | + The 401 (Unauthorized) status code indicates that the request has not + been applied because it lacks valid authentication credentials for + the target resource. Please ensure that the request includes a valid Bearer token. + 403: + description: | + The 403 (Forbidden) status code indicates that the server understood + the request but refuses to authorize it. Please reach out to your account manager + if you think this is a mistake. + '5XX': + description: Unexpected error. \ No newline at end of file diff --git a/docs/attempts-api/paths/transmitter-configuration.yml b/docs/attempts-api/paths/transmitter-configuration.yml new file mode 100644 index 00000000000..92348f8611e --- /dev/null +++ b/docs/attempts-api/paths/transmitter-configuration.yml @@ -0,0 +1,22 @@ +get: + summary: Request for transmitter configuration + description: Return Transmitter Configuration information. + operationId: _well_known_configuration_get + security: [ ] + responses: + 200: + description: | + A set of Claims about the Transmitter's configuration, + including all necessary endpoints and public key location information + content: + application/json: + schema: + $ref: "../schemas/TransmitterConfiguration.yml" + 400: + description: | + The 400 (Bad Request) status code indicates that the server cannot or + will not process the request due to something that is perceived to be + a client error (e.g., malformed request syntax, invalid request + message framing, or deceptive request routing). + "5XX": + description: Unexpected error. \ No newline at end of file diff --git a/docs/attempts-api/paths/verification.yml b/docs/attempts-api/paths/verification.yml new file mode 100644 index 00000000000..91727168d73 --- /dev/null +++ b/docs/attempts-api/paths/verification.yml @@ -0,0 +1,27 @@ +post: + summary: Request that a verification event be sent over an Event Stream + operationId: verification_request + security: + - BearerAuth: [ ] + responses: + 204: + description: | + Request for verification event was successfully received. + 400: + description: | + The 400 (Bad Request) status code indicates that the server cannot or + will not process the request due to something that is perceived to be + a client error (e.g., malformed request syntax, invalid request + message framing, or deceptive request routing). + 401: + description: | + The 401 (Unauthorized) status code indicates that the request has not + been applied because it lacks valid authentication credentials for + the target resource. Please ensure that the request includes a valid Bearer token. + 403: + description: | + The 403 (Forbidden) status code indicates that the server understood + the request but refuses to authorize it. Please reach out to your account manager + if you think this is a mistake. + '5XX': + description: Unexpected error. \ No newline at end of file diff --git a/docs/attempts-api/schemas/AllEvents.yml b/docs/attempts-api/schemas/AllEvents.yml new file mode 100644 index 00000000000..16b12ed0110 --- /dev/null +++ b/docs/attempts-api/schemas/AllEvents.yml @@ -0,0 +1,6 @@ +type: object +allOf: + - $ref: './events/RegistrationEvents.yml' + - $ref: './events/SignInEvents.yml' + - $ref: './events/AccountRecoveryEvents.yml' + - $ref: './events/IdentityProofingEvents.yml' diff --git a/docs/attempts-api/schemas/DecodedJWTPayload.yml b/docs/attempts-api/schemas/DecodedJWTPayload.yml new file mode 100644 index 00000000000..17a7052b8e1 --- /dev/null +++ b/docs/attempts-api/schemas/DecodedJWTPayload.yml @@ -0,0 +1,26 @@ +type: object +title: 'Decoded JWT Payload' +description: | + Default payload body for each event. +properties: + aud: + type: string + description: Event audience, ie the issuer of the RP's integration. + example: https://serviceprovider.com + iat: + type: integer + format: int64 + description: | + Issued at timestamp for when the SET was created. `occurred_at` within the event may be a more reliable indicator of when the user action occurred, though in practical terms the two will typically be identical. + example: 1657920726 + iss: + type: string + description: Event issuer, which will be a Login.gov URL indicating the environment in which the event occurred. + example: https://secure.login.gov + jti: + type: string + description: | + Transaction ID, a unique identifier for the SET. This functions as the CSP transaction ID. + example: 6454bc0b-00fc-4404-885c-7f07bef243ce + events: + $ref: './AllEvents.yml' diff --git a/docs/attempts-api/schemas/PollParameters.yml b/docs/attempts-api/schemas/PollParameters.yml new file mode 100644 index 00000000000..10e26e0d04c --- /dev/null +++ b/docs/attempts-api/schemas/PollParameters.yml @@ -0,0 +1,11 @@ +type: object +title: Polling Request +description: | + Acceptable body to poll the Attempts API +properties: + acks: + type: array + items: + type: string + description: | + List of event JTIs that the receiver is acknowledging. The Transmitter can stop keeping track of these. \ No newline at end of file diff --git a/docs/attempts-api/schemas/StreamStatus.yml b/docs/attempts-api/schemas/StreamStatus.yml new file mode 100644 index 00000000000..92da3ce6840 --- /dev/null +++ b/docs/attempts-api/schemas/StreamStatus.yml @@ -0,0 +1,31 @@ +type: object +properties: + status: + type: string + example: disabled + enum: + - disabled + - enabled + - paused + description: |- + REQUIRED. The status of the stream. Values can be one of: + + `enabled`: + The Transmitter MUST transmit events over the stream, + according to the stream’s configured delivery method. + + `paused`: + The Transmitter MUST NOT transmit events over the stream. + The transmitter will hold any events it would have transmitted while paused, + and SHOULD transmit them when the stream’s status becomes enabled. + If a Transmitter holds successive events that affect the same Subject Principal, + then the Transmitter MUST make sure that those events are transmitted in + the order of time that they were generated OR the Transmitter MUST send + only the last events that do not require the previous events affecting + the same Subject Principal to be processed by the Receiver, + because the previous events are either cancelled by the later events or + the previous events are outdated. + + `disabled`: + The Transmitter MUST NOT transmit events over the stream, + and will not hold any events for later transmission. \ No newline at end of file diff --git a/docs/attempts-api/schemas/TransmitterConfiguration.yml b/docs/attempts-api/schemas/TransmitterConfiguration.yml new file mode 100644 index 00000000000..39dde38b949 --- /dev/null +++ b/docs/attempts-api/schemas/TransmitterConfiguration.yml @@ -0,0 +1,66 @@ +type: object +title: Transmitter Configuration Response +description: | + Metadata describing the Login.gov transmitter configuration. +properties: + issuer: + type: string + description: | + Event issuer, which will be a Login.gov URL indicating the environment in which the event occurred. + + This MUST be identical to the iss claim value in Security Event Tokens issued from this Transmitter. + format: uri + example: https://secure.login.gov + jwks_uri: + type: string + description: | + URL of the Transmitter's JSON Web Key Set + format: uri + example: https://secure.login.gov/api/openid_connect/certs + delivery: + type: object + description: Details about the delivery method and endpoint + properties: + endpoint_url: + type: string + format: uri + description: | + The URL of the polling endpoint + + prod: `https://secure.login.gov/api/attempts/poll` + + sandbox: `https://idp.int.identitysandbox.gov/attempts/poll` + example: https://secure.login.gov/api/attempts/poll + method: + type: string + format: uri + enum: + - https://schemas.openid.net/secevent/risc/delivery-method/poll + delivery_methods_supported: + type: array + items: + type: string + format: uri + enum: + - https://schemas.openid.net/secevent/risc/delivery-method/poll + description: List of supported delivery method URIs. + status_endpoint: + type: string + format: uri + description: | + The URL of the Status Endpoint. + + prod: `https://secure.login.gov/api/attempts/status` + + sandbox: `https://idp.int.identitysandbox.gov/attempts/status` + example: https://secure.login.gov/api/attempts/status + verification_endpoint: + type: string + format: uri + description: | + The URL of the Verification Endpoint. + + prod: `https://secure.login.gov/api/attempts/verification` + + sandbox: `https://idp.int.identitysandbox.gov/attempts/verification` + example: https://secure.login.gov/api/attempts/verification diff --git a/docs/attempts-api/schemas/events/AccountRecoveryEvents.yml b/docs/attempts-api/schemas/events/AccountRecoveryEvents.yml new file mode 100644 index 00000000000..c8fb28246de --- /dev/null +++ b/docs/attempts-api/schemas/events/AccountRecoveryEvents.yml @@ -0,0 +1,19 @@ +properties: + account-reset-account-deleted: + $ref: './account-recovery/AccountResetAccountDeleted.yml' + account-reset-cancel-request: + $ref: './account-recovery/AccountResetCancelRequest.yml' + account-reset-request-submitted: + $ref: './account-recovery/AccountResetRequestSubmitted.yml' + forgot-password-email-confirmed: + $ref: './account-recovery/ForgotPasswordEmailConfirmed.yml' + forgot-password-email-rate-limited: + $ref: './account-recovery/ForgotPasswordEmailRateLimited.yml' + forgot-password-email-sent: + $ref: './account-recovery/ForgotPasswordEmailSent.yml' + forgot-password-new-password-submitted: + $ref: './account-recovery/ForgotPasswordNewPasswordSubmitted.yml' + personal-key-reactivation-submitted: + $ref: './account-recovery/PersonalKeyReactivationSubmitted.yml' + personal-key-reactivation-rate-limited: + $ref: './account-recovery/PersonalKeyReactivationRateLimited.yml' \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/IdentityProofingEvents.yml b/docs/attempts-api/schemas/events/IdentityProofingEvents.yml new file mode 100644 index 00000000000..93b254410fc --- /dev/null +++ b/docs/attempts-api/schemas/events/IdentityProofingEvents.yml @@ -0,0 +1,43 @@ +properties: + idv-document-upload-method-selected: + $ref: './identity-proofing/IdvDocumentUploadMethodSelected.yml' + idv-document-upload-submitted: + $ref: './identity-proofing/IdvDocumentUploadSubmitted.yml' + idv-document-upload-rate-limited: + $ref: './identity-proofing/IdvDocumentUploadRateLimited.yml' + idv-gpo-letter-requested: + $ref: './identity-proofing/IdvGpoLetterRequested.yml' + idv-gpo-verification-submitted: + $ref: './identity-proofing/IdvGpoVerificationSubmitted.yml' + idv-gpo-verification-rate-limited: + $ref: './identity-proofing/IdvGpoVerificationRateLimited.yml' + idv-password-entered: + $ref: './identity-proofing/IdvPasswordEntered.yml' + idv-personal-key-generated: + $ref: './identity-proofing/IdvPersonalKeyGenerated.yml' + idv-phone-otp-sent: + $ref: './identity-proofing/IdvPhoneOtpSent.yml' + idv-phone-otp-submitted: + $ref: './identity-proofing/IdvPhoneOtpSubmitted.yml' + idv-phone-otp-submitted-rate-limited: + $ref: './identity-proofing/IdvPhoneOtpSubmittedRateLimited.yml' + idv-phone-otp-sent-rate-limited: + $ref: './identity-proofing/IdvPhoneOtpSentRateLimited.yml' + idv-phone-send-link-rate-limited: + $ref: './identity-proofing/IdvPhoneSendLinkRateLimited.yml' + idv-phone-submitted: + $ref: './identity-proofing/IdvPhoneSubmitted.yml' + idv-phone-upload-link-sent: + $ref: './identity-proofing/IdvPhoneUploadLinkSent.yml' + idv-phone-upload-link-used: + $ref: './identity-proofing/IdvPhoneUploadLinkUsed.yml' + idv-reproof: + $ref: './identity-proofing/IdvReproof.yml' + idv-ssn-submitted: + $ref: './identity-proofing/IdvSsnSubmitted.yml' + idv-tmx-fraud-check: + $ref: './identity-proofing/IdvTmxFraudCheck.yml' + idv-verification-submitted: + $ref: './identity-proofing/IdvVerificationSubmitted.yml' + idv-verification-rate-limited: + $ref: './identity-proofing/IdvVerificationRateLimited.yml' diff --git a/docs/attempts-api/schemas/events/RegistrationEvents.yml b/docs/attempts-api/schemas/events/RegistrationEvents.yml new file mode 100644 index 00000000000..1f6e39b8cc3 --- /dev/null +++ b/docs/attempts-api/schemas/events/RegistrationEvents.yml @@ -0,0 +1,31 @@ +properties: + mfa-enroll-backup-code: + $ref: './registration/MfaEnrollBackupCode.yml' + mfa-enroll-options-selected: + $ref: './registration/MfaEnrollOptionsSelected.yml' + mfa-enroll-phone-otp-sent: + $ref: './registration/MfaEnrollPhoneOtpSent.yml' + mfa-enroll-phone-otp-code-rate-limited: + $ref: './registration/MfaEnrollPhoneOtpCodeRateLimited.yml' + mfa-enroll-phone-otp-sent-rate-limited: + $ref: './registration/MfaEnrollPhoneOtpSentRateLimited.yml' + mfa-enroll-phone-otp-submitted: + # note: should this event not include "submitted" for consistency? + # Or is that confusing with the "sent" event? + $ref: './registration/MfaEnrollPhoneOtpSubmitted.yml' + mfa-enroll-piv-cac: + $ref: './registration/MfaEnrollPivCac.yml' + mfa-enroll-totp: + $ref: './registration/MfaEnrollTotp.yml' + mfa-enroll-webauthn-platform: + $ref: './registration/MfaEnrollWebauthnPlatform.yml' + mfa-enroll-webauthn-roaming: + $ref: './registration/MfaEnrollWebauthnRoaming.yml' + user-registration-email-confirmed: + $ref: './registration/UserRegistrationEmailConfirmed.yml' + user-registration-email-submission-rate-limited: + $ref: './registration/UserRegistrationEmailSubmissionRateLimited.yml' + user-registration-email-submitted: + $ref: './registration/UserRegistrationEmailSubmitted.yml' + user-registration-password-submitted: + $ref: './registration/UserRegistrationPasswordSubmitted.yml' \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/SignInEvents.yml b/docs/attempts-api/schemas/events/SignInEvents.yml new file mode 100644 index 00000000000..65430d91bd6 --- /dev/null +++ b/docs/attempts-api/schemas/events/SignInEvents.yml @@ -0,0 +1,33 @@ +properties: + logged-in-account-purged: + $ref: './sign-in/LoggedInAccountPurged.yml' + logged-in-password-change: + $ref: './sign-in/LoggedInPasswordChange.yml' + logged-in-profile-change-reauthentication-submitted: + $ref: './sign-in/LoggedInProfileChangeReauthenticationSubmitted.yml' + logged-in-profile-change-reauthentication-rate-limited: + $ref: './sign-in/LoggedInProfileChangeReauthenticationRateLimited.yml' + login-email-and-password-auth: + $ref: './sign-in/LoginEmailAndPasswordAuth.yml' + login-rate-limited: + $ref: './sign-in/LoginRateLimited.yml' + logout-initiated: + $ref: './sign-in/LogoutInitiated.yml' + mfa-login-backup-code-submitted: + $ref: './sign-in/MfaLoginBackupCodeSubmitted.yml' + mfa-login-phone-otp-sent: + $ref: './sign-in/MfaLoginPhoneOtpSent.yml' + mfa-login-phone-otp-sent-rate-limited: + $ref: './sign-in/MfaLoginPhoneOtpSentRateLimited.yml' + mfa-login-phone-otp-submitted: + $ref: './sign-in/MfaLoginPhoneOtpSubmitted.yml' + mfa-login-piv-cac-submitted: + $ref: './sign-in/MfaLoginPivCacSubmitted.yml' + mfa-login-totp-submitted: + $ref: './sign-in/MfaLoginTotpSubmitted.yml' + mfa-login-webauthn-platform-submitted: + $ref: './sign-in/MfaLoginWebauthnPlatformSubmitted.yml' + mfa-login-webauthn-roaming-submitted: + $ref: './sign-in/MfaLoginWebauthnRoamingSubmitted.yml' + mfa-submitted-rate-limited: + $ref: './sign-in/MfaSubmissionRateLimited.yml' diff --git a/docs/attempts-api/schemas/events/account-recovery/AccountResetAccountDeleted.yml b/docs/attempts-api/schemas/events/account-recovery/AccountResetAccountDeleted.yml new file mode 100644 index 00000000000..979026b7d0c --- /dev/null +++ b/docs/attempts-api/schemas/events/account-recovery/AccountResetAccountDeleted.yml @@ -0,0 +1,23 @@ +description: | + A user requesting an account reset has completed the process. As a result, the account was deleted successfully. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + token: + type: array + description: Errors that explain the failure. + items: + type: string + enum: + - The link to delete your Login.gov account has expired. Please create another request to delete your account. + success: + type: boolean + description: | + Indicates whether the account was successfully deleted. + \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/account-recovery/AccountResetCancelRequest.yml b/docs/attempts-api/schemas/events/account-recovery/AccountResetCancelRequest.yml new file mode 100644 index 00000000000..056e3066807 --- /dev/null +++ b/docs/attempts-api/schemas/events/account-recovery/AccountResetCancelRequest.yml @@ -0,0 +1,5 @@ +description: | + A user who has requested an account reset has canceled their request. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/account-recovery/AccountResetRequestSubmitted.yml b/docs/attempts-api/schemas/events/account-recovery/AccountResetRequestSubmitted.yml new file mode 100644 index 00000000000..71483adef31 --- /dev/null +++ b/docs/attempts-api/schemas/events/account-recovery/AccountResetRequestSubmitted.yml @@ -0,0 +1,10 @@ +description: | + A user is able to login with their email address and password, but is unable to use any of their MFA methods to complete the login process. In this case, users can request to reset their account (which effectively deletes their account and allows them to create a new one). +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the account reset request was successfully submitted. diff --git a/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailConfirmed.yml b/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailConfirmed.yml new file mode 100644 index 00000000000..7644213bade --- /dev/null +++ b/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailConfirmed.yml @@ -0,0 +1,25 @@ +description: | + A password reset having been previously requested, a user has clicked the link in the email to reset their password, confirming the email address’s validity. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + user: + type: array + description: Errors that explain the failure. "blank" indicates that the token provided in the link is invalid, i.e. does not match an existing user. + items: + type: string + enum: + - blank + - token_expired + success: + type: boolean + description: | + Indicates whether the link clicked on by a user is valid and not expired. + + \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailRateLimited.yml b/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailRateLimited.yml new file mode 100644 index 00000000000..b6e5e59ff33 --- /dev/null +++ b/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailRateLimited.yml @@ -0,0 +1,9 @@ +description: | + User has exceeded the number of email reset notifications that can be sent to one user’s email addresses. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + email: + type: string + description: User's email address \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailSent.yml b/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailSent.yml new file mode 100644 index 00000000000..d70888d6dd7 --- /dev/null +++ b/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordEmailSent.yml @@ -0,0 +1,10 @@ +description: | + A user has requested a password reset. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + email: + type: string + description: User's email address + \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordNewPasswordSubmitted.yml b/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordNewPasswordSubmitted.yml new file mode 100644 index 00000000000..14adbe9cf0b --- /dev/null +++ b/docs/attempts-api/schemas/events/account-recovery/ForgotPasswordNewPasswordSubmitted.yml @@ -0,0 +1,32 @@ +description: | + After previously requesting a password reset and having already clicked through from the email, a user has now submitted a new password. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + password: + type: array + description: Errors that explain the failure. + items: + type: string + enum: + - pwned + - too_short + reset_password_token: + type: array + description: Errors that explain the failure. + items: + type: string + enum: + - token_expired_error + success: + type: boolean + description: | + Indicates whether the new password was successfully submitted. + + \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/account-recovery/PersonalKeyReactivationRateLimited.yml b/docs/attempts-api/schemas/events/account-recovery/PersonalKeyReactivationRateLimited.yml new file mode 100644 index 00000000000..2b274d6055b --- /dev/null +++ b/docs/attempts-api/schemas/events/account-recovery/PersonalKeyReactivationRateLimited.yml @@ -0,0 +1,5 @@ +description: | + When the user exceeds the limit for entering their personal key to reactivate their account incorrectly. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object diff --git a/docs/attempts-api/schemas/events/account-recovery/PersonalKeyReactivationSubmitted.yml b/docs/attempts-api/schemas/events/account-recovery/PersonalKeyReactivationSubmitted.yml new file mode 100644 index 00000000000..ecb6a1a7cee --- /dev/null +++ b/docs/attempts-api/schemas/events/account-recovery/PersonalKeyReactivationSubmitted.yml @@ -0,0 +1,24 @@ +description: | + A user has submitted their personal key to reactivate their account. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + personal_key: + type: array + description: Errors that explain the failure. + items: + type: string + enum: + - blank + - personal_key_incorrect + success: + type: boolean + description: | + Indicates whether the entered personal key matched and the account was reactivated successfully. + \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadMethodSelected.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadMethodSelected.yml new file mode 100644 index 00000000000..cccddea6da2 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadMethodSelected.yml @@ -0,0 +1,11 @@ +description: | + When the user selects the method (mobile or desktop) for uploading the documentation required for identity proofing. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + upload_method: + type: string + enum: + - desktop + - mobile diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadRateLimited.yml new file mode 100644 index 00000000000..253c65e6d7a --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadRateLimited.yml @@ -0,0 +1,5 @@ +description: | + A user exceeds the failure limit for document capture during identity proofing. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadSubmitted.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadSubmitted.yml new file mode 100644 index 00000000000..d6e36ccb874 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvDocumentUploadSubmitted.yml @@ -0,0 +1,92 @@ +description: | + The document was uploaded during identity proofing and authenticated by the vendor. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + document_state: + type: string + description: Driver's licenses document_state + document_number: + type: string + description: Driver's license number + document_issued: + type: string + description: Issuance date + document_expiration: + type: string + description: Driver's license expiration + first_name: + type: string + last_name: + type: string + date_of_birth: + type: string + address: + type: string + document_front_image_file_id: + type: string + description: The ID used to retrieve this image if needed + document_back_image_file_id: + type: string + description: The ID used to retrieve this image if needed + document_front_image_encryption_key: + type: string + description: Randomly generated Base64-encoded key used to encrypt the front image file. + document_back_image_encryption_key: + type: string + description: Randomly generated Base64-encoded key used to encrypt the back image file. + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + pii: + type: array + description: | + Errors that describe the failure + items: + type: string + enum: + - 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 you have one. + - We couldn't read the barcode on the back of your ID. Try taking a new picture. + - We couldn't read the birth date on your ID. Try taking new pictures. + - We couldn't read the reference number on the back of your ID. Try taking a new picture. + - We couldn't recognize your ID. It may be worn or damaged, or the front and the back of the ID don't match. Try taking new pictures. + - We couldn't read the document number on your ID. Try taking new pictures. + - The document has expired, or we couldn't read the expiration date on your ID. If your ID is not expired, try taking new pictures. + - We couldn't read the full name on your ID. Try taking new pictures. + - We couldn't recognize your ID. It may be worn, damaged, or a type of ID that we don't recognize. Try taking new pictures. + - We couldn't verify your ID. It might have moved when you took the picture. Try taking new pictures. + - We couldn't read the issue date on your ID. Try taking new pictures. + - We couldn't read the control number barcode. Try taking new pictures. + - We couldn't read the sex on your ID. Try taking new pictures. + - We couldn't verify your ID. It might have moved when you took the picture, or the picture is too dark. Try taking new pictures in brighter lighting. + - We couldn't verify the photo on your ID. Try taking new pictures. + - If the information below is incorrect, please upload new photos of your state-issued ID. + - We couldn't read the barcode on your ID. + - Your camera is blocked + - We don't have permission to access the camera. Please check your browser or system settings, reload this page, or upload a photo instead. + - Camera failed to start, please try again. + - Image is too small or blurry, please try again. + - We couldn't read your ID. Your image sizes may be too small, or your ID is too small or blurry in the photos. Make sure your ID is large within the image frame and try taking new pictures. + - This file type is not accepted, please choose a JPG or PNG file. + - Please add a new image + - We couldn't verify the back of your ID. Try taking a new picture. + - We couldn't verify the front of your ID. Try taking a new picture. + - We are having technical difficulties on our end. Please try to submit your images again later. + - Try taking new pictures. + - Image has glare, please try again. + - We couldn't read your ID. Your photos may have glare. Make sure that the flash on your camera is off and try taking new pictures. + - The image file that you added is not supported. Please take new photos of your ID and try again. + - Your image size is too large or too small. Please add images of your ID that are about 2025 x 1275 pixels. + - The pixel depth of your image file is not supported. Please take new photos of your ID and try again. Supported image pixel depth is 24-bit RGB. + - To continue, sign in to your Login.gov account on any device that has a camera, like a mobile phone, tablet or computer with a webcam. + - The selection was not a valid file. + - Your birthday does not meet the minimum age requirement. + - Image is blurry, please try again. + - We couldn't read your ID. Your photos may be too blurry or dark. Try taking new pictures in a bright area. + success: + type: boolean + description: | + Indicates whether the backup codes were successfully generated \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvGpoLetterRequested.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvGpoLetterRequested.yml new file mode 100644 index 00000000000..eebf0bcf920 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvGpoLetterRequested.yml @@ -0,0 +1,11 @@ +description: | + The user has requested that a letter be sent via mail in order to verify their address. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + resend: + type: boolean + description: | + False indicates that this is the initial request to be sent + True indicates a resend request. diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvGpoVerificationRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvGpoVerificationRateLimited.yml new file mode 100644 index 00000000000..ae47d270ec9 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvGpoVerificationRateLimited.yml @@ -0,0 +1,5 @@ +description: | + When the user exceeds the limit for entering the security code that should have been received in a letter incorrectly during the letter verification process. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvGpoVerificationSubmitted.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvGpoVerificationSubmitted.yml new file mode 100644 index 00000000000..b277310d76f --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvGpoVerificationSubmitted.yml @@ -0,0 +1,20 @@ +description: | + A user that requested to verify their address by mail has entered the security code contained in their letter. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + otp: + type: string + description: An OPTIONAL key if the code does not match + enum: + - confirmation_code_incorrect + success: + type: boolean + description: | + Indicates whether the entered code matched the code that was sent confirming the address diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPasswordEntered.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPasswordEntered.yml new file mode 100644 index 00000000000..8dd4018222b --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPasswordEntered.yml @@ -0,0 +1,10 @@ +description: | + When the user enters their Login.gov password during the identity proofing flow. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the password entered matches the user’s password diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPersonalKeyGenerated.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPersonalKeyGenerated.yml new file mode 100644 index 00000000000..c73a59ef8bf --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPersonalKeyGenerated.yml @@ -0,0 +1,5 @@ +description: | + This event is triggered when a personal key is generated after a user has completed the identity verification process. The personal key can be used if a user forgets their password, which triggers a separate event. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSent.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSent.yml new file mode 100644 index 00000000000..5f1f7ab3eca --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSent.yml @@ -0,0 +1,41 @@ +description: | + When a security code is sent to the phone number provided by the user to verify the phone number belongs to them. The user can choose to receive the code via text message/SMS or phone call. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string + opt_delivery_method: + type: string + enum: + - sms + - voice + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + telephony_errors: + type: array + description: Errors that describe the failure + items: + type: string + enum: + - daily_voice_limit_reached + - duplicate_endpoint + - generic + - invalid_calling_area + - invalid_phone_number + - opt_out + - permanent_failure + - sms_unsupported + - temporary_failure + - throttled + - timeout + - unknown_failure + - voice_unsupported + success: + type: boolean + description: | + Indicates whether the backup codes were successfully generated diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSentRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSentRateLimited.yml new file mode 100644 index 00000000000..554f2b63e38 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSentRateLimited.yml @@ -0,0 +1,8 @@ +description: | + When the user exceeds the number of times they can request a new security code to be sent to their provided phone number for address verification. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmitted.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmitted.yml new file mode 100644 index 00000000000..fab62ced030 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmitted.yml @@ -0,0 +1,30 @@ +description: | + When the user submits the security code that they received on their phone to verify their phone or address during identity proofing. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + # these are true/false bools rather than arrays of strings. + # can we update this to be something like: + # code: + # type: array + # items: + # type: string + # enum: ['code_matches', 'code_expired'] + code_matches: + type: boolean + description: An OPTIONAL key if the code does not match + code_expired: + type: boolean + description: An OPTIONAL key if the code has expired + success: + type: boolean + description: | + Indicates whether the entered code matched what was sent and is still valid. diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmittedRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmittedRateLimited.yml new file mode 100644 index 00000000000..73ccbd42660 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneOtpSubmittedRateLimited.yml @@ -0,0 +1,8 @@ +description: | + When the user exceeds the number of times they can try verifying the security code that they received on their phone for phone or address verification during identity proofing. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSendLinkRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSendLinkRateLimited.yml new file mode 100644 index 00000000000..de5293b217c --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSendLinkRateLimited.yml @@ -0,0 +1,8 @@ +description: | + When the user is rate limited for submitting a phone number to receive a mobile upload link too many times during identity verification. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSubmitted.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSubmitted.yml new file mode 100644 index 00000000000..71a2ddda2af --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneSubmitted.yml @@ -0,0 +1,26 @@ +description: | + When the user provides their phone number for identity verification. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + phone: + type: array + description: An OPTIONAL key that describes errors with the phone number + items: + type: string + enum: + - improbable_phone + - We are unable to call phone numbers in {Country Name} + - We are unable to send text messages to phone numbers in {Country Name} + success: + type: boolean + description: | + Indicates whether the upload link was successfully sent to the user’s phone. diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneUploadLinkSent.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneUploadLinkSent.yml new file mode 100644 index 00000000000..59322f029d9 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneUploadLinkSent.yml @@ -0,0 +1,36 @@ +description: | + If users choose to upload a document via phone, they are sent a message containing a link to use for document upload to the phone. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + telephony: + type: array + description: An OPTIONAL key that describes errors with the link sending telephony + items: + type: string + enum: + - DailyLimitReachedError + - DuplicateEndpointError + - InvalidCallingAreaError + - InvalidPhoneNumberError + - OptOutError + - PermanentFailureError + - SmsUnsupportedError + - TelephonyError + - TemporaryFailureError + - ThrottledError + - TimeoutError + - UnknownFailureError + - VoiceUnsupportedError + success: + type: boolean + description: | + Indicates whether the upload link was successfully sent to the user’s phone. diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneUploadLinkUsed.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneUploadLinkUsed.yml new file mode 100644 index 00000000000..81bb5754f36 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvPhoneUploadLinkUsed.yml @@ -0,0 +1,6 @@ +description: | + When the user uses the link sent to their phone to begin mobile document upload. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvReproof.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvReproof.yml new file mode 100644 index 00000000000..452cee7a965 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvReproof.yml @@ -0,0 +1,5 @@ +description: | + This event is triggered when a user who has previously completed proofing completes identity proofing again. Note that the reproofing flow is identical to the regular proofing flow and will generate the same events as initial proofing. This is an additional event signifying that the just-completed proofing process was not the first time the user has proofed. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvSsnSubmitted.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvSsnSubmitted.yml new file mode 100644 index 00000000000..d2fe519af47 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvSsnSubmitted.yml @@ -0,0 +1,9 @@ +description: | + When the user submits their Social Security Number during identity proofing. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + social_security: + type: string + description: User-submitted Social Security Number diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvTmxFraudCheck.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvTmxFraudCheck.yml new file mode 100644 index 00000000000..a77d9da3d87 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvTmxFraudCheck.yml @@ -0,0 +1,41 @@ +description: | + This event captures the result of the TMX fraud check during Identity Verification. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if user has failed fraud review + properties: + tmx_summary_reason_code: + type: array + description: | + An OPTIONAL key that describes rules that failed during the test. Can include multiple elements in the array. + * `Identity_Negative_History`: Rules that indicate a negative history for related Identity (such as black listed email address or phone number). + * `IP_Negative_History`: Rules that indicate a negative history for IP Address (such as block-listed IP/Proxy address). + * `Bot`: Rules that examine velocity counts for anomalies and rules that indicate the presence of an aggregator (Online Bots). + * `Device_Spoofing`: Rules that indicate device anomalies, rules for OS spoofing, and rules that examine the mobile device for malware, root-cloaking applications and so on. + * `Geo_Spoofing`: Rules for anomalies pertaining to geographical location. + * `Identity_Spoofing`: Rules that indicate Identity spoofing such as the presence of multiple logins/email addresses/account addresses and high distance traveled. + * `IP_Spoofing`: Rules that indicate IP spoofing and rules for VPN detection. + * `MITB`: Rules that check the presence of malware tags. + * `Level_1_Link_Reject`: Rules that indicate that there is a direct link to historic confirmed fraud. + * `Level_1_Link_Accept`: Indicates the user has passed fraud check + items: + type: string + enum: + - Bot + - Device_Spoofing + - Geo_Spoofing + - Identity_Negative_History + - Identity_Spoofing + - IP_Negative_History + - Level_1_Link_Accept + - Level_1_Link_Reject + - MITB + success: + type: boolean + description: | + Indicates whether the TMX response status pass. diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationRateLimited.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationRateLimited.yml new file mode 100644 index 00000000000..ed24326b19a --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationRateLimited.yml @@ -0,0 +1,14 @@ +description: | + The user reaches verification submission rate limits when identity proofing. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + throttle_context: + type: string + description: | + `multi-session` context means that multiple sessions attempted to submit verification using the same Social Security number multiple times with failure and exceeded the rate limit. + `single-session` context means that one session attempted to submit verification using the same PII multiple times with failure and exceeded the rate limit. + enum: + - multi-session + - single-session diff --git a/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationSubmitted.yml b/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationSubmitted.yml new file mode 100644 index 00000000000..75b5995df29 --- /dev/null +++ b/docs/attempts-api/schemas/events/identity-proofing/IdvVerificationSubmitted.yml @@ -0,0 +1,40 @@ +description: | + When the user verifies their information when identity proofing. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + document_state: + type: string + description: Driver's licenses document_state + document_number: + type: string + description: Driver's license number + document_issued: + type: string + description: Issuance date + document_expiration: + type: string + description: Driver's license expiration + first_name: + type: string + last_name: + type: string + date_of_birth: + type: string + address: + type: string + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + pii: + type: array + # What is the best way of documenting all these errors? https://docs.google.com/document/d/1H7SAM8DSnbRJqKoSj4Qdzr-UvLCNMupt2zoqHLsIFe8/edit?tab=t.0#heading=h.26slvbezaoz5 + # They won't be very readable in this document + # is there a way for us to make errors out of this more in line with other failure_reasons? + success: + type: boolean + description: | + Indicates whether the verification form was submitted successfully \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollBackupCode.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollBackupCode.yml new file mode 100644 index 00000000000..48a0b749857 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollBackupCode.yml @@ -0,0 +1,9 @@ +description: The user has selected a set of backup codes for MFA. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the backup codes were successfully generated diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollOptionsSelected.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollOptionsSelected.yml new file mode 100644 index 00000000000..3807c66a872 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollOptionsSelected.yml @@ -0,0 +1,22 @@ +description: A user has selected MFA options. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + mfa_device_types: + type: array + items: + type: string + enum: + - auth_app + - backup_code + - phone + - piv_cac + - sms + - voice + - webauthn + - webauthn_platform + success: + type: boolean + description: | + Indicates whether an MFA option was successfully chosen during the enrollment process. diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpCodeRateLimited.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpCodeRateLimited.yml new file mode 100644 index 00000000000..1458c02a439 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpCodeRateLimited.yml @@ -0,0 +1,10 @@ +description: | + The user has exceeded the rate limit for entering an MFA code when setting up an MFA device. Note that this event is currently only generated for phone mfa types. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + mfa_device_type: + type: string + enum: + - otp diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSent.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSent.yml new file mode 100644 index 00000000000..c54ab1e1022 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSent.yml @@ -0,0 +1,18 @@ +description: | + Relevant only when the user is enrolling a phone as their MFA. The user has been sent a one-time password by Login.gov over SMS or voice during the MFA enrollment process. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string + description: The provided phone number + otp_delivery_method: + type: string + enum: + - sms + - voice + success: + type: boolean + description: | + Indicates whether the one-time password was sent. diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSentRateLimited.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSentRateLimited.yml new file mode 100644 index 00000000000..9697509f830 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSentRateLimited.yml @@ -0,0 +1,9 @@ +description: | + The user has exceeded the rate limit for requesting OTPs be sent to their phone when setting up an MFA device. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string + description: The provided phone number diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSubmitted.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSubmitted.yml new file mode 100644 index 00000000000..06eba494d7a --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollPhoneOtpSubmitted.yml @@ -0,0 +1,10 @@ +description: | + The user, after having previously been sent an OTP code during phone enrollment, has been asked to submit that code. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the entered code matched what was sent AND was not expired. diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollPivCac.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollPivCac.yml new file mode 100644 index 00000000000..7da2146d377 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollPivCac.yml @@ -0,0 +1,58 @@ +description: | + The user, after having previously been sent an OTP code during phone enrollment, has been asked to submit that code. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + subject_dn: + type: string + description: | + The subject's Distinguished Name (DN) + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + piv_cac: + type: array + description: An OPTIONAL key that describes errors with the PIV/CAC + items: + type: string + enum: + - already_associated + user: + type: array + description: An OPTIONAL key that describes errors with the user + items: + type: string + enum: + - not_found + - piv_cac_mismatch + certificate: + type: array + description: An OPTIONAL key that describes errors with the certificate + items: + type: string + enum: + - bad + - expired + - invalid + - none + - not_auth_cert + - revoked + - self-signed cert + - unverified + token: + type: array + description: An OPTIONAL key that describes errors with the certificate - + items: + type: string + enum: + - bad + - http_failure + - invalid + - missing + success: + type: boolean + description: | + Indicates whether the entered code matched what was sent AND was not expired. diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollTotp.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollTotp.yml new file mode 100644 index 00000000000..7764ea342d9 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollTotp.yml @@ -0,0 +1,9 @@ +description: The user has enrolled a TOTP device for MFA. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the entered code matched what was sent AND was not expired. diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollWebauthnPlatform.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollWebauthnPlatform.yml new file mode 100644 index 00000000000..b63360991b6 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollWebauthnPlatform.yml @@ -0,0 +1,10 @@ +description: The user has enrolled face or touch unlock for MFA. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the platform device was successfully added for MFA. + diff --git a/docs/attempts-api/schemas/events/registration/MfaEnrollWebauthnRoaming.yml b/docs/attempts-api/schemas/events/registration/MfaEnrollWebauthnRoaming.yml new file mode 100644 index 00000000000..085a308e7e3 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/MfaEnrollWebauthnRoaming.yml @@ -0,0 +1,10 @@ +description: The user has enrolled a security key for MFA. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the security key was successfully added for MFA. + diff --git a/docs/attempts-api/schemas/events/registration/UserRegistrationEmailConfirmed.yml b/docs/attempts-api/schemas/events/registration/UserRegistrationEmailConfirmed.yml new file mode 100644 index 00000000000..2aa157253a8 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/UserRegistrationEmailConfirmed.yml @@ -0,0 +1,32 @@ +description: | + User clicks on the confirmation link sent to their provided email address. Note: Internally, Login's meaning of a 'confirmed' email is overloaded, and also requires that a password has been set. This event uses the plain-language meaning of 'confirmed': a user clicked the link in an email, showing ownership of the email account. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + email: + type: string + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + email: + type: array + description: An OPTIONAL key that describes errors with the email provided + items: + type: string + enum: + - already_confirmed + confirmation_token: + type: array + description: An OPTIONAL key that describes errors with the confirmation token + items: + type: string + enum: + - expired + - not_found + success: + type: boolean + description: | + Indicates whether the user clicked on the link that was sent to them and confirmed the email they submitted is theirs. diff --git a/docs/attempts-api/schemas/events/registration/UserRegistrationEmailSubmissionRateLimited.yml b/docs/attempts-api/schemas/events/registration/UserRegistrationEmailSubmissionRateLimited.yml new file mode 100644 index 00000000000..a495057bcaf --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/UserRegistrationEmailSubmissionRateLimited.yml @@ -0,0 +1,15 @@ +description: | + This event can occur during sign up when a user attempts to submit the same email address multiple times without setting up a password. Note that Login.gov does not actually prevent the user from retrying in this case. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + email: + type: string + email_already_registered: + type: boolean + # i'm not sure what this description means, exactly. can we come + # up with better language to describe this case? + description: | + A 'true' value indicates that the user has exceeded the rate limit for submitting an email address that is already registered. A 'false' value indicates that the user has exceeded the rate limit for submitting an email address that has *not* been registered previously. + diff --git a/docs/attempts-api/schemas/events/registration/UserRegistrationEmailSubmitted.yml b/docs/attempts-api/schemas/events/registration/UserRegistrationEmailSubmitted.yml new file mode 100644 index 00000000000..cadbd29ae5d --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/UserRegistrationEmailSubmitted.yml @@ -0,0 +1,24 @@ +description: A user provides their email address to create a new account. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + email: + type: string + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + email: + type: array + description: Errors that explain the failure + items: + type: string + enum: + - Email address is not valid + - invalid + success: + type: boolean + description: | + Indicates whether the account was created successfully for the submitted email. \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/registration/UserRegistrationPasswordSubmitted.yml b/docs/attempts-api/schemas/events/registration/UserRegistrationPasswordSubmitted.yml new file mode 100644 index 00000000000..0da690a4f36 --- /dev/null +++ b/docs/attempts-api/schemas/events/registration/UserRegistrationPasswordSubmitted.yml @@ -0,0 +1,22 @@ +description: A user provides a password for their account. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + password: + type: array + description: Errors that explain the failure + items: + type: string + enum: + - pwned + - too_short + success: + type: boolean + description: | + Indicates whether the entered password is valid and matches the user’s password. \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/shared/EventProperties.yml b/docs/attempts-api/schemas/events/shared/EventProperties.yml new file mode 100644 index 00000000000..97091c47d46 --- /dev/null +++ b/docs/attempts-api/schemas/events/shared/EventProperties.yml @@ -0,0 +1,35 @@ +type: object +properties: + application_url: + type: string + description: | + Indicates the SP URL (redirect_uri) from which the action was triggered. + client_port: + type: string + description: Known more commonly as ephemeral port numbers associated with the Client IP – This is NOT the CSP server port 443 or 80. + device_fingerprint: + type: string + description: Hashed cookie device UUID + occurred_at: + type: integer + format: int64 + description: The time when the event occurred. + subject: + $ref: './Subject.yml' + useragent_string: + type: string + description: The end user's browser's useragent string, which identifies the type of browser, device and operating system being used as well as the version of the browser. + user_ip_address: + type: string + description: The end user’s IP address. + user_uuid: + type: string + description: | + The primary key identifying a user in Login.gov. This field is populated only for users who have used their accounts to access the Agency Application, since it is generated when a user provides their consent for the first time. + unique_session_id: + type: string + description: | + A string identifying the Login.gov session ID for the user. This is generated by Login.gov and is distinct from the SP-provided session_id within the subject attribute. + + + \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/shared/Subject.yml b/docs/attempts-api/schemas/events/shared/Subject.yml new file mode 100644 index 00000000000..2a2abe5970d --- /dev/null +++ b/docs/attempts-api/schemas/events/shared/Subject.yml @@ -0,0 +1,10 @@ +type: object +properties: + subject_type: + type: string + enum: + - session + session_id: + type: string + description: Matches the session ID passed in from the service provider + example: '12345' diff --git a/docs/attempts-api/schemas/events/sign-in/LoggedInAccountPurged.yml b/docs/attempts-api/schemas/events/sign-in/LoggedInAccountPurged.yml new file mode 100644 index 00000000000..689b311bb18 --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/LoggedInAccountPurged.yml @@ -0,0 +1,9 @@ +description: A logged in user deletes their account. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the the account was successfully deleted. diff --git a/docs/attempts-api/schemas/events/sign-in/LoggedInPasswordChange.yml b/docs/attempts-api/schemas/events/sign-in/LoggedInPasswordChange.yml new file mode 100644 index 00000000000..589e7e8ed81 --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/LoggedInPasswordChange.yml @@ -0,0 +1,22 @@ +description: A logged in user changes the password for their account. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + password: + type: array + description: Errors that explain the failure + items: + type: string + enum: + - pwned + - too_short + success: + type: boolean + description: | + Indicates whether the password was successfully changed by logged in user diff --git a/docs/attempts-api/schemas/events/sign-in/LoggedInProfileChangeReauthenticationRateLimited.yml b/docs/attempts-api/schemas/events/sign-in/LoggedInProfileChangeReauthenticationRateLimited.yml new file mode 100644 index 00000000000..e2ad48bdfe2 --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/LoggedInProfileChangeReauthenticationRateLimited.yml @@ -0,0 +1,5 @@ +description: | + When the user exceeds the limit for entering their current Login.gov password during the logged-in change profile-info flow incorrectly. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object diff --git a/docs/attempts-api/schemas/events/sign-in/LoggedInProfileChangeReauthenticationSubmitted.yml b/docs/attempts-api/schemas/events/sign-in/LoggedInProfileChangeReauthenticationSubmitted.yml new file mode 100644 index 00000000000..beec92bb733 --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/LoggedInProfileChangeReauthenticationSubmitted.yml @@ -0,0 +1,10 @@ +description: | + When the user enters their current Login.gov password during the logged-in change profile-info flow. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the password entered matches the user’s password diff --git a/docs/attempts-api/schemas/events/sign-in/LoginEmailAndPasswordAuth.yml b/docs/attempts-api/schemas/events/sign-in/LoginEmailAndPasswordAuth.yml new file mode 100644 index 00000000000..927d3522f25 --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/LoginEmailAndPasswordAuth.yml @@ -0,0 +1,10 @@ +description: | + During a login attempt, a user has submitted a password. Rather than tracking the number of failed password auth attempts, we generate this event on each attempt. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the entered email and password were correct. diff --git a/docs/attempts-api/schemas/events/sign-in/LoginRateLimited.yml b/docs/attempts-api/schemas/events/sign-in/LoginRateLimited.yml new file mode 100644 index 00000000000..8d32488eecb --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/LoginRateLimited.yml @@ -0,0 +1,9 @@ +description: | + The user has exceeded the limit for providing their email address and password all resulting in a failed login attempt. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + email: + type: string + description: User-provided email address \ No newline at end of file diff --git a/docs/attempts-api/schemas/events/sign-in/LogoutInitiated.yml b/docs/attempts-api/schemas/events/sign-in/LogoutInitiated.yml new file mode 100644 index 00000000000..73d5e5692ca --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/LogoutInitiated.yml @@ -0,0 +1,4 @@ +description: The user has logged out of their account. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object diff --git a/docs/attempts-api/schemas/events/sign-in/MfaLoginBackupCodeSubmitted.yml b/docs/attempts-api/schemas/events/sign-in/MfaLoginBackupCodeSubmitted.yml new file mode 100644 index 00000000000..17ab80fbdb1 --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/MfaLoginBackupCodeSubmitted.yml @@ -0,0 +1,12 @@ +description: During a login attempt, the user uses face or touch unlock for MFA. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the entered code matched an expected code + + + diff --git a/docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSent.yml b/docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSent.yml new file mode 100644 index 00000000000..ff0454cbaac --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSent.yml @@ -0,0 +1,35 @@ +description: | + During a login attempt, an OTP code has been sent via SMS or Voice. + Reauthentication occurs when the user was already signed in but their session has timed out and they need to reauthenticate to continue. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + otp_delivery_method: + type: string + enum: + - sms + - voice + phone_number: + type: string + description: User-provided phone number + reauthentication: + type: boolean + success: + type: boolean + description: | + Indicates whether the one-time code was sent. + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + telephony: + type: array + description: | + The user registered a phone for MFA, but then opted out by replying “STOP” over text, so when attempting to send a subsequent OTP it fails with this failure reason. + items: + type: string + # is this the only possible failure? + enum: + - "Telephony::OptOutError - Pinpoint Error: PERMANENT_FAILURE - 400" diff --git a/docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSentRateLimited.yml b/docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSentRateLimited.yml new file mode 100644 index 00000000000..e20f110103a --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSentRateLimited.yml @@ -0,0 +1,9 @@ +description: | + During a login attempt, the user has exceeded the limit for requesting an OTP be sent to their phone. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + phone_number: + type: string + description: User-provided phone number diff --git a/docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSubmitted.yml b/docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSubmitted.yml new file mode 100644 index 00000000000..9f85e3758dc --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/MfaLoginPhoneOtpSubmitted.yml @@ -0,0 +1,13 @@ +description: | + During a login attempt, the user, having previously been sent an OTP code via SMS, has entered an OTP code. + Reauthentication occurs when the user was already signed in but their session has timed out and they need to reauthenticate to continue. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + reauthentication: + type: boolean + success: + type: boolean + description: | + Indicates whether the the entered code matched what was sent and has not expired. diff --git a/docs/attempts-api/schemas/events/sign-in/MfaLoginPivCacSubmitted.yml b/docs/attempts-api/schemas/events/sign-in/MfaLoginPivCacSubmitted.yml new file mode 100644 index 00000000000..e2da595d63b --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/MfaLoginPivCacSubmitted.yml @@ -0,0 +1,53 @@ +description: | + During a login attempt, the user uses a PIV/CAC card. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + subject_dn: + type: string + description: | + The subject's Distinguished Name (DN) + success: + type: boolean + description: | + Indicates whether the PIV/CAC card was authenticated successfully for MFA. + failure_reason: + type: object + description: | + An OPTIONAL object. An associative array of attributes and errors if success is false + properties: + piv_cac: + type: array + items: + type: string + enum: + - already_associated + user: + type: array + items: + type: string + enum: + - not_found + - piv_cac_mismatch + certificate: + type: array + items: + type: string + enum: + - bad + - expired + - invalid + - none + - not_auth_cert + - revoked + - unverified + token: + type: array + items: + type: string + enum: + - bad + - http_failure + - invalid + - missing diff --git a/docs/attempts-api/schemas/events/sign-in/MfaLoginTotpSubmitted.yml b/docs/attempts-api/schemas/events/sign-in/MfaLoginTotpSubmitted.yml new file mode 100644 index 00000000000..ecd24e2240f --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/MfaLoginTotpSubmitted.yml @@ -0,0 +1,10 @@ +description: During a login attempt, the user enters a TOTP code. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the code entered matched what was sent + diff --git a/docs/attempts-api/schemas/events/sign-in/MfaLoginWebauthnPlatformSubmitted.yml b/docs/attempts-api/schemas/events/sign-in/MfaLoginWebauthnPlatformSubmitted.yml new file mode 100644 index 00000000000..bd117948dfe --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/MfaLoginWebauthnPlatformSubmitted.yml @@ -0,0 +1,11 @@ +description: During a login attempt, the user uses face or touch unlock for MFA. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the platform authenticated successfully for MFA + + diff --git a/docs/attempts-api/schemas/events/sign-in/MfaLoginWebauthnRoamingSubmitted.yml b/docs/attempts-api/schemas/events/sign-in/MfaLoginWebauthnRoamingSubmitted.yml new file mode 100644 index 00000000000..0206e0cda13 --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/MfaLoginWebauthnRoamingSubmitted.yml @@ -0,0 +1,12 @@ +description: During a login attempt, the user uses face or touch unlock for MFA. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + success: + type: boolean + description: | + Indicates whether the security key was authenticated successfully for MFA + + + diff --git a/docs/attempts-api/schemas/events/sign-in/MfaSubmissionRateLimited.yml b/docs/attempts-api/schemas/events/sign-in/MfaSubmissionRateLimited.yml new file mode 100644 index 00000000000..288ca3b9d78 --- /dev/null +++ b/docs/attempts-api/schemas/events/sign-in/MfaSubmissionRateLimited.yml @@ -0,0 +1,14 @@ +description: | + The user has exceeded the limit for submitting an MFA code when signing in with a registered MFA device. +allOf: + - $ref: '../shared/EventProperties.yml' + - type: object + properties: + mfa_device_type: + type: string + enum: + - backup_code + - otp + - piv_cac + - totp + - web_authn \ No newline at end of file diff --git a/package.json b/package.json index ebb73b0c461..b84fb6a204c 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "typecheck": "tsc", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "lint:css": "stylelint 'app/assets/stylesheets/**/*.scss' 'app/javascript/**/*.scss' 'app/components/*.scss'", + "lint:openapi": "./scripts/lint-openapi-spec.mjs", "test": "mocha 'spec/javascript/**/**spec.+(j|t)s?(x)' 'app/javascript/packages/**/*.spec.+(j|t)s?(x)'", "normalize-yaml": "normalize-yaml", "generate-browsers-json": "./scripts/generate-browsers-json.js", @@ -47,6 +48,7 @@ }, "devDependencies": { "@babel/cli": "^7.22.15", + "@redocly/openapi-core": "^1.28.5", "@testing-library/dom": "^8.18.1", "@testing-library/react": "^11.2.2", "@testing-library/react-hooks": "^3.7.0", @@ -79,6 +81,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-testing-library": "^6.2.0", "jsdom": "^25.0.1", + "json-schema-to-ts": "^3.1.1", "mocha": "^10.0.0", "mq-polyfill": "^1.1.8", "msw": "^2.6.5", diff --git a/scripts/lint-openapi-spec.mjs b/scripts/lint-openapi-spec.mjs new file mode 100755 index 00000000000..596dde418c7 --- /dev/null +++ b/scripts/lint-openapi-spec.mjs @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +import { lint, loadConfig } from '@redocly/openapi-core'; +import assert from 'node:assert'; + +const pathToApi = './docs/attempts-api/openapi.yml'; +const config = await loadConfig({ configPath: '' }); +const lintResults = await lint({ ref: pathToApi, config }); + +assert( + !lintResults.length, + `The OpenAPI spec is not valid. + + Found ${JSON.stringify(lintResults, null, 2)} + `) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cef33b89d49..36917ab39df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -986,10 +986,10 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.9", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4": - version "7.23.8" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650" - integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw== +"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.7.tgz#f4e7fe527cd710f8dc0618610b61b4b060c3c341" + integrity sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ== dependencies: regenerator-runtime "^0.14.0" @@ -1299,6 +1299,36 @@ picocolors "^1.0.0" tslib "^2.6.0" +"@redocly/ajv@^8.11.2": + version "8.11.2" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.11.2.tgz#46e1bf321ec0ac1e0fd31dea41a3d1fcbdcda0b5" + integrity sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js-replace "^1.0.1" + +"@redocly/config@^0.20.1": + version "0.20.3" + resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.20.3.tgz#a8250528c3c7d61430119ef79efcbdb536bd56ff" + integrity sha512-Nyyv1Bj7GgYwj/l46O0nkH1GTKWbO3Ixe7KFcn021aZipkZd+z8Vlu1BwkhqtVgivcKaClaExtWU/lDHkjBzag== + +"@redocly/openapi-core@^1.28.5": + version "1.28.5" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.28.5.tgz#5415f5bb12cc6916743169758d5cba7413df14ad" + integrity sha512-eAuL+x1oBbodJksPm4UpFU57A6z1n1rx9JNpD87CObwtbRf5EzW29Ofd0t057bPGcHc8cYZtZzJ69dcRQ9xGdg== + dependencies: + "@redocly/ajv" "^8.11.2" + "@redocly/config" "^0.20.1" + colorette "^1.2.0" + https-proxy-agent "^7.0.5" + js-levenshtein "^1.1.6" + js-yaml "^4.1.0" + minimatch "^5.0.1" + pluralize "^8.0.0" + yaml-ast-parser "0.0.43" + "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.3": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -2396,6 +2426,11 @@ colord@^2.9.3: resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== +colorette@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + colorette@^2.0.14: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" @@ -3940,6 +3975,11 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" +js-levenshtein@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -3999,6 +4039,14 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-to-ts@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz#81f3acaf5a34736492f6f5f51870ef9ece1ca853" + integrity sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g== + dependencies: + "@babel/runtime" "^7.18.3" + ts-algebra "^2.0.0" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -4784,6 +4832,11 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + postcss-media-query-parser@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" @@ -5855,6 +5908,11 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== +ts-algebra@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ts-algebra/-/ts-algebra-2.0.0.tgz#4e3e0953878f26518fce7f6bb115064a65388b7a" + integrity sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw== + ts-api-utils@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" @@ -5984,6 +6042,11 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.0" +uri-js-replace@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uri-js-replace/-/uri-js-replace-1.0.1.tgz#c285bb352b701c9dfdaeffc4da5be77f936c9048" + integrity sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g== + uri-js@^4.2.2, uri-js@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -6257,6 +6320,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yaml-ast-parser@0.0.43: + version "0.0.43" + resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" + integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== + yaml@^2.3.4: version "2.3.4" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" From d57b82f50ee8ef0f4363fe5dadcae43fc1766646 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:04:03 -0500 Subject: [PATCH 05/14] Include MJS and CJS files in ESLint lint (#11881) changelog: Internal, Linting, Include MJS and CJS files in ESLint lint --- package.json | 2 +- scripts/lint-openapi-spec.mjs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b84fb6a204c..31a5e819393 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "postinstall": "yarn workspace @18f/identity-analytics run postinstall", "typecheck": "tsc", - "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx,.cjs,.mjs", "lint:css": "stylelint 'app/assets/stylesheets/**/*.scss' 'app/javascript/**/*.scss' 'app/components/*.scss'", "lint:openapi": "./scripts/lint-openapi-spec.mjs", "test": "mocha 'spec/javascript/**/**spec.+(j|t)s?(x)' 'app/javascript/packages/**/*.spec.+(j|t)s?(x)'", diff --git a/scripts/lint-openapi-spec.mjs b/scripts/lint-openapi-spec.mjs index 596dde418c7..2c7d84de844 100755 --- a/scripts/lint-openapi-spec.mjs +++ b/scripts/lint-openapi-spec.mjs @@ -12,4 +12,5 @@ assert( `The OpenAPI spec is not valid. Found ${JSON.stringify(lintResults, null, 2)} - `) \ No newline at end of file + `, +); From 4ab0dcb1f2da0b5c2b2e43e6abad7c102f89720b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:06:23 -0500 Subject: [PATCH 06/14] LG-15484: Update design of Sign In/Create Account tabs (#11872) * Update design of Sign In/Create Account tabs changelog: User-Facing Improvements, Sign In, Improve design clarity of "Sign In" / "Create an Account" tabs * Change sign-in form button text from "Sign in" to "Submit" * Render registration page button as full-width * Add documentation for lg-click-observed new attributes * Update hard-coded references to replaced sign-in label --- .../tab_navigation_component.html.erb | 33 +++++------------ app/components/tab_navigation_component.rb | 35 ++++++++++++++++--- app/components/tab_navigation_component.scss | 15 ++++++-- app/javascript/packages/analytics/README.md | 5 +++ app/views/devise/sessions/new.html.erb | 4 +-- app/views/sign_up/registrations/new.html.erb | 9 +++-- .../tab_navigation_component_spec.rb | 13 ++++++- spec/features/saml/saml_spec.rb | 12 +++---- .../visitors/password_recovery_spec.rb | 2 +- spec/support/features/session_helper.rb | 2 +- .../devise/sessions/new.html.erb_spec.rb | 6 ++-- 11 files changed, 89 insertions(+), 47 deletions(-) diff --git a/app/components/tab_navigation_component.html.erb b/app/components/tab_navigation_component.html.erb index 283ecf17e17..10536eeaaca 100644 --- a/app/components/tab_navigation_component.html.erb +++ b/app/components/tab_navigation_component.html.erb @@ -1,29 +1,14 @@ <%= content_tag(:nav, aria: { label: }, **tag_options, class: [*tag_options[:class], 'tab-navigation']) do %> -
    +
      <% routes.each do |route| %> - <% if current_path?(route[:path]) %> - <%= render ClickObserverComponent.new( - event_name: 'tab_navigation_current_page_clicked', - payload: { path: route[:path] }, - role: 'listitem', - class: 'usa-button-group__item display-list-item', - ) do %> - <%= render ButtonComponent.new( - url: route[:path], - big: true, - outline: !current_path?(route[:path]), - aria: { current: current_path?(route[:path]) ? 'page' : nil }, - ).with_content(route[:text]) %> - <% end %> - <% else %> -
    • - <%= render ButtonComponent.new( - url: route[:path], - big: true, - outline: !current_path?(route[:path]), - aria: { current: current_path?(route[:path]) ? 'page' : nil }, - ).with_content(route[:text]) %> -
    • + <%= nav_list_item(route) do %> + <%= render ButtonComponent.new( + url: route[:path], + big: true, + outline: current_path?(route[:path]), + unstyled: !current_path?(route[:path]), + aria: { current: current_path?(route[:path]) ? 'page' : nil }, + ).with_content(route[:text]) %> <% end %> <% end %>
    diff --git a/app/components/tab_navigation_component.rb b/app/components/tab_navigation_component.rb index 5fdf9678067..960d1ca5392 100644 --- a/app/components/tab_navigation_component.rb +++ b/app/components/tab_navigation_component.rb @@ -10,10 +10,35 @@ def initialize(label:, routes:, **tag_options) end def current_path?(path) - recognized_path = Rails.application.routes.recognize_path(path, method: request.method) - request.params[:controller] == recognized_path[:controller] && - request.params[:action] == recognized_path[:action] - rescue ActionController::RoutingError - false + @current_path ||= {} + if !@current_path.key?(path) + @current_path[path] = begin + recognized_path = Rails.application.routes.recognize_path(path, method: request.method) + request.params[:controller] == recognized_path[:controller] && + request.params[:action] == recognized_path[:action] + rescue ActionController::RoutingError + false + end + end + + @current_path[path] + end + + private + + def nav_list_item(route, &block) + if current_path?(route[:path]) + render( + ClickObserverComponent.new( + event_name: 'tab_navigation_current_page_clicked', + payload: { path: route[:path] }, + role: 'listitem', + class: 'usa-button-group__item display-list-item', + ), + &block + ) + else + tag.li(class: 'usa-button-group__item', &block) + end end end diff --git a/app/components/tab_navigation_component.scss b/app/components/tab_navigation_component.scss index 013eb6b4dda..2570ce80ccf 100644 --- a/app/components/tab_navigation_component.scss +++ b/app/components/tab_navigation_component.scss @@ -2,16 +2,27 @@ @forward 'usa-button-group/src/styles'; -.tab-navigation .usa-button-group--segmented { +.tab-navigation .usa-button-group { + @include u-bg('base-lightest'); + border-radius: 1.625rem; + flex-flow: nowrap; + .usa-button-group__item { flex-basis: 50%; } - .usa-button-group__item:last-child > .usa-button, .usa-button { + @include u-flex('align-center', 'justify-center'); + @include u-padding(1.5); + border-radius: 1.375rem; width: 100%; } + .usa-button--unstyled { + @include u-text('bold'); + text-decoration: none; + } + .usa-button--big { @include at-media-max('tablet') { font-size: units(2); diff --git a/app/javascript/packages/analytics/README.md b/app/javascript/packages/analytics/README.md index e33fb2b669a..1a11f6d17d0 100644 --- a/app/javascript/packages/analytics/README.md +++ b/app/javascript/packages/analytics/README.md @@ -40,3 +40,8 @@ The custom element will implement the analytics logging behavior, but all markup ``` + +The element supports the following attributes to customize its behavior: + +- `event-name`: The name of the analytics event that should be logged when clicked +- `payload`: (Optional) JSON payload of additional data that should be included in the logged event diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 251090747f6..be5afdedfed 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -48,9 +48,9 @@ form: f, action: SignInRecaptchaForm::RECAPTCHA_ACTION, button_options: { full_width: true }, - ).with_content(t('links.sign_in')) %> + ).with_content(t('forms.buttons.submit.default')) %> <% else %> - <%= f.submit t('links.sign_in'), full_width: true, wide: false %> + <%= f.submit t('forms.buttons.submit.default'), full_width: true, wide: false %> <% end %> <% end %> <% if desktop_device? %> diff --git a/app/views/sign_up/registrations/new.html.erb b/app/views/sign_up/registrations/new.html.erb index e9e5dbee2c5..d654bfba7fc 100644 --- a/app/views/sign_up/registrations/new.html.erb +++ b/app/views/sign_up/registrations/new.html.erb @@ -42,7 +42,12 @@ required: true, ) %> - <%= f.submit t('forms.buttons.submit.default'), class: 'display-block margin-y-5' %> + <%= f.submit( + t('forms.buttons.submit.default'), + full_width: true, + wide: false, + class: 'display-block margin-y-5', + ) %> <% end %> <%= render 'shared/cancel', link: decorated_sp_session.cancel_link_url %> @@ -67,4 +72,4 @@ step: :enter_email, ), ) %> -

    \ No newline at end of file +

    diff --git a/spec/components/tab_navigation_component_spec.rb b/spec/components/tab_navigation_component_spec.rb index 8bf8686a95c..9bdaeb50a64 100644 --- a/spec/components/tab_navigation_component_spec.rb +++ b/spec/components/tab_navigation_component_spec.rb @@ -11,6 +11,7 @@ it 'renders labelled navigation' do expect(rendered).to have_css('.tab-navigation[aria-label="Navigation"]') + expect(rendered).to have_css('li', count: 2) expect(rendered).to have_link('First') { |link| !is_current_link?(link) } expect(rendered).to have_link('Second') { |link| !is_current_link?(link) } end @@ -41,10 +42,20 @@ end it 'renders current link as highlighted' do + expect(rendered).to have_css('li,[role=listitem]', count: 2) expect(rendered).to have_link('First') { |link| is_current_link?(link) } expect(rendered).to have_link('Second') { |link| !is_current_link?(link) } end + it 'wraps link in click observer' do + expect(rendered).to have_link('First') do |link| + expect(link).to have_ancestor('lg-click-observer') + end + expect(rendered).to have_link('Second') do |link| + expect(link).not_to have_ancestor('lg-click-observer') + end + end + context 'with routes defining full URL' do let(:routes) do [ @@ -112,6 +123,6 @@ end def is_current_link?(link) - link.matches_css?('[aria-current="page"]:not(.usa-button--outline)') + link.matches_css?('[aria-current="page"].usa-button--outline') end end diff --git a/spec/features/saml/saml_spec.rb b/spec/features/saml/saml_spec.rb index 2e3262ff730..049f6a59ad5 100644 --- a/spec/features/saml/saml_spec.rb +++ b/spec/features/saml/saml_spec.rb @@ -284,7 +284,7 @@ expect(page).to have_content( t('headings.create_account_with_sp.sp_text', app_name: APP_NAME), ) - expect(page).to have_button('Sign in') + expect(page).to have_button(t('forms.buttons.submit.default')) # visit from SP with force_authn: true expect(page).to have_content( strip_tags( @@ -334,7 +334,7 @@ ), ), ) - expect(page).to have_button('Sign in') + expect(page).to have_button(t('forms.buttons.submit.default')) # Log in with Test SP as the SP session fill_in_credentials_and_submit(user.email, user.password) fill_in_code_with_last_phone_otp @@ -363,7 +363,7 @@ ), ), ) - expect(page).to have_button('Sign in') + expect(page).to have_button(t('forms.buttons.submit.default')) # log in for second time fill_in_credentials_and_submit(user.email, user.password) @@ -404,7 +404,7 @@ expect(page).to have_content( t('headings.create_account_with_sp.sp_text', app_name: APP_NAME), ) - expect(page).to have_button('Sign in') + expect(page).to have_button(t('forms.buttons.submit.default')) expect(page).to have_content( strip_tags( t( @@ -439,7 +439,7 @@ expect(page).to have_content( t('headings.create_account_with_sp.sp_text', app_name: APP_NAME), ) - expect(page).to have_button('Sign in') + expect(page).to have_button(t('forms.buttons.submit.default')) expect(page).to have_content( strip_tags( t( @@ -454,7 +454,7 @@ expect(page).to have_content( t('headings.create_account_with_sp.sp_text', app_name: APP_NAME), ) - expect(page).to have_button('Sign in') + expect(page).to have_button(t('forms.buttons.submit.default')) expect(page).to_not have_content( strip_tags( t( diff --git a/spec/features/visitors/password_recovery_spec.rb b/spec/features/visitors/password_recovery_spec.rb index 3f1bed3114d..bd55de37eef 100644 --- a/spec/features/visitors/password_recovery_spec.rb +++ b/spec/features/visitors/password_recovery_spec.rb @@ -192,7 +192,7 @@ fill_in t('account.index.email'), with: @user.email fill_in t('components.password_toggle.label'), with: 'NewVal!dPassw0rd' - click_button t('links.sign_in') + click_button t('forms.buttons.submit.default') fill_in_code_with_last_phone_otp click_submit_default click_agree_and_continue diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb index 60b5ecdacfe..feaa8174111 100644 --- a/spec/support/features/session_helper.rb +++ b/spec/support/features/session_helper.rb @@ -98,7 +98,7 @@ def fill_in_bad_piv_cac_credentials_and_submit def fill_in_credentials_and_submit(email, password) fill_in t('account.index.email'), with: email fill_in t('account.index.password'), with: password - click_button t('links.sign_in') + click_button t('forms.buttons.submit.default') end def fill_in_totp_name(nickname = 'App') diff --git a/spec/views/devise/sessions/new.html.erb_spec.rb b/spec/views/devise/sessions/new.html.erb_spec.rb index 5cd8955c27d..5f76b8bb72b 100644 --- a/spec/views/devise/sessions/new.html.erb_spec.rb +++ b/spec/views/devise/sessions/new.html.erb_spec.rb @@ -218,7 +218,7 @@ let(:sign_in_recaptcha_enabled) { false } it 'renders default sign-in submit button' do - expect(rendered).to have_button(t('links.sign_in')) + expect(rendered).to have_button(t('forms.buttons.submit.default')) expect(rendered).not_to have_css('lg-captcha-submit-button') end @@ -243,7 +243,7 @@ let(:recaptcha_mock_validator) { true } it 'renders captcha sign-in submit button' do - expect(rendered).to have_button(t('links.sign_in')) + expect(rendered).to have_button(t('forms.buttons.submit.default')) expect(rendered).to have_css('lg-captcha-submit-button') end end @@ -253,7 +253,7 @@ let(:sign_in_recaptcha_enabled) { true } it 'renders captcha sign-in submit button' do - expect(rendered).to have_button(t('links.sign_in')) + expect(rendered).to have_button(t('forms.buttons.submit.default')) expect(rendered).to have_css('lg-captcha-submit-button') end From e8be426c19dcbd3af7e05e572e397eedff1aa8e9 Mon Sep 17 00:00:00 2001 From: Malick Diarra Date: Fri, 14 Feb 2025 11:00:39 -0500 Subject: [PATCH 07/14] LG-15661: Update delete your account styling (#11879) * changelog: User-Facing Improvements, Account reset, update account reset styling * add to status page componenet for deleting * get rid of unneeded spacing --- app/components/status_page_component.rb | 3 ++- .../account_reset/confirm_delete_account/show.html.erb | 10 +++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/components/status_page_component.rb b/app/components/status_page_component.rb index 21c83e81f27..1054d3cc8d6 100644 --- a/app/components/status_page_component.rb +++ b/app/components/status_page_component.rb @@ -5,6 +5,7 @@ class StatusPageComponent < BaseComponent info: [:question], warning: [nil], error: [nil, :lock], + delete: [nil], }.freeze renders_one :header, ::PageHeadingComponent @@ -16,7 +17,7 @@ class StatusPageComponent < BaseComponent attr_reader :status, :icon - validates_inclusion_of :status, in: %i[info error warning] + validates_inclusion_of :status, in: %i[info error warning delete] validate :validate_status_icon def initialize(status: :error, icon: nil) diff --git a/app/views/account_reset/confirm_delete_account/show.html.erb b/app/views/account_reset/confirm_delete_account/show.html.erb index ae22b9f80bb..e8b57151e6b 100644 --- a/app/views/account_reset/confirm_delete_account/show.html.erb +++ b/app/views/account_reset/confirm_delete_account/show.html.erb @@ -1,10 +1,6 @@ <% self.title = t('account_reset.confirm_delete_account.title') %> -
    - <%= render AlertIconComponent.new( - icon_name: :delete, - class: 'alert-icon--centered-top', - ) %> - <%= render PageHeadingComponent.new.with_content(t('account_reset.confirm_delete_account.title')) %> +<%= render StatusPageComponent.new(status: :delete) do |c| %> + <% c.with_header { t('account_reset.confirm_delete_account.title') } %>

    <%= t('account_reset.confirm_delete_account.info_html', email: email) %>

    <%= t( @@ -15,4 +11,4 @@ ), ) %>

    -
    +<% end %> \ No newline at end of file From 5233c067c77013bcd6995742aa05eace7a018d08 Mon Sep 17 00:00:00 2001 From: Shane Chesnutt Date: Fri, 14 Feb 2025 11:25:25 -0500 Subject: [PATCH 08/14] Add higher dpi images for warning and info email icons (#11566) * Add higher dpi images for warning and info email icons changelog: User-facing Improvements, In-person Proofing, Add higher dpi images for warning and info alert email icons * run info and warning pngs through Squoosh * use high resolution info icon for inlined images and use existing info icon for tables reports mailer --------- Co-authored-by: Eileen McFarland <80347702+eileen-nava@users.noreply.github.com> --- app/assets/images/email/info@4x.png | Bin 0 -> 549 bytes app/assets/images/email/warning.png | Bin 351 -> 410 bytes .../shared/_in_person_ready_to_verify.html.erb | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 app/assets/images/email/info@4x.png diff --git a/app/assets/images/email/info@4x.png b/app/assets/images/email/info@4x.png new file mode 100644 index 0000000000000000000000000000000000000000..25c7411199ad6cea479dd9a051d98aff1cb4b912 GIT binary patch literal 549 zcmV+=0^0qFP)ws~#wh`fM8ZOg`%Fm-1y-~6xl^Px`$-<7X@JON&E!?BnPto z1p(3=V9)@-DF1q_0>I!dlMnOC3HG9yHKdaT&{4RY0M?VUrrIJasxjbI0XhSNCIGz} z80Sreh)2=v5BX>)AS=`XIS0t90Q?W24xqmYVN?Ye4Gby(Qi!Tf69BxyMiXH3Xlkno zaK0l^Py-0($yKKY(Chz{UIs)JfJvILUIT!hjC4aG76nMVhv3yNr;>xB08`Q>fgN%& z$@(Wzl+mQxq^dq7{VK}}h@U7-pJ#>4x43k(r5;m@oTyefR_8^reYfaAv(x+iD9Q n1{g7X+fMLay-ys!3-pKgwJ{cq-+9wLR^7#KtOn1VAYW15R$9vsVtNU$`X5FPK3^D?isY48XC8SpH*2QIm)dg@4|a$ga$6>#_bV>p`LF9c{M0+w^D@b;iRxOoV!g*N zowTn;dsnDyCI`k&WWDdglB#bN=qvOw*5%I>C*g-_axcZCZy(YBIOD+UYDJCjkp`|W z^o`xOzKT=1?{TYZ&czGGUp!Ck*-@$)vN2vTT4Lj@i>WO#Qj*MJMy~snI8`F`oFA#M kx*LA-uxxt6>TYVj=}=V20%hs%z))cDboFyt=akR{01FDD&j0`b literal 351 zcmV-l0igbgP)Px$8A(JzR5(wSk8 zTMCj?P*v5|Id=nqtsfeVF}tQ|#w8>PAfhV(9OGt0yfE|WDZp=^Zb!sHL^i*t=mR2h zSQ`4EfQY^T;3W(cMFIXC{nT4)4|mGq7mW=7RQ^=DaRE?OH8VG%=S~3;sq|nRWLb7{ z&gsxcv}iEK?1P~*pe)O?h$N$WFfNL90>s8GoUgr!^I0L*JkR%S+YS>T6`ODTM8r`< x&IuI_GuKE&>bN?;y3}L^+z@d#ZVg35`X7o+d5MSEA!Gmm002ovPDHLkV1ixMjH>_u diff --git a/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb b/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb index 9fcddaa4dbd..679959c85da 100644 --- a/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb +++ b/app/views/user_mailer/shared/_in_person_ready_to_verify.html.erb @@ -38,7 +38,7 @@
    - <%= image_tag('email/info.png', width: 16, height: 16, alt: '', style: 'margin-top: 4px;') %> + <%= image_tag('email/info@4x.png', width: 16, height: 16, alt: '', style: 'margin-top: 4px;') %>

    <%= t('in_person_proofing.body.barcode.deadline', deadline: @presenter.formatted_due_date) %>

    From 5bc42233b9a1bddf267f5a0748053645a1e37f6e Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Fri, 14 Feb 2025 11:46:09 -0500 Subject: [PATCH 09/14] Update Rack gem to latest 3.0.x (#11882) * Update Rack gem to latest 3.1.x changelog: Internal, Dependencies, Update dependency to resolve security advisory * Downgrade to Rack 3.0.12 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index d2c794d0dcc..00dde337bbb 100644 --- a/Gemfile +++ b/Gemfile @@ -58,7 +58,7 @@ gem 'phonelib' gem 'premailer-rails', '>= 1.12.0' gem 'profanity_filter' gem 'propshaft' -gem 'rack', '>= 3.0' +gem 'rack', '~> 3.0.12' gem 'rack-attack', github: 'rack/rack-attack', ref: 'd9fedfae4f7f6409f33857763391f4e18a6d7467' gem 'rack-cors', '> 2.0.1', require: 'rack/cors' gem 'rack-headers_filter' diff --git a/Gemfile.lock b/Gemfile.lock index 02778feb0f1..c5814ba5ef7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -512,7 +512,7 @@ GEM nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) - rack (3.0.11) + rack (3.0.12) rack-cors (2.0.2) rack (>= 2.0.0) rack-headers_filter (0.0.1) @@ -831,7 +831,7 @@ DEPENDENCIES pry-rails psych puma (~> 6.0) - rack (>= 3.0) + rack (~> 3.0.12) rack-attack! rack-cors (> 2.0.1) rack-headers_filter From 4bc70b1272ee4aaf9f35859415770cac4875023c Mon Sep 17 00:00:00 2001 From: A Shukla Date: Fri, 14 Feb 2025 15:12:24 -0600 Subject: [PATCH 10/14] LG-15434 add env variable for passports to application configuration (#11884) * changelog: Upcoming Features, passport, adding env variables for passport doc auth development * Removing unused PassportConcern --- config/application.yml.default | 6 ++++++ lib/identity_config.rb | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/config/application.yml.default b/config/application.yml.default index bc7af40f6cf..e0ac736500c 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -106,6 +106,7 @@ doc_auth_error_sharpness_threshold: 40 doc_auth_max_attempts: 5 doc_auth_max_capture_attempts_before_native_camera: 3 doc_auth_max_submission_attempts_before_native_camera: 3 +doc_auth_passports_enabled: false doc_auth_redirect_to_correct_vendor_disabled: false doc_auth_selfie_desktop_test_mode: false doc_auth_socure_max_allowed_users: 10_000 @@ -120,6 +121,11 @@ doc_auth_vendor_switching_enabled: false doc_capture_polling_enabled: true doc_capture_request_valid_for_minutes: 15 domain_name: login.gov +dos_passport_client_id: '' +dos_passport_client_secret: '' +dos_passport_composite_healthcheck_endpoint: '' +dos_passport_healthcheck_endpoint: '' +dos_passport_mrz_endpoint: '' drop_off_report_config: '[{"emails":["ursula@example.com"],"issuers": ["urn:gov:gsa:openidconnect.profiles:sp:sso:agency_name:app_name"]}]' email_from: no-reply@login.gov email_from_display_name: Login.gov diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 0218ab2e76b..4ee29e8d6c2 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -125,6 +125,7 @@ def self.store config.add(:doc_auth_max_attempts, type: :integer) config.add(:doc_auth_max_capture_attempts_before_native_camera, type: :integer) config.add(:doc_auth_max_submission_attempts_before_native_camera, type: :integer) + config.add(:doc_auth_passports_enabled, type: :boolean) config.add(:doc_auth_selfie_desktop_test_mode, type: :boolean) config.add(:doc_auth_socure_max_allowed_users, type: :integer) config.add(:doc_auth_socure_wait_polling_refresh_max_seconds, type: :integer) @@ -139,6 +140,11 @@ def self.store config.add(:doc_capture_request_valid_for_minutes, type: :integer) config.add(:drop_off_report_config, type: :json) config.add(:domain_name, type: :string) + config.add(:dos_passport_client_id, type: :string) + config.add(:dos_passport_client_secret, type: :string) + config.add(:dos_passport_composite_healthcheck_endpoint, type: :string) + config.add(:dos_passport_healthcheck_endpoint, type: :string) + config.add(:dos_passport_mrz_endpoint, type: :string) config.add(:email_from, type: :string) config.add(:email_from_display_name, type: :string) config.add(:email_registrations_per_ip_limit, type: :integer) From 1cf4e4d4a57f1b91dafde8958dffb6d0d151c4a4 Mon Sep 17 00:00:00 2001 From: Amir Reavis-Bey Date: Fri, 14 Feb 2025 17:45:25 -0500 Subject: [PATCH 11/14] Refactor step info preconditions for Socure document capture (#11704) * remove skip hybrid handoff preconditions * add no sdlfi req'd * [skip changelog] --- .../idv/socure/document_capture_controller.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/controllers/idv/socure/document_capture_controller.rb b/app/controllers/idv/socure/document_capture_controller.rb index 65b28a5dd0d..030a1556ae2 100644 --- a/app/controllers/idv/socure/document_capture_controller.rb +++ b/app/controllers/idv/socure/document_capture_controller.rb @@ -91,13 +91,11 @@ def self.step_info controller: self, next_steps: [:ssn, :ipp_ssn], preconditions: ->(idv_session:, user:) { - idv_session.flow_path == 'standard' && ( - # mobile - idv_session.skip_doc_auth_from_handoff || - idv_session.skip_hybrid_handoff || - idv_session.skip_doc_auth_from_how_to_verify || - !idv_session.selfie_check_required || - idv_session.desktop_selfie_test_mode_enabled?) + idv_session.flow_path == 'standard' && + !idv_session.selfie_check_required && ( + # mobile + idv_session.skip_hybrid_handoff || + idv_session.desktop_selfie_test_mode_enabled?) }, undo_step: ->(idv_session:, user:) do idv_session.pii_from_doc = nil From 064352bd8db3b3901edc71598b4a898f173e3af0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 08:17:14 -0500 Subject: [PATCH 12/14] Bump libphonenumber-js from 1.11.19 to 1.11.20 (#11886) Bumps [libphonenumber-js](https://gitlab.com/catamphetamine/libphonenumber-js) from 1.11.19 to 1.11.20. - [Changelog](https://gitlab.com/catamphetamine/libphonenumber-js/blob/master/CHANGELOG.md) - [Commits](https://gitlab.com/catamphetamine/libphonenumber-js/commits/master) --- updated-dependencies: - dependency-name: libphonenumber-js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/javascript/packages/phone-input/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/javascript/packages/phone-input/package.json b/app/javascript/packages/phone-input/package.json index 0d1cb043cfe..733b960d889 100644 --- a/app/javascript/packages/phone-input/package.json +++ b/app/javascript/packages/phone-input/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "dependencies": { "intl-tel-input": "^24.5.0", - "libphonenumber-js": "^1.11.19" + "libphonenumber-js": "^1.11.20" }, "sideEffects": [ "./index.ts" diff --git a/yarn.lock b/yarn.lock index 36917ab39df..8c66a2fa111 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4129,10 +4129,10 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -libphonenumber-js@^1.11.19: - version "1.11.19" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.19.tgz#de6437b15d23d9e3b16e5b14c6f2d6d65f58c9b7" - integrity sha512-bW/Yp/9dod6fmyR+XqSUL1N5JE7QRxQ3KrBIbYS1FTv32e5i3SEtQVX+71CYNv8maWNSOgnlCoNp9X78f/cKiA== +libphonenumber-js@^1.11.20: + version "1.11.20" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.20.tgz#ef5c663e80f55d9ea01a6e2952bf240e8a6fff52" + integrity sha512-/ipwAMvtSZRdiQBHqW1qxqeYiBMzncOQLVA+62MWYr7N4m7Q2jqpJ0WgT7zlOEOpyLRSqrMXidbJpC0J77AaKA== lightningcss-darwin-arm64@1.23.0: version "1.23.0" From e2605fbc0e2648f0c970619f9dd5380727523301 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Tue, 18 Feb 2025 10:22:54 -0600 Subject: [PATCH 13/14] Remove some NewRelic method tracing (#11887) changelog: Internal, Performance, Remove some NewRelic method tracing --- app/models/user.rb | 3 --- app/presenters/confirmation_email_presenter.rb | 6 ------ app/services/send_sign_up_email_confirmation.rb | 4 ---- config/initializers/new_relic_tracers.rb | 5 ----- lib/ab_test.rb | 5 ----- lib/mailer_sensitive_information_checker.rb | 5 ----- 6 files changed, 28 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 242168e2666..e5878e72b1c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,7 +3,6 @@ class User < ApplicationRecord include NonNullUuid - include ::NewRelic::Agent::MethodTracer include ActionView::Helpers::DateHelper devise( @@ -508,8 +507,6 @@ def send_confirmation_instructions # no-op end - add_method_tracer :send_devise_notification, "Custom/#{name}/send_devise_notification" - def analytics @analytics ||= Analytics.new(user: self, request: nil, session: {}, sp: nil) end diff --git a/app/presenters/confirmation_email_presenter.rb b/app/presenters/confirmation_email_presenter.rb index 4fd7e55b8f2..7b6dbfdfdde 100644 --- a/app/presenters/confirmation_email_presenter.rb +++ b/app/presenters/confirmation_email_presenter.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class ConfirmationEmailPresenter - include ::NewRelic::Agent::MethodTracer - def initialize(user, view) @user = user @view = view @@ -40,8 +38,4 @@ def confirmation_period private attr_reader :user, :view - - add_method_tracer :initialize, "Custom/#{name}/initialize" - add_method_tracer :first_sentence, "Custom/#{name}/first_sentence" - add_method_tracer :confirmation_period, "Custom/#{name}/confirmation_period" end diff --git a/app/services/send_sign_up_email_confirmation.rb b/app/services/send_sign_up_email_confirmation.rb index 62af8f19ecf..62d4a90ea78 100644 --- a/app/services/send_sign_up_email_confirmation.rb +++ b/app/services/send_sign_up_email_confirmation.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class SendSignUpEmailConfirmation - include ::NewRelic::Agent::MethodTracer - attr_reader :user def initialize(user) @@ -65,6 +63,4 @@ def send_suspended_user_email def handle_multiple_email_address_error raise 'sign up user has multiple email address records' end - - add_method_tracer :call, "Custom/#{name}/call" end diff --git a/config/initializers/new_relic_tracers.rb b/config/initializers/new_relic_tracers.rb index 7c66c0d6fa1..bd5a70fefbc 100644 --- a/config/initializers/new_relic_tracers.rb +++ b/config/initializers/new_relic_tracers.rb @@ -19,8 +19,3 @@ add_method_tracer :deliver, "Custom/#{name}/deliver" add_method_tracer :deliver!, "Custom/#{name}/deliver!" end - -SamlIdp::SignedInfoBuilder.class_eval do - include ::NewRelic::Agent::MethodTracer - add_method_tracer :encoded, "Custom/#{name}/encoded" -end diff --git a/lib/ab_test.rb b/lib/ab_test.rb index dbe70fb8d13..2b367d83d53 100644 --- a/lib/ab_test.rb +++ b/lib/ab_test.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class AbTest - include ::NewRelic::Agent::MethodTracer - attr_reader :buckets, :experiment_name, :default_bucket, @@ -173,7 +171,4 @@ def ensure_numeric_percentages def within_100_percent? valid_bucket_data_structure? && buckets.values.sum <= 100 end - - add_method_tracer :bucket, "Custom/#{name}/bucket" - add_method_tracer :percent, "Custom/#{name}/percent" end diff --git a/lib/mailer_sensitive_information_checker.rb b/lib/mailer_sensitive_information_checker.rb index ac22a3cdcc2..9bb63b9b269 100644 --- a/lib/mailer_sensitive_information_checker.rb +++ b/lib/mailer_sensitive_information_checker.rb @@ -48,9 +48,4 @@ def self.alert(exception) raise exception end end - - class << self - include ::NewRelic::Agent::MethodTracer - add_method_tracer :check_for_sensitive_pii!, "Custom/#{name}/check_for_sensitive_pii!" - end end From 5846d9a50572ca32cf00cfece94a68b9e9b5ce8c Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Tue, 18 Feb 2025 16:03:58 -0500 Subject: [PATCH 14/14] Revert "LG-15484: Update design of Sign In/Create Account tabs (#11872)" This reverts commit 4ab0dcb1f2da0b5c2b2e43e6abad7c102f89720b. --- .../tab_navigation_component.html.erb | 33 ++++++++++++----- app/components/tab_navigation_component.rb | 35 +++---------------- app/components/tab_navigation_component.scss | 15 ++------ app/javascript/packages/analytics/README.md | 5 --- app/views/devise/sessions/new.html.erb | 4 +-- app/views/sign_up/registrations/new.html.erb | 9 ++--- .../tab_navigation_component_spec.rb | 13 +------ spec/features/saml/saml_spec.rb | 12 +++---- .../visitors/password_recovery_spec.rb | 2 +- spec/support/features/session_helper.rb | 2 +- .../devise/sessions/new.html.erb_spec.rb | 6 ++-- 11 files changed, 47 insertions(+), 89 deletions(-) diff --git a/app/components/tab_navigation_component.html.erb b/app/components/tab_navigation_component.html.erb index 10536eeaaca..283ecf17e17 100644 --- a/app/components/tab_navigation_component.html.erb +++ b/app/components/tab_navigation_component.html.erb @@ -1,14 +1,29 @@ <%= content_tag(:nav, aria: { label: }, **tag_options, class: [*tag_options[:class], 'tab-navigation']) do %> -
      +
        <% routes.each do |route| %> - <%= nav_list_item(route) do %> - <%= render ButtonComponent.new( - url: route[:path], - big: true, - outline: current_path?(route[:path]), - unstyled: !current_path?(route[:path]), - aria: { current: current_path?(route[:path]) ? 'page' : nil }, - ).with_content(route[:text]) %> + <% if current_path?(route[:path]) %> + <%= render ClickObserverComponent.new( + event_name: 'tab_navigation_current_page_clicked', + payload: { path: route[:path] }, + role: 'listitem', + class: 'usa-button-group__item display-list-item', + ) do %> + <%= render ButtonComponent.new( + url: route[:path], + big: true, + outline: !current_path?(route[:path]), + aria: { current: current_path?(route[:path]) ? 'page' : nil }, + ).with_content(route[:text]) %> + <% end %> + <% else %> +
      • + <%= render ButtonComponent.new( + url: route[:path], + big: true, + outline: !current_path?(route[:path]), + aria: { current: current_path?(route[:path]) ? 'page' : nil }, + ).with_content(route[:text]) %> +
      • <% end %> <% end %>
      diff --git a/app/components/tab_navigation_component.rb b/app/components/tab_navigation_component.rb index 960d1ca5392..5fdf9678067 100644 --- a/app/components/tab_navigation_component.rb +++ b/app/components/tab_navigation_component.rb @@ -10,35 +10,10 @@ def initialize(label:, routes:, **tag_options) end def current_path?(path) - @current_path ||= {} - if !@current_path.key?(path) - @current_path[path] = begin - recognized_path = Rails.application.routes.recognize_path(path, method: request.method) - request.params[:controller] == recognized_path[:controller] && - request.params[:action] == recognized_path[:action] - rescue ActionController::RoutingError - false - end - end - - @current_path[path] - end - - private - - def nav_list_item(route, &block) - if current_path?(route[:path]) - render( - ClickObserverComponent.new( - event_name: 'tab_navigation_current_page_clicked', - payload: { path: route[:path] }, - role: 'listitem', - class: 'usa-button-group__item display-list-item', - ), - &block - ) - else - tag.li(class: 'usa-button-group__item', &block) - end + recognized_path = Rails.application.routes.recognize_path(path, method: request.method) + request.params[:controller] == recognized_path[:controller] && + request.params[:action] == recognized_path[:action] + rescue ActionController::RoutingError + false end end diff --git a/app/components/tab_navigation_component.scss b/app/components/tab_navigation_component.scss index 2570ce80ccf..013eb6b4dda 100644 --- a/app/components/tab_navigation_component.scss +++ b/app/components/tab_navigation_component.scss @@ -2,27 +2,16 @@ @forward 'usa-button-group/src/styles'; -.tab-navigation .usa-button-group { - @include u-bg('base-lightest'); - border-radius: 1.625rem; - flex-flow: nowrap; - +.tab-navigation .usa-button-group--segmented { .usa-button-group__item { flex-basis: 50%; } + .usa-button-group__item:last-child > .usa-button, .usa-button { - @include u-flex('align-center', 'justify-center'); - @include u-padding(1.5); - border-radius: 1.375rem; width: 100%; } - .usa-button--unstyled { - @include u-text('bold'); - text-decoration: none; - } - .usa-button--big { @include at-media-max('tablet') { font-size: units(2); diff --git a/app/javascript/packages/analytics/README.md b/app/javascript/packages/analytics/README.md index 1a11f6d17d0..e33fb2b669a 100644 --- a/app/javascript/packages/analytics/README.md +++ b/app/javascript/packages/analytics/README.md @@ -40,8 +40,3 @@ The custom element will implement the analytics logging behavior, but all markup ``` - -The element supports the following attributes to customize its behavior: - -- `event-name`: The name of the analytics event that should be logged when clicked -- `payload`: (Optional) JSON payload of additional data that should be included in the logged event diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index be5afdedfed..251090747f6 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -48,9 +48,9 @@ form: f, action: SignInRecaptchaForm::RECAPTCHA_ACTION, button_options: { full_width: true }, - ).with_content(t('forms.buttons.submit.default')) %> + ).with_content(t('links.sign_in')) %> <% else %> - <%= f.submit t('forms.buttons.submit.default'), full_width: true, wide: false %> + <%= f.submit t('links.sign_in'), full_width: true, wide: false %> <% end %> <% end %> <% if desktop_device? %> diff --git a/app/views/sign_up/registrations/new.html.erb b/app/views/sign_up/registrations/new.html.erb index d654bfba7fc..e9e5dbee2c5 100644 --- a/app/views/sign_up/registrations/new.html.erb +++ b/app/views/sign_up/registrations/new.html.erb @@ -42,12 +42,7 @@ required: true, ) %> - <%= f.submit( - t('forms.buttons.submit.default'), - full_width: true, - wide: false, - class: 'display-block margin-y-5', - ) %> + <%= f.submit t('forms.buttons.submit.default'), class: 'display-block margin-y-5' %> <% end %> <%= render 'shared/cancel', link: decorated_sp_session.cancel_link_url %> @@ -72,4 +67,4 @@ step: :enter_email, ), ) %> -

      +

      \ No newline at end of file diff --git a/spec/components/tab_navigation_component_spec.rb b/spec/components/tab_navigation_component_spec.rb index 9bdaeb50a64..8bf8686a95c 100644 --- a/spec/components/tab_navigation_component_spec.rb +++ b/spec/components/tab_navigation_component_spec.rb @@ -11,7 +11,6 @@ it 'renders labelled navigation' do expect(rendered).to have_css('.tab-navigation[aria-label="Navigation"]') - expect(rendered).to have_css('li', count: 2) expect(rendered).to have_link('First') { |link| !is_current_link?(link) } expect(rendered).to have_link('Second') { |link| !is_current_link?(link) } end @@ -42,20 +41,10 @@ end it 'renders current link as highlighted' do - expect(rendered).to have_css('li,[role=listitem]', count: 2) expect(rendered).to have_link('First') { |link| is_current_link?(link) } expect(rendered).to have_link('Second') { |link| !is_current_link?(link) } end - it 'wraps link in click observer' do - expect(rendered).to have_link('First') do |link| - expect(link).to have_ancestor('lg-click-observer') - end - expect(rendered).to have_link('Second') do |link| - expect(link).not_to have_ancestor('lg-click-observer') - end - end - context 'with routes defining full URL' do let(:routes) do [ @@ -123,6 +112,6 @@ end def is_current_link?(link) - link.matches_css?('[aria-current="page"].usa-button--outline') + link.matches_css?('[aria-current="page"]:not(.usa-button--outline)') end end diff --git a/spec/features/saml/saml_spec.rb b/spec/features/saml/saml_spec.rb index 049f6a59ad5..2e3262ff730 100644 --- a/spec/features/saml/saml_spec.rb +++ b/spec/features/saml/saml_spec.rb @@ -284,7 +284,7 @@ expect(page).to have_content( t('headings.create_account_with_sp.sp_text', app_name: APP_NAME), ) - expect(page).to have_button(t('forms.buttons.submit.default')) + expect(page).to have_button('Sign in') # visit from SP with force_authn: true expect(page).to have_content( strip_tags( @@ -334,7 +334,7 @@ ), ), ) - expect(page).to have_button(t('forms.buttons.submit.default')) + expect(page).to have_button('Sign in') # Log in with Test SP as the SP session fill_in_credentials_and_submit(user.email, user.password) fill_in_code_with_last_phone_otp @@ -363,7 +363,7 @@ ), ), ) - expect(page).to have_button(t('forms.buttons.submit.default')) + expect(page).to have_button('Sign in') # log in for second time fill_in_credentials_and_submit(user.email, user.password) @@ -404,7 +404,7 @@ expect(page).to have_content( t('headings.create_account_with_sp.sp_text', app_name: APP_NAME), ) - expect(page).to have_button(t('forms.buttons.submit.default')) + expect(page).to have_button('Sign in') expect(page).to have_content( strip_tags( t( @@ -439,7 +439,7 @@ expect(page).to have_content( t('headings.create_account_with_sp.sp_text', app_name: APP_NAME), ) - expect(page).to have_button(t('forms.buttons.submit.default')) + expect(page).to have_button('Sign in') expect(page).to have_content( strip_tags( t( @@ -454,7 +454,7 @@ expect(page).to have_content( t('headings.create_account_with_sp.sp_text', app_name: APP_NAME), ) - expect(page).to have_button(t('forms.buttons.submit.default')) + expect(page).to have_button('Sign in') expect(page).to_not have_content( strip_tags( t( diff --git a/spec/features/visitors/password_recovery_spec.rb b/spec/features/visitors/password_recovery_spec.rb index bd55de37eef..3f1bed3114d 100644 --- a/spec/features/visitors/password_recovery_spec.rb +++ b/spec/features/visitors/password_recovery_spec.rb @@ -192,7 +192,7 @@ fill_in t('account.index.email'), with: @user.email fill_in t('components.password_toggle.label'), with: 'NewVal!dPassw0rd' - click_button t('forms.buttons.submit.default') + click_button t('links.sign_in') fill_in_code_with_last_phone_otp click_submit_default click_agree_and_continue diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb index feaa8174111..60b5ecdacfe 100644 --- a/spec/support/features/session_helper.rb +++ b/spec/support/features/session_helper.rb @@ -98,7 +98,7 @@ def fill_in_bad_piv_cac_credentials_and_submit def fill_in_credentials_and_submit(email, password) fill_in t('account.index.email'), with: email fill_in t('account.index.password'), with: password - click_button t('forms.buttons.submit.default') + click_button t('links.sign_in') end def fill_in_totp_name(nickname = 'App') diff --git a/spec/views/devise/sessions/new.html.erb_spec.rb b/spec/views/devise/sessions/new.html.erb_spec.rb index 5f76b8bb72b..5cd8955c27d 100644 --- a/spec/views/devise/sessions/new.html.erb_spec.rb +++ b/spec/views/devise/sessions/new.html.erb_spec.rb @@ -218,7 +218,7 @@ let(:sign_in_recaptcha_enabled) { false } it 'renders default sign-in submit button' do - expect(rendered).to have_button(t('forms.buttons.submit.default')) + expect(rendered).to have_button(t('links.sign_in')) expect(rendered).not_to have_css('lg-captcha-submit-button') end @@ -243,7 +243,7 @@ let(:recaptcha_mock_validator) { true } it 'renders captcha sign-in submit button' do - expect(rendered).to have_button(t('forms.buttons.submit.default')) + expect(rendered).to have_button(t('links.sign_in')) expect(rendered).to have_css('lg-captcha-submit-button') end end @@ -253,7 +253,7 @@ let(:sign_in_recaptcha_enabled) { true } it 'renders captcha sign-in submit button' do - expect(rendered).to have_button(t('forms.buttons.submit.default')) + expect(rendered).to have_button(t('links.sign_in')) expect(rendered).to have_css('lg-captcha-submit-button') end