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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ jobs:
command: |
sudo apt-get update
sudo apt-get install python3-pip python-dev jq
sudo pip install awscli
sudo pip install awscli --ignore-installed six
- run:
name: Install Code Climate Test Reporter
command: |
Expand Down
1 change: 0 additions & 1 deletion app/assets/stylesheets/_vendor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@
@import 'basscss-sass/positions';
@import 'basscss-sass/grid';
@import 'basscss-sass/flex-object';
@import 'basscss-sass/responsive-white-space';
4 changes: 2 additions & 2 deletions app/controllers/users/verify_password_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ def confirm_personal_key
redirect_to root_url
end

# @return [Pii::Attributes, nil]
def decrypted_pii
pii = reactivate_account_session.decrypted_pii
@_decrypted_pii ||= Pii::Attributes.new_from_json(pii)
@_decrypted_pii ||= reactivate_account_session.decrypted_pii
end

def handle_success(result)
Expand Down
7 changes: 4 additions & 3 deletions app/controllers/users/verify_personal_key_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def create

analytics.track_event(Analytics::PERSONAL_KEY_REACTIVATION_SUBMITTED, result.to_h)
if result.success?
handle_success(decrypted_pii_json: personal_key_form.decrypted_pii_json)
handle_success(decrypted_pii: personal_key_form.decrypted_pii)
else
handle_failure(result)
end
Expand Down Expand Up @@ -61,9 +61,10 @@ def init_account_reactivation
reactivate_account_session.start
end

def handle_success(decrypted_pii_json:)
# @param [Pii::Attributes] decrypted_pii
def handle_success(decrypted_pii:)
analytics.track_event(Analytics::PERSONAL_KEY_REACTIVATION)
reactivate_account_session.store_decrypted_pii(decrypted_pii_json)
reactivate_account_session.store_decrypted_pii(decrypted_pii)
redirect_to verify_password_url
end

Expand Down
9 changes: 3 additions & 6 deletions app/forms/verify_personal_key_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ def submit
FormResponse.new(success: valid?, errors: errors, extra: extra)
end

def decrypted_pii_json
decrypted_pii&.to_json
# @return [Pii::Attributes,nil]
def decrypted_pii
@_pii ||= password_reset_profile.recover_pii(personal_key)
end

private
Expand All @@ -32,10 +33,6 @@ def password_reset_profile
user.decorate.password_reset_profile
end

def decrypted_pii
@_pii ||= password_reset_profile.recover_pii(personal_key)
end

def validate_personal_key
return check_personal_key if personal_key_decrypts?
errors.add :personal_key, :personal_key_incorrect
Expand Down
27 changes: 25 additions & 2 deletions app/javascript/packages/document-capture/context/acuant.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import AnalyticsContext from './analytics';

/** @typedef {import('react').ReactNode} ReactNode */

/**
* @typedef AcuantConfig
*
* @prop {string=} path Path from which to load SDK service worker.
*
* @see https://github.com/Acuant/JavascriptWebSDKV11/blob/11.4.3/SimpleHTMLApp/webSdk/dist/AcuantJavascriptWebSdk.js#L1025-L1027
* @see https://github.com/Acuant/JavascriptWebSDKV11/blob/11.4.3/SimpleHTMLApp/webSdk/dist/AcuantJavascriptWebSdk.js#L1049
*/

/**
* @typedef AcuantCamera
*
Expand Down Expand Up @@ -40,8 +49,9 @@ import AnalyticsContext from './analytics';
/**
* @typedef AcuantGlobals
*
* @prop {()=>void} onAcuantSdkLoaded Acuant initialization callback.
* @prop {AcuantCamera} AcuantCamera Acuant camera API.
* @prop {AcuantConfig=} acuantConfig Acuant configuration.
* @prop {()=>void} onAcuantSdkLoaded Acuant initialization callback.
* @prop {AcuantCamera} AcuantCamera Acuant camera API.
* @prop {AcuantJavaScriptWebSDK} AcuantJavascriptWebSdk Acuant web SDK.
*/

Expand Down Expand Up @@ -74,6 +84,15 @@ export const DEFAULT_ACCEPTABLE_GLARE_SCORE = 30;
*/
export const DEFAULT_ACCEPTABLE_SHARPNESS_SCORE = 30;

/**
* Returns the containing directory of the given file, including a trailing slash.
*
* @param {string} file
*
* @return {string}
*/
export const dirname = (file) => file.split('/').slice(0, -1).concat('').join('/');

