Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/controllers/concerns/ab_testing_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ module AbTestingConcern
# @param [Symbol] test Name of the test, which should correspond to an A/B test defined in
# # config/initializer/ab_tests.rb.
# @return [Symbol,nil] Bucket to use for the given test, or nil if the test is not active.
def ab_test_bucket(test_name)
def ab_test_bucket(test_name, user: current_user)
test = AbTests.all[test_name]
raise "Unknown A/B test: #{test_name}" unless test

test.bucket(
request:,
service_provider: current_sp&.issuer,
session:,
user: current_user,
user:,
user_session:,
)
end
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/idv/enter_password_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def create
gpo_verification_pending: idv_session.profile.gpo_verification_pending?,
in_person_verification_pending: idv_session.profile.in_person_verification_pending?,
deactivation_reason: idv_session.profile.deactivation_reason,
proofing_workflow_time_in_seconds: idv_session.proofing_workflow_time_in_seconds,
**ab_test_analytics_buckets,
)
Funnel::DocAuth::RegisterStep.new(current_user.id, current_sp&.issuer).
Expand All @@ -62,6 +63,7 @@ def create
gpo_verification_pending: idv_session.profile.gpo_verification_pending?,
in_person_verification_pending: idv_session.profile.in_person_verification_pending?,
deactivation_reason: idv_session.profile.deactivation_reason,
proofing_workflow_time_in_seconds: idv_session.proofing_workflow_time_in_seconds,
**ab_test_analytics_buckets,
)

Expand Down
1 change: 1 addition & 0 deletions app/controllers/idv/welcome_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class WelcomeController < ApplicationController
before_action :confirm_not_rate_limited

def show
idv_session.proofing_started_at ||= Time.zone.now.iso8601
analytics.idv_doc_auth_welcome_visited(**analytics_arguments)

Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]).
Expand Down
33 changes: 26 additions & 7 deletions app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class SessionsController < Devise::SessionsController
include Api::CsrfTokenConcern
include ForcedReauthenticationConcern
include NewDeviceConcern
include AbTestingConcern

rescue_from ActionController::InvalidAuthenticityToken, with: :redirect_to_signin

Expand Down Expand Up @@ -41,7 +42,7 @@ def create
handle_valid_authentication
ensure
handle_invalid_authentication if rate_limit_password_failure && !current_user
track_authentication_attempt(auth_params[:email])
track_authentication_attempt
end

def destroy
Expand Down Expand Up @@ -97,13 +98,24 @@ def locked_out_time_remaining

def valid_captcha_result?
return @valid_captcha_result if defined?(@valid_captcha_result)
@valid_captcha_result = SignInRecaptchaForm.new(**recaptcha_form_args).submit(
email: auth_params[:email],
@valid_captcha_result = recaptcha_form.submit(
recaptcha_token: params.require(:user)[:recaptcha_token],
device_cookie: cookies[:device],
).success?
end

def recaptcha_form
@recaptcha_form ||= SignInRecaptchaForm.new(
email: auth_params[:email],
device_cookie: cookies[:device],
ab_test_bucket: ab_test_bucket(:RECAPTCHA_SIGN_IN, user: user_from_params),
**recaptcha_form_args,
)
end

def captcha_validation_performed?
!recaptcha_form.exempt?
end

def process_failed_captcha
sign_out(:user)
warden.lock!
Expand Down Expand Up @@ -140,6 +152,11 @@ def check_user_needs_redirect
end
end

def user_from_params
return @user_from_params if defined?(@user_from_params)
@user_from_params = User.find_with_email(auth_params[:email])
end

def auth_params
params.require(:user).permit(:email, :password)
end
Expand Down Expand Up @@ -169,21 +186,23 @@ def handle_valid_authentication
user_id: current_user.id,
email: auth_params[:email],
)
user_session[:captcha_validation_performed_at_sign_in] = captcha_validation_performed?
user_session[:platform_authenticator_available] =
params[:platform_authenticator_available] == 'true'
check_password_compromised
redirect_to next_url_after_valid_authentication
end

def track_authentication_attempt(email)
user = User.find_with_email(email) || AnonymousUser.new
def track_authentication_attempt
user = user_from_params || AnonymousUser.new

