- <% MfaContext.new(current_user).webauthn_roaming_configurations.each do |cfg| %>
-
-
- <%= cfg.name %>
-
- <% if MfaPolicy.new(current_user).multiple_factors_enabled? %>
-
- <%= link_to(
- t('account.index.webauthn_delete'),
- webauthn_setup_delete_path(id: cfg.id),
- ) %>
-
- <% end %>
-
+
+ <% MfaContext.new(current_user).webauthn_roaming_configurations.each do |configuration| %>
+ <%= render ManageableAuthenticatorComponent.new(
+ configuration:,
+ user_session:,
+ manage_url: edit_webauthn_path(id: configuration.id),
+ manage_api_url: api_internal_two_factor_authentication_webauthn_path(id: configuration.id),
+ custom_strings: {
+ deleted: t('two_factor_authentication.webauthn_roaming.deleted'),
+ renamed: t('two_factor_authentication.webauthn_roaming.renamed'),
+ manage_accessible_label: t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ },
+ role: 'list-item',
+ ) %>
<% end %>
@@ -25,5 +24,6 @@
link_to(webauthn_setup_path, **tag_options, &block)
end,
icon: :add,
- class: 'usa-button usa-button--outline margin-top-2',
+ outline: true,
+ class: 'margin-top-2',
).with_content(t('account.index.webauthn_add')) %>
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb
index 2d17b9e9020..508af264d4b 100644
--- a/app/views/devise/passwords/edit.html.erb
+++ b/app/views/devise/passwords/edit.html.erb
@@ -17,16 +17,14 @@
<%= render PasswordConfirmationComponent.new(
form: f,
password_label: t('forms.passwords.edit.labels.password'),
+ forbidden_passwords: @forbidden_passwords,
field_options: {
input_html: {
aria: { describedby: 'password-description' },
},
},
) %>
- <%= render 'devise/shared/password_strength', forbidden_passwords: @forbidden_passwords %>
<%= f.submit t('forms.passwords.edit.buttons.submit'), class: 'display-block margin-y-5' %>
<% end %>
<%= render 'shared/password_accordion' %>
-
-<%= javascript_packs_tag_once 'pw-strength' %>
diff --git a/app/views/devise/shared/_password_strength.html.erb b/app/views/devise/shared/_password_strength.html.erb
deleted file mode 100644
index f1e3d3a77a5..00000000000
--- a/app/views/devise/shared/_password_strength.html.erb
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
- <%= t('instructions.password.strength.intro') %>
- <%= tag.span '...', id: 'pw-strength-txt', class: 'text-bold', data: {
- forbidden: local_assigns[:forbidden_passwords],
- } %>
-
-
-
-
diff --git a/app/views/event_disavowal/new.html.erb b/app/views/event_disavowal/new.html.erb
index 137f751e5fa..38412a44d6b 100644
--- a/app/views/event_disavowal/new.html.erb
+++ b/app/views/event_disavowal/new.html.erb
@@ -16,14 +16,16 @@
label: t('forms.passwords.edit.labels.password'),
required: true,
input_html: {
+ id: 'new-password',
autocomplete: 'new-password',
},
},
) %>
- <%= render 'devise/shared/password_strength', forbidden_passwords: @forbidden_passwords %>
+ <%= render PasswordStrengthComponent.new(
+ input_id: 'new-password',
+ forbidden_passwords: @forbidden_passwords,
+ ) %>
<%= f.submit t('forms.passwords.edit.buttons.submit'), class: 'margin-bottom-4' %>
<% end %>
<%= render 'shared/password_accordion' %>
-
-<%= javascript_packs_tag_once 'pw-strength' %>
diff --git a/app/views/sign_up/passwords/new.html.erb b/app/views/sign_up/passwords/new.html.erb
index 70b18b831c8..f0d3a80144b 100644
--- a/app/views/sign_up/passwords/new.html.erb
+++ b/app/views/sign_up/passwords/new.html.erb
@@ -14,11 +14,11 @@
<%= text_field_tag('username', @email_address.email, hidden: true, autocomplete: 'username') %>
<%= render PasswordConfirmationComponent.new(
form: f,
+ forbidden_passwords: @forbidden_passwords,
field_options: {
input_html: { aria: { describedby: 'password-description' } },
},
) %>
- <%= render 'devise/shared/password_strength', forbidden_passwords: @forbidden_passwords %>
<%= hidden_field_tag :confirmation_token, @confirmation_token, id: 'confirmation_token' %>
<%= f.submit t('forms.buttons.continue'), class: 'display-block margin-y-5' %>
<% end %>
@@ -26,5 +26,3 @@
<%= render 'shared/password_accordion' %>
<%= render 'shared/cancel' %>
-
-<%= javascript_packs_tag_once 'pw-strength' %>
diff --git a/app/views/users/passwords/edit.html.erb b/app/views/users/passwords/edit.html.erb
index fd08abe5f5b..45a479f38f1 100644
--- a/app/views/users/passwords/edit.html.erb
+++ b/app/views/users/passwords/edit.html.erb
@@ -14,18 +14,16 @@
<%= render PasswordConfirmationComponent.new(
form: f,
password_label: t('forms.passwords.edit.labels.password'),
+ forbidden_passwords: @forbidden_passwords,
field_options: {
input_html: {
aria: { describedby: 'password-description' },
},
},
) %>
- <%= render 'devise/shared/password_strength', forbidden_passwords: @forbidden_passwords %>
<%= f.submit t('forms.buttons.submit.update'), class: 'display-block margin-top-5 margin-bottom-4' %>
<% end %>
<%= render 'shared/password_accordion' %>
<%= render 'shared/cancel', link: account_path %>
-
-<%= javascript_packs_tag_once 'pw-strength' %>
diff --git a/app/views/users/webauthn/edit.html.erb b/app/views/users/webauthn/edit.html.erb
index c8be25675b3..5dc61948680 100644
--- a/app/views/users/webauthn/edit.html.erb
+++ b/app/views/users/webauthn/edit.html.erb
@@ -1,6 +1,6 @@
-<% self.title = t('two_factor_authentication.webauthn_platform.edit_heading') %>
+<% self.title = @presenter.heading %>
-<%= render PageHeadingComponent.new.with_content(t('two_factor_authentication.webauthn_platform.edit_heading')) %>
+<%= render PageHeadingComponent.new.with_content(@presenter.heading) %>
<%= simple_form_for(
@form,
@@ -12,20 +12,17 @@
<%= render ValidatedFieldComponent.new(
form: f,
name: :name,
- label: t('two_factor_authentication.webauthn_platform.nickname'),
+ label: @presenter.nickname_field_label,
) %>
- <%= f.submit(
- t('two_factor_authentication.webauthn_platform.change_nickname'),
- class: 'display-block margin-top-5',
- ) %>
+ <%= f.submit(@presenter.rename_button_label, class: 'display-block margin-top-5') %>
<% end %>
<%= render ButtonComponent.new(
action: ->(**tag_options, &block) do
button_to(
webauthn_path(id: @form.configuration.id),
- form: { aria: { label: t('two_factor_authentication.webauthn_platform.delete') } },
+ form: { aria: { label: @presenter.delete_button_label } },
**tag_options,
&block
)
@@ -35,6 +32,6 @@
wide: true,
danger: true,
class: 'display-block margin-top-2',
- ).with_content(t('two_factor_authentication.webauthn_platform.delete')) %>
+ ).with_content(@presenter.delete_button_label) %>
<%= render 'shared/cancel', link: account_path %>
diff --git a/config/locales/account/en.yml b/config/locales/account/en.yml
index e34c1204fbf..8c51de05e9a 100644
--- a/config/locales/account/en.yml
+++ b/config/locales/account/en.yml
@@ -55,7 +55,6 @@ en:
webauthn: Security key
webauthn_add: Add security key
webauthn_confirm_delete: Yes, remove key
- webauthn_delete: Remove key
webauthn_platform: Face or touch unlock
webauthn_platform_add: Add face or touch unlock
webauthn_platform_confirm_delete: Yes, remove face or touch unlock
diff --git a/config/locales/account/es.yml b/config/locales/account/es.yml
index 9e72845a8cf..daf1a7c82bf 100644
--- a/config/locales/account/es.yml
+++ b/config/locales/account/es.yml
@@ -56,7 +56,6 @@ es:
webauthn: Clave de seguridad
webauthn_add: Añadir clave de seguridad
webauthn_confirm_delete: Si quitar la llave
- webauthn_delete: Quitar llave
webauthn_platform: El desbloqueo facial o táctil
webauthn_platform_add: Añadir el desbloqueo facial o táctil
webauthn_platform_confirm_delete: Si, quitar el desbloqueo facial o táctil
diff --git a/config/locales/account/fr.yml b/config/locales/account/fr.yml
index 445776e7d5a..6fb436a6fb8 100644
--- a/config/locales/account/fr.yml
+++ b/config/locales/account/fr.yml
@@ -59,7 +59,6 @@ fr:
webauthn: Clé de sécurité
webauthn_add: Ajouter une clé de sécurité
webauthn_confirm_delete: Oui, supprimer la clé
- webauthn_delete: Supprimer la clé
webauthn_platform: Le déverouillage facial ou déverrouillage par empreinte digitale
webauthn_platform_add: Ajouter le déverouillage facial ou déverrouillage par empreinte digitale
webauthn_platform_confirm_delete: Oui, supprimer le déverouillage facial ou
diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml
index e3861954239..e00fab81232 100644
--- a/config/locales/errors/en.yml
+++ b/config/locales/errors/en.yml
@@ -50,6 +50,7 @@ en:
messages:
already_confirmed: was already confirmed, please try signing in
blank: Please fill in this field.
+ blank_cert_element_req: We cannot detect a certificate in your request.
confirmation_code_incorrect: Incorrect verification code. Did you type it in correctly?
confirmation_invalid_token: Invalid confirmation link. Either the link expired
or you already confirmed your account.
diff --git a/config/locales/errors/es.yml b/config/locales/errors/es.yml
index be20e24d5d1..970491697c4 100644
--- a/config/locales/errors/es.yml
+++ b/config/locales/errors/es.yml
@@ -54,6 +54,7 @@ es:
messages:
already_confirmed: ya estaba confirmado, por favor intente iniciar una sesión
blank: Por favor, rellenar este campo.
+ blank_cert_element_req: No podemos detectar un certificado en su solicitud.
confirmation_code_incorrect: Código de verificación incorrecto. ¿Lo escribió correctamente?
confirmation_invalid_token: El enlace de confirmación no es válido. El enlace
expiró o usted ya ha confirmado su cuenta.
diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml
index fb52095e419..a62e9a84798 100644
--- a/config/locales/errors/fr.yml
+++ b/config/locales/errors/fr.yml
@@ -59,6 +59,7 @@ fr:
messages:
already_confirmed: a déjà été confirmé, veuillez essayer de vous connecter
blank: Veuillez remplir ce champ.
+ blank_cert_element_req: Nous ne pouvons pas détecter un certificat sur votre demande.
confirmation_code_incorrect: Code de vérification incorrect. L’avez-vous saisi correctement ?
confirmation_invalid_token: Lien de confirmation non valide. Le lien est expiré
ou vous avez déjà confirmé votre compte.
diff --git a/config/locales/instructions/en.yml b/config/locales/instructions/en.yml
index 0783d62a70e..63edaca651b 100644
--- a/config/locales/instructions/en.yml
+++ b/config/locales/instructions/en.yml
@@ -88,12 +88,12 @@ en:
you verified your identity with this account. If you don’t have it, you
can still reset your password and then reverify your identity.
strength:
- i: Very weak
- ii: Weak
- iii: Average
+ 0: Very weak
+ 1: Weak
+ 2: Average
+ 3: Good
+ 4: Great
intro: 'Password strength: '
- iv: Good
- v: Great
sp_handoff_bounced: Your sign in was successful, but %{sp_name} sent you back to
%{app_name}. Please contact %{sp_link} for help.
sp_handoff_bounced_with_no_sp: your service provider
diff --git a/config/locales/instructions/es.yml b/config/locales/instructions/es.yml
index 52014ed2233..37a4532fb86 100644
--- a/config/locales/instructions/es.yml
+++ b/config/locales/instructions/es.yml
@@ -93,12 +93,12 @@ es:
con ella, de todos modos puede restablecer su contraseña y luego volver
a verificar su identidad.
strength:
- i: Muy débil
- ii: Débil
- iii: Promedio
+ 0: Muy débil
+ 1: Débil
+ 2: Promedio
+ 3: Buena
+ 4: 'Muy buena'
intro: 'Seguridad de la contraseña:'
- iv: Buena
- v: 'Muy buena'
sp_handoff_bounced: Su inicio de sesión fue exitoso, pero %{sp_name} lo envió de
regreso a %{app_name}. Póngase en contacto con %{sp_link} para obtener
ayuda.
diff --git a/config/locales/instructions/fr.yml b/config/locales/instructions/fr.yml
index 0fdb4d98fe4..ec2e74cfe48 100644
--- a/config/locales/instructions/fr.yml
+++ b/config/locales/instructions/fr.yml
@@ -105,12 +105,12 @@ fr:
avec ce compte. Si vous ne l’avez pas, vous pouvez toujours
réinitialiser votre mot de passe et ensuite revérifier votre identité.
strength:
- i: Très faible
- ii: Faible
- iii: Moyen
+ 0: Très faible
+ 1: Faible
+ 2: Moyen
+ 3: Bonne
+ 4: Excellente
intro: 'Force du mot de passe : '
- iv: Bonne
- v: Excellente
sp_handoff_bounced: Votre connexion a réussi, mais %{sp_name} vous a renvoyé à
%{app_name}. Veuillez contacter %{sp_link} pour obtenir de l’aide.
sp_handoff_bounced_with_no_sp: votre fournisseur de service
diff --git a/config/locales/two_factor_authentication/en.yml b/config/locales/two_factor_authentication/en.yml
index e55ae0021a5..f09a8862afb 100644
--- a/config/locales/two_factor_authentication/en.yml
+++ b/config/locales/two_factor_authentication/en.yml
@@ -208,4 +208,12 @@ en:
renamed: Successfully renamed your face or touch unlock method
webauthn_platform_header_text: Use face or touch unlock
webauthn_platform_use_key: Use screen unlock
+ webauthn_roaming:
+ change_nickname: Change nickname
+ delete: Delete this device
+ deleted: Successfully deleted a security key method
+ edit_heading: Manage your security key settings
+ manage_accessible_label: Manage security key
+ nickname: Nickname
+ renamed: Successfully renamed your security key method
webauthn_use_key: Use security key
diff --git a/config/locales/two_factor_authentication/es.yml b/config/locales/two_factor_authentication/es.yml
index 5f094a6efae..ac4d95f3f8f 100644
--- a/config/locales/two_factor_authentication/es.yml
+++ b/config/locales/two_factor_authentication/es.yml
@@ -223,4 +223,12 @@ es:
facial o táctil
webauthn_platform_header_text: Usar desbloqueo facial o táctil
webauthn_platform_use_key: Usar el desbloqueo de pantalla
+ webauthn_roaming:
+ change_nickname: Cambiar apodo
+ delete: Eliminar este dispositivo
+ deleted: Se ha eliminado correctamente un método de clave de seguridad
+ edit_heading: Gestionar la configuración de su clave de seguridad
+ manage_accessible_label: Gestionar la clave de seguridad
+ nickname: Apodo
+ renamed: Se ha cambiado correctamente el nombre de su método de clave de seguridad
webauthn_use_key: Usar llave de seguridad
diff --git a/config/locales/two_factor_authentication/fr.yml b/config/locales/two_factor_authentication/fr.yml
index 01465df3f19..af4a50414f1 100644
--- a/config/locales/two_factor_authentication/fr.yml
+++ b/config/locales/two_factor_authentication/fr.yml
@@ -235,4 +235,12 @@ fr:
empreinte digitale a été renommée avec succès
webauthn_platform_header_text: Utilisez le déverrouillage facial ou tactile
webauthn_platform_use_key: Utiliser le déverrouillage de l’écran
+ webauthn_roaming:
+ change_nickname: Changer de pseudo
+ delete: Supprimer cet appareil
+ deleted: Suppression réussie d’une méthode de clé de sécurité
+ edit_heading: Gérer les paramètres de votre clé de sécurité
+ manage_accessible_label: Gérer la clé de sécurité
+ nickname: Pseudo
+ renamed: Votre méthode de clé de sécurité a été renommée avec succès
webauthn_use_key: Utiliser la clé de sécurité
diff --git a/dockerfiles/idp_review_app.Dockerfile b/dockerfiles/idp_review_app.Dockerfile
index 839115cc1d1..01c5d97bb91 100644
--- a/dockerfiles/idp_review_app.Dockerfile
+++ b/dockerfiles/idp_review_app.Dockerfile
@@ -1,12 +1,6 @@
FROM ruby:3.3.0-slim
# Set environment variables
-ARG ARG_CI_ENVIRONMENT_SLUG="placeholder"
-ARG ARG_CI_COMMIT_BRANCH="branch_placeholder"
-ARG ARG_CI_COMMIT_SHA="sha_placeholder"
-ENV CI_ENVIRONMENT_SLUG=${ARG_CI_ENVIRONMENT_SLUG}
-ENV CI_COMMIT_BRANCH=${ARG_CI_COMMIT_BRANCH}
-ENV CI_COMMIT_SHA=${ARG_CI_COMMIT_SHA}
ENV RAILS_ROOT /app
ENV RAILS_ENV production
ENV NODE_ENV production
@@ -36,29 +30,6 @@ ENV DOMAIN_NAME localhost:3000
ENV PIV_CAC_SERVICE_URL https://localhost:8443/
ENV PIV_CAC_VERIFY_TOKEN_URL https://localhost:8443/
-RUN echo Env Value : $CI_ENVIRONMENT_SLUG
-
-# Prevent documentation installation
-RUN echo 'path-exclude=/usr/share/doc/*' > /etc/dpkg/dpkg.cfg.d/00_nodoc && \
- echo 'path-exclude=/usr/share/man/*' >> /etc/dpkg/dpkg.cfg.d/00_nodoc && \
- echo 'path-exclude=/usr/share/groff/*' >> /etc/dpkg/dpkg.cfg.d/00_nodoc && \
- echo 'path-exclude=/usr/share/info/*' >> /etc/dpkg/dpkg.cfg.d/00_nodoc && \
- echo 'path-exclude=/usr/share/lintian/*' >> /etc/dpkg/dpkg.cfg.d/00_nodoc && \
- echo 'path-exclude=/usr/share/linda/*' >> /etc/dpkg/dpkg.cfg.d/00_nodoc
-
-# Create a new user and set up the working directory
-RUN addgroup --gid 1000 app && \
- adduser --uid 1000 --gid 1000 --disabled-password --gecos "" app && \
- mkdir -p $RAILS_ROOT && \
- mkdir -p $BUNDLE_PATH && \
- mkdir -p $RAILS_ROOT/tmp/pids && \
- chown -R app:app $RAILS_ROOT && \
- chown -R app:app $BUNDLE_PATH
-
-# Setup timezone data
-ENV TZ=Etc/UTC
-RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
-
# Install dependencies
RUN apt-get update && \
apt-get install -y \
@@ -90,6 +61,19 @@ RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr
RUN echo "deb [signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update && apt-get install -y yarn=1.22.5-1
+# Create a new user and set up the working directory
+RUN addgroup --gid 1000 app && \
+ adduser --uid 1000 --gid 1000 --disabled-password --gecos "" app && \
+ mkdir -p $RAILS_ROOT && \
+ mkdir -p $BUNDLE_PATH && \
+ mkdir -p $RAILS_ROOT/tmp/pids && \
+ chown -R app:app $RAILS_ROOT && \
+ chown -R app:app $BUNDLE_PATH
+
+# Setup timezone data
+ENV TZ=Etc/UTC
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
# Create the working directory
WORKDIR $RAILS_ROOT
@@ -128,9 +112,6 @@ COPY --chown=app:app ./babel.config.js ./babel.config.js
COPY --chown=app:app ./webpack.config.js ./webpack.config.js
COPY --chown=app:app ./.browserslistrc ./.browserslistrc
-RUN mkdir -p $RAILS_ROOT/public/api/
-RUN echo "{\"branch\":\"$CI_COMMIT_BRANCH\",\"git_sha\":\"$CI_COMMIT_SHA\"}" > $RAILS_ROOT/public/api/deploy.json
-
# Copy keys
COPY --chown=app:app keys.example $RAILS_ROOT/keys
@@ -143,15 +124,6 @@ COPY --chown=app:app public/ban-robots.txt $RAILS_ROOT/public/robots.txt
# Copy application.yml.default to application.yml
COPY --chown=app:app ./config/application.yml.default.docker $RAILS_ROOT/config/application.yml
-# Generate and place SSL certificates for puma
-RUN openssl req -x509 -sha256 -nodes -newkey rsa:2048 -days 1825 \
- -keyout $RAILS_ROOT/keys/localhost.key \
- -out $RAILS_ROOT/keys/localhost.crt \
- -subj "/C=US/ST=Fake/L=Fakerton/O=Dis/CN=localhost"
-
-# Precompile assets
-RUN bundle exec rake assets:precompile --trace
-
# Setup config files
COPY --chown=app:app config/agencies.localdev.yml $RAILS_ROOT/config/agencies.yml
COPY --chown=app:app config/iaa_gtcs.localdev.yml $RAILS_ROOT/config/iaa_gtcs.yml
@@ -164,6 +136,20 @@ COPY --chown=app:app config/partner_accounts.localdev.yml $RAILS_ROOT/config/par
COPY --chown=app:app certs.example $RAILS_ROOT/certs
COPY --chown=app:app config/service_providers.localdev.yml $RAILS_ROOT/config/service_providers.yml
+# Precompile assets
+RUN bundle exec rake assets:precompile --trace
+
+ARG ARG_CI_COMMIT_BRANCH="branch_placeholder"
+ARG ARG_CI_COMMIT_SHA="sha_placeholder"
+RUN mkdir -p $RAILS_ROOT/public/api/
+RUN echo "{\"branch\":\"$ARG_CI_COMMIT_BRANCH\",\"git_sha\":\"$ARG_CI_COMMIT_SHA\"}" > $RAILS_ROOT/public/api/deploy.json
+
+# Generate and place SSL certificates for puma
+RUN openssl req -x509 -sha256 -nodes -newkey rsa:2048 -days 1825 \
+ -keyout $RAILS_ROOT/keys/localhost.key \
+ -out $RAILS_ROOT/keys/localhost.crt \
+ -subj "/C=US/ST=Fake/L=Fakerton/O=Dis/CN=localhost"
+
# Expose the port the app runs on
EXPOSE 3000
diff --git a/docs/sdk-upgrade.md b/docs/sdk-upgrade.md
index cf03ed0e37d..b6f79973aaa 100644
--- a/docs/sdk-upgrade.md
+++ b/docs/sdk-upgrade.md
@@ -41,7 +41,7 @@ Steps:
6. Inspect the `Sources` of the page. Expand the local IP address from which you are serving the page. You should see a folder with a version number in the name, like `acuant/11.N.N`. Check that the version here is the new one — the version you noted in step 1. This screenshot shows where the version number appears in Chrome:
- 
+ 
7. Assuming the version is correct, you are ready to test it. On your phone, tap to photograph your state ID card. Point the camera at the card. Ensure the SDK finds the edges of the card and captures an image. Normally the SDK will put a yellowish box over the card to show where it believes the edges are.
@@ -87,6 +87,14 @@ Steps:
Set the default to the new SDK version and the alternate to the old version. (That way, the new version is in place if the A/B testing goes well.)
+ **Note**: For testing in `staging`, `idv_acuant_sdk_upgrade_a_b_testing_enabled` can be set to `false` like following to test the new SDK version:
+ ```yaml
+ idv_acuant_sdk_upgrade_a_b_testing_enabled: false
+ idv_acuant_sdk_upgrade_a_b_testing_percent: 50 # ignored
+ idv_acuant_sdk_version_alternate: 11.M.M # previous
+ idv_acuant_sdk_version_default: 11.N.N # newest
+ ```
+ The testing phase should continue until we have accumulated sufficient traffic.
4. Save the file. If the file opened in the vi editor, use `:wq` to save. A diff of your changes will appear. Copy the diff and paste it into the Slack thread. Type `y` to accept the changes.
5. Recycle the servers [with these Handbook instructions](https://handbook.login.gov/articles/appdev-deploy.html#production). This will involve:
@@ -98,6 +106,26 @@ Steps:
6. While you monitor the recycle, manually check the document capture page in the environment you are deploying to. Ensure the SDK loads and can capture images.
Monitoring the A/B test begins now. Proceed to the next section.
+## Testing Considerations
+Manual testing should be performed to cover the following with verification *Success* or *Failure*:
+* SDK UI
+ * Camera permission prompt is shown
+ * Instruction text for taking ID and selfie
+ * Countdown while capturing
+ * Auto-capture mode
+* Camera permissions
+ * Prompt is shown upon the first time opening the SDK
+ * Tapping 'Decline' shows error message on the 'Add photos' page
+ * Opening the SDK again shows the same prompt
+
+Operating systems:
+ * iOS
+ * Android
+
+Browser:
+ * Chrome
+ * Firefox
+ * Safari
## Monitor A/B testing
diff --git a/package.json b/package.json
index 16b3ef2886d..c1c8fb04671 100644
--- a/package.json
+++ b/package.json
@@ -40,8 +40,7 @@
"source-map-loader": "^4.0.0",
"webpack": "^5.76.1",
"webpack-assets-manifest": "^5.1.0",
- "webpack-cli": "^4.10.0",
- "zxcvbn": "4.4.2"
+ "webpack-cli": "^4.10.0"
},
"devDependencies": {
"@babel/cli": "^7.22.15",
@@ -61,6 +60,7 @@
"@types/react-dom": "^17.0.11",
"@types/sinon": "^10.0.13",
"@types/sinon-chai": "^3.2.8",
+ "@types/zxcvbn": "^4.4.4",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"chai": "^4.3.10",
diff --git a/spec/components/manageable_authenticator_component_spec.rb b/spec/components/manageable_authenticator_component_spec.rb
index 20d438abf24..56eed6d93ce 100644
--- a/spec/components/manageable_authenticator_component_spec.rb
+++ b/spec/components/manageable_authenticator_component_spec.rb
@@ -56,11 +56,10 @@
expect(edit_element.attr('tabindex')).to be_present
expect(edit_element).to have_name(
- format(
- '%s: %s',
+ [
t('components.manageable_authenticator.manage_accessible_label'),
configuration.name,
- ),
+ ].join(': '),
)
end
@@ -80,11 +79,10 @@
it 'renders with buttons that have accessibly distinct manage label' do
expect(rendered).to have_button(
- format(
- '%s: %s',
+ [
t('components.manageable_authenticator.manage_accessible_label'),
configuration.name,
- ),
+ ].join(': '),
)
end
@@ -147,7 +145,7 @@
let(:custom_strings) { { manage_accessible_label: custom_manage_accessible_label } }
it 'overrides button label and affected linked content' do
- manage_label = format('%s: %s', custom_manage_accessible_label, configuration.name)
+ manage_label = [custom_manage_accessible_label, configuration.name].join(': ')
expect(rendered).to have_button(manage_label)
edit_element = page.find_css('.manageable-authenticator__edit').first
expect(edit_element).to have_name(manage_label)
diff --git a/spec/components/password_confirmation_component_spec.rb b/spec/components/password_confirmation_component_spec.rb
index 0116d517731..f88d773359a 100644
--- a/spec/components/password_confirmation_component_spec.rb
+++ b/spec/components/password_confirmation_component_spec.rb
@@ -3,9 +3,10 @@
RSpec.describe PasswordConfirmationComponent, type: :component do
let(:view_context) { vc_test_controller.view_context }
let(:form) { SimpleForm::FormBuilder.new('', {}, view_context, {}) }
+ let(:options) { { form: } }
subject(:rendered) do
- render_inline PasswordConfirmationComponent.new(form:)
+ render_inline PasswordConfirmationComponent.new(**options)
end
it 'renders password fields with expected attributes' do
@@ -22,15 +23,9 @@
end
context 'with labels passed in' do
- subject(:rendered) do
- render_inline PasswordConfirmationComponent.new(
- form:,
- password_label: password_label,
- confirmation_label: confirmation_label,
- )
- end
let(:password_label) { 'edited password label' }
let(:confirmation_label) { 'edited password confirmation label' }
+ let(:options) { super().merge(password_label:, confirmation_label:) }
it 'renders custom password label' do
expect(rendered).to have_content(password_label)
@@ -40,4 +35,17 @@
expect(rendered).to have_content(confirmation_label)
end
end
+
+ context 'with forbidden passwords' do
+ let(:forbidden_passwords) { ['password'] }
+ let(:options) { super().merge(forbidden_passwords:) }
+
+ it 'forwards forbidden passwords to rendered password strength component' do
+ expect(PasswordStrengthComponent).to receive(:new).
+ with(hash_including(forbidden_passwords:)).
+ and_call_original
+
+ rendered
+ end
+ end
end
diff --git a/spec/components/password_strength_component_spec.rb b/spec/components/password_strength_component_spec.rb
new file mode 100644
index 00000000000..1f2d7d6ba87
--- /dev/null
+++ b/spec/components/password_strength_component_spec.rb
@@ -0,0 +1,33 @@
+require 'rails_helper'
+
+RSpec.describe PasswordStrengthComponent, type: :component do
+ let(:input_id) { 'input' }
+ let(:options) { { input_id: } }
+
+ subject(:rendered) { render_inline PasswordStrengthComponent.new(**options) }
+
+ it 'renders with default attributes' do
+ element = rendered.at_css('lg-password-strength')
+
+ expect(element.attr('input-id')).to eq('input')
+ expect(element.attr('minimum-length')).to eq('12')
+ expect(element.attr('forbidden-passwords')).to eq('[]')
+ expect(element.attr('class')).to eq('display-none')
+ end
+
+ context 'with customized options' do
+ let(:minimum_length) { 10 }
+ let(:forbidden_passwords) { ['password'] }
+ let(:tag_options) { { class: 'example-class', data: { foo: 'bar' } } }
+ let(:options) { super().merge(minimum_length:, forbidden_passwords:, **tag_options) }
+
+ it 'renders with customized option attributes' do
+ element = rendered.at_css('lg-password-strength')
+
+ expect(element.attr('minimum-length')).to eq('10')
+ expect(element.attr('forbidden-passwords')).to eq('["password"]')
+ expect(element.attr('class').split(' ')).to match_array(['example-class', 'display-none'])
+ expect(element.attr('data-foo')).to eq('bar')
+ end
+ end
+end
diff --git a/spec/components/password_toggle_component_spec.rb b/spec/components/password_toggle_component_spec.rb
index 726a9f8a27f..efdd3c434a5 100644
--- a/spec/components/password_toggle_component_spec.rb
+++ b/spec/components/password_toggle_component_spec.rb
@@ -64,6 +64,15 @@
expect(toggle_two.input_id).to be_present
expect(toggle_one.input_id).not_to eq(toggle_two.input_id)
end
+
+ context 'with field_options customizing id' do
+ let(:options) { { field_options: { input_html: { id: 'custom' } } } }
+
+ it 'respects customized id' do
+ expect(rendered).to have_css('#custom[type=password]')
+ expect(rendered).to have_css('[aria-controls=custom]')
+ end
+ end
end
context 'with tag options' do
diff --git a/spec/components/previews/password_strength_component_preview.rb b/spec/components/previews/password_strength_component_preview.rb
new file mode 100644
index 00000000000..aa1b954072c
--- /dev/null
+++ b/spec/components/previews/password_strength_component_preview.rb
@@ -0,0 +1,31 @@
+class PasswordStrengthComponentPreview < BaseComponentPreview
+ # @after_render :inject_input_html
+ # @!group Preview
+ def default
+ render(PasswordStrengthComponent.new(input_id: 'preview-input'))
+ end
+ # @!endgroup
+
+ # @after_render :inject_input_html
+ # @param minimum_length text
+ # @param forbidden_passwords text
+ def workbench(minimum_length: '12', forbidden_passwords: 'password')
+ render(
+ PasswordStrengthComponent.new(
+ input_id: 'preview-input',
+ minimum_length:,
+ forbidden_passwords: forbidden_passwords.split(','),
+ ),
+ )
+ end
+
+ private
+
+ def inject_input_html(html, _context)
+ <<~HTML
+
+
+ #{html}
+ HTML
+ end
+end
diff --git a/spec/controllers/api/internal/two_factor_authentication/webauthn_controller_spec.rb b/spec/controllers/api/internal/two_factor_authentication/webauthn_controller_spec.rb
index 38067823cb0..513a0e4c424 100644
--- a/spec/controllers/api/internal/two_factor_authentication/webauthn_controller_spec.rb
+++ b/spec/controllers/api/internal/two_factor_authentication/webauthn_controller_spec.rb
@@ -27,6 +27,7 @@
:webauthn_update_name_submitted,
success: true,
configuration_id: configuration.id.to_s,
+ platform_authenticator: false,
error_details: nil,
)
end
@@ -60,6 +61,7 @@
:webauthn_update_name_submitted,
success: false,
configuration_id: configuration.id.to_s,
+ platform_authenticator: false,
error_details: { name: { blank: true } },
)
end
@@ -118,6 +120,7 @@
:webauthn_delete_submitted,
success: true,
configuration_id: configuration.id.to_s,
+ platform_authenticator: false,
error_details: nil,
)
end
@@ -174,6 +177,7 @@
:webauthn_delete_submitted,
success: false,
configuration_id: configuration.id.to_s,
+ platform_authenticator: false,
error_details: { configuration_id: { only_method: true } },
)
end
diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb
index 9a5c3a8595e..73cdc8f8cf1 100644
--- a/spec/controllers/saml_idp_controller_spec.rb
+++ b/spec/controllers/saml_idp_controller_spec.rb
@@ -125,6 +125,61 @@
expect(response).to be_bad_request
end
+
+ context 'cert element in SAML request is blank' do
+ let(:user) { create(:user, :fully_registered) }
+ let(:service_provider) { build(:service_provider, issuer: 'http://localhost:3000') }
+
+ # the RubySAML library won't let us pass an empty string in as the certificate
+ # element, so this test substitutes a SAMLRequest that has that element blank
+ let(:blank_cert_element_req) do
+ <<-XML.gsub(/^[\s\t]*|[\s\t]*\n/, '')
+
+
+ http://localhost:3000
+
+
+
+
+
+
+
+
+
+
+
+
+ 2Nb3RLbiFHn0cyn+7JA7hWbbK1NvFMVGa4MYTb3Q91I=
+
+
+ UmsRcaWkHXrUnBMfOQBC2DIQk1rkQqMc5oucz6FAjulq0ZX7qT+zUbSZ7K/us+lzcL1hrgHXi2wxjKSRiisWrJNSmbIGGZIa4+U8wIMhkuY5vZVKgxRc2aP88i/lWwURMI183ifAzCwpq5Y4yaJ6pH+jbgYOtmOhcXh1OwrI+QqR7QSglyUJ55WO+BCR07Hf8A7DSA/Wgp9xH+DUw1EnwbDdzoi7TFqaHY8S4SWIcc26DHsq88mjsmsxAFRQ+4t6nadOnrrFnJWKJeiFlD8MxcQuBiuYBetKRLIPxyXKFxjEn7EkJ5zDkkrBWyUT4VT/JnthUlD825D+v81ZXIX3Tg==
+
+
+
+
+
+
+
+ _13ae90d1-2f9b-4ed5-b84d-3722ea42e386
+
+ XML
+ end
+ let(:deflated_encoded_req) do
+ Base64.encode64(Zlib::Deflate.deflate(blank_cert_element_req, 9)[2..-5])
+ end
+
+ it 'a ValidationError is raised' do
+ expect do
+ delete :logout, params: {
+ 'SAMLRequest' => deflated_encoded_req,
+ path_year:,
+ }
+ end.to raise_error(
+ SamlIdp::XMLSecurity::SignedDocument::ValidationError,
+ 'Certificate element present in response (ds:X509Certificate) but evaluating to nil',
+ )
+ end
+ end
end
describe '/api/saml/remotelogout' do
@@ -1103,7 +1158,7 @@ def name_id_version(format_urn)
)
end
- it 'deoes not blow up' do
+ it 'does not blow up' do
user = create(:user, :fully_registered)
expect { generate_saml_response(user, second_cert_settings) }.to_not raise_error
@@ -1304,6 +1359,87 @@ def name_id_version(format_urn)
end
end
+ context 'cert element in SAML request is blank' do
+ let(:user) { create(:user, :fully_registered) }
+ let(:service_provider) { build(:service_provider, issuer: 'http://localhost:3000') }
+ let(:analytics_hash) do
+ {
+ success: false,
+ errors: { service_provider: ['We cannot detect a certificate in your request.'] },
+ error_details: { service_provider: { blank_cert_element_req: true } },
+ nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT,
+ authn_context: [Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF],
+ authn_context_comparison: 'exact',
+ service_provider: 'http://localhost:3000',
+ request_signed: true,
+ }
+ end
+
+ before do
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+ end
+
+ # the RubySAML library won't let us pass an empty string in as the certificate
+ # element, so this test substitutes a SAMLRequest that has that element blank
+ let(:blank_cert_element_req) do
+ <<-XML.gsub(/^[\s\t]*|[\s\t]*\n/, '')
+
+
+ http://localhost:3000
+
+
+
+
+
+
+
+
+
+
+
+
+ aoHPDDUZTRSIVsbuE954QKbo6StafYvbVUPU+p33m8E=
+
+
+ JH0VD0SLKawSS9tnlUxUL2fYVCza4MT6L79aRiKQi56+arGfnPHZ21cIYOEHxDn2xIg6EV6tda+WwOP9WTrsuqJLAfTWLz9Ah2A8ukITIOYED5WboiodLr5sjkr4HFKwRjERtLycLaxDt8Ya9tHQa5mOjln8yIWFDLdf89jnXaTM9gReq2k1MpI3YlhIYHJMALY5NxbOPTTmWeXdiUUYH/Irq2jzXrI+2ruyCZt8Xpo9tfosFGnoTGFkeK7sWOmndle2WqRE29k4S582JJtXgi4A8JDGw0KK8zM4JttxpK+DbowN8wJ4gWpgRppkBi5e6JiV4W0DNgZC72WHjXULQg==
+
+
+
+
+
+
+
+
+ urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo
+
+
+ XML
+ end
+ let(:deflated_encoded_req) do
+ Base64.encode64(Zlib::Deflate.deflate(blank_cert_element_req, 9)[2..-5])
+ end
+
+ before do
+ IdentityLinker.new(user, service_provider).link_identity
+ user.identities.last.update!(verified_attributes: ['email'])
+ expect(CGI).to receive(:unescape).and_return deflated_encoded_req
+ end
+
+ it 'notes it in the analytics event' do
+ generate_saml_response(user, saml_settings)
+ expect(@analytics).to have_received(:track_event).
+ with('SAML Auth', analytics_hash)
+ end
+
+ it 'returns a 400' do
+ generate_saml_response(user, saml_settings)
+ expect(controller).to render_template('saml_idp/auth/error')
+ expect(response.status).to eq(400)
+ expect(response.body).to include(t('errors.messages.blank_cert_element_req'))
+ end
+ end
+
context 'no IAL explicitly requested' do
let(:user) { create(:user, :fully_registered) }
@@ -2146,9 +2282,9 @@ def stub_requested_attributes
expect(subject).to have_actions(
:before,
:disable_caching,
- :validate_saml_request,
- :validate_service_provider_and_authn_context,
:store_saml_request,
+ :validate_and_create_saml_request_object,
+ :validate_service_provider_and_authn_context,
)
end
end
diff --git a/spec/controllers/users/webauthn_controller_spec.rb b/spec/controllers/users/webauthn_controller_spec.rb
index 4a9d5792ea7..836ebf525b7 100644
--- a/spec/controllers/users/webauthn_controller_spec.rb
+++ b/spec/controllers/users/webauthn_controller_spec.rb
@@ -13,11 +13,12 @@
let(:params) { { id: configuration.id } }
let(:response) { get :edit, params: params }
- it 'assigns the form instance' do
+ it 'assigns the form and presenter instances' do
response
expect(assigns(:form)).to be_kind_of(TwoFactorAuthentication::WebauthnUpdateForm)
expect(assigns(:form).configuration).to eq(configuration)
+ expect(assigns(:presenter)).to be_kind_of(TwoFactorAuthentication::WebauthnEditPresenter)
end
context 'signed out' do
@@ -63,7 +64,7 @@
it 'redirects to account page with success message' do
expect(response).to redirect_to(account_path)
- expect(flash[:success]).to eq(t('two_factor_authentication.webauthn_platform.renamed'))
+ expect(flash[:success]).to eq(t('two_factor_authentication.webauthn_roaming.renamed'))
end
it 'assigns the form instance' do
@@ -80,6 +81,7 @@
:webauthn_update_name_submitted,
success: true,
configuration_id: configuration.id.to_s,
+ platform_authenticator: false,
error_details: nil,
)
end
@@ -96,6 +98,14 @@
context 'with invalid submission' do
let(:name) { '' }
+ it 'assigns form and presenter instances' do
+ response
+
+ expect(assigns(:form)).to be_kind_of(TwoFactorAuthentication::WebauthnUpdateForm)
+ expect(assigns(:form).configuration).to eq(configuration)
+ expect(assigns(:presenter)).to be_kind_of(TwoFactorAuthentication::WebauthnEditPresenter)
+ end
+
it 'renders edit template with error' do
expect(response).to render_template(:edit)
expect(flash.now[:error]).to eq(t('errors.messages.blank'))
@@ -108,6 +118,7 @@
:webauthn_update_name_submitted,
success: false,
configuration_id: configuration.id.to_s,
+ platform_authenticator: false,
error_details: { name: { blank: true } },
)
end
@@ -146,7 +157,7 @@
it 'responds with successful result' do
expect(response).to redirect_to(account_path)
- expect(flash[:success]).to eq(t('two_factor_authentication.webauthn_platform.deleted'))
+ expect(flash[:success]).to eq(t('two_factor_authentication.webauthn_roaming.deleted'))
end
it 'logs the submission attempt' do
@@ -156,6 +167,7 @@
:webauthn_delete_submitted,
success: true,
configuration_id: configuration.id.to_s,
+ platform_authenticator: false,
error_details: nil,
)
end
@@ -211,6 +223,7 @@
:webauthn_delete_submitted,
success: false,
configuration_id: configuration.id.to_s,
+ platform_authenticator: false,
error_details: { configuration_id: { only_method: true } },
)
end
diff --git a/spec/controllers/users/webauthn_setup_controller_spec.rb b/spec/controllers/users/webauthn_setup_controller_spec.rb
index efa3a03fbe9..483c02b6cdc 100644
--- a/spec/controllers/users/webauthn_setup_controller_spec.rb
+++ b/spec/controllers/users/webauthn_setup_controller_spec.rb
@@ -156,6 +156,7 @@
success: true,
error_details: nil,
configuration_id: webauthn_configuration.id.to_s,
+ platform_authenticator: false,
)
end
@@ -165,6 +166,22 @@
delete :delete, params: { id: webauthn_configuration.id }
end
+
+ context 'when authenticator is the sole authentication method' do
+ let(:user) { create(:user) }
+
+ it 'tracks the delete in analytics' do
+ delete :delete, params: { id: webauthn_configuration.id }
+
+ expect(@analytics).to have_logged_event(
+ :webauthn_delete_submitted,
+ success: false,
+ error_details: nil,
+ configuration_id: webauthn_configuration.id.to_s,
+ platform_authenticator: nil,
+ )
+ end
+ end
end
describe 'show_delete' do
diff --git a/spec/features/event_disavowal_spec.rb b/spec/features/event_disavowal_spec.rb
index 661bedc7130..41fc36ea09c 100644
--- a/spec/features/event_disavowal_spec.rb
+++ b/spec/features/event_disavowal_spec.rb
@@ -131,6 +131,24 @@
expect(page.current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms))
end
+ scenario 'disavowing an event with javascript enabled', :js do
+ perform_disavowable_password_reset
+
+ open_last_email
+ click_email_link_matching(%r{events/disavow})
+
+ expect(page).to have_content(t('headings.passwords.change'))
+
+ fill_in t('forms.passwords.edit.labels.password'), with: 'abc'
+
+ expect(page).to have_content t('zxcvbn.feedback.sequences_like_abc_or_6543_are_easy_to_guess')
+
+ fill_in t('forms.passwords.edit.labels.password'), with: 'NewVal!dPassw0rd'
+ click_button t('forms.passwords.edit.buttons.submit')
+
+ expect(page).to have_content(t('devise.passwords.updated_not_active'))
+ end
+
def submit_prefilled_otp_code(user, delivery_preference)
expect(current_path).
to eq login_two_factor_path(otp_delivery_preference: delivery_preference)
diff --git a/spec/features/saml/saml_spec.rb b/spec/features/saml/saml_spec.rb
index 5cb5a3c1a84..a8870235201 100644
--- a/spec/features/saml/saml_spec.rb
+++ b/spec/features/saml/saml_spec.rb
@@ -34,17 +34,11 @@
sp.save
end
- it 'returns the user to the account page after authentication' do
- expect(page).
- to have_link t('links.back_to_sp', sp: sp.friendly_name),
- href: return_to_sp_cancel_path(step: :authentication)
-
+ it 'returns a 403' do
sign_in_via_branded_page(user)
click_submit_default
- click_agree_and_continue
- click_submit_default
- expect(current_url).to eq account_url
+ expect(page.status_code).to eq 403
end
end
diff --git a/spec/features/users/user_profile_spec.rb b/spec/features/users/user_profile_spec.rb
index 3c134230883..edc060d8b99 100644
--- a/spec/features/users/user_profile_spec.rb
+++ b/spec/features/users/user_profile_spec.rb
@@ -123,7 +123,7 @@
with: 'this is a great sentence'
expect(page).to have_content(t('instructions.password.strength.intro'))
- expect(page).to have_content t('instructions.password.strength.v')
+ expect(page).to have_content t('instructions.password.strength.4')
check t('components.password_toggle.toggle_label')
diff --git a/spec/features/visitors/set_password_spec.rb b/spec/features/visitors/set_password_spec.rb
index 13556b8c812..6c7edfe9d99 100644
--- a/spec/features/visitors/set_password_spec.rb
+++ b/spec/features/visitors/set_password_spec.rb
@@ -28,7 +28,7 @@
create(:user, :unconfirmed)
confirm_last_user
- expect(page).to have_css('#pw-strength-cntnr.display-none')
+ expect(page).to have_css('lg-password-strength.display-none')
end
context 'password strength indicator when JS is on', js: true do
@@ -42,13 +42,13 @@
fill_in t('forms.password'), with: 'password'
expect(page).to have_content(t('instructions.password.strength.intro'))
- expect(page).to have_content t('instructions.password.strength.i')
+ expect(page).to have_content t('instructions.password.strength.0')
fill_in t('forms.password'), with: '123456789'
expect(page).to have_content t('zxcvbn.feedback.this_is_a_top_10_common_password')
fill_in t('forms.password'), with: 'this is a great sentence'
- expect(page).to have_content t('instructions.password.strength.v')
+ expect(page).to have_content t('instructions.password.strength.4')
fill_in t('forms.password'), with: ':b/}6tT#,'
expect(page).to have_content t('errors.attributes.password.too_short.other', count: 12)
diff --git a/spec/features/webauthn/management_spec.rb b/spec/features/webauthn/management_spec.rb
index f7ea5e8bbf0..20e91e42226 100644
--- a/spec/features/webauthn/management_spec.rb
+++ b/spec/features/webauthn/management_spec.rb
@@ -48,9 +48,9 @@ def expect_webauthn_platform_setup_error
end
context 'with webauthn roaming associations' do
- it 'displays the user supplied names of the security keys' do
- webauthn_config1 = create(:webauthn_configuration, user: user)
- webauthn_config2 = create(:webauthn_configuration, user: user)
+ it 'displays the user supplied names of the platform authenticators' do
+ webauthn_config1 = create(:webauthn_configuration, user:)
+ webauthn_config2 = create(:webauthn_configuration, user:)
sign_in_and_2fa_user(user)
visit account_two_factor_authentication_path
@@ -59,15 +59,15 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content webauthn_config2.name
end
- it 'allows the user to setup another key' do
+ it 'allows the user to setup another roaming authenticator' do
mock_webauthn_setup_challenge
- create(:webauthn_configuration, user: user)
+ create(:webauthn_configuration, user:)
sign_in_and_2fa_user(user)
visit_webauthn_setup
- expect(current_path).to eq webauthn_setup_path
+ expect(page).to have_current_path webauthn_setup_path
fill_in_nickname_and_click_continue
mock_press_button_on_hardware_key_on_setup
@@ -75,41 +75,147 @@ def expect_webauthn_platform_setup_error
expect_webauthn_setup_success
end
- it 'allows user to delete security key when another 2FA option is set up' do
- webauthn_config = create(:webauthn_configuration, user: user)
+ it 'allows user to delete a roaming authenticator when another 2FA option is set up' do
+ webauthn_config = create(:webauthn_configuration, user:)
+ name = webauthn_config.name
sign_in_and_2fa_user(user)
visit account_two_factor_authentication_path
- expect(page).to have_content webauthn_config.name
+ expect(page).to have_content(name)
- click_link t('account.index.webauthn_delete')
+ click_link(
+ [
+ t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ name,
+ ].join(': '),
+ )
- expect(current_path).to eq webauthn_setup_delete_path
+ expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
- click_button t('account.index.webauthn_confirm_delete')
+ click_button t('two_factor_authentication.webauthn_roaming.delete')
- expect(page).to_not have_content webauthn_config.name
- expect(page).to have_content t('notices.webauthn_deleted')
+ expect(page).to_not have_content(name)
+ expect(page).to have_content(t('two_factor_authentication.webauthn_roaming.deleted'))
expect(user.reload.webauthn_configurations.empty?).to eq(true)
end
- it 'prevents a user from deleting the last key' do
- webauthn_config = create(:webauthn_configuration, user: user)
+ it 'allows user to rename a roaming authenticator' do
+ webauthn_config = create(:webauthn_configuration, user:)
+ name = webauthn_config.name
+
+ sign_in_and_2fa_user(user)
+ visit account_two_factor_authentication_path
+
+ expect(page).to have_content(name)
+
+ click_link(
+ [
+ t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ name,
+ ].join(': '),
+ )
+
+ expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
+ expect(page).to have_field(
+ t('two_factor_authentication.webauthn_roaming.nickname'),
+ with: name,
+ )
+
+ fill_in t('two_factor_authentication.webauthn_roaming.nickname'), with: 'new name'
+
+ click_button t('two_factor_authentication.webauthn_roaming.change_nickname')
+
+ expect(page).to have_content('new name')
+ expect(page).to have_content(t('two_factor_authentication.webauthn_roaming.renamed'))
+ end
+
+ it 'allows the user to cancel deletion of the roaming authenticator' do
+ webauthn_config = create(:webauthn_configuration, user:)
+ name = webauthn_config.name
+
+ sign_in_and_2fa_user(user)
+ visit account_two_factor_authentication_path
+
+ expect(page).to have_content(name)
+
+ click_link(
+ [
+ t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ name,
+ ].join(': '),
+ )
+
+ expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
+
+ click_link t('links.cancel')
+
+ expect(page).to have_content(name)
+ end
+
+ it 'prevents a user from deleting the last roaming authenticator' do
+ webauthn_config = create(:webauthn_configuration, user:)
+ name = webauthn_config.name
sign_in_and_2fa_user(user)
PhoneConfiguration.first.update(mfa_enabled: false)
user.backup_code_configurations.destroy_all
- visit account_two_factor_authentication_path
- expect(current_path).to eq account_two_factor_authentication_path
+ expect(page).to have_content(name)
+
+ click_link(
+ [
+ t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ name,
+ ].join(': '),
+ )
- expect(page).to have_content webauthn_config.name
- expect(page).to_not have_link t('account.index.webauthn_delete')
+ expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
+
+ click_button t('two_factor_authentication.webauthn_roaming.delete')
+
+ expect(page).to have_current_path(edit_webauthn_path(id: webauthn_config.id))
+ expect(page).to have_content(t('errors.manage_authenticator.remove_only_method_error'))
+ expect(user.reload.webauthn_configurations.empty?).to eq(false)
+ end
+
+ it 'requires a user to use a unique name when renaming' do
+ webauthn_config = create(:webauthn_configuration, user:)
+ create(:webauthn_configuration, user:, name: 'existing')
+ name = webauthn_config.name
+
+ sign_in_and_2fa_user(user)
+
+ expect(page).to have_content(name)
+
+ click_link(
+ [
+ t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ name,
+ ].join(': '),
+ )
+
+ expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
+ expect(page).to have_field(
+ t('two_factor_authentication.webauthn_roaming.nickname'),
+ with: name,
+ )
+
+ fill_in t('two_factor_authentication.webauthn_roaming.nickname'), with: 'existing'
+
+ click_button t('two_factor_authentication.webauthn_roaming.change_nickname')
+
+ expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
+ expect(page).to have_field(
+ t('two_factor_authentication.webauthn_roaming.nickname'),
+ with: 'existing',
+ )
+ expect(page).to have_content(t('errors.manage_authenticator.unique_name_error'))
+ expect(page).to have_css('.usa-input--error')
end
it 'gives an error if name is taken and stays on the configuration screen' do
- webauthn_config = create(:webauthn_configuration, user: user)
+ webauthn_config = create(:webauthn_configuration, user:)
mock_webauthn_setup_challenge
sign_in_and_2fa_user(user)
@@ -126,6 +232,120 @@ def expect_webauthn_platform_setup_error
expect(current_path).to eq webauthn_setup_path
expect(page).to have_content t('errors.webauthn_setup.unique_name')
end
+
+ context 'with javascript enabled', :js do
+ it 'allows user to delete a roaming authenticator when another 2FA option is set up' do
+ webauthn_config = create(:webauthn_configuration, user:)
+ name = webauthn_config.name
+
+ sign_in_and_2fa_user(user)
+ visit account_two_factor_authentication_path
+
+ expect(page).to have_content(name)
+
+ click_button(
+ [
+ t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ name,
+ ].join(': '),
+ )
+
+ # Verify user can cancel deletion. There's an implied assertion here that the button becomes
+ # clickable again, since the following confirmation occurs upon successive button click.
+ dismiss_confirm(wait: 5) { click_button t('components.manageable_authenticator.delete') }
+
+ # Verify user confirms deletion
+ accept_confirm(wait: 5) { click_button t('components.manageable_authenticator.delete') }
+
+ expect(page).to have_content(
+ t('two_factor_authentication.webauthn_roaming.deleted'),
+ wait: 5,
+ )
+ expect(page).to_not have_content(name)
+ expect(user.reload.webauthn_configurations.empty?).to eq(true)
+ end
+
+ it 'allows user to rename a roaming authenticator' do
+ webauthn_config = create(:webauthn_configuration, user:)
+ name = webauthn_config.name
+
+ sign_in_and_2fa_user(user)
+ visit account_two_factor_authentication_path
+
+ expect(page).to have_content(name)
+
+ click_button(
+ [
+ t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ name,
+ ].join(': '),
+ )
+ click_button t('components.manageable_authenticator.rename')
+
+ expect(page).to have_field(t('components.manageable_authenticator.nickname'), with: name)
+
+ fill_in t('components.manageable_authenticator.nickname'), with: 'new name'
+
+ click_button t('components.manageable_authenticator.save')
+
+ expect(page).to have_content(
+ t('two_factor_authentication.webauthn_roaming.renamed'),
+ wait: 5,
+ )
+ expect(page).to have_content('new name')
+ end
+
+ it 'prevents a user from deleting the last roaming authenticator', allow_browser_log: true do
+ webauthn_config = create(:webauthn_configuration, user:)
+ name = webauthn_config.name
+
+ sign_in_and_2fa_user(user)
+ PhoneConfiguration.first.update(mfa_enabled: false)
+ user.backup_code_configurations.destroy_all
+
+ expect(page).to have_content(name)
+
+ click_button(
+ [
+ t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ name,
+ ].join(': '),
+ )
+ accept_confirm(wait: 5) { click_button t('components.manageable_authenticator.delete') }
+
+ expect(page).to have_content(
+ t('errors.manage_authenticator.remove_only_method_error'),
+ wait: 5,
+ )
+ expect(user.reload.webauthn_configurations.empty?).to eq(false)
+ end
+
+ it 'requires a user to use a unique name when renaming', allow_browser_log: true do
+ webauthn_config = create(:webauthn_configuration, user:)
+ create(:webauthn_configuration, user:, name: 'existing')
+ name = webauthn_config.name
+
+ sign_in_and_2fa_user(user)
+
+ expect(page).to have_content(name)
+
+ click_button(
+ [
+ t('two_factor_authentication.webauthn_roaming.manage_accessible_label'),
+ name,
+ ].join(': '),
+ )
+ click_button t('components.manageable_authenticator.rename')
+
+ expect(page).to have_field(t('components.manageable_authenticator.nickname'), with: name)
+
+ fill_in t('components.manageable_authenticator.nickname'), with: 'existing'
+
+ click_button t('components.manageable_authenticator.save')
+
+ expect(page).to have_content(t('errors.manage_authenticator.unique_name_error'), wait: 5)
+ end
+ end
end
context 'with webauthn platform associations' do
@@ -140,7 +360,7 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content webauthn_config2.name
end
- it 'allows the user to setup another key' do
+ it 'allows the user to setup another platform authenticator' do
mock_webauthn_setup_challenge
create(:webauthn_configuration, :platform_authenticator, user:)
@@ -175,11 +395,10 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
click_link(
- format(
- '%s: %s',
+ [
t('two_factor_authentication.webauthn_platform.manage_accessible_label'),
name,
- ),
+ ].join(': '),
)
expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
@@ -201,11 +420,10 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
click_link(
- format(
- '%s: %s',
+ [
t('two_factor_authentication.webauthn_platform.manage_accessible_label'),
name,
- ),
+ ].join(': '),
)
expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
@@ -232,11 +450,10 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
click_link(
- format(
- '%s: %s',
+ [
t('two_factor_authentication.webauthn_platform.manage_accessible_label'),
name,
- ),
+ ].join(': '),
)
expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
@@ -246,7 +463,7 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
end
- it 'prevents a user from deleting the last key' do
+ it 'prevents a user from deleting the last platform authenticator' do
webauthn_config = create(:webauthn_configuration, :platform_authenticator, user:)
name = webauthn_config.name
@@ -257,11 +474,10 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
click_link(
- format(
- '%s: %s',
+ [
t('two_factor_authentication.webauthn_platform.manage_accessible_label'),
name,
- ),
+ ].join(': '),
)
expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
@@ -283,11 +499,10 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
click_link(
- format(
- '%s: %s',
+ [
t('two_factor_authentication.webauthn_platform.manage_accessible_label'),
name,
- ),
+ ].join(': '),
)
expect(current_path).to eq(edit_webauthn_path(id: webauthn_config.id))
@@ -306,6 +521,7 @@ def expect_webauthn_platform_setup_error
with: 'existing',
)
expect(page).to have_content(t('errors.manage_authenticator.unique_name_error'))
+ expect(page).to have_css('.usa-input--error')
end
it 'gives an error if name is taken and stays on the configuration screen' do
@@ -338,11 +554,10 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
click_button(
- format(
- '%s: %s',
+ [
t('two_factor_authentication.webauthn_platform.manage_accessible_label'),
name,
- ),
+ ].join(': '),
)
# Verify user can cancel deletion. There's an implied assertion here that the button becomes
@@ -370,11 +585,10 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
click_button(
- format(
- '%s: %s',
+ [
t('two_factor_authentication.webauthn_platform.manage_accessible_label'),
name,
- ),
+ ].join(': '),
)
click_button t('components.manageable_authenticator.rename')
@@ -391,7 +605,7 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content('new name')
end
- it 'prevents a user from deleting the last key', allow_browser_log: true do
+ it 'prevents a user from deleting the last platform authenticator', allow_browser_log: true do
webauthn_config = create(:webauthn_configuration, :platform_authenticator, user:)
name = webauthn_config.name
@@ -402,11 +616,10 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
click_button(
- format(
- '%s: %s',
+ [
t('two_factor_authentication.webauthn_platform.manage_accessible_label'),
name,
- ),
+ ].join(': '),
)
accept_confirm(wait: 5) { click_button t('components.manageable_authenticator.delete') }
@@ -427,11 +640,10 @@ def expect_webauthn_platform_setup_error
expect(page).to have_content(name)
click_button(
- format(
- '%s: %s',
+ [
t('two_factor_authentication.webauthn_platform.manage_accessible_label'),
name,
- ),
+ ].join(': '),
)
click_button t('components.manageable_authenticator.rename')
diff --git a/spec/forms/two_factor_authentication/webauthn_delete_form_spec.rb b/spec/forms/two_factor_authentication/webauthn_delete_form_spec.rb
index eb28c95004a..f0c210aceb9 100644
--- a/spec/forms/two_factor_authentication/webauthn_delete_form_spec.rb
+++ b/spec/forms/two_factor_authentication/webauthn_delete_form_spec.rb
@@ -14,7 +14,21 @@
it 'returns a successful result' do
expect(result.success?).to eq(true)
- expect(result.to_h).to eq(success: true, configuration_id:)
+ expect(result.to_h).to eq(
+ success: true,
+ configuration_id:,
+ platform_authenticator: false,
+ )
+ end
+
+ context 'with platform authenticator' do
+ let(:configuration) do
+ create(:webauthn_configuration, :platform_authenticator, user:)
+
+ it 'includes platform authenticator detail in result' do
+ expect(result.to_h[:platform_authenticator]).to eq(true)
+ end
+ end
end
context 'with blank configuration' do
@@ -28,6 +42,7 @@
configuration_id: { configuration_not_found: true },
},
configuration_id:,
+ platform_authenticator: nil,
)
end
end
@@ -43,6 +58,7 @@
configuration_id: { configuration_not_found: true },
},
configuration_id:,
+ platform_authenticator: nil,
)
end
end
@@ -58,6 +74,7 @@
configuration_id: { configuration_not_found: true },
},
configuration_id:,
+ platform_authenticator: nil,
)
end
end
@@ -74,8 +91,19 @@
configuration_id: { only_method: true },
},
configuration_id:,
+ platform_authenticator: false,
)
end
+
+ context 'with platform authenticator' do
+ let(:configuration) do
+ create(:webauthn_configuration, :platform_authenticator, user:)
+
+ it 'includes platform authenticator detail in result' do
+ expect(result.to_h[:platform_authenticator]).to eq(true)
+ end
+ end
+ end
end
end
diff --git a/spec/forms/two_factor_authentication/webauthn_update_form_spec.rb b/spec/forms/two_factor_authentication/webauthn_update_form_spec.rb
index 8f85687ecbd..4d371bd7822 100644
--- a/spec/forms/two_factor_authentication/webauthn_update_form_spec.rb
+++ b/spec/forms/two_factor_authentication/webauthn_update_form_spec.rb
@@ -13,7 +13,21 @@
it 'returns a successful result' do
expect(result.success?).to eq(true)
- expect(result.to_h).to eq(success: true, configuration_id:)
+ expect(result.to_h).to eq(
+ success: true,
+ configuration_id:,
+ platform_authenticator: false,
+ )
+ end
+
+ context 'with platform authenticator' do
+ let(:configuration) do
+ create(:webauthn_configuration, :platform_authenticator, user:, name: original_name)
+
+ it 'includes platform authenticator detail in result' do
+ expect(result.to_h[:platform_authenticator]).to eq(true)
+ end
+ end
end
it 'saves the new name' do
@@ -33,6 +47,7 @@
configuration_id: { configuration_not_found: true },
},
configuration_id:,
+ platform_authenticator: nil,
)
end
end
@@ -48,6 +63,7 @@
configuration_id: { configuration_not_found: true },
},
configuration_id:,
+ platform_authenticator: nil,
)
end
end
@@ -63,6 +79,7 @@
configuration_id: { configuration_not_found: true },
},
configuration_id:,
+ platform_authenticator: nil,
)
end
@@ -86,6 +103,7 @@
name: { blank: true },
},
configuration_id:,
+ platform_authenticator: false,
)
end
@@ -96,6 +114,16 @@
expect(configuration.reload.name).to eq(original_name)
end
+
+ context 'with platform authenticator' do
+ let(:configuration) do
+ create(:webauthn_configuration, :platform_authenticator, user:, name: original_name)
+
+ it 'includes platform authenticator detail in result' do
+ expect(result.to_h[:platform_authenticator]).to eq(true)
+ end
+ end
+ end
end
context 'with duplicate name' do
@@ -111,6 +139,7 @@
name: { duplicate: true },
},
configuration_id:,
+ platform_authenticator: false,
)
end
@@ -121,6 +150,16 @@
expect(configuration.reload.name).to eq(original_name)
end
+
+ context 'with platform authenticator' do
+ let(:configuration) do
+ create(:webauthn_configuration, :platform_authenticator, user:, name: original_name)
+
+ it 'includes platform authenticator detail in result' do
+ expect(result.to_h[:platform_authenticator]).to eq(true)
+ end
+ end
+ end
end
end
diff --git a/spec/forms/webauthn_setup_form_spec.rb b/spec/forms/webauthn_setup_form_spec.rb
index e467c404ec0..284b5811446 100644
--- a/spec/forms/webauthn_setup_form_spec.rb
+++ b/spec/forms/webauthn_setup_form_spec.rb
@@ -246,6 +246,10 @@
end
end
context 'webauthn_platform' do
+ let(:params) do
+ super().merge(platform_authenticator: true, transports: 'internal,hybrid')
+ end
+
context 'with one platform authenticator with the same name' do
let(:user) do
user = create(:user)
@@ -257,12 +261,7 @@
)
user
end
- let(:params) do
- super().merge(
- platform_authenticator: true,
- transports: 'internal,hybrid',
- )
- end
+
it 'adds a new platform device with the same existing name and appends a (1)' do
result = subject.submit(protocol, params)
expect(result.extra[:multi_factor_auth_method]).to eq 'webauthn_platform'
@@ -276,37 +275,26 @@
end
context 'with two existing platform authenticators one with the same name' do
- let(:user) do
- user = create(:user)
- user.webauthn_configurations << create(
- :webauthn_configuration,
- name: device_name,
- platform_authenticator: true,
- transports: ['internal', 'hybrid'],
- )
- user.webauthn_configurations << create(
- :webauthn_configuration,
- name: device_name,
- platform_authenticator: true,
- transports: ['internal', 'hybrid'],
- )
- user
- end
- let(:params) do
- super().merge(
- platform_authenticator: true,
- transports: 'internal,hybrid',
+ let!(:user) do
+ create(
+ :user,
+ webauthn_configurations: create_list(
+ :webauthn_configuration,
+ 2,
+ name: device_name,
+ platform_authenticator: true,
+ transports: ['internal', 'hybrid'],
+ ),
)
end
+
it 'adds a second new platform device with the same existing name and appends a (2)' do
result = subject.submit(protocol, params)
- expect(result.extra[:multi_factor_auth_method]).to eq 'webauthn_platform'
+
+ expect(result.success?).to eq(true)
expect(user.webauthn_configurations.platform_authenticators.count).to eq(3)
- expect(
- user.webauthn_configurations.platform_authenticators[2].name,
- ).
+ expect(user.webauthn_configurations.platform_authenticators.last.name).
to eq("#{device_name} (2)")
- expect(result.to_h[:success]).to eq(true)
end
end
end
diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb
index aea5b92134e..d5648c3cb64 100644
--- a/spec/i18n_spec.rb
+++ b/spec/i18n_spec.rb
@@ -174,14 +174,6 @@ def allowed_untranslated_key?(locale, key)
bad_keys = keys.reject { |key| key =~ /^[a-z0-9_.]+$/ }
expect(bad_keys).to be_empty
end
-
- it 'has only has XML-safe identifiers (keys start with a letter)' do
- keys = flattened_yaml_data.keys
-
- bad_keys = keys.select { |key| key.split('.').any? { |part| part =~ /^[0-9]/ } }
-
- expect(bad_keys).to be_empty
- end
end
it 'has correctly-formatted interpolation values' do
diff --git a/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx b/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx
index dd5a2467c9d..364ed89dafa 100644
--- a/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx
+++ b/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx
@@ -375,7 +375,6 @@ describe('document-capture/components/review-issues-step', () => {
FeatureFlagContext.Provider,
{
value: {
- notReadySectionEnabled: true,
exitQuestionSectionEnabled: true,
},
},
diff --git a/spec/javascript/packs/pw-strength-spec.js b/spec/javascript/packs/pw-strength-spec.js
deleted file mode 100644
index b5b7700cbf0..00000000000
--- a/spec/javascript/packs/pw-strength-spec.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import zxcvbn from 'zxcvbn';
-import { getForbiddenPasswords, getFeedback } from '../../../app/javascript/packs/pw-strength';
-
-describe('pw-strength', () => {
- describe('getForbiddenPasswords', () => {
- it('returns empty array if given argument is null', () => {
- const element = null;
- const result = getForbiddenPasswords(element);
-
- expect(result).to.deep.equal([]);
- });
-
- it('returns empty array if element has absent dataset value', () => {
- const element = document.createElement('span');
- const result = getForbiddenPasswords(element);
-
- expect(result).to.deep.equal([]);
- });
-
- it('returns empty array if element has invalid dataset value', () => {
- const element = document.createElement('span');
- element.setAttribute('data-forbidden', 'nil');
- const result = getForbiddenPasswords(element);
-
- expect(result).to.deep.equal([]);
- });
-
- it('parsed array of forbidden passwords', () => {
- const element = document.createElement('span');
- element.setAttribute('data-forbidden', '["foo","bar","baz"]');
- const result = getForbiddenPasswords(element);
-
- expect(result).to.be.deep.equal(['foo', 'bar', 'baz']);
- });
- });
-
- describe('getFeedback', () => {
- const EMPTY_RESULT = ' ';
- const MINIMUM_LENGTH = 12;
- const FORBIDDEN_PASSWORDS = ['gsa', 'Login.gov'];
-
- it('returns an empty result for empty password', () => {
- const z = zxcvbn('');
-
- expect(getFeedback(z, MINIMUM_LENGTH, FORBIDDEN_PASSWORDS)).to.equal(EMPTY_RESULT);
- });
-
- it('returns an empty result for a strong password', () => {
- const z = zxcvbn('!Juq2Uk2**RBEsA8');
-
- expect(getFeedback(z, MINIMUM_LENGTH, FORBIDDEN_PASSWORDS)).to.equal(EMPTY_RESULT);
- });
-
- it('returns feedback for a weak password', () => {
- const z = zxcvbn('password');
-
- expect(getFeedback(z, MINIMUM_LENGTH, FORBIDDEN_PASSWORDS)).to.equal(
- 'zxcvbn.feedback.this_is_a_top_10_common_password',
- );
- });
-
- it('shows feedback when a password is too short', () => {
- const z = zxcvbn('_3G%JMyR"');
-
- expect(getFeedback(z, MINIMUM_LENGTH, FORBIDDEN_PASSWORDS)).to.equal(
- 'errors.attributes.password.too_short.other',
- { count: MINIMUM_LENGTH },
- );
- });
-
- it('shows feedback when a user enters a forbidden password', () => {
- const z = zxcvbn('gsa');
-
- expect(getFeedback(z, MINIMUM_LENGTH, FORBIDDEN_PASSWORDS)).to.equal(
- 'errors.attributes.password.avoid_using_phrases_that_are_easily_guessed',
- );
- });
- });
-});
diff --git a/spec/presenters/two_factor_authentication/webauthn_edit_presenter_spec.rb b/spec/presenters/two_factor_authentication/webauthn_edit_presenter_spec.rb
new file mode 100644
index 00000000000..c52957eac15
--- /dev/null
+++ b/spec/presenters/two_factor_authentication/webauthn_edit_presenter_spec.rb
@@ -0,0 +1,103 @@
+require 'rails_helper'
+
+RSpec.describe TwoFactorAuthentication::WebauthnEditPresenter do
+ let(:configuration) { build(:webauthn_configuration) }
+
+ subject(:presenter) { described_class.new(configuration:) }
+
+ describe '#heading' do
+ subject(:heading) { presenter.heading }
+
+ context 'with roaming authenticator' do
+ let(:configuration) { build(:webauthn_configuration) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_roaming.edit_heading')) }
+ end
+
+ context 'with platform authenticator' do
+ let(:configuration) { build(:webauthn_configuration, :platform_authenticator) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_platform.edit_heading')) }
+ end
+ end
+
+ describe '#nickname_field_label' do
+ subject(:heading) { presenter.nickname_field_label }
+
+ context 'with roaming authenticator' do
+ let(:configuration) { build(:webauthn_configuration) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_roaming.nickname')) }
+ end
+
+ context 'with platform authenticator' do
+ let(:configuration) { build(:webauthn_configuration, :platform_authenticator) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_platform.nickname')) }
+ end
+ end
+
+ describe '#rename_button_label' do
+ subject(:heading) { presenter.rename_button_label }
+
+ context 'with roaming authenticator' do
+ let(:configuration) { build(:webauthn_configuration) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_roaming.change_nickname')) }
+ end
+
+ context 'with platform authenticator' do
+ let(:configuration) { build(:webauthn_configuration, :platform_authenticator) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_platform.change_nickname')) }
+ end
+ end
+
+ describe '#delete_button_label' do
+ subject(:heading) { presenter.delete_button_label }
+
+ context 'with roaming authenticator' do
+ let(:configuration) { build(:webauthn_configuration) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_roaming.delete')) }
+ end
+
+ context 'with platform authenticator' do
+ let(:configuration) { build(:webauthn_configuration, :platform_authenticator) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_platform.delete')) }
+ end
+ end
+
+ describe '#rename_success_alert_text' do
+ subject(:heading) { presenter.rename_success_alert_text }
+
+ context 'with roaming authenticator' do
+ let(:configuration) { build(:webauthn_configuration) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_roaming.renamed')) }
+ end
+
+ context 'with platform authenticator' do
+ let(:configuration) { build(:webauthn_configuration, :platform_authenticator) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_platform.renamed')) }
+ end
+ end
+
+ describe '#delete_success_alert_text' do
+ subject(:heading) { presenter.delete_success_alert_text }
+
+ context 'with roaming authenticator' do
+ let(:configuration) { build(:webauthn_configuration) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_roaming.deleted')) }
+ end
+
+ context 'with platform authenticator' do
+ let(:configuration) { build(:webauthn_configuration, :platform_authenticator) }
+
+ it { expect(heading).to eq(t('two_factor_authentication.webauthn_platform.deleted')) }
+ end
+ end
+end
diff --git a/spec/services/push_notification/http_push_spec.rb b/spec/services/push_notification/http_push_spec.rb
index c4d0cf79759..6e3e08b2c8b 100644
--- a/spec/services/push_notification/http_push_spec.rb
+++ b/spec/services/push_notification/http_push_spec.rb
@@ -5,8 +5,8 @@
let(:user) { create(:user) }
- let(:sp_with_push_url) { create(:service_provider, push_notification_url: 'http://foo.bar/push') }
- let(:sp_no_push_url) { create(:service_provider, push_notification_url: nil) }
+ let(:sp_with_push_url) { create(:service_provider, active: true, push_notification_url: 'http://foo.bar/push') }
+ let(:sp_no_push_url) { create(:service_provider, active: true, push_notification_url: nil) }
let!(:sp_with_push_url_identity) do
IdentityLinker.new(user, sp_with_push_url).link_identity
@@ -128,7 +128,7 @@
end
context 'with a timeout when posting to one url' do
- let(:third_sp) { create(:service_provider, push_notification_url: 'http://sp.url/push') }
+ let(:third_sp) { create(:service_provider, active: true, push_notification_url: 'http://sp.url/push') }
before do
IdentityLinker.new(user, third_sp).link_identity
@@ -182,6 +182,16 @@
end
end
+ context 'when a service provider is no longer active' do
+ before { sp_with_push_url.update!(active: false) }
+
+ it 'does not notify that SP' do
+ deliver
+
+ expect(WebMock).not_to have_requested(:get, sp_with_push_url.push_notification_url)
+ end
+ end
+
context 'when a user has revoked access to an SP' do
before do
identity = user.identities.find_by(service_provider: sp_with_push_url.issuer)
diff --git a/spec/views/accounts/_webauthn_roaming.html.erb_spec.rb b/spec/views/accounts/_webauthn_roaming.html.erb_spec.rb
new file mode 100644
index 00000000000..27f4375a3db
--- /dev/null
+++ b/spec/views/accounts/_webauthn_roaming.html.erb_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+RSpec.describe 'accounts/_webauthn_roaming.html.erb' do
+ let(:user) do
+ create(
+ :user,
+ webauthn_configurations: create_list(:webauthn_configuration, 2),
+ )
+ end
+ let(:user_session) { { auth_events: [] } }
+
+ subject(:rendered) { render partial: 'accounts/webauthn_roaming' }
+
+ before do
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:user_session).and_return(user_session)
+ end
+
+ it 'renders a list of roaming authenticators' do
+ expect(rendered).to have_selector('[role="list"] [role="list-item"]', count: 2)
+ end
+end
diff --git a/spec/views/devise/shared/_password_strength.html.erb_spec.rb b/spec/views/devise/shared/_password_strength.html.erb_spec.rb
deleted file mode 100644
index 7d04b321dc9..00000000000
--- a/spec/views/devise/shared/_password_strength.html.erb_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe 'devise/shared/_password_strength.html.erb' do
- describe 'forbidden attributes' do
- context 'when local is unassigned' do
- before do
- render
- end
-
- it 'omits data-forbidden attribute from strength text tag' do
- expect(rendered).to have_selector('#pw-strength-txt:not([data-forbidden])')
- end
- end
-
- context 'when local is nil' do
- before do
- render 'devise/shared/password_strength', forbidden_passwords: nil
- end
-
- it 'omits data-forbidden attribute from strength text tag' do
- expect(rendered).to have_selector('#pw-strength-txt:not([data-forbidden])')
- end
- end
-
- context 'when local is assigned' do
- before do
- render 'devise/shared/password_strength', forbidden_passwords: ['a', 'b', 'c']
- end
-
- it 'adds JSON-encoded data-forbidden to strength text tag' do
- expect(rendered).to have_selector('#pw-strength-txt[data-forbidden="[\"a\",\"b\",\"c\"]"]')
- end
- end
- end
-end
diff --git a/spec/views/users/webauthn/edit.html.erb_spec.rb b/spec/views/users/webauthn/edit.html.erb_spec.rb
index d5bbc9fb16a..30785d7e71a 100644
--- a/spec/views/users/webauthn/edit.html.erb_spec.rb
+++ b/spec/views/users/webauthn/edit.html.erb_spec.rb
@@ -6,6 +6,7 @@
let(:nickname) { 'Example' }
let(:configuration) { create(:webauthn_configuration, :platform_authenticator, name: nickname) }
let(:user) { create(:user, webauthn_configurations: [configuration]) }
+ let(:presenter) { TwoFactorAuthentication::WebauthnEditPresenter.new(configuration:) }
let(:form) do
TwoFactorAuthentication::WebauthnUpdateForm.new(
user:,
@@ -17,6 +18,7 @@
before do
@form = form
+ @presenter = presenter
end
it 'renders form to update configuration' do
diff --git a/tsconfig.json b/tsconfig.json
index 1c56906d480..e697d8bc798 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -25,10 +25,5 @@
"./*.js",
"scripts"
],
- "exclude": [
- "**/fixtures",
- "**/*.spec.js",
- "app/javascript/packs/pw-strength.js",
- "app/javascript/packs/saml-post.js"
- ]
+ "exclude": ["**/fixtures", "**/*.spec.js", "app/javascript/packs/saml-post.js"]
}
diff --git a/yarn.lock b/yarn.lock
index 24a103d9ec2..5ca9b3b21f0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1684,6 +1684,11 @@
dependencies:
"@types/yargs-parser" "*"
+"@types/zxcvbn@^4.4.4":
+ version "4.4.4"
+ resolved "https://registry.yarnpkg.com/@types/zxcvbn/-/zxcvbn-4.4.4.tgz#987f5fcd87e957097433c476c3a1c91a54f53131"
+ integrity sha512-Tuk4q7q0DnpzyJDI4aMeghGuFu2iS1QAdKpabn8JfbtfGmVDUgvZv1I7mEjP61Bvnp3ljKCC8BE6YYSTNxmvRQ==
+
"@typescript-eslint/eslint-plugin@^6.7.5":
version "6.7.5"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz#f4024b9f63593d0c2b5bd6e4ca027e6f30934d4f"
@@ -4650,10 +4655,10 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
-libphonenumber-js@^1.10.53:
- version "1.10.53"
- resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.53.tgz#8dbfe1355ef1a3d8e13b8d92849f7db7ebddc98f"
- integrity sha512-sDTnnqlWK4vH4AlDQuswz3n4Hx7bIQWTpIcScJX+Sp7St3LXHmfiax/ZFfyYxHmkdCvydOLSuvtAO/XpXiSySw==
+libphonenumber-js@^1.10.54:
+ version "1.10.54"
+ resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.54.tgz#8dfba112f49d1b9c2a160e55f9697f22e50f0841"
+ integrity sha512-P+38dUgJsmh0gzoRDoM4F5jLbyfztkU6PY6eSK6S5HwTi/LPvnwXqVCQZlAy1FxZ5c48q25QhxGQ0pq+WQcSlQ==
lightningcss-darwin-arm64@1.22.0:
version "1.22.0"
@@ -5980,50 +5985,90 @@ safe-regex-test@^1.0.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sass-embedded-darwin-arm64@1.69.2:
- version "1.69.2"
- resolved "https://registry.yarnpkg.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.69.2.tgz#43acf549b5ca753a0fc32dc504e8c2b007c5a503"
- integrity sha512-3e/tRdLTMlmJ45g0vKRDgJJi5P3DO6eS/L7mS89QQcGSTWI5hIS4Yk3K2KkxGwH8QqjkUHAqrVuaD//eBjkGdA==
-
-sass-embedded-darwin-x64@1.69.2:
- version "1.69.2"
- resolved "https://registry.yarnpkg.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.69.2.tgz#703e59cae6511f9b8f9ed5fbaf625dc708ebbab2"
- integrity sha512-kJAqg/0fzJMlJY6lzUaWdkzw7P7HJpIXPgxZ8JTCcYM2Xnkt0/kOMMk3a6xwh5m9uNmCNyd18xaau7BnItiVLw==
-
-sass-embedded-linux-arm64@1.69.2:
- version "1.69.2"
- resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.69.2.tgz#91756876417e1b1637226dc3f006f05f1aa8dcd4"
- integrity sha512-WoNDh/nJ3XNayzSDV7GLOEnW1X6x1zgOBqiXTRQDtQ/Vlf8ydvASUsdcQ4xYDnGlPPkpNgG7XhQjsk1oPSi3Kg==
-
-sass-embedded-linux-arm@1.69.2:
- version "1.69.2"
- resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.69.2.tgz#93156cb92797351e4f3a2386df040ceca17a7600"
- integrity sha512-U/n1qL516VfCkMZ4nuNhbORkyvmoyirloSWECuG07L5/irNr0OlAJZ1gmAoLFaZvTLNC5jepLSh18E5N2yvcOQ==
-
-sass-embedded-linux-ia32@1.69.2:
- version "1.69.2"
- resolved "https://registry.yarnpkg.com/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.69.2.tgz#990976b81846493e7948b18131687c1b83bcfabb"
- integrity sha512-94/3sR1xuIWBX/UqnYLBhxS7Xx6mn0iuVuxQBHv3emdFKCLLrUhUBPT3CaBJPcRtzpeRf9docBBNEbJbk92hyQ==
-
-sass-embedded-linux-x64@1.69.2:
- version "1.69.2"
- resolved "https://registry.yarnpkg.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.69.2.tgz#2baf19761d28bc1c9fea680c3375e00ea18c5e13"
- integrity sha512-0rgdwkVBCNKnmlxHHs5DSp8WzicYXAdFGy5KkpCUngRuDZt8P3bwXD7j0fwLOZx83nbaAI54PmZVUDQBODkvMg==
-
-sass-embedded-win32-ia32@1.69.2:
- version "1.69.2"
- resolved "https://registry.yarnpkg.com/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.69.2.tgz#a24ae3f696efef6418ae44a6af2e8fd2eb0ec658"
- integrity sha512-dBV8Gb9EvqZ4R7VgpYGXaPcqsDDHWznvnY7Cenp6Ub9ookq9X+wXp86JGifQSeisC/sYQPiJafvPrbLZKz4lLQ==
-
-sass-embedded-win32-x64@1.69.2:
- version "1.69.2"
- resolved "https://registry.yarnpkg.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.69.2.tgz#281906403c3ee87d63909542220eda864318c313"
- integrity sha512-TqsJajpboz/qY96R38KF/XSWlt/sdZ+O5Nnkm0os5rbBw3Rv8j/80C8eQ30T4BNinLPrsyBWfH5xJw+Cl9mpWA==
-
-sass-embedded@^1.69.2:
- version "1.69.2"
- resolved "https://registry.yarnpkg.com/sass-embedded/-/sass-embedded-1.69.2.tgz#3d68cf30da4c14b5b2c009d8cb47f9a245c65c6d"
- integrity sha512-B6vRMgpKkWagflo57FXvrpWxizQDJwCB7vquV3WVXzGsEWxRIX4CUWNR/Mq6lMohnkzuUb3ctW54Zrt/716l9Q==
+sass-embedded-android-arm64@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.70.0.tgz#3bdc0591239a0c4c45313e949883a87bb37e07a2"
+ integrity sha512-vMr7fruLUv/VvF7CPVF1z7Bc28a8K9Ps5nyN3UatOj+irxN1LbZIbeQua6neX2eFUsXvcg7hLZwvV3+T96Fhrw==
+
+sass-embedded-android-arm@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.70.0.tgz#e003444e41e1ac2f85cbaa662a7e39df510286e6"
+ integrity sha512-Vog4Z+tsDYGv7m9sZisr/P6KvqDioCMu0cinexdnXhHXReo+X6CFe79yv/zA/Xfq5HtAAmFjGD6CO/nTjoydtw==
+
+sass-embedded-android-ia32@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.70.0.tgz#3051a973b902be3ec66a2b24549273e23484b08f"
+ integrity sha512-RWEJ7sBGBCd101oSBPuePPU8yXb1iB/ME4sRhgI5xjjyIsldiuvX48saW25u1ZqCo2AVA0BTXfWpNJnhKB3b4Q==
+
+sass-embedded-android-x64@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.70.0.tgz#86f4e7d1c91d35443cb9c06566e9a61efb4cf6e1"
+ integrity sha512-u+ijV6AQR/84kjjGb3mp0aibPiXkFKqfmHxqYBMN7h2xV7EM70Yz054nVifaBr8nfC0E8aT/DurSI4nkkQ6Uvg==
+
+sass-embedded-darwin-arm64@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.70.0.tgz#6fca5c9925e70b85ae2008b5cb9d5359c7f0b80d"
+ integrity sha512-qMs08h0nwRA1B/Ieakcg/Y6lcCEnuBnPTNEkFkBlnfj3PFVPTb50wQvDr9JLpcjXWznlBxyFrz1nZM+pXDix7Q==
+
+sass-embedded-darwin-x64@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.70.0.tgz#c618be025ddc47515f4cb7e25c71ac1cb21551c2"
+ integrity sha512-Vf8UQY3IBmsaz9L5DeJDjn19N//1n3rTquH69x29zPCd3zF2gnay38atxIZ+6h7VsZT3C6evm0y58JUJDWN1CA==
+
+sass-embedded-linux-arm64@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.70.0.tgz#24ca8d8ed74611cb9f9efbf76ecd65908387a99e"
+ integrity sha512-PzhBg5xlyXcZ8FgyjqAcVtfaq462l3KeEid2OxrsOzBQgdgJb0La1tAEOpP9jz7YOOTr9A96vm609W9fRLI2Iw==
+
+sass-embedded-linux-arm@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.70.0.tgz#017085c42a2060e36f629c22f9daaa5491dd866f"
+ integrity sha512-U9e+k0XHwubeSIwsBYTNrTVH+0zF/ErSfuHfgTfuvlcKlhoGtFgAb7W8Qfe9FDF6TYTt0fJAJhSV2MdoExsgRA==
+
+sass-embedded-linux-ia32@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.70.0.tgz#cf6bc32beda3c8fc8c6886fb1b796de925c5f201"
+ integrity sha512-UOxTJywQRC/HzFQthlyNWJ07MX8EzKuTgH0N5T3XyXQTNuGeJQ8EPWY9fv1weLCjydVOEwm853F3djtUNmkCtg==
+
+sass-embedded-linux-musl-arm64@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.70.0.tgz#cc81f56d2e14cdf412133a5ae2ed9d62f5b1dcac"
+ integrity sha512-DJl1AV9W7T3SHzXFqAtyjPZy4O2g4AC6QctY5/aM42DTY/xpWOmwUBgsDzDoRbNqP7qDl+GtHLlggrLWCBP9fg==
+
+sass-embedded-linux-musl-arm@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.70.0.tgz#4e22f7dc127b6920a784b0acb9a59efb465f1d1f"
+ integrity sha512-8zudDFpAoNrQDujNYBKkq8nwl4i0jMmXcysO9Ou0llrzdY7Keok2z1aS3IbZy7AvUXtGaeYSHUi5lXdOalJ/QQ==
+
+sass-embedded-linux-musl-ia32@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.70.0.tgz#c0a7278d542870a8114134b9bd1829d6fd16828e"
+ integrity sha512-CcAvT3KPc7cCJfTu1E0HzsAjE/dPQsKaXQD/nsBXNZo081R+lLR2u22wpXM2pnzMNJETRV/pDwozHoYEcPkPqQ==
+
+sass-embedded-linux-musl-x64@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.70.0.tgz#5c0710733cf98b309e72f82b6b17bf53f3c9b230"
+ integrity sha512-g3i9PKmqTxuyrM1Yeju1s4Fj6fzAGyyfzw/LiZZtq0ZZGhJXJMVvEDog/OxQ37eYxWqq9XHFTW2PphMvukVK0g==
+
+sass-embedded-linux-x64@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.70.0.tgz#1ceae4677f7dc9052727d08aa6f54dee2cf6c0b2"
+ integrity sha512-F9F2CA7C6z/ROfF0U/jtYWknbDe9S/TJoCJ5TlHafwS+SrZE1A+Czf2MWJ+8mc2NFiRjYzYxt4Ad29cuc6rrhw==
+
+sass-embedded-win32-ia32@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.70.0.tgz#b67e4a66548deca91eb75a9c467d40b1ad080e95"
+ integrity sha512-TITx2QwJouhMwA0CAjCmnTNeCDL9g2fkLe9z+5rf39OdmcX9CEBrY4CNaO5REnMpgoa+o82u272ZR3oWrsUs8Q==
+
+sass-embedded-win32-x64@1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.70.0.tgz#5a07423cf1370c302e6d44105358f6591b675b1e"
+ integrity sha512-rPe8WUdARhlfgIhGcCTGbTNgd6OppcmjtBrxUNoGs3AENSREQCpaNv5d+HBOMhGUfYgXIHUSiipilFUhLWpsrQ==
+
+sass-embedded@^1.70.0:
+ version "1.70.0"
+ resolved "https://registry.yarnpkg.com/sass-embedded/-/sass-embedded-1.70.0.tgz#558f5e9776c6e4b91d9859a4dd325ac7c2b91391"
+ integrity sha512-1sVSh5MlSdktkwC2zG9WuaVR6j7AlDxadPmZBN0wP4GhznMQTvpwNIAFhAqgjwJYhwdWFOKEdIHSQK4V8K434Q==
dependencies:
"@bufbuild/protobuf" "^1.0.0"
buffer-builder "^0.2.0"
@@ -6032,14 +6077,22 @@ sass-embedded@^1.69.2:
supports-color "^8.1.1"
varint "^6.0.0"
optionalDependencies:
- sass-embedded-darwin-arm64 "1.69.2"
- sass-embedded-darwin-x64 "1.69.2"
- sass-embedded-linux-arm "1.69.2"
- sass-embedded-linux-arm64 "1.69.2"
- sass-embedded-linux-ia32 "1.69.2"
- sass-embedded-linux-x64 "1.69.2"
- sass-embedded-win32-ia32 "1.69.2"
- sass-embedded-win32-x64 "1.69.2"
+ sass-embedded-android-arm "1.70.0"
+ sass-embedded-android-arm64 "1.70.0"
+ sass-embedded-android-ia32 "1.70.0"
+ sass-embedded-android-x64 "1.70.0"
+ sass-embedded-darwin-arm64 "1.70.0"
+ sass-embedded-darwin-x64 "1.70.0"
+ sass-embedded-linux-arm "1.70.0"
+ sass-embedded-linux-arm64 "1.70.0"
+ sass-embedded-linux-ia32 "1.70.0"
+ sass-embedded-linux-musl-arm "1.70.0"
+ sass-embedded-linux-musl-arm64 "1.70.0"
+ sass-embedded-linux-musl-ia32 "1.70.0"
+ sass-embedded-linux-musl-x64 "1.70.0"
+ sass-embedded-linux-x64 "1.70.0"
+ sass-embedded-win32-ia32 "1.70.0"
+ sass-embedded-win32-x64 "1.70.0"
saxes@^6.0.0:
version "6.0.0"
@@ -7315,7 +7368,7 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
-zxcvbn@4.4.2:
+zxcvbn@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30"
integrity sha1-KOwXzwl0PtyrBW3dixsGJizHPDA=