const AcuantContext = createContext({
isReady: false,
isAcuantLoaded: false,
Expand Down Expand Up @@ -175,6 +194,9 @@ function AcuantContextProvider({
);
};

const originalAcuantConfig = /** @type {AcuantGlobal} */ (window).acuantConfig;
/** @type {AcuantGlobal} */ (window).acuantConfig = { path: dirname(sdkSrc) };

const script = document.createElement('script');
script.async = true;
script.src = sdkSrc;
Expand All @@ -183,6 +205,7 @@ function AcuantContextProvider({

return () => {
/** @type {AcuantGlobal} */ (window).onAcuantSdkLoaded = originalOnAcuantSdkLoaded;
/** @type {AcuantGlobal} */ (window).acuantConfig = originalAcuantConfig;
document.body.removeChild(script);
};
}, []);
Expand Down
13 changes: 10 additions & 3 deletions app/jobs/reports/agency_invoice_issuer_supplement_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ class AgencyInvoiceIssuerSupplementReport < BaseReport

def perform(_date)
raw_results = service_providers.flat_map do |service_provider|
transaction_with_timeout do
Db::MonthlySpAuthCount::TotalMonthlyAuthCountsWithinIaaWindow.call(service_provider)
end.to_a
[:sum, :unique].flat_map do |aggregate|
transaction_with_timeout do
Db::MonthlySpAuthCount::TotalMonthlyAuthCountsWithinIaaWindow.call(
service_provider: service_provider,
aggregate: aggregate,
)
end.to_a
end
end

results = combine_by_issuer_month(raw_results)
Expand Down Expand Up @@ -46,6 +51,8 @@ def combine_by_issuer_month(raw_results)
year_month: year_month,
ial1_total_auth_count: extract(grouped, 'total_auth_count', ial: 1),
ial2_total_auth_count: extract(grouped, 'total_auth_count', ial: 2),
ial1_unique_users: extract(grouped, 'unique_users', ial: 1),
ial2_unique_users: extract(grouped, 'unique_users', ial: 2),
}
end.values
end
Expand Down
4 changes: 4 additions & 0 deletions app/models/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ def decrypt_pii(password)
Pii::Attributes.new_from_json(decrypted_json)
end

# @return [Pii::Attributes]
def recover_pii(personal_key)
encryptor = Encryption::Encryptors::PiiEncryptor.new(personal_key)
decrypted_recovery_json = encryptor.decrypt(encrypted_pii_recovery, user_uuid: user.uuid)
Pii::Attributes.new_from_json(decrypted_recovery_json)
end

# @param [Pii::Attributes] pii
def encrypt_pii(pii, password)
encrypt_ssn_fingerprint(pii)
encrypt_compound_pii_fingerprint(pii)
Expand All @@ -54,6 +56,7 @@ def encrypt_pii(pii, password)
encrypt_recovery_pii(pii)
end

# @param [Pii::Attributes] pii
def encrypt_recovery_pii(pii)
personal_key = personal_key_generator.create
encryptor = Encryption::Encryptors::PiiEncryptor.new(
Expand All @@ -63,6 +66,7 @@ def encrypt_recovery_pii(pii)
@personal_key = personal_key
end

# @param [Pii::Attributes] pii
def self.build_compound_pii(pii)
values = [
pii.first_name,
Expand Down
14 changes: 14 additions & 0 deletions app/services/analytics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ def track_event(event, attributes = {})
event_properties: attributes.except(:user_id),
new_event: first_event_this_session?,
new_session_path: first_path_visit_this_session?,
new_session_success_state: first_success_state_this_session?,
success_state: success_state_token(event),
path: request&.path,
user_id: attributes[:user_id] || user.uuid,
locale: I18n.locale,
Expand All @@ -38,7 +40,11 @@ def track_event(event, attributes = {})
def update_session_events_and_paths_visited_for_analytics(event)
@session[:paths_visited] ||= {}
@session[:events] ||= {}
@session[:success_states] ||= {}
if request
token = success_state_token(event)
@session[:first_success_state] = !@session[:success_states].key?(token)
@session[:success_states][token] = true
@session[:first_path_visit] = !@session[:paths_visited].key?(request.path)
@session[:paths_visited][request.path] = true
end
Expand All @@ -50,6 +56,14 @@ def first_path_visit_this_session?
@session[:first_path_visit]
end

def first_success_state_this_session?
@session[:first_success_state]
end

def success_state_token(event)
"#{request&.env&.dig('REQUEST_METHOD')}|#{request&.path}|#{event}"
end

def first_event_this_session?
@session[:first_event]
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ module TotalMonthlyAuthCountsWithinIaaWindow

module_function

# @param [ServiceProvider] service_provider
# @param [Symbol] aggregate (one of :sum, :unique)
# @return [PG::Result,Array]
def call(service_provider)
def call(service_provider:, aggregate:)
return [] if !service_provider.iaa_start_date || !service_provider.iaa_end_date

iaa_range = (service_provider.iaa_start_date..service_provider.iaa_end_date)
Expand All @@ -25,35 +27,68 @@ def call(service_provider)
# The subqueries create a uniform representation of data:
# - full months from monthly_sp_auth_counts
# - partial months by aggregating sp_return_logs
# The results are rows with [ial, auth_count, year_month, issuer, iaa, iaa start, iaa end]
union_query = [
*full_month_subquery(sp: service_provider, full_months: full_months),
*partial_month_subqueries(sp: service_provider, partial_months: partial_months),
# The results are rows with [user_id, ial, auth_count, year_month]
subquery = [
*full_month_subquery(issuer: issuer, full_months: full_months),
*partial_month_subqueries(issuer: issuer, partial_months: partial_months),
].join(' UNION ALL ')

ActiveRecord::Base.connection.execute(union_query)
select_clause = case aggregate
when :sum
<<~SQL
SUM(billing_month_logs.auth_count)::bigint AS total_auth_count
SQL
when :unique
<<~SQL
COUNT(DISTINCT billing_month_logs.user_id) AS unique_users
SQL
else
raise "unknown aggregate=#{aggregate}"
end

params = {
iaa_start_date: quote(iaa_range.begin),
iaa_end_date: quote(iaa_range.end),
iaa: quote(service_provider.iaa),
issuer: quote(issuer),
subquery: subquery,
select_clause: select_clause,
}

sql = format(<<~SQL, params)
WITH subquery AS (%{subquery})
SELECT
billing_month_logs.year_month
, billing_month_logs.ial
, %{iaa_start_date} AS iaa_start_date
, %{iaa_end_date} AS iaa_end_date
, %{issuer} AS issuer
, %{iaa} AS iaa
, %{select_clause}
FROM
subquery billing_month_logs
GROUP BY
billing_month_logs.year_month
, billing_month_logs.ial
SQL

ActiveRecord::Base.connection.execute(sql)
end

# @return [String]
def full_month_subquery(sp:, full_months:)
def full_month_subquery(issuer:, full_months:)
return if full_months.blank?
params = {
iaa: sp.iaa,
issuer: sp.issuer,
iaa_start_date: sp.iaa_start_date,
iaa_end_date: sp.iaa_end_date,
issuer: issuer,
year_months: full_months.map { |r| r.begin.strftime('%Y%m') },
}.transform_values { |value| quote(value) }

full_month_subquery = format(<<~SQL, params)
SELECT
monthly_sp_auth_counts.year_month
monthly_sp_auth_counts.user_id
, monthly_sp_auth_counts.year_month
, monthly_sp_auth_counts.ial
, SUM(monthly_sp_auth_counts.auth_count)::bigint AS total_auth_count
, %{issuer} AS issuer
, %{iaa} AS iaa
, %{iaa_start_date} AS iaa_start_date
, %{iaa_end_date} AS iaa_end_date
, SUM(monthly_sp_auth_counts.auth_count)::bigint AS auth_count
FROM
monthly_sp_auth_counts
WHERE
Expand All @@ -62,38 +97,34 @@ def full_month_subquery(sp:, full_months:)
GROUP BY
monthly_sp_auth_counts.year_month
, monthly_sp_auth_counts.ial
, monthly_sp_auth_counts.user_id
SQL
end

# @return [Array<String>]
def partial_month_subqueries(sp:, partial_months:)
def partial_month_subqueries(issuer:, partial_months:)
partial_months.map do |month_range|
params = {
iaa: sp.iaa,
issuer: sp.issuer,
iaa_start_date: sp.iaa_start_date,
iaa_end_date: sp.iaa_end_date,
range_start: month_range.begin,
range_end: month_range.end,
issuer: issuer,
year_month: month_range.begin.strftime('%Y%m'),
}.transform_values { |value| quote(value) }

format(<<~SQL, params)
SELECT
%{year_month} AS year_month
sp_return_logs.user_id
, %{year_month} AS year_month
, sp_return_logs.ial
, COUNT(sp_return_logs.id) AS auth_count
, %{issuer} AS issuer
, %{iaa} AS iaa
, %{iaa_start_date} AS iaa_start_date
, %{iaa_end_date} AS iaa_end_date
FROM sp_return_logs
WHERE
sp_return_logs.requested_at BETWEEN %{range_start} AND %{range_end}
AND sp_return_logs.returned_at IS NOT NULL
AND sp_return_logs.issuer = %{issuer}
GROUP BY
sp_return_logs.ial
sp_return_logs.user_id
, sp_return_logs.ial
SQL
end
end
Expand Down
7 changes: 1 addition & 6 deletions app/services/idv/steps/document_capture_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,8 @@ def post_images

def handle_document_verification_failure(response)
mark_step_incomplete(:document_capture)
notice = if liveness_checking_enabled?
{ notice: I18n.t('errors.doc_auth.document_capture_info_with_selfie_html') }
else
{ notice: I18n.t('errors.doc_auth.document_capture_info_html') }
end
log_document_error(response)
extra = response.to_h.merge(notice)
extra = response.to_h
failure(response.first_error_message, extra)
end

Expand Down
Loading