success = current_user.present? && !user_locked_out?(user) && valid_captcha_result?
analytics.email_and_password_auth(
success: success,
user_id: user.uuid,
user_locked_out: user_locked_out?(user),
rate_limited: rate_limited?,
captcha_validation_performed: captcha_validation_performed?,
valid_captcha_result: valid_captcha_result?,
bad_password_count: session[:bad_password_count].to_i,
sp_request_url_present: sp_session[:request_url].present?,
Expand All @@ -202,7 +221,7 @@ def rate_limited?

def rate_limiter
return @rate_limiter if defined?(@rate_limiter)
user = User.find_with_email(auth_params[:email])
user = user_from_params
return @rate_limiter = nil unless user
@rate_limiter = RateLimiter.new(
rate_limit_type: :sign_in_user_id_per_ip,
Expand Down
25 changes: 19 additions & 6 deletions app/forms/sign_in_recaptcha_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,37 @@ class SignInRecaptchaForm

RECAPTCHA_ACTION = 'sign_in'

attr_reader :form_class, :form_args, :email, :recaptcha_token, :device_cookie
attr_reader :form_class, :form_args, :email, :recaptcha_token, :device_cookie, :ab_test_bucket

validate :validate_recaptcha_result

def initialize(form_class: RecaptchaForm, **form_args)
def initialize(
email:,
device_cookie:,
ab_test_bucket:,
form_class: RecaptchaForm,
**form_args
)
@email = email
@device_cookie = device_cookie
@ab_test_bucket = ab_test_bucket
@form_class = form_class
@form_args = form_args
end

def submit(email:, recaptcha_token:, device_cookie:)
@email = email
def submit(recaptcha_token:)
@recaptcha_token = recaptcha_token
@device_cookie = device_cookie

success = valid?
FormResponse.new(success:, errors:, serialize_error_details_only: true)
end

def exempt?
IdentityConfig.store.sign_in_recaptcha_score_threshold.zero? ||
ab_test_bucket != :sign_in_recaptcha ||
device.present?
end

private

def validate_recaptcha_result
Expand All @@ -35,7 +48,7 @@ def device
end

def score_threshold
if IdentityConfig.store.sign_in_recaptcha_score_threshold.zero? || device.present?
if exempt?
0.0
else
IdentityConfig.store.sign_in_recaptcha_score_threshold
Expand Down
5 changes: 4 additions & 1 deletion app/forms/webauthn_setup_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def generic_error_message

private

attr_reader :success, :transports, :invalid_transports, :protocol
attr_reader :success, :transports, :aaguid, :invalid_transports, :protocol
attr_accessor :user, :challenge, :attestation_object, :client_data_json,
:name, :platform_authenticator, :authenticator_data_flags, :device_name

Expand Down Expand Up @@ -110,6 +110,7 @@ def valid_attestation_response?(protocol)
)

begin
@aaguid = attestation_response.authenticator_data.aaguid
attestation_response.valid?(@challenge.pack('c*'), original_origin)
rescue StandardError
false
Expand Down Expand Up @@ -141,6 +142,7 @@ def create_webauthn_configuration
platform_authenticator: platform_authenticator,
transports: transports.presence,
authenticator_data_flags: authenticator_data_flags,
aaguid: aaguid,
)
end

Expand Down Expand Up @@ -172,6 +174,7 @@ def extra_analytics_attributes
pii_like_keypaths: [[:mfa_method_counts, :phone]],
authenticator_data_flags: authenticator_data_flags,
unknown_transports: invalid_transports.presence,
aaguid: aaguid,
}.compact
end
end
2 changes: 2 additions & 0 deletions app/forms/webauthn_verification_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def valid_assertion_response?
client_data_json.blank? ||
signature.blank? ||
challenge.blank?

WebAuthn::AuthenticatorAssertionResponse.new(
authenticator_data: Base64.decode64(authenticator_data),
client_data_json: Base64.decode64(client_data_json),
Expand Down Expand Up @@ -165,6 +166,7 @@ def extra_analytics_attributes
{
webauthn_configuration_id: webauthn_configuration&.id,
frontend_error: webauthn_error.presence,
webauthn_aaguid: webauthn_configuration&.aaguid,
}.compact
end
end
6 changes: 2 additions & 4 deletions app/jobs/reports/fraud_metrics_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def perform(date = Time.zone.yesterday.end_of_day)

ReportMailer.tables_report(
email: email_addresses,
subject: "Fraud Metrics Report - #{date.to_date}",
subject: "Fraud Metrics Report - #{report_date.to_date}",
reports: reports,
message: preamble,
attachment_format: :xlsx,
Expand Down Expand Up @@ -60,9 +60,7 @@ def preamble(env: Identity::Hostdata.env || 'local')
end

def reports
@reports ||= [
fraud_metrics_lg99_report.as_emailable_reports,
]
@reports ||= fraud_metrics_lg99_report.as_emailable_reports
end

def fraud_metrics_lg99_report
Expand Down
16 changes: 15 additions & 1 deletion app/jobs/resolution_proofing_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ def perform(
device_profiling_success: callback_log_data&.device_profiling_success,
timing: timer.results,
)

if IdentityConfig.store.idv_socure_shadow_mode_enabled
SocureShadowModeProofingJob.perform_later(
document_capture_session_result_id: document_capture_session.result_id,
encrypted_arguments:,
service_provider_issuer:,
user_email: user_email_for_proofing(user),
user_uuid: user.uuid,
)
end
end

private
Expand All @@ -89,7 +99,7 @@ def make_vendor_proofing_requests(
)
result = progressive_proofer.proof(
applicant_pii: applicant_pii,
user_email: user.confirmed_email_addresses.first.email,
user_email: user_email_for_proofing(user),
threatmetrix_session_id: threatmetrix_session_id,
request_ip: request_ip,
ipp_enrollment_in_progress: ipp_enrollment_in_progress,
Expand All @@ -109,6 +119,10 @@ def make_vendor_proofing_requests(
)
end

def user_email_for_proofing(user)
user.confirmed_email_addresses.first.email
end

def log_threatmetrix_info(threatmetrix_result, user)
logger_info_hash(
name: 'ThreatMetrix',
Expand Down
Loading