You may %{link} or close
- this window if you're done.
+ email confirmation of the account deletion.
link_text: create a new account
title: You have deleted your account
confirm_request:
@@ -20,7 +25,7 @@ en:
delete_button: Delete account
info: Deleting your account should be your last resort if you are locked out
of your account. You will not be able to recover any information linked to
- your account. Once your account is deleted, you can create a new one using
+ your account. Once your account is deleted, you can create a new one using
the same email address.
title: Deleting your account should be your last resort
request:
diff --git a/config/locales/account_reset/es.yml b/config/locales/account_reset/es.yml
index e4344e0de0c..ec756dff785 100644
--- a/config/locales/account_reset/es.yml
+++ b/config/locales/account_reset/es.yml
@@ -1,10 +1,15 @@
---
es:
account_reset:
+ cancel_request:
+ are_you_sure: "¿Seguro que quieres cancelar tu solicitud de eliminación de cuenta?"
+ cancel: Salida
+ cancel_button: Cancelar la cuenta eliminada
+ title: Cancelar la cuenta eliminada
confirm_delete_account:
+ cta: Puede %{link} o cierra esta ventana si ya terminaste.
info: La cuenta para %{email} ha sido eliminada. Nosotros enviamos
- una confirmación por correo electrónico de la eliminación de la cuenta.
- Puede %{link} o cierra esta ventana si ya terminaste.
+ una confirmación por correo electrónico de la eliminación de la cuenta.
link_text: crea una cuenta nueva
title: Has eliminado tu cuenta
confirm_request:
diff --git a/config/locales/account_reset/fr.yml b/config/locales/account_reset/fr.yml
index 089b9e588df..bbd0758b6d5 100644
--- a/config/locales/account_reset/fr.yml
+++ b/config/locales/account_reset/fr.yml
@@ -1,10 +1,16 @@
---
fr:
account_reset:
+ cancel_request:
+ are_you_sure: Êtes-vous sûr de vouloir annuler votre demande de suppression
+ de compte?
+ cancel: Sortie
+ cancel_button: Annuler supprimer un compte
+ title: Annuler supprimer un compte
confirm_delete_account:
+ cta: Vous pouvez %{link} ou fermer cette fenêtre si vous avez terminé.
info: Le compte pour %{email} a été supprimé. Nous avons envoyé
- un email de confirmation de la suppression du compte.
Vous pouvez
- %{link} ou fermer cette fenêtre si vous avez terminé.
+ un email de confirmation de la suppression du compte.
link_text: créer un nouveau compte
title: Vous avez supprimé votre compte
confirm_request:
diff --git a/config/locales/devise/en.yml b/config/locales/devise/en.yml
index 2ed967d24f9..c58594198d7 100644
--- a/config/locales/devise/en.yml
+++ b/config/locales/devise/en.yml
@@ -69,70 +69,3 @@ en:
sessions:
signed_in: ''
signed_out: You are now signed out.
- two_factor_authentication:
- account_reset:
- cancel_link: Cancel your request
- link: deleting your account
- pending_html: You currently have a pending request to delete your account.
- It takes 24 hours from the time you made the request to complete the process.
- Please check back later. %{cancel_link}
- successful_cancel: Thank you. Your request to delete your login.gov account
- has been cancelled.
- text_html: If you can't use any of these security options above, you can reset
- your preferences by %{link}.
- header_text: Enter your security code
- invalid_otp: That security code is invalid. You can try entering it again or
- request a new one-time security code.
- invalid_personal_key: That personal key is invalid.
- invalid_piv_cac: That PIV/CAC is incorrect.
- max_generic_login_attempts_reached: For your security, your account is temporarily
- locked.
- max_otp_login_attempts_reached: For your security, your account is temporarily
- locked because you have entered the one-time security code incorrectly too
- many times.
- max_otp_requests_reached: For your security, your account is temporarily locked
- because you have requested a security code too many times.
- max_personal_key_login_attempts_reached: For your security, your account is
- temporarily locked because you have entered the personal key incorrectly too
- many times.
- max_piv_cac_login_attempts_reached: For your security, your account is temporarily
- locked because you have presented your piv/cac credential incorrectly too
- many times.
- otp_delivery_preference:
- instruction: You can change this selection the next time you log in. If you
- entered a landline, please select "Phone call" below.
- phone_unsupported: We're unable to make phone calls to people in %{location}
- at this time.
- sms: Text message (SMS)
- title: How should we send you a code?
- voice: Phone call
- personal_key_header_text: Enter your personal key
- personal_key_prompt: You can use this personal key once. After you enter it,
- you'll be provided a new key.
- phone_sms_info_html: We'll text a security code each time you sign in.
- phone_sms_label: Mobile phone number
- phone_voice_info_html: We'll call you with a security code each time
- you sign in.
- phone_voice_label: Phone number
- piv_cac_fallback:
- link: Use your PIV/CAC instead
- text_html: Do you have your PIV/CAC? %{link}
- piv_cac_header_text: Present your PIV/CAC
- please_try_again_html: Please try again in %{time_remaining}.
- read_about_two_factor_authentication:
- link: read about two-factor authentication
- text_html: You can %{link} and why we use it at our Help page.
- totp_header_text: Enter your authentication app code
- two_factor_choice: Secure your account
- two_factor_choice_intro: login.gov makes sure you can access your account by
- adding a second layer of security.
- two_factor_choice_options:
- auth_app: Authentication application
- auth_app_info: Set up an authentication application to get your security code
- without providing a phone number
- piv_cac: Government employees
- piv_cac_info: Use your PIV/CAC card to secure your account
- sms: Text message / SMS
- sms_info: Get your security code via text message / SMS
- voice: Phone call
- voice_info: Get your security code via phone call
diff --git a/config/locales/devise/es.yml b/config/locales/devise/es.yml
index 005161c8b43..07348c31d70 100644
--- a/config/locales/devise/es.yml
+++ b/config/locales/devise/es.yml
@@ -71,67 +71,3 @@ es:
sessions:
signed_in: ''
signed_out: Su sesión ha terminado ahora.
- two_factor_authentication:
- account_reset:
- cancel_link: Cancelar su solicitud
- link: eliminando su cuenta
- pending_html: Actualmente tiene una solicitud pendiente para eliminar su cuenta.
- Se necesitan 24 horas desde el momento en que realizó la solicitud para
- completar el proceso. Por favor, vuelva más tarde. %{cancel_link}
- successful_cancel: Gracias. Su solicitud para eliminar su cuenta de login.gov
- ha sido cancelada.
- text_html: Si no puede usar ninguna de estas opciones de seguridad anteriores,
- puede restablecer tus preferencias por %{link}.
- header_text: Ingrese su código de seguridad
- invalid_otp: Ese código de seguridad no es válido. Puede intentar ingresarlo
- de nuevo o solicitar un nuevo código de seguridad de sólo un uso.
- invalid_personal_key: Esa clave personal no es válida.
- invalid_piv_cac: NOT TRANSLATED YET
- max_generic_login_attempts_reached: Para su seguridad, su cuenta está bloqueada
- temporalmente.
- max_otp_login_attempts_reached: Para su seguridad, su cuenta ha sido bloqueada
- temporalmente porque ha ingresado incorrectamente el código de seguridad de
- sólo un uso demasiadas veces.
- max_otp_requests_reached: Para su seguridad, su cuenta ha sido bloqueada temporalmente
- porque ha solicitado un código de seguridad demasiadas veces más de lo permitido.
- max_personal_key_login_attempts_reached: Para su seguridad, su cuenta ha sido
- bloqueada temporalmente porque ha ingresado incorrectamente la clave personal
- demasiadas veces.
- max_piv_cac_login_attempts_reached: NOT TRANSLATED YET
- otp_delivery_preference:
- instruction: Puede cambiar esta selección la próxima vez que inicie sesión.
- phone_unsupported: NOT TRANSLATED YET
- sms: Mensaje de texto (SMS, sigla en inglés)
- title: "¿Cómo deberíamos enviarle un código?"
- voice: Llamada telefónica
- personal_key_header_text: Ingrese su clave personal
- personal_key_prompt: Puede usar esta clave personal una vez. Después de ingresarlo,
- se le dará una nueva clave.
- phone_sms_info_html: Le enviaremos un mensaje de texto con un código de seguridad
- cada vez que inicie sesión.
- phone_sms_label: Número de teléfono móvil
- phone_voice_info_html: Te llamaremos con un código de seguridad cada
- vez que inicies sesión.
- phone_voice_label: Número de teléfono
- piv_cac_fallback:
- link: Use su PIV/CAC en su lugar
- text_html: "¿Tiene usted PIV/CAC? %{link}"
- piv_cac_header_text: NOT TRANSLATED YET
- please_try_again_html: Inténtelo de nuevo en %{time_remaining}.
- read_about_two_factor_authentication:
- link: leer acerca de la autenticación de dos factores
- text_html: Puede %{link} y por qué la utilizamos en nuestra página de Ayuda.
- totp_header_text: Ingrese su código de la app de autenticación
- two_factor_choice: Asegure su cuenta
- two_factor_choice_intro: login.gov se asegura de que pueda acceder a su cuenta
- agregando una segunda capa de seguridad.
- two_factor_choice_options:
- auth_app: Aplicación de autenticación
- auth_app_info: Configure una aplicación de autenticación para obtener su código
- de seguridad sin proporcionar un número de teléfono
- piv_cac: Empleados del Gobierno
- piv_cac_info: Use su tarjeta PIV / CAC para asegurar su cuenta
- sms: Mensaje de texto / SMS
- sms_info: Obtenga su código de seguridad a través de mensajes de texto / SMS
- voice: Llamada telefónica
- voice_info: Obtenga su código de seguridad a través de una llamada telefónica
diff --git a/config/locales/devise/fr.yml b/config/locales/devise/fr.yml
index 6e186b1d2eb..c67096c17f5 100644
--- a/config/locales/devise/fr.yml
+++ b/config/locales/devise/fr.yml
@@ -77,70 +77,3 @@ fr:
sessions:
signed_in: ''
signed_out: Vous êtes maintenant déconnecté(e).
- two_factor_authentication:
- account_reset:
- cancel_link: Annuler votre demande
- link: supprimer votre compte
- pending_html: Vous avez actuellement une demande en attente pour supprimer
- votre compte. Il faut compter 24 heures à partir du moment où vous avez
- fait la demande pour terminer le processus. Veuillez vérifier plus tard.
- %{cancel_link}
- successful_cancel: Je vous remercie. Votre demande de suppression de votre
- compte login.gov a été annulée.
- text_html: Si vous ne pouvez pas utiliser l'une de ces options de sécurité
- ci-dessus, vous pouvez réinitialiser vos préférences par %{link}.
- header_text: Entrez votre code de sécurité
- invalid_otp: Ce code de sécurité est non valide. Vous pouvez essayer de l'entrer
- de nouveau ou demander un nouveau code de sécurité à utilisation unique.
- invalid_personal_key: Cette clé personnelle est non valide.
- invalid_piv_cac: NOT TRANSLATED YET
- max_generic_login_attempts_reached: Pour votre sécurité, votre compte est temporairement
- verrouillé.
- max_otp_login_attempts_reached: Pour votre sécurité, votre compte est temporairement
- verrouillé, car vous avez entré le code de sécurité à utilisation unique de
- façon erronée à de trop nombreuses reprises.
- max_otp_requests_reached: Pour votre sécurité, votre compte est temporairement
- verrouillé car vous avez demandé un code de sécurité à trop de reprises.
- max_personal_key_login_attempts_reached: Pour votre sécurité, votre compte est
- temporairement verrouillé, car vous avez entré le code de sécurité à utilisation
- unique de façon erronée à de trop nombreuses reprises.
- max_piv_cac_login_attempts_reached: NOT TRANSLATED YET
- otp_delivery_preference:
- instruction: Vous pouvez changer cette sélection la prochaine fois que vous
- vous connectez.
- phone_unsupported: NOT TRANSLATED YET
- sms: Message texte (SMS)
- title: Comment devrions-nous vous envoyer un code?
- voice: Appel téléphonique
- personal_key_header_text: Entrez votre clé personnelle
- personal_key_prompt: Vous pouvez utiliser cette clé personnelle une fois seulement.
- Une fois que vous l'entrez, vous recevrez une nouvelle clé.
- phone_sms_info_html: Nous vous enverrons un code de sécurité chaque
- fois que vous vous connectez.
- phone_sms_label: Numéro de téléphone portable
- phone_voice_info_html: Nous vous appellerons avec un code de sécurité chaque
- fois que vous vous connectez.
- phone_voice_label: Numéro de téléphone
- piv_cac_fallback:
- link: Utilisez plutôt votre PIV/CAC
- text_html: Avez-vous votre PIV/CAC? %{link}
- piv_cac_header_text: NOT TRANSLATED YET
- please_try_again_html: Veuillez essayer de nouveau dans %{time_remaining}.
- read_about_two_factor_authentication:
- link: lire sur l'authentification à deux facteurs
- text_html: Vous pouvez %{link} et pourquoi nous l'utilisons sur notre page
- d'aide.
- totp_header_text: Entrez votre code d'application d'authentification
- two_factor_choice: Sécurise ton compte
- two_factor_choice_intro: login.gov s'assure que vous pouvez accéder à votre
- compte en ajoutant une deuxième couche de sécurité.
- two_factor_choice_options:
- auth_app: Application d'authentification
- auth_app_info: Configurez une application d'authentification pour obtenir
- votre code de sécurité sans fournir de numéro de téléphone
- piv_cac: Employés du gouvernement
- piv_cac_info: Utilisez votre carte PIV / CAC pour sécuriser votre compte
- sms: SMS
- sms_info: Obtenez votre code de sécurité par SMS
- voice: Appel téléphonique
- voice_info: Obtenez votre code de sécurité par appel téléphonique
diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml
new file mode 100644
index 00000000000..f042646b799
--- /dev/null
+++ b/config/locales/doc_auth/en.yml
@@ -0,0 +1,27 @@
+---
+en:
+ doc_auth:
+ buttons:
+ capture: Capture
+ start_over: Start over
+ errors:
+ selfie: Sorry, we are unable to match your picture, please try again.
+ state_id_fail: Sorry. Information from your uploaded state-issued ID does not
+ match information for your social security number.
+ forms:
+ dob: Date of Birth
+ doc_success: We've verified your social security number and state-issued ID.
+ entered_info: 'Information you entered:'
+ first_name: First Name
+ last_name: Last Name
+ selfie_next: Next, we'll need to take your picture.
+ ssn: Social Security Number
+ state_id_info: 'Information from your uploaded state-issued ID:'
+ headings:
+ selfie: Take a selfie!
+ ssn: To verify your identity, you'll need your social security number and state-issued
+ ID.
+ upload_back: Please upload a photo of the back of your state-issued ID.
+ upload_front: Please upload a photo of the front of your state-issued ID.
+ titles:
+ doc_auth: Document Authentication
diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml
new file mode 100644
index 00000000000..e25000ca40d
--- /dev/null
+++ b/config/locales/doc_auth/es.yml
@@ -0,0 +1,29 @@
+---
+es:
+ doc_auth:
+ buttons:
+ capture: Capturar
+ start_over: Comenzar de nuevo
+ errors:
+ selfie: Lo sentimos, no podemos hacer coincidir su imagen, intente de nuevo.
+ state_id_fail: Lo siento. La información de su ID emitida por el estado no coincide
+ con la información de su número de seguro social.
+ forms:
+ dob: Fecha de nacimiento
+ doc_success: Verificamos su número de seguro social y su identificación emitida
+ por el estado.
+ entered_info: 'Información que ingresó:'
+ first_name: Nombre de pila
+ last_name: Apellido
+ selfie_next: A continuación, necesitaremos tomar su foto.
+ ssn: Número de seguridad social
+ state_id_info: 'Información de su ID emitida por el estado:'
+ headings:
+ selfie: "¡Toma una selfie!"
+ ssn: Para verificar su identidad, necesitará su número de seguro social y su
+ identificación emitida por el estado.
+ upload_back: Cargue una foto del dorso de su identificación emitida por el estado.
+ upload_front: Cargue una foto del frente de su identificación emitida por el
+ estado.
+ titles:
+ doc_auth: Autenticación de documentos
diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml
new file mode 100644
index 00000000000..dae08a2b117
--- /dev/null
+++ b/config/locales/doc_auth/fr.yml
@@ -0,0 +1,31 @@
+---
+fr:
+ doc_auth:
+ buttons:
+ capture: Capturer
+ start_over: Recommencer
+ errors:
+ selfie: Désolé, nous ne pouvons pas correspondre à votre photo, veuillez réessayer.
+ state_id_fail: Pardon. Les informations de votre identifiant émis par l'état
+ téléchargé ne correspondent pas aux informations de votre numéro de sécurité
+ sociale.
+ forms:
+ dob: Date de naissance
+ doc_success: Nous avons vérifié votre numéro de sécurité sociale et votre identifiant
+ délivré par l'État.
+ entered_info: 'Informations que vous avez entrées:'
+ first_name: Prénom
+ last_name: Nom de famille
+ selfie_next: Ensuite, nous devrons prendre votre photo.
+ ssn: Numéro de sécurité sociale
+ state_id_info: 'Information from your uploaded state-issued ID:'
+ headings:
+ selfie: Prendre un selfie!
+ ssn: Pour vérifier votre identité, vous aurez besoin de votre numéro de sécurité
+ sociale et de votre identifiant délivré par l'État.
+ upload_back: S'il vous plaît télécharger une photo du dos de votre ID émis par
+ l'état.
+ upload_front: Veuillez télécharger une photo du recto de votre identifiant émis
+ par l'État.
+ titles:
+ doc_auth: Authentification de document
diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml
index aab2f394d27..6a7b966143d 100644
--- a/config/locales/errors/en.yml
+++ b/config/locales/errors/en.yml
@@ -27,8 +27,8 @@ en:
format_mismatch: Please match the requested format.
improbable_phone: Invalid phone number. Please make sure you enter a valid phone
number.
- invalid_calling_area: Calls to that phone number are not supported. Please
- try SMS if you have an SMS-capable phone.
+ invalid_calling_area: Calls to that phone number are not supported. Please try
+ SMS if you have an SMS-capable phone.
invalid_phone_number: The phone number entered is not valid.
invalid_sms_number: The phone number entered doesn't support text messaging.
Try the Phone call option.
@@ -53,6 +53,6 @@ en:
weak_password: Your password is not strong enough. %{feedback}
webauthn_setup:
delete_last: Sorry, you can not remove your last MFA option.
- general_error: There was an error adding your hardward security key. Please
+ general_error: There was an error adding your hardware security key. Please
try again.
- unique_name: That name is already taken. Please choose a different name.
+ unique_name: That name is already taken. Please choose a different name.
diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml
index 5de9f9da324..377194462f9 100644
--- a/config/locales/errors/fr.yml
+++ b/config/locales/errors/fr.yml
@@ -51,5 +51,5 @@ fr:
webauthn_setup:
delete_last: Désolé, vous ne pouvez pas supprimer votre dernière option MFA
general_error: Une erreur s'est produite lors de l'ajout de votre clé de sécurité
- matérielle. Veuillez réessayer.
+ physique. Veuillez réessayer.
unique_name: Ce nom est déjà pris. Veuillez choisir un autre nom.
diff --git a/config/locales/forms/fr.yml b/config/locales/forms/fr.yml
index 2e55658e0c5..d7bbbe966dd 100644
--- a/config/locales/forms/fr.yml
+++ b/config/locales/forms/fr.yml
@@ -90,7 +90,7 @@ fr:
title: Confirmez votre compte
webauthn_setup:
intro_html: Lorsque vous vous connectez, vous pouvez utiliser votre clé de sécurité
- matérielle. %{link}
+ physique. %{link}
step_1: Insérez votre clé de sécurité dans le port USB de votre ordinateur ou
connectez-le avec un câble USB.
step_2: Une fois connecté, appuyez sur le bouton ou le disque d'or si votre
diff --git a/config/locales/headings/en.yml b/config/locales/headings/en.yml
index 5f89dd03125..6d76ba17319 100644
--- a/config/locales/headings/en.yml
+++ b/config/locales/headings/en.yml
@@ -30,7 +30,7 @@ en:
change: Change your password
confirm: Confirm your current password to continue
forgot: Forgot your password?
- personal_key: Store your personal key
+ personal_key: Always have access to your account with your personal key
piv_cac_setup:
certificate:
bad: The certificate you selected is invalid.
diff --git a/config/locales/headings/es.yml b/config/locales/headings/es.yml
index a6d77419541..5829a932541 100644
--- a/config/locales/headings/es.yml
+++ b/config/locales/headings/es.yml
@@ -30,7 +30,7 @@ es:
change: Cambie su contraseña
confirm: Confirme la contraseña actual para continuar
forgot: "¿Olvidó su contraseña?"
- personal_key: Aquí está su clave personal
+ personal_key: Siempre tenga acceso a su cuenta con su clave personal
piv_cac_setup:
certificate:
bad: El certificado que seleccionaste no es válido.
diff --git a/config/locales/headings/fr.yml b/config/locales/headings/fr.yml
index cd236014104..df54de5f284 100644
--- a/config/locales/headings/fr.yml
+++ b/config/locales/headings/fr.yml
@@ -30,7 +30,7 @@ fr:
change: Changez votre mot de passe
confirm: Confirmez votre mot de passe actuel pour continuer
forgot: Vous avez oublié votre mot de passe?
- personal_key: Voici votre clé personnelle
+ personal_key: Ayez toujours accès à votre compte avec votre clé personnelle
piv_cac_setup:
certificate:
bad: Le certificat que vous avez sélectionné n'est pas valide.
@@ -55,4 +55,4 @@ fr:
new: Activer une application d'authentification
verify_email: Consultez vos courriels
webauthn_setup:
- new: Enregistrez votre clé de sécurité matérielle
+ new: Enregistrez votre clé de sécurité physique
diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml
index 688c82967ae..0c33593ef1c 100644
--- a/config/locales/idv/en.yml
+++ b/config/locales/idv/en.yml
@@ -11,7 +11,6 @@ en:
send_confirmation_code: Continue
cancel:
modal_header: Are you sure you want to cancel?
- return_to_account: Return to account
warning_header: If you cancel now
warnings:
warning_1: We won't be able to verify your identity
@@ -29,9 +28,11 @@ en:
dob: Your date of birth must be entered in as mm/dd/yyyy
personal_key: 'Please enter your personal key for this account. Example: ABC1-DEF2-G3HI-J456'
ssn: 'Your Social Security Number must be entered in as ###-##-####'
+ state_id_number: Your ID number cannot be more than 25 characters.
zipcode: 'Your zipcode must be entered in as #####-####'
unsupported_jurisdiction: Sorry, we can't verify people from this state.
unsupported_jurisdiction_sp: Please visit %{sp_name} to access your account.
+ unsupported_otp_delivery_method: Select a method to receive a code.
failure:
attempts:
one: You have 1 attempt remaining.
@@ -122,7 +123,6 @@ en:
where: Where was your driver's license, driver's permit, or state ID issued?
why: To verify your identity, you'll need information from your state-issued
ID.
- loading: Verifying your identity
mail_sent: Your letter is on its way
otp_delivery_method:
phone_number_html: We'll send a code to %{phone}
@@ -130,9 +130,11 @@ en:
safe place. You will need it if you ever lose your password.
phone:
alert: This phone number must be
+ description: We're checking records to make sure you are who you say you are,
+ and that you have ownership of your account.
phone_of_record: Phone of record
rules:
- - in your name, or a family member's name
+ - on a phone plan with your name on it
- not a virtual phone (such as Google Voice or Skype)
- a U.S. number
return_to_profile: "‹ Return to your login.gov profile"
diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml
index 2647bec0619..549c8c4f43c 100644
--- a/config/locales/idv/es.yml
+++ b/config/locales/idv/es.yml
@@ -11,7 +11,6 @@ es:
send_confirmation_code: NOT TRANSLATED YET
cancel:
modal_header: "¿Está seguro que desea cancelar?"
- return_to_account: NOT TRANSLATED YET
warning_header: Si usted cancela ahora
warnings:
warning_1: NOT TRANSLATED YET
@@ -28,10 +27,12 @@ es:
dob: Su fecha de nacimiento debe ser ingresada en este formato mes/día/año.
personal_key: 'Introduzca su clave personal para esta cuenta. Ejemplo: ABC1-DEF2-G3HI-J456'
ssn: 'Su número de Seguro Social debe ser ingresado como ### - ## - ####'
+ state_id_number: Su número de ID no puede tener más de 25 caracteres
zipcode: 'Su código postal debe ser ingresado como #####-####'
unsupported_jurisdiction: Lo sentimos, no podemos verificar personas de este
estado.
unsupported_jurisdiction_sp: Visita %{sp_name} para acceder a tu cuenta.
+ unsupported_otp_delivery_method: Seleccione una manera de recibir un código.
failure:
attempts:
one: Tiene usted 1 intento restante. strong>
@@ -121,7 +122,6 @@ es:
del estado?"
why: Para verificar su identidad, necesitará información de su identificación
emitida por el estado.
- loading: NOT TRANSLATED YET
mail_sent: Su carta está en camino
otp_delivery_method:
phone_number_html: NOT TRANSLATED YET
@@ -129,9 +129,11 @@ es:
seguro. La necesitará si pierde su contraseña.
phone:
alert: Este número de teléfono debe ser
+ description: Verificamos los registros para asegurarnos de que eres quien
+ eres, y de que eres el propietario de tu cuenta.
phone_of_record: Teléfono del registro
rules:
- - a su nombre o el nombre de un miembro de familia
+ - en un plan de teléfono con su nombre en él
- no es un teléfono virtual (como Google Voice o Skype)
- no es un número de teléfono prepago
- un número de EE. UU.
diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml
index 3e3aa266df2..269cb238c58 100644
--- a/config/locales/idv/fr.yml
+++ b/config/locales/idv/fr.yml
@@ -11,7 +11,6 @@ fr:
send_confirmation_code: NOT TRANSLATED YET
cancel:
modal_header: Souhaitez-vous vraiment annuler?
- return_to_account: NOT TRANSLATED YET
warning_header: Si vous annulez maintenant
warnings:
warning_1: NOT TRANSLATED YET
@@ -31,11 +30,13 @@ fr:
exemple : ABC1-DEF2-G3HI-J456'
ssn: 'Votre numéro de sécurité sociale doit être inscrit de cette façon :
###-##-####'
+ state_id_number: Votre numéro d'identification ne peut excéder 25 caractères
zipcode: 'Votre code ZIP doit être inscrit de cette façon : #####-####'
unsupported_jurisdiction: Désolé, nous ne pouvons pas vérifier les personnes
de cet état.
unsupported_jurisdiction_sp: Veuillez visiter %{sp_name} pour accéder à votre
compte.
+ unsupported_otp_delivery_method: Sélectionnez une méthode pour recevoir un code.
failure:
attempts:
one: Il ne vous reste qu' strongune tentative./strong
@@ -127,7 +128,6 @@ fr:
ou votre carte d'identité?
why: Pour vérifier votre identité, vous aurez besoin d'informations provenant
de votre carte d'identité officielle.
- loading: NOT TRANSLATED YET
mail_sent: Votre lettre est en route
otp_delivery_method:
phone_number_html: NOT TRANSLATED YET
@@ -136,9 +136,11 @@ fr:
de passe.
phone:
alert: Ce numéro de téléphone doit être
+ description: Nous vérifions les dossiers pour vous assurer que vous êtes ce
+ que vous dites et que vous êtes propriétaire de votre compte.
phone_of_record: numéro de téléphone enregistré
rules:
- - en votre nom ou celui d'un membre de votre famille
+ - sur un plan de téléphone avec votre nom dessus
- pas un téléphone virtuel (comme Google Voice ou Skype)
- pas un numéro de téléphone prépayé
- un numéro américain
diff --git a/config/locales/instructions/en.yml b/config/locales/instructions/en.yml
index d9be77b1ae4..bd072e01754 100644
--- a/config/locales/instructions/en.yml
+++ b/config/locales/instructions/en.yml
@@ -36,6 +36,9 @@ en:
voice:
confirm_code_html: Want us to call you again? %{resend_code_link}
number_message: We just called you at %{number}.
+ webauthn:
+ confirm_webauthn_html: Present the hardware security key that you associated
+ with your account.
wrong_number_html: Entered the wrong phone number? %{link}
password:
forgot: Don’t know your password? Reset it after confirming your email address.
@@ -53,6 +56,11 @@ en:
intro: 'Password strength: '
iv: Good
v: Great!
- personal_key_accent: Write it down or print it out.
- personal_key_html: This is the only way to regain access to your account if you
- lose the phone where we send your security code. %{accent}
+ personal_key:
+ accent: Write it down or print it out.
+ info_html: This is the only way to regain access to your account if you lose
+ your phone or security options. %{accent}
+ once_html: "Personal keys are one-time use. If you need to
+ use your personal key for any reason, you'll be issued a new key."
+ safety_html: "Keep it private and safe. Don't share your personal
+ key, and only use it when you do not have access to your normal sign-in methods."
diff --git a/config/locales/instructions/es.yml b/config/locales/instructions/es.yml
index e50e7460309..6dd8e41aa07 100644
--- a/config/locales/instructions/es.yml
+++ b/config/locales/instructions/es.yml
@@ -37,6 +37,9 @@ es:
voice:
confirm_code_html: "¿Desea que le llamemos de nuevo? %{resend_code_link}"
number_message: Acabamos de llamarte en %{number}.
+ webauthn:
+ confirm_webauthn_html: Presente la clave de seguridad de hardware que ha asociado
+ con su cuenta.
wrong_number_html: "¿Ingresó el número de teléfono equivocado? %{link}"
password:
forgot: "¿No sabe su contraseña? Restablézcala después de confirmar su email."
@@ -54,6 +57,12 @@ es:
intro: 'Seguridad de la contraseña:'
iv: Buena
v: "¡Muy buena!"
- personal_key_accent: Anótelo o imprímalo.
- personal_key_html: Esta es la única manera de recuperar el acceso a su cuenta
- si pierde el teléfono donde enviamos su código de seguridad. %{accent}
+ personal_key:
+ accent: Anótelo o imprímalo.
+ info_html: Esta es la única manera de recuperar el acceso a su cuenta si pierde
+ su teléfono o las opciones de seguridad. %{accent}
+ once_html: "Las claves personales son de un solo uso. Si necesita
+ utilizar su clave personal por algún motivo, se le emitirá una nueva clave."
+ safety_html: "Manténgalo privado y seguro. No comparta su clave
+ personal, y solo úsela cuando no tenga acceso a sus métodos normales de inicio
+ de sesión."
diff --git a/config/locales/instructions/fr.yml b/config/locales/instructions/fr.yml
index 8d3264ab284..454c9d802c7 100644
--- a/config/locales/instructions/fr.yml
+++ b/config/locales/instructions/fr.yml
@@ -39,6 +39,9 @@ fr:
voice:
confirm_code_html: Vous voulez que nous vous appelions de nouveau? %{resend_code_link}
number_message: Nous venons de vous appeler à %{number}.
+ webauthn:
+ confirm_webauthn_html: Présentez la clé de sécurité physique associée à votre
+ compte.
wrong_number_html: Vous avez entré un mauvais numéro de téléphone? %{link}
password:
forgot: Vous ne connaissez pas votre mot de passe? Réinitialisez-le après avoir
@@ -58,7 +61,13 @@ fr:
intro: 'Force du mot de passe : '
iv: Bonne
v: Excellente!
- personal_key_accent: Notez-la ou imprimez-la.
- personal_key_html: Il s'agit de la seule façon de récupérer l'accès à votre compte
- si vous perdez le téléphone sur lequel nous envoyons votre code de sécurité.
- %{accent}
+ personal_key:
+ accent: Notez-la ou imprimez-la.
+ info_html: C'est le seul moyen de retrouver l'accès à votre compte si vous perdez
+ votre téléphone ou vos options de sécurité. %{accent}
+ once_html: "Les clés personnelles sont à usage unique. Si vous
+ devez utiliser votre clé personnelle pour quelque raison que ce soit, vous
+ recevrez une nouvelle clé."
+ safety_html: "Protégez-vous en privé. Ne partagez pas votre
+ clé personnelle et ne l'utilisez que lorsque vous n'avez pas accès à vos méthodes
+ de connexion habituelles."
diff --git a/config/locales/links/en.yml b/config/locales/links/en.yml
index 4d334021e03..49f65c605bb 100644
--- a/config/locales/links/en.yml
+++ b/config/locales/links/en.yml
@@ -14,6 +14,7 @@ en:
create_account: Create account
go_back: Go back
help: Help
+ my_account: my account
next: Next
passwords:
forgot: Forgot your password?
diff --git a/config/locales/links/es.yml b/config/locales/links/es.yml
index 6bb67395759..70297bd60dc 100644
--- a/config/locales/links/es.yml
+++ b/config/locales/links/es.yml
@@ -14,6 +14,7 @@ es:
create_account: Crear cuenta
go_back: Regresa
help: Ayuda
+ my_account: mi cuenta
next: Siguiente
passwords:
forgot: "¿Olvidó su contraseña?"
diff --git a/config/locales/links/fr.yml b/config/locales/links/fr.yml
index 274cd199b08..769acfe5e98 100644
--- a/config/locales/links/fr.yml
+++ b/config/locales/links/fr.yml
@@ -14,6 +14,7 @@ fr:
create_account: Créer un compte
go_back: Retourner
help: Aide
+ my_account: mon compte
next: Suivant
passwords:
forgot: Vous avez oublié votre mot de passe?
@@ -26,4 +27,4 @@ fr:
app_option: Utilisez une application d'authentification à la place.
get_another_code: Obtenir un autre code
what_is_totp: Qu'est-ce qu'une application d'authentification?
- what_is_webauthn: Qu'est-ce qu'une clé de sécurité matérielle?
+ what_is_webauthn: Qu'est-ce qu'une clé de sécurité physique?
diff --git a/config/locales/notices/en.yml b/config/locales/notices/en.yml
index 72abeff218c..fbc5a3728f9 100644
--- a/config/locales/notices/en.yml
+++ b/config/locales/notices/en.yml
@@ -18,8 +18,6 @@ en:
piv_cac_disabled: PIV/CAC card unlinked successfully.
resend_confirmation_email:
success: We sent another confirmation email.
- send_code:
- personal_key: You have a new personal key.
session_cleared: For your security, we clear what you entered if you don't move
to a new page within %{minutes} minutes.
signed_up_but_unconfirmed:
diff --git a/config/locales/notices/es.yml b/config/locales/notices/es.yml
index d19462fb9ba..f207da9bb60 100644
--- a/config/locales/notices/es.yml
+++ b/config/locales/notices/es.yml
@@ -18,8 +18,6 @@ es:
piv_cac_disabled: Tarjeta PIV/CAC desvinculada con éxito.
resend_confirmation_email:
success: Enviamos otro email de confirmación.
- send_code:
- personal_key: Tiene una nueva clave personal.
session_cleared: Para su seguridad, borramos lo que ingresó si no pasa a una página
nueva dentro de %{minutes} minutos.
signed_up_but_unconfirmed:
diff --git a/config/locales/notices/fr.yml b/config/locales/notices/fr.yml
index 3a39856f34b..bb495f0f119 100644
--- a/config/locales/notices/fr.yml
+++ b/config/locales/notices/fr.yml
@@ -19,8 +19,6 @@ fr:
piv_cac_disabled: Carte PIV/CAC dissociée avec succès.
resend_confirmation_email:
success: Nous avons envoyé un autre courriel de confirmation.
- send_code:
- personal_key: Vous avez une nouvelle clé personnelle.
session_cleared: Pour votre sécurité, nous effacerons l'information que vous avez
entrée si vous ne vous déplacez pas vers une nouvelle page dans les %{minutes}
prochaines minutes.
@@ -47,8 +45,8 @@ fr:
use_diff_email:
link: utilisez une adresse courriel différente
text_html: Or, %{link}
- webauthn_added: Vous avez ajouté une clé de sécurité matérielle.
- webauthn_deleted: Vous avez supprimé une clé de sécurité matérielle.
+ webauthn_added: Vous avez ajouté une clé de sécurité physique.
+ webauthn_deleted: Vous avez supprimé une clé de sécurité physique.
session_timedout: Nous vous avons déconnecté. Pour votre sécurité, %{app} désactive
votre session lorsque vous demeurez sur une page sans vous déplacer pendant %{minutes}
minutes.
diff --git a/config/locales/service_providers/en.yml b/config/locales/service_providers/en.yml
index 68aa3071517..a4c3c206366 100644
--- a/config/locales/service_providers/en.yml
+++ b/config/locales/service_providers/en.yml
@@ -1,6 +1,17 @@
---
en:
service_providers:
+ homes_mil:
+ account_page:
+ body: Your old HOMES.mil username and password won’t work. Please create a
+ login.gov account using the same email address you use for HOMES.mil.
+ body_html: Your old HOMES.mil username and password won’t work. Please %{link}
+ using the same email address you use for HOMES.mil.
+ create_account_link: create a login.gov account
+ create_account_page:
+ body: Please create a login.gov account using the same email address you use
+ for HOMES.mil
+ header: First time here from HOMES.mil?
learn_more: Learn more.
sam:
account_page:
diff --git a/config/locales/service_providers/es.yml b/config/locales/service_providers/es.yml
index abd83083fe2..fc9526d3893 100644
--- a/config/locales/service_providers/es.yml
+++ b/config/locales/service_providers/es.yml
@@ -1,6 +1,18 @@
---
es:
service_providers:
+ homes_mil:
+ account_page:
+ body: Si tiene un perfil de HOMES.mil existente, favor de usar la dirección
+ de correo electrónico primaria o secundaria que usó para HOMES.mil para
+ crear su nueva cuenta de login.gov.
+ body_html: Si tiene un perfil de HOMES.mil existente, favor de usar la dirección
+ de correo electrónico primaria o secundaria que usó para HOMES.mil para %{link}.
+ create_account_link: crear su nueva cuenta de login.gov
+ create_account_page:
+ body: Por favor crea un login.gov cuenta usando la misma dirección de correo
+ electrónico que utiliza para HOMES.mil.
+ header: "¿Ha venido de HOMES.mil?"
learn_more: Obtenga más información.
sam:
account_page:
diff --git a/config/locales/service_providers/fr.yml b/config/locales/service_providers/fr.yml
index 5c2fb500ef2..15a819eec50 100644
--- a/config/locales/service_providers/fr.yml
+++ b/config/locales/service_providers/fr.yml
@@ -1,6 +1,19 @@
---
fr:
service_providers:
+ homes_mil:
+ account_page:
+ body: Si vous avez déjà un profil HOMES.mil, veuillez utiliser l'adresse e-mail
+ principale ou secondaire que vous avez utilisée pour HOMES.mil pour créer
+ votre nouveau compte login.gov.
+ body_html: Si vous avez déjà un profil HOMES.mil, veuillez utiliser l'adresse
+ e-mail principale ou secondaire que vous avez utilisée pour HOMES.mil pour
+ %{link}.
+ create_account_link: créer votre nouveau compte login.gov
+ create_account_page:
+ body: Veuillez créer un compte login.gov avec la même adresse e-mail que vous
+ avez utilisée pour HOMES.mil.
+ header: Êtes-vous venu(e) de HOMES.mil?
learn_more: En savoir plus.
sam:
account_page:
diff --git a/config/locales/titles/en.yml b/config/locales/titles/en.yml
index 436844f9338..6e5379493ed 100644
--- a/config/locales/titles/en.yml
+++ b/config/locales/titles/en.yml
@@ -36,6 +36,7 @@ en:
invalid: PIV/CAC certificate error
missing: Internal error
present_piv_cac: Present your PIV/CAC
+ present_webauthn: Present your hardware security key
reactivate_account: Reactivate your account
registrations:
new: Sign up for a account
diff --git a/config/locales/titles/es.yml b/config/locales/titles/es.yml
index 2cae099f029..823e5f7ecc0 100644
--- a/config/locales/titles/es.yml
+++ b/config/locales/titles/es.yml
@@ -36,6 +36,7 @@ es:
invalid: NOT TRANSLATED YET
missing: NOT TRANSLATED YET
present_piv_cac: NOT TRANSLATED YET
+ present_webauthn: Presente su clave de seguridad de hardware
reactivate_account: Reactive su cuenta
registrations:
new: Regístrese para una cuenta
diff --git a/config/locales/titles/fr.yml b/config/locales/titles/fr.yml
index af068ced0b0..90e59d1377e 100644
--- a/config/locales/titles/fr.yml
+++ b/config/locales/titles/fr.yml
@@ -36,6 +36,7 @@ fr:
invalid: NOT TRANSLATED YET
missing: NOT TRANSLATED YET
present_piv_cac: NOT TRANSLATED YET
+ present_webauthn: Présentez votre clé de sécurité physique
reactivate_account: Réactiver le profil
registrations:
new: S'inscrire et créer un compte
diff --git a/config/locales/two_factor_authentication/en.yml b/config/locales/two_factor_authentication/en.yml
index e327f048f22..74e92186695 100644
--- a/config/locales/two_factor_authentication/en.yml
+++ b/config/locales/two_factor_authentication/en.yml
@@ -1,7 +1,22 @@
---
en:
two_factor_authentication:
+ account_reset:
+ cancel_link: Cancel your request
+ link: deleting your account
+ pending_html: You currently have a pending request to delete your account. It
+ takes 24 hours from the time you made the request to complete the process.
+ Please check back later. %{cancel_link}
+ successful_cancel: Thank you. Your request to delete your login.gov account
+ has been cancelled.
+ text_html: If you can't use any of these security options above, you can reset
+ your preferences by %{link}.
choose_another_option: "‹ Choose another option"
+ header_text: Enter your security code
+ invalid_otp: That security code is invalid. You can try entering it again or request
+ a new one-time security code.
+ invalid_personal_key: That personal key is invalid.
+ invalid_piv_cac: That PIV/CAC is incorrect.
login_intro: You set these up when you created your account
login_options:
auth_app: Authentication app
@@ -12,16 +27,74 @@ en:
piv_cac: Government employee ID
piv_cac_info: Use your PIV/CAC card instead of a security code.
sms: Text message
- sms_info: Get security code via text message.
+ sms_info_html: Get security code via text message to %{phone}.
+ sms_setup_info: Get security code via text message.
voice: Automated phone call
- voice_info: Get security code via phone call (North America phone numbers only).
+ voice_info_html: Get security code via phone call to %{phone}
+ (North America phone numbers only).
+ voice_setup_info: Get security code via phone call (North America phone numbers
+ only).
+ webauthn: Hardware security key
+ webauthn_info: Use your hardware security key instead of a security code.
login_options_link_text: Choose another security option
login_options_title: Select your security option
+ max_generic_login_attempts_reached: For your security, your account is temporarily
+ locked.
+ max_otp_login_attempts_reached: For your security, your account is temporarily
+ locked because you have entered the one-time security code incorrectly too many
+ times.
+ max_otp_requests_reached: For your security, your account is temporarily locked
+ because you have requested a security code too many times.
+ max_personal_key_login_attempts_reached: For your security, your account is temporarily
+ locked because you have entered the personal key incorrectly too many times.
+ max_piv_cac_login_attempts_reached: For your security, your account is temporarily
+ locked because you have presented your piv/cac credential incorrectly too many
+ times.
+ otp_delivery_preference:
+ instruction: You can change this selection the next time you log in. If you
+ entered a landline, please select "Phone call" below.
+ phone_unsupported: We're unable to make phone calls to people in %{location}
+ at this time.
+ sms: Text message (SMS)
+ title: How should we send you a code?
+ voice: Phone call
personal_key_fallback:
question: Don't have your personal key?
+ personal_key_header_text: Enter your personal key
+ personal_key_prompt: You can use this personal key once. After you enter it, you'll
+ be provided a new key.
phone_fallback:
question: Don't have access to your phone right now?
+ phone_sms_info_html: We'll text a security code each time you sign in.
+ phone_sms_label: Mobile phone number
+ phone_voice_info_html: We'll call you with a security code each time you
+ sign in.
+ phone_voice_label: Phone number
piv_cac_fallback:
question: Don't have your piv/cac card available?
+ piv_cac_header_text: Present your PIV/CAC
+ please_try_again_html: Please try again in %{time_remaining}.
+ read_about_two_factor_authentication:
+ link: read about two-factor authentication
+ text_html: You can %{link} and why we use it at our Help page.
totp_fallback:
question: Don't have your authenticator app?
+ totp_header_text: Enter your authentication app code
+ two_factor_choice: Secure your account
+ two_factor_choice_intro: login.gov makes sure you can access your account by adding
+ a second layer of security.
+ two_factor_choice_options:
+ auth_app: Authentication application
+ auth_app_info: Set up an authentication application to get your security code
+ without providing a phone number
+ piv_cac: Government employees
+ piv_cac_info: Use your PIV/CAC card to secure your account
+ sms: Text message / SMS
+ sms_info: Get your security code via text message / SMS
+ voice: Phone call
+ voice_info: Get your security code via phone call
+ webauthn: Hardware security key
+ webauthn_info: Use a hardware security key to secure your account
+ webauthn_fallback:
+ question: Don't have your hardware security key available?
+ webauthn_header_text: Present your hardware security key
diff --git a/config/locales/two_factor_authentication/es.yml b/config/locales/two_factor_authentication/es.yml
index 4af916b12e4..c48a5d2774a 100644
--- a/config/locales/two_factor_authentication/es.yml
+++ b/config/locales/two_factor_authentication/es.yml
@@ -1,7 +1,22 @@
---
es:
two_factor_authentication:
+ account_reset:
+ cancel_link: Cancelar su solicitud
+ link: eliminando su cuenta
+ pending_html: Actualmente tiene una solicitud pendiente para eliminar su cuenta.
+ Se necesitan 24 horas desde el momento en que realizó la solicitud para completar
+ el proceso. Por favor, vuelva más tarde. %{cancel_link}
+ successful_cancel: Gracias. Su solicitud para eliminar su cuenta de login.gov
+ ha sido cancelada.
+ text_html: Si no puede usar ninguna de estas opciones de seguridad anteriores,
+ puede restablecer tus preferencias por %{link}.
choose_another_option: "‹ Elige otra opción"
+ header_text: Ingrese su código de seguridad
+ invalid_otp: Ese código de seguridad no es válido. Puede intentar ingresarlo de
+ nuevo o solicitar un nuevo código de seguridad de sólo un uso.
+ invalid_personal_key: Esa clave personal no es válida.
+ invalid_piv_cac: NOT TRANSLATED YET
login_intro: Usted configuró esto cuando creó su cuenta
login_options:
auth_app: Aplicación de autenticación
@@ -13,17 +28,75 @@ es:
piv_cac: Empleados del Gobierno
piv_cac_info: Use su tarjeta PIV / CAC para asegurar su cuenta.
sms: Mensaje de texto / SMS
- sms_info: Obtenga su código de seguridad a través de mensajes de texto / SMS.
+ sms_info_html: Obtenga su código de seguridad a través de mensajes de texto
+ / SMS a %{phone}.
+ sms_setup_info: Obtenga su código de seguridad a través de mensajes de texto
+ / SMS.
voice: Llamada telefónica automatizada
- voice_info: Obtenga su código de seguridad a través de una llamada telefónica.
+ voice_info_html: Obtenga su código de seguridad a través de una llamada telefónica
+ a %{phone}. (Solo números de teléfono de América del Norte).
+ voice_setup_info: Obtenga su código de seguridad a través de una llamada telefónica.
(Solo números de teléfono de América del Norte).
+ webauthn: Clave de seguridad de hardware
+ webauthn_info: Use su clave de seguridad de hardware en lugar de un código de
+ seguridad.
login_options_link_text: Elige otra opción de seguridad
login_options_title: Seleccione su opción de seguridad
+ max_generic_login_attempts_reached: Para su seguridad, su cuenta está bloqueada
+ temporalmente.
+ max_otp_login_attempts_reached: Para su seguridad, su cuenta ha sido bloqueada
+ temporalmente porque ha ingresado incorrectamente el código de seguridad de
+ sólo un uso demasiadas veces.
+ max_otp_requests_reached: Para su seguridad, su cuenta ha sido bloqueada temporalmente
+ porque ha solicitado un código de seguridad demasiadas veces más de lo permitido.
+ max_personal_key_login_attempts_reached: Para su seguridad, su cuenta ha sido
+ bloqueada temporalmente porque ha ingresado incorrectamente la clave personal
+ demasiadas veces.
+ max_piv_cac_login_attempts_reached: NOT TRANSLATED YET
+ otp_delivery_preference:
+ instruction: Puede cambiar esta selección la próxima vez que inicie sesión.
+ phone_unsupported: NOT TRANSLATED YET
+ sms: Mensaje de texto (SMS, sigla en inglés)
+ title: "¿Cómo deberíamos enviarle un código?"
+ voice: Llamada telefónica
personal_key_fallback:
question: "¿No tiene su clave personal?"
+ personal_key_header_text: Ingrese su clave personal
+ personal_key_prompt: Puede usar esta clave personal una vez. Después de ingresarlo,
+ se le dará una nueva clave.
phone_fallback:
question: "¿No tiene acceso a su teléfono ahora mismo?"
+ phone_sms_info_html: Le enviaremos un mensaje de texto con un código de seguridad
+ cada vez que inicie sesión.
+ phone_sms_label: Número de teléfono móvil
+ phone_voice_info_html: Te llamaremos con un código de seguridad cada vez
+ que inicies sesión.
+ phone_voice_label: Número de teléfono
piv_cac_fallback:
question: "¿No tiene su tarjeta PIV/CAC disponible?"
+ piv_cac_header_text: NOT TRANSLATED YET
+ please_try_again_html: Inténtelo de nuevo en %{time_remaining}.
+ read_about_two_factor_authentication:
+ link: leer acerca de la autenticación de dos factores
+ text_html: Puede %{link} y por qué la utilizamos en nuestra página de Ayuda.
totp_fallback:
question: "¿No tiene su aplicación de autenticación?"
+ totp_header_text: Ingrese su código de la app de autenticación
+ two_factor_choice: Asegure su cuenta
+ two_factor_choice_intro: login.gov se asegura de que pueda acceder a su cuenta
+ agregando una segunda capa de seguridad.
+ two_factor_choice_options:
+ auth_app: Aplicación de autenticación
+ auth_app_info: Configure una aplicación de autenticación para obtener su código
+ de seguridad sin proporcionar un número de teléfono
+ piv_cac: Empleados del Gobierno
+ piv_cac_info: Use su tarjeta PIV / CAC para asegurar su cuenta
+ sms: Mensaje de texto / SMS
+ sms_info: Obtenga su código de seguridad a través de mensajes de texto / SMS
+ voice: Llamada telefónica
+ voice_info: Obtenga su código de seguridad a través de una llamada telefónica
+ webauthn: Clave de seguridad de hardware
+ webauthn_info: Use una clave de seguridad de hardware para proteger su cuenta
+ webauthn_fallback:
+ question: "¿No tienes tu clave de seguridad de hardware disponible?"
+ webauthn_header_text: Presente su clave de seguridad de hardware
diff --git a/config/locales/two_factor_authentication/fr.yml b/config/locales/two_factor_authentication/fr.yml
index b2fec4b80ab..77b3c17b0e8 100644
--- a/config/locales/two_factor_authentication/fr.yml
+++ b/config/locales/two_factor_authentication/fr.yml
@@ -1,7 +1,22 @@
---
fr:
two_factor_authentication:
+ account_reset:
+ cancel_link: Annuler votre demande
+ link: supprimer votre compte
+ pending_html: Vous avez actuellement une demande en attente pour supprimer votre
+ compte. Il faut compter 24 heures à partir du moment où vous avez fait la
+ demande pour terminer le processus. Veuillez vérifier plus tard. %{cancel_link}
+ successful_cancel: Je vous remercie. Votre demande de suppression de votre compte
+ login.gov a été annulée.
+ text_html: Si vous ne pouvez pas utiliser l'une de ces options de sécurité ci-dessus,
+ vous pouvez réinitialiser vos préférences par %{link}.
choose_another_option: "‹ Choisissez une autre option"
+ header_text: Entrez votre code de sécurité
+ invalid_otp: Ce code de sécurité est non valide. Vous pouvez essayer de l'entrer
+ de nouveau ou demander un nouveau code de sécurité à utilisation unique.
+ invalid_personal_key: Cette clé personnelle est non valide.
+ invalid_piv_cac: NOT TRANSLATED YET
login_intro: Vous les avez configurés lorsque vous avez crée votre compte
login_options:
auth_app: Application d'authentification
@@ -13,17 +28,74 @@ fr:
piv_cac: Employés du gouvernement
piv_cac_info: Utilisez votre carte PIV / CAC pour sécuriser votre compte.
sms: SMS
- sms_info: Obtenez votre code de sécurité par SMS.
+ sms_info_html: Obtenez votre code de sécurité par SMS à %{phone}.
+ sms_setup_info: Obtenez votre code de sécurité par SMS.
voice: Appel téléphonique
- voice_info: Obtenez votre code de sécurité par appel téléphonique. (Seulement
+ voice_info_html: Obtenez votre code de sécurité par appel téléphonique à %{phone}.
+ (Seulement les numéros de téléphone en Amerique du Nord)
+ voice_setup_info: Obtenez votre code de sécurité par appel téléphonique. (Seulement
les numéros de téléphone en Amerique du Nord)
+ webauthn: Clé de sécurité physique
+ webauthn_info: Utilisez votre clé de sécurité physique au lieu d'un code de
+ sécurité.
login_options_link_text: Choisissez une autre option de sécurité
login_options_title: Sélectionnez votre option de sécurité
+ max_generic_login_attempts_reached: Pour votre sécurité, votre compte est temporairement
+ verrouillé.
+ max_otp_login_attempts_reached: Pour votre sécurité, votre compte est temporairement
+ verrouillé, car vous avez entré le code de sécurité à utilisation unique de
+ façon erronée à de trop nombreuses reprises.
+ max_otp_requests_reached: Pour votre sécurité, votre compte est temporairement
+ verrouillé car vous avez demandé un code de sécurité à trop de reprises.
+ max_personal_key_login_attempts_reached: Pour votre sécurité, votre compte est
+ temporairement verrouillé, car vous avez entré le code de sécurité à utilisation
+ unique de façon erronée à de trop nombreuses reprises.
+ max_piv_cac_login_attempts_reached: NOT TRANSLATED YET
+ otp_delivery_preference:
+ instruction: Vous pouvez changer cette sélection la prochaine fois que vous
+ vous connectez.
+ phone_unsupported: NOT TRANSLATED YET
+ sms: Message texte (SMS)
+ title: Comment devrions-nous vous envoyer un code?
+ voice: Appel téléphonique
personal_key_fallback:
question: Vous n'avez pas votre clé personnelle?
+ personal_key_header_text: Entrez votre clé personnelle
+ personal_key_prompt: Vous pouvez utiliser cette clé personnelle une fois seulement.
+ Une fois que vous l'entrez, vous recevrez une nouvelle clé.
phone_fallback:
question: Vous n'avez pas accès à votre téléphone maintenant?
+ phone_sms_info_html: Nous vous enverrons un code de sécurité chaque fois
+ que vous vous connectez.
+ phone_sms_label: Numéro de téléphone portable
+ phone_voice_info_html: Nous vous appellerons avec un code de sécurité chaque
+ fois que vous vous connectez.
+ phone_voice_label: Numéro de téléphone
piv_cac_fallback:
question: Vous n'avez pas accès à votre carte PIV/CAC?
+ piv_cac_header_text: NOT TRANSLATED YET
+ please_try_again_html: Veuillez essayer de nouveau dans %{time_remaining}.
+ read_about_two_factor_authentication:
+ link: lire sur l'authentification à deux facteurs
+ text_html: Vous pouvez %{link} et pourquoi nous l'utilisons sur notre page d'aide.
totp_fallback:
question: Vous n'avez pas votre application d'authentification?
+ totp_header_text: Entrez votre code d'application d'authentification
+ two_factor_choice: Sécurise ton compte
+ two_factor_choice_intro: login.gov s'assure que vous pouvez accéder à votre compte
+ en ajoutant une deuxième couche de sécurité.
+ two_factor_choice_options:
+ auth_app: Application d'authentification
+ auth_app_info: Configurez une application d'authentification pour obtenir votre
+ code de sécurité sans fournir de numéro de téléphone
+ piv_cac: Employés du gouvernement
+ piv_cac_info: Utilisez votre carte PIV / CAC pour sécuriser votre compte
+ sms: SMS
+ sms_info: Obtenez votre code de sécurité par SMS
+ voice: Appel téléphonique
+ voice_info: Obtenez votre code de sécurité par appel téléphonique
+ webauthn: Clé de sécurité physique
+ webauthn_info: Utilisez une clé de sécurité physique pour sécuriser votre compte
+ webauthn_fallback:
+ question: Votre clé de sécurité physique n'est pas disponible?
+ webauthn_header_text: Présentez votre clé de sécurité physique
diff --git a/config/locales/users/en.yml b/config/locales/users/en.yml
index 6fc62dce80d..eb31ef6245b 100644
--- a/config/locales/users/en.yml
+++ b/config/locales/users/en.yml
@@ -17,17 +17,7 @@ en:
close: Close
confirmation_error: You've entered an incorrect personal key.
generated_on_html: Generated on %{date}
- get_another: Get another key
header: Your personal key
- help_text: |-
- To protect your account, you need a password and access to your telephone or authentication application at sign-in. If you can’t use your phone or app, you can sign in with your personal key instead.
-
- For your privacy and security, login.gov does not store your password and personal key. Only you know them. Only you can access or share your personal information.
-
- We require you to store your personal key outside your computer or mobile device so that it will be safe even if your devices are stolen or your online accounts are hacked.
-
- If you don’t have your personal key and you forget your password, the only way to keep your account safe is to verify that you are the legal owner.
- help_text_header: Why do I need to store my new key on paper?
print: Print this page
totp_setup:
new:
diff --git a/config/locales/users/es.yml b/config/locales/users/es.yml
index 1540202175c..0d618b1fb5e 100644
--- a/config/locales/users/es.yml
+++ b/config/locales/users/es.yml
@@ -17,17 +17,7 @@ es:
close: Cerrar
confirmation_error: Ha ingresado una clave personal incorrecta.
generated_on_html: Generado el %{date}
- get_another: Obtener otra clave
header: Su clave personal
- help_text: |-
- Para proteger su cuenta, necesita una contraseña y acceso a su teléfono o app de autenticación al iniciar una sesión. Si no puede utilizar su teléfono o app, puede iniciar una sesión con su clave personal.
-
- Para su privacidad y seguridad, login.gov no guarda su contraseña y clave personal. Sólo usted las conoce. Sólo usted puede acceder o compartir su información personal.
-
- Le pedimos que guarde su clave personal afuera de su computadora o dispositivo móvil para que mantenerla segura en caso de que le roben sus aparatos o sus cuentas en línea sean hackeadas.
-
- Si no tiene su clave personal y olvida su contraseña, la única manera de mantener su cuenta segura es verificando que usted es el propietario legal.
- help_text_header: "¿Por qué necesito guardar mi nueva clave en papel?"
print: Imprima esta página
totp_setup:
new:
diff --git a/config/locales/users/fr.yml b/config/locales/users/fr.yml
index fea033f1390..9a56406bf80 100644
--- a/config/locales/users/fr.yml
+++ b/config/locales/users/fr.yml
@@ -19,17 +19,7 @@ fr:
close: Fermer
confirmation_error: Vous avez entré un clé personnelle erronée.
generated_on_html: Générée le %{date}
- get_another: Obtenir une autre clé
header: Votre clé personnelle
- help_text: |-
- Pour protéger votre compte, vous devez avoir un mot de passe et l'accès à votre téléphone ou application d'authentification au moment de la connexion. Si vous ne pouvez utiliser votre téléphone ou application, vous pouvez vous connecter avec votre clé personnelle.
-
- Pour votre confidentialité et votre sécurité, login.gov ne conserve pas votre mot de passe ni votre clé personnelle. Seul(e) vous les connaissez. Seul(e) vous pouvez accéder à votre information personnelle et la partager .
-
- Nous vous demandons de conserver votre clé personnelle à l'extérieur de votre ordinateur ou appareil mobile afin qu'elle soit en sûreté même si vos appareils sont volés ou si vos comptes en ligne sont piratés.
-
- Si vous n'avez pas votre clé personnelle et que vous oubliez votre mot de passe, la seule façon de garder votre compte en sécurité est de vérifier que vous en êtes le(la) propriétaire légal(e).
- help_text_header: Pourquoi dois-je conserver ma nouvelle clé sur papier?
print: Imprimer cette page
totp_setup:
new:
diff --git a/config/routes.rb b/config/routes.rb
index adc12757ff3..a7f8b5a3d6c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,11 +1,7 @@
Rails.application.routes.draw do
- require 'sidekiq/web'
- mount Sidekiq::Web => '/sidekiq', constraints: AdminConstraint.new
-
# Non i18n routes. Alphabetically sorted.
get '/api/health' => 'health/health#index'
get '/api/health/database' => 'health/database#index'
- get '/api/health/workers' => 'health/workers#index'
get '/api/openid_connect/certs' => 'openid_connect/certs#index'
post '/api/openid_connect/token' => 'openid_connect/token#create'
match '/api/openid_connect/token' => 'openid_connect/token#options', via: :options
@@ -58,8 +54,8 @@
get '/account_reset/request' => 'account_reset/request#show'
post '/account_reset/request' => 'account_reset/request#create'
- get '/account_reset/cancel' => 'account_reset/cancel#create'
- get '/account_reset/report_fraud' => 'account_reset/report_fraud#update'
+ get '/account_reset/cancel' => 'account_reset/cancel#show'
+ post '/account_reset/cancel' => 'account_reset/cancel#create'
get '/account_reset/confirm_request' => 'account_reset/confirm_request#show'
get '/account_reset/delete_account' => 'account_reset/delete_account#show'
delete '/account_reset/delete_account' => 'account_reset/delete_account#delete'
@@ -73,8 +69,10 @@
post '/login/two_factor/authenticator' => 'two_factor_authentication/totp_verification#create'
get '/login/two_factor/personal_key' => 'two_factor_authentication/personal_key_verification#show'
post '/login/two_factor/personal_key' => 'two_factor_authentication/personal_key_verification#create'
- if FeatureManagement.piv_cac_enabled?
- get '/login/two_factor/piv_cac' => 'two_factor_authentication/piv_cac_verification#show'
+ get '/login/two_factor/piv_cac' => 'two_factor_authentication/piv_cac_verification#show'
+ if FeatureManagement.webauthn_enabled?
+ get '/login/two_factor/webauthn' => 'two_factor_authentication/webauthn_verification#show'
+ patch '/login/two_factor/webauthn' => 'two_factor_authentication/webauthn_verification#confirm'
end
get '/login/two_factor/:otp_delivery_preference' => 'two_factor_authentication/otp_verification#show',
as: :login_two_factor, constraints: { otp_delivery_preference: /sms|voice/ }
@@ -93,10 +91,8 @@
get '/saml/decode_assertion' => 'saml_test#start'
post '/saml/decode_assertion' => 'saml_test#decode_response'
post '/saml/decode_slo_request' => 'saml_test#decode_slo_request'
- if FeatureManagement.piv_cac_enabled?
- get '/piv_cac_entry' => 'piv_cac_authentication_test_subject#new'
- post '/piv_cac_entry' => 'piv_cac_authentication_test_subject#create'
- end
+ get '/piv_cac_entry' => 'piv_cac_authentication_test_subject#new'
+ post '/piv_cac_entry' => 'piv_cac_authentication_test_subject#create'
end
end
@@ -117,11 +113,9 @@
as: :create_verify_personal_key
get '/account_recovery_setup' => 'account_recovery_setup#index'
- if FeatureManagement.piv_cac_enabled?
- get '/piv_cac' => 'users/piv_cac_authentication_setup#new', as: :setup_piv_cac
- delete '/piv_cac' => 'users/piv_cac_authentication_setup#delete', as: :disable_piv_cac
- get '/present_piv_cac' => 'users/piv_cac_authentication_setup#redirect_to_piv_cac_service', as: :redirect_to_piv_cac_service
- end
+ get '/piv_cac' => 'users/piv_cac_authentication_setup#new', as: :setup_piv_cac
+ delete '/piv_cac' => 'users/piv_cac_authentication_setup#delete', as: :disable_piv_cac
+ get '/present_piv_cac' => 'users/piv_cac_authentication_setup#redirect_to_piv_cac_service', as: :redirect_to_piv_cac_service
if FeatureManagement.webauthn_enabled?
get '/webauthn_setup' => 'users/webauthn_setup#new', as: :webauthn_setup
@@ -192,7 +186,6 @@
put '/otp_delivery_method' => 'otp_delivery_method#create'
get '/phone' => 'phone#new'
put '/phone' => 'phone#create'
- get '/phone/result' => 'phone#show'
get '/phone/failure/:reason' => 'phone#failure', as: :phone_failure
post '/phone/resend_code' => 'resend_otp#create', as: :resend_otp
get '/phone_confirmation' => 'otp_verification#show', as: :otp_verification
@@ -201,7 +194,6 @@
put '/review' => 'review#create'
get '/session' => 'sessions#new'
put '/session' => 'sessions#create'
- get '/session/result' => 'sessions#show'
get '/session/success' => 'sessions#success'
get '/session/failure/:reason' => 'sessions#failure', as: :session_failure
delete '/session' => 'sessions#destroy'
@@ -210,6 +202,11 @@
get '/jurisdiction/failure/:reason' => 'jurisdiction#failure', as: :jurisdiction_failure
get '/cancel/' => 'cancellations#new', as: :cancel
delete '/cancel' => 'cancellations#destroy'
+ if FeatureManagement.doc_auth_enabled?
+ get '/doc_auth' => 'doc_auth#index'
+ get '/doc_auth/:step' => 'doc_auth#show', as: :doc_auth_step
+ put '/doc_auth/:step' => 'doc_auth#update'
+ end
end
end
diff --git a/config/service_providers.yml b/config/service_providers.yml
index 7ec97a5db43..7e14c1ed52a 100644
--- a/config/service_providers.yml
+++ b/config/service_providers.yml
@@ -264,7 +264,7 @@ production:
redirect_uris:
- 'http://localhost:9292/auth/result'
<% if LoginGov::Hostdata.in_datacenter? %>
- - 'https://sp-oidc-sinatra.<%= LoginGov::Hostdata.env %>.<%= LoginGov::Hostdata.domain %>/'
+ - 'https://sp-oidc-sinatra.<%= LoginGov::Hostdata.env %>.<%= LoginGov::Hostdata.domain %>/auth/result'
<% end %>
'urn:gov:gsa:openidconnect:sp:expressjs':
@@ -399,6 +399,7 @@ production:
redirect_uris:
- 'https://ttp.cbp.dhs.gov'
- 'https://ttp.cbp.dhs.gov/login'
+ - 'https://ttp.cbp.dhs.gov/login?logout=logout&undefined'
return_to_sp_url: https://ttp.cbp.dhs.gov/
# CBP ROAM (formerly OARS)
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
deleted file mode 100644
index f72f4153bcd..00000000000
--- a/config/sidekiq.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-:queues:
- - sms
- - voice
- - mailers
- - analytics
- - idv
-:logfile: 'log/sidekiq.log'
diff --git a/db/migrate/20180805121236_create_doc_auths.rb b/db/migrate/20180805121236_create_doc_auths.rb
new file mode 100644
index 00000000000..bb50932e186
--- /dev/null
+++ b/db/migrate/20180805121236_create_doc_auths.rb
@@ -0,0 +1,12 @@
+class CreateDocAuths < ActiveRecord::Migration[5.1]
+ def change
+ create_table :doc_auths do |t|
+ t.references :user, null: false
+ t.datetime :attempted_at
+ t.integer :attempts, default: 0
+ t.datetime :license_confirmed_at
+ t.datetime :selfie_confirmed_at
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20180906181420_create_email_address_table.rb b/db/migrate/20180906181420_create_email_address_table.rb
new file mode 100644
index 00000000000..6a82c5d3ef2
--- /dev/null
+++ b/db/migrate/20180906181420_create_email_address_table.rb
@@ -0,0 +1,16 @@
+class CreateEmailAddressTable < ActiveRecord::Migration[5.1]
+ def change
+ create_table :email_addresses do |t|
+ t.references :user
+ t.string :confirmation_token, limit: 255
+ t.datetime :confirmed_at
+ t.datetime :confirmation_sent_at
+ t.string :email_fingerprint, null: false, default: ""
+ t.string :encrypted_email, null: false, default: ""
+
+ t.timestamps
+
+ t.index :email_fingerprint, unique: true, where: 'confirmed_at IS NOT NULL'
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 410c606a561..e9a0594cee3 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180827225542) do
+ActiveRecord::Schema.define(version: 20180906181420) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -55,6 +55,30 @@
t.index ["user_id"], name: "index_authorizations_on_user_id"
end
+ create_table "doc_auths", force: :cascade do |t|
+ t.bigint "user_id", null: false
+ t.datetime "attempted_at"
+ t.integer "attempts", default: 0
+ t.datetime "license_confirmed_at"
+ t.datetime "selfie_confirmed_at"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_doc_auths_on_user_id"
+ end
+
+ create_table "email_addresses", force: :cascade do |t|
+ t.bigint "user_id"
+ t.string "confirmation_token", limit: 255
+ t.datetime "confirmed_at"
+ t.datetime "confirmation_sent_at"
+ t.string "email_fingerprint", default: "", null: false
+ t.string "encrypted_email", default: "", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["email_fingerprint"], name: "index_email_addresses_on_email_fingerprint", unique: true, where: "(confirmed_at IS NOT NULL)"
+ t.index ["user_id"], name: "index_email_addresses_on_user_id"
+ end
+
create_table "events", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "event_type", null: false
diff --git a/docker-compose.yml b/docker-compose.yml
index fe7f7688b57..3e10e659070 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -14,19 +14,6 @@ services:
depends_on:
- db
- redis
- sidekiq:
- build: .
- command: "bundle exec sidekiq --config config/sidekiq.yml"
- environment:
- REDIS_URL: "redis://redis"
- DATABASE_URL: "postgres://postgres@db"
- SMTP_HOST: "mailcatcher"
- depends_on:
- - db
- - redis
- - mailcatcher
- volumes:
- - .:/upaya
db:
image: postgres
redis:
diff --git a/lib/encrypted_sidekiq_redis.rb b/lib/encrypted_sidekiq_redis.rb
deleted file mode 100644
index 922b2b21e66..00000000000
--- a/lib/encrypted_sidekiq_redis.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-require 'redis'
-require 'gibberish'
-
-class EncryptedSidekiqRedis
- attr_accessor :redis, :cipher
-
- def initialize(opts)
- self.redis = Redis.new(opts)
- self.cipher = Gibberish::AES.new(Figaro.env.session_encryption_key)
- end
-
- def lpush(key, value)
- super(key, encrypt_job(value))
- end
-
- def rpush(key, value)
- super(key, encrypt_job(value))
- end
-
- def lpop(key)
- decrypt_job(super(key))
- end
-
- def rpop(key)
- decrypt_job(super(key))
- end
-
- def blpop(*args)
- queue, job = super(args)
- [queue, decrypt_job(job)]
- end
-
- def brpop(*args)
- queue, job = super(args)
- [queue, decrypt_job(job)]
- end
-
- def zadd(key, *args)
- ts, job = args
- super(key, [ts, encrypt_job(job)])
- end
-
- def zrem(key, member)
- # member must be removed from redis as-is (encrypted)
- # but it is used elsewhere as if it was decrypted, so alter it in place.
- ret = super(key, member)
- if ret
- decrypted_job = decrypt_job(member)
- member.clear
- member << decrypted_job
- end
- ret
- end
-
- # rubocop:disable Style/MethodMissingSuper
- def method_missing(meth, *args, &block)
- redis.send(meth, *args, &block)
- end
- # rubocop:enable Style/MethodMissingSuper
-
- def respond_to_missing?(meth, include_private)
- redis.respond_to?(meth, include_private)
- end
-
- private
-
- def decrypt_job(job_json)
- # if job is JSON, possibly ActiveJob format, possibly Gibberish format.
- begin
- job = JSON.parse(job_json)
- rescue StandardError
- return job_json
- end
- if encrypted?(job)
- cipher.decrypt(job_json)
- else
- job_json
- end
- end
-
- def encrypt_job(plain_job)
- if plain_job.is_a?(Array)
- plain_job.map { |job| encrypt_job(job) }
- else
- encrypted?(plain_job) ? plain_job : cipher.encrypt(plain_job)
- end
- end
-
- def encrypted?(job)
- return true if job.is_a?(Hash) && job.key?('cipher')
- return true if job.is_a?(String) && job =~ /"cipher"/
- false
- end
-end
diff --git a/lib/feature_management.rb b/lib/feature_management.rb
index 4ca5caf17e5..03720126f11 100644
--- a/lib/feature_management.rb
+++ b/lib/feature_management.rb
@@ -13,10 +13,6 @@ def self.telephony_disabled?
Figaro.env.telephony_disabled == 'true'
end
- def self.piv_cac_enabled?
- Figaro.env.piv_cac_enabled == 'true'
- end
-
def self.identity_pki_disabled?
env = Figaro.env
env.identity_pki_disabled == 'true' ||
@@ -24,10 +20,10 @@ def self.identity_pki_disabled?
!env.piv_cac_verify_token_url
end
- def self.development_and_piv_cac_entry_enabled?
+ def self.development_and_identity_pki_disabled?
# This controls if we try to hop over to identity-pki or just throw up
# a screen asking for a Subject or one of a list of error conditions.
- Rails.env.development? && piv_cac_enabled? && identity_pki_disabled?
+ Rails.env.development? && identity_pki_disabled?
end
def self.prefill_otp_codes?
@@ -109,4 +105,12 @@ def self.account_reset_enabled?
def self.webauthn_enabled?
Figaro.env.webauthn_enabled == 'true'
end
+
+ def self.doc_auth_enabled?
+ Figaro.env.doc_auth_enabled == 'true'
+ end
+
+ def self.doc_auth_exclusive?
+ Figaro.env.doc_auth_exclusive == 'true'
+ end
end
diff --git a/lib/no_retry_jobs.rb b/lib/no_retry_jobs.rb
deleted file mode 100644
index 840aeb0cba7..00000000000
--- a/lib/no_retry_jobs.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class NoRetryJobs
- def call(_worker, msg, queue)
- yield
- rescue StandardError => _e
- msg['retry'] = false if %w[idv sms voice].include?(queue)
- raise
- end
-end
diff --git a/lib/production_database_configuration.rb b/lib/production_database_configuration.rb
index d7474b9c0f3..65979447772 100644
--- a/lib/production_database_configuration.rb
+++ b/lib/production_database_configuration.rb
@@ -18,16 +18,7 @@ def self.password
end
def self.pool
- env = Figaro.env
- role = File.read('/etc/login.gov/info/role') if File.exist?('/etc/login.gov/info/role')
- case role
- when 'idp'
- env.database_pool_idp.presence || 5
- when 'worker'
- env.database_pool_worker.presence || 26
- else
- 5
- end
+ Figaro.env.database_pool_idp.presence || 5
end
private_class_method def self.readonly_mode?
diff --git a/lib/proofer_mocks/address_mock.rb b/lib/proofer_mocks/address_mock.rb
index 530ac83d129..48c6e92cd17 100644
--- a/lib/proofer_mocks/address_mock.rb
+++ b/lib/proofer_mocks/address_mock.rb
@@ -7,6 +7,10 @@ class AddressMock < Proofer::Base
plain_phone = applicant[:phone].gsub(/\D/, '').gsub(/\A1/, '')
if plain_phone == '7035555555'
result.add_error(:phone, 'The phone number could not be verified.')
+ elsif plain_phone == '7035555999'
+ raise 'Failed to contact proofing vendor'
+ elsif plain_phone == '7035555888'
+ raise Proofer::TimeoutError, 'address mock timeout'
end
result.context[:message] = 'some context for the mock address proofer'
end
diff --git a/lib/proofer_mocks/resolution_mock.rb b/lib/proofer_mocks/resolution_mock.rb
index 60ad3bf31b0..ace8577c67d 100644
--- a/lib/proofer_mocks/resolution_mock.rb
+++ b/lib/proofer_mocks/resolution_mock.rb
@@ -12,7 +12,7 @@ class ResolutionMock < Proofer::Base
result.add_error(:first_name, 'Unverified first name.')
elsif first_name.match?(/Time/i)
- sleep((Figaro.env.async_job_refresh_max_wait_seconds.to_i + 5).seconds)
+ raise Proofer::TimeoutError, 'resolution mock timeout'
elsif applicant[:ssn].match?(/6666/)
result.add_error(:ssn, 'Unverified SSN.')
diff --git a/lib/proofer_mocks/state_id_mock.rb b/lib/proofer_mocks/state_id_mock.rb
index 764de54ab75..aa5246ed172 100644
--- a/lib/proofer_mocks/state_id_mock.rb
+++ b/lib/proofer_mocks/state_id_mock.rb
@@ -1,6 +1,7 @@
class StateIdMock < Proofer::Base
SUPPORTED_STATES = %w[
- AR AZ CO DC DE FL IA ID IL IN KY MA MD ME MI MS MT ND NE NJ NM PA SD TX VA WA WI WY
+ AR AZ CO DC DE FL IA ID IL IN KY MA MD ME MI MO MS MT ND NE NJ NM PA RI SC
+ SD TX VA VT WA WI WY
].freeze
SUPPORTED_STATE_ID_TYPES = %w[
diff --git a/lib/queue_config.rb b/lib/queue_config.rb
index 2853281f34a..e6615f63ba1 100644
--- a/lib/queue_config.rb
+++ b/lib/queue_config.rb
@@ -5,7 +5,7 @@ module QueueConfig
# rubocop:disable Metrics/MethodLength
# Known acceptable values for config.active_job.queue_adapter
- KNOWN_QUEUE_ADAPTERS = %i[sidekiq inline async].freeze
+ KNOWN_QUEUE_ADAPTERS = %i[inline async].freeze
# Select a queue adapter for use, including possible random weights as
# defined by Figaro.env.queue_adapter_weights (a JSON mapping from queue
@@ -13,8 +13,8 @@ module QueueConfig
def self.choose_queue_adapter
adapter_config = Figaro.env.queue_adapter_weights
- # default to Sidekiq if no config present
- return :sidekiq unless adapter_config
+ # default to async if no config present
+ return :async unless adapter_config
options = JSON.parse(adapter_config, symbolize_names: true)
diff --git a/lib/sidekiq_logger_formatter.rb b/lib/sidekiq_logger_formatter.rb
deleted file mode 100644
index e8b87b2423d..00000000000
--- a/lib/sidekiq_logger_formatter.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-class SidekiqLoggerFormatter < Logger::Formatter
- # This method is stdlib in ruby :reek:LongParameterList { max_params: 4 }
- def call(severity, time, progname, msg)
- msg = filter_msg(msg)
- super(severity, time, progname, msg)
- end
-
- private
-
- def filter_msg(msg)
- return filter_msg_string(msg) if msg.is_a? String
- return filter_msg_hash(msg) if msg.is_a? Hash
- end
-
- def filter_msg_string(msg)
- parsed = JSON.parse(msg)
- filter_msg_hash(parsed)
- rescue StandardError
- msg
- end
-
- def filter_msg_hash(msg)
- if msg.key?('job')
- msg['job']['args'].each { |arg| arg['arguments'] = '[redacted]' }
- msg['jobstr'] = '[redacted]'
- end
- msg
- end
-end
diff --git a/lib/tasks/account_reset.rake b/lib/tasks/account_reset.rake
index 5b8e5ceb4f2..03e014f54bb 100644
--- a/lib/tasks/account_reset.rake
+++ b/lib/tasks/account_reset.rake
@@ -1,6 +1,6 @@
namespace :account_reset do
desc 'Send Notifications'
task send_notifications: :environment do
- AccountResetService.grant_tokens_and_send_notifications
+ AccountReset::GrantRequestsAndSendEmails.new.call
end
end
diff --git a/lib/tasks/create_test_accounts.rb b/lib/tasks/create_test_accounts.rb
index aa2ac0ae785..601b9d73212 100644
--- a/lib/tasks/create_test_accounts.rb
+++ b/lib/tasks/create_test_accounts.rb
@@ -16,7 +16,7 @@ def create_account(email: 'joe.smith@email.com', password: 'salty pickles', mfa_
user.skip_confirmation!
user.reset_password(password, password)
user.save!
- user.phone_configurations.create(
+ MfaContext.new(user).phone_configurations.create(
phone: mfa_phone || phone,
confirmed_at: Time.zone.now,
delivery_preference: user.otp_delivery_preference
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index 50b78ae7577..28064a2aa92 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -88,7 +88,7 @@ namespace :dev do
user.encrypted_email = args[:ee].encrypted
user.skip_confirmation!
user.reset_password(args[:pw], args[:pw])
- user.phone_configurations.create(phone_configuration_data(user, args))
+ MfaContext.new(user).phone_configurations.create(phone_configuration_data(user, args))
Event.create(user_id: user.id, event_type: :account_created)
end
diff --git a/lib/tasks/migrate_email_addresses.rake b/lib/tasks/migrate_email_addresses.rake
new file mode 100644
index 00000000000..0d669df442f
--- /dev/null
+++ b/lib/tasks/migrate_email_addresses.rake
@@ -0,0 +1,7 @@
+namespace :adhoc do
+ desc 'Copy email addresses to the new table'
+ task populate_email_addresses: :environment do
+ Rails.logger = Logger.new(STDOUT)
+ PopulateEmailAddressesTable.new.call
+ end
+end
diff --git a/lib/worker_health_checker.rb b/lib/worker_health_checker.rb
deleted file mode 100644
index 721fdb76b12..00000000000
--- a/lib/worker_health_checker.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-# Helps with reading and writing queue health from Sidekiq
-module WorkerHealthChecker
- module_function
-
- # Sidekiq server-side middleware that wraps jobs and marks the queues as healthy
- # when a job completes successfully
- class Middleware
- def call(_worker, _job, queue)
- yield
-
- WorkerHealthChecker.mark_healthy!(queue)
- end
- end
-
- # Empty job that we put in each background queue to make sure the queue is running
- # Relies on the Middleware to mark the queue as healthy
- class DummyJob < ApplicationJob
- def perform; end
- end
-
- Status = Struct.new(:queue, :last_run_at, :healthy) do
- alias_method :healthy?, :healthy
- end
-
- Summary = Struct.new(:statuses) do
- def healthy?
- statuses.all?(&:healthy?)
- end
-
- def to_h
- super.merge(all_healthy: healthy?) # monitoring currently depends on "all_healthy"
- end
-
- def as_json(*args)
- to_h.as_json(*args)
- end
- end
-
- # called on an interval to enqueue a dummy job in each queue
- # @see deploy/schedule.rb
- def enqueue_dummy_jobs(queues = sidekiq_queues)
- queues.each do |queue|
- DummyJob.set(queue: queue).perform_later
- end
- end
-
- def sidekiq_queues
- @_queues ||= YAML.load_file(Rails.root.join('config', 'sidekiq.yml'))[:queues]
- end
-
- # @return [Summary]
- def check(now: Time.zone.now)
- Summary.new(statuses(now: now))
- end
-
- # @return [Array]
- def statuses(now: Time.zone.now)
- Sidekiq::Queue.all.map(&:name).map do |name|
- status(name, now: now)
- end
- end
-
- def mark_healthy!(queue_name, now: Time.zone.now)
- with_redis do |redis|
- redis.set(health_check_key(queue_name), now.to_i)
- end
- end
-
- # @return [Status]
- def status(queue_name, now: Time.zone.now)
- last_run_value = with_redis { |redis| redis.get(health_check_key(queue_name)) }
-
- last_run_at = last_run_value && Time.zone.at(last_run_value.to_i)
-
- Status.new(queue_name, last_run_at, healthy?(last_run_at, now: now))
- end
-
- # @api private
- def healthy?(last_run_at, now: Time.zone.now)
- last_run_at.present? &&
- (now.to_i - last_run_at.to_i < Figaro.env.queue_health_check_dead_interval_seconds.to_i)
- end
-
- # @api private
- def health_check_key(queue_name)
- "health:#{queue_name}"
- end
-
- # @api private
- # This makes reek complain less about referencing things less than self
- def with_redis(&block)
- Sidekiq.redis(&block)
- end
-end
diff --git a/spec/controllers/account_reset/cancel_controller_spec.rb b/spec/controllers/account_reset/cancel_controller_spec.rb
index a3b2a5c3777..3b853b12e10 100644
--- a/spec/controllers/account_reset/cancel_controller_spec.rb
+++ b/spec/controllers/account_reset/cancel_controller_spec.rb
@@ -11,6 +11,7 @@
describe '#create' do
it 'logs a good token to the analytics' do
token = create_account_reset_request_for(user)
+ session[:cancel_token] = token
stub_analytics
analytics_hash = {
@@ -23,7 +24,7 @@
expect(@analytics).to receive(:track_event).
with(Analytics::ACCOUNT_RESET, analytics_hash)
- post :create, params: { token: token }
+ post :create
end
it 'logs a bad token to the analytics' do
@@ -37,8 +38,9 @@
expect(@analytics).to receive(:track_event).
with(Analytics::ACCOUNT_RESET, analytics_hash)
+ session[:cancel_token] = 'FOO'
- post :create, params: { token: 'FOO' }
+ post :create
end
it 'logs a missing token to the analytics' do
@@ -63,11 +65,12 @@
it 'redirects to the root with a flash message when the token is valid' do
token = create_account_reset_request_for(user)
+ session[:cancel_token] = token
- post :create, params: { token: token }
+ post :create
expect(flash[:success]).
- to eq t('devise.two_factor_authentication.account_reset.successful_cancel')
+ to eq t('two_factor_authentication.account_reset.successful_cancel')
expect(response).to redirect_to root_url
end
@@ -75,10 +78,44 @@
stub_sign_in(user)
token = create_account_reset_request_for(user)
+ session[:cancel_token] = token
expect(controller).to receive(:sign_out)
- post :create, params: { token: token }
+ post :create
+ end
+ end
+
+ describe '#show' do
+ it 'redirects to root if the token does not match one in the DB' do
+ stub_analytics
+ properties = {
+ user_id: 'anonymous-uuid',
+ event: 'visit',
+ success: false,
+ errors: { token: [t('errors.account_reset.cancel_token_invalid')] },
+ }
+ expect(@analytics).
+ to receive(:track_event).with(Analytics::ACCOUNT_RESET, properties)
+
+ get :show, params: { token: 'FOO' }
+
+ expect(response).to redirect_to(root_url)
+ expect(flash[:error]).to eq t('errors.account_reset.cancel_token_invalid')
+ end
+
+ it 'renders the show view if the token is missing' do
+ get :show
+
+ expect(response).to render_template(:show)
+ end
+
+ it 'redirects to root if feature is not enabled' do
+ allow(FeatureManagement).to receive(:account_reset_enabled?).and_return(false)
+
+ get :show
+
+ expect(response).to redirect_to root_url
end
end
end
diff --git a/spec/controllers/account_reset/delete_account_controller_spec.rb b/spec/controllers/account_reset/delete_account_controller_spec.rb
index 2675a82b723..1923c3ff6ca 100644
--- a/spec/controllers/account_reset/delete_account_controller_spec.rb
+++ b/spec/controllers/account_reset/delete_account_controller_spec.rb
@@ -7,7 +7,7 @@
it 'logs a good token to the analytics' do
user = create(:user)
create_account_reset_request_for(user)
- AccountResetService.new(user).grant_request
+ grant_request(user)
session[:granted_token] = AccountResetRequest.all[0].granted_token
stub_analytics
@@ -63,7 +63,7 @@
it 'displays a flash and redirects to root if the token is expired' do
user = create(:user)
create_account_reset_request_for(user)
- AccountResetService.new(user).grant_request
+ grant_request(user)
stub_analytics
properties = {
@@ -114,7 +114,7 @@
it 'displays a flash and redirects to root if the token is expired' do
user = create(:user)
create_account_reset_request_for(user)
- AccountResetService.new(user).grant_request
+ grant_request(user)
stub_analytics
properties = {
diff --git a/spec/controllers/account_reset/report_fraud_controller_spec.rb b/spec/controllers/account_reset/report_fraud_controller_spec.rb
deleted file mode 100644
index 56114cf0104..00000000000
--- a/spec/controllers/account_reset/report_fraud_controller_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require 'rails_helper'
-
-describe AccountReset::ReportFraudController do
- include AccountResetHelper
-
- describe '#update' do
- it 'logs a good token to the analytics' do
- user = create(:user)
- create_account_reset_request_for(user)
-
- stub_analytics
- expect(@analytics).to receive(:track_event).
- with(Analytics::ACCOUNT_RESET, event: :fraud, token_valid: true)
-
- post :update, params: { token: AccountResetRequest.all[0].request_token }
- end
-
- it 'logs a bad token to the analytics' do
- stub_analytics
- expect(@analytics).to receive(:track_event).
- with(Analytics::ACCOUNT_RESET, event: :fraud, token_valid: false)
-
- post :update, params: { token: 'FOO' }
- end
-
- it 'redirects to the root' do
- post :update
- expect(response).to redirect_to root_url
- end
- end
-end
diff --git a/spec/controllers/account_reset/send_notifications_controller_spec.rb b/spec/controllers/account_reset/send_notifications_controller_spec.rb
index b32b17be624..c4a3dc37844 100644
--- a/spec/controllers/account_reset/send_notifications_controller_spec.rb
+++ b/spec/controllers/account_reset/send_notifications_controller_spec.rb
@@ -15,7 +15,9 @@
end
it 'logs the number of notifications sent in the analytics' do
- allow(AccountResetService).to receive(:grant_tokens_and_send_notifications).and_return(7)
+ service = instance_double(AccountReset::GrantRequestsAndSendEmails)
+ allow(AccountReset::GrantRequestsAndSendEmails).to receive(:new).and_return(service)
+ allow(service).to receive(:call).and_return(7)
stub_analytics
expect(@analytics).to receive(:track_event).
diff --git a/spec/controllers/concerns/idv_step_concern_spec.rb b/spec/controllers/concerns/idv_step_concern_spec.rb
index 61d589fa6d9..36fbf7546fc 100644
--- a/spec/controllers/concerns/idv_step_concern_spec.rb
+++ b/spec/controllers/concerns/idv_step_concern_spec.rb
@@ -106,7 +106,7 @@ def show
context 'user has started IdV session' do
before do
- idv_session.params = { first_name: 'Jane' }
+ idv_session.applicant = { first_name: 'Jane' }
allow(subject).to receive(:idv_session).and_return(idv_session)
end
diff --git a/spec/controllers/health/health_controller_spec.rb b/spec/controllers/health/health_controller_spec.rb
index 9fa15add5d9..24671855569 100644
--- a/spec/controllers/health/health_controller_spec.rb
+++ b/spec/controllers/health/health_controller_spec.rb
@@ -2,74 +2,55 @@
RSpec.describe Health::HealthController do
describe '#index' do
- subject(:action) { get :index }
-
- before do
- allow(WorkerHealthChecker).to receive(:check).
- and_return(WorkerHealthChecker::Summary.new(statuses))
- end
-
- let(:statuses) { [WorkerHealthChecker::Status.new('voice', 0.minutes.ago, true)] }
-
context 'when all checked resources are healthy' do
- it 'is a 200' do
- action
-
- expect(response.status).to eq(200)
- end
-
- it 'renders the result' do
- action
+ it 'returns a successful JSON response' do
+ allow(DatabaseHealthChecker).to receive(:simple_query).and_return('foo')
+ allow(AccountResetHealthChecker).to receive(:check).
+ and_return(AccountResetHealthChecker::Summary.new(true, 'foo'))
+ get :index
json = JSON.parse(response.body, symbolize_names: true)
+ expect(response.status).to eq(200)
expect(json[:healthy]).to eq(true)
- expect(json[:statuses][:workers][:all_healthy]).to eq(true)
expect(json[:statuses][:database][:healthy]).to eq(true)
expect(json[:statuses][:account_reset][:healthy]).to eq(true)
end
end
context 'when one resource is unhealthy' do
- before do
- expect(DatabaseHealthChecker).to receive(:simple_query).
+ it 'returns an unsuccessful JSON response' do
+ allow(DatabaseHealthChecker).to receive(:simple_query).
and_raise(RuntimeError.new('canceling statement due to statement timeout'))
- end
-
- it 'is a 500' do
- action
-
- expect(response.status).to eq(500)
- end
-
- it 'renders the error' do
- action
+ allow(AccountResetHealthChecker).to receive(:check).
+ and_return(AccountResetHealthChecker::Summary.new(true, 'foo'))
+ get :index
json = JSON.parse(response.body, symbolize_names: true)
expect(json[:healthy]).to eq(false)
expect(json[:statuses][:database][:result]).
to include('canceling statement due to statement timeout')
+ expect(json[:statuses][:account_reset][:healthy]).to eq(true)
+ expect(response.status).to eq(500)
end
end
- context 'when activejob queue_adapter is inline/async' do
- before do
- expect(Rails.application.config.active_job).to receive(:queue_adapter).
- and_return(:inline)
- end
-
- it 'does not check worker health' do
- expect(WorkerHealthChecker).not_to receive(:check)
-
- action
+ context 'all resources are unhealthy' do
+ it 'returns an unsuccessful JSON response' do
+ allow(DatabaseHealthChecker).to receive(:simple_query).
+ and_raise(RuntimeError.new('canceling statement due to statement timeout'))
+ allow(AccountResetHealthChecker).to receive(:check).
+ and_return(AccountResetHealthChecker::Summary.new(false, 'foo'))
- expect(response.status).to eq(200)
+ get :index
json = JSON.parse(response.body, symbolize_names: true)
- expect(json[:healthy]).to eq(true)
- expect(json[:statuses][:workers]).to be_nil
- expect(json[:statuses][:database][:healthy]).to eq(true)
+ expect(json[:healthy]).to eq(false)
+ expect(json[:statuses][:database][:result]).
+ to include('canceling statement due to statement timeout')
+ expect(json[:statuses][:account_reset][:healthy]).to eq(false)
+ expect(response.status).to eq(500)
end
end
end
diff --git a/spec/controllers/health/workers_controller_spec.rb b/spec/controllers/health/workers_controller_spec.rb
deleted file mode 100644
index 5e304b66e01..00000000000
--- a/spec/controllers/health/workers_controller_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Health::WorkersController do
- describe '#index' do
- before do
- allow(WorkerHealthChecker).to receive(:check).
- and_return(WorkerHealthChecker::Summary.new(statuses))
- end
-
- subject(:action) { get :index }
-
- let(:statuses) do
- [
- WorkerHealthChecker::Status.new('voice', 0.minutes.ago, true),
- WorkerHealthChecker::Status.new('sms', 0.minutes.ago, true),
- ]
- end
-
- it 'renders the responses as json' do
- action
-
- json = JSON.parse(response.body)
-
- expect(json['all_healthy']).to eq(true)
- expect(json['statuses'].first['queue']).to eq('voice')
- expect(json['statuses'].first['healthy']).to eq(true)
- end
-
- context 'with all healthy statuses' do
- it 'is a 200' do
- action
-
- expect(response.status).to eq(200)
- end
- end
-
- context 'with an unhealthy status' do
- let(:statuses) do
- [
- WorkerHealthChecker::Status.new('voice', 0.minutes.ago, true),
- WorkerHealthChecker::Status.new('sms', nil, false),
- ]
- end
-
- it 'is a 500' do
- action
-
- expect(response.status).to eq(500)
- end
- end
- end
-end
diff --git a/spec/controllers/idv/confirmations_controller_spec.rb b/spec/controllers/idv/confirmations_controller_spec.rb
index 713e51d88f6..e9763fe30d1 100644
--- a/spec/controllers/idv/confirmations_controller_spec.rb
+++ b/spec/controllers/idv/confirmations_controller_spec.rb
@@ -10,7 +10,7 @@ def stub_idv_session
current_user: user,
issuer: nil
)
- idv_session.applicant = idv_session.vendor_params
+ idv_session.applicant = applicant
idv_session.resolution_successful = true
profile_maker = Idv::ProfileMaker.new(
applicant: applicant,
@@ -110,7 +110,8 @@ def index
context 'user used 2FA phone as phone of record' do
before do
- subject.idv_session.params['phone'] = user.phone_configurations.first.phone
+ subject.idv_session.applicant['phone'] =
+ MfaContext.new(user).phone_configurations.first.phone
end
it 'tracks final IdV event' do
@@ -130,7 +131,7 @@ def index
context 'user confirmed a new phone' do
before do
- subject.idv_session.params['phone'] = '+1 (202) 555-9876'
+ subject.idv_session.applicant['phone'] = '+1 (202) 555-9876'
end
it 'tracks final IdV event' do
diff --git a/spec/controllers/idv/doc_auth_controller_spec.rb b/spec/controllers/idv/doc_auth_controller_spec.rb
new file mode 100644
index 00000000000..c9fd4a2cf35
--- /dev/null
+++ b/spec/controllers/idv/doc_auth_controller_spec.rb
@@ -0,0 +1,100 @@
+require 'rails_helper'
+
+describe Idv::DocAuthController do
+ include DocAuthHelper
+
+ describe 'before_actions' do
+ it 'includes corrects before_actions' do
+ expect(subject).to have_actions(:before,
+ :confirm_two_factor_authenticated,
+ :fsm_initialize,
+ :ensure_correct_step)
+ end
+ end
+
+ before do
+ enable_doc_auth
+ stub_sign_in
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+ end
+
+ describe '#index' do
+ it 'redirects to the first step' do
+ get :index
+
+ expect(response).to redirect_to idv_doc_auth_step_url(step: :ssn)
+ end
+ end
+
+ describe '#show' do
+ it 'renders the front_image template' do
+ get :show, params: { step: 'ssn' }
+
+ expect(response).to render_template :ssn
+ end
+
+ it 'renders the front_image template' do
+ mock_next_step(:front_image)
+ get :show, params: { step: 'front_image' }
+
+ expect(response).to render_template :front_image
+ end
+
+ it 'renders the back_image template' do
+ mock_next_step(:back_image)
+ get :show, params: { step: 'back_image' }
+
+ expect(response).to render_template :back_image
+ end
+
+ it 'renders the self image template' do
+ mock_next_step(:self_image)
+ get :show, params: { step: 'self_image' }
+
+ expect(response).to render_template :self_image
+ end
+
+ it 'redirect to the right step' do
+ mock_next_step(:front_image)
+ get :show, params: { step: 'back_image' }
+
+ expect(response).to redirect_to idv_doc_auth_step_url(:front_image)
+ end
+
+ it 'renders a 404 with a non existent step' do
+ get :show, params: { step: 'foo' }
+
+ expect(response).to_not be_not_found
+ end
+
+ it 'tracks analytics' do
+ result = { step: 'ssn' }
+
+ get :show, params: { step: 'ssn' }
+
+ expect(@analytics).to have_received(:track_event).with(
+ Analytics::DOC_AUTH + ' visited', result
+ )
+ end
+ end
+
+ describe '#update' do
+ it 'renders the front_image template' do
+ end
+
+ it 'tracks analytics' do
+ result = { success: true, errors: {}, step: 'ssn' }
+
+ put :update, params: { step: 'ssn', doc_auth: { step: 'ssn', ssn: '111-11-1111' } }
+
+ expect(@analytics).to have_received(:track_event).with(
+ Analytics::DOC_AUTH + ' submitted', result
+ )
+ end
+ end
+
+ def mock_next_step(step)
+ allow_any_instance_of(Idv::Flows::DocAuthFlow).to receive(:next_step).and_return(step)
+ end
+end
diff --git a/spec/controllers/idv/jurisdiction_controller_spec.rb b/spec/controllers/idv/jurisdiction_controller_spec.rb
index 4d0a515dd67..8e295ccf36e 100644
--- a/spec/controllers/idv/jurisdiction_controller_spec.rb
+++ b/spec/controllers/idv/jurisdiction_controller_spec.rb
@@ -49,7 +49,7 @@
it 'puts the jurisdiction into the user session' do
post :create, params: { jurisdiction: { state: supported_jurisdiction } }
- expect(controller.user_session[:idv_jurisdiction]).to eq(supported_jurisdiction)
+ expect(controller.user_session[:idv][:selected_jurisdiction]).to eq(supported_jurisdiction)
end
context 'with an unsupported jurisdiction' do
@@ -73,7 +73,7 @@
let(:reason) { 'unsupported_jurisdiction' }
before do
- controller.user_session[:idv_jurisdiction] = supported_jurisdiction
+ controller.user_session[:idv] = { selected_jurisdiction: supported_jurisdiction }
end
it 'renders the `_failure` template' do
diff --git a/spec/controllers/idv/otp_delivery_method_controller_spec.rb b/spec/controllers/idv/otp_delivery_method_controller_spec.rb
index 5faa5629464..0e5fe4f6756 100644
--- a/spec/controllers/idv/otp_delivery_method_controller_spec.rb
+++ b/spec/controllers/idv/otp_delivery_method_controller_spec.rb
@@ -6,7 +6,7 @@
before do
stub_verify_steps_one_and_two(user)
subject.idv_session.address_verification_mechanism = 'phone'
- subject.idv_session.params[:phone] = '2255555000'
+ subject.idv_session.applicant[:phone] = '2255555000'
subject.idv_session.vendor_phone_confirmation = true
subject.idv_session.user_phone_confirmation = false
end
@@ -64,13 +64,7 @@
end
describe '#create' do
- let(:params) do
- {
- otp_delivery_selection_form: {
- otp_delivery_preference: :sms,
- },
- }
- end
+ let(:params) { { otp_delivery_preference: :sms } }
context 'user has not selected phone verification method' do
before do
@@ -130,13 +124,7 @@
end
context 'user has selected voice' do
- let(:params) do
- {
- otp_delivery_selection_form: {
- otp_delivery_preference: :voice,
- },
- }
- end
+ let(:params) { { otp_delivery_preference: :voice } }
it 'redirects to the otp send path for voice' do
post :create, params: params
@@ -162,13 +150,7 @@
end
context 'form is invalid' do
- let(:params) do
- {
- otp_delivery_selection_form: {
- otp_delivery_preference: :🎷,
- },
- }
- end
+ let(:params) { { otp_delivery_preference: :🎷 } }
it 'renders the new template' do
post :create, params: params
diff --git a/spec/controllers/idv/otp_verification_controller_spec.rb b/spec/controllers/idv/otp_verification_controller_spec.rb
index fd516a2a51c..5102fe4d1dc 100644
--- a/spec/controllers/idv/otp_verification_controller_spec.rb
+++ b/spec/controllers/idv/otp_verification_controller_spec.rb
@@ -15,7 +15,7 @@
sign_in(user)
stub_verify_steps_one_and_two(user)
- subject.idv_session.params[:phone] = phone
+ subject.idv_session.applicant[:phone] = phone
subject.idv_session.vendor_phone_confirmation = true
subject.idv_session.user_phone_confirmation = user_phone_confirmation
subject.idv_session.phone_confirmation_otp_delivery_method =
diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb
index ff2aac508a5..002174cec8f 100644
--- a/spec/controllers/idv/phone_controller_spec.rb
+++ b/spec/controllers/idv/phone_controller_spec.rb
@@ -62,6 +62,21 @@
expect(response).to redirect_to idv_phone_failure_url(:fail)
end
+
+ context 'when there is a pending verification' do
+ let(:pending_profile) do
+ create(:profile, user_id: user.id, deactivation_reason: :verification_pending)
+ end
+
+ it 'clears the pending verification' do
+ pending_profile
+ expect(Profile.all.first.deactivation_reason).to eq('verification_pending')
+
+ get :new
+
+ expect(Profile.all.first.deactivation_reason).to eq('verification_cancelled')
+ end
+ end
end
describe '#create' do
@@ -77,11 +92,11 @@
put :create, params: { idv_phone_form: { phone: '703' } }
expect(flash[:warning]).to be_nil
- expect(subject.idv_session.params).to be_empty
+ expect(response).to render_template(:new)
end
it 'tracks form error and does not make a vendor API call' do
- expect(Idv::Job).to_not receive(:submit)
+ expect(Idv::Proofer).to_not receive(:get_vendor)
put :create, params: { idv_phone_form: { phone: '703' } }
@@ -126,7 +141,7 @@
end
context 'when same as user phone' do
- it 'redirects to result page and sets phone_confirmed_at' do
+ it 'redirects to review page and sets phone_confirmed_at' do
user = build(:user, :with_phone, with: {
phone: good_phone, confirmed_at: Time.zone.now
})
@@ -134,17 +149,21 @@
put :create, params: { idv_phone_form: { phone: good_phone } }
- expect(response).to redirect_to idv_phone_result_path
+ expect(response).to redirect_to idv_review_path
- expected_params = {
+ expected_applicant = {
+ first_name: 'Some',
+ last_name: 'One',
phone: normalized_phone,
- phone_confirmed_at: user.phone_configurations.first.confirmed_at,
- }
- expect(subject.idv_session.params).to eq expected_params
+ }.with_indifferent_access
+
+ expect(subject.idv_session.applicant).to eq expected_applicant
+ expect(subject.idv_session.vendor_phone_confirmation).to eq true
+ expect(subject.idv_session.user_phone_confirmation).to eq true
end
end
- context 'when different from user phone' do
+ context 'when different phone from user phone' do
it 'redirects to otp page and does not set phone_confirmed_at' do
user = build(:user, :with_phone, with: {
phone: '+1 (415) 555-0130', confirmed_at: Time.zone.now
@@ -153,151 +172,75 @@
put :create, params: { idv_phone_form: { phone: good_phone } }
- expect(response).to redirect_to idv_phone_result_path
+ expect(response).to redirect_to idv_otp_delivery_method_path
- expected_params = {
- phone: normalized_phone,
- phone_confirmed_at: nil,
- }
- expect(subject.idv_session.params).to eq expected_params
+ expect(subject.idv_session.vendor_phone_confirmation).to eq true
+ expect(subject.idv_session.user_phone_confirmation).to eq false
end
end
- end
- end
- describe '#show' do
- let(:user) do
- build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now })
- end
- let(:params) { { phone: good_phone } }
-
- before do
- stub_verify_steps_one_and_two(user)
- controller.idv_session.params = params
- end
+ it 'tracks event with valid phone' do
+ user = build(:user, with: { phone: '+1 (415) 555-0130', phone_confirmed_at: Time.zone.now })
+ stub_verify_steps_one_and_two(user)
- context 'when the background job is not complete yet' do
- render_views
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
- it 'renders a spinner and has the page refresh' do
- get :show
+ context = { stages: [{ address: 'AddressMock' }] }
+ result = {
+ success: true,
+ errors: {},
+ vendor: { messages: [], context: context, exception: nil, timed_out: false },
+ }
- expect(response).to render_template('shared/refresh')
+ expect(@analytics).to receive(:track_event).ordered.with(
+ Analytics::IDV_PHONE_CONFIRMATION_FORM, hash_including(:success)
+ )
+ expect(@analytics).to receive(:track_event).ordered.with(
+ Analytics::IDV_PHONE_CONFIRMATION_VENDOR, result
+ )
- dom = Nokogiri::HTML(response.body)
- expect(dom.css('meta[http-equiv="refresh"]')).to be_present
+ put :create, params: { idv_phone_form: { phone: good_phone } }
end
end
- context 'when the background job has timed out' do
- let(:expired_started_at) do
- Time.zone.now.to_i - Figaro.env.async_job_refresh_max_wait_seconds.to_i
- end
+ context 'when verification fails' do
+ it 'renders failure page and does not set phone confirmation' do
+ user = build(:user, with: { phone: '+1 (415) 555-0130', phone_confirmed_at: Time.zone.now })
+ stub_verify_steps_one_and_two(user)
- before do
- controller.idv_session.async_result_started_at = expired_started_at
- end
+ put :create, params: { idv_phone_form: { phone: '7035555555' } }
- it 'displays an error' do
- get :show
+ expect(response).to redirect_to idv_phone_failure_path(reason: :warning)
- expect(response).to redirect_to idv_phone_failure_path(:timeout)
+ expect(subject.idv_session.vendor_phone_confirmation).to be_falsy
+ expect(subject.idv_session.user_phone_confirmation).to be_falsy
end
- it 'tracks the failure as a timeout' do
+ it 'tracks event with invalid phone' do
+ user = build(:user, with: { phone: '+1 (415) 555-0130', phone_confirmed_at: Time.zone.now })
+ stub_verify_steps_one_and_two(user)
+
stub_analytics
allow(@analytics).to receive(:track_event)
- get :show
-
+ context = { stages: [{ address: 'AddressMock' }] }
result = {
success: false,
- errors: { timed_out: ['Timed out waiting for vendor response'] },
- vendor: { messages: [], context: {}, exception: nil },
+ errors: {
+ phone: ['The phone number could not be verified.'],
+ },
+ vendor: { messages: [], context: context, exception: nil, timed_out: false },
}
- expect(@analytics).to have_received(:track_event).with(
+ expect(@analytics).to receive(:track_event).ordered.with(
+ Analytics::IDV_PHONE_CONFIRMATION_FORM, hash_including(:success)
+ )
+ expect(@analytics).to receive(:track_event).ordered.with(
Analytics::IDV_PHONE_CONFIRMATION_VENDOR, result
)
- end
- end
-
- context 'when the background job has completed' do
- let(:result_id) { SecureRandom.uuid }
-
- before do
- controller.idv_session.async_result_id = result_id
- VendorValidatorResultStorage.new.store(result_id: result_id, result: result)
- end
-
- let(:result) { Idv::VendorResult.new(success: true) }
-
- context 'when the phone is invalid' do
- let(:result) do
- Idv::VendorResult.new(
- success: false,
- errors: { phone: ['The phone number could not be verified.'] }
- )
- end
-
- let(:params) { { phone: bad_phone } }
- let(:user) do
- build(:user, :with_phone, with: { phone: bad_phone, confirmed_at: Time.zone.now })
- end
-
- it 'tracks event with invalid phone' do
- stub_analytics
- allow(@analytics).to receive(:track_event)
-
- get :show
-
- result = {
- success: false,
- errors: {
- phone: ['The phone number could not be verified.'],
- },
- vendor: { messages: [], context: {}, exception: nil },
- }
- expect(response).to redirect_to idv_phone_failure_path(:warning)
- expect(@analytics).to have_received(:track_event).with(
- Analytics::IDV_PHONE_CONFIRMATION_VENDOR, result
- )
- end
- end
-
- context 'attempt window has expired, previous attempts == max-1' do
- let(:two_days_ago) { Time.zone.now - 2.days }
- let(:user) do
- build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now })
- end
-
- before do
- user.idv_attempts = max_attempts - 1
- user.idv_attempted_at = two_days_ago
- subject.idv_session.user_phone_confirmation = true
- end
-
- it 'allows and does not affect attempt counter' do
- get :show
-
- expect(response).to redirect_to idv_review_path
- expect(user.idv_attempts).to eq(max_attempts - 1)
- expect(user.idv_attempted_at).to eq two_days_ago
- end
- end
-
- it 'passes the normalized phone to the background job' do
- user = build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now })
-
- stub_verify_steps_one_and_two(user)
-
- subject.params = { phone: normalized_phone }
- expect(Idv::Job).to receive(:submit).
- with(subject.idv_session, [:address]).
- and_call_original
-
- put :create, params: { idv_phone_form: { phone: good_phone } }
+ put :create, params: { idv_phone_form: { phone: '7035555555' } }
end
end
end
diff --git a/spec/controllers/idv/resend_otp_controller_spec.rb b/spec/controllers/idv/resend_otp_controller_spec.rb
index 7d73377f7c1..74c598aba22 100644
--- a/spec/controllers/idv/resend_otp_controller_spec.rb
+++ b/spec/controllers/idv/resend_otp_controller_spec.rb
@@ -13,7 +13,7 @@
sign_in(user)
stub_verify_steps_one_and_two(user)
- subject.idv_session.params[:phone] = phone
+ subject.idv_session.applicant[:phone] = phone
subject.idv_session.vendor_phone_confirmation = true
subject.idv_session.user_phone_confirmation = user_phone_confirmation
subject.idv_session.phone_confirmation_otp_delivery_method =
diff --git a/spec/controllers/idv/review_controller_spec.rb b/spec/controllers/idv/review_controller_spec.rb
index b981a6d942f..4c607f449bc 100644
--- a/spec/controllers/idv/review_controller_spec.rb
+++ b/spec/controllers/idv/review_controller_spec.rb
@@ -20,7 +20,7 @@
city: 'Somewhere',
state: 'KS',
zipcode: zipcode,
- phone: user.phone_configurations.first&.phone,
+ phone: MfaContext.new(user).phone_configurations.first&.phone,
ssn: '12345678',
}
end
@@ -32,7 +32,7 @@
)
idv_session.profile_confirmation = true
idv_session.vendor_phone_confirmation = true
- idv_session.params = user_attrs
+ idv_session.applicant = user_attrs
idv_session
end
@@ -61,7 +61,7 @@ def show
routes.draw do
get 'show' => 'idv/review#show'
end
- idv_session.params = user_attrs
+ idv_session.applicant = user_attrs
allow(subject).to receive(:idv_session).and_return(idv_session)
allow(subject).to receive(:confirm_idv_attempts_allowed).and_return(true)
end
@@ -151,7 +151,7 @@ def show
end
allow(subject).to receive(:confirm_idv_steps_complete).and_return(true)
allow(subject).to receive(:confirm_idv_attempts_allowed).and_return(true)
- idv_session.params = user_attrs.merge(phone_confirmed_at: Time.zone.now)
+ idv_session.applicant = user_attrs.merge(phone_confirmed_at: Time.zone.now)
allow(subject).to receive(:idv_session).and_return(idv_session)
end
@@ -191,7 +191,7 @@ def show
context 'user has completed all steps' do
before do
- idv_session.params = user_attrs
+ idv_session.applicant = user_attrs
end
it 'shows completed session' do
@@ -266,7 +266,7 @@ def show
context 'user fails to supply correct password' do
before do
- idv_session.params = user_attrs.merge(phone_confirmed_at: Time.zone.now)
+ idv_session.applicant = user_attrs.merge(phone_confirmed_at: Time.zone.now)
end
it 'redirects to original path' do
@@ -278,7 +278,7 @@ def show
context 'user has completed all steps' do
before do
- idv_session.params = user_attrs
+ idv_session.applicant = user_attrs
idv_session.applicant = idv_session.vendor_params
stub_analytics
allow(@analytics).to receive(:track_event)
diff --git a/spec/controllers/idv/sessions_controller_spec.rb b/spec/controllers/idv/sessions_controller_spec.rb
index 3c3a5f2ad5f..1affa55d209 100644
--- a/spec/controllers/idv/sessions_controller_spec.rb
+++ b/spec/controllers/idv/sessions_controller_spec.rb
@@ -21,7 +21,6 @@
let(:idv_session) do
Idv::Session.new(user_session: subject.user_session, current_user: user, issuer: nil)
end
- let(:applicant) { user_attrs }
describe 'before_actions' do
it 'includes before_actions from AccountStateChecker' do
@@ -35,306 +34,175 @@
end
end
- context 'user has created account' do
- before do
- stub_sign_in(user)
- allow(subject).to receive(:idv_session).and_return(idv_session)
- stub_analytics
- allow(@analytics).to receive(:track_event)
- end
+ before do
+ stub_sign_in(user)
+ allow(subject).to receive(:idv_session).and_return(idv_session)
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+ end
- describe '#new' do
- it 'starts new proofing session' do
- get :new
+ describe '#new' do
+ it 'starts a new proofing session' do
+ get :new
- expect(response.status).to eq 200
- end
+ expect(response.status).to eq 200
+ end
- it 'redirects if step is complete' do
+ context 'the user has already completed the step' do
+ it 'redirects to the success step' do
idv_session.profile_confirmation = true
+ idv_session.resolution_successful = true
get :new
expect(response).to redirect_to idv_session_success_path
end
-
- context 'max attempts exceeded' do
- before do
- user.idv_attempts = max_attempts
- user.idv_attempted_at = Time.zone.now
- end
-
- it 'redirects to fail' do
- get :new
-
- result = {
- request_path: idv_session_path,
- }
-
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_MAX_ATTEMPTS_EXCEEDED, result)
- expect(response).to redirect_to idv_session_failure_url(:fail)
- end
- end
end
- describe '#create' do
- before do
- stub_analytics
- allow(@analytics).to receive(:track_event)
- end
-
- context 'UUID' do
- it 'assigned user UUID to applicant' do
- post :create, params: { profile: user_attrs }
+ context 'max attempts exceeded' do
+ it 'redirects to fail' do
+ user.idv_attempts = max_attempts
+ user.idv_attempted_at = Time.zone.now
- expect(subject.idv_session.applicant['uuid']).to eq subject.current_user.uuid
- end
- end
-
- context 'existing SSN' do
- it 'redirects to custom error' do
- create(:profile, pii: { ssn: '666-66-1234' })
-
- result = {
- success: false,
- errors: { ssn: [t('idv.errors.duplicate_ssn')] },
- }
-
- expect(@analytics).to receive(:track_event).
- with(Analytics::IDV_BASIC_INFO_SUBMITTED_FORM, result)
-
- post :create, params: { profile: user_attrs.merge(ssn: '666-66-1234') }
-
- expect(response).to redirect_to(idv_session_failure_url(:dupe_ssn))
- end
- end
-
- context 'empty SSN' do
- it 'renders the form' do
- post :create, params: { profile: user_attrs.merge(ssn: '') }
-
- expect(response).to_not redirect_to(idv_session_failure_url(:dupe_ssn))
- expect(response).to render_template(:new)
- end
- end
-
- context 'missing fields' do
- let(:partial_attrs) do
- user_attrs.tap { |attrs| attrs.delete :first_name }
- end
-
- it 'checks for required fields' do
- post :create, params: { profile: partial_attrs }
+ get :new
- expect(response).to render_template(:new)
- expect(flash[:warning]).to be_nil
- end
+ result = {
+ request_path: idv_session_path,
+ }
- it 'does not increment attempts count' do
- expect { post :create, params: { profile: partial_attrs } }.
- to_not change(user, :idv_attempts)
- end
+ expect(@analytics).to have_received(:track_event).
+ with(Analytics::IDV_MAX_ATTEMPTS_EXCEEDED, result)
+ expect(response).to redirect_to idv_session_failure_url(:fail)
end
end
+ end
- describe '#show' do
- before do
- stub_analytics
- allow(@analytics).to receive(:track_event)
- end
-
- context 'when the background job is not complete yet' do
- render_views
-
- it 'renders a spinner and has the page refresh' do
- get :show
-
- expect(response).to render_template('shared/refresh')
-
- dom = Nokogiri::HTML(response.body)
- expect(dom.css('meta[http-equiv="refresh"]')).to be_present
- end
- end
-
- context 'when the background job has timed out' do
- let(:expired_started_at) do
- Time.zone.now.to_i - Figaro.env.async_job_refresh_max_wait_seconds.to_i
- end
-
- before do
- controller.idv_session.async_result_started_at = expired_started_at
- controller.idv_session.params = user_attrs
- end
+ describe '#create' do
+ it 'assigns a UUID to the applicant' do
+ post :create, params: { profile: user_attrs }
- it 'displays an error' do
- get :show
+ expect(subject.idv_session.applicant['uuid']).to eq subject.current_user.uuid
+ end
- expect(response).to redirect_to(idv_session_failure_url(:timeout))
- end
+ it 'redirects to the SSN error if the SSN exists' do
+ create(:profile, pii: { ssn: '666-66-1234' })
- it 'tracks the failure as a timeout' do
- stub_analytics
- allow(@analytics).to receive(:track_event)
+ result = {
+ success: false,
+ errors: { ssn: [t('idv.errors.duplicate_ssn')] },
+ }
- get :show
+ expect(@analytics).to receive(:track_event).
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_FORM, result)
- result = {
- success: false,
- errors: { timed_out: ['Timed out waiting for vendor response'] },
- idv_attempts_exceeded: false,
- vendor: { messages: [], context: {}, exception: nil },
- }
+ post :create, params: { profile: user_attrs.merge(ssn: '666-66-1234') }
- expect(@analytics).to have_received(:track_event).with(
- Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result
- )
- end
- end
+ expect(response).to redirect_to(idv_session_failure_url(:dupe_ssn))
+ expect(idv_session.profile_confirmation).to be_falsy
+ expect(idv_session.resolution_successful).to be_falsy
+ end
- context 'when the background job has completed' do
- let(:result_id) { SecureRandom.uuid }
- let(:params) { user_attrs }
+ it 'renders the forms if there are missing fields' do
+ partial_attrs = user_attrs.tap { |attrs| attrs.delete :first_name }
- before do
- controller.idv_session.async_result_id = result_id
- VendorValidatorResultStorage.new.store(result_id: result_id, result: result)
+ result = {
+ success: false,
+ errors: { first_name: [t('errors.messages.blank')] },
+ }
- controller.idv_session.params = params
- end
+ expect(@analytics).to receive(:track_event).
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_FORM, result)
- context 'un-resolvable attributes' do
- let(:params) { user_attrs.dup.merge(first_name: 'Bad') }
-
- let(:result) do
- Idv::VendorResult.new(
- success: false,
- errors: { first_name: ['Unverified first name.'] },
- messages: ['The name was suspicious']
- )
- end
-
- it 'redirects to the failure page' do
- get :show
-
- expect(response).to redirect_to(idv_session_failure_url(:warning))
- end
-
- it 'creates analytics event' do
- get :show
-
- result = {
- success: false,
- idv_attempts_exceeded: false,
- errors: {
- first_name: ['Unverified first name.'],
- },
- vendor: { messages: ['The name was suspicious'], context: {}, exception: nil },
- }
-
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result)
- end
- end
+ expect { post :create, params: { profile: partial_attrs } }.
+ to_not change(user, :idv_attempts)
- context 'vendor agent throws exception' do
- let(:params) { user_attrs.dup.merge(first_name: 'Fail') }
- let(:exception_msg) { 'Failed to contact proofing vendor' }
- let(:result) do
- Idv::VendorResult.new(
- success: false,
- errors: { agent: [exception_msg] },
- messages: [exception_msg]
- )
- end
-
- it 'logs failure and redirects to the failure page' do
- get :show
-
- result = {
- success: false,
- idv_attempts_exceeded: false,
- errors: {
- agent: [exception_msg],
- },
- vendor: { messages: [exception_msg], context: {}, exception: nil },
- }
-
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result)
- expect(response).to redirect_to(idv_session_failure_url(:warning))
- end
- end
+ expect(response).to render_template(:new)
+ expect(flash[:warning]).to be_nil
+ expect(idv_session.profile_confirmation).to be_falsy
+ expect(idv_session.resolution_successful).to be_falsy
+ end
- context 'success' do
- let(:result) do
- Idv::VendorResult.new(
- success: true,
- messages: ['Everything looks good'],
- applicant: applicant
- )
- end
-
- it 'creates analytics event' do
- get :show
-
- result = {
- success: true,
- idv_attempts_exceeded: false,
- errors: {},
- vendor: { messages: ['Everything looks good'], context: {}, exception: nil },
- }
-
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result)
- end
-
- it 'increments attempts count' do
- expect { get :show }.to change(user, :idv_attempts).by(1)
- end
- end
+ it 'redirects to the warning page and increments attempts when verification fails' do
+ user_attrs[:first_name] = 'Bad'
+
+ context = { stages: [{ resolution: 'ResolutionMock' }] }
+ result = {
+ success: false,
+ idv_attempts_exceeded: false,
+ errors: {
+ first_name: ['Unverified first name.'],
+ },
+ vendor: { messages: [], context: context, exception: nil, timed_out: false },
+ }
+
+ expect(@analytics).to receive(:track_event).ordered.
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_FORM, hash_including(success: true))
+ expect(@analytics).to receive(:track_event).ordered.
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result)
+
+ expect { post :create, params: { profile: user_attrs } }.
+ to change(user, :idv_attempts).by(1)
+
+ expect(response).to redirect_to(idv_session_failure_url(:warning))
+ expect(idv_session.profile_confirmation).to be_falsy
+ expect(idv_session.resolution_successful).to be_falsy
+ end
- context 'max attempts exceeded' do
- let(:result) { Idv::VendorResult.new(success: true) }
+ it 'redirects to the success page when verification succeeds' do
+ context = { stages: [{ resolution: 'ResolutionMock' }, { state_id: 'StateIdMock' }] }
+ result = {
+ success: true,
+ idv_attempts_exceeded: false,
+ errors: {},
+ vendor: { messages: [], context: context, exception: nil, timed_out: false },
+ }
+
+ expect(@analytics).to receive(:track_event).ordered.
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_FORM, hash_including(success: true))
+ expect(@analytics).to receive(:track_event).ordered.
+ with(Analytics::IDV_BASIC_INFO_SUBMITTED_VENDOR, result)
+
+ expect { post :create, params: { profile: user_attrs } }.
+ to change(user, :idv_attempts).by(1)
+
+ expect(response).to redirect_to(idv_session_success_url)
+ expect(idv_session.profile_confirmation).to eq(true)
+ expect(idv_session.resolution_successful).to eq(true)
+ end
- before do
- user.idv_attempts = max_attempts
- user.idv_attempted_at = Time.zone.now
- end
+ it 'redirects to the fail page when max attempts are exceeded' do
+ user.idv_attempts = max_attempts
+ user.idv_attempted_at = Time.zone.now
- it 'redirects to fail' do
- get :show
+ post :create, params: { profile: user_attrs }
- result = {
- request_path: idv_session_result_path,
- }
+ result = {
+ request_path: idv_session_path,
+ }
- expect(@analytics).to have_received(:track_event).
- with(Analytics::IDV_MAX_ATTEMPTS_EXCEEDED, result)
- expect(response).to redirect_to idv_session_failure_url(:fail)
- end
- end
+ expect(@analytics).to have_received(:track_event).
+ with(Analytics::IDV_MAX_ATTEMPTS_EXCEEDED, result)
+ expect(response).to redirect_to idv_session_failure_url(:fail)
+ expect(idv_session.profile_confirmation).to be_falsy
+ expect(idv_session.resolution_successful).to be_falsy
+ end
+ end
- context 'attempt window has expired, previous attempts == max-1' do
- let(:result) do
- Idv::VendorResult.new(success: true, applicant: applicant)
- end
+ describe '#failure' do
+ it 'renders the dup ssn error if the failure reason is dupe ssn' do
+ get :failure, params: { reason: :dupe_ssn }
- before do
- user.idv_attempts = max_attempts - 1
- user.idv_attempted_at = Time.zone.now - 2.days
- end
+ expect(response).to render_template('shared/_failure')
+ end
- it 'allows and resets attempt counter' do
- get :show
+ it 'renders the error for the the given error case if the failure reason is not dupe ssn' do
+ expect(controller).to receive(:render_idv_step_failure).with(:sessions, :fail)
- expect(response).to redirect_to idv_session_success_path
- expect(user.idv_attempts).to eq 1
- end
- end
- end
+ get :failure, params: { reason: :fail }
end
+ end
+ context 'user has created account' do
describe '#failure' do
context 'reason == :dupe_ssn' do
it 'renders the dupe_ssn failure screen' do
diff --git a/spec/controllers/idv_controller_spec.rb b/spec/controllers/idv_controller_spec.rb
index ec974cfab0a..a4c1231288c 100644
--- a/spec/controllers/idv_controller_spec.rb
+++ b/spec/controllers/idv_controller_spec.rb
@@ -2,6 +2,10 @@
describe IdvController do
describe '#index' do
+ before do
+ allow(FeatureManagement).to receive(:doc_auth_enabled?).and_return(false)
+ end
+
it 'tracks page visit' do
stub_sign_in
stub_analytics
@@ -44,6 +48,16 @@
expect(response).to redirect_to reactivate_account_url
end
+
+ it 'redirects to doc auth if doc auth is enabled and exclusive' do
+ stub_sign_in
+ allow(FeatureManagement).to receive(:doc_auth_enabled?).and_return(true)
+ allow(FeatureManagement).to receive(:doc_auth_exclusive?).and_return(true)
+
+ get :index
+
+ expect(response).to redirect_to idv_doc_auth_path
+ end
end
describe '#activated' do
diff --git a/spec/controllers/test/piv_cac_authentication_test_subject_controller_spec.rb b/spec/controllers/test/piv_cac_authentication_test_subject_controller_spec.rb
index c691bd87d70..ff87a1acbfa 100644
--- a/spec/controllers/test/piv_cac_authentication_test_subject_controller_spec.rb
+++ b/spec/controllers/test/piv_cac_authentication_test_subject_controller_spec.rb
@@ -5,12 +5,11 @@
before(:each) do
allow(Rails.env).to receive(:development?) { false }
allow(Figaro.env).to receive(:enable_test_routes) { 'true' }
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'true' }
end
- describe 'FeatureManagement#development_and_piv_cac_entry_enabled?' do
+ describe 'FeatureManagement#development_and_identity_pki_disabled?' do
it 'is disabled' do
- expect(FeatureManagement.development_and_piv_cac_entry_enabled?).to be_falsey
+ expect(FeatureManagement.development_and_identity_pki_disabled?).to be_falsey
end
end
@@ -39,12 +38,11 @@
before(:each) do
allow(Rails.env).to receive(:development?) { true }
allow(Figaro.env).to receive(:enable_test_routes) { 'true' }
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'true' }
end
- describe 'FeatureManagement#development_and_piv_cac_entry_enabled?' do
+ describe 'FeatureManagement#development_and_identity_pki_disabled?' do
it 'is enabled' do
- expect(FeatureManagement.development_and_piv_cac_entry_enabled?).to be_truthy
+ expect(FeatureManagement.development_and_identity_pki_disabled?).to be_truthy
end
end
diff --git a/spec/controllers/two_factor_authentication/options_controller_spec.rb b/spec/controllers/two_factor_authentication/options_controller_spec.rb
index a2ad7631828..812bb3d4fc8 100644
--- a/spec/controllers/two_factor_authentication/options_controller_spec.rb
+++ b/spec/controllers/two_factor_authentication/options_controller_spec.rb
@@ -24,6 +24,18 @@
describe '#create' do
before { sign_in_before_2fa }
+ it 'redirects to login_two_factor_url for sms with piv/cac and webauthn disabled' do
+ piv_cac_webauthn_enabled('false')
+
+ post :create, params: { two_factor_options_form: { selection: 'sms' } }
+
+ expect(response).to redirect_to otp_send_url( \
+ otp_delivery_selection_form: { otp_delivery_preference: 'sms' }
+ )
+
+ piv_cac_webauthn_enabled('true')
+ end
+
it 'redirects to login_two_factor_url if user selects sms' do
post :create, params: { two_factor_options_form: { selection: 'sms' } }
@@ -52,6 +64,12 @@
expect(response).to redirect_to login_two_factor_authenticator_url
end
+ it 'redirects to login_two_factor_webauthn_url if user selects webauthn' do
+ post :create, params: { two_factor_options_form: { selection: 'webauthn' } }
+
+ expect(response).to redirect_to login_two_factor_webauthn_url
+ end
+
it 'rerenders the page with errors on failure' do
post :create, params: { two_factor_options_form: { selection: 'foo' } }
@@ -74,4 +92,9 @@
post :create, params: { two_factor_options_form: { selection: 'sms' } }
end
end
+
+ def piv_cac_webauthn_enabled(bool)
+ allow(Figaro.env).to receive(:webauthn_enabled) { bool }
+ Rails.application.reload_routes!
+ end
end
diff --git a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb
index da6606f3cb7..d04dc55eb49 100644
--- a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb
+++ b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb
@@ -100,7 +100,7 @@
end
it 'displays flash error message' do
- expect(flash[:error]).to eq t('devise.two_factor_authentication.invalid_otp')
+ expect(flash[:error]).to eq t('two_factor_authentication.invalid_otp')
end
end
@@ -264,7 +264,8 @@
sign_in_as_user
subject.user_session[:unconfirmed_phone] = '+1 (703) 555-5555'
subject.user_session[:context] = 'confirmation'
- @previous_phone_confirmed_at = subject.current_user.phone_configurations.first&.confirmed_at
+ @previous_phone_confirmed_at =
+ MfaContext.new(subject.current_user).phone_configurations.first&.confirmed_at
subject.current_user.create_direct_otp
stub_analytics
allow(@analytics).to receive(:track_event)
@@ -272,7 +273,7 @@
@mailer = instance_double(ActionMailer::MessageDelivery, deliver_later: true)
allow(UserMailer).to receive(:phone_changed).with(subject.current_user).
and_return(@mailer)
- @previous_phone = subject.current_user.phone_configurations.first&.phone
+ @previous_phone = MfaContext.new(subject.current_user).phone_configurations.first&.phone
end
context 'user has an existing phone number' do
@@ -322,10 +323,9 @@
end
it 'does not update user phone or phone_confirmed_at attributes' do
- expect(subject.current_user.phone_configurations.first.phone).to eq('+1 202-555-1212')
- expect(
- subject.current_user.phone_configurations.first.confirmed_at
- ).to eq(@previous_phone_confirmed_at)
+ first_configuration = MfaContext.new(subject.current_user).phone_configurations.first
+ expect(first_configuration.phone).to eq('+1 202-555-1212')
+ expect(first_configuration.confirmed_at).to eq(@previous_phone_confirmed_at)
end
it 'renders :show' do
@@ -333,7 +333,7 @@
end
it 'displays error flash notice' do
- expect(flash[:error]).to eq t('devise.two_factor_authentication.invalid_otp')
+ expect(flash[:error]).to eq t('two_factor_authentication.invalid_otp')
end
it 'tracks an event' do
@@ -353,7 +353,7 @@
context 'when user does not have an existing phone number' do
before do
- subject.current_user.phone_configurations.clear
+ MfaContext.new(subject.current_user).phone_configurations.clear
subject.current_user.create_direct_otp
end
diff --git a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb
index 84ee076916e..f011714cde1 100644
--- a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb
+++ b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb
@@ -94,7 +94,7 @@
post :create, params: payload
expect(response).to render_template(:show)
- expect(flash[:error]).to eq t('devise.two_factor_authentication.invalid_personal_key')
+ expect(flash[:error]).to eq t('two_factor_authentication.invalid_personal_key')
end
end
@@ -120,7 +120,7 @@
post :create, params: payload
expect(response).to render_template(:show)
- expect(flash[:error]).to eq t('devise.two_factor_authentication.invalid_personal_key')
+ expect(flash[:error]).to eq t('two_factor_authentication.invalid_personal_key')
end
it 'tracks the max attempts event' do
diff --git a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb
index 3f7dbc95674..d4ee8d27217 100644
--- a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb
+++ b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb
@@ -54,7 +54,13 @@
end
it 'redirects to the profile' do
- expect(subject.current_user).to receive(:confirm_piv_cac?).and_return(true)
+ mock_mfa = MfaContext.new(subject.current_user)
+ mock_piv_cac_configuration = mock_mfa.piv_cac_configuration
+ allow(mock_piv_cac_configuration).to receive(:mfa_confirmed?).and_return(true)
+ allow(mock_mfa).to receive(:piv_cac_configuration).and_return(
+ mock_piv_cac_configuration
+ )
+ allow(MfaContext).to receive(:new).with(subject.current_user).and_return(mock_mfa)
expect(subject.current_user.reload.second_factor_attempts_count).to eq 0
get :show, params: { token: 'good-token' }
@@ -107,7 +113,7 @@
end
it 'displays flash error message' do
- expect(flash[:error]).to eq t('devise.two_factor_authentication.invalid_piv_cac')
+ expect(flash[:error]).to eq t('two_factor_authentication.invalid_piv_cac')
end
it 'resets the piv/cac session information' do
@@ -131,7 +137,7 @@
end
it 'displays flash error message' do
- expect(flash[:error]).to eq t('devise.two_factor_authentication.invalid_piv_cac')
+ expect(flash[:error]).to eq t('two_factor_authentication.invalid_piv_cac')
end
it 'resets the piv/cac session information' do
diff --git a/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb
index 519dba532ac..d1323bd6558 100644
--- a/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb
+++ b/spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb
@@ -59,7 +59,7 @@
end
it 'displays flash error message' do
- expect(flash[:error]).to eq t('devise.two_factor_authentication.invalid_otp')
+ expect(flash[:error]).to eq t('two_factor_authentication.invalid_otp')
end
end
diff --git a/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb
new file mode 100644
index 00000000000..3fb40cfa2ad
--- /dev/null
+++ b/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb
@@ -0,0 +1,91 @@
+require 'rails_helper'
+
+describe TwoFactorAuthentication::WebauthnVerificationController do
+ include WebauthnVerificationHelper
+
+ describe 'before_actions' do
+ it 'includes appropriate before_actions' do
+ expect(subject).to have_actions(:before, :confirm_webauthn_enabled)
+ end
+ end
+
+ describe 'when not signed in' do
+ describe 'GET show' do
+ it 'redirects to root url' do
+ get :show
+
+ expect(response).to redirect_to(root_url)
+ end
+ end
+
+ describe 'patch confirm' do
+ it 'redirects to root url' do
+ patch :confirm
+
+ expect(response).to redirect_to(root_url)
+ end
+ end
+ end
+
+ describe 'when signed in before 2fa' do
+ before do
+ stub_analytics
+ sign_in_before_2fa
+ end
+
+ describe 'GET show' do
+ it 'saves challenge in session and renders show' do
+ create_webauthn_configuration(controller.current_user)
+ get :show
+
+ expect(subject.user_session[:webauthn_challenge].length).to eq(16)
+ expect(response).to render_template(:show)
+ end
+
+ it 'redirects if no webauthn configured' do
+ get :show
+
+ expect(response).to redirect_to(user_two_factor_authentication_url)
+ end
+ end
+
+ describe 'patch confirm' do
+ let(:params) do
+ {
+ authenticator_data: authenticator_data,
+ client_data_json: client_data_json,
+ signature: signature,
+ credential_id: credential_id,
+ }
+ end
+ before do
+ controller.user_session[:webauthn_challenge] = challenge
+ create_webauthn_configuration(controller.current_user)
+ end
+
+ it 'processes an invalid webauthn' do
+ # the wrong domain name is embedded in the assertion test data
+ patch :confirm, params: params
+
+ expect(response).to redirect_to(login_two_factor_webauthn_url)
+ end
+
+ it 'processes a valid webauthn' do
+ allow(WebauthnVerificationForm).to receive(:domain_name).and_return('localhost:3000')
+ patch :confirm, params: params
+
+ expect(response).to redirect_to(account_url)
+ end
+
+ it 'tracks the submission' do
+ allow(WebauthnVerificationForm).to receive(:domain_name).and_return('localhost:3000')
+ result = { context: 'authentication', errors: {}, multi_factor_auth_method: 'webauthn',
+ success: true }
+ expect(@analytics).to receive(:track_event).
+ with(Analytics::MULTI_FACTOR_AUTH, result)
+
+ patch :confirm, params: params
+ end
+ end
+ end
+end
diff --git a/spec/controllers/users/personal_keys_controller_spec.rb b/spec/controllers/users/personal_keys_controller_spec.rb
index 2eaab123ecd..386fd0a1608 100644
--- a/spec/controllers/users/personal_keys_controller_spec.rb
+++ b/spec/controllers/users/personal_keys_controller_spec.rb
@@ -129,14 +129,6 @@
expect(response).to redirect_to manage_personal_key_path
end
- it 'populates the flash when resending code' do
- stub_sign_in
- expect(flash[:success]).to be_nil
-
- post :create, params: { resend: true }
- expect(flash[:success]).to eq t('notices.send_code.personal_key')
- end
-
it 'tracks CSRF errors' do
stub_sign_in
stub_analytics
diff --git a/spec/controllers/users/phone_setup_controller_spec.rb b/spec/controllers/users/phone_setup_controller_spec.rb
index d96c2364cd6..a1afa5f8e90 100644
--- a/spec/controllers/users/phone_setup_controller_spec.rb
+++ b/spec/controllers/users/phone_setup_controller_spec.rb
@@ -21,7 +21,7 @@
expect(@analytics).to receive(:track_event).
with(Analytics::USER_REGISTRATION_PHONE_SETUP_VISIT)
expect(PhoneSetupPresenter).to receive(:new).with(user.otp_delivery_preference)
- expect(UserPhoneForm).to receive(:new).with(user)
+ expect(UserPhoneForm).to receive(:new).with(user, nil)
get :index
diff --git a/spec/controllers/users/phones_controller_spec.rb b/spec/controllers/users/phones_controller_spec.rb
index fbb5b11a0c9..a10ee8b3fb2 100644
--- a/spec/controllers/users/phones_controller_spec.rb
+++ b/spec/controllers/users/phones_controller_spec.rb
@@ -25,7 +25,9 @@
it 'lets user know they need to confirm their new phone' do
expect(flash[:notice]).to eq t('devise.registrations.phone_update_needs_confirmation')
- expect(user.phone_configurations.reload.first.phone).to_not eq '+1 202-555-4321'
+ expect(
+ MfaContext.new(user).phone_configurations.reload.first.phone
+ ).to_not eq '+1 202-555-4321'
expect(@analytics).to have_received(:track_event).
with(Analytics::PHONE_CHANGE_REQUESTED)
expect(response).to redirect_to(
@@ -47,7 +49,7 @@
otp_delivery_preference: 'sms' },
}
- expect(user.phone_configurations.reload.first).to be_present
+ expect(MfaContext.new(user).phone_configurations.reload.first).to be_present
expect(response).to render_template(:edit)
end
end
@@ -60,7 +62,7 @@
allow(@analytics).to receive(:track_event)
put :update, params: {
- user_phone_form: { phone: second_user.phone_configurations.first.phone,
+ user_phone_form: { phone: MfaContext.new(second_user).phone_configurations.first.phone,
international_code: 'US',
otp_delivery_preference: 'sms' },
}
@@ -68,8 +70,8 @@
it 'processes successfully and informs user' do
expect(flash[:notice]).to eq t('devise.registrations.phone_update_needs_confirmation')
- expect(user.phone_configurations.reload.first.phone).to_not eq(
- second_user.phone_configurations.first.phone
+ expect(MfaContext.new(user).phone_configurations.reload.first.phone).to_not eq(
+ MfaContext.new(second_user).phone_configurations.first.phone
)
expect(@analytics).to have_received(:track_event).
with(Analytics::PHONE_CHANGE_REQUESTED)
@@ -94,7 +96,7 @@
otp_delivery_preference: 'sms' },
}
- expect(user.phone_configurations.first.phone).not_to eq invalid_phone
+ expect(MfaContext.new(user).phone_configurations.first.phone).not_to eq invalid_phone
expect(response).to render_template(:edit)
end
end
@@ -104,7 +106,7 @@
stub_sign_in(user)
put :update, params: {
- user_phone_form: { phone: user.phone_configurations.first.phone,
+ user_phone_form: { phone: MfaContext.new(user).phone_configurations.first.phone,
international_code: 'US',
otp_delivery_preference: 'sms' },
}
diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb
index 2287da46a7f..d5dda6d4240 100644
--- a/spec/controllers/users/two_factor_authentication_controller_spec.rb
+++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb
@@ -71,8 +71,12 @@ def index
describe '#show' do
context 'when user is piv/cac enabled' do
it 'renders the piv/cac entry screen' do
- stub_sign_in_before_2fa(build(:user))
- allow(subject.current_user).to receive(:piv_cac_enabled?).and_return(true)
+ user = build(:user)
+ stub_sign_in_before_2fa(user)
+ allow_any_instance_of(
+ TwoFactorAuthentication::PivCacPolicy
+ ).to receive(:enabled?).and_return(true)
+
get :show
expect(response).to redirect_to login_two_factor_piv_cac_path
@@ -81,14 +85,32 @@ def index
context 'when user is TOTP enabled' do
it 'renders the :confirm_totp view' do
- stub_sign_in_before_2fa(build(:user))
- allow(subject.current_user).to receive(:totp_enabled?).and_return(true)
+ user = build(:user)
+ stub_sign_in_before_2fa(user)
+ allow_any_instance_of(
+ TwoFactorAuthentication::AuthAppPolicy
+ ).to receive(:enabled?).and_return(true)
+
get :show
expect(response).to redirect_to login_two_factor_authenticator_path
end
end
+ context 'when user is webauthn enabled' do
+ it 'renders the :webauthn view' do
+ stub_sign_in_before_2fa(build(:user, :with_webauthn))
+
+ allow_any_instance_of(
+ TwoFactorAuthentication::WebauthnPolicy
+ ).to receive(:enabled?).and_return(true)
+
+ get :show
+
+ expect(response).to redirect_to login_two_factor_webauthn_path
+ end
+ end
+
context 'when there is no session (signed out or locked out), and the user reloads the page' do
it 'redirects to the home page' do
expect(controller.user_session).to be_nil
@@ -134,7 +156,7 @@ def index
expect(SmsOtpSenderJob).to have_received(:perform_later).with(
code: subject.current_user.direct_otp,
- phone: subject.current_user.phone_configurations.first.phone,
+ phone: MfaContext.new(subject.current_user).phone_configurations.first.phone,
otp_created_at: subject.current_user.direct_otp_sent_at.to_s,
message: 'jobs.sms_otp_sender_job.login_message',
locale: nil
@@ -151,7 +173,7 @@ def index
expect(SmsOtpSenderJob).to have_received(:perform_later).with(
code: subject.current_user.direct_otp,
- phone: subject.current_user.phone_configurations.first.phone,
+ phone: MfaContext.new(subject.current_user).phone_configurations.first.phone,
otp_created_at: subject.current_user.direct_otp_sent_at.to_s,
message: 'jobs.sms_otp_sender_job.login_message',
locale: nil
@@ -180,7 +202,7 @@ def index
it 'calls OtpRateLimiter#exceeded_otp_send_limit? and #increment' do
otp_rate_limiter = instance_double(OtpRateLimiter)
allow(OtpRateLimiter).to receive(:new).with(
- phone: @user.phone_configurations.first.phone,
+ phone: MfaContext.new(@user).phone_configurations.first.phone,
user: @user
).and_return(otp_rate_limiter)
@@ -218,7 +240,7 @@ def index
expect(VoiceOtpSenderJob).to have_received(:perform_later).with(
code: subject.current_user.direct_otp,
- phone: subject.current_user.phone_configurations.first.phone,
+ phone: MfaContext.new(subject.current_user).phone_configurations.first.phone,
otp_created_at: subject.current_user.direct_otp_sent_at.to_s,
locale: nil
)
diff --git a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb
index e5af8d4b2e5..a722d4bd861 100644
--- a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb
+++ b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb
@@ -138,6 +138,20 @@
end
end
+ context 'when the selection is webauthn' do
+ it 'redirects to webauthn setup page' do
+ stub_sign_in_before_2fa
+
+ patch :create, params: {
+ two_factor_options_form: {
+ selection: 'webauthn',
+ },
+ }
+
+ expect(response).to redirect_to webauthn_setup_url
+ end
+ end
+
context 'when the selection is piv_cac' do
it 'redirects to piv/cac setup page' do
stub_sign_in_before_2fa
diff --git a/spec/controllers/users/webauthn_setup_controller_spec.rb b/spec/controllers/users/webauthn_setup_controller_spec.rb
index 2ac17d1fe70..eb26e3390d5 100644
--- a/spec/controllers/users/webauthn_setup_controller_spec.rb
+++ b/spec/controllers/users/webauthn_setup_controller_spec.rb
@@ -31,10 +31,11 @@
end
end
- describe 'when signed in' do
+ describe 'when signed in and not account creation' do
before do
stub_analytics
- stub_sign_in
+ user = build(:user, personal_key: 'ABCD-DEFG-HIJK-LMNO')
+ stub_sign_in(user)
end
describe 'GET new' do
@@ -67,7 +68,7 @@
controller.user_session[:webauthn_challenge] = challenge
end
- it 'processes a valid webauthn' do
+ it 'processes a valid webauthn and redirects to account page' do
patch :confirm, params: params
expect(response).to redirect_to(account_url)
@@ -85,7 +86,14 @@
describe 'delete' do
before do
- allow(controller.current_user).to receive(:total_mfa_options_enabled).and_return(2)
+ mock_mfa = MfaContext.new(controller.current_user)
+ allow(mock_mfa).to receive(:enabled_two_factor_configurations_count).and_return(2)
+ allow(MfaContext).to receive(:new).with(controller.current_user).and_return(mock_mfa)
+ mock_mfa_policy = MfaPolicy.new(controller.current_user)
+ allow(mock_mfa_policy).to receive(:multiple_factors_enabled?).and_return(true)
+ allow(MfaPolicy).to receive(:new).with(
+ controller.current_user
+ ).and_return(mock_mfa_policy)
end
it 'deletes a webauthn configuration' do
@@ -108,6 +116,33 @@
end
end
+ describe 'when signed in and account creation' do
+ before do
+ user = build(:user)
+ stub_sign_in(user)
+ end
+
+ describe 'patch confirm' do
+ let(:params) do
+ {
+ attestation_object: attestation_object,
+ client_data_json: client_data_json,
+ name: 'mykey',
+ }
+ end
+ before do
+ allow(Figaro.env).to receive(:domain_name).and_return('localhost:3000')
+ controller.user_session[:webauthn_challenge] = challenge
+ end
+
+ it 'processes a valid webauthn and redirects to personal key page' do
+ patch :confirm, params: params
+
+ expect(response).to redirect_to(sign_up_personal_key_url)
+ end
+ end
+ end
+
def create_webauthn_configuration(user, name, id, key)
WebauthnConfiguration.create(user_id: user.id,
credential_public_key: key,
diff --git a/spec/decorators/mfa_context_spec.rb b/spec/decorators/mfa_context_spec.rb
new file mode 100644
index 00000000000..e3856d3dc70
--- /dev/null
+++ b/spec/decorators/mfa_context_spec.rb
@@ -0,0 +1,120 @@
+require 'rails_helper'
+
+describe MfaContext do
+ let(:mfa) { MfaContext.new(user) }
+
+ context 'with no user' do
+ let(:user) {}
+
+ describe '#auth_app_configuration' do
+ it 'returns a AuthAppConfiguration object' do
+ expect(mfa.auth_app_configuration).to be_a AuthAppConfiguration
+ end
+ end
+
+ describe '#piv_cac_configuration' do
+ it 'returns a PivCacConfiguration object' do
+ expect(mfa.piv_cac_configuration).to be_a PivCacConfiguration
+ end
+ end
+
+ describe '#phone_configurations' do
+ it 'is empty' do
+ expect(mfa.phone_configurations).to be_empty
+ end
+ end
+
+ describe '#webauthn_configurations' do
+ it 'is empty' do
+ expect(mfa.webauthn_configurations).to be_empty
+ end
+
+ it 'has #selection_presenters defined' do
+ expect(mfa.webauthn_configurations).to respond_to(:selection_presenters)
+ end
+
+ it 'has no selection presenters' do
+ expect(mfa.webauthn_configurations.selection_presenters).to be_empty
+ end
+ end
+ end
+
+ context 'with a user' do
+ let(:user) { create(:user) }
+
+ describe '#auth_app_configuration' do
+ it 'returns a AuthAppConfiguration object' do
+ expect(mfa.auth_app_configuration).to be_a AuthAppConfiguration
+ end
+ end
+
+ describe '#piv_cac_configuration' do
+ it 'returns a PivCacConfiguration object' do
+ expect(mfa.piv_cac_configuration).to be_a PivCacConfiguration
+ end
+ end
+
+ describe '#phone_configurations' do
+ it 'mirrors the user relationship' do
+ expect(mfa.phone_configurations).to eq user.phone_configurations
+ end
+ end
+
+ describe '#webauthn_configurations' do
+ context 'with no user' do
+ let(:user) {}
+
+ it 'is empty' do
+ expect(mfa.webauthn_configurations).to be_empty
+ end
+
+ it 'has #selection_presenters defined' do
+ expect(mfa.webauthn_configurations).to respond_to(:selection_presenters)
+ end
+
+ it 'has no selection presenters' do
+ expect(mfa.webauthn_configurations.selection_presenters).to be_empty
+ end
+ end
+
+ describe '#selection_presenters' do
+ it 'is defined' do
+ expect(mfa.webauthn_configurations).to respond_to(:selection_presenters)
+ end
+
+ context 'with no webauthn_configurations' do
+ it 'is empty' do
+ expect(mfa.webauthn_configurations.selection_presenters).to be_empty
+ end
+ end
+
+ context 'with webauthn enabled' do
+ before(:each) do
+ allow(FeatureManagement).to receive(:webauthn_enabled?).and_return(true)
+ end
+
+ context 'with one webauthn_configuration' do
+ let(:user) { create(:user, :with_webauthn) }
+
+ it 'has one element' do
+ expect(mfa.webauthn_configurations.selection_presenters.count).to eq 1
+ end
+ end
+
+ context 'with more than one webauthn_configuration' do
+ let(:user) do
+ record = create(:user)
+ create_list(:webauthn_configuration, 3, user: record)
+ record.webauthn_configurations.reload
+ record
+ end
+
+ it 'has one element' do
+ expect(mfa.webauthn_configurations.selection_presenters.count).to eq 1
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/factories/email_addresses.rb b/spec/factories/email_addresses.rb
new file mode 100644
index 00000000000..d7f45cff689
--- /dev/null
+++ b/spec/factories/email_addresses.rb
@@ -0,0 +1,9 @@
+FactoryBot.define do
+ Faker::Config.locale = :en
+
+ factory :email_address do
+ confirmed_at { Time.zone.now }
+ email { 'jd@example.com' }
+ association :user
+ end
+end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index f9d0be59544..d8335391b33 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -10,6 +10,45 @@
email { Faker::Internet.safe_email }
password { '!1a Z@6s' * 16 } # Maximum length password.
+ trait :with_webauthn do
+ after(:build) do |user, evaluator|
+ if user.webauthn_configurations.empty?
+ user.save!
+ if user.id.present?
+ create(:webauthn_configuration,
+ { user: user }.merge(
+ evaluator.with.slice(:name, :credential_id, :credential_public_key)
+ ))
+ user.webauthn_configurations.reload
+ else
+ user.webauthn_configurations << build(
+ :webauthn_configuration,
+ evaluator.with.slice(:name, :credential_id, :credential_public_key)
+ )
+ end
+ end
+ end
+
+ after(:create) do |user, evaluator|
+ if user.webauthn_configurations.empty?
+ create(:webauthn_configuration,
+ { user: user }.merge(
+ evaluator.with.slice(:name, :credential_id, :credential_public_key)
+ ))
+ user.webauthn_configurations.reload
+ end
+ end
+
+ after(:stub) do |user, evaluator|
+ if user.webauthn_configurations.empty?
+ user.webauthn_configurations << build(
+ :webauthn_configuration,
+ evaluator.with.slice(:name, :credential_id, :credential_public_key)
+ )
+ end
+ end
+ end
+
trait :with_phone do
after(:build) do |user, evaluator|
if user.phone_configurations.empty?
@@ -19,7 +58,7 @@
{ user: user, delivery_preference: user.otp_delivery_preference }.merge(
evaluator.with.slice(:phone, :confirmed_at, :delivery_preference, :mfa_enabled)
))
- user.reload
+ user.phone_configurations.reload
else
user.phone_configurations << build(
:phone_configuration,
@@ -37,7 +76,7 @@
{ user: user, delivery_preference: user.otp_delivery_preference }.merge(
evaluator.with.slice(:phone, :confirmed_at, :delivery_preference, :mfa_enabled)
))
- user.reload
+ user.phone_configurations.reload
end
end
diff --git a/spec/factories/webauthn_configurations.rb b/spec/factories/webauthn_configurations.rb
new file mode 100644
index 00000000000..6fb2ce52aa9
--- /dev/null
+++ b/spec/factories/webauthn_configurations.rb
@@ -0,0 +1,10 @@
+FactoryBot.define do
+ Faker::Config.locale = :en
+
+ factory :webauthn_configuration do
+ sequence(:name) { |n| "token #{n}" }
+ sequence(:credential_id) { |n| "credential #{n}" }
+ sequence(:credential_public_key) { |n| "key #{n}" }
+ user { association :user }
+ end
+end
diff --git a/spec/features/accessibility/idv_pages_spec.rb b/spec/features/accessibility/idv_pages_spec.rb
index e3e1377736d..1d2cbb3f544 100644
--- a/spec/features/accessibility/idv_pages_spec.rb
+++ b/spec/features/accessibility/idv_pages_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
require 'axe/rspec'
-feature 'Accessibility on IDV pages', :js, idv_job: true do
+feature 'Accessibility on IDV pages', :js do
describe 'IDV pages' do
include IdvHelper
diff --git a/spec/features/account_reset/cancel_request_spec.rb b/spec/features/account_reset/cancel_request_spec.rb
index d2524fac848..0596e0d54f7 100644
--- a/spec/features/account_reset/cancel_request_spec.rb
+++ b/spec/features/account_reset/cancel_request_spec.rb
@@ -7,14 +7,15 @@
user = create(:user, :signed_up)
signin(user.email, user.password)
click_link t('two_factor_authentication.login_options_link_text')
- click_link t('devise.two_factor_authentication.account_reset.link')
+ click_link t('two_factor_authentication.account_reset.link')
click_button t('account_reset.request.yes_continue')
open_last_email
click_email_link_matching(/cancel\?token/)
+ click_button t('account_reset.cancel_request.cancel_button')
expect(page).to have_current_path new_user_session_path
expect(page).
- to have_content t('devise.two_factor_authentication.account_reset.successful_cancel')
+ to have_content t('two_factor_authentication.account_reset.successful_cancel')
signin(user.email, user.password)
@@ -29,18 +30,19 @@
user = create(:user, :signed_up)
signin(user.email, user.password)
click_link t('two_factor_authentication.login_options_link_text')
- click_link t('devise.two_factor_authentication.account_reset.link')
+ click_link t('two_factor_authentication.account_reset.link')
click_button t('account_reset.request.yes_continue')
reset_email
Timecop.travel(Time.zone.now + 2.days) do
- AccountResetService.grant_tokens_and_send_notifications
+ AccountReset::GrantRequestsAndSendEmails.new.call
open_last_email
click_email_link_matching(/cancel\?token/)
+ click_button t('account_reset.cancel_request.cancel_button')
expect(page).to have_current_path new_user_session_path
expect(page).
- to have_content t('devise.two_factor_authentication.account_reset.successful_cancel')
+ to have_content t('two_factor_authentication.account_reset.successful_cancel')
signin(user.email, user.password)
diff --git a/spec/features/account_reset/delete_account_spec.rb b/spec/features/account_reset/delete_account_spec.rb
index 6d260c2c291..1439fe77fa5 100644
--- a/spec/features/account_reset/delete_account_spec.rb
+++ b/spec/features/account_reset/delete_account_spec.rb
@@ -11,7 +11,7 @@
it 'allows the user to delete their account after 24 hours' do
signin(user.email, user.password)
click_link t('two_factor_authentication.login_options_link_text')
- click_link t('devise.two_factor_authentication.account_reset.link')
+ click_link t('two_factor_authentication.account_reset.link')
click_button t('account_reset.request.yes_continue')
expect(page).
@@ -24,7 +24,7 @@
reset_email
Timecop.travel(Time.zone.now + 2.days) do
- AccountResetService.grant_tokens_and_send_notifications
+ AccountReset::GrantRequestsAndSendEmails.new.call
open_last_email
click_email_link_matching(/delete_account\?token/)
@@ -58,7 +58,7 @@
user = create(:user, :with_authentication_app)
signin(user.email, user.password)
click_link t('two_factor_authentication.login_options_link_text')
- click_link t('devise.two_factor_authentication.account_reset.link')
+ click_link t('two_factor_authentication.account_reset.link')
click_button t('account_reset.request.yes_continue')
expect(page).
@@ -90,7 +90,7 @@
click_link t('two_factor_authentication.login_options_link_text')
# Account reset link should not be present
- expect(page).to_not have_content(t('devise.two_factor_authentication.account_reset.link'))
+ expect(page).to_not have_content(t('two_factor_authentication.account_reset.link'))
# Visiting account reset directly should redirect to 2FA
visit account_reset_request_path
diff --git a/spec/features/flows/sp_authentication_flows_spec.rb b/spec/features/flows/sp_authentication_flows_spec.rb
index 2d5f9f66598..3e213eb2a87 100644
--- a/spec/features/flows/sp_authentication_flows_spec.rb
+++ b/spec/features/flows/sp_authentication_flows_spec.rb
@@ -7,7 +7,7 @@
I18n.available_locales.each do |locale|
context "with locale=#{locale}" do
context 'with a valid SP' do
- context 'when LOA3', :idv_job do
+ context 'when LOA3' do
before do
visit "#{loa3_authnrequest}&locale=#{locale}"
end
@@ -63,7 +63,7 @@
context 'with SMS delivery' do
before do
- choose t('devise.two_factor_authentication.otp_delivery_preference.sms')
+ choose t('two_factor_authentication.otp_delivery_preference.sms')
click_send_security_code
end
@@ -241,7 +241,7 @@
context 'with SMS delivery' do
before do
- choose t('devise.two_factor_authentication.otp_delivery_preference.sms')
+ choose t('two_factor_authentication.otp_delivery_preference.sms')
click_send_security_code
end
@@ -252,7 +252,7 @@
context 'with Voice delivery' do
before do
- choose t('devise.two_factor_authentication.otp_delivery_preference.voice')
+ choose t('two_factor_authentication.otp_delivery_preference.voice')
click_send_security_code
end
diff --git a/spec/features/idv/account_creation_spec.rb b/spec/features/idv/account_creation_spec.rb
index a76067bc3cd..5d07c25a61a 100644
--- a/spec/features/idv/account_creation_spec.rb
+++ b/spec/features/idv/account_creation_spec.rb
@@ -3,7 +3,11 @@
describe 'LOA3 account creation' do
include IdvHelper
include SamlAuthHelper
+ include WebauthnHelper
it_behaves_like 'creating an LOA3 account using authenticator app for 2FA', :saml
it_behaves_like 'creating an LOA3 account using authenticator app for 2FA', :oidc
+
+ it_behaves_like 'creating an LOA3 account using webauthn for 2FA', :saml
+ it_behaves_like 'creating an LOA3 account using webauthn for 2FA', :oidc
end
diff --git a/spec/features/idv/actions/reset_action_spec.rb b/spec/features/idv/actions/reset_action_spec.rb
new file mode 100644
index 00000000000..1b94670458b
--- /dev/null
+++ b/spec/features/idv/actions/reset_action_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+feature 'doc auth reset action' do
+ include IdvStepHelper
+ include DocAuthHelper
+
+ before do
+ enable_doc_auth
+ complete_doc_auth_steps_before_front_image_step
+ end
+
+ it 'resets doc auth to the first step' do
+ expect(page).to have_current_path(idv_doc_auth_front_image_step)
+
+ click_on t('doc_auth.buttons.start_over')
+
+ expect(page).to have_current_path(idv_doc_auth_ssn_step)
+ end
+end
diff --git a/spec/features/idv/doc_auth/back_image_step_spec.rb b/spec/features/idv/doc_auth/back_image_step_spec.rb
new file mode 100644
index 00000000000..31a221ccb1b
--- /dev/null
+++ b/spec/features/idv/doc_auth/back_image_step_spec.rb
@@ -0,0 +1,51 @@
+require 'rails_helper'
+
+feature 'doc auth back image step' do
+ include IdvStepHelper
+ include DocAuthHelper
+
+ before do
+ enable_doc_auth
+ complete_doc_auth_steps_before_back_image_step
+ mock_assure_id_ok
+ end
+
+ it 'is on the correct page' do
+ expect(page).to have_current_path(idv_doc_auth_back_image_step)
+ expect(page).to have_content(t('doc_auth.headings.upload_back'))
+ end
+
+ it 'proceeds to the next page with valid info' do
+ attach_image
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_doc_success_step)
+ end
+
+ it 'does not proceed to the next page if resolution fails' do
+ allow_any_instance_of(Idv::Agent).to receive(:proof).
+ and_return(success: false, errors: {})
+ attach_image
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_doc_failed_step)
+ end
+
+ it 'does not proceed to the next page with invalid info' do
+ allow_any_instance_of(Idv::Acuant::AssureId).to receive(:post_back_image).
+ and_return([false, ''])
+ attach_image
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_back_image_step)
+ end
+
+ it 'does not proceed to the next page with result=2' do
+ allow_any_instance_of(Idv::Acuant::AssureId).to receive(:results).
+ and_return([true, assure_id_results_with_result_2])
+ attach_image
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_back_image_step)
+ end
+end
diff --git a/spec/features/idv/doc_auth/doc_failed_step_spec.rb b/spec/features/idv/doc_auth/doc_failed_step_spec.rb
new file mode 100644
index 00000000000..52294fb8254
--- /dev/null
+++ b/spec/features/idv/doc_auth/doc_failed_step_spec.rb
@@ -0,0 +1,16 @@
+require 'rails_helper'
+
+feature 'doc auth fail step' do
+ include IdvStepHelper
+ include DocAuthHelper
+
+ before do
+ enable_doc_auth
+ complete_doc_auth_steps_before_doc_failed_step
+ end
+
+ it 'is on the correct page' do
+ expect(page).to have_current_path(idv_doc_auth_doc_failed_step)
+ expect(page).to have_content(t('doc_auth.errors.state_id_fail'))
+ end
+end
diff --git a/spec/features/idv/doc_auth/doc_success_step_spec.rb b/spec/features/idv/doc_auth/doc_success_step_spec.rb
new file mode 100644
index 00000000000..7ae336cca41
--- /dev/null
+++ b/spec/features/idv/doc_auth/doc_success_step_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+feature 'doc auth success step' do
+ include IdvStepHelper
+ include DocAuthHelper
+
+ before do
+ enable_doc_auth
+ complete_doc_auth_steps_before_doc_success_step
+ end
+
+ it 'is on the correct page' do
+ expect(page).to have_current_path(idv_doc_auth_doc_success_step)
+ expect(page).to have_content(t('doc_auth.forms.doc_success'))
+ end
+
+ it 'proceeds to the next page with valid info' do
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_self_image_step)
+ end
+end
diff --git a/spec/features/idv/doc_auth/front_image_step_spec.rb b/spec/features/idv/doc_auth/front_image_step_spec.rb
new file mode 100644
index 00000000000..29eb2e4c064
--- /dev/null
+++ b/spec/features/idv/doc_auth/front_image_step_spec.rb
@@ -0,0 +1,32 @@
+require 'rails_helper'
+
+feature 'doc auth front image step' do
+ include IdvStepHelper
+ include DocAuthHelper
+
+ before do
+ enable_doc_auth
+ complete_doc_auth_steps_before_front_image_step
+ mock_assure_id_ok
+ end
+
+ it 'is on the correct page' do
+ expect(page).to have_current_path(idv_doc_auth_front_image_step)
+ expect(page).to have_content(t('doc_auth.headings.upload_front'))
+ end
+
+ it 'proceeds to the next page with valid info' do
+ attach_image
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_back_image_step)
+ end
+
+ it 'does not proceed to the next page with invalid info' do
+ mock_assure_id_fail
+ attach_image
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_front_image_step)
+ end
+end
diff --git a/spec/features/idv/doc_auth/self_image_step_spec.rb b/spec/features/idv/doc_auth/self_image_step_spec.rb
new file mode 100644
index 00000000000..b3b42981153
--- /dev/null
+++ b/spec/features/idv/doc_auth/self_image_step_spec.rb
@@ -0,0 +1,40 @@
+require 'rails_helper'
+
+feature 'doc auth self image step' do
+ include IdvStepHelper
+ include DocAuthHelper
+
+ before do
+ enable_doc_auth
+ complete_doc_auth_steps_before_self_image_step
+ mock_assure_id_ok
+ end
+
+ it 'is on the correct page' do
+ expect(page).to have_current_path(idv_doc_auth_self_image_step)
+ expect(page).to have_content(t('doc_auth.headings.selfie'))
+ end
+
+ it 'proceeds to the next page with valid info' do
+ first('input#_doc_auth_image', visible: false).set('data:image/png;base64,abc')
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_review_url)
+ end
+
+ it 'does not proceed to the next page with invalid info' do
+ allow_any_instance_of(Idv::Acuant::AssureId).to receive(:face_image).and_return([false, ''])
+
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_self_image_step)
+ end
+
+ it 'creates a doc auth record' do
+ first('input#_doc_auth_image', visible: false).set('data:image/png;base64,abc')
+ click_idv_continue
+
+ expect(DocAuth.count).to eq(1)
+ expect(DocAuth.all[0].license_confirmed_at).to be_present
+ end
+end
diff --git a/spec/features/idv/doc_auth/ssn_step_spec.rb b/spec/features/idv/doc_auth/ssn_step_spec.rb
new file mode 100644
index 00000000000..7c969d9fff6
--- /dev/null
+++ b/spec/features/idv/doc_auth/ssn_step_spec.rb
@@ -0,0 +1,33 @@
+require 'rails_helper'
+
+feature 'doc auth ssn step' do
+ include IdvStepHelper
+ include DocAuthHelper
+
+ before do
+ enable_doc_auth
+ complete_doc_auth_steps_before_ssn_step
+ end
+
+ it 'is on the correct page' do
+ expect(page).to have_current_path(idv_doc_auth_ssn_step)
+ expect(page).to have_content(t('doc_auth.headings.ssn'))
+ end
+
+ it 'proceeds to the next page with valid info' do
+ fill_out_ssn_form_ok
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_front_image_step)
+ end
+
+ it 'does not proceed to the next page with invalid info' do
+ fill_out_ssn_form_fail
+ click_idv_continue
+
+ expect(page).to have_current_path(idv_doc_auth_ssn_step)
+ end
+
+ # it 'prevents a duplicate ssn' do
+ # end
+end
diff --git a/spec/features/idv/phone_input_spec.rb b/spec/features/idv/phone_input_spec.rb
index 8deb53e9abc..6b39470949e 100644
--- a/spec/features/idv/phone_input_spec.rb
+++ b/spec/features/idv/phone_input_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'IdV phone number input', :idv_job, :js do
+feature 'IdV phone number input', :js do
include IdvStepHelper
before do
diff --git a/spec/features/idv/phone_otp_rate_limiting_spec.rb b/spec/features/idv/phone_otp_rate_limiting_spec.rb
index 713333e2596..925e4e34f2d 100644
--- a/spec/features/idv/phone_otp_rate_limiting_spec.rb
+++ b/spec/features/idv/phone_otp_rate_limiting_spec.rb
@@ -61,7 +61,7 @@
def expect_max_otp_request_rate_limiting
expect(page).to have_content t('titles.account_locked')
expect(page).to have_content t(
- 'devise.two_factor_authentication.max_otp_requests_reached'
+ 'two_factor_authentication.max_otp_requests_reached'
)
expect_rate_limit_circumvention_to_be_disallowed(user)
@@ -83,7 +83,7 @@ def expect_max_otp_request_rate_limiting
expect(page).to have_content t('titles.account_locked')
expect(page).
- to have_content t('devise.two_factor_authentication.max_otp_login_attempts_reached')
+ to have_content t('two_factor_authentication.max_otp_login_attempts_reached')
expect_rate_limit_circumvention_to_be_disallowed(user)
expect_rate_limit_to_expire(user)
diff --git a/spec/features/idv/sp_handoff_spec.rb b/spec/features/idv/sp_handoff_spec.rb
index 64f68dcb864..62ed8302980 100644
--- a/spec/features/idv/sp_handoff_spec.rb
+++ b/spec/features/idv/sp_handoff_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'IdV SP handoff', :idv_job, :email do
+feature 'IdV SP handoff', :email do
include SamlAuthHelper
include IdvHelper
diff --git a/spec/features/idv/sp_requested_attributes_spec.rb b/spec/features/idv/sp_requested_attributes_spec.rb
index bf383f07f01..f070653f0a6 100644
--- a/spec/features/idv/sp_requested_attributes_spec.rb
+++ b/spec/features/idv/sp_requested_attributes_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'sp requested IdV attributes', :idv_job, :email do
+feature 'sp requested IdV attributes', :email do
context 'oidc' do
it_behaves_like 'sp requesting attributes', :oidc
end
diff --git a/spec/features/idv/state_id_data_spec.rb b/spec/features/idv/state_id_data_spec.rb
index b7b973727d6..61d502f8f42 100644
--- a/spec/features/idv/state_id_data_spec.rb
+++ b/spec/features/idv/state_id_data_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'idv state id data entry', :idv_job do
+feature 'idv state id data entry' do
include IdvStepHelper
let(:locale) { LinkLocaleResolver.locale }
@@ -19,9 +19,8 @@
expect(current_path).to eq(idv_session_failure_path(:warning, locale: locale))
end
- it 'renders an error for blank state id number and does not submit a job', :email do
- expect(Idv::ProoferJob).to_not receive(:perform_now)
- expect(Idv::ProoferJob).to_not receive(:perform_later)
+ it 'renders an error for blank state id number and does not attempt to proof', :email do
+ expect(Idv::Proofer).to_not receive(:get_vendor)
fill_in :profile_state_id_number, with: ''
click_idv_continue
@@ -31,8 +30,7 @@
end
it 'renders an error for unsupported jurisdiction and does not submit a job', :email do
- expect(Idv::ProoferJob).to_not receive(:perform_now)
- expect(Idv::ProoferJob).to_not receive(:perform_later)
+ expect(Idv::Proofer).to_not receive(:get_vendor)
select 'Alabama', from: 'profile_state'
click_idv_continue
@@ -41,6 +39,16 @@
expect(current_path).to eq(idv_session_path)
end
+ it 'renders an error for a state id that is too long and does not submit a job', :email do
+ expect(Idv::Proofer).to_not receive(:get_vendor)
+
+ fill_in 'profile_state_id_number', with: '8' * 26
+ click_idv_continue
+
+ expect(page).to have_content t('idv.errors.pattern_mismatch.state_id_number')
+ expect(current_path).to eq(idv_session_path)
+ end
+
it 'allows selection of different state id types', :email do
choose 'profile_state_id_type_drivers_permit'
click_idv_continue
diff --git a/spec/features/idv/steps/confirmation_step_spec.rb b/spec/features/idv/steps/confirmation_step_spec.rb
index 0d24b7d25f8..0d11ab90b9a 100644
--- a/spec/features/idv/steps/confirmation_step_spec.rb
+++ b/spec/features/idv/steps/confirmation_step_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'idv confirmation step', :idv_job do
+feature 'idv confirmation step' do
include IdvStepHelper
it_behaves_like 'idv confirmation step'
diff --git a/spec/features/idv/steps/jurisdiction_step_spec.rb b/spec/features/idv/steps/jurisdiction_step_spec.rb
index 75b51f2f9cd..265228dac17 100644
--- a/spec/features/idv/steps/jurisdiction_step_spec.rb
+++ b/spec/features/idv/steps/jurisdiction_step_spec.rb
@@ -45,6 +45,23 @@
end
end
+ it 'is not re-entrant' do
+ start_idv_from_sp
+ complete_idv_steps_before_jurisdiction_step
+
+ select 'Virginia', from: 'jurisdiction_state'
+ click_idv_continue
+ visit idv_jurisdiction_path
+
+ expect(page).to have_current_path(idv_session_path)
+
+ fill_out_idv_form_ok
+ click_idv_continue
+ visit idv_jurisdiction_path
+
+ expect(page).to have_current_path(idv_session_success_path)
+ end
+
context 'cancelling idv' do
it_behaves_like 'cancel at idv step', :jurisdiction
it_behaves_like 'cancel at idv step', :jurisdiction, :oidc
diff --git a/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb b/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb
index f765761b47c..cd86d0cc5ee 100644
--- a/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb
+++ b/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'IdV phone OTP delivery method selection', :idv_job do
+feature 'IdV phone OTP deleivery method selection' do
include IdvStepHelper
context 'the users chooses sms' do
@@ -12,7 +12,7 @@
complete_idv_steps_before_phone_otp_delivery_selection_step
choose_idv_otp_delivery_method_sms
- expect(page).to have_content(t('devise.two_factor_authentication.header_text'))
+ expect(page).to have_content(t('two_factor_authentication.header_text'))
expect(current_path).to eq(idv_otp_verification_path)
end
end
@@ -26,11 +26,25 @@
complete_idv_steps_before_phone_otp_delivery_selection_step
choose_idv_otp_delivery_method_voice
- expect(page).to have_content(t('devise.two_factor_authentication.header_text'))
+ expect(page).to have_content(t('two_factor_authentication.header_text'))
expect(current_path).to eq(idv_otp_verification_path)
end
end
+ context 'the user does not make a selection' do
+ it 'does not send a voice call or sms and renders an error' do
+ expect(VoiceOtpSenderJob).to_not receive(:perform_later)
+ expect(SmsOtpSenderJob).to_not receive(:perform_later)
+
+ start_idv_from_sp
+ complete_idv_steps_before_phone_otp_delivery_selection_step
+ click_on t('idv.buttons.send_confirmation_code')
+
+ expect(page).to have_content(t('idv.errors.unsupported_otp_delivery_method'))
+ expect(current_path).to eq(idv_otp_delivery_method_path)
+ end
+ end
+
context 'with a non-US number' do
let(:bahamas_phone) { '+12423270143' }
diff --git a/spec/features/idv/steps/phone_otp_verification_step_spec.rb b/spec/features/idv/steps/phone_otp_verification_step_spec.rb
index 80302062dd1..e747892221c 100644
--- a/spec/features/idv/steps/phone_otp_verification_step_spec.rb
+++ b/spec/features/idv/steps/phone_otp_verification_step_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'phone otp verification step spec', :idv_job do
+feature 'phone otp verification step spec' do
include IdvStepHelper
let(:otp_code) { '777777' }
@@ -23,7 +23,7 @@
fill_in 'code', with: '000000'
click_submit_default
- expect(page).to have_content(t('devise.two_factor_authentication.invalid_otp'))
+ expect(page).to have_content(t('two_factor_authentication.invalid_otp'))
expect(current_path).to eq(idv_otp_verification_path)
# Enter the correct code
@@ -44,7 +44,7 @@
fill_in(:code, with: otp_code)
click_button t('forms.buttons.submit.default')
- expect(page).to have_content(t('devise.two_factor_authentication.invalid_otp'))
+ expect(page).to have_content(t('two_factor_authentication.invalid_otp'))
expect(page).to have_current_path(idv_otp_verification_path)
end
end
diff --git a/spec/features/idv/steps/phone_step_spec.rb b/spec/features/idv/steps/phone_step_spec.rb
index 7a3a0bbb20b..3a494fb2e4b 100644
--- a/spec/features/idv/steps/phone_step_spec.rb
+++ b/spec/features/idv/steps/phone_step_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'idv phone step', :idv_job do
+feature 'idv phone step' do
include IdvStepHelper
include IdvHelper
@@ -19,7 +19,7 @@
user = user_with_2fa
start_idv_from_sp
complete_idv_steps_before_phone_step(user)
- fill_out_phone_form_ok(user.phone_configurations.first.phone)
+ fill_out_phone_form_ok(MfaContext.new(user).phone_configurations.first.phone)
click_idv_continue
expect(page).to have_content(t('idv.titles.session.review'))
@@ -39,8 +39,8 @@
choose_idv_otp_delivery_method_sms
- expect(page).to have_content(t('devise.two_factor_authentication.header_text'))
- expect(page).to_not have_content(t('devise.two_factor_authentication.totp_header_text'))
+ expect(page).to have_content(t('two_factor_authentication.header_text'))
+ expect(page).to_not have_content(t('two_factor_authentication.totp_header_text'))
expect(page).to_not have_content(t('two_factor_authentication.login_options_link_text'))
end
end
diff --git a/spec/features/idv/steps/profile_step_spec.rb b/spec/features/idv/steps/profile_step_spec.rb
index e68cdfcd6e3..6e62ceb570d 100644
--- a/spec/features/idv/steps/profile_step_spec.rb
+++ b/spec/features/idv/steps/profile_step_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'idv profile step', :idv_job do
+feature 'idv profile step' do
include IdvStepHelper
context 'with valid information' do
diff --git a/spec/features/idv/steps/review_step_spec.rb b/spec/features/idv/steps/review_step_spec.rb
index cb42d043f6c..a310544b005 100644
--- a/spec/features/idv/steps/review_step_spec.rb
+++ b/spec/features/idv/steps/review_step_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'idv review step', :idv_job do
+feature 'idv review step' do
include IdvStepHelper
it 'requires the user to enter the correct password to redirect to confirmation step' do
diff --git a/spec/features/idv/steps/usps_step_spec.rb b/spec/features/idv/steps/usps_step_spec.rb
index fdd31b3cb4b..c8035a070fe 100644
--- a/spec/features/idv/steps/usps_step_spec.rb
+++ b/spec/features/idv/steps/usps_step_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'idv usps step', :idv_job do
+feature 'idv usps step' do
include IdvStepHelper
it 'redirects to the review step when the user chooses to verify by letter' do
diff --git a/spec/features/idv/usps_disabled_spec.rb b/spec/features/idv/usps_disabled_spec.rb
index c5f59e2ae72..42b6b566bff 100644
--- a/spec/features/idv/usps_disabled_spec.rb
+++ b/spec/features/idv/usps_disabled_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'disabling USPS address verification', :idv_job do
+feature 'disabling USPS address verification' do
include IdvStepHelper
context 'with USPS address verification disabled' do
diff --git a/spec/features/openid_connect/openid_connect_spec.rb b/spec/features/openid_connect/openid_connect_spec.rb
index 314a4ca0a65..f819872be7c 100644
--- a/spec/features/openid_connect/openid_connect_spec.rb
+++ b/spec/features/openid_connect/openid_connect_spec.rb
@@ -119,7 +119,7 @@ class MockSession; end
fill_in :code, with: 'wrong otp'
click_submit_default
- expect(page).to have_content(t('devise.two_factor_authentication.invalid_otp'))
+ expect(page).to have_content(t('two_factor_authentication.invalid_otp'))
expect(page.response_headers['Content-Security-Policy']).
to(include('form-action \'self\' http://localhost:7654'))
click_submit_default
diff --git a/spec/features/saml/loa1_sso_spec.rb b/spec/features/saml/loa1_sso_spec.rb
index 4aa7596082e..daf4fa484b0 100644
--- a/spec/features/saml/loa1_sso_spec.rb
+++ b/spec/features/saml/loa1_sso_spec.rb
@@ -114,22 +114,6 @@
expect(current_path).to eq sign_up_completed_path
end
- it 'redirects user to SP after asking for new personal key during sign up' do
- allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true)
-
- saml_authn_request = auth_request.create(saml_settings)
- visit saml_authn_request
- register_user
- click_on t('users.personal_key.get_another')
- click_acknowledge_personal_key
-
- expect(current_path).to eq sign_up_completed_path
-
- click_on t('forms.buttons.continue')
-
- expect(current_url).to eq saml_authn_request
- end
-
it 'after session timeout, signing in takes user back to SP' do
allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true)
diff --git a/spec/features/saml/loa3_sso_spec.rb b/spec/features/saml/loa3_sso_spec.rb
index c1f00f5b9bb..1006943c9d6 100644
--- a/spec/features/saml/loa3_sso_spec.rb
+++ b/spec/features/saml/loa3_sso_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'LOA3 Single Sign On', idv_job: true do
+feature 'LOA3 Single Sign On' do
include SamlAuthHelper
include IdvHelper
diff --git a/spec/features/two_factor_authentication/change_factor_spec.rb b/spec/features/two_factor_authentication/change_factor_spec.rb
index 3d2cf3aa677..f6935ceb28b 100644
--- a/spec/features/two_factor_authentication/change_factor_spec.rb
+++ b/spec/features/two_factor_authentication/change_factor_spec.rb
@@ -20,7 +20,8 @@
mailer = instance_double(ActionMailer::MessageDelivery, deliver_later: true)
allow(UserMailer).to receive(:phone_changed).with(user).and_return(mailer)
- @previous_phone_confirmed_at = user.phone_configurations.reload.first.confirmed_at
+ @previous_phone_confirmed_at =
+ MfaContext.new(user).phone_configurations.reload.first.confirmed_at
new_phone = '+1 703-555-0100'
visit manage_phone_path
@@ -33,13 +34,13 @@
expect(page).to have_link t('links.cancel'), href: account_path
expect(page).to have_link t('forms.two_factor.try_again'), href: manage_phone_path
expect(page).not_to have_content(
- t('devise.two_factor_authentication.personal_key_fallback.text_html')
+ t('two_factor_authentication.personal_key_fallback.text_html')
)
enter_incorrect_otp_code
- expect(page).to have_content t('devise.two_factor_authentication.invalid_otp')
- expect(user.phone_configurations.reload.first.phone).to_not eq new_phone
+ expect(page).to have_content t('two_factor_authentication.invalid_otp')
+ expect(MfaContext.new(user).phone_configurations.reload.first.phone).to_not eq new_phone
expect(page).to have_link t('forms.two_factor.try_again'), href: manage_phone_path
submit_correct_otp
@@ -49,7 +50,7 @@
expect(mailer).to have_received(:deliver_later)
expect(page).to have_content new_phone
expect(
- user.phone_configurations.reload.first.confirmed_at
+ MfaContext.new(user).phone_configurations.reload.first.confirmed_at
).to_not eq(@previous_phone_confirmed_at)
visit login_two_factor_path(otp_delivery_preference: 'sms')
@@ -58,7 +59,7 @@
scenario 'editing phone number with no voice otp support only allows sms delivery' do
user.update(otp_delivery_preference: 'voice')
- user.phone_configurations.first.update(delivery_preference: 'voice')
+ MfaContext.new(user).phone_configurations.first.update(delivery_preference: 'voice')
unsupported_phone = '242-327-0143'
visit manage_phone_path
@@ -81,7 +82,7 @@
allow(SmsOtpSenderJob).to receive(:perform_later)
user = sign_in_and_2fa_user
- old_phone = user.phone_configurations.first.phone
+ old_phone = MfaContext.new(user).phone_configurations.first.phone
visit manage_phone_path
update_phone_number
@@ -108,7 +109,7 @@
allow(SmsOtpSenderJob).to receive(:perform_later)
user = sign_in_and_2fa_user
- old_phone = user.phone_configurations.first.phone
+ old_phone = MfaContext.new(user).phone_configurations.first.phone
Timecop.travel(Figaro.env.reauthn_window.to_i + 1) do
visit manage_phone_path
diff --git a/spec/features/two_factor_authentication/remember_device_spec.rb b/spec/features/two_factor_authentication/remember_device_spec.rb
index 888ad1d5a4d..06c166dd3e3 100644
--- a/spec/features/two_factor_authentication/remember_device_spec.rb
+++ b/spec/features/two_factor_authentication/remember_device_spec.rb
@@ -72,7 +72,7 @@ def remember_device_and_sign_out_user
end
end
- context 'identity verification', :idv_job do
+ context 'identity verification' do
let(:user) { user_with_2fa }
before do
diff --git a/spec/features/two_factor_authentication/sign_in_spec.rb b/spec/features/two_factor_authentication/sign_in_spec.rb
index 31ce0e50b10..be155f1b85b 100644
--- a/spec/features/two_factor_authentication/sign_in_spec.rb
+++ b/spec/features/two_factor_authentication/sign_in_spec.rb
@@ -34,7 +34,7 @@
expect(page).to_not have_content invalid_phone_message
expect(current_path).to eq login_two_factor_path(otp_delivery_preference: 'sms')
- expect(user.phone_configurations).to be_empty
+ expect(MfaContext.new(user).phone_configurations).to be_empty
expect(user.sms?).to eq true
end
@@ -51,7 +51,7 @@
expect(page).to have_content t('titles.account_locked')
expect(page).
- to have_content t('devise.two_factor_authentication.max_otp_login_attempts_reached')
+ to have_content t('two_factor_authentication.max_otp_login_attempts_reached')
end
end
@@ -67,7 +67,7 @@
expect(current_path).to eq phone_setup_path
expect(page).to have_content t(
- 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ 'two_factor_authentication.otp_delivery_preference.phone_unsupported',
location: 'Bahamas'
)
@@ -181,7 +181,7 @@ def submit_2fa_setup_form_with_valid_phone
expect(current_path).to eq login_two_factor_path(otp_delivery_preference: 'sms')
expect(page).
- to have_content t('devise.two_factor_authentication.header_text')
+ to have_content t('two_factor_authentication.header_text')
attempt_to_bypass_2fa
@@ -293,7 +293,7 @@ def submit_prefilled_otp_code
expect(page).to have_content t('titles.account_locked')
expect(page).to have_content(five_minute_countdown_regex)
- expect(page).to have_content t('devise.two_factor_authentication.max_otp_requests_reached')
+ expect(page).to have_content t('two_factor_authentication.max_otp_requests_reached')
visit root_path
signin(user.email, user.password)
@@ -301,7 +301,7 @@ def submit_prefilled_otp_code
expect(page).to have_content t('titles.account_locked')
expect(page).to have_content(five_minute_countdown_regex)
expect(page).
- to have_content t('devise.two_factor_authentication.max_generic_login_attempts_reached')
+ to have_content t('two_factor_authentication.max_generic_login_attempts_reached')
# let lockout period expire
Timecop.travel(lockout_period) do
@@ -349,7 +349,9 @@ def submit_prefilled_otp_code
expect(current_path).to eq account_path
- phone_fingerprint = Pii::Fingerprinter.fingerprint(user.phone_configurations.first.phone)
+ phone_fingerprint = Pii::Fingerprinter.fingerprint(
+ MfaContext.new(user).phone_configurations.first.phone
+ )
rate_limited_phone = OtpRequestsTracker.find_by(phone_fingerprint: phone_fingerprint)
# let findtime period expire
@@ -387,7 +389,7 @@ def submit_prefilled_otp_code
sign_in_before_2fa(second_user)
click_link t('links.two_factor_authentication.get_another_code')
phone_fingerprint = Pii::Fingerprinter.fingerprint(
- first_user.phone_configurations.first.phone
+ MfaContext.new(first_user).phone_configurations.first.phone
)
rate_limited_phone = OtpRequestsTracker.find_by(phone_fingerprint: phone_fingerprint)
@@ -402,7 +404,7 @@ def submit_prefilled_otp_code
signin(first_user.email, first_user.password)
- expect(page).to have_content t('devise.two_factor_authentication.max_otp_requests_reached')
+ expect(page).to have_content t('two_factor_authentication.max_otp_requests_reached')
visit account_path
expect(current_path).to eq root_path
@@ -438,7 +440,7 @@ def submit_prefilled_otp_code
allow_any_instance_of(User).to receive(:max_login_attempts?).and_return(true)
signin(user.email, user.password)
- expect(page).to have_content t('devise.two_factor_authentication.' \
+ expect(page).to have_content t('two_factor_authentication.' \
'max_generic_login_attempts_reached')
visit account_path
@@ -472,7 +474,7 @@ def submit_prefilled_otp_code
click_button t('forms.buttons.submit.default')
expect(page).
- to_not have_content t('devise.two_factor_authentication.invalid_otp')
+ to_not have_content t('two_factor_authentication.invalid_otp')
end
end
end
@@ -544,7 +546,7 @@ def submit_prefilled_otp_code
dn: 'C=US, O=U.S. Government, OU=DoD, OU=PKI, CN=DOE.JOHN.12345',
nonce: nonce)
expect(current_path).to eq login_two_factor_piv_cac_path
- expect(page).to have_content(t('devise.two_factor_authentication.invalid_piv_cac'))
+ expect(page).to have_content(t('two_factor_authentication.invalid_piv_cac'))
end
context 'with SMS, international number, and locale header' do
@@ -614,7 +616,7 @@ def submit_prefilled_otp_code
user = create(:user, :signed_up)
sign_in_before_2fa(user)
- expect(page).not_to have_link(t('devise.two_factor_authentication.piv_cac_fallback.link'))
+ expect(page).not_to have_link(t('two_factor_authentication.piv_cac_fallback.link'))
end
end
@@ -660,7 +662,7 @@ def submit_prefilled_otp_code
fill_in 'code', with: otp
click_submit_default
- expect(page).to have_content(t('devise.two_factor_authentication.invalid_otp'))
+ expect(page).to have_content(t('two_factor_authentication.invalid_otp'))
expect(current_path).to eq login_two_factor_authenticator_path
end
end
diff --git a/spec/features/two_factor_authentication/sign_in_via_personal_key_spec.rb b/spec/features/two_factor_authentication/sign_in_via_personal_key_spec.rb
index fa68d848a1a..d28fede5c2f 100644
--- a/spec/features/two_factor_authentication/sign_in_via_personal_key_spec.rb
+++ b/spec/features/two_factor_authentication/sign_in_via_personal_key_spec.rb
@@ -29,7 +29,7 @@
click_submit_default
expect(page).to have_content(
- t('devise.two_factor_authentication.max_personal_key_login_attempts_reached')
+ t('two_factor_authentication.max_personal_key_login_attempts_reached')
)
end
end
diff --git a/spec/features/users/password_recovery_via_recovery_code_spec.rb b/spec/features/users/password_recovery_via_recovery_code_spec.rb
index 410e5e4c954..4b8a5d39ddc 100644
--- a/spec/features/users/password_recovery_via_recovery_code_spec.rb
+++ b/spec/features/users/password_recovery_via_recovery_code_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Password recovery via personal key', idv_job: true do
+feature 'Password recovery via personal key' do
include PersonalKeyHelper
include IdvHelper
include SamlAuthHelper
diff --git a/spec/features/users/piv_cac_management_spec.rb b/spec/features/users/piv_cac_management_spec.rb
index ceec1f6ca59..0fde0d7782c 100644
--- a/spec/features/users/piv_cac_management_spec.rb
+++ b/spec/features/users/piv_cac_management_spec.rb
@@ -7,10 +7,6 @@ def find_form(page, attributes)
end
end
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled).and_return('true')
- end
-
context 'with no piv/cac associated yet' do
let(:uuid) { SecureRandom.uuid }
let(:user) { create(:user, :signed_up, :with_phone, with: { phone: '+1 202-555-1212' }) }
@@ -107,8 +103,7 @@ def find_form(page, attributes)
stub_piv_cac_service
user.update(otp_secret_key: 'secret')
- user.phone_configurations.clear
- expect(user.phone_configurations).to be_empty
+ MfaContext.new(user).phone_configurations.clear
sign_in_and_2fa_user(user)
visit account_path
click_link t('forms.buttons.enable'), href: setup_piv_cac_url
diff --git a/spec/features/users/regenerate_personal_key_spec.rb b/spec/features/users/regenerate_personal_key_spec.rb
index 0e44a04223d..88f9887669c 100644
--- a/spec/features/users/regenerate_personal_key_spec.rb
+++ b/spec/features/users/regenerate_personal_key_spec.rb
@@ -76,19 +76,12 @@
context 'with javascript enabled', js: true do
let(:invisible_selector) { generate_class_selector('invisible') }
- let(:accordion_control_selector) { generate_class_selector('accordion-header-controls') }
it 'prompts the user to enter their personal key to confirm they have it' do
Capybara.current_session.current_window.resize_to(2560, 1600)
sign_in_and_2fa_user
click_button t('account.links.regenerate_personal_key')
- expect_accordion_content_to_be_hidden_by_default
-
- expand_accordion
-
- expect_accordion_content_to_become_visible
-
click_acknowledge_personal_key
expect_confirmation_modal_to_appear_with_first_code_field_in_focus
@@ -137,9 +130,6 @@
expect(current_path).to eq account_path
end
end
-
- it_behaves_like 'csrf error when asking for new personal key', :saml
- it_behaves_like 'csrf error when asking for new personal key', :oidc
end
def sign_up_and_view_personal_key
@@ -151,23 +141,6 @@ def sign_up_and_view_personal_key
click_submit_default
end
-def expect_accordion_content_to_be_hidden_by_default
- expect(page).to have_xpath("//#{accordion_control_selector}")
- expect(page).not_to have_content(t('users.personal_key.help_text'))
- expect(page).to have_xpath(
- "//div[@id='personal-key-confirm'][@class='display-none']", visible: false
- )
-end
-
-def expand_accordion
- page.find('.accordion-header-controls').click
-end
-
-def expect_accordion_content_to_become_visible
- expect(page).to have_xpath("//#{accordion_control_selector}[@aria-expanded='true']")
- expect(page).to have_content(t('users.personal_key.help_text'))
-end
-
def expect_confirmation_modal_to_appear_with_first_code_field_in_focus
expect(page).not_to have_xpath("//div[@id='personal-key-confirm'][@class='display-none']")
expect(page).not_to have_xpath("//#{invisible_selector}[@id='personal-key']")
diff --git a/spec/features/users/sign_in_spec.rb b/spec/features/users/sign_in_spec.rb
index 3e27ae5d831..158ff451607 100644
--- a/spec/features/users/sign_in_spec.rb
+++ b/spec/features/users/sign_in_spec.rb
@@ -330,7 +330,7 @@
expect(page).
to have_current_path(login_two_factor_path(otp_delivery_preference: 'sms', reauthn: false))
expect(page).to have_content t(
- 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ 'two_factor_authentication.otp_delivery_preference.phone_unsupported',
location: 'Bermuda'
)
expect(user.reload.otp_delivery_preference).to eq 'sms'
@@ -351,7 +351,7 @@
expect(page).
to have_current_path(login_two_factor_path(otp_delivery_preference: 'sms', reauthn: false))
expect(page).to have_content t(
- 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ 'two_factor_authentication.otp_delivery_preference.phone_unsupported',
location: 'India'
)
expect(user.reload.otp_delivery_preference).to eq 'sms'
@@ -374,7 +374,7 @@
expect(page).
to have_current_path(login_two_factor_path(otp_delivery_preference: 'sms'))
expect(page).to have_content t(
- 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ 'two_factor_authentication.otp_delivery_preference.phone_unsupported',
location: 'India'
)
expect(user.reload.otp_delivery_preference).to eq 'sms'
@@ -397,7 +397,7 @@
expect(page).
to have_current_path(login_two_factor_path(otp_delivery_preference: 'sms'))
expect(page).to have_content t(
- 'devise.two_factor_authentication.otp_delivery_preference.phone_unsupported',
+ 'two_factor_authentication.otp_delivery_preference.phone_unsupported',
location: 'India'
)
expect(user.reload.otp_delivery_preference).to eq 'sms'
@@ -441,7 +441,7 @@
click_submit_default
expect(page).to have_current_path(login_two_factor_personal_key_path)
- expect(page).to have_content t('devise.two_factor_authentication.invalid_personal_key')
+ expect(page).to have_content t('two_factor_authentication.invalid_personal_key')
end
end
@@ -478,7 +478,7 @@
it 'does not display OTP Fallback text and links' do
expect(page).
- to_not have_content t('devise.two_factor_authentication.totp_fallback.sms_link_text')
+ to_not have_content t('two_factor_authentication.totp_fallback.sms_link_text')
end
end
end
diff --git a/spec/features/users/sign_up_spec.rb b/spec/features/users/sign_up_spec.rb
index 575ded876b6..c18316701b4 100644
--- a/spec/features/users/sign_up_spec.rb
+++ b/spec/features/users/sign_up_spec.rb
@@ -168,12 +168,11 @@
it 'does not allow a user to choose piv/cac as 2FA method during sign up' do
allow(PivCacService).to receive(:piv_cac_available_for_agency?).and_return(false)
- allow(FeatureManagement).to receive(:piv_cac_enabled?).and_return(true)
begin_sign_up_with_sp_and_loa(loa3: false)
expect(page).to have_current_path two_factor_options_path
expect(page).not_to have_content(
- t('devise.two_factor_authentication.two_factor_choice_options.piv_cac')
+ t('two_factor_authentication.two_factor_choice_options.piv_cac')
)
end
diff --git a/spec/features/users/user_profile_spec.rb b/spec/features/users/user_profile_spec.rb
index c3288adbaab..18e53724956 100644
--- a/spec/features/users/user_profile_spec.rb
+++ b/spec/features/users/user_profile_spec.rb
@@ -117,7 +117,7 @@
expect(page).to have_content(t('idv.messages.personal_key'))
end
- it 'allows the user reactivate their profile by reverifying', idv_job: true do
+ it 'allows the user reactivate their profile by reverifying' do
profile = create(:profile, :active, :verified, pii: { ssn: '1234', dob: '1920-01-01' })
user = profile.user
diff --git a/spec/features/users/webauthn_management_spec.rb b/spec/features/users/webauthn_management_spec.rb
index 1b7c89156d0..866096dac63 100644
--- a/spec/features/users/webauthn_management_spec.rb
+++ b/spec/features/users/webauthn_management_spec.rb
@@ -15,7 +15,7 @@
click_link t('account.index.webauthn_add'), href: webauthn_setup_url
expect(current_path).to eq webauthn_setup_path
- mock_press_button_on_hardware_key
+ mock_press_button_on_hardware_key_and_fill_in_name_field
click_submit_default
expect(current_path).to eq account_path
@@ -30,7 +30,7 @@
click_link t('account.index.webauthn_add'), href: webauthn_setup_url
expect(current_path).to eq webauthn_setup_path
- mock_press_button_on_hardware_key
+ mock_press_button_on_hardware_key_and_fill_in_name_field
click_submit_default
expect(current_path).to eq account_path
@@ -62,7 +62,7 @@
click_link t('account.index.webauthn_add'), href: webauthn_setup_url
expect(current_path).to eq webauthn_setup_path
- mock_press_button_on_hardware_key
+ mock_press_button_on_hardware_key_and_fill_in_name_field
click_submit_default
expect(current_path).to eq account_path
@@ -71,7 +71,7 @@
click_link t('account.index.webauthn_add'), href: webauthn_setup_url
expect(current_path).to eq webauthn_setup_path
- mock_press_button_on_hardware_key
+ mock_press_button_on_hardware_key_and_fill_in_name_field
click_submit_default
expect(current_path).to eq webauthn_setup_path
@@ -128,25 +128,6 @@
end
end
- def mock_challenge
- allow(WebAuthn).to receive(:credential_creation_options).and_return(
- challenge: challenge.pack('c*')
- )
- end
-
- def mock_press_button_on_hardware_key
- # this is required because the domain is embedded in the supplied attestation object
- allow(WebauthnSetupForm).to receive(:domain_name).and_return('localhost:3000')
-
- set_hidden_field('attestation_object', attestation_object)
- set_hidden_field('client_data_json', client_data_json)
- set_hidden_field('name', 'mykey')
- end
-
- def set_hidden_field(id, value)
- first("input##{id}", visible: false).set(value)
- end
-
def create_webauthn_configuration(user, name, id, key)
WebauthnConfiguration.create(user_id: user.id,
credential_public_key: key,
diff --git a/spec/features/visitors/phone_confirmation_spec.rb b/spec/features/visitors/phone_confirmation_spec.rb
index 4915e1a9462..0e07b7db6c7 100644
--- a/spec/features/visitors/phone_confirmation_spec.rb
+++ b/spec/features/visitors/phone_confirmation_spec.rb
@@ -22,7 +22,7 @@
it 'updates phone_confirmed_at and redirects to acknowledge personal key' do
click_button t('forms.buttons.submit.default')
- expect(@user.phone_configurations.reload.first.confirmed_at).to be_present
+ expect(MfaContext.new(@user).phone_configurations.reload.first.confirmed_at).to be_present
expect(current_path).to eq sign_up_personal_key_path
click_acknowledge_personal_key
@@ -40,7 +40,7 @@
fill_in 'code', with: '12345678'
click_button t('forms.buttons.submit.default')
- expect(@user.reload.two_factor_enabled?).to be false
+ expect(MfaPolicy.new(@user.reload).two_factor_enabled?).to be false
end
it 'provides user with link to type in a phone number so they are not locked out' do
@@ -61,7 +61,7 @@
@user = sign_in_before_2fa
select_2fa_option('sms')
fill_in 'user_phone_form_phone',
- with: @existing_user.phone_configurations.detect(&:mfa_enabled?).phone
+ with: MfaContext.new(@existing_user).phone_configurations.detect(&:mfa_enabled?).phone
click_send_security_code
end
@@ -76,8 +76,8 @@
fill_in 'code', with: 'foobar'
click_submit_default
- expect(@user.phone_configurations.reload).to be_empty
- expect(page).to have_content t('devise.two_factor_authentication.invalid_otp')
+ expect(MfaContext.new(@user).phone_configurations.reload).to be_empty
+ expect(page).to have_content t('two_factor_authentication.invalid_otp')
expect(current_path).to eq login_two_factor_path(otp_delivery_preference: 'sms')
end
end
diff --git a/spec/forms/idv/image_upload_form_spec.rb b/spec/forms/idv/image_upload_form_spec.rb
new file mode 100644
index 00000000000..81517b996b5
--- /dev/null
+++ b/spec/forms/idv/image_upload_form_spec.rb
@@ -0,0 +1,34 @@
+require 'rails_helper'
+
+describe Idv::ImageUploadForm do
+ let(:user) { create(:user) }
+ let(:subject) { Idv::ImageUploadForm.new(user) }
+ let(:image_data) { 'abc' }
+
+ describe '#submit' do
+ context 'when the form is valid' do
+ it 'returns a successful form response' do
+ result = subject.submit(image: image_data)
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(true)
+ expect(result.errors).to be_empty
+ end
+ end
+
+ context 'when the form has invalid attributes' do
+ it 'raises an error' do
+ expect { subject.submit(image: image_data, foo: 1) }.
+ to raise_error(ArgumentError, 'foo is an invalid image attribute')
+ end
+ end
+ end
+
+ describe 'presence validations' do
+ it 'is invalid when required attribute is not present' do
+ subject.submit(image: nil)
+
+ expect(subject).to_not be_valid
+ end
+ end
+end
diff --git a/spec/forms/idv/phone_form_spec.rb b/spec/forms/idv/phone_form_spec.rb
index 22667076b30..f9d039aa3e4 100644
--- a/spec/forms/idv/phone_form_spec.rb
+++ b/spec/forms/idv/phone_form_spec.rb
@@ -3,8 +3,9 @@
describe Idv::PhoneForm do
let(:user) { build_stubbed(:user, :signed_up) }
let(:params) { { phone: '703-555-5000' } }
+ let(:previous_params) { nil }
- subject { Idv::PhoneForm.new({}, user) }
+ subject { Idv::PhoneForm.new(user: user, previous_params: previous_params) }
it_behaves_like 'a phone form'
@@ -17,17 +18,6 @@
expect(result.success?).to eq(true)
expect(result.errors).to be_empty
end
-
- it 'adds phone key to idv_params' do
- subject.submit(phone: '703-555-1212')
-
- expected_params = {
- phone: '7035551212',
- phone_confirmed_at: nil,
- }
-
- expect(subject.idv_params).to eq expected_params
- end
end
context 'when the form is invalid' do
@@ -40,31 +30,27 @@
end
end
- it 'adds phone_confirmed_at key to idv_params when submitted phone equals user phone' do
- subject.submit(phone: '+1 (202) 555-1212')
-
- expected_params = {
- phone: '2025551212',
- phone_confirmed_at: user.phone_configurations.first.confirmed_at,
- }
-
- expect(subject.idv_params).to eq expected_params
- end
-
it 'uses the user phone number as the initial phone value' do
user = build_stubbed(:user, :signed_up, with: { phone: '7035551234' })
- subject = Idv::PhoneForm.new({}, user)
+ subject = Idv::PhoneForm.new(previous_params: {}, user: user)
expect(subject.phone).to eq('+1 703-555-1234')
end
it 'does not use an international number as the initial phone value' do
user = build_stubbed(:user, :signed_up, with: { phone: '+81 54 354 3643' })
- subject = Idv::PhoneForm.new({}, user)
+ subject = Idv::PhoneForm.new(previous_params: {}, user: user)
expect(subject.phone).to eq(nil)
end
+ it 'uses the previously submitted value as the initial phone value' do
+ user = build_stubbed(:user, :signed_up, with: { phone: '7035551234' })
+ subject = Idv::PhoneForm.new(previous_params: { phone: '2255555000' }, user: user)
+
+ expect(subject.phone).to eq('+1 225-555-5000')
+ end
+
it 'does not allow non-US numbers' do
invalid_phones = ['+81 54 354 3643', '+12423270143']
invalid_phones.each do |phone|
diff --git a/spec/forms/idv/profile_form_spec.rb b/spec/forms/idv/profile_form_spec.rb
index 52847558953..456559263e8 100644
--- a/spec/forms/idv/profile_form_spec.rb
+++ b/spec/forms/idv/profile_form_spec.rb
@@ -4,7 +4,7 @@
let(:password) { 'a really long sekrit' }
let(:ssn) { '123-11-1234' }
let(:user) { create(:user, password: password) }
- let(:subject) { Idv::ProfileForm.new({}, user) }
+ let(:subject) { Idv::ProfileForm.new(user: user, previous_params: {}) }
let(:profile_attrs) do
{
first_name: 'Some',
@@ -21,6 +21,17 @@
}
end
+ describe '#initialize' do
+ context 'when there are params from a previous submission' do
+ it 'assigns those params to the form' do
+ form = Idv::ProfileForm.new(user: user, previous_params: profile_attrs)
+
+ expect(form.first_name).to eq('Some')
+ expect(form.last_name).to eq('One')
+ end
+ end
+ end
+
describe '#submit' do
context 'when the form is valid' do
it 'returns a successful form response' do
@@ -178,4 +189,12 @@
expect(subject.errors).to include(:state_id_type)
end
end
+
+ describe 'state id number length validity' do
+ it 'populates error for invalid state id number length' do
+ subject.submit(profile_attrs.merge(state_id_number: '8' * 26))
+ expect(subject.valid?).to eq false
+ expect(subject.errors).to include(:state_id_number)
+ end
+ end
end
diff --git a/spec/forms/idv/ssn_form_spec.rb b/spec/forms/idv/ssn_form_spec.rb
new file mode 100644
index 00000000000..2736cb9841b
--- /dev/null
+++ b/spec/forms/idv/ssn_form_spec.rb
@@ -0,0 +1,88 @@
+require 'rails_helper'
+
+describe Idv::SsnForm do
+ let(:user) { create(:user) }
+ let(:subject) { Idv::SsnForm.new(user) }
+ let(:ssn) { '111-11-1111' }
+
+ describe '#submit' do
+ context 'when the form is valid' do
+ it 'returns a successful form response' do
+ result = subject.submit(ssn: '111111111')
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(true)
+ expect(result.errors).to be_empty
+ end
+ end
+
+ context 'when the form is invalid' do
+ it 'returns an unsuccessful form response' do
+ result = subject.submit(ssn: 'abc')
+
+ expect(result).to be_kind_of(FormResponse)
+ expect(result.success?).to eq(false)
+ expect(result.errors).to include(:ssn)
+ end
+ end
+
+ context 'when the form has invalid attributes' do
+ it 'raises an error' do
+ expect { subject.submit(ssn: '111111111', foo: 1) }.
+ to raise_error(ArgumentError, 'foo is an invalid ssn attribute')
+ end
+ end
+ end
+
+ describe 'presence validations' do
+ it 'is invalid when required attribute is not present' do
+ subject.submit(ssn: nil)
+
+ expect(subject).to_not be_valid
+ end
+ end
+
+ describe 'ssn uniqueness' do
+ context 'when ssn is already taken by another profile' do
+ it 'is invalid' do
+ diff_user = create(:user)
+ create(:profile, pii: { ssn: ssn }, user: diff_user)
+
+ subject.submit(ssn: ssn)
+
+ expect(subject.valid?).to eq false
+ expect(subject.errors[:ssn]).to eq [t('idv.errors.duplicate_ssn')]
+ end
+
+ it 'recognizes fingerprint regardless of HMAC key age' do
+ diff_user = create(:user)
+ create(:profile, pii: { ssn: ssn }, user: diff_user)
+ rotate_hmac_key
+
+ subject.submit(ssn: ssn)
+
+ expect(subject.valid?).to eq false
+ expect(subject.errors[:ssn]).to eq [t('idv.errors.duplicate_ssn')]
+ end
+ end
+
+ context 'when ssn is already taken by same profile' do
+ it 'is valid' do
+ create(:profile, pii: { ssn: ssn }, user: user)
+
+ subject.submit(ssn: ssn)
+
+ expect(subject.valid?).to eq true
+ end
+
+ it 'recognizes fingerprint regardless of HMAC key age' do
+ create(:profile, pii: { ssn: ssn }, user: user)
+ rotate_hmac_key
+
+ subject.submit(ssn: ssn)
+
+ expect(subject.valid?).to eq true
+ end
+ end
+ end
+end
diff --git a/spec/forms/user_phone_form_spec.rb b/spec/forms/user_phone_form_spec.rb
index 5badd5ac3e8..b47e5eee323 100644
--- a/spec/forms/user_phone_form_spec.rb
+++ b/spec/forms/user_phone_form_spec.rb
@@ -11,7 +11,7 @@
otp_delivery_preference: 'sms',
}
end
- subject { UserPhoneForm.new(user) }
+ subject { UserPhoneForm.new(user, MfaContext.new(user).phone_configurations.first) }
it_behaves_like 'a phone form'
@@ -21,16 +21,16 @@
with: { phone: '+1 (703) 500-5000' },
otp_delivery_preference: 'voice'
)
- subject = UserPhoneForm.new(user)
+ subject = UserPhoneForm.new(user, MfaContext.new(user).phone_configurations.first)
- expect(subject.phone).to eq(user.phone_configurations.first.phone)
+ expect(subject.phone).to eq(MfaContext.new(user).phone_configurations.first.phone)
expect(subject.international_code).to eq('US')
expect(subject.otp_delivery_preference).to eq(user.otp_delivery_preference)
end
it 'infers the international code from the user phone number' do
user = build_stubbed(:user, :with_phone, with: { phone: '+81 744 21 1234' })
- subject = UserPhoneForm.new(user)
+ subject = UserPhoneForm.new(user, MfaContext.new(user).phone_configurations.first)
expect(subject.international_code).to eq('JP')
end
@@ -72,13 +72,13 @@
it 'does not update the user phone attribute' do
user = create(:user)
- subject = UserPhoneForm.new(user)
+ subject = UserPhoneForm.new(user, MfaContext.new(user).phone_configurations.first)
params[:phone] = '+1 504 444 1643'
subject.submit(params)
user.reload
- expect(user.phone_configurations).to be_empty
+ expect(MfaContext.new(user).phone_configurations).to be_empty
end
it 'preserves the format of the submitted phone number if phone is invalid' do
@@ -211,7 +211,7 @@
end
it 'returns false if the user phone has not changed' do
- params[:phone] = user.phone_configurations.first.phone
+ params[:phone] = MfaContext.new(user).phone_configurations.first.phone
subject.submit(params)
expect(subject.phone_changed?).to eq(false)
@@ -219,7 +219,7 @@
context 'when a user has no phone' do
it 'returns true' do
- user.phone_configurations.clear
+ MfaContext.new(user).phone_configurations.clear
params[:phone] = '+1 504 444 1643'
subject.submit(params)
diff --git a/spec/forms/user_piv_cac_setup_form_spec.rb b/spec/forms/user_piv_cac_setup_form_spec.rb
index aa36dd2f055..ec5be960b35 100644
--- a/spec/forms/user_piv_cac_setup_form_spec.rb
+++ b/spec/forms/user_piv_cac_setup_form_spec.rb
@@ -32,7 +32,7 @@
with(user_id: user.id, event_type: :piv_cac_enabled)
expect(form.submit).to eq result
user.reload
- expect(user.piv_cac_enabled?).to eq true
+ expect(TwoFactorAuthentication::PivCacPolicy.new(user).enabled?).to eq true
expect(user.x509_dn_uuid).to eq x509_dn_uuid
end
@@ -46,7 +46,7 @@
with(success: false, errors: {}).and_return(result)
expect(Event).to_not receive(:create)
expect(form.submit).to eq result
- expect(user.reload.piv_cac_enabled?).to eq true
+ expect(TwoFactorAuthentication::PivCacPolicy.new(user.reload).enabled?).to eq true
expect(form.error_type).to eq 'user.piv_cac_associated'
end
end
@@ -62,7 +62,7 @@
with(success: false, errors: {}).and_return(result)
expect(Event).to_not receive(:create)
expect(form.submit).to eq result
- expect(user.reload.piv_cac_enabled?).to eq false
+ expect(TwoFactorAuthentication::PivCacPolicy.new(user.reload).enabled?).to eq false
expect(form.error_type).to eq 'piv_cac.already_associated'
end
@@ -78,7 +78,7 @@
with(success: false, errors: {}).and_return(result)
expect(Event).to_not receive(:create)
expect(form.submit).to eq result
- expect(user.reload.piv_cac_enabled?).to eq false
+ expect(TwoFactorAuthentication::PivCacPolicy.new(user.reload).enabled?).to eq false
expect(form.error_type).to eq 'piv_cac.already_associated'
end
end
@@ -98,7 +98,7 @@
with(success: false, errors: {}).and_return(result)
expect(Event).to_not receive(:create)
expect(form.submit).to eq result
- expect(user.reload.piv_cac_enabled?).to eq false
+ expect(TwoFactorAuthentication::PivCacPolicy.new(user.reload).enabled?).to eq false
expect(form.error_type).to eq 'token.bad'
end
end
@@ -131,7 +131,7 @@
with(success: false, errors: {}).and_return(result)
expect(Event).to_not receive(:create)
expect(form.submit).to eq result
- expect(user.reload.piv_cac_enabled?).to eq false
+ expect(TwoFactorAuthentication::PivCacPolicy.new(user.reload).enabled?).to eq false
end
end
end
diff --git a/spec/forms/webauthn_verification_form_spec.rb b/spec/forms/webauthn_verification_form_spec.rb
new file mode 100644
index 00000000000..dd48dde7388
--- /dev/null
+++ b/spec/forms/webauthn_verification_form_spec.rb
@@ -0,0 +1,45 @@
+require 'rails_helper'
+
+describe WebauthnVerificationForm do
+ include WebauthnVerificationHelper
+
+ let(:user) { create(:user) }
+ let(:user_session) { { webauthn_challenge: challenge } }
+ let(:subject) { WebauthnVerificationForm.new(user, user_session) }
+
+ describe '#submit' do
+ before do
+ create_webauthn_configuration(user)
+ end
+ context 'when the input is valid' do
+ it 'returns FormResponse with success: true' do
+ allow(Figaro.env).to receive(:domain_name).and_return('localhost:3000')
+ result = instance_double(FormResponse)
+ params = {
+ authenticator_data: authenticator_data,
+ client_data_json: client_data_json,
+ signature: signature,
+ credential_id: credential_id,
+ }
+ expect(FormResponse).to receive(:new).
+ with(success: true, errors: {}).and_return(result)
+ expect(subject.submit(protocol, params)).to eq result
+ end
+ end
+
+ context 'when the input is invalid' do
+ it 'returns FormResponse with success: false' do
+ result = instance_double(FormResponse)
+ params = {
+ authenticator_data: authenticator_data,
+ client_data_json: client_data_json,
+ signature: signature,
+ credential_id: credential_id,
+ }
+ expect(FormResponse).to receive(:new).
+ with(success: false, errors: {}).and_return(result)
+ expect(subject.submit(protocol, params)).to eq result
+ end
+ end
+ end
+end
diff --git a/spec/jobs/idv/proofer_job_spec.rb b/spec/jobs/idv/proofer_job_spec.rb
deleted file mode 100644
index 58287c838af..00000000000
--- a/spec/jobs/idv/proofer_job_spec.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-require 'rails_helper'
-
-describe Idv::ProoferJob do
- describe '#perform' do
- context 'without mocking the agent' do
- let(:result_id) { SecureRandom.uuid }
- let(:applicant) { { first_name: 'Jean-Luc', ssn: '123456789', zipcode: '11111' } }
- let(:stages) { %i[resolution] }
-
- it 'works' do
- Idv::ProoferJob.perform_now(
- result_id: result_id,
- applicant_json: applicant.to_json,
- stages: stages.to_json
- )
-
- result = VendorValidatorResultStorage.new.load(result_id)
-
- expect(result.success?).to eq(true)
- expect(result.timed_out?).to eq(false)
- expect(result.job_failed?).to eq(false)
- expect(result.messages).to be_empty
- expect(result.errors).to be_empty
- end
- end
-
- context 'when mocking the agent' do
- let(:result_id) { SecureRandom.uuid }
- let(:applicant) { { first_name: 'Jean-Luc', last_name: 'Picard' } }
- let(:stages) { %i[phone] }
- let(:agent) { instance_double(Idv::Agent) }
- let(:proofer_results) { {} }
-
- before do
- allow(agent).to receive(:proof).and_return(proofer_results)
- allow(Idv::Agent).to receive(:new).and_return(agent)
- end
-
- subject do
- Idv::ProoferJob.perform_now(
- result_id: result_id,
- applicant_json: applicant.to_json,
- stages: stages.to_json
- )
- end
-
- shared_examples 'a proofer job' do
- it 'uses the Idv::Agent' do
- subject
-
- expect(Idv::Agent).to have_received(:new).with(applicant)
- expect(agent).to have_received(:proof).with(*stages)
- end
- end
-
- context 'when verification succeeds' do
- let(:proofer_results) { { success: true, messages: ['a reason'] } }
-
- it_behaves_like 'a proofer job'
-
- it 'should save a successful result' do
- subject
-
- result = VendorValidatorResultStorage.new.load(result_id)
-
- expect(result.success?).to eq(true)
- expect(result.timed_out?).to eq(false)
- expect(result.job_failed?).to eq(false)
- expect(result.messages).to eq(['a reason'])
- expect(result.errors).to be_empty
- end
- end
-
- context 'when verification fails' do
- let(:proofer_results) do
- {
- success: false,
- messages: ['Bad number'],
- errors: { phone: 'The phone number could not be verified.' },
- }
- end
-
- it_behaves_like 'a proofer job'
-
- it 'should save an unsuccessful result' do
- subject
-
- result = VendorValidatorResultStorage.new.load(result_id)
-
- expect(result.success?).to eq(false)
- expect(result.timed_out?).to eq(false)
- expect(result.job_failed?).to eq(false)
- expect(result.messages).to eq(['Bad number'])
- expect(result.errors).to eq(phone: 'The phone number could not be verified.')
- end
- end
-
- context 'when the idv agent raises' do
- before do
- allow(agent).to receive(:proof).and_raise(RuntimeError, '🔥🔥🔥')
- end
-
- it 'should rescue from errors and save a failed job result' do
- expect { subject }.to raise_error(RuntimeError, '🔥🔥🔥')
-
- result = VendorValidatorResultStorage.new.load(result_id)
-
- expect(result.success?).to eq(false)
- expect(result.timed_out?).to eq(false)
- expect(result.job_failed?).to eq(true)
- end
- end
- end
- end
-end
diff --git a/spec/lib/encrypted_sidekiq_redis_spec.rb b/spec/lib/encrypted_sidekiq_redis_spec.rb
deleted file mode 100644
index 3c2139143d3..00000000000
--- a/spec/lib/encrypted_sidekiq_redis_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'rails_helper'
-
-describe EncryptedSidekiqRedis do
- let(:key) { 'test-queue' }
- let(:value) { 'some random string' }
-
- subject { EncryptedSidekiqRedis.new(url: Figaro.env.redis_url) }
-
- before do
- subject.flushall
- end
-
- describe '#new' do
- it 'takes same options as Redis.new' do
- expect(subject.ping).to eq 'PONG'
- end
- end
-
- describe 'encryption' do
- it 'encrypts strings pushed to redis' do
- subject.lpush(key, value)
-
- raw_value = subject.redis.blpop(key).last
-
- expect(raw_value).to_not eq value
- expect(raw_value).to match 'cipher'
- end
- end
-
- describe 'decryption' do
- it 'decrypts transparently' do
- subject.lpush(key, value)
-
- pulled_value = subject.blpop(key).last
-
- expect(pulled_value).to eq value
- end
- end
-
- describe '#zrem' do
- it 'modifies value string in-place' do
- subject.zadd(key, 1, value)
-
- raw_value = subject.zrangebyscore(key, 0, 1, limit: [0, 1]).first
-
- expect(raw_value).to_not eq value
- expect(subject.zrem(key, raw_value)).to eq true
- expect(raw_value).to eq value
- end
- end
-end
diff --git a/spec/lib/feature_management_spec.rb b/spec/lib/feature_management_spec.rb
index a9e61d67ba6..e16b4589792 100644
--- a/spec/lib/feature_management_spec.rb
+++ b/spec/lib/feature_management_spec.rb
@@ -214,28 +214,6 @@
end
describe 'piv/cac feature' do
- describe '#piv_cac_enabled?' do
- context 'when enabled' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'true' }
- end
-
- it 'has the feature disabled' do
- expect(FeatureManagement.piv_cac_enabled?).to be_truthy
- end
- end
-
- context 'when disabled' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'false' }
- end
-
- it 'has the feature disabled' do
- expect(FeatureManagement.piv_cac_enabled?).to be_falsey
- end
- end
- end
-
describe '#identity_pki_disabled?' do
context 'when enabled' do
before(:each) do
@@ -258,29 +236,23 @@
end
end
- describe '#development_and_piv_cac_entry_enabled?' do
+ describe '#development_and_identity_pki_disabled?' do
context 'in development environment' do
before(:each) do
allow(Rails.env).to receive(:development?).and_return(true)
end
- context 'has piv/cac enabled' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'true' }
- end
-
- it 'has piv/cac test entry enabled' do
- expect(FeatureManagement.development_and_piv_cac_entry_enabled?).to be_truthy
+ context 'identity_pki disabled' do
+ it 'returns true' do
+ allow(Figaro.env).to receive(:identity_pki_disabled) { 'true' }
+ expect(FeatureManagement.development_and_identity_pki_disabled?).to be_truthy
end
end
- context 'has piv/cac disabled' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'false' }
- end
-
- it 'has piv/cac test entry disabled' do
- expect(FeatureManagement.development_and_piv_cac_entry_enabled?).to be_falsey
+ context 'identity_pki not disabled' do
+ it 'returns false' do
+ allow(Figaro.env).to receive(:identity_pki_disabled) { 'false' }
+ expect(FeatureManagement.development_and_identity_pki_disabled?).to be_falsey
end
end
end
@@ -291,23 +263,17 @@
allow(Rails.env).to receive(:development?).and_return(false)
end
- context 'has piv/cac enabled' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'true' }
- end
-
- it 'has piv/cac test entry disabled' do
- expect(FeatureManagement.development_and_piv_cac_entry_enabled?).to be_falsey
+ context 'identity_pki disabled' do
+ it 'returns false' do
+ allow(Figaro.env).to receive(:identity_pki_disabled) { 'true' }
+ expect(FeatureManagement.development_and_identity_pki_disabled?).to be_falsey
end
end
- context 'has piv/cac disabled' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'false' }
- end
-
- it 'has piv/cac test entry disabled' do
- expect(FeatureManagement.development_and_piv_cac_entry_enabled?).to be_falsey
+ context 'identity_pki not disabled' do
+ it 'returns false' do
+ allow(Figaro.env).to receive(:identity_pki_disabled) { 'false' }
+ expect(FeatureManagement.development_and_identity_pki_disabled?).to be_falsey
end
end
end
@@ -461,4 +427,32 @@
end
end
end
+
+ describe '#doc_auth_enabled?' do
+ it 'returns true when Figaro setting is true' do
+ allow(Figaro.env).to receive(:doc_auth_enabled) { 'true' }
+
+ expect(FeatureManagement.doc_auth_enabled?).to eq(true)
+ end
+
+ it 'returns false when Figaro setting is false' do
+ allow(Figaro.env).to receive(:doc_auth_enabled) { 'false' }
+
+ expect(FeatureManagement.doc_auth_enabled?).to eq(false)
+ end
+ end
+
+ describe '#doc_auth_exclusive?' do
+ it 'returns true when Figaro setting is true' do
+ allow(Figaro.env).to receive(:doc_auth_exclusive) { 'true' }
+
+ expect(FeatureManagement.doc_auth_exclusive?).to eq(true)
+ end
+
+ it 'returns false when Figaro setting is false' do
+ allow(Figaro.env).to receive(:doc_auth_exclusive) { 'false' }
+
+ expect(FeatureManagement.doc_auth_exclusive?).to eq(false)
+ end
+ end
end
diff --git a/spec/lib/no_retry_jobs_spec.rb b/spec/lib/no_retry_jobs_spec.rb
deleted file mode 100644
index 6ca3ee0f544..00000000000
--- a/spec/lib/no_retry_jobs_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe NoRetryJobs do
- describe '#call' do
- context 'when the queue is idv, sms, or voice' do
- it 'runs' do
- %w[idv sms voice].each do |queue|
- count = 0
- NoRetryJobs.new.call(nil, nil, queue) { count += 1 }
-
- expect(count).to eq 1
- end
- end
-
- it 'sets retry to false when rescuing StandardError then raises the error' do
- %w[idv sms voice].each do |queue|
- msg = {}
-
- expect { NoRetryJobs.new.call(nil, msg, queue) { raise StandardError } }.
- to change { msg }.from({}).to('retry' => false).and raise_error(StandardError)
- end
- end
- end
-
- context 'when the queue is not idv, sms, or voice' do
- it 'does not set retry to false and raises StandardError' do
- msg = {}
- expect { NoRetryJobs.new.call(nil, msg, 'mailers') { raise StandardError } }.
- to change(msg, :keys).by([]).and raise_error(StandardError)
- end
- end
- end
-end
diff --git a/spec/lib/production_database_configuration_spec.rb b/spec/lib/production_database_configuration_spec.rb
index acca56198a7..6d1f7d17f36 100644
--- a/spec/lib/production_database_configuration_spec.rb
+++ b/spec/lib/production_database_configuration_spec.rb
@@ -117,26 +117,6 @@
end
end
- context 'when the app is running on a worker host' do
- before { stub_role_config('worker') }
-
- it 'returns the worker pool size' do
- allow(Figaro.env).to receive(:database_pool_worker).and_return(8)
-
- expect(ProductionDatabaseConfiguration.pool).to eq(8)
- end
-
- it 'defaults to 26' do
- allow(Figaro.env).to receive(:database_pool_worker).and_return(nil)
-
- expect(ProductionDatabaseConfiguration.pool).to eq(26)
-
- allow(Figaro.env).to receive(:database_pool_worker).and_return('')
-
- expect(ProductionDatabaseConfiguration.pool).to eq(26)
- end
- end
-
context 'when the app is running on an host with an ambigous role' do
before { stub_role_config('fake') }
diff --git a/spec/lib/queue_config_spec.rb b/spec/lib/queue_config_spec.rb
index 55ab9ed39b0..e9f229c11bc 100644
--- a/spec/lib/queue_config_spec.rb
+++ b/spec/lib/queue_config_spec.rb
@@ -15,13 +15,8 @@
end.to raise_error(ArgumentError, /Unknown queue adapter/)
end
- it 'handles sidekiq' do
- expect(Figaro.env).to receive(:queue_adapter_weights).and_return('{"sidekiq": 1}')
- expect(Upaya::QueueConfig.choose_queue_adapter).to eq :sidekiq
- end
-
it 'handles async' do
- expect(Figaro.env).to receive(:queue_adapter_weights).and_return('{"async": 1, "sidekiq": 0}')
+ expect(Figaro.env).to receive(:queue_adapter_weights).and_return('{"async": 1, "inline": 0}')
expect(Upaya::QueueConfig.choose_queue_adapter).to eq :async
end
diff --git a/spec/lib/sidekiq_logging_formatter_spec.rb b/spec/lib/sidekiq_logging_formatter_spec.rb
deleted file mode 100644
index c8af73cdbe5..00000000000
--- a/spec/lib/sidekiq_logging_formatter_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'rails_helper'
-
-describe SidekiqLoggerFormatter do
- let(:job_json) do
- {
- 'context' => 'Job raised exception',
- 'job' => {
- 'class' => 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper',
- 'wrapped' => 'TestJob',
- 'queue' => 'sms',
- 'args' => [
- {
- 'job_class' => 'TestJob',
- 'job_id' => 'f1f1a7d1-b33a-4ce3-aa71-f3d74a1d99ae',
- 'queue_name' => 'sms',
- 'arguments' => ['sensitive pii'],
- 'locale' => 'en',
- },
- ],
- 'retry' => true,
- 'jid' => '5187f014c38c66d0840633c2',
- 'error_message' => 'hello world',
- 'error_class' => 'RuntimeError',
- },
- 'jobstr' => '{"args":"sensitive pii"}',
- }
- end
-
- describe '#call' do
- let(:now) { Time.zone.now }
-
- it 'redacts job arguments from JSON string' do
- expect(subject.call(:WARN, now, 'job', job_json.to_json)).to_not match 'sensitive pii'
- end
-
- it 'redacts job arguments from Hash' do
- expect(subject.call(:WARN, now, 'job', job_json)).to_not match 'sensitive pii'
- end
-
- it 'leaves non-JSON string alone' do
- expect(subject.call(:WARN, now, 'job', 'sensitive pii')).to match 'sensitive pii'
- end
- end
-end
diff --git a/spec/lib/worker_health_checker_spec.rb b/spec/lib/worker_health_checker_spec.rb
deleted file mode 100644
index 8ed30ec5884..00000000000
--- a/spec/lib/worker_health_checker_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe WorkerHealthChecker do
- before do
- ActiveJob::Base.queue_adapter = :sidekiq
- Sidekiq.redis(&:flushdb)
- end
-
- after do
- Sidekiq.redis(&:flushdb)
- ActiveJob::Base.queue_adapter = :test
- end
-
- def create_sidekiq_queues(*queues)
- Sidekiq.redis do |redis|
- queues.each do |queue|
- redis.sadd('queues', queue)
- end
- end
- end
-
- describe '#enqueue_dummy_jobs' do
- let(:queues) { YAML.load_file(Rails.root.join('config', 'sidekiq.yml'))[:queues] }
-
- subject(:enqueue_dummy_jobs) { WorkerHealthChecker.enqueue_dummy_jobs }
-
- it 'queues a dummy job per queue that updates health per job' do
- queues.each do |queue|
- expect(WorkerHealthChecker.status(queue).healthy?).to eq(false)
- end
-
- enqueue_dummy_jobs
-
- queues.each do |queue|
- expect(WorkerHealthChecker.status(queue).healthy?).to eq(true)
- end
- end
- end
-
- describe '#mark_healthy!' do
- let(:queue) { 'myqueue' }
- let(:now) { Time.zone.now }
-
- it 'sets a key in redis' do
- expect { WorkerHealthChecker.mark_healthy!(queue, now: now) }.
- to change { WorkerHealthChecker.status(queue, now: now).healthy? }.
- from(false).to(true)
- end
- end
-
- describe '.check' do
- let(:now) { Time.zone.now }
- subject(:check) { WorkerHealthChecker.check(now: now) }
-
- let(:queue1) { 'queue1' }
- let(:queue2) { 'queue2' }
-
- before do
- create_sidekiq_queues(queue1, queue2)
- WorkerHealthChecker.mark_healthy!(queue1, now: now)
- end
-
- it 'creates a snapshot check of the queues' do
- expect(check.statuses.length).to eq(2)
-
- queue1_status, queue2_status = check.statuses.sort_by(&:queue)
-
- expect(queue1_status.queue).to eq('queue1')
- expect(queue1_status.last_run_at.to_i).to eq(now.to_i)
- expect(queue1_status.healthy).to eq(true)
-
- expect(queue2_status.queue).to eq('queue2')
- expect(queue2_status.last_run_at).to be_nil
- expect(queue2_status.healthy).to eq(false)
- end
-
- it 'is unhealthy when not all queues are healthy' do
- expect(check.healthy?).to eq(false)
- end
-
- context 'when all queues are healthy' do
- before { WorkerHealthChecker.mark_healthy!(queue2, now: now) }
-
- it 'is all healthy' do
- expect(check.healthy?).to eq(true)
- end
- end
- end
-end
diff --git a/spec/models/anonymous_user_spec.rb b/spec/models/anonymous_user_spec.rb
new file mode 100644
index 00000000000..3dd4bff1624
--- /dev/null
+++ b/spec/models/anonymous_user_spec.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe AnonymousUser do
+ describe 'Methods' do
+ it { is_expected.to respond_to(:phone_configurations) }
+ it { is_expected.to respond_to(:uuid) }
+ it { is_expected.to respond_to(:phone) }
+ it { is_expected.to respond_to(:email) }
+ it { is_expected.to respond_to(:second_factor_locked_at) }
+ end
+
+ describe '#phone_configurations' do
+ subject { described_class.new.phone_configurations }
+
+ it { is_expected.to eq [] }
+ end
+end
diff --git a/spec/models/doc_auth_spec.rb b/spec/models/doc_auth_spec.rb
new file mode 100644
index 00000000000..f8a018f2969
--- /dev/null
+++ b/spec/models/doc_auth_spec.rb
@@ -0,0 +1,8 @@
+require 'rails_helper'
+
+describe DocAuth do
+ describe 'Associations' do
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to validate_presence_of(:user_id) }
+ end
+end
diff --git a/spec/models/email_address_spec.rb b/spec/models/email_address_spec.rb
new file mode 100644
index 00000000000..4b1fcc7b5f4
--- /dev/null
+++ b/spec/models/email_address_spec.rb
@@ -0,0 +1,35 @@
+require 'rails_helper'
+
+describe EmailAddress do
+ describe 'Associations' do
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to validate_presence_of(:user_id) }
+ it { is_expected.to validate_presence_of(:encrypted_email) }
+ it { is_expected.to validate_presence_of(:email_fingerprint) }
+ end
+
+ let(:email) { 'jd@example.com' }
+
+ let(:email_address) { create(:email_address, email: email) }
+
+ describe 'creation' do
+ it 'stores an encrypted form of the email address' do
+ expect(email_address.encrypted_email).to_not be_blank
+ end
+ end
+
+ describe 'encrypted attributes' do
+ it 'decrypts email' do
+ expect(email_address.email).to eq email
+ end
+
+ context 'with unnormalized email' do
+ let(:email) { ' jD@Example.Com ' }
+ let(:normalized_email) { 'jd@example.com' }
+
+ it 'normalizes email' do
+ expect(email_address.email).to eq normalized_email
+ end
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0a31acfe469..8ea4fee2ec3 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -13,6 +13,7 @@
it { is_expected.to have_one(:account_reset_request) }
it { is_expected.to have_many(:phone_configurations) }
it { is_expected.to have_many(:webauthn_configurations) }
+ it { is_expected.to have_one(:doc_auth) }
end
it 'does not send an email when #create is called' do
@@ -21,6 +22,32 @@
end.to change(ActionMailer::Base.deliveries, :count).by(0)
end
+ describe 'email_address' do
+ it 'creates an entry for the user when created' do
+ expect do
+ User.create(email: 'nobody@nobody.com')
+ end.to change(EmailAddress, :count).by(1)
+ end
+
+ it 'mirrors the info from the user object on creation' do
+ user = create(:user)
+ email_address = user.email_address
+ expect(email_address).to be_present
+ expect(email_address.encrypted_email).to eq user.encrypted_email
+ expect(email_address.email).to eq user.email
+ expect(email_address.confirmed_at).to eq user.confirmed_at
+ end
+
+ it 'mirrors the info from an unconfirmed user object' do
+ user = create(:user, :unconfirmed)
+ email_address = user.email_address
+ expect(email_address).to be_present
+ expect(email_address.encrypted_email).to eq user.encrypted_email
+ expect(email_address.email).to eq user.email
+ expect(email_address.confirmed_at).to be_nil
+ end
+ end
+
describe 'password validations' do
it 'allows long phrases that contain common words' do
user = create(:user)
@@ -87,114 +114,22 @@
end
end
- describe '#piv_cac_enabled?' do
- it 'is true when the user has a confirmed piv/cac associated' do
- user = create(:user, :with_piv_or_cac)
-
- expect(user.piv_cac_enabled?).to eq true
- end
-
- it 'is false when the user has no piv/cac associated' do
- user = create(:user)
-
- expect(user.piv_cac_enabled?).to eq false
- end
- end
-
- describe '#piv_cac_available?' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled).and_return('true')
- end
-
- context 'when a user has no identities' do
- let(:user) { create(:user) }
-
- it 'does not allow piv/cac' do
- expect(user.piv_cac_available?).to be_falsey
- end
- end
-
- context 'when a user has an identity' do
- let(:user) { create(:user) }
-
- let(:service_provider) do
- create(:service_provider)
- end
-
- let(:identity_with_sp) do
- Identity.create(
- user_id: user.id,
- service_provider: service_provider.issuer
- )
- end
-
- before(:each) do
- user.identities << [identity_with_sp]
- end
-
- context 'not allowing it' do
- it 'does not allow piv/cac' do
- expect(user.piv_cac_available?).to be_falsey
- end
- end
-
- context 'allowing it' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_agencies).and_return(
- [service_provider.agency].to_json
- )
- PivCacService.send(:reset_piv_cac_avaialable_agencies)
- end
-
- it 'does allows piv/cac' do
- expect(user.piv_cac_available?).to be_truthy
- end
-
- context 'but piv/cac feature is not enabled' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled).and_return('false')
- end
-
- it 'does not allow piv/cac' do
- expect(user.piv_cac_available?).to be_falsey
- end
- end
- end
- end
-
- context 'when a user has a piv/cac associated' do
- let(:user) { create(:user, :with_piv_or_cac) }
-
- it 'allows piv/cac' do
- expect(user.piv_cac_available?).to be_truthy
- end
-
- context 'but the piv/cac feature is disabled' do
- before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled).and_return('false')
- end
-
- it 'does not allow piv/cac' do
- expect(user.piv_cac_available?).to be_falsey
- end
- end
- end
- end
-
describe '#confirm_piv_cac?' do
context 'when the user has a piv/cac associated' do
let(:user) { create(:user, :with_piv_or_cac) }
it 'is false when a blank is provided' do
- expect(user.confirm_piv_cac?('')).to be_falsey
+ expect(MfaContext.new(user).piv_cac_configuration.mfa_confirmed?('')).to be_falsey
end
it 'is false when a nil is provided' do
- expect(user.confirm_piv_cac?(nil)).to be_falsey
+ expect(MfaContext.new(user).piv_cac_configuration.mfa_confirmed?(nil)).to be_falsey
end
it 'is true when the correct valud is provided' do
- expect(user.confirm_piv_cac?(user.x509_dn_uuid)).to be_truthy
+ expect(
+ MfaContext.new(user).piv_cac_configuration.mfa_confirmed?(user.x509_dn_uuid)
+ ).to be_truthy
end
end
@@ -202,15 +137,17 @@
let(:user) { create(:user) }
it 'is false when a blank is provided' do
- expect(user.confirm_piv_cac?('')).to be_falsey
+ expect(MfaContext.new(user).piv_cac_configuration.mfa_confirmed?('')).to be_falsey
end
it 'is false when a nil is provided' do
- expect(user.confirm_piv_cac?(nil)).to be_falsey
+ expect(MfaContext.new(user).piv_cac_configuration.mfa_confirmed?(nil)).to be_falsey
end
it 'is false when the user x509_dn_uuid value is provided' do
- expect(user.confirm_piv_cac?(user.x509_dn_uuid)).to be_falsey
+ expect(
+ MfaContext.new(user).piv_cac_configuration.mfa_confirmed?(user.x509_dn_uuid)
+ ).to be_falsey
end
end
end
@@ -219,13 +156,13 @@
it 'is true when user has a confirmed phone' do
user = create(:user, :with_phone)
- expect(user.two_factor_enabled?).to eq true
+ expect(MfaPolicy.new(user).two_factor_enabled?).to eq true
end
it 'is false when user does not have a phone' do
user = create(:user)
- expect(user.two_factor_enabled?).to eq false
+ expect(MfaPolicy.new(user).two_factor_enabled?).to eq false
end
end
@@ -235,7 +172,9 @@
it 'is true when two_factor_enabled' do
user = build_stubbed(:user)
- allow(user).to receive(:two_factor_enabled?).and_return true
+ mock_mfa = MfaPolicy.new(user)
+ allow(mock_mfa).to receive(:two_factor_enabled?).and_return true
+ allow(MfaPolicy).to receive(:new).with(user).and_return mock_mfa
expect(user.need_two_factor_authentication?(nil)).to be_truthy
end
@@ -243,7 +182,9 @@
it 'is false when not two_factor_enabled' do
user = build_stubbed(:user)
- allow(user).to receive(:two_factor_enabled?).and_return false
+ mock_mfa = MfaPolicy.new(user)
+ allow(mock_mfa).to receive(:two_factor_enabled?).and_return false
+ allow(MfaPolicy).to receive(:new).with(user).and_return(mock_mfa)
expect(user.need_two_factor_authentication?(nil)).to be_falsey
end
diff --git a/spec/models/webauthn_configuration_spec.rb b/spec/models/webauthn_configuration_spec.rb
new file mode 100644
index 00000000000..76f3f39411b
--- /dev/null
+++ b/spec/models/webauthn_configuration_spec.rb
@@ -0,0 +1,42 @@
+require 'rails_helper'
+
+describe WebauthnConfiguration do
+ describe 'Associations' do
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to validate_presence_of(:user_id) }
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_presence_of(:credential_id) }
+ it { is_expected.to validate_presence_of(:credential_public_key) }
+ end
+
+ let(:subject) { create(:webauthn_configuration) }
+
+ describe '#selection_presenters' do
+ it 'returns a WebauthnSelectionPresenter in an array' do
+ presenters = subject.selection_presenters
+ expect(presenters.count).to eq 1
+ expect(presenters.first).to be_instance_of(
+ TwoFactorAuthentication::WebauthnSelectionPresenter
+ )
+ end
+ end
+
+ describe '#mfa_enabled?' do
+ let(:mfa_enabled) { subject.mfa_enabled? }
+ context 'when webauthn enabled' do
+ before(:each) do
+ allow(FeatureManagement).to receive(:webauthn_enabled?).and_return(true)
+ end
+
+ it { expect(mfa_enabled).to be_truthy }
+ end
+
+ context 'when webauthn not enabled' do
+ before(:each) do
+ allow(FeatureManagement).to receive(:webauthn_enabled?).and_return(false)
+ end
+
+ it { expect(mfa_enabled).to be_falsey }
+ end
+ end
+end
diff --git a/spec/policies/piv_cac_login_option_policy_spec.rb b/spec/policies/piv_cac_login_option_policy_spec.rb
deleted file mode 100644
index b17cefbb3c9..00000000000
--- a/spec/policies/piv_cac_login_option_policy_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-require 'rails_helper'
-
-describe PivCacLoginOptionPolicy do
- let(:subject) { described_class.new(user) }
-
- describe '#configured?' do
- context 'without a piv configured' do
- let(:user) { build(:user) }
-
- it { expect(subject.configured?).to be_falsey }
- end
-
- context 'with a piv configured' do
- let(:user) { build(:user, :with_piv_or_cac) }
-
- it { expect(subject.configured?).to be_truthy }
- end
- end
-
- describe '#enabled?' do
- context 'without a piv configured' do
- let(:user) { build(:user) }
-
- it { expect(subject.configured?).to be_falsey }
- end
-
- context 'with a piv configured' do
- let(:user) { build(:user, :with_piv_or_cac) }
-
- it { expect(subject.configured?).to be_truthy }
- end
- end
-
- describe '#available?' do
- let(:user) { build(:user) }
-
- context 'when enabled' do
- before(:each) do
- allow(subject).to receive(:enabled?).and_return(true)
- end
-
- it { expect(subject.available?).to be_truthy }
- end
-
- context 'when associated with a supported identity' do
- before(:each) do
- identity = double
- allow(identity).to receive(:piv_cac_available?).and_return(true)
- allow(user).to receive(:identities).and_return([identity])
- end
-
- it { expect(subject.available?).to be_truthy }
- end
-
- context 'when not enabled and not a supported identity' do
- before(:each) do
- identity = double
- allow(identity).to receive(:piv_cac_available?).and_return(false)
- allow(user).to receive(:identities).and_return([identity])
- allow(subject).to receive(:enabled?).and_return(false)
- end
-
- it { expect(subject.available?).to be_falsey }
- end
- end
-end
diff --git a/spec/policies/two_factor_authentication/piv_cac_policy_spec.rb b/spec/policies/two_factor_authentication/piv_cac_policy_spec.rb
new file mode 100644
index 00000000000..115c3760ac8
--- /dev/null
+++ b/spec/policies/two_factor_authentication/piv_cac_policy_spec.rb
@@ -0,0 +1,126 @@
+require 'rails_helper'
+
+describe TwoFactorAuthentication::PivCacPolicy do
+ let(:subject) { described_class.new(user) }
+
+ describe '#available?' do
+ context 'when a user has no identities' do
+ let(:user) { create(:user) }
+
+ it 'does not allow piv/cac' do
+ expect(subject.available?).to be_falsey
+ end
+ end
+
+ context 'when a user has an identity' do
+ let(:user) { create(:user) }
+
+ let(:service_provider) do
+ create(:service_provider)
+ end
+
+ let(:identity_with_sp) do
+ Identity.create(
+ user_id: user.id,
+ service_provider: service_provider.issuer
+ )
+ end
+
+ before(:each) do
+ user.identities << [identity_with_sp]
+ end
+
+ context 'not allowing it' do
+ it 'does not allow piv/cac' do
+ expect(subject.available?).to be_falsey
+ end
+ end
+
+ context 'allowing it' do
+ before(:each) do
+ allow(Figaro.env).to receive(:piv_cac_agencies).and_return(
+ [service_provider.agency].to_json
+ )
+ PivCacService.send(:reset_piv_cac_avaialable_agencies)
+ end
+
+ it 'does allows piv/cac' do
+ expect(subject.available?).to be_truthy
+ end
+ end
+ end
+
+ context 'when a user has a piv/cac associated' do
+ let(:user) { create(:user, :with_piv_or_cac) }
+
+ it 'disallows piv/cac setup' do
+ expect(subject.available?).to be_falsey
+ end
+
+ it 'allow piv/cac visibility' do
+ expect(subject.visible?).to be_truthy
+ end
+ end
+ end
+
+ describe '#configured?' do
+ context 'without a piv configured' do
+ let(:user) { build(:user) }
+
+ it { expect(subject.configured?).to be_falsey }
+ end
+
+ context 'with a piv configured' do
+ let(:user) { build(:user, :with_piv_or_cac) }
+
+ it { expect(subject.configured?).to be_truthy }
+ end
+ end
+
+ describe '#enabled?' do
+ context 'without a piv configured' do
+ let(:user) { build(:user) }
+
+ it { expect(subject.configured?).to be_falsey }
+ end
+
+ context 'with a piv configured' do
+ let(:user) { build(:user, :with_piv_or_cac) }
+
+ it { expect(subject.configured?).to be_truthy }
+ end
+ end
+
+ describe '#visible?' do
+ let(:user) { build(:user) }
+
+ context 'when enabled' do
+ before(:each) do
+ allow(subject).to receive(:enabled?).and_return(true)
+ end
+
+ it { expect(subject.visible?).to be_truthy }
+ end
+
+ context 'when associated with a supported identity' do
+ before(:each) do
+ identity = double
+ allow(identity).to receive(:piv_cac_available?).and_return(true)
+ allow(user).to receive(:identities).and_return([identity])
+ end
+
+ it { expect(subject.visible?).to be_truthy }
+ end
+
+ context 'when not enabled and not a supported identity' do
+ before(:each) do
+ identity = double
+ allow(identity).to receive(:piv_cac_available?).and_return(false)
+ allow(user).to receive(:identities).and_return([identity])
+ allow(subject).to receive(:enabled?).and_return(false)
+ end
+
+ it { expect(subject.visible?).to be_falsey }
+ end
+ end
+end
diff --git a/spec/policies/user_mfa_policy_spec.rb b/spec/policies/user_mfa_policy_spec.rb
new file mode 100644
index 00000000000..a3af0d5968e
--- /dev/null
+++ b/spec/policies/user_mfa_policy_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+describe MfaPolicy do
+ let(:subject) { described_class.new(user) }
+
+ context 'no mfa configurations' do
+ let(:user) { create(:user) }
+
+ it { expect(subject.two_factor_enabled?).to eq false }
+ it { expect(subject.multiple_factors_enabled?).to eq false }
+ end
+
+ context 'one mfa configuration' do
+ let(:user) { create(:user, :with_phone) }
+
+ it { expect(subject.two_factor_enabled?).to eq true }
+ it { expect(subject.multiple_factors_enabled?).to eq false }
+ end
+
+ context 'two mfa configuration' do
+ let(:user) { create(:user, :with_phone, :with_piv_or_cac) }
+
+ it { expect(subject.two_factor_enabled?).to eq true }
+ it { expect(subject.multiple_factors_enabled?).to eq true }
+ end
+end
diff --git a/spec/policies/webauthn_login_option_policy_spec.rb b/spec/policies/webauthn_login_option_policy_spec.rb
new file mode 100644
index 00000000000..41e6f8f73a5
--- /dev/null
+++ b/spec/policies/webauthn_login_option_policy_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+describe TwoFactorAuthentication::WebauthnPolicy do
+ include WebauthnVerificationHelper
+
+ describe '#configured?' do
+ context 'with no sp' do
+ let(:subject) { described_class.new(user, nil) }
+
+ context 'without a webauthn configured' do
+ let(:user) { build(:user) }
+
+ it { expect(subject.configured?).to be_falsey }
+ end
+
+ context 'with a webauthn configured' do
+ let(:user) { create(:user) }
+ before do
+ create_webauthn_configuration(user)
+ end
+
+ it 'returns a truthy value' do
+ expect(subject.configured?).to be_truthy
+ end
+ end
+ end
+
+ context 'with an sp' do
+ let(:subject) { described_class.new(user, 'foo') }
+
+ context 'without a webauthn configured' do
+ let(:user) { build(:user) }
+
+ it { expect(subject.configured?).to be_falsey }
+ end
+
+ context 'with a webauthn configured' do
+ let(:user) { create(:user) }
+ before do
+ create_webauthn_configuration(user)
+ end
+
+ it 'returns a truthy value' do
+ expect(subject.configured?).to be_falsey
+ end
+ end
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_auth_code/authenticator_delivery_presenter_spec.rb b/spec/presenters/two_factor_auth_code/authenticator_delivery_presenter_spec.rb
index 7f4d9f4e860..7eeb436c35b 100644
--- a/spec/presenters/two_factor_auth_code/authenticator_delivery_presenter_spec.rb
+++ b/spec/presenters/two_factor_auth_code/authenticator_delivery_presenter_spec.rb
@@ -9,7 +9,7 @@
describe '#header' do
it 'supplies a header' do
- expect(presenter.header).to eq(t('devise.two_factor_authentication.totp_header_text'))
+ expect(presenter.header).to eq(t('two_factor_authentication.totp_header_text'))
end
end
diff --git a/spec/presenters/two_factor_auth_code/generic_delivery_presenter_spec.rb b/spec/presenters/two_factor_auth_code/generic_delivery_presenter_spec.rb
index 394a38e9dd2..acdc077c739 100644
--- a/spec/presenters/two_factor_auth_code/generic_delivery_presenter_spec.rb
+++ b/spec/presenters/two_factor_auth_code/generic_delivery_presenter_spec.rb
@@ -11,48 +11,6 @@
end
end
- describe '#piv_cac_option' do
- context 'for a user without a piv/cac enabled' do
- let(:presenter) { presenter_with(has_piv_cac_configured: false) }
-
- it 'returns nothing' do
- expect(presenter.send(:piv_cac_option)).to be_nil
- end
- end
-
- context 'for a user with a piv/cac enabled' do
- let(:presenter) { presenter_with(has_piv_cac_configured: true) }
-
- it 'returns a link to the piv/cac option' do
- expect(presenter.send(:piv_cac_option)).to eq t(
- 'devise.two_factor_authentication.piv_cac_fallback.text_html',
- link: presenter.send(:piv_cac_link)
- )
- end
- end
- end
-
- describe '#piv_cac_link' do
- context 'for a user without a piv/cac enabled' do
- let(:presenter) { presenter_with(has_piv_cac_configured: false) }
-
- it 'returns nothing' do
- expect(presenter.send(:piv_cac_link)).to be_nil
- end
- end
-
- context 'for a user with a piv/cac enabled' do
- let(:presenter) { presenter_with(has_piv_cac_configured: true) }
-
- it 'returns a link to the piv/cac option' do
- expect(presenter.send(:piv_cac_link)).to eq ActionController::Base.new.view_context.link_to(
- t('devise.two_factor_authentication.piv_cac_fallback.link'),
- login_two_factor_piv_cac_path(locale: LinkLocaleResolver.locale)
- )
- end
- end
- end
-
def presenter_with(arguments = {}, view = ActionController::Base.new.view_context)
TwoFactorAuthCode::GenericDeliveryPresenter.new(data: arguments, view: view)
end
diff --git a/spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb b/spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb
index 4650062ec07..4bb56e1e613 100644
--- a/spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb
+++ b/spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb
@@ -58,14 +58,14 @@
end
def account_reset_cancel_link(account_reset_token)
- I18n.t('devise.two_factor_authentication.account_reset.pending_html', cancel_link:
- view.link_to(t('devise.two_factor_authentication.account_reset.cancel_link'),
+ I18n.t('two_factor_authentication.account_reset.pending_html', cancel_link:
+ view.link_to(t('two_factor_authentication.account_reset.cancel_link'),
account_reset_cancel_url(token: account_reset_token)))
end
def account_reset_delete_account_link
- I18n.t('devise.two_factor_authentication.account_reset.text_html', link:
- view.link_to(t('devise.two_factor_authentication.account_reset.link'),
+ I18n.t('two_factor_authentication.account_reset.text_html', link:
+ view.link_to(t('two_factor_authentication.account_reset.link'),
account_reset_request_path(locale: LinkLocaleResolver.locale)))
end
end
diff --git a/spec/presenters/two_factor_auth_code/piv_cac_authentication_presenter_spec.rb b/spec/presenters/two_factor_auth_code/piv_cac_authentication_presenter_spec.rb
index 9449d4137e0..b9282a9e425 100644
--- a/spec/presenters/two_factor_auth_code/piv_cac_authentication_presenter_spec.rb
+++ b/spec/presenters/two_factor_auth_code/piv_cac_authentication_presenter_spec.rb
@@ -13,7 +13,7 @@ def presenter_with(arguments = {}, view = ActionController::Base.new.view_contex
let(:presenter) { presenter_with(reauthn: reauthn, user_email: user_email) }
describe '#header' do
- let(:expected_header) { t('devise.two_factor_authentication.piv_cac_header_text') }
+ let(:expected_header) { t('two_factor_authentication.piv_cac_header_text') }
it { expect(presenter.header).to eq expected_header }
end
diff --git a/spec/presenters/two_factor_auth_code/webauthn_authentication_presenter_spec.rb b/spec/presenters/two_factor_auth_code/webauthn_authentication_presenter_spec.rb
new file mode 100644
index 00000000000..ec0d6f474a8
--- /dev/null
+++ b/spec/presenters/two_factor_auth_code/webauthn_authentication_presenter_spec.rb
@@ -0,0 +1,56 @@
+require 'rails_helper'
+
+describe TwoFactorAuthCode::WebauthnAuthenticationPresenter do
+ include Rails.application.routes.url_helpers
+
+ let(:view) { ActionController::Base.new.view_context }
+ let(:reauthn) {}
+ let(:presenter) do
+ TwoFactorAuthCode::WebauthnAuthenticationPresenter.
+ new(data: { reauthn: reauthn }, view: view)
+ end
+
+ describe '#help_text' do
+ it 'supplies no help text' do
+ expect(presenter.help_text).to eq('')
+ end
+ end
+
+ describe '#fallback_question' do
+ it 'supplies a fallback_question' do
+ expect(presenter.fallback_question).to \
+ eq(t('two_factor_authentication.webauthn_fallback.question'))
+ end
+ end
+
+ describe '#cancel_link' do
+ let(:locale) { LinkLocaleResolver.locale }
+
+ context 'reauthn' do
+ let(:reauthn) { true }
+
+ it 'returns the account path' do
+ expect(presenter.cancel_link).to eq account_path(locale: locale)
+ end
+ end
+
+ context 'not reauthn' do
+ let(:reauthn) { false }
+
+ it 'returns the sign out path' do
+ expect(presenter.cancel_link).to eq sign_out_path(locale: locale)
+ end
+ end
+ end
+
+ it 'handles multiple locales' do
+ I18n.available_locales.each do |locale|
+ I18n.locale = locale
+ if locale == :en
+ expect(presenter.cancel_link).not_to match(%r{/en/})
+ else
+ expect(presenter.cancel_link).to match(%r{/#{locale}/})
+ end
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb
new file mode 100644
index 00000000000..43c92e4786f
--- /dev/null
+++ b/spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+describe TwoFactorAuthentication::AuthAppSelectionPresenter do
+ let(:subject) { described_class.new(configuration) }
+ let(:configuration) {}
+
+ describe '#type' do
+ it 'returns auth_app' do
+ expect(subject.type).to eq 'auth_app'
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_authentication/personal_key_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/personal_key_selection_presenter_spec.rb
new file mode 100644
index 00000000000..f5204d63262
--- /dev/null
+++ b/spec/presenters/two_factor_authentication/personal_key_selection_presenter_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+describe TwoFactorAuthentication::PersonalKeySelectionPresenter do
+ let(:subject) { described_class.new(configuration) }
+ let(:configuration) {}
+
+ describe '#type' do
+ it 'returns personal_key' do
+ expect(subject.type).to eq 'personal_key'
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_authentication/piv_cac_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/piv_cac_selection_presenter_spec.rb
new file mode 100644
index 00000000000..5cd077d058b
--- /dev/null
+++ b/spec/presenters/two_factor_authentication/piv_cac_selection_presenter_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+describe TwoFactorAuthentication::PivCacSelectionPresenter do
+ let(:subject) { described_class.new(configuration) }
+ let(:configuration) {}
+
+ describe '#type' do
+ it 'returns piv_cac' do
+ expect(subject.type).to eq 'piv_cac'
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_authentication/sms_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/sms_selection_presenter_spec.rb
new file mode 100644
index 00000000000..ce1794d1579
--- /dev/null
+++ b/spec/presenters/two_factor_authentication/sms_selection_presenter_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+describe TwoFactorAuthentication::SmsSelectionPresenter do
+ let(:subject) { described_class.new(phone) }
+
+ describe '#type' do
+ context 'when a user has only one phone configuration' do
+ let(:user) { create(:user, :with_phone) }
+ let(:phone) { MfaContext.new(user).phone_configurations.first }
+
+ it 'returns sms' do
+ expect(subject.type).to eq 'sms'
+ end
+ end
+
+ context 'when a user has more than one phone configuration' do
+ let(:user) { create(:user, :with_phone) }
+ let(:phone) do
+ record = create(:phone_configuration, user: user)
+ user.reload
+ record
+ end
+
+ it 'returns sms:id' do
+ expect(subject.type).to eq "sms:#{phone.id}"
+ end
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_authentication/voice_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/voice_selection_presenter_spec.rb
new file mode 100644
index 00000000000..9e2089fa988
--- /dev/null
+++ b/spec/presenters/two_factor_authentication/voice_selection_presenter_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+describe TwoFactorAuthentication::VoiceSelectionPresenter do
+ let(:subject) { described_class.new(phone) }
+
+ describe '#type' do
+ context 'when a user has only one phone configuration' do
+ let(:user) { create(:user, :with_phone) }
+ let(:phone) { MfaContext.new(user).phone_configurations.first }
+
+ it 'returns voice' do
+ expect(subject.type).to eq 'voice'
+ end
+ end
+
+ context 'when a user has more than one phone configuration' do
+ let(:user) { create(:user, :with_phone) }
+ let(:phone) do
+ record = create(:phone_configuration, user: user)
+ user.reload
+ record
+ end
+
+ it 'returns voice:id' do
+ expect(subject.type).to eq "voice:#{phone.id}"
+ end
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_authentication/webauthn_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/webauthn_selection_presenter_spec.rb
new file mode 100644
index 00000000000..74457bdb301
--- /dev/null
+++ b/spec/presenters/two_factor_authentication/webauthn_selection_presenter_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+describe TwoFactorAuthentication::WebauthnSelectionPresenter do
+ let(:subject) { described_class.new(configuration) }
+ let(:configuration) {}
+
+ describe '#type' do
+ it 'returns webauthn' do
+ expect(subject.type).to eq 'webauthn'
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_login_options_presenter_spec.rb b/spec/presenters/two_factor_login_options_presenter_spec.rb
index c39f5d4bcd5..660413baace 100644
--- a/spec/presenters/two_factor_login_options_presenter_spec.rb
+++ b/spec/presenters/two_factor_login_options_presenter_spec.rb
@@ -27,9 +27,9 @@
receive(:account_reset_token).and_return('foo')
expect(presenter.account_reset_or_cancel_link).to eq \
- t('devise.two_factor_authentication.account_reset.pending_html',
+ t('two_factor_authentication.account_reset.pending_html',
cancel_link: view.link_to(
- t('devise.two_factor_authentication.account_reset.cancel_link'),
+ t('two_factor_authentication.account_reset.cancel_link'),
account_reset_cancel_url(token: 'foo')
))
end
@@ -39,9 +39,9 @@
receive(:account_reset_token_valid?).and_return(false)
expect(presenter.account_reset_or_cancel_link).to eq \
- t('devise.two_factor_authentication.account_reset.text_html',
+ t('two_factor_authentication.account_reset.text_html',
link: view.link_to(
- t('devise.two_factor_authentication.account_reset.link'),
+ t('two_factor_authentication.account_reset.link'),
account_reset_request_path(locale: LinkLocaleResolver.locale)
))
end
diff --git a/spec/presenters/two_factor_options_presenter_spec.rb b/spec/presenters/two_factor_options_presenter_spec.rb
new file mode 100644
index 00000000000..472ec89aa79
--- /dev/null
+++ b/spec/presenters/two_factor_options_presenter_spec.rb
@@ -0,0 +1,69 @@
+require 'rails_helper'
+
+describe TwoFactorOptionsPresenter do
+ include Rails.application.routes.url_helpers
+
+ let(:user) { build(:user) }
+ let(:presenter) do
+ described_class.new(user, nil)
+ end
+
+ it 'supplies a title' do
+ expect(presenter.title).to eq \
+ t('titles.two_factor_setup')
+ end
+
+ it 'supplies a heading' do
+ expect(presenter.heading).to eq \
+ t('two_factor_authentication.two_factor_choice')
+ end
+
+ describe '#options' do
+ it 'supplies all the options for a user with no mfa configured' do
+ expect(presenter.options.map(&:class)).to eq [
+ TwoFactorAuthentication::SmsSelectionPresenter,
+ TwoFactorAuthentication::VoiceSelectionPresenter,
+ TwoFactorAuthentication::AuthAppSelectionPresenter,
+ TwoFactorAuthentication::WebauthnSelectionPresenter,
+ ]
+ end
+
+ context 'with a user with a phone configured' do
+ let(:user) { build(:user, :with_phone) }
+
+ it 'supplies all the options' do
+ expect(presenter.options.map(&:class)).to eq [
+ TwoFactorAuthentication::SmsSelectionPresenter,
+ TwoFactorAuthentication::VoiceSelectionPresenter,
+ TwoFactorAuthentication::AuthAppSelectionPresenter,
+ TwoFactorAuthentication::WebauthnSelectionPresenter,
+ ]
+ end
+ end
+
+ context 'with a user with totp configured' do
+ let(:user) { build(:user, :with_authentication_app) }
+
+ it 'supplies all the options but the auth app' do
+ expect(presenter.options.map(&:class)).to eq [
+ TwoFactorAuthentication::SmsSelectionPresenter,
+ TwoFactorAuthentication::VoiceSelectionPresenter,
+ TwoFactorAuthentication::WebauthnSelectionPresenter,
+ ]
+ end
+ end
+
+ context 'with a user with webauthn configured' do
+ let(:user) { build(:user, :with_webauthn) }
+
+ it 'supplies all the options' do
+ expect(presenter.options.map(&:class)).to eq [
+ TwoFactorAuthentication::SmsSelectionPresenter,
+ TwoFactorAuthentication::VoiceSelectionPresenter,
+ TwoFactorAuthentication::AuthAppSelectionPresenter,
+ TwoFactorAuthentication::WebauthnSelectionPresenter,
+ ]
+ end
+ end
+ end
+end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 2654e358a0b..fecc5585a22 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -17,7 +17,6 @@
require 'spec_helper'
require 'email_spec'
require 'factory_bot'
-require 'sidekiq/testing'
# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
@@ -72,21 +71,9 @@
FakeVoiceCall.calls = []
end
- config.before(:each, idv_job: true) do
- allow(Idv::ProoferJob).to receive(:perform_later) do |*args|
- Idv::ProoferJob.perform_now(*args)
- end
- end
-
config.around(:each, user_flow: true) do |example|
Capybara.current_driver = :rack_test
example.run
Capybara.use_default_driver
end
end
-
-Sidekiq::Testing.inline!
-
-Sidekiq::Testing.server_middleware do |chain|
- chain.add WorkerHealthChecker::Middleware
-end
diff --git a/spec/requests/constrained_route_spec.rb b/spec/requests/constrained_route_spec.rb
deleted file mode 100644
index b5a1bdc7c57..00000000000
--- a/spec/requests/constrained_route_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'rails_helper'
-
-describe 'routes that require admin + 2FA' do
- def sign_in_user(user)
- post(
- new_user_session_path,
- params: { user: { email: user.email, password: user.password } }
- )
- get otp_send_path, params: { otp_delivery_selection_form: { otp_delivery_preference: 'sms' } }
- follow_redirect!
- post(
- login_two_factor_path,
- params: { otp_delivery_preference: 'sms', code: user.reload.direct_otp }
- )
- end
-
- shared_examples 'constrained route' do |endpoint|
- context 'user is signed in via 2FA but is not an admin' do
- it 'does not allow access' do
- user = create(:user, :signed_up)
- sign_in_user(user)
-
- get endpoint
-
- expect(response.body).
- to match('The page you were looking for doesn't exist')
- end
- end
-
- context 'user is an admin but is not signed in via 2FA' do
- it 'prompts admin to 2FA' do
- user = create(:user, :signed_up, :admin)
-
- post(
- new_user_session_path,
- params: { user: { email: user.email, password: user.password } }
- )
-
- get endpoint
-
- expect(response.status).to eq 404
- end
- end
-
- context 'user is an admin and is signed in via 2FA' do
- it 'allows access' do
- user = create(:user, :signed_up, :admin)
- sign_in_user(user)
-
- get endpoint
-
- expect(response.body).
- to_not match('The page you were looking for doesn't exist')
- end
- end
- end
-
- it_behaves_like 'constrained route', '/sidekiq'
-end
diff --git a/spec/requests/invalid_sign_in_params_spec.rb b/spec/requests/invalid_sign_in_params_spec.rb
index c0dd3dc1b33..c4721221143 100644
--- a/spec/requests/invalid_sign_in_params_spec.rb
+++ b/spec/requests/invalid_sign_in_params_spec.rb
@@ -5,3 +5,9 @@
get new_user_session_path, params: { user: 'test@test.com' }
end
end
+
+context 'when the request_id param is present but with a nil value' do
+ it 'does not raise an error' do
+ get new_user_session_path, params: { request_id: nil }
+ end
+end
diff --git a/spec/services/account_reset/cancel_spec.rb b/spec/services/account_reset/cancel_spec.rb
index 85bd56db94e..c34f82109b2 100644
--- a/spec/services/account_reset/cancel_spec.rb
+++ b/spec/services/account_reset/cancel_spec.rb
@@ -22,7 +22,7 @@
context 'when the token is valid' do
context 'when the user has a phone enabled for SMS' do
before(:each) do
- user.phone_configurations.first.update!(delivery_preference: :sms)
+ MfaContext.new(user).phone_configurations.first.update!(delivery_preference: :sms)
end
it 'notifies the user via SMS of the account reset cancellation' do
@@ -31,8 +31,11 @@
AccountReset::Cancel.new(token).call
- expect(SmsAccountResetCancellationNotifierJob).
- to have_received(:perform_now).with(phone: user.phone_configurations.first.phone)
+ expect(
+ SmsAccountResetCancellationNotifierJob
+ ).to have_received(:perform_now).with(
+ phone: MfaContext.new(user).phone_configurations.first.phone
+ )
end
end
@@ -40,7 +43,7 @@
it 'does not notify the user via SMS' do
token = create_account_reset_request_for(user)
allow(SmsAccountResetCancellationNotifierJob).to receive(:perform_now)
- user.phone_configurations.clear
+ MfaContext.new(user).phone_configurations.clear
AccountReset::Cancel.new(token).call
@@ -86,7 +89,7 @@
context 'when the user does not have a phone enabled for SMS' do
it 'does not notify the user via SMS' do
allow(SmsAccountResetCancellationNotifierJob).to receive(:perform_now)
- user.phone_configurations.first.update!(mfa_enabled: false)
+ MfaContext.new(user).phone_configurations.first.update!(mfa_enabled: false)
AccountReset::Cancel.new('foo').call
diff --git a/spec/services/account_reset/grant_request_spec.rb b/spec/services/account_reset/grant_request_spec.rb
new file mode 100644
index 00000000000..fe85fc9dd56
--- /dev/null
+++ b/spec/services/account_reset/grant_request_spec.rb
@@ -0,0 +1,18 @@
+require 'rails_helper'
+
+describe AccountReset::GrantRequest do
+ include AccountResetHelper
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+
+ describe '#call' do
+ it 'adds a notified at timestamp and granted token to the user' do
+ create_account_reset_request_for(user)
+ AccountReset::GrantRequest.new(user).call
+ arr = AccountResetRequest.find_by(user_id: user.id)
+ expect(arr.granted_at).to be_present
+ expect(arr.granted_token).to be_present
+ end
+ end
+end
diff --git a/spec/services/account_reset/grant_requests_and_send_emails_spec.rb b/spec/services/account_reset/grant_requests_and_send_emails_spec.rb
new file mode 100644
index 00000000000..e46352b8789
--- /dev/null
+++ b/spec/services/account_reset/grant_requests_and_send_emails_spec.rb
@@ -0,0 +1,78 @@
+require 'rails_helper'
+
+describe AccountReset::GrantRequestsAndSendEmails do
+ include AccountResetHelper
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+
+ describe '#call' do
+ context 'after waiting the full wait period' do
+ it 'does not send notifications when the notifications were already sent' do
+ create_account_reset_request_for(user)
+
+ after_waiting_the_full_wait_period do
+ AccountReset::GrantRequestsAndSendEmails.new.call
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.call
+ expect(notifications_sent).to eq(0)
+ end
+ end
+
+ it 'does not send notifications when the request was cancelled' do
+ create_account_reset_request_for(user)
+ cancel_request_for(user)
+
+ after_waiting_the_full_wait_period do
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.call
+ expect(notifications_sent).to eq(0)
+ end
+ end
+
+ it 'sends notifications after a request is granted' do
+ create_account_reset_request_for(user)
+
+ after_waiting_the_full_wait_period do
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.call
+
+ expect(notifications_sent).to eq(1)
+ end
+ end
+
+ it 'sends 2 notifications after 2 requests are granted' do
+ create_account_reset_request_for(user)
+ create_account_reset_request_for(user2)
+
+ after_waiting_the_full_wait_period do
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.call
+
+ expect(notifications_sent).to eq(2)
+ end
+ end
+ end
+
+ context 'after not waiting the full wait period' do
+ it 'does not send notifications after a request' do
+ create_account_reset_request_for(user)
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.call
+ expect(notifications_sent).to eq(0)
+ end
+
+ it 'does not send notifications when the request was cancelled' do
+ create_account_reset_request_for(user)
+ cancel_request_for(user)
+
+ notifications_sent = AccountReset::GrantRequestsAndSendEmails.new.call
+ expect(notifications_sent).to eq(0)
+ end
+ end
+ end
+
+ def after_waiting_the_full_wait_period
+ TwilioService::Utils.telephony_service = FakeSms
+ days = Figaro.env.account_reset_wait_period_days.to_i.days
+ Timecop.travel(Time.zone.now + days) do
+ yield
+ end
+ end
+end
diff --git a/spec/services/account_reset_service_spec.rb b/spec/services/account_reset_service_spec.rb
deleted file mode 100644
index a7b50bb112d..00000000000
--- a/spec/services/account_reset_service_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-require 'rails_helper'
-
-describe AccountResetService do
- include AccountResetHelper
-
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
-
- describe '#report_fraud' do
- it 'removes tokens from the request' do
- create_account_reset_request_for(user)
- AccountResetService.report_fraud(user.account_reset_request.request_token)
- arr = AccountResetRequest.find_by(user_id: user.id)
- expect(arr.request_token).to_not be_present
- expect(arr.granted_token).to_not be_present
- expect(arr.requested_at).to be_present
- expect(arr.cancelled_at).to be_present
- expect(arr.reported_fraud_at).to be_present
- end
-
- it 'does not raise an error for a fraud request with a blank token' do
- token_found = AccountResetService.report_fraud('')
- expect(token_found).to be(false)
- end
-
- it 'does not raise an error for a cancel request with a nil token' do
- token_found = AccountResetService.report_fraud('')
- expect(token_found).to be(false)
- end
-
- it 'does not raise an error for a cancel request with a bad token' do
- token_found = AccountResetService.report_fraud('ABC')
- expect(token_found).to be(false)
- end
- end
-
- describe '#grant_request' do
- it 'adds a notified at timestamp and granted token to the user' do
- create_account_reset_request_for(user)
- AccountResetService.new(user).grant_request
- arr = AccountResetRequest.find_by(user_id: user.id)
- expect(arr.granted_at).to be_present
- expect(arr.granted_token).to be_present
- end
- end
-
- describe '.grant_tokens_and_send_notifications' do
- context 'after waiting the full wait period' do
- it 'does not send notifications when the notifications were already sent' do
- create_account_reset_request_for(user)
-
- after_waiting_the_full_wait_period do
- AccountResetService.grant_tokens_and_send_notifications
- notifications_sent = AccountResetService.grant_tokens_and_send_notifications
- expect(notifications_sent).to eq(0)
- end
- end
-
- it 'does not send notifications when the request was cancelled' do
- create_account_reset_request_for(user)
- cancel_request_for(user)
-
- after_waiting_the_full_wait_period do
- notifications_sent = AccountResetService.grant_tokens_and_send_notifications
- expect(notifications_sent).to eq(0)
- end
- end
-
- it 'sends notifications after a request is granted' do
- create_account_reset_request_for(user)
-
- after_waiting_the_full_wait_period do
- notifications_sent = AccountResetService.grant_tokens_and_send_notifications
-
- expect(notifications_sent).to eq(1)
- end
- end
-
- it 'sends 2 notifications after 2 requests are granted' do
- create_account_reset_request_for(user)
- create_account_reset_request_for(user2)
-
- after_waiting_the_full_wait_period do
- notifications_sent = AccountResetService.grant_tokens_and_send_notifications
-
- expect(notifications_sent).to eq(2)
- end
- end
- end
-
- context 'after not waiting the full wait period' do
- it 'does not send notifications after a request' do
- create_account_reset_request_for(user)
-
- notifications_sent = AccountResetService.grant_tokens_and_send_notifications
- expect(notifications_sent).to eq(0)
- end
-
- it 'does not send notifications when the request was cancelled' do
- create_account_reset_request_for(user)
- cancel_request_for(user)
-
- notifications_sent = AccountResetService.grant_tokens_and_send_notifications
- expect(notifications_sent).to eq(0)
- end
- end
- end
-
- def after_waiting_the_full_wait_period
- TwilioService::Utils.telephony_service = FakeSms
- days = Figaro.env.account_reset_wait_period_days.to_i.days
- Timecop.travel(Time.zone.now + days) do
- yield
- end
- end
-end
diff --git a/spec/services/encryption/password_verifier_spec.rb b/spec/services/encryption/password_verifier_spec.rb
index fa93fbcb490..e62bcd53aa9 100644
--- a/spec/services/encryption/password_verifier_spec.rb
+++ b/spec/services/encryption/password_verifier_spec.rb
@@ -4,6 +4,11 @@
describe '.digest' do
it 'creates a digest from the password' do
salt = '1' * 64 # 32 hex encoded bytes is 64 characters
+ # The newrelic_rpm gem added a call to `SecureRandom.hex(8)` in
+ # abstract_segment.rb on 6/13/18. Our New Relic tracers in
+ # config/initializers/new_relic_tracers.rb trigger this call, which
+ # is why we stub with a default value first.
+ allow(SecureRandom).to receive(:hex) { salt }
allow(SecureRandom).to receive(:hex).once.with(32).and_return(salt)
digest = described_class.digest('saltypickles')
diff --git a/spec/services/encryption/user_access_key_spec.rb b/spec/services/encryption/user_access_key_spec.rb
index 8a3f0a05c11..5a5f264ba26 100644
--- a/spec/services/encryption/user_access_key_spec.rb
+++ b/spec/services/encryption/user_access_key_spec.rb
@@ -21,6 +21,11 @@
before do
allow(FeatureManagement).to receive(:use_kms?).and_return(true)
+ # The newrelic_rpm gem added a call to `SecureRandom.hex(8)` in
+ # abstract_segment.rb on 6/13/18. Our New Relic tracers in
+ # config/initializers/new_relic_tracers.rb trigger this call, which
+ # is why we stub with a default value first.
+ allow(SecureRandom).to receive(:random_bytes) { random_r }
allow(SecureRandom).to receive(:random_bytes).with(32).and_return(random_r)
stub_aws_kms_client(random_r, encrypted_random_r)
end
@@ -78,7 +83,7 @@
it 'assigns random_r and calculates the cek, encryption_key, and encrypted_password' do
subject.build
- expect(SecureRandom).to have_received(:random_bytes).once
+ expect(SecureRandom).to have_received(:random_bytes).with(32).once
expect(subject.random_r).to eq(random_r)
expect(subject.encryption_key).to eq(encryption_key)
expect(subject.cek).to eq(cek)
@@ -90,7 +95,7 @@
it 'derives random_r from the encryption key and sets the cek and encrypted password' do
subject.unlock(encryption_key)
- expect(SecureRandom).to_not have_received(:random_bytes)
+ expect(SecureRandom).to_not have_received(:random_bytes).with(32)
expect(subject.random_r).to eq(random_r)
expect(subject.encryption_key).to eq(encryption_key)
expect(subject.cek).to eq(cek)
diff --git a/spec/services/idv/acuant/assure_id_spec.rb b/spec/services/idv/acuant/assure_id_spec.rb
new file mode 100644
index 00000000000..f012491fb58
--- /dev/null
+++ b/spec/services/idv/acuant/assure_id_spec.rb
@@ -0,0 +1,141 @@
+require 'rails_helper'
+
+describe Idv::Acuant::AssureId do
+ let(:subject) { Idv::Acuant::AssureId.new }
+ let(:instance_id) { '123' }
+ let(:accuant_result_2) { '{"Result":2,"Alerts":[{"Actions":"Check the document"}]}' }
+ let(:good_acuant_status) { [true, '{"Result":1}'] }
+ let(:bad_acuant_status) { [false, ''] }
+ let(:good_http_status) { { status: 200, body: '{"Result":1}' } }
+ let(:failure_alerts_status) { { status: 200, body: accuant_result_2 } }
+ let(:bad_http_status) { { status: 441, body: '' } }
+ let(:acuant_base_url) { 'https://example.com' }
+ let(:image_data) { 'abc' }
+
+ describe '#create_document' do
+ let(:path) { '/AssureIDService/Document/Instance' }
+
+ it 'returns a good status with an instance id' do
+ stub_request(:post, acuant_base_url + path).to_return(status: 200, body: instance_id)
+
+ result = subject.create_document
+
+ expect(result).to eq([true, instance_id])
+ expect(subject.instance_id).to eq(instance_id)
+ end
+
+ it 'returns a bad status' do
+ stub_request(:post, acuant_base_url + path).to_return(bad_http_status)
+
+ result = subject.create_document
+
+ expect(result).to eq(bad_acuant_status)
+ end
+ end
+
+ describe '#post_front_image' do
+ let(:side) { Idv::Acuant::AssureId::FRONT }
+ let(:path) { "/AssureIDService/Document/#{subject.instance_id}/Image?side=#{side}&light=0" }
+
+ before do
+ subject.instance_id = instance_id
+ end
+
+ it 'returns a good status' do
+ stub_request(:post, acuant_base_url + path).to_return(good_http_status)
+
+ result = subject.post_front_image(image_data)
+
+ expect(result).to eq(good_acuant_status)
+ end
+
+ it 'returns a bad status' do
+ stub_request(:post, acuant_base_url + path).to_return(bad_http_status)
+
+ result = subject.post_front_image(image_data)
+
+ expect(result).to eq(bad_acuant_status)
+ end
+ end
+
+ describe '#post_back_image' do
+ let(:side) { Idv::Acuant::AssureId::BACK }
+ let(:path) { "/AssureIDService/Document/#{subject.instance_id}/Image?side=#{side}&light=0" }
+
+ before do
+ subject.instance_id = instance_id
+ end
+
+ it 'returns a good status' do
+ stub_request(:post, acuant_base_url + path).to_return(good_http_status)
+
+ result = subject.post_back_image(image_data)
+
+ expect(result).to eq(good_acuant_status)
+ end
+
+ it 'returns a bad status' do
+ stub_request(:post, acuant_base_url + path).to_return(bad_http_status)
+
+ result = subject.post_back_image(image_data)
+
+ expect(result).to eq(bad_acuant_status)
+ end
+ end
+
+ describe '#results' do
+ let(:path) { "/AssureIDService/Document/#{subject.instance_id}" }
+
+ before do
+ subject.instance_id = instance_id
+ end
+
+ it 'returns a good status' do
+ stub_request(:get, acuant_base_url + path).to_return(status: 200, body: '{}')
+
+ result = subject.results
+
+ expect(result).to eq([true, {}])
+ end
+
+ it 'returns a bad status' do
+ stub_request(:get, acuant_base_url + path).to_return(bad_http_status)
+
+ result = subject.results
+
+ expect(result).to eq(bad_acuant_status)
+ end
+
+ it 'returns failure alerts for accuant result=2' do
+ stub_request(:get, acuant_base_url + path).to_return(failure_alerts_status)
+
+ result = subject.results
+
+ expect(result).to eq([true, JSON.parse(accuant_result_2)])
+ end
+ end
+
+ describe '#face_image' do
+ let(:path) { "/AssureIDService/Document/#{subject.instance_id}/Field/Image?key=Photo" }
+
+ before do
+ subject.instance_id = instance_id
+ end
+
+ it 'returns a good status' do
+ stub_request(:get, acuant_base_url + path).to_return(good_http_status)
+
+ result = subject.face_image
+
+ expect(result).to eq(good_acuant_status)
+ end
+
+ it 'returns a bad status' do
+ stub_request(:get, acuant_base_url + path).to_return(bad_http_status)
+
+ result = subject.face_image
+
+ expect(result).to eq(bad_acuant_status)
+ end
+ end
+end
diff --git a/spec/services/idv/acuant/facial_match_spec.rb b/spec/services/idv/acuant/facial_match_spec.rb
new file mode 100644
index 00000000000..137edbfaef8
--- /dev/null
+++ b/spec/services/idv/acuant/facial_match_spec.rb
@@ -0,0 +1,28 @@
+require 'rails_helper'
+
+describe Idv::Acuant::FacialMatch do
+ let(:subject) { Idv::Acuant::FacialMatch.new }
+ let(:acuant_facial_match_url) { 'https://example.com' }
+
+ describe '#call' do
+ let(:path) { '/FacialMatch' }
+ let(:id_image) { '123' }
+ let(:image) { 'abc' }
+
+ it 'returns a good status' do
+ stub_request(:post, acuant_facial_match_url + path).to_return(status: 200, body: '{}')
+
+ result = subject.call(id_image, image)
+
+ expect(result).to eq([true, {}])
+ end
+
+ it 'returns a bad status' do
+ stub_request(:post, acuant_facial_match_url + path).to_return(status: 441, body: '')
+
+ result = subject.call(id_image, image)
+
+ expect(result).to eq([false, ''])
+ end
+ end
+end
diff --git a/spec/services/idv/agent_spec.rb b/spec/services/idv/agent_spec.rb
index 601caeeccc2..e4e15321ffe 100644
--- a/spec/services/idv/agent_spec.rb
+++ b/spec/services/idv/agent_spec.rb
@@ -70,6 +70,8 @@
proc { |_, r| r.add_message('reason 2') }
when :failed
proc { |_, r| r.add_message('bah humbug').add_error(:bad, 'stuff') }
+ when :timed_out
+ proc { |_, r| r.instance_variable_set(:@exception, Proofer::TimeoutError.new) }
end
Class.new(Proofer::Base) do
required_attributes(:foo)
@@ -86,7 +88,8 @@
errors: {},
messages: [resolution_message, state_id_message],
success: true,
- exception: nil
+ exception: nil,
+ timed_out: false
)
end
end
@@ -99,7 +102,19 @@
errors: { bad: ['stuff'] },
messages: [failed_message],
success: false,
- exception: nil
+ exception: nil,
+ timed_out: false
+ )
+ end
+ end
+
+ context 'when the first stage times out' do
+ let(:stages) { %i[timed_out state_id] }
+
+ it 'returns a result where timed out is true' do
+ expect(subject.to_h).to include(
+ success: false,
+ timed_out: true
)
end
end
diff --git a/spec/services/idv/flows/doc_auth_flow_spec.rb b/spec/services/idv/flows/doc_auth_flow_spec.rb
new file mode 100644
index 00000000000..527e1269807
--- /dev/null
+++ b/spec/services/idv/flows/doc_auth_flow_spec.rb
@@ -0,0 +1,54 @@
+require 'rails_helper'
+
+describe Idv::Flows::DocAuthFlow do
+ include DocAuthHelper
+
+ let(:user) { create(:user) }
+ let(:new_session) { { doc_auth: {} } }
+ let(:name) { :doc_auth }
+
+ describe '#next_step' do
+ it 'returns ssn as the first step' do
+ subject = Idv::Flows::DocAuthFlow.new(new_session, user, name)
+ result = subject.next_step
+
+ expect(result).to eq('ssn')
+ end
+
+ it 'returns front image after the ssn step' do
+ expect_next_step(:ssn, :front_image)
+ end
+
+ it 'returns back image after the front image step' do
+ expect_next_step(:front_image, :back_image)
+ end
+
+ it 'returns self_image after the doc success step' do
+ expect_next_step(:doc_success, :self_image)
+ end
+
+ it 'returns self_image after the doc success step' do
+ expect_next_step(:self_image, nil)
+ end
+ end
+
+ describe '#handle' do
+ it 'handles the next step and returns a form response object' do
+ subject = Idv::Flows::DocAuthFlow.new(new_session, user, name)
+ params = ActionController::Parameters.new(doc_auth: { ssn: '111111111' })
+ expect_any_instance_of(Idv::Steps::SsnStep).to receive(:call).exactly(:once)
+
+ result = subject.handle(:ssn, params)
+ expect(result.class).to eq(FormResponse)
+ expect(result.success?).to eq(true)
+ end
+ end
+
+ def expect_next_step(step, next_step)
+ session = session_from_completed_flow_steps(step)
+ subject = Idv::Flows::DocAuthFlow.new(session, user, name)
+ result = subject.next_step
+
+ expect(result.to_s).to eq(next_step.to_s)
+ end
+end
diff --git a/spec/services/idv/job_spec.rb b/spec/services/idv/job_spec.rb
deleted file mode 100644
index bfb37ac5690..00000000000
--- a/spec/services/idv/job_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Idv::Job do
- let(:idv_session) do
- Idv::Session.
- new(current_user: build(:user), issuer: nil, user_session: {}).
- tap { |session| session.params = applicant }
- end
-
- let(:applicant) { { first_name: 'Greatest', dob: '01/01/1985' } }
- let(:result_id) { 'abcdef' }
- let(:stages) { %i[resolution] }
-
- describe '#submit' do
- it 'generates a UUID and enqueues a Idv::ProoferJob and saves the UUID in the session' do
- expect(Idv::ProoferJob).to receive(:perform_later).
- with(
- result_id: result_id,
- applicant_json: idv_session.vendor_params.to_json,
- stages: stages.to_json
- )
-
- expect(idv_session.async_result_id).to eq(nil)
- expect(idv_session.async_result_started_at).to eq(nil)
-
- expect(SecureRandom).to receive(:uuid).and_return(result_id).once
-
- Idv::Job.submit(idv_session, stages)
-
- expect(idv_session.async_result_id).to eq(result_id)
- expect(idv_session.async_result_started_at).to be_within(1).of(Time.zone.now.to_i)
- end
- end
-end
diff --git a/spec/services/idv/phone_step_spec.rb b/spec/services/idv/phone_step_spec.rb
index 093ccc89418..4d3e49e6e93 100644
--- a/spec/services/idv/phone_step_spec.rb
+++ b/spec/services/idv/phone_step_spec.rb
@@ -9,87 +9,102 @@
idvs.applicant = { first_name: 'Some' }
idvs
end
- let(:idv_form_params) { { phone: '703-555-0000', phone_confirmed_at: nil } }
- let(:idv_phone_form) { Idv::PhoneForm.new(idv_session.params, user) }
-
- def build_step(vendor_validator_result)
- described_class.new(
- idv_session: idv_session,
- idv_form_params: idv_form_params,
- vendor_validator_result: vendor_validator_result
- )
- end
+ let(:good_phone) { '2255555000' }
+ let(:bad_phone) { '7035555555' }
+ let(:fail_phone) { '7035555999' }
+ let(:timeout_phone) { '7035555888' }
- describe '#submit' do
- let(:context) { 'some context' }
+ subject { described_class.new(idv_session: idv_session) }
- it 'returns true for mock-happy phone' do
- step = build_step(
- Idv::VendorResult.new(
- success: true,
- errors: {},
- context: context
- )
- )
+ describe '#submit' do
+ it 'succeeds with good params' do
+ context = { stages: [{ address: 'AddressMock' }] }
+ extra = { vendor: { messages: [], context: context, exception: nil, timed_out: false } }
- result = step.submit
+ result = subject.submit(phone: good_phone)
expect(result).to be_kind_of(FormResponse)
expect(result.success?).to eq(true)
expect(result.errors).to be_empty
+ expect(result.extra).to eq(extra)
expect(idv_session.vendor_phone_confirmation).to eq true
- expect(idv_session.params).to eq idv_phone_form.idv_params
- expect(result.extra).to include(
- vendor: {
- messages: [],
- context: context,
- exception: nil,
- }
- )
+ expect(idv_session.applicant).to eq(first_name: 'Some', phone: good_phone)
end
- it 'returns false for mock-sad phone' do
- idv_form_params[:phone] = '703-555-5555'
- errors = { phone: ['The phone number could not be verified.'] }
+ it 'fails with bad params' do
+ context = { stages: [{ address: 'AddressMock' }] }
+ extra = { vendor: { messages: [], context: context, exception: nil, timed_out: false } }
- step = build_step(
- Idv::VendorResult.new(
- success: false,
- errors: errors
- )
- )
-
- result = step.submit
+ result = subject.submit(phone: bad_phone)
expect(result).to be_kind_of(FormResponse)
expect(result.success?).to eq(false)
- expect(result.errors).to eq(errors)
- expect(idv_session.vendor_phone_confirmation).to eq false
+ expect(result.errors).to eq(phone: ['The phone number could not be verified.'])
+ expect(result.extra).to eq(extra)
+ expect(idv_session.vendor_phone_confirmation).to be_falsy
+ expect(idv_session.user_phone_confirmation).to be_falsy
+ expect(idv_session.applicant).to eq(first_name: 'Some')
+ end
+
+ it 'increments step attempts' do
+ original_step_attempts = idv_session.step_attempts[:phone]
+
+ subject.submit(phone: bad_phone)
+
+ expect(idv_session.step_attempts[:phone]).to eq(original_step_attempts + 1)
end
- it 'marks the phone number as confirmed by user if it matches 2FA phone' do
- idv_form_params[:phone_confirmed_at] = Time.zone.now
- step = build_step(
- Idv::VendorResult.new(
- success: true,
- errors: {}
- )
- )
- step.submit
+ it 'marks the phone as confirmed if it matches 2FA phone' do
+ user.phone_configurations = [build(:phone_configuration, user: user, phone: good_phone)]
+ result = subject.submit(phone: good_phone)
+ expect(result.success?).to eq(true)
+ expect(idv_session.vendor_phone_confirmation).to eq(true)
expect(idv_session.user_phone_confirmation).to eq(true)
end
- it 'does not mark the phone number as confirmed by user if it does not match 2FA phone' do
- step = build_step(
- Idv::VendorResult.new(
- success: true,
- errors: {}
- )
- )
- step.submit
+ it 'does not mark the phone as confirmed if it does not match 2FA phone' do
+ result = subject.submit(phone: good_phone)
+
+ expect(result.success?).to eq(true)
+ expect(idv_session.vendor_phone_confirmation).to eq(true)
+ expect(idv_session.user_phone_confirmation).to be_falsy
+ end
+ end
+
+ describe '#failure_reason' do
+ context 'when there are idv attempts remaining' do
+ it 'returns :warning' do
+ subject.submit(phone: bad_phone)
+
+ expect(subject.failure_reason).to eq(:warning)
+ end
+ end
+
+ context 'when there are not idv attempts remaining' do
+ it 'returns :fail' do
+ idv_session.step_attempts[:phone] = Idv::Attempter.idv_max_attempts - 1
+
+ subject.submit(phone: bad_phone)
+
+ expect(subject.failure_reason).to eq(:fail)
+ end
+ end
+
+ context 'when the vendor raises a timeout exception' do
+ it 'returns :timeout' do
+ subject.submit(phone: timeout_phone)
+
+ expect(subject.failure_reason).to eq(:timeout)
+ end
+ end
+
+ context 'when the vendor raises an exception' do
+ it 'returns :jobfail' do
+ subject.submit(phone: fail_phone)
- expect(idv_session.user_phone_confirmation).to eq(false)
+ expect(subject.failure_reason).to eq(:jobfail)
+ end
end
end
end
diff --git a/spec/services/idv/profile_step_spec.rb b/spec/services/idv/profile_step_spec.rb
index 714875ba80b..ef0af5b7491 100644
--- a/spec/services/idv/profile_step_spec.rb
+++ b/spec/services/idv/profile_step_spec.rb
@@ -3,7 +3,6 @@
describe Idv::ProfileStep do
let(:user) { create(:user) }
let(:idv_session) { Idv::Session.new(user_session: {}, current_user: user, issuer: nil) }
- let(:idv_profile_form) { Idv::ProfileForm.new(idv_session.params, user) }
let(:user_attrs) do
{
first_name: 'Some',
@@ -13,137 +12,93 @@
address1: '123 Main St',
address2: '',
city: 'Somewhere',
- state: 'KS',
+ state: 'VA',
zipcode: '66044',
+ state_id_number: '123abc',
+ state_id_type: 'drivers_license',
}
end
- def build_step(params, vendor_validator_result)
- idv_session.params.merge!(params)
- idv_session.applicant = idv_session.vendor_params
-
- described_class.new(
- idv_form_params: params,
- vendor_validator_result: vendor_validator_result,
- idv_session: idv_session
- )
- end
+ subject { described_class.new(idv_session: idv_session) }
describe '#submit' do
it 'succeeds with good params' do
- messages = ['Everything looks good']
+ context = { stages: [{ resolution: 'ResolutionMock' }, { state_id: 'StateIdMock' }] }
extra = {
idv_attempts_exceeded: false,
- vendor: { messages: messages, context: {}, exception: nil },
+ vendor: { messages: [], context: context, exception: nil, timed_out: false },
}
- step = build_step(
- user_attrs,
- Idv::VendorResult.new(
- success: true,
- errors: {},
- messages: messages,
- applicant: { first_name: 'Some' }
- )
- )
-
- result = step.submit
+ result = subject.submit(user_attrs)
expect(result).to be_kind_of(FormResponse)
expect(result.success?).to eq(true)
expect(result.errors).to be_empty
expect(result.extra).to eq(extra)
expect(idv_session.profile_confirmation).to eq true
+ expect(idv_session.resolution_successful).to eq true
+ expect(idv_session.applicant).to eq(user_attrs.merge(uuid: user.uuid))
end
- it 'fails with invalid SSN' do
- messages = ['The SSN was suspicious']
+ it 'fails with bad params' do
+ user_attrs[:ssn] = '666-66-6666'
+
+ context = { stages: [{ resolution: 'ResolutionMock' }] }
errors = { ssn: ['Unverified SSN.'] }
extra = {
idv_attempts_exceeded: false,
- vendor: { messages: messages, context: {}, exception: nil },
+ vendor: { messages: [], context: context, exception: nil, timed_out: false },
}
- step = build_step(
- user_attrs.merge(ssn: '666-66-6666'),
- Idv::VendorResult.new(success: false, errors: errors, messages: messages)
- )
-
- result = step.submit
+ result = subject.submit(user_attrs)
expect(result).to be_kind_of(FormResponse)
expect(result.success?).to eq(false)
expect(result.errors).to eq(errors)
expect(result.extra).to eq(extra)
expect(idv_session.profile_confirmation).to be_nil
+ expect(idv_session.resolution_successful).to be_nil
+ expect(idv_session.applicant).to be_nil
end
- it 'fails with invalid first name' do
- errors = { first_name: ['Unverified first name.'] }
- messages = ['The name was suspicious']
- extra = {
- idv_attempts_exceeded: false,
- vendor: { messages: messages, context: {}, exception: nil },
- }
-
- step = build_step(
- user_attrs.merge(first_name: 'Bad'),
- Idv::VendorResult.new(success: false, errors: errors, messages: messages)
- )
-
- result = step.submit
-
- expect(result).to be_kind_of(FormResponse)
- expect(result.success?).to eq(false)
- expect(result.errors).to eq(errors)
- expect(result.extra).to eq(extra)
- expect(idv_session.profile_confirmation).to be_nil
+ it 'increments attempts count' do
+ expect { subject.submit(user_attrs) }.to change(user, :idv_attempts).by(1)
end
+ end
- it 'fails with invalid ZIP code on current address' do
- messages = ['The ZIP code was suspicious']
- errors = { zipcode: ['Unverified ZIP code.'] }
- extra = {
- idv_attempts_exceeded: false,
- vendor: { messages: messages, context: {}, exception: nil },
- }
+ describe '#failure_reason' do
+ context 'when there are idv attempts remaining' do
+ it 'returns :warning' do
+ subject.submit(user_attrs.merge(first_name: 'Bad'))
- step = build_step(
- user_attrs.merge(zipcode: '00000'),
- Idv::VendorResult.new(success: false, errors: errors, messages: messages)
- )
+ expect(subject.failure_reason).to eq(:warning)
+ end
+ end
- result = step.submit
+ context 'when there are not idv attempts remaining' do
+ it 'returns :fail' do
+ user.update(idv_attempts: Idv::Attempter.idv_max_attempts - 1)
- expect(result).to be_kind_of(FormResponse)
- expect(result.success?).to eq(false)
- expect(result.errors).to eq(errors)
- expect(result.extra).to eq(extra)
- expect(idv_session.profile_confirmation).to be_nil
- end
+ subject.submit(user_attrs.merge(first_name: 'Bad'))
- it 'increments attempts count' do
- step = build_step(user_attrs, Idv::VendorResult.new(errors: {}))
- expect { step.submit }.to change(user, :idv_attempts).by(1)
+ expect(subject.failure_reason).to eq(:fail)
+ end
end
- it 'initializes the idv_session' do
- step = build_step(user_attrs, Idv::VendorResult.new(errors: {}))
- step.submit
+ context 'when the vendor raises a timeout exception' do
+ it 'returns :timeout' do
+ subject.submit(user_attrs.merge(first_name: 'Time'))
- expect(idv_session.params).to eq user_attrs
- expect(idv_session.applicant).to eq user_attrs.merge('uuid' => user.uuid)
+ expect(subject.failure_reason).to eq(:timeout)
+ end
end
- end
- describe '#attempts_exceeded?' do
- it 'calls Idv::Attempter#exceeded?' do
- attempter = instance_double(Idv::Attempter)
- allow(Idv::Attempter).to receive(:new).with(user).and_return(attempter)
- allow(attempter).to receive(:exceeded?)
+ context 'when the vendor raises an exception' do
+ it 'returns :jobfail' do
+ subject.submit(user_attrs.merge(first_name: 'Fail'))
- step = build_step(user_attrs, Idv::VendorResult.new(errors: {}))
- expect(step.attempts_exceeded?).to eq attempter.exceeded?
+ expect(subject.failure_reason).to eq(:jobfail)
+ end
end
end
end
diff --git a/spec/services/idv/send_phone_confirmation_otp_spec.rb b/spec/services/idv/send_phone_confirmation_otp_spec.rb
index 1acb84c319e..8e782e7db13 100644
--- a/spec/services/idv/send_phone_confirmation_otp_spec.rb
+++ b/spec/services/idv/send_phone_confirmation_otp_spec.rb
@@ -14,7 +14,7 @@
before do
# Setup Idv::Session
- idv_session.params[:phone] = phone
+ idv_session.applicant = { phone: phone }
idv_session.phone_confirmation_otp_delivery_method = otp_delivery_preference
# Mock Idv::GeneratePhoneConfirmationOtp
diff --git a/spec/services/idv/utils/images_to_tmp_files_spec.rb b/spec/services/idv/utils/images_to_tmp_files_spec.rb
new file mode 100644
index 00000000000..5442cbdfe52
--- /dev/null
+++ b/spec/services/idv/utils/images_to_tmp_files_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+
+describe Idv::Utils::ImagesToTmpFiles do
+ let(:images) { %w[abc def] }
+ let(:subject) { Idv::Utils::ImagesToTmpFiles.new(*images) }
+
+ describe '#call' do
+ it 'creates temporary files for the images' do
+ save_paths = []
+ subject.call do |tmp_fns|
+ save_paths << tmp_fns[0].path
+ save_paths << tmp_fns[1].path
+ expect(File.read(tmp_fns[0])).to eq('abc')
+ expect(File.read(tmp_fns[1])).to eq('def')
+ end
+
+ expect(File.exist?(save_paths[0])).to eq(false)
+ expect(File.exist?(save_paths[1])).to eq(false)
+ end
+ end
+end
diff --git a/spec/services/idv/utils/pii_from_doc_spec.rb b/spec/services/idv/utils/pii_from_doc_spec.rb
new file mode 100644
index 00000000000..48c07d0e901
--- /dev/null
+++ b/spec/services/idv/utils/pii_from_doc_spec.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe Idv::Utils::PiiFromDoc do
+ include DocAuthHelper
+
+ let(:subject) { Idv::Utils::PiiFromDoc }
+ let(:ssn) { '123' }
+ let(:phone) { '456' }
+
+ describe '#call' do
+ it 'correctly parses the pii data from acuant and returns a hash' do
+ results = subject.new(DocAuthHelper::ACUANT_RESULTS).call(ssn, phone)
+
+ expect(results).to eq(DocAuthHelper::ACUANT_RESULTS_TO_PII)
+ end
+ end
+end
diff --git a/spec/services/idv/vendor_result_spec.rb b/spec/services/idv/vendor_result_spec.rb
deleted file mode 100644
index d3cc893faf2..00000000000
--- a/spec/services/idv/vendor_result_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Idv::VendorResult do
- let(:success) { true }
- let(:errors) { { foo: ['is not valid'] } }
- let(:messages) { %w[foo bar baz] }
- let(:applicant) { { last_name: 'Ever', first_name: 'Greatest' } }
- let(:timed_out) { false }
-
- subject(:vendor_result) do
- Idv::VendorResult.new(
- success: success,
- errors: errors,
- messages: messages,
- applicant: applicant,
- timed_out: timed_out
- )
- end
-
- describe '#success?' do
- it 'is the success value' do
- expect(vendor_result.success?).to eq(success)
- end
- end
-
- describe '#timed_out?' do
- it 'is the timed_out value' do
- expect(vendor_result.timed_out?).to eq(timed_out)
- end
- end
-
- describe '#to_json' do
- it 'serializes applicant correctly' do
- json = vendor_result.to_json
-
- parsed = JSON.parse(json, symbolize_names: true)
- expect(parsed[:applicant][:last_name]).to eq(applicant[:last_name])
- end
- end
-
- describe '.new_from_json' do
- subject(:new_from_json) { Idv::VendorResult.new_from_json(vendor_result.to_json) }
-
- it 'has simple attributes' do
- expect(new_from_json.success?).to eq(vendor_result.success?)
- expect(new_from_json.errors).to eq(vendor_result.errors)
- expect(new_from_json.messages).to eq(vendor_result.messages)
- end
-
- it 'turns applicant into a full object' do
- expect(new_from_json.applicant[:last_name]).to eq(applicant[:last_name])
- end
-
- context 'without an applicant' do
- let(:applicant) { nil }
-
- it 'does not have an applicant' do
- expect(new_from_json.applicant).to eq(nil)
- end
- end
- end
-end
diff --git a/spec/services/otp_rate_limiter_spec.rb b/spec/services/otp_rate_limiter_spec.rb
index 51ce271c314..df7aa0980b3 100644
--- a/spec/services/otp_rate_limiter_spec.rb
+++ b/spec/services/otp_rate_limiter_spec.rb
@@ -2,12 +2,14 @@
RSpec.describe OtpRateLimiter do
let(:current_user) { build(:user, :with_phone) }
+ let(:phone) { MfaContext.new(current_user).phone_configurations.first.phone }
+
subject(:otp_rate_limiter) do
- OtpRateLimiter.new(phone: current_user.phone_configurations.first.phone, user: current_user)
+ OtpRateLimiter.new(phone: phone, user: current_user)
end
let(:phone_fingerprint) do
- Pii::Fingerprinter.fingerprint(current_user.phone_configurations.first.phone)
+ Pii::Fingerprinter.fingerprint(phone)
end
let(:rate_limited_phone) { OtpRequestsTracker.find_by(phone_fingerprint: phone_fingerprint) }
@@ -29,9 +31,7 @@
describe '#increment' do
it 'updates otp_last_sent_at' do
- tracker = OtpRequestsTracker.find_or_create_with_phone(
- current_user.phone_configurations.first.phone
- )
+ tracker = OtpRequestsTracker.find_or_create_with_phone(phone)
old_otp_last_sent_at = tracker.reload.otp_last_sent_at
otp_rate_limiter.increment
new_otp_last_sent_at = tracker.reload.otp_last_sent_at
diff --git a/spec/services/phone_verification_spec.rb b/spec/services/phone_verification_spec.rb
index 86db9f3fe67..b771ba20d05 100644
--- a/spec/services/phone_verification_spec.rb
+++ b/spec/services/phone_verification_spec.rb
@@ -2,44 +2,24 @@
describe PhoneVerification do
describe '#send_sms' do
- it 'makes a POST request to Twilio Verify endpoint' do
- PhoneVerification.adapter = FakeAdapter
-
- phone = '17873270143'
- headers = { 'X-Authy-API-Key' => 'secret' }
- locale = 'es'
- code = '123456'
- body = {
- code_length: 6,
- country_code: '1',
- custom_code: code,
- locale: locale,
- phone_number: '7873270143',
- via: 'sms',
- }
- connecttimeout = PhoneVerification::OPEN_TIMEOUT
- timeout = PhoneVerification::READ_TIMEOUT
+ let(:phone) { '17035551212' }
+ let(:code) { '123456' }
+ let(:verification) do
+ PhoneVerification.new(phone: phone, code: code).send_sms
+ end
- expect(FakeAdapter).to receive(:post).
- with(
- PhoneVerification::AUTHY_START_ENDPOINT,
- headers: headers,
- body: body,
- connecttimeout: connecttimeout,
- timeout: timeout
- ).and_return(FakeAdapter::SuccessResponse.new)
+ it 'does not raise an error when the response is successful' do
+ PhoneVerification.adapter = FakeAdapter
+ allow(FakeAdapter).to receive(:post).and_return(FakeAdapter::SuccessResponse.new)
- PhoneVerification.new(phone: phone, locale: locale, code: code).send_sms
+ expect { verification }.to_not raise_error
end
it 'raises VerifyError when response is not successful' do
PhoneVerification.adapter = FakeAdapter
- phone = '17035551212'
- code = '123456'
-
allow(FakeAdapter).to receive(:post).and_return(FakeAdapter::ErrorResponse.new)
- expect { PhoneVerification.new(phone: phone, code: code).send_sms }.to raise_error do |error|
+ expect { verification }.to raise_error do |error|
expect(error.code).to eq 60_033
expect(error.message).to eq 'Invalid number'
expect(error).to be_a(PhoneVerification::VerifyError)
@@ -48,12 +28,9 @@
it 'raises VerifyError when response body is not valid JSON' do
PhoneVerification.adapter = FakeAdapter
- phone = '17035551212'
- code = '123456'
-
allow(FakeAdapter).to receive(:post).and_return(FakeAdapter::EmptyResponse.new)
- expect { PhoneVerification.new(phone: phone, code: code).send_sms }.to raise_error do |error|
+ expect { verification }.to raise_error do |error|
expect(error.code).to eq 0
expect(error.message).to eq ''
expect(error.status).to eq 400
@@ -61,5 +38,54 @@
expect(error).to be_a(PhoneVerification::VerifyError)
end
end
+
+ it 'calls the Twilio/Authy Verify API with the right parameters' do
+ PhoneVerification.adapter = Faraday.new(url: PhoneVerification::AUTHY_HOST)
+
+ locale = 'fr'
+ body = "code_length=6&country_code=1&custom_code=#{code}&locale=#{locale}&" \
+ "phone_number=7035551212&via=sms"
+
+ stub_request(:post, 'https://api.authy.com/protected/json/phones/verification/start').
+ with(
+ body: body,
+ headers: {
+ 'Accept' => '*/*',
+ 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'User-Agent' => 'Faraday v0.15.2',
+ 'X-Authy-Api-Key' => Figaro.env.twilio_verify_api_key,
+ }
+ ).
+ to_return(status: 200, body: '', headers: {})
+
+ PhoneVerification.new(phone: phone, code: code, locale: locale).send_sms
+ end
+
+ it 'rescues timeout errors, retries, then raises a custom Twilio error' do
+ PhoneVerification.adapter = FakeAdapter
+ expect(FakeAdapter).to receive(:post).twice.and_raise(Faraday::TimeoutError)
+
+ expect { verification }.to raise_error do |error|
+ expect(error.code).to eq 4_815_162_342
+ expect(error.message).to eq 'Twilio Verify: Faraday::TimeoutError'
+ expect(error.status).to eq 0
+ expect(error.response).to eq ''
+ expect(error).to be_a(PhoneVerification::VerifyError)
+ end
+ end
+
+ it 'rescues failed connection errors, retries, then raises a custom Twilio error' do
+ PhoneVerification.adapter = FakeAdapter
+ expect(FakeAdapter).to receive(:post).twice.and_raise(Faraday::ConnectionFailed.new('error'))
+
+ expect { verification }.to raise_error do |error|
+ expect(error.code).to eq 4_815_162_342
+ expect(error.message).to eq 'Twilio Verify: Faraday::ConnectionFailed'
+ expect(error.status).to eq 0
+ expect(error.response).to eq ''
+ expect(error).to be_a(PhoneVerification::VerifyError)
+ end
+ end
end
end
diff --git a/spec/services/piv_cac_service_spec.rb b/spec/services/piv_cac_service_spec.rb
index 5222cb9afc4..f94795b43ad 100644
--- a/spec/services/piv_cac_service_spec.rb
+++ b/spec/services/piv_cac_service_spec.rb
@@ -27,7 +27,7 @@
describe '#decode_token' do
context 'when configured for local development' do
before(:each) do
- allow(FeatureManagement).to receive(:development_and_piv_cac_entry_enabled?) { true }
+ allow(FeatureManagement).to receive(:development_and_identity_pki_disabled?) { true }
end
it 'raises an error if no token provided' do
@@ -58,7 +58,7 @@
context 'when communicating with piv/cac service' do
context 'when in non-development mode' do
before(:each) do
- allow(FeatureManagement).to receive(:development_and_piv_cac_entry_enabled?) { false }
+ allow(FeatureManagement).to receive(:development_and_identity_pki_disabled?) { false }
end
it 'raises an error if no token provided' do
@@ -69,7 +69,6 @@
describe 'when configured with a user-facing endpoint' do
before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'true' }
allow(Figaro.env).to receive(:identity_pki_disabled) { 'false' }
allow(Figaro.env).to receive(:piv_cac_service_url) { base_url }
end
@@ -85,7 +84,7 @@
context 'when in development mode' do
before(:each) do
- allow(FeatureManagement).to receive(:development_and_piv_cac_entry_enabled?) { true }
+ allow(FeatureManagement).to receive(:development_and_identity_pki_disabled?) { true }
end
let(:nonce) { 'once' }
@@ -97,7 +96,6 @@
describe 'when configured to contact remote service' do
before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'true' }
allow(Figaro.env).to receive(:identity_pki_disabled) { 'false' }
allow(Figaro.env).to receive(:piv_cac_verify_token_url) { 'http://localhost:8443/' }
end
@@ -138,7 +136,6 @@
describe 'with bad json' do
before(:each) do
- allow(Figaro.env).to receive(:piv_cac_enabled) { 'true' }
allow(Figaro.env).to receive(:identity_pki_disabled) { 'false' }
allow(Figaro.env).to receive(:piv_cac_verify_token_url) { 'http://localhost:8443/' }
end
@@ -198,7 +195,6 @@
context 'with the agency not configured to be available' do
before(:each) do
- allow(FeatureManagement).to receive(:piv_cac_enabled?).and_return(true)
allow(Figaro.env).to receive(:piv_cac_agencies).and_return('["bar"]')
end
@@ -207,7 +203,6 @@
context 'with the agency configured to be available' do
before(:each) do
- allow(FeatureManagement).to receive(:piv_cac_enabled?).and_return(true)
allow(Figaro.env).to receive(:piv_cac_agencies).and_return('["bar","foo"]')
end
@@ -220,7 +215,6 @@
context 'with the agency not configured to be available' do
before(:each) do
- allow(FeatureManagement).to receive(:piv_cac_enabled?).and_return(true)
allow(Figaro.env).to receive(:piv_cac_agencies_scoped_by_email).and_return('["bar"]')
end
@@ -229,7 +223,6 @@
context 'with the agency configured to be available' do
before(:each) do
- allow(FeatureManagement).to receive(:piv_cac_enabled?).and_return(true)
allow(Figaro.env).to receive(:piv_cac_agencies_scoped_by_email).and_return('["bar","foo"]')
end
diff --git a/spec/services/populate_email_addresses_table_spec.rb b/spec/services/populate_email_addresses_table_spec.rb
new file mode 100644
index 00000000000..10fe5e0abe5
--- /dev/null
+++ b/spec/services/populate_email_addresses_table_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+describe PopulateEmailAddressesTable do
+ let(:subject) { described_class.new }
+
+ describe '#call' do
+ context 'a user with no email' do
+ let!(:user) { create(:user, email: '', confirmed_at: nil) }
+
+ it 'migrates nothing' do
+ expect(user.email_address).to be_nil
+
+ expect { subject.call }.to change { EmailAddress.count }.by(0)
+ end
+ end
+
+ context 'a user with an email' do
+ let!(:user) { create(:user) }
+
+ context 'and no email_address entry' do
+ before(:each) do
+ user.email_address.delete
+ user.reload
+ end
+
+ it 'migrates without decrypting and re-encrypting' do
+ expect(EncryptedAttribute).to_not receive(:new)
+ subject.call
+ end
+
+ it 'migrates the email' do
+ expect { subject.call }.to change { EmailAddress.count }.by(1)
+
+ address = user.reload.email_address
+ expect(user.email).to eq address.email
+ expect(user.confirmed_at).to eq user.confirmed_at
+ expect(user.confirmation_sent_at).to eq user.confirmation_sent_at
+ expect(user.confirmation_token).to eq user.confirmation_token
+ end
+ end
+
+ context 'and an existing email_address entry' do
+ it 'adds no new rows' do
+ expect { subject.call }.to change { EmailAddress.count }.by(0)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/twilio_service_spec.rb b/spec/services/twilio_service_spec.rb
index d8cc7409821..8a7a0c5976a 100644
--- a/spec/services/twilio_service_spec.rb
+++ b/spec/services/twilio_service_spec.rb
@@ -101,7 +101,7 @@
to raise_error(Twilio::REST::RestError, sanitized_message)
end
- it 'rescues timeout errors and raises a custom Twilio error' do
+ it 'rescues timeout errors, retries, then raises a custom Twilio error' do
TwilioService::Utils.telephony_service = FakeVoiceCall
error_code = 4_815_162_342
status_code = 4_815_162_342
@@ -109,12 +109,27 @@
message = "[HTTP #{status_code}] #{error_code} : timeout\n\n"
service = TwilioService::Utils.new
- expect(service.send(:client).calls).to receive(:create).
+ expect(service.send(:client).calls).to receive(:create).twice.
and_raise(Faraday::TimeoutError)
expect { service.place_call(to: '+123456789012', url: 'https://twimlet.com') }.
to raise_error(Twilio::REST::RestError, message)
end
+
+ it 'rescues failed connection errors, retries, then raises a custom Twilio error' do
+ TwilioService::Utils.telephony_service = FakeVoiceCall
+ error_code = 4_815_162_342
+ status_code = 4_815_162_342
+
+ message = "[HTTP #{status_code}] #{error_code} : timeout\n\n"
+ service = TwilioService::Utils.new
+
+ expect(service.send(:client).calls).to receive(:create).twice.
+ and_raise(Faraday::ConnectionFailed.new('error'))
+
+ expect { service.place_call(to: '+123456789012', url: 'https://twimlet.com') }.
+ to raise_error(Twilio::REST::RestError, message)
+ end
end
describe '#send_sms' do
@@ -158,7 +173,7 @@
to raise_error(Twilio::REST::RestError, sanitized_message)
end
- it 'rescues timeout errors and raises a custom Twilio error' do
+ it 'rescues timeout errors, retries, then raises a custom Twilio error' do
TwilioService::Utils.telephony_service = FakeSms
error_code = 4_815_162_342
status_code = 4_815_162_342
@@ -166,11 +181,36 @@
message = "[HTTP #{status_code}] #{error_code} : timeout\n\n"
service = TwilioService::Utils.new
- expect(service.send(:client).messages).to receive(:create).
+ request_data = {
+ event: 'Twilio Request Timeout',
+ url: 'foo',
+ method: 'get',
+ params: {},
+ headers: {},
+ }.to_json
+
+ expect(Rails.logger).to receive(:info).with(request_data)
+
+ expect(service.send(:client).messages).to receive(:create).twice.
and_raise(Faraday::TimeoutError)
expect { service.send_sms(to: '+123456789012', body: 'test') }.
to raise_error(Twilio::REST::RestError, message)
end
+
+ it 'rescues failed connection errors, retries, then raises a custom Twilio error' do
+ TwilioService::Utils.telephony_service = FakeSms
+ error_code = 4_815_162_342
+ status_code = 4_815_162_342
+
+ message = "[HTTP #{status_code}] #{error_code} : timeout\n\n"
+ service = TwilioService::Utils.new
+
+ expect(service.send(:client).messages).to receive(:create).twice.
+ and_raise(Faraday::ConnectionFailed.new('error'))
+
+ expect { service.send_sms(to: '+123456789012', body: 'test') }.
+ to raise_error(Twilio::REST::RestError, message)
+ end
end
end
diff --git a/spec/services/vendor_validator_result_storage_spec.rb b/spec/services/vendor_validator_result_storage_spec.rb
deleted file mode 100644
index 009075ac889..00000000000
--- a/spec/services/vendor_validator_result_storage_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe VendorValidatorResultStorage do
- subject(:service) { VendorValidatorResultStorage.new }
-
- let(:result_id) { SecureRandom.uuid }
- let(:original_result) do
- Idv::VendorResult.new(
- success: false,
- applicant: { first_name: 'First' }
- )
- end
-
- describe '#store_result' do
- it 'stores the result in redis with a TTL' do
- key = service.redis_key(result_id)
-
- before_redis = Sidekiq.redis { |redis| redis.get(key) }
- expect(before_redis).to be_nil
-
- service.store(result_id: result_id, result: original_result)
-
- Sidekiq.redis do |redis|
- expect(redis.get(key)).to be_present
- expect(redis.ttl(key)).to be_within(1).of(VendorValidatorResultStorage::TTL)
- end
- end
- end
-
- describe '#vendor_validator_result' do
- before { service.store(result_id: result_id, result: original_result) }
-
- it 'retrieves a stored result' do
- result = service.load(result_id)
-
- expect(result.success?).to eq(original_result.success?)
- expect(result.errors).to eq(original_result.errors)
- expect(result.messages).to eq(original_result.messages)
- expect(result.applicant.as_json).
- to eq(original_result.applicant.as_json)
- end
-
- it 'is nil with a bad result id' do
- result = service.load(SecureRandom.uuid)
-
- expect(result).to be_nil
- end
- end
-end
diff --git a/spec/support/account_reset_helper.rb b/spec/support/account_reset_helper.rb
index 9176d3078c1..1e4e1f86319 100644
--- a/spec/support/account_reset_helper.rb
+++ b/spec/support/account_reset_helper.rb
@@ -9,4 +9,8 @@ def cancel_request_for(user)
account_reset_request = AccountResetRequest.find_by(user_id: user.id)
account_reset_request.update(cancelled_at: Time.zone.now)
end
+
+ def grant_request(user)
+ AccountReset::GrantRequest.new(user).call
+ end
end
diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb
index 88544ad25b7..79ad53a4873 100644
--- a/spec/support/controller_helper.rb
+++ b/spec/support/controller_helper.rb
@@ -36,7 +36,7 @@ def stub_verify_steps_one_and_two(user)
user_session = {}
stub_sign_in(user)
idv_session = Idv::Session.new(user_session: user_session, current_user: user, issuer: nil)
- idv_session.applicant = { first_name: 'Some', last_name: 'One' }
+ idv_session.applicant = { first_name: 'Some', last_name: 'One' }.with_indifferent_access
allow(subject).to receive(:confirm_idv_session_started).and_return(true)
allow(subject).to receive(:confirm_idv_attempts_allowed).and_return(true)
allow(subject).to receive(:idv_session).and_return(idv_session)
diff --git a/spec/support/fake_adapter.rb b/spec/support/fake_adapter.rb
index 628551296c9..1b831cff66f 100644
--- a/spec/support/fake_adapter.rb
+++ b/spec/support/fake_adapter.rb
@@ -14,14 +14,14 @@ def success?
false
end
- def response_body
+ def body
{
error_code: '60033',
message: 'Invalid number',
}.to_json
end
- def response_code
+ def status
400
end
end
@@ -31,11 +31,11 @@ def success?
false
end
- def response_body
+ def body
''
end
- def response_code
+ def status
400
end
end
diff --git a/spec/support/fake_sms.rb b/spec/support/fake_sms.rb
index f884cf2e64d..93c6a6568eb 100644
--- a/spec/support/fake_sms.rb
+++ b/spec/support/fake_sms.rb
@@ -1,6 +1,7 @@
class FakeSms
Message = Struct.new(:to, :body, :messaging_service_sid)
- HttpClient = Struct.new(:adapter)
+ HttpClient = Struct.new(:adapter, :last_request)
+ LastRequest = Struct.new(:url, :params, :headers, :method)
cattr_accessor :messages
self.messages = []
@@ -20,6 +21,6 @@ def create(opts = {})
end
def http_client
- HttpClient.new(adapter: 'foo')
+ HttpClient.new('foo', LastRequest.new('foo', {}, {}, 'get'))
end
end
diff --git a/spec/support/fake_voice_call.rb b/spec/support/fake_voice_call.rb
index f5afeb89093..d069486b091 100644
--- a/spec/support/fake_voice_call.rb
+++ b/spec/support/fake_voice_call.rb
@@ -1,5 +1,6 @@
class FakeVoiceCall
- HttpClient = Struct.new(:adapter)
+ HttpClient = Struct.new(:adapter, :last_request)
+ LastRequest = Struct.new(:url, :params, :headers, :method)
cattr_accessor :calls
self.calls = []
@@ -15,6 +16,6 @@ def create(opts = {})
end
def http_client
- HttpClient.new(adapter: 'foo')
+ HttpClient.new('foo', LastRequest.new('foo', {}, {}, 'get'))
end
end
diff --git a/spec/support/features/doc_auth_helper.rb b/spec/support/features/doc_auth_helper.rb
new file mode 100644
index 00000000000..b555c541e88
--- /dev/null
+++ b/spec/support/features/doc_auth_helper.rb
@@ -0,0 +1,142 @@
+module DocAuthHelper
+ ACUANT_RESULTS = {
+ 'Result' => 1,
+ 'Fields' => [
+ { 'Name' => 'First Name', 'Value' => 'Jane' },
+ { 'Name' => 'Middle Name', 'Value' => 'Ann' },
+ { 'Name' => 'Surname', 'Value' => 'Doe' },
+ { 'Name' => 'Address Line 1', 'Value' => '1 Street' },
+ { 'Name' => 'Address City', 'Value' => 'New York' },
+ { 'Name' => 'Address State', 'Value' => 'NY' },
+ { 'Name' => 'Address Postal Code', 'Value' => '11364' },
+ { 'Name' => 'Birth Date', 'Value' => '/Date(' +
+ (Date.strptime('10-05-1938', '%m-%d-%Y').strftime('%Q').to_i + 43_200_000).to_s + ')/' },
+ ],
+ }.freeze
+
+ ACUANT_RESULTS_TO_PII =
+ {
+ first_name: 'Jane',
+ middle_name: 'Ann',
+ last_name: 'Doe',
+ address1: '1 Street',
+ city: 'New York',
+ state: 'NY',
+ zipcode: '11364',
+ dob: '10/05/1938',
+ ssn: '123',
+ phone: '456',
+ }.freeze
+
+ def session_from_completed_flow_steps(finished_step)
+ session = { doc_auth: {} }
+ Idv::Flows::DocAuthFlow::STEPS.each do |step, klass|
+ session[:doc_auth][klass.to_s] = true
+ return session if step == finished_step
+ end
+ session
+ end
+
+ def fill_out_ssn_form_ok
+ fill_in 'doc_auth_ssn', with: '666-66-1234'
+ end
+
+ def fill_out_ssn_form_fail
+ fill_in 'doc_auth_ssn', with: ''
+ end
+
+ def idv_doc_auth_ssn_step
+ idv_doc_auth_step_path(step: :ssn)
+ end
+
+ def idv_doc_auth_front_image_step
+ idv_doc_auth_step_path(step: :front_image)
+ end
+
+ def idv_doc_auth_back_image_step
+ idv_doc_auth_step_path(step: :back_image)
+ end
+
+ def idv_doc_auth_doc_success_step
+ idv_doc_auth_step_path(step: :doc_success)
+ end
+
+ def idv_doc_auth_doc_failed_step
+ idv_doc_auth_step_path(step: :doc_failed)
+ end
+
+ def idv_doc_auth_self_image_step
+ idv_doc_auth_step_path(step: :self_image)
+ end
+
+ def complete_doc_auth_steps_before_ssn_step(user = user_with_2fa)
+ sign_in_and_2fa_user(user)
+ visit idv_doc_auth_ssn_step unless current_path == idv_doc_auth_ssn_step
+ end
+
+ def complete_doc_auth_steps_before_front_image_step(user = user_with_2fa)
+ complete_doc_auth_steps_before_ssn_step(user)
+ fill_out_ssn_form_ok
+ click_idv_continue
+ end
+
+ def complete_doc_auth_steps_before_back_image_step(user = user_with_2fa)
+ complete_doc_auth_steps_before_front_image_step(user)
+ mock_assure_id_ok
+ attach_image
+ click_idv_continue
+ end
+
+ def complete_doc_auth_steps_before_doc_success_step(user = user_with_2fa)
+ complete_doc_auth_steps_before_back_image_step(user)
+ attach_image
+ click_idv_continue
+ end
+
+ def complete_doc_auth_steps_before_doc_failed_step(user = user_with_2fa)
+ complete_doc_auth_steps_before_back_image_step(user)
+ attach_image
+ allow_any_instance_of(Idv::Agent).to receive(:proof).
+ and_return(success: false, errors: {})
+ click_idv_continue
+ end
+
+ def complete_doc_auth_steps_before_self_image_step(user = user_with_2fa)
+ complete_doc_auth_steps_before_doc_success_step(user)
+ click_idv_continue
+ end
+
+ def mock_assure_id_ok
+ allow_any_instance_of(Idv::Acuant::AssureId).to receive(:create_document).
+ and_return([true, '123'])
+ allow_any_instance_of(Idv::Acuant::AssureId).to receive(:post_front_image).
+ and_return([true, ''])
+ allow_any_instance_of(Idv::Acuant::AssureId).to receive(:post_back_image).
+ and_return([true, ''])
+ allow_any_instance_of(Idv::Acuant::AssureId).to receive(:results).
+ and_return([true, ACUANT_RESULTS])
+ allow_any_instance_of(Idv::Acuant::AssureId).to receive(:face_image).and_return([true, ''])
+ allow_any_instance_of(Idv::Acuant::FacialMatch).to receive(:call).
+ and_return([true, { 'FacialMatch' => 1 }])
+ end
+
+ def mock_assure_id_fail
+ allow_any_instance_of(Idv::Acuant::AssureId).to receive(:create_document).
+ and_return([false, ''])
+ end
+
+ def enable_doc_auth
+ allow(FeatureManagement).to receive(:doc_auth_enabled?).and_return(true)
+ end
+
+ def attach_image
+ attach_file 'doc_auth_image', 'app/assets/images/logo.png'
+ end
+
+ def assure_id_results_with_result_2
+ result = DocAuthHelper::ACUANT_RESULTS.dup
+ result['Result'] = 2
+ result['Alerts'] = [{ 'Actions': 'Check the document' }]
+ result
+ end
+end
diff --git a/spec/support/features/idv_helper.rb b/spec/support/features/idv_helper.rb
index 33980872edb..0b77bd47e95 100644
--- a/spec/support/features/idv_helper.rb
+++ b/spec/support/features/idv_helper.rb
@@ -65,15 +65,17 @@ def click_idv_continue
end
def choose_idv_otp_delivery_method_sms
- using_wait_time(5) do
- click_on t('idv.buttons.send_confirmation_code')
- end
+ page.find(
+ 'label',
+ text: t('two_factor_authentication.otp_delivery_preference.sms')
+ ).click
+ click_on t('idv.buttons.send_confirmation_code')
end
def choose_idv_otp_delivery_method_voice
page.find(
'label',
- text: t('devise.two_factor_authentication.otp_delivery_preference.voice')
+ text: t('two_factor_authentication.otp_delivery_preference.voice')
).click
click_on t('idv.buttons.send_confirmation_code')
end
diff --git a/spec/support/features/idv_step_helper.rb b/spec/support/features/idv_step_helper.rb
index 879fdd3b932..8286027e2ad 100644
--- a/spec/support/features/idv_step_helper.rb
+++ b/spec/support/features/idv_step_helper.rb
@@ -63,7 +63,7 @@ def complete_idv_steps_before_phone_otp_verification_step(user = user_with_2fa)
def complete_idv_steps_with_phone_before_review_step(user = user_with_2fa)
complete_idv_steps_before_phone_step(user)
- fill_out_phone_form_ok(user.phone_configurations.first.phone)
+ fill_out_phone_form_ok(MfaContext.new(user).phone_configurations.first.phone)
click_idv_continue
end
diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb
index 5d8f458b1a2..0873b43dfba 100644
--- a/spec/support/features/session_helper.rb
+++ b/spec/support/features/session_helper.rb
@@ -81,7 +81,7 @@ def sign_in_before_2fa(user = create(:user))
allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true)
login_as(user, scope: :user, run_callbacks: false)
- if user.phone_configurations.any?
+ if TwoFactorAuthentication::PhonePolicy.new(user).enabled?
Warden.on_next_request do |proxy|
session = proxy.env['rack.session']
session['warden.user.user.session'] = {}
@@ -144,8 +144,7 @@ def sign_in_live_with_2fa(user = user_with_2fa)
def sign_in_live_with_piv_cac(user = user_with_piv_cac)
sign_in_user(user)
- allow(FeatureManagement).to receive(:piv_cac_enabled?).and_return(true)
- allow(FeatureManagement).to receive(:development_and_piv_cac_entry_enabled?).and_return(true)
+ allow(FeatureManagement).to receive(:development_and_identity_pki_disabled?).and_return(true)
visit login_two_factor_piv_cac_path
stub_piv_cac_service
visit_piv_cac_service(
@@ -417,12 +416,11 @@ def set_up_2fa_with_authenticator_app
def register_user_with_piv_cac(email = 'test@test.com')
allow(PivCacService).to receive(:piv_cac_available_for_agency?).and_return(true)
- allow(FeatureManagement).to receive(:piv_cac_enabled?).and_return(true)
confirm_email_and_password(email)
expect(page).to have_current_path two_factor_options_path
expect(page).to have_content(
- t('devise.two_factor_authentication.two_factor_choice_options.piv_cac')
+ t('two_factor_authentication.login_options.piv_cac')
)
set_up_2fa_with_piv_cac
@@ -458,7 +456,6 @@ def stub_twilio_service
def stub_piv_cac_service
allow(Figaro.env).to receive(:identity_pki_disabled).and_return('false')
- allow(Figaro.env).to receive(:piv_cac_enabled).and_return('true')
allow(Figaro.env).to receive(:piv_cac_service_url).and_return('http://piv.example.com/')
allow(Figaro.env).to receive(:piv_cac_verify_token_url).and_return('http://piv.example.com/')
stub_request(:post, 'piv.example.com').to_return do |request|
diff --git a/spec/support/features/webauth_verification_helper.rb b/spec/support/features/webauth_verification_helper.rb
new file mode 100644
index 00000000000..83ff633985d
--- /dev/null
+++ b/spec/support/features/webauth_verification_helper.rb
@@ -0,0 +1,44 @@
+module WebauthnVerificationHelper
+ def protocol
+ 'http://'
+ end
+
+ def challenge
+ [152, 207, 129, 117, 183, 199, 18, 19, 51, 104, 207, 109, 12, 50, 143, 155]
+ end
+
+ def credential_ids
+ '60Aa7rKEJJEkqDM0flq4NoNu3L/ZpZfamNbScSG+I9AZnV3efKCyRNXK78lRxuqmxmfa87fwrrS1+5PJvJdG0A=='
+ end
+
+ def credential_id
+ '60Aa7rKEJJEkqDM0flq4NoNu3L/ZpZfamNbScSG+I9AZnV3efKCyRNXK78lRxuqmxmfa87fwrrS1+5PJvJdG0A=='
+ end
+
+ def authenticator_data
+ 'SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MBAAAAJg=='
+ end
+
+ def signature
+ 'MEUCIQDlEB4VUN/X15N/Jmgx4ACbOlLLHRRcKsBkejpdQj81vQIgIo97sxdpP/hZgQpIXJMa3cBnAzcnfw+1CJ2LP3VvOg\
+4='
+ end
+
+ def client_data_json
+ 'eyJjaGFsbGVuZ2UiOiJtTS1CZGJmSEVoTXphTTl0RERLUG13Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwI\
+iwidHlwZSI6IndlYmF1dGhuLmdldCJ9'
+ end
+
+ def public_key
+ 'BBWEWPFKW60xPIcf/U098QEsiB3wUmJm9TN+bU1d5Y9noAnfr412Wu3KOrX0uhy/t14t4aFuUfNu054zkKVhQDM='
+ end
+
+ def create_webauthn_configuration(user)
+ WebauthnConfiguration.create(
+ user_id: user.id,
+ credential_id: credential_id,
+ credential_public_key: public_key,
+ name: 'foo'
+ )
+ end
+end
diff --git a/spec/support/features/webauthn_helper.rb b/spec/support/features/webauthn_helper.rb
index e079499f2b4..8de7714f29d 100644
--- a/spec/support/features/webauthn_helper.rb
+++ b/spec/support/features/webauthn_helper.rb
@@ -1,4 +1,23 @@
module WebauthnHelper
+ def mock_challenge
+ allow(WebAuthn).to receive(:credential_creation_options).and_return(
+ challenge: challenge.pack('c*')
+ )
+ end
+
+ def mock_press_button_on_hardware_key_and_fill_in_name_field
+ # this is required because the domain is embedded in the supplied attestation object
+ allow(WebauthnSetupForm).to receive(:domain_name).and_return('localhost:3000')
+
+ set_hidden_field('attestation_object', attestation_object)
+ set_hidden_field('client_data_json', client_data_json)
+ fill_in 'name', with: 'mykey'
+ end
+
+ def set_hidden_field(id, value)
+ first("input##{id}", visible: false).set(value)
+ end
+
def protocol
'http://'
end
diff --git a/spec/support/idv_examples/cancel_at_idv_step.rb b/spec/support/idv_examples/cancel_at_idv_step.rb
index ad7c93a2856..8d032f4f901 100644
--- a/spec/support/idv_examples/cancel_at_idv_step.rb
+++ b/spec/support/idv_examples/cancel_at_idv_step.rb
@@ -19,20 +19,53 @@
expect(current_path).to eq(original_path)
end
- it 'shows the user a cancellation message with the option to cancel and reset idv' do
- click_link t('links.cancel')
+ context 'with an sp', if: sp do
+ it 'shows the user a cancellation message with the option to cancel and reset idv' do
+ failure_to_proof_url = 'https://www.example.com/failure'
+ sp_name = 'Test SP'
+ allow_any_instance_of(ServiceProviderSessionDecorator).to receive(:failure_to_proof_url).
+ and_return(failure_to_proof_url)
+ allow_any_instance_of(ServiceProviderSessionDecorator).to receive(:sp_name).
+ and_return(sp_name)
- expect(page).to have_content(t('idv.cancel.modal_header'))
- expect(current_path).to eq(idv_cancel_path)
+ click_link t('links.cancel')
- click_on t('forms.buttons.cancel')
+ expect(page).to have_content(t('idv.cancel.modal_header'))
+ expect(current_path).to eq(idv_cancel_path)
- expect(page).to have_content(t('headings.cancellations.confirmation'))
- expect(current_path).to eq(idv_cancel_path)
+ click_on t('forms.buttons.cancel')
+
+ expect(page).to have_content(t('headings.cancellations.confirmation'))
+ expect(current_path).to eq(idv_cancel_path)
+
+ expect(page).to have_link("‹ #{t('links.back_to_sp', sp: sp_name)}",
+ href: failure_to_proof_url)
+
+ # After visiting /verify, expect to redirect to the jurisdiction step,
+ # the first step in the IdV flow
+ visit idv_path
+ expect(current_path).to eq(idv_jurisdiction_path)
+ end
+ end
+
+ context 'without an sp' do
+ it 'shows a cancellation message with option to cancel and reset idv', if: sp.nil? do
+ click_link t('links.cancel')
+
+ expect(page).to have_content(t('idv.cancel.modal_header'))
+ expect(current_path).to eq(idv_cancel_path)
+
+ click_on t('forms.buttons.cancel')
+
+ expect(page).to have_content(t('headings.cancellations.confirmation'))
+ expect(current_path).to eq(idv_cancel_path)
+ expect(page).to have_link("‹ #{t('links.back_to_sp', sp: t('links.my_account'))}",
+ href: account_url)
- # After visiting /verify, expect to redirect to the jurisdiction step,
- # the first step in the IdV flow
- visit idv_path
- expect(current_path).to eq(idv_jurisdiction_path)
+ # After visiting /verify, expect to redirect to the jurisdiction step,
+ # the first step in the IdV flow
+ visit idv_path
+ expect(current_path).to eq(idv_jurisdiction_path)
+ end
end
end
diff --git a/spec/support/idv_examples/failed_idv_job.rb b/spec/support/idv_examples/failed_idv_job.rb
index 0e1699cec07..6bf0074dbfd 100644
--- a/spec/support/idv_examples/failed_idv_job.rb
+++ b/spec/support/idv_examples/failed_idv_job.rb
@@ -1,6 +1,5 @@
shared_examples 'failed idv job' do |step|
let(:locale) { LinkLocaleResolver.locale }
- let(:idv_job_class) { Idv::ProoferJob }
let(:step_locale_key) do
return :sessions if step == :profile
step
@@ -12,12 +11,10 @@
complete_idv_steps_before_step(step)
end
- context 'the job raises an error' do
+ context 'the proofer raises an error' do
before do
- stub_idv_job_to_raise_error_in_background(idv_job_class)
-
- fill_out_idv_form_ok if step == :profile
- fill_out_phone_form_ok if step == :phone
+ fill_out_idv_form_error if step == :profile
+ fill_out_phone_form_error if step == :phone
click_idv_continue
end
@@ -29,25 +26,14 @@
end
end
- context 'the job times out' do
+ context 'the proofer times out' do
before do
- stub_idv_job_to_timeout_in_background(idv_job_class)
-
- fill_out_idv_form_ok if step == :profile
- fill_out_phone_form_ok('5202691958') if step == :phone
+ fill_out_idv_form_timeout if step == :profile
+ fill_out_phone_form_timeout if step == :phone
click_idv_continue
-
- seconds_to_travel = (Figaro.env.async_job_refresh_max_wait_seconds.to_i + 1).seconds
- Timecop.travel seconds_to_travel
-
- visit current_path
- end
-
- after do
- Timecop.return
end
- it 'renders a timeout failure page' do
+ it 'renders a timeout failure screen' do
expect(page).to have_current_path(session_failure_path(:timeout)) if step == :profile
expect(page).to have_current_path(phone_failure_path(:timeout)) if step == :phone
expect(page).to have_content t("idv.failure.#{step_locale_key}.heading")
@@ -55,25 +41,22 @@
end
end
- # rubocop:disable Lint/HandleExceptions
- # rubocop:disable Style/RedundantBegin
- # Disabling Style/RedundantBegin because when i remove make the changes
- # to remove it, fasterer can no longer parse the code...
- def stub_idv_job_to_raise_error_in_background(idv_job_class)
- allow(Idv::Agent).to receive(:new).and_raise('this is a test error')
- allow(idv_job_class).to receive(:perform_now).and_wrap_original do |perform_now, *args|
- begin
- perform_now.call(*args)
- rescue StandardError
- # Swallow the error so it does not get re-raised by the job
- end
- end
+ def fill_out_idv_form_error
+ fill_out_idv_form_ok
+ fill_in 'profile_first_name', with: 'Fail'
+ end
+
+ def fill_out_phone_form_error
+ fill_in :idv_phone_form_phone, with: '7035555999'
+ end
+
+ def fill_out_idv_form_timeout
+ fill_out_idv_form_ok
+ fill_in 'profile_first_name', with: 'Time'
end
- # rubocop:enable Style/RedundantBegin
- # rubocop:enable Lint/HandleExceptions
- def stub_idv_job_to_timeout_in_background(idv_job_class)
- allow(idv_job_class).to receive(:perform_now)
+ def fill_out_phone_form_timeout
+ fill_in :idv_phone_form_phone, with: '7035555888'
end
def session_failure_path(reason)
diff --git a/spec/support/idv_examples/max_attempts.rb b/spec/support/idv_examples/max_attempts.rb
index 25fb4bdd203..094569e18c0 100644
--- a/spec/support/idv_examples/max_attempts.rb
+++ b/spec/support/idv_examples/max_attempts.rb
@@ -119,8 +119,8 @@ def expect_user_to_fail_at_phone_step
end
def advance_to_phone_step
- fill_out_idv_jurisdiction_ok
- click_idv_continue
+ # Currently on the session success path
+ # Click continue to advance to the phone step
click_idv_continue
end
end
diff --git a/spec/support/shared_examples/account_creation.rb b/spec/support/shared_examples/account_creation.rb
index dde4eaf5b47..ef357ffcccd 100644
--- a/spec/support/shared_examples/account_creation.rb
+++ b/spec/support/shared_examples/account_creation.rb
@@ -1,16 +1,3 @@
-shared_examples 'csrf error when asking for new personal key' do |sp|
- it 'redirects to sign in page', email: true do
- visit_idp_from_sp_with_loa1(sp)
- register_user
- allow_any_instance_of(Users::PersonalKeysController).
- to receive(:create).and_raise(ActionController::InvalidAuthenticityToken)
- click_on t('users.personal_key.get_another')
-
- expect(current_path).to eq new_user_session_path
- expect(page).to have_content t('errors.invalid_authenticity_token')
- end
-end
-
shared_examples 'csrf error when acknowledging personal key' do |sp|
it 'redirects to sign in page', email: true do
visit_idp_from_sp_with_loa1(sp)
@@ -134,3 +121,48 @@
end
end
end
+
+shared_examples 'creating an LOA3 account using webauthn for 2FA' do |sp|
+ it 'does not have webauthn as an option', email: true do
+ mock_challenge
+ visit_idp_from_sp_with_loa3(sp)
+ confirm_email_and_password('test@test.com')
+ expect(page).to_not have_css("label[for='two_factor_options_form_selection_webauthn']")
+ end
+
+ # Remove the above test and enable this one when we enable webauthn for the SPs
+ xit 'does not prompt for recovery code before IdV flow', email: true do
+ mock_challenge
+ visit_idp_from_sp_with_loa3(sp)
+ confirm_email_and_password('test@test.com')
+ select_2fa_option('webauthn')
+ mock_press_button_on_hardware_key_and_fill_in_name_field
+ click_submit_default
+ fill_out_idv_jurisdiction_ok
+ click_idv_continue
+ fill_out_idv_form_ok
+ click_idv_continue
+ click_idv_continue
+ fill_out_phone_form_ok
+ click_idv_continue
+ choose_idv_otp_delivery_method_sms
+ click_submit_default
+ fill_in 'Password', with: Features::SessionHelper::VALID_PASSWORD
+ click_continue
+ click_acknowledge_personal_key
+
+ if sp == :oidc
+ expect(page.response_headers['Content-Security-Policy']).
+ to(include('form-action \'self\' http://localhost:7654'))
+ end
+
+ click_on t('forms.buttons.continue')
+ expect(current_url).to eq @saml_authn_request if sp == :saml
+
+ if sp == :oidc
+ redirect_uri = URI(current_url)
+
+ expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/sign_in.rb b/spec/support/shared_examples/sign_in.rb
index 564464611c5..9e165e77f8b 100644
--- a/spec/support/shared_examples/sign_in.rb
+++ b/spec/support/shared_examples/sign_in.rb
@@ -51,7 +51,7 @@
end
shared_examples 'signing in as LOA3 with personal key' do |sp|
- it 'redirects to the SP after acknowledging new personal key', :email, :idv_job do
+ it 'redirects to the SP after acknowledging new personal key', :email do
user = create_loa3_account_go_back_to_sp_and_sign_out(sp)
pii = { ssn: '666-66-1234', dob: '1920-01-01', first_name: 'alice' }
@@ -105,7 +105,7 @@
end
shared_examples 'signing in as LOA3 with personal key after resetting password' do |sp|
- xit 'redirects to SP after reactivating account', :email, :idv_job do
+ xit 'redirects to SP after reactivating account', :email do
user = create_loa3_account_go_back_to_sp_and_sign_out(sp)
visit_idp_from_sp_with_loa3(sp)
trigger_reset_password_and_click_email_link(user.email)
@@ -175,7 +175,7 @@
stub_piv_cac_service
user = create(:user, :signed_up, :with_piv_or_cac)
- user.phone_configurations.clear
+ MfaContext.new(user).phone_configurations.clear
visit_idp_from_sp_with_loa1(sp)
click_link t('links.sign_in')
fill_in_credentials_and_submit(user.email, user.password)
diff --git a/spec/support/shared_examples_for_personal_keys.rb b/spec/support/shared_examples_for_personal_keys.rb
index f9e76283884..bba05290f02 100644
--- a/spec/support/shared_examples_for_personal_keys.rb
+++ b/spec/support/shared_examples_for_personal_keys.rb
@@ -1,33 +1,7 @@
shared_examples_for 'personal key page' do
include XPathHelper
- context 'regenerating personal key with `Get another code` button' do
- scenario 'displays a flash message and a new code' do
- old_digest = @user.reload.encrypted_recovery_code_digest
-
- click_button t('users.personal_key.get_another')
-
- expect(@user.reload.encrypted_recovery_code_digest).to_not eq old_digest
- expect(page).to have_content t('notices.send_code.personal_key')
- end
- end
-
context 'informational text' do
- let(:accordion_control_selector) { generate_class_selector('accordion-header-controls') }
- let(:content_selector) { generate_class_selector('accordion-content') }
-
- scenario 'it displays the personal key info header' do
- expect(page).to have_content(t('users.personal_key.help_text_header'))
- end
-
- context 'with javascript disabled' do
- scenario 'content is visible by default' do
- expect(page).to have_xpath("//#{accordion_control_selector}[@aria-expanded='true']")
- expect(page).to have_xpath("//#{content_selector}")
- expect(page).to have_content(t('users.personal_key.help_text'))
- end
- end
-
context 'modal content' do
it 'displays the modal title' do
expect(page).to have_content t('forms.personal_key.title')
diff --git a/spec/support/shared_examples_for_phone_validation.rb b/spec/support/shared_examples_for_phone_validation.rb
index 2d8e33503b8..580acae5492 100644
--- a/spec/support/shared_examples_for_phone_validation.rb
+++ b/spec/support/shared_examples_for_phone_validation.rb
@@ -16,10 +16,12 @@
second_user = build_stubbed(:user, :signed_up, with: { phone: '+1 (202) 555-1213' })
allow(User).to receive(:exists?).with(email: 'new@gmail.com').and_return(false)
allow(User).to receive(:exists?).with(
- phone_configuration: { phone: second_user.phone_configurations.first.phone }
+ phone_configuration: {
+ phone: MfaContext.new(second_user).phone_configurations.first.phone,
+ }
).and_return(true)
- params[:phone] = second_user.phone_configurations.first.phone
+ params[:phone] = MfaContext.new(second_user).phone_configurations.first.phone
result = subject.submit(params)
expect(result).to be_kind_of(FormResponse)
@@ -37,8 +39,8 @@
context 'when phone is same as current user' do
it 'is valid' do
- user.phone_configurations.first.phone = '+1 (703) 500-5000'
- params[:phone] = user.phone_configurations.first.phone
+ MfaContext.new(user).phone_configurations.first.phone = '+1 (703) 500-5000'
+ params[:phone] = MfaContext.new(user).phone_configurations.first.phone
result = subject.submit(params)
expect(result).to be_kind_of(FormResponse)
diff --git a/spec/support/sp_auth_helper.rb b/spec/support/sp_auth_helper.rb
index 3036c9b524e..c9599c32230 100644
--- a/spec/support/sp_auth_helper.rb
+++ b/spec/support/sp_auth_helper.rb
@@ -27,7 +27,7 @@ def create_loa3_account_go_back_to_sp_and_sign_out(sp)
fill_out_idv_form_ok
click_idv_continue
click_idv_continue
- fill_out_phone_form_ok(user.phone_configurations.detect(&:mfa_enabled?).phone)
+ fill_out_phone_form_ok(MfaContext.new(user).phone_configurations.detect(&:mfa_enabled?).phone)
click_idv_continue
fill_in :user_password, with: user.password
click_continue
diff --git a/spec/view_models/account_show_spec.rb b/spec/view_models/account_show_spec.rb
index cca373d355a..7ec51fd00d4 100644
--- a/spec/view_models/account_show_spec.rb
+++ b/spec/view_models/account_show_spec.rb
@@ -28,7 +28,7 @@
it 'returns the personal_key partial' do
user = User.new
profile_index = AccountShow.new(
- decrypted_pii: {}, personal_key: 'foo', decorated_user: user
+ decrypted_pii: {}, personal_key: 'foo', decorated_user: user.decorate
)
expect(profile_index.personal_key_partial).to eq 'accounts/personal_key'
@@ -39,7 +39,7 @@
it 'returns the shared/null partial' do
user = User.new
profile_index = AccountShow.new(
- decrypted_pii: {}, personal_key: '', decorated_user: user
+ decrypted_pii: {}, personal_key: '', decorated_user: user.decorate
)
expect(profile_index.personal_key_partial).to eq 'shared/null'
@@ -50,7 +50,7 @@
describe '#password_reset_partial' do
context 'user has a password_reset_profile' do
it 'returns the accounts/password_reset partial' do
- user = User.new
+ user = User.new.decorate
allow(user).to receive(:password_reset_profile).and_return('profile')
profile_index = AccountShow.new(
decrypted_pii: {}, personal_key: 'foo', decorated_user: user
@@ -65,7 +65,7 @@
user = User.new
allow(user).to receive(:password_reset_profile).and_return(nil)
profile_index = AccountShow.new(
- decrypted_pii: {}, personal_key: '', decorated_user: user
+ decrypted_pii: {}, personal_key: '', decorated_user: user.decorate
)
expect(profile_index.password_reset_partial).to eq 'shared/null'
@@ -76,7 +76,7 @@
describe '#pending_profile_partial' do
context 'user needs profile usps verification' do
it 'returns the accounts/pending_profile_usps partial' do
- user = User.new
+ user = User.new.decorate
allow(user).to receive(:pending_profile_requires_verification?).and_return(true)
profile_index = AccountShow.new(
decrypted_pii: {}, personal_key: 'foo', decorated_user: user
@@ -88,7 +88,7 @@
context 'user does not need profile verification' do
it 'returns the shared/null partial' do
- user = User.new
+ user = User.new.decorate
allow(user).to receive(:pending_profile_requires_verification?).and_return(false)
profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user)
@@ -100,7 +100,7 @@
describe '#pii_partial' do
context 'AccountShow instance has decrypted_pii' do
it 'returns the accounts/password_reset partial' do
- user = User.new
+ user = User.new.decorate
profile_index = AccountShow.new(
decrypted_pii: { foo: 'bar' }, personal_key: '', decorated_user: user
)
@@ -111,7 +111,7 @@
context 'AccountShow instance does not have decrypted_pii' do
it 'returns the shared/null partial' do
- user = User.new
+ user = User.new.decorate
profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user)
expect(profile_index.pii_partial).to eq 'shared/null'
@@ -123,8 +123,13 @@
context 'user has enabled an authenticator app' do
it 'returns the disable_totp partial' do
user = User.new
- allow(user).to receive(:totp_enabled?).and_return(true)
- profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user)
+ allow_any_instance_of(
+ TwoFactorAuthentication::AuthAppPolicy
+ ).to receive(:enabled?).and_return(true)
+
+ profile_index = AccountShow.new(
+ decrypted_pii: {}, personal_key: '', decorated_user: user.decorate
+ )
expect(profile_index.totp_partial).to eq 'accounts/actions/disable_totp'
end
@@ -133,8 +138,13 @@
context 'user does not have an authenticator app enabled' do
it 'returns the enable_totp partial' do
user = User.new
- allow(user).to receive(:totp_enabled?).and_return(false)
- profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user)
+ allow_any_instance_of(
+ TwoFactorAuthentication::AuthAppPolicy
+ ).to receive(:enabled?).and_return(false)
+
+ profile_index = AccountShow.new(
+ decrypted_pii: {}, personal_key: '', decorated_user: user.decorate
+ )
expect(profile_index.totp_partial).to eq 'accounts/actions/enable_totp'
end
@@ -148,7 +158,7 @@
first_name = 'John'
decrypted_pii = Pii::Attributes.new_from_json({ first_name: first_name }.to_json)
profile_index = AccountShow.new(
- decrypted_pii: decrypted_pii, personal_key: '', decorated_user: user
+ decrypted_pii: decrypted_pii, personal_key: '', decorated_user: user.decorate
)
expect(profile_index.header_personalization).to eq first_name
@@ -158,7 +168,7 @@
context 'AccountShow instance does not have decrypted_pii' do
it "returns the user's email" do
email = 'john@smith.com'
- user = User.new(email: email)
+ user = User.new(email: email).decorate
profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user)
expect(profile_index.header_personalization).to eq email
@@ -170,8 +180,13 @@
context 'user has enabled an authenticator app' do
it 'returns localization for auth_app_enabled' do
user = User.new
- allow(user).to receive(:totp_enabled?).and_return(true)
- profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user)
+ allow_any_instance_of(
+ TwoFactorAuthentication::AuthAppPolicy
+ ).to receive(:enabled?).and_return(true)
+
+ profile_index = AccountShow.new(
+ decrypted_pii: {}, personal_key: '', decorated_user: user.decorate
+ )
expect(profile_index.totp_content).to eq t('account.index.auth_app_enabled')
end
@@ -179,8 +194,10 @@
context 'user does not have an authenticator app enabled' do
it 'returns localization for auth_app_disabled' do
- user = User.new
- allow(user).to receive(:totp_enabled?).and_return(false)
+ user = User.new.decorate
+ allow_any_instance_of(
+ TwoFactorAuthentication::AuthAppPolicy
+ ).to receive(:enabled?).and_return(false)
profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user)
expect(profile_index.totp_content).to eq t('account.index.auth_app_disabled')
diff --git a/spec/views/account_reset/cancel/show.html.slim_spec.rb b/spec/views/account_reset/cancel/show.html.slim_spec.rb
new file mode 100644
index 00000000000..d970e889e6d
--- /dev/null
+++ b/spec/views/account_reset/cancel/show.html.slim_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+describe 'account_reset/cancel/show.html.slim' do
+ it 'has a localized title' do
+ expect(view).to receive(:title).with(t('account_reset.cancel_request.title'))
+
+ render
+ end
+
+ it 'has button to cancel request' do
+ render
+ expect(rendered).to have_button t('account_reset.cancel_request.cancel_button')
+ end
+end
diff --git a/spec/views/accounts/show.html.slim_spec.rb b/spec/views/accounts/show.html.slim_spec.rb
index e3f94da5dfe..a1d3bc3629a 100644
--- a/spec/views/accounts/show.html.slim_spec.rb
+++ b/spec/views/accounts/show.html.slim_spec.rb
@@ -147,4 +147,47 @@
expect(view).to render_template(partial: '_delete_account_item_heading')
end
+
+ context 'phone listing and adding' do
+ it 'renders the phone section' do
+ render
+
+ expect(view).to render_template(partial: '_phone')
+ end
+
+ context 'user has no phone' do
+ let(:user) do
+ record = build_stubbed(:user, :signed_up, :with_piv_or_cac)
+ record.phone_configurations = []
+ record
+ end
+
+ it 'shows the add phone link' do
+ render
+
+ expect(rendered).to have_link(
+ t('account.index.phone_add'), href: manage_phone_path
+ )
+ end
+ end
+
+ context 'user has a phone' do
+ it 'shows no add phone link' do
+ render
+
+ expect(rendered).to_not have_content t('account.index.phone_add')
+ expect(rendered).to_not have_link(
+ t('account.index.phone_add'), href: manage_phone_path
+ )
+ end
+
+ it 'shows an edit link' do
+ render
+
+ expect(rendered).to have_link(
+ t('account.index.phone'), href: manage_phone_url
+ )
+ end
+ end
+ end
end
diff --git a/spec/views/idv/review/new.html.slim_spec.rb b/spec/views/idv/review/new.html.slim_spec.rb
index 52fa0b5e440..25b84e5b230 100644
--- a/spec/views/idv/review/new.html.slim_spec.rb
+++ b/spec/views/idv/review/new.html.slim_spec.rb
@@ -7,7 +7,7 @@
before do
user = build_stubbed(:user, :signed_up)
allow(view).to receive(:current_user).and_return(user)
- @idv_params = {
+ @applicant = {
first_name: 'Some',
last_name: 'One',
ssn: '666-66-1234',
diff --git a/spec/views/phone_setup/index.html.slim_spec.rb b/spec/views/phone_setup/index.html.slim_spec.rb
index 5f50343aac4..428203ec39f 100644
--- a/spec/views/phone_setup/index.html.slim_spec.rb
+++ b/spec/views/phone_setup/index.html.slim_spec.rb
@@ -6,7 +6,7 @@
allow(view).to receive(:current_user).and_return(user)
- @user_phone_form = UserPhoneForm.new(user)
+ @user_phone_form = UserPhoneForm.new(user, nil)
@presenter = PhoneSetupPresenter.new('voice')
render
end
diff --git a/spec/views/sign_up/personal_keys/show.html.slim_spec.rb b/spec/views/sign_up/personal_keys/show.html.slim_spec.rb
index 0e000395d6e..5f0e88c9e1d 100644
--- a/spec/views/sign_up/personal_keys/show.html.slim_spec.rb
+++ b/spec/views/sign_up/personal_keys/show.html.slim_spec.rb
@@ -40,8 +40,8 @@
it 'informs the user of importance of keeping the personal key in a safe place' do
render
expect(rendered).to have_content(
- t('instructions.personal_key_html',
- accent: t('instructions.personal_key_accent'))
+ t('instructions.personal_key.info_html',
+ accent: t('instructions.personal_key.accent'))
)
end
@@ -61,7 +61,6 @@
it 'displays a button to get a new personal key' do
render
- expect(rendered).to have_xpath("//input[@value='#{t('users.personal_key.get_another')}']")
expect(rendered).to have_xpath("//form[@action='#{sign_up_personal_key_path}']")
end
end
diff --git a/spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb b/spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb
index 2f39b20e64e..3decf89da77 100644
--- a/spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb
+++ b/spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb
@@ -35,7 +35,7 @@
it 'has a localized heading' do
render
- expect(rendered).to have_content t('devise.two_factor_authentication.header_text')
+ expect(rendered).to have_content t('two_factor_authentication.header_text')
end
end
@@ -138,7 +138,7 @@
render
expect(rendered).not_to have_link(
- t('devise.two_factor_authentication.personal_key_fallback.link'),
+ t('two_factor_authentication.personal_key_fallback.link'),
href: login_two_factor_personal_key_path
)
end
diff --git a/spec/views/two_factor_authentication/personal_key_verification/show.html.slim_spec.rb b/spec/views/two_factor_authentication/personal_key_verification/show.html.slim_spec.rb
index c616b68c0a8..57de794f908 100644
--- a/spec/views/two_factor_authentication/personal_key_verification/show.html.slim_spec.rb
+++ b/spec/views/two_factor_authentication/personal_key_verification/show.html.slim_spec.rb
@@ -21,14 +21,14 @@
render
expect(rendered).
- to have_content t('devise.two_factor_authentication.personal_key_header_text')
+ to have_content t('two_factor_authentication.personal_key_header_text')
end
it 'prompts the user to enter their personal key' do
render
expect(rendered).
- to have_content t('devise.two_factor_authentication.personal_key_prompt')
+ to have_content t('two_factor_authentication.personal_key_prompt')
end
it 'contains a form to submit the personal key' do
diff --git a/spec/views/two_factor_authentication/totp_verification/show.html.slim_spec.rb b/spec/views/two_factor_authentication/totp_verification/show.html.slim_spec.rb
index 18312877504..735e3ee73c7 100644
--- a/spec/views/two_factor_authentication/totp_verification/show.html.slim_spec.rb
+++ b/spec/views/two_factor_authentication/totp_verification/show.html.slim_spec.rb
@@ -6,7 +6,7 @@
attributes_for(:generic_otp_presenter).merge(
two_factor_authentication_method: 'authenticator',
user_email: view.current_user.email,
- phone_enabled: user.phone_configurations.any?(&:mfa_enabled?)
+ phone_enabled: TwoFactorAuthentication::PhonePolicy.new(user).enabled?
)
end
@@ -23,7 +23,7 @@
it_behaves_like 'an otp form'
it 'shows the correct header' do
- expect(rendered).to have_content t('devise.two_factor_authentication.totp_header_text')
+ expect(rendered).to have_content t('two_factor_authentication.totp_header_text')
end
it 'shows the correct help text' do
diff --git a/spec/views/users/phones/edit.html.slim_spec.rb b/spec/views/users/phones/edit.html.slim_spec.rb
index a18ee38b7ab..c8cb2ff5867 100644
--- a/spec/views/users/phones/edit.html.slim_spec.rb
+++ b/spec/views/users/phones/edit.html.slim_spec.rb
@@ -5,7 +5,7 @@
before do
user = build_stubbed(:user, :signed_up)
allow(view).to receive(:current_user).and_return(user)
- @user_phone_form = UserPhoneForm.new(user)
+ @user_phone_form = UserPhoneForm.new(user, MfaContext.new(user).phone_configurations.first)
@presenter = PhoneSetupPresenter.new('voice')
end