({
+ addressVerificationMethod: DEFAULT_METHOD,
+ setAddressVerificationMethod: () => {},
+});
+
+AddressVerificationMethodContext.displayName = 'AddressVerificationMethodContext';
+
+export function AddressVerificationMethodContextProvider({
+ initialMethod = DEFAULT_METHOD,
+ children,
+}: AddressVerificationMethodContextProviderProps) {
+ const [addressVerificationMethod, setAddressVerificationMethod] = useState(initialMethod);
+ const value = useObjectMemo({ addressVerificationMethod, setAddressVerificationMethod });
+
+ return (
+
+ {children}
+
+ );
+}
+
+export default AddressVerificationMethodContext;
diff --git a/app/javascript/packages/verify-flow/index.ts b/app/javascript/packages/verify-flow/index.ts
index 26ca86f8c73..a4d10d989ad 100644
--- a/app/javascript/packages/verify-flow/index.ts
+++ b/app/javascript/packages/verify-flow/index.ts
@@ -4,4 +4,5 @@ export { default as StartOverOrCancel } from './start-over-or-cancel';
export { default as VerifyFlow } from './verify-flow';
export type { SecretValues } from './context/secrets-context';
+export type { AddressVerificationMethod } from './context/address-verification-method-context';
export type { VerifyFlowValues } from './verify-flow';
diff --git a/app/javascript/packages/verify-flow/steps/password-confirm/password-confirm-step.spec.tsx b/app/javascript/packages/verify-flow/steps/password-confirm/password-confirm-step.spec.tsx
index 22d0c9a1007..05fde8acf62 100644
--- a/app/javascript/packages/verify-flow/steps/password-confirm/password-confirm-step.spec.tsx
+++ b/app/javascript/packages/verify-flow/steps/password-confirm/password-confirm-step.spec.tsx
@@ -2,12 +2,14 @@ import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { computeAccessibleDescription } from 'dom-accessibility-api';
import { accordion } from 'identity-style-guide';
+import type { SinonSpy } from 'sinon';
import * as analytics from '@18f/identity-analytics';
import { useSandbox, usePropertyValue } from '@18f/identity-test-helpers';
import { FormSteps } from '@18f/identity-form-steps';
import { t, i18n } from '@18f/identity-i18n';
import PasswordConfirmStep from './password-confirm-step';
import submit, { PasswordSubmitError } from './submit';
+import { AddressVerificationMethodContextProvider } from '../../context/address-verification-method-context';
describe('PasswordConfirmStep', () => {
const sandbox = useSandbox();
@@ -82,15 +84,24 @@ describe('PasswordConfirmStep', () => {
,
);
+ sandbox.spy(Element.prototype, 'scrollIntoView');
+ const continueButton = getByRole('button', { name: 'forms.buttons.continue' });
+
await userEvent.type(getByLabelText('components.password_toggle.label'), 'password');
- await userEvent.click(getByRole('button', { name: 'forms.buttons.continue' }));
+ await userEvent.click(continueButton);
// There should not be a field-specific error, only a top-level alert.
const alert = await findByRole('alert');
+ expect(Element.prototype.scrollIntoView).to.have.been.calledOnce();
+ const { thisValue: scrollElement } = (Element.prototype.scrollIntoView as SinonSpy).getCall(0);
+ expect((scrollElement as Element).contains(alert)).to.be.true();
expect(alert.textContent).to.equal('Incorrect password');
const input = getByLabelText('components.password_toggle.label');
const description = computeAccessibleDescription(input);
expect(description).to.be.empty();
+
+ await userEvent.click(continueButton);
+ expect(Element.prototype.scrollIntoView).to.have.been.calledTwice();
});
describe('forgot password', () => {
@@ -118,18 +129,24 @@ describe('PasswordConfirmStep', () => {
});
describe('alert', () => {
- context('without phone value', () => {
+ context('with gpo as address verification method', () => {
it('does not render success alert', () => {
- const { queryByRole } = render();
+ const { queryByRole } = render(
+
+
+ ,
+ );
expect(queryByRole('status')).to.not.exist();
});
});
- context('with phone value', () => {
+ context('with phone as address verification method', () => {
it('renders success alert', () => {
const { queryByRole } = render(
- ,
+
+
+ ,
);
const status = queryByRole('status')!;
diff --git a/app/javascript/packages/verify-flow/steps/password-confirm/password-confirm-step.tsx b/app/javascript/packages/verify-flow/steps/password-confirm/password-confirm-step.tsx
index c51531e2cc5..8f6d566b114 100644
--- a/app/javascript/packages/verify-flow/steps/password-confirm/password-confirm-step.tsx
+++ b/app/javascript/packages/verify-flow/steps/password-confirm/password-confirm-step.tsx
@@ -10,13 +10,14 @@ import {
import { PasswordToggle } from '@18f/identity-password-toggle';
import { FlowContext } from '@18f/identity-verify-flow';
import { formatHTML } from '@18f/identity-react-i18n';
-import { PageHeading, Accordion, Alert, Link } from '@18f/identity-components';
+import { PageHeading, Accordion, Alert, Link, ScrollIntoView } from '@18f/identity-components';
import { getConfigValue } from '@18f/identity-config';
import type { ChangeEvent } from 'react';
import type { FormStepComponentProps } from '@18f/identity-form-steps';
import { ForgotPassword } from './forgot-password';
import PersonalInfoSummary from './personal-info-summary';
import StartOverOrCancel from '../../start-over-or-cancel';
+import AddressVerificationMethodContext from '../../context/address-verification-method-context';
import type { VerifyFlowValues } from '../..';
import { PasswordSubmitError } from './submit';
@@ -27,6 +28,7 @@ const FORGOT_PASSWORD_PATH = 'forgot_password';
function PasswordConfirmStep({ errors, registerField, onChange, value }: PasswordConfirmStepProps) {
const { basePath } = useContext(FlowContext);
const { onPageTransition } = useContext(FormStepsContext);
+ const { addressVerificationMethod } = useContext(AddressVerificationMethodContext);
const stepPath = `${basePath}/password_confirm`;
const [path] = useHistoryParam(undefined, { basePath: stepPath });
useDidUpdateEffect(onPageTransition, [path]);
@@ -36,10 +38,11 @@ function PasswordConfirmStep({ errors, registerField, onChange, value }: Passwor
}
const appName = getConfigValue('appName');
+ const stepErrors = errors.filter(({ error }) => error instanceof PasswordSubmitError);
return (
<>
- {value.phone && (
+ {addressVerificationMethod === 'phone' && (
{formatHTML(
t('idv.messages.review.info_verified_html', {
@@ -49,13 +52,15 @@ function PasswordConfirmStep({ errors, registerField, onChange, value }: Passwor
)}
)}
- {errors
- .filter(({ error }) => error instanceof PasswordSubmitError)
- .map(({ error }) => (
-
- {error.message}
-
- ))}
+ {stepErrors.length > 0 && (
+
+ {stepErrors.map(({ error }) => (
+
+ {error.message}
+
+ ))}
+
+ )}
{t('idv.titles.session.review', { app_name: appName })}
{t('idv.messages.sessions.review_message', { app_name: appName })}
diff --git a/app/javascript/packages/verify-flow/steps/password-confirm/submit.spec.ts b/app/javascript/packages/verify-flow/steps/password-confirm/submit.spec.ts
index 8238940081c..ebf6d59c81b 100644
--- a/app/javascript/packages/verify-flow/steps/password-confirm/submit.spec.ts
+++ b/app/javascript/packages/verify-flow/steps/password-confirm/submit.spec.ts
@@ -14,14 +14,21 @@ describe('submit', () => {
sandbox.match({ body: JSON.stringify({ user_bundle_token: '..', password: 'hunter2' }) }),
)
.resolves({
- json: () => Promise.resolve({ personal_key: '0000-0000-0000-0000' }),
+ json: () =>
+ Promise.resolve({
+ personal_key: '0000-0000-0000-0000',
+ completion_url: 'http://example.com',
+ }),
} as Response);
});
it('sends with password confirmation values', async () => {
const patch = await submit({ userBundleToken: '..', password: 'hunter2' });
- expect(patch).to.deep.equal({ personalKey: '0000-0000-0000-0000' });
+ expect(patch).to.deep.equal({
+ personalKey: '0000-0000-0000-0000',
+ completionURL: 'http://example.com',
+ });
});
});
diff --git a/app/javascript/packages/verify-flow/steps/password-confirm/submit.ts b/app/javascript/packages/verify-flow/steps/password-confirm/submit.ts
index 0212e28b1f9..34db4f51a6f 100644
--- a/app/javascript/packages/verify-flow/steps/password-confirm/submit.ts
+++ b/app/javascript/packages/verify-flow/steps/password-confirm/submit.ts
@@ -13,7 +13,15 @@ export class PasswordSubmitError extends FormError {}
* Successful API response shape.
*/
interface PasswordConfirmSuccessResponse {
+ /**
+ * Personal key generated for the user profile.
+ */
personal_key: string;
+
+ /**
+ * Final redirect URL for this verification session.
+ */
+ completion_url: string;
}
/**
@@ -26,7 +34,10 @@ type PasswordConfirmErrorResponse = ErrorResponse<'password'>;
*/
type PasswordConfirmResponse = PasswordConfirmSuccessResponse | PasswordConfirmErrorResponse;
-async function submit({ userBundleToken, password }: VerifyFlowValues) {
+async function submit({
+ userBundleToken,
+ password,
+}: VerifyFlowValues): Promise> {
const payload = { user_bundle_token: userBundleToken, password };
const json = await post(API_ENDPOINT, payload, {
json: true,
@@ -38,7 +49,7 @@ async function submit({ userBundleToken, password }: VerifyFlowValues) {
throw new PasswordSubmitError(error, { field });
}
- return { personalKey: json.personal_key };
+ return { personalKey: json.personal_key, completionURL: json.completion_url };
}
export default submit;
diff --git a/app/javascript/packages/verify-flow/steps/personal-key-confirm/personal-key-input.spec.tsx b/app/javascript/packages/verify-flow/steps/personal-key-confirm/personal-key-input.spec.tsx
index 828de0d5291..67616a4a328 100644
--- a/app/javascript/packages/verify-flow/steps/personal-key-confirm/personal-key-input.spec.tsx
+++ b/app/javascript/packages/verify-flow/steps/personal-key-confirm/personal-key-input.spec.tsx
@@ -49,6 +49,17 @@ describe('PersonalKeyInput', () => {
expect(input.value).to.equal('1234-1234-1234-1234');
});
+ it('allows the user to paste the personal key from their clipboard', async () => {
+ const { getByRole } = render();
+
+ const input = getByRole('textbox') as HTMLInputElement;
+
+ input.focus();
+ await userEvent.paste('1234-1234-1234-1234');
+
+ expect(input.value).to.equal('1234-1234-1234-1234');
+ });
+
it('validates the input value against the expected value (case-insensitive, crockford)', async () => {
const { getByRole } = render();
diff --git a/app/javascript/packages/verify-flow/steps/personal-key-confirm/personal-key-input.tsx b/app/javascript/packages/verify-flow/steps/personal-key-confirm/personal-key-input.tsx
index 03d2a835c3f..21b7bde05a0 100644
--- a/app/javascript/packages/verify-flow/steps/personal-key-confirm/personal-key-input.tsx
+++ b/app/javascript/packages/verify-flow/steps/personal-key-confirm/personal-key-input.tsx
@@ -1,10 +1,18 @@
import { forwardRef, useCallback } from 'react';
import type { ForwardedRef } from 'react';
import Cleave from 'cleave.js/react';
+import type { ReactInstanceWithCleave } from 'cleave.js/react/props';
import { t } from '@18f/identity-i18n';
import { ValidatedField } from '@18f/identity-validated-field';
import type { ValidatedFieldValidator } from '@18f/identity-validated-field';
+/**
+ * Internal Cleave.js React instance API methods.
+ */
+interface CleaveInstanceInternalAPI {
+ updateValueState: () => void;
+}
+
interface PersonalKeyInputProps {
/**
* The correct personal key to validate against.
@@ -42,6 +50,9 @@ function PersonalKeyInput(
return (
{
+ (owner as ReactInstanceWithCleave & CleaveInstanceInternalAPI).updateValueState();
+ }}
options={{
blocks: [4, 4, 4, 4],
delimiter: '-',
diff --git a/app/javascript/packages/verify-flow/steps/personal-key/personal-key-step.spec.tsx b/app/javascript/packages/verify-flow/steps/personal-key/personal-key-step.spec.tsx
index 9536b7d6116..d4a207065fe 100644
--- a/app/javascript/packages/verify-flow/steps/personal-key/personal-key-step.spec.tsx
+++ b/app/javascript/packages/verify-flow/steps/personal-key/personal-key-step.spec.tsx
@@ -3,6 +3,7 @@ import * as analytics from '@18f/identity-analytics';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import PersonalKeyStep from './personal-key-step';
+import { AddressVerificationMethodContextProvider } from '../../context/address-verification-method-context';
describe('PersonalKeyStep', () => {
const sandbox = sinon.createSandbox();
@@ -49,11 +50,31 @@ describe('PersonalKeyStep', () => {
expect(analytics.trackEvent).to.have.been.calledWith('IdV: print personal key');
});
- it('renders success alert', () => {
- const { getByRole } = render();
+ context('with gpo as address verification method', () => {
+ it('renders success alert', () => {
+ const { getByRole } = render(
+
+
+ ,
+ );
- const status = getByRole('status');
+ const status = getByRole('status');
- expect(status.textContent).to.equal('idv.messages.confirm');
+ expect(status.textContent).to.equal('idv.messages.mail_sent');
+ });
+ });
+
+ context('with phone as address verification method', () => {
+ it('renders success alert', () => {
+ const { getByRole } = render(
+
+
+ ,
+ );
+
+ const status = getByRole('status');
+
+ expect(status.textContent).to.equal('idv.messages.confirm');
+ });
});
});
diff --git a/app/javascript/packages/verify-flow/steps/personal-key/personal-key-step.tsx b/app/javascript/packages/verify-flow/steps/personal-key/personal-key-step.tsx
index 45dceb738cc..f8827dacff1 100644
--- a/app/javascript/packages/verify-flow/steps/personal-key/personal-key-step.tsx
+++ b/app/javascript/packages/verify-flow/steps/personal-key/personal-key-step.tsx
@@ -1,3 +1,4 @@
+import { useContext } from 'react';
import { Alert, PageHeading } from '@18f/identity-components';
import { ClipboardButton } from '@18f/identity-clipboard-button';
import { PrintButton } from '@18f/identity-print-button';
@@ -8,17 +9,21 @@ import type { FormStepComponentProps } from '@18f/identity-form-steps';
import { getAssetPath } from '@18f/identity-assets';
import { trackEvent } from '@18f/identity-analytics';
import type { VerifyFlowValues } from '../../verify-flow';
+import AddressVerificationMethodContext from '../../context/address-verification-method-context';
import DownloadButton from './download-button';
interface PersonalKeyStepProps extends FormStepComponentProps {}
function PersonalKeyStep({ value }: PersonalKeyStepProps) {
const personalKey = value.personalKey!;
+ const { addressVerificationMethod } = useContext(AddressVerificationMethodContext);
return (
<>
- {t('idv.messages.confirm')}
+ {addressVerificationMethod === 'phone'
+ ? t('idv.messages.confirm')
+ : t('idv.messages.mail_sent')}
{t('headings.personal_key')}
{t('instructions.personal_key.info')}
diff --git a/app/javascript/packages/verify-flow/verify-flow-step-indicator.spec.tsx b/app/javascript/packages/verify-flow/verify-flow-step-indicator.spec.tsx
index 7c2086a2257..136b1e6cf31 100644
--- a/app/javascript/packages/verify-flow/verify-flow-step-indicator.spec.tsx
+++ b/app/javascript/packages/verify-flow/verify-flow-step-indicator.spec.tsx
@@ -1,5 +1,6 @@
import { render } from '@testing-library/react';
import { StepStatus } from '@18f/identity-step-indicator';
+import { AddressVerificationMethodContextProvider } from './context/address-verification-method-context';
import VerifyFlowStepIndicator, { getStepStatus } from './verify-flow-step-indicator';
describe('getStepStatus', () => {
@@ -32,4 +33,17 @@ describe('VerifyFlowStepIndicator', () => {
const previous = getByText('step_indicator.flows.idv.verify_phone_or_address');
expect(previous.closest('.step-indicator__step--complete')).to.exist();
});
+
+ context('with gpo as address verification method', () => {
+ it('renders address verification as pending', () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ const previous = getByText('step_indicator.flows.idv.verify_phone_or_address');
+ expect(previous.closest('.step-indicator__step--pending')).to.exist();
+ });
+ });
});
diff --git a/app/javascript/packages/verify-flow/verify-flow-step-indicator.tsx b/app/javascript/packages/verify-flow/verify-flow-step-indicator.tsx
index 16bf41696ff..1932a531a60 100644
--- a/app/javascript/packages/verify-flow/verify-flow-step-indicator.tsx
+++ b/app/javascript/packages/verify-flow/verify-flow-step-indicator.tsx
@@ -1,11 +1,8 @@
+import { useContext } from 'react';
import { StepIndicator, StepIndicatorStep, StepStatus } from '@18f/identity-step-indicator';
import { t } from '@18f/identity-i18n';
-
-// i18n-tasks-use t('step_indicator.flows.idv.getting_started')
-// i18n-tasks-use t('step_indicator.flows.idv.verify_id')
-// i18n-tasks-use t('step_indicator.flows.idv.verify_info')
-// i18n-tasks-use t('step_indicator.flows.idv.verify_phone_or_address')
-// i18n-tasks-use t('step_indicator.flows.idv.secure_account')
+import AddressVerificationMethodContext from './context/address-verification-method-context';
+import type { AddressVerificationMethod } from './context/address-verification-method-context';
type VerifyFlowStepIndicatorStep =
| 'getting_started'
@@ -62,8 +59,38 @@ export function getStepStatus(index, currentStepIndex): StepStatus {
return StepStatus.INCOMPLETE;
}
+/**
+ * Given contextual details of the current flow path, returns explicit statuses which should be used
+ * at particular steps.
+ *
+ * @param details Flow details
+ *
+ * @return Step status overrides.
+ */
+function getStatusOverrides({
+ addressVerificationMethod,
+}: {
+ addressVerificationMethod: AddressVerificationMethod;
+}) {
+ const statuses: Partial> = {};
+
+ if (addressVerificationMethod === 'gpo') {
+ statuses.verify_phone_or_address = StepStatus.PENDING;
+ }
+
+ return statuses;
+}
+
function VerifyFlowStepIndicator({ currentStep }: VerifyFlowStepIndicatorProps) {
const currentStepIndex = STEP_INDICATOR_STEPS.indexOf(FLOW_STEP_STEP_MAPPING[currentStep]);
+ const { addressVerificationMethod } = useContext(AddressVerificationMethodContext);
+ const statusOverrides = getStatusOverrides({ addressVerificationMethod });
+
+ // i18n-tasks-use t('step_indicator.flows.idv.getting_started')
+ // i18n-tasks-use t('step_indicator.flows.idv.verify_id')
+ // i18n-tasks-use t('step_indicator.flows.idv.verify_info')
+ // i18n-tasks-use t('step_indicator.flows.idv.verify_phone_or_address')
+ // i18n-tasks-use t('step_indicator.flows.idv.secure_account')
return (
@@ -71,7 +98,7 @@ function VerifyFlowStepIndicator({ currentStep }: VerifyFlowStepIndicatorProps)
))}
diff --git a/app/javascript/packages/verify-flow/verify-flow.spec.tsx b/app/javascript/packages/verify-flow/verify-flow.spec.tsx
index 80f48d8b575..8fc5a5f9ce1 100644
--- a/app/javascript/packages/verify-flow/verify-flow.spec.tsx
+++ b/app/javascript/packages/verify-flow/verify-flow.spec.tsx
@@ -1,3 +1,4 @@
+import sinon from 'sinon';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as analytics from '@18f/identity-analytics';
@@ -12,7 +13,8 @@ describe('VerifyFlow', () => {
beforeEach(() => {
sandbox.spy(analytics, 'trackEvent');
sandbox.stub(window, 'fetch').resolves({
- json: () => Promise.resolve({ personal_key: personalKey }),
+ json: () =>
+ Promise.resolve({ personal_key: personalKey, completion_url: 'http://example.com' }),
} as Response);
document.body.innerHTML = ``;
});
@@ -50,7 +52,9 @@ describe('VerifyFlow', () => {
await userEvent.type(getByLabelText('forms.personal_key.confirmation_label'), personalKey);
await userEvent.keyboard('{Enter}');
- expect(onComplete).to.have.been.called();
+ expect(onComplete).to.have.been.calledWith(
+ sinon.match({ completionURL: 'http://example.com' }),
+ );
expect(sessionStorage.getItem('completedStep')).to.be.null();
});
diff --git a/app/javascript/packages/verify-flow/verify-flow.tsx b/app/javascript/packages/verify-flow/verify-flow.tsx
index 654dadf78bb..99aaf291ddc 100644
--- a/app/javascript/packages/verify-flow/verify-flow.tsx
+++ b/app/javascript/packages/verify-flow/verify-flow.tsx
@@ -8,6 +8,10 @@ import VerifyFlowStepIndicator from './verify-flow-step-indicator';
import { useSyncedSecretValues } from './context/secrets-context';
import FlowContext from './context/flow-context';
import useInitialStepValidation from './hooks/use-initial-step-validation';
+import {
+ AddressVerificationMethod,
+ AddressVerificationMethodContextProvider,
+} from './context/address-verification-method-context';
export interface VerifyFlowValues {
userBundleToken?: string;
@@ -37,6 +41,8 @@ export interface VerifyFlowValues {
password?: string;
dob?: string;
+
+ completionURL?: string;
}
interface VerifyFlowProps {
@@ -65,10 +71,15 @@ interface VerifyFlowProps {
*/
cancelURL?: string;
+ /**
+ * Initial value for address verification method.
+ */
+ initialAddressVerificationMethod?: AddressVerificationMethod;
+
/**
* Callback invoked after completing the form.
*/
- onComplete: () => void;
+ onComplete: (values: VerifyFlowValues) => void;
}
/**
@@ -98,6 +109,7 @@ function VerifyFlow({
basePath,
startOverURL = '',
cancelURL = '',
+ initialAddressVerificationMethod,
onComplete,
}: VerifyFlowProps) {
let steps = STEPS;
@@ -118,26 +130,28 @@ function VerifyFlow({
setCompletedStep(stepName);
}
- function onFormComplete() {
+ function onFormComplete(values: VerifyFlowValues) {
setCompletedStep(null);
- onComplete();
+ onComplete(values);
}
return (
-
-
+
+
+
+
);
}
diff --git a/app/javascript/packs/verify-flow.tsx b/app/javascript/packs/verify-flow.tsx
index 2b4094472bc..a61ff269b64 100644
--- a/app/javascript/packs/verify-flow.tsx
+++ b/app/javascript/packs/verify-flow.tsx
@@ -1,5 +1,9 @@
import { render } from 'react-dom';
-import { VerifyFlow, SecretsContextProvider } from '@18f/identity-verify-flow';
+import {
+ VerifyFlow,
+ SecretsContextProvider,
+ AddressVerificationMethod,
+} from '@18f/identity-verify-flow';
import SecretSessionStorage, { s2ab } from '@18f/identity-secret-session-storage';
import type { SecretValues, VerifyFlowValues } from '@18f/identity-verify-flow';
@@ -29,20 +33,20 @@ interface AppRootValues {
*/
cancelUrl: string;
- /**
- * URL to which user should be redirected after completing the form.
- */
- completionUrl: string;
-
/**
* Base64-encoded encryption key for secret session store.
*/
storeKey: string;
+}
- /**
- * Signed JWT containing user data.
- */
- userBundleToken: string;
+interface UserBundleMetadata {
+ address_verification_mechanism: AddressVerificationMethod;
+}
+
+interface UserBundle {
+ pii: Record;
+
+ metadata: UserBundleMetadata;
}
interface AppRootElement extends HTMLElement {
@@ -56,7 +60,6 @@ const {
basePath,
startOverUrl: startOverURL,
cancelUrl: cancelURL,
- completionUrl: completionURL,
storeKey: storeKeyBase64,
} = appRoot.dataset;
const storeKey = s2ab(atob(storeKeyBase64));
@@ -78,20 +81,16 @@ const storage = new SecretSessionStorage('verify');
]);
storage.key = cryptoKey;
await storage.load();
- if (initialValues.userBundleToken) {
- await storage.setItem('userBundleToken', initialValues.userBundleToken);
- }
-
- const userBundleToken = storage.getItem('userBundleToken');
- if (userBundleToken) {
- const jwtData = JSON.parse(atob(userBundleToken.split('.')[1]));
- const pii = Object.fromEntries(mapKeys(jwtData.pii, camelCase));
- Object.assign(initialValues, pii);
- }
+ const userBundleToken = initialValues.userBundleToken as string;
+ await storage.setItem('userBundleToken', userBundleToken);
+ const { pii, metadata } = JSON.parse(atob(userBundleToken.split('.')[1])) as UserBundle;
+ Object.assign(initialValues, Object.fromEntries(mapKeys(pii, camelCase)));
- function onComplete() {
+ function onComplete({ completionURL }: VerifyFlowValues) {
storage.clear();
- window.location.href = completionURL;
+ if (completionURL) {
+ window.location.href = completionURL;
+ }
}
render(
@@ -103,6 +102,7 @@ const storage = new SecretSessionStorage('verify');
cancelURL={cancelURL}
basePath={basePath}
onComplete={onComplete}
+ initialAddressVerificationMethod={metadata.address_verification_mechanism}
/>
,
appRoot,
diff --git a/app/jobs/remove_old_throttles_job.rb b/app/jobs/remove_old_throttles_job.rb
deleted file mode 100644
index 56be2bb65d9..00000000000
--- a/app/jobs/remove_old_throttles_job.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-class RemoveOldThrottlesJob < ApplicationJob
- queue_as :low
-
- WINDOW = 30.days.freeze
-
- include GoodJob::ActiveJobExtensions::Concurrency
-
- good_job_control_concurrency_with(
- total_limit: 1,
- key: -> do
- rounded = TimeService.round_time(time: arguments.first, interval: 1.hour)
- "remove-old-throttles-#{rounded.to_i}"
- end,
- )
-
- discard_on GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError
-
- def perform(now, limit: 500, total_limit: 50_000)
- max_window = Throttle::THROTTLE_CONFIG.map { |_, config| config[:attempt_window] }.max
- total_removed = 0
-
- loop do
- removed_count = DatabaseThrottle.
- where('updated_at < ?', now - (WINDOW + max_window.minutes)).
- or(DatabaseThrottle.where(updated_at: nil)).
- limit(limit).
- delete_all
-
- total_removed += removed_count
-
- Rails.logger.info(
- {
- name: 'remove_old_throttles',
- removed_count: removed_count,
- total_removed: total_removed,
- total_limit: total_limit,
- }.to_json,
- )
-
- break if removed_count.zero?
- break if total_removed >= total_limit
- end
- end
-end
diff --git a/app/models/concerns/deprecated_user_attributes.rb b/app/models/concerns/deprecated_user_attributes.rb
index 147404d4f0d..3bb60134096 100644
--- a/app/models/concerns/deprecated_user_attributes.rb
+++ b/app/models/concerns/deprecated_user_attributes.rb
@@ -2,7 +2,7 @@ module DeprecatedUserAttributes
extend ActiveSupport::Concern
DEPRECATED_ATTRIBUTES = %i[
- email_fingerprint encrypted_email email confirmed_at confirmation_token confirmation_sent_at
+ email_fingerprint encrypted_email email confirmed_at
].freeze
def []=(attribute, value)
diff --git a/app/models/user.rb b/app/models/user.rb
index 8eec4e70361..87f13543af6 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,5 +1,5 @@
class User < ApplicationRecord
- self.ignored_columns = %w[totp_timestamp confirmation_token confirmation_sent_at]
+ self.ignored_columns = %w[totp_timestamp]
include NonNullUuid
include ::NewRelic::Agent::MethodTracer
@@ -41,7 +41,6 @@ class User < ApplicationRecord
has_many :auth_app_configurations, dependent: :destroy, inverse_of: :user
has_many :backup_code_configurations, dependent: :destroy
has_many :document_capture_sessions, dependent: :destroy
- has_many :database_throttles, dependent: :destroy
has_one :registration_log, dependent: :destroy
has_one :proofing_component, dependent: :destroy
has_many :service_providers,
diff --git a/app/presenters/cancellation_presenter.rb b/app/presenters/cancellation_presenter.rb
index 209e04529f6..8172dacb267 100644
--- a/app/presenters/cancellation_presenter.rb
+++ b/app/presenters/cancellation_presenter.rb
@@ -9,7 +9,7 @@ def initialize(referer:, url_options:)
end
def go_back_path
- referer_path || two_factor_options_path
+ referer_path || authentication_methods_setup_path
end
def url_options
diff --git a/app/presenters/navigation_presenter.rb b/app/presenters/navigation_presenter.rb
index c4f5c2f4918..80539314dd9 100644
--- a/app/presenters/navigation_presenter.rb
+++ b/app/presenters/navigation_presenter.rb
@@ -17,7 +17,7 @@ def navigation_items
NavItem.new(I18n.t('account.navigation.add_email'), add_email_path),
NavItem.new(I18n.t('account.navigation.edit_password'), manage_password_path),
NavItem.new(I18n.t('account.navigation.delete_account'), account_delete_path),
- user.encrypted_recovery_code_digest.present? ? NavItem.new(
+ user.encrypted_recovery_code_digest.present? && user.active_profile ? NavItem.new(
I18n.t('account.navigation.reset_personal_key'), create_new_personal_key_path
) : nil,
].compact
diff --git a/app/services/analytics.rb b/app/services/analytics.rb
index 68dbc048ee1..773cca74999 100644
--- a/app/services/analytics.rb
+++ b/app/services/analytics.rb
@@ -137,16 +137,6 @@ def session_started_at
# rubocop:disable Layout/LineLength
DOC_AUTH = 'Doc Auth' # visited or submitted is appended
- MULTI_FACTOR_AUTH_MAX_ATTEMPTS = 'Multi-Factor Authentication: max attempts reached'
- MULTI_FACTOR_AUTH_OPTION_LIST = 'Multi-Factor Authentication: option list'
- MULTI_FACTOR_AUTH_OPTION_LIST_VISIT = 'Multi-Factor Authentication: option list visited'
- MULTI_FACTOR_AUTH_PHONE_SETUP = 'Multi-Factor Authentication: phone setup'
- MULTI_FACTOR_AUTH_MAX_SENDS = 'Multi-Factor Authentication: max otp sends reached'
- MULTI_FACTOR_AUTH_SETUP = 'Multi-Factor Authentication Setup'
- OPENID_CONNECT_BEARER_TOKEN = 'OpenID Connect: bearer token authentication'
- OPENID_CONNECT_REQUEST_AUTHORIZATION = 'OpenID Connect: authorization request'
- OPENID_CONNECT_TOKEN = 'OpenID Connect: token'
- OTP_DELIVERY_SELECTION = 'OTP: Delivery Selection'
PASSWORD_RESET_TOKEN = 'Password Reset: Token Submitted'
PASSWORD_RESET_VISIT = 'Password Reset: Email Form Visited'
PENDING_ACCOUNT_RESET_CANCELLED = 'Pending account reset cancelled'
diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb
index a9c78062875..48a3cef1536 100644
--- a/app/services/analytics_events.rb
+++ b/app/services/analytics_events.rb
@@ -777,6 +777,7 @@ def idv_phone_confirmation_otp_resent(
# @param [String] country_code country code of phone number
# @param [String] area_code area code of phone number
# @param [Boolean] rate_limit_exceeded whether or not the rate limit was exceeded by this attempt
+ # @param [String] phone_fingerprint the hmac fingerprint of the phone number formatted as e164
# @param [Hash] telephony_response response from Telephony gem
# The user requested an OTP to confirm their phone during the IDV phone step
def idv_phone_confirmation_otp_sent(
@@ -786,6 +787,7 @@ def idv_phone_confirmation_otp_sent(
country_code:,
area_code:,
rate_limit_exceeded:,
+ phone_fingerprint:,
telephony_response:,
**extra
)
@@ -797,6 +799,7 @@ def idv_phone_confirmation_otp_sent(
country_code: country_code,
area_code: area_code,
rate_limit_exceeded: rate_limit_exceeded,
+ phone_fingerprint: phone_fingerprint,
telephony_response: telephony_response,
**extra,
)
@@ -1137,6 +1140,156 @@ def multi_factor_auth_enter_webauthn_visit(
)
end
+ # Max multi factor auth attempts met
+ def multi_factor_auth_max_attempts
+ track_event('Multi-Factor Authentication: max attempts reached')
+ end
+
+ # Multi factor selected from auth options list
+ # @param [Boolean] success
+ # @param [Hash] errors
+ # @param [String] selection
+ def multi_factor_auth_option_list(success:, errors:, selection:, **extra)
+ track_event(
+ 'Multi-Factor Authentication: option list',
+ success: success,
+ errors: errors,
+ selection: selection,
+ **extra,
+ )
+ end
+
+ # User visited the list of multi-factor options to use
+ def multi_factor_auth_option_list_visit
+ track_event('Multi-Factor Authentication: option list visited')
+ end
+
+ # Multi factor auth phone setup
+ # @param [Boolean] success
+ # @param [Hash] errors
+ # @param [String] otp_delivery_preference
+ # @param [String] area_code
+ # @param [String] carrier
+ # @param [String] country_code
+ # @param [String] phone_type
+ # @param [Hash] types
+ # @param [Hash] pii_like_keypaths
+ def multi_factor_auth_phone_setup(success:,
+ errors:,
+ otp_delivery_preference:,
+ area_code:,
+ carrier:,
+ country_code:,
+ phone_type:,
+ types:,
+ **extra)
+
+ track_event(
+ 'Multi-Factor Authentication: phone setup',
+ success: success,
+ errors: errors,
+ otp_delivery_preference: otp_delivery_preference,
+ area_code: area_code,
+ carrier: carrier,
+ country_code: country_code,
+ phone_type: phone_type,
+ types: types,
+ **extra,
+ )
+ end
+
+ # Max multi factor max otp sends reached
+ def multi_factor_auth_max_sends
+ track_event('Multi-Factor Authentication: max otp sends reached')
+ end
+
+ # Tracks when a user sets up a multi factor auth method
+ # @param [String] multi_factor_auth_method
+ def multi_factor_auth_setup(multi_factor_auth_method:, **extra)
+ track_event(
+ 'Multi-Factor Authentication Setup',
+ multi_factor_auth_method: multi_factor_auth_method,
+ **extra,
+ )
+ end
+
+ # Tracks when an openid connect bearer token authentication request is made
+ # @param [Boolean] success
+ # @param [Hash] errors
+ def openid_connect_bearer_token(success:, errors:, **extra)
+ track_event(
+ 'OpenID Connect: bearer token authentication',
+ success: success,
+ errors: errors,
+ **extra,
+ )
+ end
+
+ # Tracks when openid authorization request is made
+ # @param [String] client_id
+ # @param [String] scope
+ # @param [Array] acr_values
+ # @param [Boolean] unauthorized_scope
+ # @param [Boolean] user_fully_authenticated
+ def openid_connect_request_authorization(
+ client_id:,
+ scope:,
+ acr_values:,
+ unauthorized_scope:,
+ user_fully_authenticated:,
+ **extra
+ )
+ track_event(
+ 'OpenID Connect: authorization request',
+ client_id: client_id,
+ scope: scope,
+ acr_values: acr_values,
+ unauthorized_scope: unauthorized_scope,
+ user_fully_authenticated: user_fully_authenticated,
+ **extra,
+ )
+ end
+
+ # Tracks when an openid connect token request is made
+ # @param [String] client_id
+ # @param [String] user_id
+ def openid_connect_token(client_id:, user_id:, **extra)
+ track_event(
+ 'OpenID Connect: token',
+ client_id: client_id,
+ user_id: user_id,
+ **extra,
+ )
+ end
+
+ # Tracks when user makes an otp delivery selection
+ # @param [String] otp_delivery_preference (sms or voice)
+ # @param [Boolean] resend
+ # @param [String] country_code
+ # @param [String] area_code
+ # @param ["authentication","reauthentication","confirmation"] context user session context
+ # @param [Hash] pii_like_keypaths
+ def otp_delivery_selection(
+ otp_delivery_preference:,
+ resend:,
+ country_code:,
+ area_code:,
+ context:,
+ pii_like_keypaths:,
+ **extra
+ )
+ track_event(
+ 'OTP: Delivery Selection',
+ otp_delivery_preference: otp_delivery_preference,
+ resend: resend,
+ country_code: country_code,
+ area_code: area_code,
+ context: context,
+ pii_like_keypaths: pii_like_keypaths,
+ **extra,
+ )
+ end
+
# @param [Boolean] success
# @param [Hash] errors
# The user updated their password
@@ -1391,6 +1544,43 @@ def saml_auth_request(
)
end
+ # @param [String] area_code
+ # @param [String] country_code
+ # @param [String] phone_fingerprint the hmac fingerprint of the phone number formatted as e164
+ # @param [String] context the context of the OTP, either "authentication" for confirmed phones
+ # or "confirmation" for unconfirmed
+ # @param ["sms","voice"] otp_delivery_preference the channel used to send the message
+ # @param [Boolean] resend
+ # @param [Hash] telephony_response
+ # @param [Boolean] success
+ # A phone one-time password send was attempted
+ def telephony_otp_sent(
+ area_code:,
+ country_code:,
+ phone_fingerprint:,
+ context:,
+ otp_delivery_preference:,
+ resend:,
+ telephony_response:,
+ success:,
+ **extra
+ )
+ track_event(
+ 'Telephony: OTP sent',
+ {
+ area_code: area_code,
+ country_code: country_code,
+ phone_fingerprint: phone_fingerprint,
+ context: context,
+ otp_delivery_preference: otp_delivery_preference,
+ resend: resend,
+ telephony_response: telephony_response,
+ success: success,
+ **extra,
+ },
+ )
+ end
+
# @param [Boolean] success
# @param [Hash] errors
# Tracks when the the user has selected and submitted additional MFA methods on user registration
diff --git a/app/services/db/monthly_sp_auth_count/total_monthly_auth_counts_within_iaa_window.rb b/app/services/db/monthly_sp_auth_count/total_monthly_auth_counts_within_iaa_window.rb
index eb16b4651fe..e7c090d1507 100644
--- a/app/services/db/monthly_sp_auth_count/total_monthly_auth_counts_within_iaa_window.rb
+++ b/app/services/db/monthly_sp_auth_count/total_monthly_auth_counts_within_iaa_window.rb
@@ -128,6 +128,7 @@ def partial_month_subqueries(issuer:, partial_months:)
sp_return_logs.requested_at::date BETWEEN %{range_start} AND %{range_end}
AND sp_return_logs.returned_at IS NOT NULL
AND sp_return_logs.issuer = %{issuer}
+ AND sp_return_logs.billable = true
GROUP BY
sp_return_logs.user_id
, sp_return_logs.ial
diff --git a/app/services/db/monthly_sp_auth_count/unique_monthly_auth_counts_by_iaa.rb b/app/services/db/monthly_sp_auth_count/unique_monthly_auth_counts_by_iaa.rb
index 498ead6e2f3..df505c918e8 100644
--- a/app/services/db/monthly_sp_auth_count/unique_monthly_auth_counts_by_iaa.rb
+++ b/app/services/db/monthly_sp_auth_count/unique_monthly_auth_counts_by_iaa.rb
@@ -119,6 +119,7 @@ def partial_month_subqueries(issuers:, partial_months:)
sp_return_logs.requested_at::date BETWEEN %{range_start} AND %{range_end}
AND sp_return_logs.returned_at IS NOT NULL
AND sp_return_logs.issuer IN %{issuers}
+ AND sp_return_logs.billable = true
GROUP BY
sp_return_logs.user_id
, sp_return_logs.ial
diff --git a/app/services/doc_auth/mock/result_response_builder.rb b/app/services/doc_auth/mock/result_response_builder.rb
index 04d128b0964..091baae7f1f 100644
--- a/app/services/doc_auth/mock/result_response_builder.rb
+++ b/app/services/doc_auth/mock/result_response_builder.rb
@@ -100,7 +100,7 @@ def pii_from_doc
raw_pii = parsed_data_from_uploaded_file['document']
raw_pii&.symbolize_keys || {}
else
- Idp::Constants::DEFAULT_MOCK_PII_FROM_DOC
+ Idp::Constants::MOCK_IDV_APPLICANT
end
end
diff --git a/app/services/idv/flows/in_person_flow.rb b/app/services/idv/flows/in_person_flow.rb
index 8f236ae02db..69f24e4fc7b 100644
--- a/app/services/idv/flows/in_person_flow.rb
+++ b/app/services/idv/flows/in_person_flow.rb
@@ -8,20 +8,15 @@ class InPersonFlow < Flow::BaseFlow
state_id: Idv::Steps::Ipp::StateIdStep, # info from state id
ssn: Idv::Steps::Ipp::SsnStep, # enter SSN
verify: Idv::Steps::Ipp::VerifyStep, # verify entered info
- # WILLFIX: add the failure branch for verify step
- # WILLFIX: add the verify by mail flow
- phone: Idv::Steps::Ipp::PhoneStep, # phone finder
- # WILLFIX: add the failure branch for phone step
- # WILLFIX: re-use existing password confirm step
- password_confirm: Idv::Steps::Ipp::PasswordConfirmStep,
- # WILLFIX: re-use existing personal key step
- personal_key: Idv::Steps::Ipp::PersonalKeyStep,
- barcode: Idv::Steps::Ipp::BarcodeStep,
}.freeze
ACTIONS = {
}.freeze
+ # WILLFIX: (LG-6308) move this to the barcode page when we finish setting up IPP step
+ # indicators
+ # i18n-tasks-use t('step_indicator.flows.idv.go_to_the_post_office')
+
STEP_INDICATOR_STEPS = [
{ name: :find_a_post_office },
{ name: :verify_info },
@@ -32,11 +27,20 @@ class InPersonFlow < Flow::BaseFlow
def initialize(controller, session, name)
@idv_session = self.class.session_idv(session)
- super(controller, STEPS, {}, session[name])
+ super(controller, STEPS, ACTIONS, session[name])
end
def self.session_idv(session)
session[:idv] ||= { params: {}, step_attempts: { phone: 0 } }
+ # WILLFIX: remove this line when we begin collecting user data
+ session[:idv][:applicant] ||= Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN
+
+ # WILLFIX: (LG-6349) remove this block when we implement the verify page
+ session[:idv]['profile_confirmation'] = true
+ session[:idv]['vendor_phone_confirmation'] = false
+ session[:idv]['user_phone_confirmation'] = false
+ session[:idv]['address_verification_mechanism'] = 'phone'
+ session[:idv]['resolution_successful'] = 'phone'
end
end
end
diff --git a/app/services/idv/send_phone_confirmation_otp.rb b/app/services/idv/send_phone_confirmation_otp.rb
index abc9e8bc235..4a9c79cdaa6 100644
--- a/app/services/idv/send_phone_confirmation_otp.rb
+++ b/app/services/idv/send_phone_confirmation_otp.rb
@@ -80,6 +80,7 @@ def extra_analytics_attributes
otp_delivery_preference: delivery_method,
country_code: parsed_phone.country,
area_code: parsed_phone.area_code,
+ phone_fingerprint: Pii::Fingerprinter.fingerprint(parsed_phone.e164),
rate_limit_exceeded: rate_limit_exceeded?,
telephony_response: @telephony_response,
}
diff --git a/app/services/idv/steps/ipp/barcode_step.rb b/app/services/idv/steps/ipp/barcode_step.rb
deleted file mode 100644
index ff6cea38fae..00000000000
--- a/app/services/idv/steps/ipp/barcode_step.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module Idv
- module Steps
- module Ipp
- class BarcodeStep < DocAuthBaseStep
- # i18n-tasks-use t('step_indicator.flows.idv.go_to_the_post_office')
- STEP_INDICATOR_STEP = :go_to_the_post_office
- def call; end
- end
- end
- end
-end
diff --git a/app/services/idv/steps/ipp/password_confirm_step.rb b/app/services/idv/steps/ipp/password_confirm_step.rb
deleted file mode 100644
index 8fd40b46ffe..00000000000
--- a/app/services/idv/steps/ipp/password_confirm_step.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Idv
- module Steps
- module Ipp
- class PasswordConfirmStep < DocAuthBaseStep
- STEP_INDICATOR_STEP = :secure_account
- def call; end
- end
- end
- end
-end
diff --git a/app/services/idv/steps/ipp/personal_key_step.rb b/app/services/idv/steps/ipp/personal_key_step.rb
deleted file mode 100644
index 0ca4189392b..00000000000
--- a/app/services/idv/steps/ipp/personal_key_step.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Idv
- module Steps
- module Ipp
- class PersonalKeyStep < DocAuthBaseStep
- STEP_INDICATOR_STEP = :secure_account
- def call; end
- end
- end
- end
-end
diff --git a/app/services/idv/steps/ipp/phone_step.rb b/app/services/idv/steps/ipp/phone_step.rb
deleted file mode 100644
index bbfa77b81c0..00000000000
--- a/app/services/idv/steps/ipp/phone_step.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Idv
- module Steps
- module Ipp
- class PhoneStep < DocAuthBaseStep
- STEP_INDICATOR_STEP = :verify_phone_or_address
- def call; end
- end
- end
- end
-end
diff --git a/app/services/idv/steps/ipp/verify_step.rb b/app/services/idv/steps/ipp/verify_step.rb
index 6b49fc9db2b..025308b2b51 100644
--- a/app/services/idv/steps/ipp/verify_step.rb
+++ b/app/services/idv/steps/ipp/verify_step.rb
@@ -3,7 +3,11 @@ module Steps
module Ipp
class VerifyStep < DocAuthBaseStep
STEP_INDICATOR_STEP = :verify_info
- def call; end
+ def call
+ # send the user to the phone page where they'll continue the remainder of
+ # the idv flow
+ redirect_to idv_phone_url
+ end
end
end
end
diff --git a/app/services/idv/steps/verify_base_step.rb b/app/services/idv/steps/verify_base_step.rb
index 1fa97d28854..ee66c7118bd 100644
--- a/app/services/idv/steps/verify_base_step.rb
+++ b/app/services/idv/steps/verify_base_step.rb
@@ -1,11 +1,6 @@
module Idv
module Steps
class VerifyBaseStep < DocAuthBaseStep
- AAMVA_SUPPORTED_JURISDICTIONS = %w[
- AR AZ CO CT DC DE FL GA IA ID IL IN KS KY MA MD ME MI MO MS MT NC ND NE
- NJ NM OH OR PA RI SC SD TN TX VA VT WA WI WY
- ].to_set.freeze
-
private
def summarize_result_and_throttle_failures(summary_result)
@@ -78,7 +73,9 @@ def should_use_aamva?(pii_from_doc)
end
def aamva_state?(pii_from_doc)
- AAMVA_SUPPORTED_JURISDICTIONS.include?(pii_from_doc['state_id_jurisdiction'])
+ IdentityConfig.store.aamva_supported_jurisdictions.include?(
+ pii_from_doc['state_id_jurisdiction'],
+ )
end
def aamva_disallowed_for_service_provider?
diff --git a/app/services/proofing/mock/state_id_mock_client.rb b/app/services/proofing/mock/state_id_mock_client.rb
index 51c09db834a..e87f1ed7f2b 100644
--- a/app/services/proofing/mock/state_id_mock_client.rb
+++ b/app/services/proofing/mock/state_id_mock_client.rb
@@ -42,7 +42,7 @@ class StateIdMockClient < Proofing::Base
private
def state_not_supported?(state_id_jurisdiction)
- !Idv::Steps::VerifyBaseStep::AAMVA_SUPPORTED_JURISDICTIONS.include? state_id_jurisdiction
+ !IdentityConfig.store.aamva_supported_jurisdictions.include? state_id_jurisdiction
end
def invalid_state_id_number?(state_id_number)
diff --git a/app/services/throttle.rb b/app/services/throttle.rb
index 7d35261b7af..df0e89c150e 100644
--- a/app/services/throttle.rb
+++ b/app/services/throttle.rb
@@ -75,19 +75,15 @@ def initialize(throttle_type:, user: nil, target: nil)
end
def attempts
- if IdentityConfig.store.redis_throttle_enabled
- redis_attempts.to_i
- else
- postgres_throttle.attempts
- end
+ return @redis_attempts.to_i if defined?(@redis_attempts)
+
+ fetch_state!
+
+ @redis_attempts.to_i
end
def throttled?
- if IdentityConfig.store.redis_throttle_enabled
- !expired? && maxed?
- else
- postgres_throttle.throttled?
- end
+ !expired? && maxed?
end
def throttled_else_increment?
@@ -100,46 +96,22 @@ def throttled_else_increment?
end
def attempted_at
- if IdentityConfig.store.redis_throttle_enabled
- redis_attempted_at
- else
- postgres_throttle.attempted_at
- end
+ return @redis_attempted_at if defined?(@redis_attempted_at)
+
+ fetch_state!
+
+ @redis_attempted_at
end
def expires_at
- if IdentityConfig.store.redis_throttle_enabled
- return Time.zone.now if redis_attempted_at.blank?
- redis_attempted_at + Throttle.attempt_window_in_minutes(throttle_type).minutes
- else
- postgres_throttle.expires_at
- end
+ return Time.zone.now if attempted_at.blank?
+ attempted_at + Throttle.attempt_window_in_minutes(throttle_type).minutes
end
def remaining_count
return 0 if throttled?
- if IdentityConfig.store.redis_throttle_enabled
- Throttle.max_attempts(throttle_type) - attempts
- else
- postgres_throttle.remaining_count
- end
- end
-
- def redis_attempts
- return @redis_attempts if defined?(@redis_attempts)
-
- fetch_state!
-
- @redis_attempts
- end
-
- def redis_attempted_at
- return @redis_attempted_at if defined?(@redis_attempted_at)
-
- fetch_state!
-
- @redis_attempted_at
+ Throttle.max_attempts(throttle_type) - attempts
end
def expired?
@@ -147,7 +119,7 @@ def expired?
end
def maxed?
- redis_attempts && redis_attempts >= Throttle.max_attempts(throttle_type)
+ attempts && attempts >= Throttle.max_attempts(throttle_type)
end
def increment!
@@ -165,8 +137,6 @@ def increment!
@redis_attempts = value.to_i
@redis_attempted_at = Time.zone.now
- postgres_throttle.increment
-
attempts
end
@@ -205,8 +175,6 @@ def reset!
client.del(key)
end
- postgres_throttle.reset
-
@redis_attempts = 0
@redis_attempted_at = nil
end
@@ -225,11 +193,6 @@ def increment_to_throttled!
@redis_attempts = value.to_i
@redis_attempted_at = Time.zone.now
- postgres_throttle.update(
- attempts: Throttle.max_attempts(throttle_type),
- attempted_at: Time.zone.now,
- )
-
attempts
end
@@ -241,16 +204,6 @@ def key
end
end
- def postgres_throttle
- return @postgres_throttle if @postgres_throttle
-
- @postgres_throttle ||= DatabaseThrottle.for(
- throttle_type: throttle_type,
- user: @user,
- target: @target,
- )
- end
-
def self.attempt_window_in_minutes(throttle_type)
THROTTLE_CONFIG.dig(throttle_type, :attempt_window)
end
diff --git a/app/views/idv/doc_auth/welcome.html.erb b/app/views/idv/doc_auth/welcome.html.erb
index f93e9253cc8..dd10278335d 100644
--- a/app/views/idv/doc_auth/welcome.html.erb
+++ b/app/views/idv/doc_auth/welcome.html.erb
@@ -89,7 +89,7 @@
<%= render 'shared/cancel', link: idv_cancel_path(step: 'welcome') %>
<% else %>
- <%= link_to(t('two_factor_authentication.choose_another_option'), two_factor_options_path) %>
+ <%= link_to(t('two_factor_authentication.choose_another_option'), authentication_methods_setup_path) %>
<% end %>
<% end %>
diff --git a/app/views/idv/in_person/password_confirm.html.erb b/app/views/idv/in_person/password_confirm.html.erb
deleted file mode 100644
index 6be83dfccef..00000000000
--- a/app/views/idv/in_person/password_confirm.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<% title t('titles.doc_auth.verify') %>
-
-
- <%= t('in_person_proofing.headings.password_confirm', app_name: APP_NAME) %>
-
-
-<%= validated_form_for :doc_auth,
- url: url_for,
- method: 'put',
- html: { autocomplete: 'off', class: 'margin-y-5' } do |f| %>
- <%= f.button :button,
- t('doc_auth.buttons.continue'),
- type: :submit,
- class: 'usa-button--big usa-button--wide' %>
-<% end %>
diff --git a/app/views/idv/in_person/personal_key.html.erb b/app/views/idv/in_person/personal_key.html.erb
deleted file mode 100644
index 79313e2f127..00000000000
--- a/app/views/idv/in_person/personal_key.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<% title t('titles.doc_auth.verify') %>
-
-
- <%= t('in_person_proofing.headings.personal_key') %>
-
-
-<%= validated_form_for :doc_auth,
- url: url_for,
- method: 'put',
- html: { autocomplete: 'off', class: 'margin-y-5' } do |f| %>
- <%= f.button :button,
- t('doc_auth.buttons.continue'),
- type: :submit,
- class: 'usa-button--big usa-button--wide' %>
-<% end %>
diff --git a/app/views/idv/in_person/phone.html.erb b/app/views/idv/in_person/phone.html.erb
deleted file mode 100644
index 51100ad4d1d..00000000000
--- a/app/views/idv/in_person/phone.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<% title t('titles.doc_auth.verify') %>
-
-
- <%= t('in_person_proofing.headings.phone') %>
-
-
-<%= validated_form_for :doc_auth,
- url: url_for,
- method: 'put',
- html: { autocomplete: 'off', class: 'margin-y-5' } do |f| %>
- <%= f.button :button,
- t('doc_auth.buttons.continue'),
- type: :submit,
- class: 'usa-button--big usa-button--wide' %>
-<% end %>
diff --git a/app/views/shared/_cancel_or_back_to_options.html.erb b/app/views/shared/_cancel_or_back_to_options.html.erb
index 24e1043e479..0e8e4222890 100644
--- a/app/views/shared/_cancel_or_back_to_options.html.erb
+++ b/app/views/shared/_cancel_or_back_to_options.html.erb
@@ -2,6 +2,6 @@
<% if MfaPolicy.new(current_user).two_factor_enabled? %>
<%= link_to t('links.cancel'), account_path %>
<% else %>
- <%= link_to t('two_factor_authentication.choose_another_option'), two_factor_options_path %>
+ <%= link_to t('two_factor_authentication.choose_another_option'), authentication_methods_setup_path %>
<% end %>
<% end %>
diff --git a/app/views/sign_up/cancellations/new.html.erb b/app/views/sign_up/cancellations/new.html.erb
index 386c62d24b5..0d8d6f75094 100644
--- a/app/views/sign_up/cancellations/new.html.erb
+++ b/app/views/sign_up/cancellations/new.html.erb
@@ -14,19 +14,11 @@
<%= t('users.delete.bullet_4', app_name: APP_NAME) %>
- <% if IdentityConfig.store.new_sign_up_cancellation_url_enabled %>
- <% c.action_button(
- action: ->(**tag_options, &block) do
- button_to(sign_up_destroy_path, method: :delete, **tag_options, &block)
- end,
- ) { t('forms.buttons.cancel') } %>
- <% else %>
- <% c.action_button(
- action: ->(**tag_options, &block) do
- button_to(destroy_user_path, method: :delete, **tag_options, &block)
- end,
- ) { t('forms.buttons.cancel') } %>
- <% end %>
+ <% c.action_button(
+ action: ->(**tag_options, &block) do
+ button_to(sign_up_destroy_path, method: :delete, **tag_options, &block)
+ end,
+ ) { t('forms.buttons.cancel') } %>
<% c.action_button(
action: ->(**tag_options, &block) do
diff --git a/app/views/users/mfa_selection/index.html.erb b/app/views/users/mfa_selection/index.html.erb
index b51cda2bb9b..8da0ce458b6 100644
--- a/app/views/users/mfa_selection/index.html.erb
+++ b/app/views/users/mfa_selection/index.html.erb
@@ -2,6 +2,8 @@
<%= render PageHeadingComponent.new.with_content(t('two_factor_authentication.two_factor_choice')) %>
+<%= @presenter.intro %>
+
<%= validated_form_for @two_factor_options_form,
html: { autocomplete: 'off' },
method: :patch,
@@ -21,6 +23,6 @@
<%= f.button :submit, t('forms.buttons.continue'), class: 'usa-button--big usa-button--wide margin-bottom-1' %>
<% end %>
-<%= render 'shared/cancel', link: destroy_user_session_path %>
+<%= render 'shared/cancel', link: @after_setup_path %>
<%= javascript_packs_tag_once('webauthn-unhide') %>
diff --git a/app/views/users/phone_setup/index.html.erb b/app/views/users/phone_setup/index.html.erb
index 26db6c61c61..cb8091f16fd 100644
--- a/app/views/users/phone_setup/index.html.erb
+++ b/app/views/users/phone_setup/index.html.erb
@@ -40,5 +40,5 @@
<% end %>
<%= render PageFooterComponent.new do %>
- <%= link_to t('two_factor_authentication.choose_another_option'), two_factor_options_path %>
+ <%= link_to t('two_factor_authentication.choose_another_option'), authentication_methods_setup_path %>
<% end %>
diff --git a/app/views/users/piv_cac_authentication_setup/error.html.erb b/app/views/users/piv_cac_authentication_setup/error.html.erb
index 1854d359069..4556e2298f7 100644
--- a/app/views/users/piv_cac_authentication_setup/error.html.erb
+++ b/app/views/users/piv_cac_authentication_setup/error.html.erb
@@ -12,6 +12,6 @@
<% if MfaPolicy.new(current_user).two_factor_enabled? %>
<%= link_to t('links.cancel'), account_path %>
<% else %>
- <%= link_to t('two_factor_authentication.choose_another_option'), two_factor_options_path %>
+ <%= link_to t('two_factor_authentication.choose_another_option'), authentication_methods_setup_path %>
<% end %>
<% end %>
diff --git a/app/views/users/two_factor_authentication_setup/index.html.erb b/app/views/users/two_factor_authentication_setup/index.html.erb
index 09efca23017..a77bcfc27f0 100644
--- a/app/views/users/two_factor_authentication_setup/index.html.erb
+++ b/app/views/users/two_factor_authentication_setup/index.html.erb
@@ -17,7 +17,7 @@
<%= validated_form_for @two_factor_options_form,
html: { autocomplete: 'off' },
method: :patch,
- url: two_factor_options_path do |f| %>
+ url: authentication_methods_setup_path do |f| %>