diff --git a/.rubocop.yml b/.rubocop.yml index c04b6c3133b..b949a8fc0e2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -74,7 +74,6 @@ IdentityIdp/ImageSizeLinter: - app/views/shared/_nav_branded.html.erb - app/views/sign_up/completions/show.html.erb - app/views/users/two_factor_authentication_setup/index.html.erb - - app/views/users/webauthn_setup/new.html.erb IdentityIdp/RedirectBackLinter: Enabled: true @@ -1218,6 +1217,10 @@ Style/MultilineMemoization: Style/MultilineWhenThen: Enabled: true +Style/MutableConstant: + Enabled: true + EnforcedStyle: strict + Style/NegatedWhile: Enabled: true diff --git a/Gemfile b/Gemfile index c89b14189e1..72c0c7bdf82 100644 --- a/Gemfile +++ b/Gemfile @@ -65,12 +65,12 @@ gem 'redacted_struct' gem 'redis', '>= 3.2.0' gem 'redis-session-store', github: '18F/redis-session-store', tag: 'v1.0.1-18f' gem 'retries' -gem 'rotp', '~> 6.1' +gem 'rotp', '~> 6.3', '>= 6.3.0' gem 'rqrcode' gem 'ruby-progressbar' gem 'ruby-saml' gem 'safe_target_blank', '>= 1.0.2' -gem 'saml_idp', github: '18F/saml_idp', tag: '0.19.2-18f' +gem 'saml_idp', github: '18F/saml_idp', tag: '0.19.3-18f' gem 'scrypt' gem 'simple_form', '>= 5.0.2' gem 'stringex', require: false @@ -115,7 +115,7 @@ group :development, :test do gem 'psych' gem 'rspec', '~> 3.12.0' gem 'rspec-rails', '~> 6.0' - gem 'rubocop', '~> 1.59.0', require: false + gem 'rubocop', '~> 1.62.0', require: false gem 'rubocop-performance', '~> 1.20.2', require: false gem 'rubocop-rails', '>= 2.5.2', require: false gem 'rubocop-rspec', require: false diff --git a/Gemfile.lock b/Gemfile.lock index d0258b2095f..1b99491b670 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -34,10 +34,10 @@ GIT GIT remote: https://github.com/18F/saml_idp.git - revision: 4c858dab80cfe32081a7e6bd7cd76c43cc3ec778 - tag: 0.19.2-18f + revision: 95369fdd9336773b9983c8de71eb35a8c92e9683 + tag: 0.19.3-18f specs: - saml_idp (0.19.2.pre.18f) + saml_idp (0.19.3.pre.18f) activesupport builder faraday @@ -199,10 +199,10 @@ GEM aws-sigv4 (~> 1.1) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) - axe-core-api (4.7.0) + axe-core-api (4.9.0) dumb_delegator virtus - axe-core-rspec (4.7.0) + axe-core-rspec (4.9.0) axe-core-api dumb_delegator virtus @@ -448,7 +448,7 @@ GEM net-ssh (6.1.0) newrelic_rpm (9.7.0) nio4r (2.7.0) - nokogiri (1.16.2) + nokogiri (1.16.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) openssl (3.0.2) @@ -456,7 +456,7 @@ GEM openssl (> 2.0, < 3.1) orm_adapter (0.5.0) parallel (1.24.0) - parser (3.3.0.0) + parser (3.3.0.5) ast (~> 2.4.1) racc pg (1.5.4) @@ -580,7 +580,7 @@ GEM rgeo-activerecord (7.0.1) activerecord (>= 5.0) rgeo (>= 1.0.0) - rotp (6.2.0) + rotp (6.3.0) rouge (4.2.0) rqrcode (2.1.0) chunky_png (~> 1.0) @@ -611,19 +611,19 @@ GEM rspec-support (3.12.1) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.59.0) + rubocop (1.62.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.4) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.30.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.30.0) - parser (>= 3.2.1.0) + rubocop-ast (1.31.2) + parser (>= 3.3.0.4) rubocop-capybara (2.19.0) rubocop (~> 1.41) rubocop-factory_bot (2.24.0) @@ -836,13 +836,13 @@ DEPENDENCIES redis (>= 3.2.0) redis-session-store! retries - rotp (~> 6.1) + rotp (~> 6.3, >= 6.3.0) rqrcode rspec (~> 3.12.0) rspec-rails (~> 6.0) rspec-retry rspec_junit_formatter - rubocop (~> 1.59.0) + rubocop (~> 1.62.0) rubocop-performance (~> 1.20.2) rubocop-rails (>= 2.5.2) rubocop-rspec diff --git a/app/assets/images/mfa-options/security_key.svg b/app/assets/images/mfa-options/security_key.svg new file mode 100644 index 00000000000..1f84f3fe32b --- /dev/null +++ b/app/assets/images/mfa-options/security_key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/mfa-options/security_key_mobile.svg b/app/assets/images/mfa-options/security_key_mobile.svg new file mode 100644 index 00000000000..e617759ddce --- /dev/null +++ b/app/assets/images/mfa-options/security_key_mobile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/security-key.svg b/app/assets/images/security-key.svg deleted file mode 100644 index 9d305f7f6d0..00000000000 --- a/app/assets/images/security-key.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/stylesheets/email.css.scss b/app/assets/stylesheets/email.css.scss index 2015a1661d0..38d0dfc7a89 100644 --- a/app/assets/stylesheets/email.css.scss +++ b/app/assets/stylesheets/email.css.scss @@ -1,4 +1,7 @@ -@use 'uswds-core' as *; +@use 'uswds-core' as * with ( + $theme-table-border-color: 'base-lighter', + $theme-table-header-background-color: 'base-lightest' +); @use 'variables/app' as *; @use 'variables/email' as *; @@ -198,6 +201,10 @@ h6 { @include u-font('sans', 'md'); } +.font-family-mono { + font-family: monospace; +} + .margin-bottom-0 { @include u-margin-bottom(0); } @@ -269,3 +276,30 @@ h6 { @extend %usa-list-item; } } + +.usa-table { + @include usa-table; + + border-collapse: separate; + border-spacing: 0; + + tbody td { + border-top: 0; + } + + thead th:first-child { + border-top-left-radius: units(0.5); + } + + thead th:last-child { + border-top-right-radius: units(0.5); + } + + tbody tr:last-child td:first-child { + border-bottom-left-radius: units(0.5); + } + + tbody tr:last-child td:last-child { + border-bottom-right-radius: units(0.5); + } +} diff --git a/app/components/README.md b/app/components/README.md index 8bc284f53a5..5a6cd4cfffe 100644 --- a/app/components/README.md +++ b/app/components/README.md @@ -1,13 +1,22 @@ # Components -This folder contains a collection of components implemented using the [ViewComponent gem](https://viewcomponent.org/). Components are reusable user interface elements, and usually comprise of a component class and ERB template. They are similar to partials in many of their use-cases, but have a few advantages in the form of class-based presenter logic, testability, performance, and conveniences for loading JavaScript assets. +This folder contains a collection of components implemented using the [ViewComponent gem](https://viewcomponent.org/). Components are reusable user interface elements, and usually comprise of a component class and ERB template. They are similar to partials in many of their use-cases, but have a few advantages in the form of class-based presenter logic, testability, performance, and conveniences for loading accompanying script and style assets. -Each component must implement a class extending the `BaseComponent` class. If an accompanying `.html.erb` file exists, it will be used as the template for that component. Similarly, if an `.ts` or `.tsx` file exists, the bundle generated from that JavaScript will be loaded automatically any time the component is rendered. +Each component must implement a class extending the `BaseComponent` class. + +Optional files: + +- `.html.erb`: Used as the template for the component +- `.ts`: A corresponding JavaScript bundle will be created and loaded automatically any time the component is rendered +- `.scss`: A corresponding CSS stylesheet will be created and loaded automatically any time the component is rendered + +Example: ``` components/ ├─ example_component.rb ├─ example_component.html.erb +├─ example_component.scss └─ example_component.ts ``` diff --git a/app/components/alert_icon_component.rb b/app/components/alert_icon_component.rb index 74b146c6c83..c85c53db461 100644 --- a/app/components/alert_icon_component.rb +++ b/app/components/alert_icon_component.rb @@ -8,7 +8,7 @@ class AlertIconComponent < BaseComponent personal_key: 'status/personal-key.svg', info_question: 'status/info-question.svg', delete: 'status/delete.svg', - } + }.freeze DEFAULT_WIDTH = 88 DEFAULT_HEIGHT = 88 diff --git a/app/components/one_time_code_input_component.rb b/app/components/one_time_code_input_component.rb index a0bbbc2c57f..a3247b49c19 100644 --- a/app/components/one_time_code_input_component.rb +++ b/app/components/one_time_code_input_component.rb @@ -16,7 +16,7 @@ class OneTimeCodeInputComponent < BaseComponent alias_method :autofocus?, :autofocus # @see https://tc39.es/ecma262/#prod-SyntaxCharacter - JS_REGEXP_SYNTAX_CHARACTER = Regexp.union(%w[^ $ \ . * + ? ( ) [ ] { } |]) + JS_REGEXP_SYNTAX_CHARACTER = Regexp.union(%w[^ $ \ . * + ? ( ) [ ] { } |]).freeze # @param [FormBuilder] form Form builder instance. # @param [Symbol] name Field name. Defaults to `:code`. diff --git a/app/components/security_key_image_component.rb b/app/components/security_key_image_component.rb new file mode 100644 index 00000000000..cefb2a981c3 --- /dev/null +++ b/app/components/security_key_image_component.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class SecurityKeyImageComponent < BaseComponent + attr_reader :tag_options + + def initialize(mobile:, **tag_options) + @mobile = mobile + @tag_options = tag_options + end + + def mobile? + !!@mobile + end + + def call + # rubocop:disable Rails/OutputSafety + Nokogiri::HTML5.fragment(read_svg).tap do |doc| + doc.at_css('svg').tap do |svg| + svg[:class] = css_class + svg[:role] = 'img' + + tag_options.except(:class, :data, :aria).each do |key, value| + svg[key] = value + end + [:data, :aria].each do |prefix| + tag_options[prefix]&.each do |key, value| + svg[:"#{prefix}-#{key}"] = value + end + end + + svg << "
+ <%= t('user_mailer.new_device_sign_in_after_2fa.info_p1', app_name: APP_NAME) %> +
+ ++ <%= t('user_mailer.new_device_sign_in_after_2fa.info_p2') %> +
+ ++ <%= t( + 'user_mailer.new_device_sign_in_after_2fa.info_p3_html', + reset_password_link_html: link_to( + t('user_mailer.new_device_sign_in_after_2fa.reset_password'), + event_disavowal_url(disavowal_token: @disavowal_token), + ), + authentication_methods_link_html: link_to( + t('user_mailer.new_device_sign_in_after_2fa.authentication_methods'), + MarketingSite.help_center_article_url( + category: 'manage-your-account', + article: 'add-or-change-your-authentication-method', + ), + ), + ) %> +
+ +<%= render 'user_mailer/shared/new_device_sign_in_attempts' %> diff --git a/app/views/user_mailer/new_device_sign_in_before_2fa.html.erb b/app/views/user_mailer/new_device_sign_in_before_2fa.html.erb new file mode 100644 index 00000000000..e7f59bcd4f4 --- /dev/null +++ b/app/views/user_mailer/new_device_sign_in_before_2fa.html.erb @@ -0,0 +1,19 @@ ++ <%= t('user_mailer.new_device_sign_in_before_2fa.info_p1_html', count: @failed_times, app_name: APP_NAME) %> +
+ ++ <%= t('user_mailer.new_device_sign_in_before_2fa.info_p2') %> +
+ ++ <%= t( + 'user_mailer.new_device_sign_in_before_2fa.info_p3_html', + reset_password_link_html: link_to( + t('user_mailer.new_device_sign_in_before_2fa.reset_password'), + event_disavowal_url(disavowal_token: @disavowal_token), + ), + ) %> +
+ +<%= render 'user_mailer/shared/new_device_sign_in_attempts' %> diff --git a/app/views/user_mailer/shared/_new_device_sign_in_attempts.html.erb b/app/views/user_mailer/shared/_new_device_sign_in_attempts.html.erb new file mode 100644 index 00000000000..09a5fb83791 --- /dev/null +++ b/app/views/user_mailer/shared/_new_device_sign_in_attempts.html.erb @@ -0,0 +1,26 @@ +<% @events.group_by { |event| IpGeocoder.new(event.device.last_ip).location }.each do |location, events| %> +| + <%= t('user_mailer.new_device_sign_in_attempts.new_sign_in_from', location:) %> + | +
|---|
|
+ <%# i18n-tasks-use t('user_mailer.new_device_sign_in_attempts.events.sign_in_after_2fa') %>
+ <%# i18n-tasks-use t('user_mailer.new_device_sign_in_attempts.events.sign_in_before_2fa') %>
+ <%# i18n-tasks-use t('user_mailer.new_device_sign_in_attempts.events.sign_in_unsuccessful_2fa') %>
+ <%= t(event.event_type, scope: [:user_mailer, :new_device_sign_in_attempts, :events]) %> + <%= EasternTimePresenter.new(event.created_at) %> + |
+
Add a security key that meets the FIDO standard as your - authentication method. You can add as many security keys as you want. To - get started, first give your security key a nickname.
' + intro: Use your physical security key to add an additional layer of protection + to your %{app_name} account to prevent unauthorized access. + learn_more: Learn more about security keys nickname: Security key nickname saving: Saving your credentials … + set_up: Set up security key + step_1: Give it a nickname + step_1a: If you add more than one security key, you’ll know which one is which. + step_2: Insert a security key into your device + step_2_image_alt: A security key being inserted into the right side of a laptop + step_2_image_mobile_alt: A security key being inserted into the bottom of a smart phone + step_3: Set up your security key + step_3a: Click “set up security key” below and follow your browser’s instructions. diff --git a/config/locales/forms/es.yml b/config/locales/forms/es.yml index 95c932fb8f6..5530b4377f4 100644 --- a/config/locales/forms/es.yml +++ b/config/locales/forms/es.yml @@ -133,10 +133,18 @@ es: nickname_hint: Si agrega más dispositivos para desbloquear con la cara o con la huella digital, podrá distinguirlos. webauthn_setup: - continue: Continuar - intro_html: 'Añada una clave de seguridad que cumpla el estándar FIDO como - método de autenticación. Puede añadir tantas claves de seguridad como - desee. Para empezar, primero asigne un apodo a su clave de - seguridad.
' + intro: Utilice su clave de seguridad física para añadir un nivel adicional de + protección a su cuenta de %{app_name} y evitar accesos no autorizados. + learn_more: Obtenga información sobre claves de seguridad nickname: Apodo clave de seguridad saving: Guardando sus credenciales … + set_up: Configure su clave de seguridad + step_1: Darle un apodo + step_1a: Si añade más de una llave de seguridad, sabrá cuál es cuál. + step_2: Inserte una clave de seguridad en su dispositivo + step_2_image_alt: Una llave de seguridad insertada en el lado derecho de una + computadora portátil + step_2_image_mobile_alt: Una llave de seguridad insertada en la parte inferior de un celular + step_3: Configure su clave de seguridad + step_3a: Haga clic en “configurar clave de seguridad” (set up security key) más + abajo y siga las instrucciones de su navegador. diff --git a/config/locales/forms/fr.yml b/config/locales/forms/fr.yml index 08a3c3d74ce..d701464683e 100644 --- a/config/locales/forms/fr.yml +++ b/config/locales/forms/fr.yml @@ -138,10 +138,20 @@ fr: déverrouillage facial ou pour le déverrouillage tactile, vous saurez les reconnaître. webauthn_setup: - continue: Continuer - intro_html: 'Ajoutez une clé de sécurité conforme à la norme FIDO comme - méthode d’authentification. Vous pouvez ajouter autant de clés de - sécurité que vous le souhaitez. Pour commencer, donnez d’abord un surnom - à votre clé de sécurité.
' + intro: Utilisez votre clé de sécurité physique pour ajouter une couche de + protection supplémentaire à votre compte %{app_name} pour empêcher tout + accès non autorisé. + learn_more: En savoir plus sur les clés de sécurité nickname: Pseudo clé de sécurité saving: Enregistrement de vos informations d’identification … + set_up: Configurer votre clé de sécurité + step_1: Donnez-lui un surnom + step_1a: Si vous ajoutez plus d’une clé de sécurité, vous saurez reconnaître + chacune d’entre elles. + step_2: Insérer votre clé de sécurité + step_2_image_alt: Insertion d’une clé de sécurité dans le côté droit d’un + ordinateur portable + step_2_image_mobile_alt: Insertion d’une clé de sécurité dans le bas d’un téléphone intelligent + step_3: Configurer une clé de sécurité + step_3a: Cliquez sur « Configurer votre clé de sécurité » ci-dessous et suivez + les instructions de votre navigateur. diff --git a/config/locales/headings/en.yml b/config/locales/headings/en.yml index f88741dd91d..92bb4db275b 100644 --- a/config/locales/headings/en.yml +++ b/config/locales/headings/en.yml @@ -73,4 +73,4 @@ en: webauthn_platform_setup: new: Add face or touch unlock webauthn_setup: - new: Add your security key + new: Insert your security key diff --git a/config/locales/headings/es.yml b/config/locales/headings/es.yml index 4b98a646142..e951e3ab7af 100644 --- a/config/locales/headings/es.yml +++ b/config/locales/headings/es.yml @@ -73,4 +73,4 @@ es: webauthn_platform_setup: new: Desbloqueo facial o táctil webauthn_setup: - new: Añade tu clave de seguridad + new: Inserte su clave de seguridad diff --git a/config/locales/headings/fr.yml b/config/locales/headings/fr.yml index 7353464d25e..d59540f0ce9 100644 --- a/config/locales/headings/fr.yml +++ b/config/locales/headings/fr.yml @@ -76,4 +76,4 @@ fr: webauthn_platform_setup: new: Déverrouillage facial ou tactile webauthn_setup: - new: Ajoutez votre clé de sécurité + new: Insérer votre clé de sécurité diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index d2cf1b14368..0803ee4201d 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -219,6 +219,32 @@ en: %{contact_link_html}. info: 'Your %{app_name} account was just used to sign in on a new device.' subject: New sign-in with your %{app_name} account + new_device_sign_in_after_2fa: + authentication_methods: authentication methods + info_p1: Your %{app_name} email and password were used to sign-in and + authenticate on a new device. + info_p2: If you recognize this activity, you don’t need to do anything. + info_p3_html: If this wasn’t you, %{reset_password_link_html} and change your + %{authentication_methods_link_html} immediately. + reset_password: reset your password + subject: New sign-in and authentication with your %{app_name} account + new_device_sign_in_attempts: + events: + sign_in_after_2fa: Authenticated + sign_in_before_2fa: Signed in with password + sign_in_unsuccessful_2fa: Failed to authenticate + new_sign_in_from: New sign-in potentially located in %{location} + new_device_sign_in_before_2fa: + info_p1_html: + one: Your %{app_name} email and password were used to sign in from a new device + but failed to authenticate. + other: Your %{app_name} email and password were used to sign in from a new + device but failed to authenticate %{count} times + info_p2: If you recognize this activity, you don’t need to do anything. + info_p3_html: Two-factor authentication protects your account from unauthorized + access. If this wasn’t you, %{reset_password_link_html} immediately. + reset_password: reset your password + subject: New sign-in with your %{app_name} account password_changed: disavowal_link: reset your password help_html: If you did not make this change, %{disavowal_link_html}. For more diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index b7c372edafb..bed4f08e957 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -232,6 +232,35 @@ es: visite el %{app_name_html} %{help_link_html} o el %{contact_link_html}. info: 'Su cuenta %{app_name} acaba de iniciar sesión en un nuevo dispositivo.' subject: Nuevo initio de sesion con su %{app_name} cuenta + new_device_sign_in_after_2fa: + authentication_methods: métodos de autenticación + info_p1: Su correo electrónico y su contraseña de %{app_name} se usaron para + iniciar sesión y para la autenticación desde un nuevo dispositivo. + info_p2: Si reconoce esta actividad, no tiene que hacer nada. + info_p3_html: Si no fue usted, %{reset_password_link_html} y cambie sus + %{authentication_methods_link_html} inmediatamente. + reset_password: restablezca la contraseña + subject: Nuevo inicio de sesión y autenticación con su cuenta de %{app_name} + new_device_sign_in_attempts: + events: + sign_in_after_2fa: Autenticado + sign_in_before_2fa: Inicia sesión con contraseña + sign_in_unsuccessful_2fa: Error al autenticar + new_sign_in_from: Nuevo inicio de sesión potencialmente ubicado en %{location} + new_device_sign_in_before_2fa: + info_p1_html: + one: Su correo electrónico y su contraseña de %{app_name} se usaron para + ingresar desde un nuevo dispositivo, pero la autenticación dio + error. + other: Su correo electrónico y su contraseña de %{app_name} se usaron para + ingresar desde un nuevo dispositivo, pero error al autenticar + %{count} veces + info_p2: Si reconoce esta actividad, no tiene que hacer nada. + info_p3_html: La autenticación de dos factores protege su cuenta de accesos no + autorizados. Si no fue usted, %{reset_password_link_html} + inmediatamente. + reset_password: restablezca la contraseña + subject: Nuevo inicio de sesión con su cuenta de %{app_name} password_changed: disavowal_link: restablecer su contraseña help_html: Si no realizó este cambio, %{disavowal_link_html}. Para más ayuda, diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index b52f7806e2d..6eefb89ad04 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -238,6 +238,35 @@ fr: %{app_name_html} ou %{contact_link_html}. info: 'Votre compte %{app_name} a été connecté sur un nouvel appareil.' subject: Nouvelle connexion avec votre compte %{app_name} + new_device_sign_in_after_2fa: + authentication_methods: méthodes d’authentification + info_p1: Votre adresse e-mail et votre mot de passe %{app_name} ont été utilisés + pour se connecter et s’authentifier sur un nouvel appareil. + info_p2: Si vous reconnaissez cette activité, vous n’avez rien à faire. + info_p3_html: Si ce n’est pas vous, %{reset_password_link_html} et modifiez + immédiatement vos %{authentication_methods_link_html}. + reset_password: réinitialisez votre mot de passe + subject: Nouvelle connexion et authentification avec votre compte %{app_name} + new_device_sign_in_attempts: + events: + sign_in_after_2fa: Signé avec deuxième facteur + sign_in_before_2fa: Connecté avec mot de passe + sign_in_unsuccessful_2fa: Échec de l’authentification + new_sign_in_from: Nouvelle connexion potentiellement située à %{location} + new_device_sign_in_before_2fa: + info_p1_html: + one: Votre adresse électronique et votre mot de passe %{app_name} ont été + utilisés pour vous connecter à partir d’un nouvel appareil, mais + l’authentification a échoué. + other: Votre adresse électronique et votre mot de passe %{app_name} ont été + utilisés pour vous connecter à partir d’un nouvel appareil, mais + l’authentification a échoué %{count} reprises. + info_p2: Si vous reconnaissez cette activité, vous n’avez rien à faire. + info_p3_html: L’authentification à deux facteurs protège votre compte contre + tout accès non autorisé. Si ce n’est pas vous, + %{reset_password_link_html}. + reset_password: réinitialisez immédiatement votre mot de passe + subject: Nouvelle connexion avec votre compte %{app_name} password_changed: disavowal_link: réinitialiser votre mot de passe help_html: Si vous n’avez pas effectué ce changement, %{disavowal_link_html}. diff --git a/db/primary_migrate/20240404154024_add_sign_in_new_device_at_to_users.rb b/db/primary_migrate/20240404154024_add_sign_in_new_device_at_to_users.rb new file mode 100644 index 00000000000..6319d15cc02 --- /dev/null +++ b/db/primary_migrate/20240404154024_add_sign_in_new_device_at_to_users.rb @@ -0,0 +1,8 @@ +class AddSignInNewDeviceAtToUsers < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + def change + add_column :users, :sign_in_new_device_at, :datetime + add_index :users, :sign_in_new_device_at, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index bf030067ebe..1af808446ec 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[7.1].define(version: 2024_03_26_181600) do +ActiveRecord::Schema[7.1].define(version: 2024_04_04_154024) do # These are extensions that must be enabled in order to support this database enable_extension "citext" enable_extension "pg_stat_statements" @@ -613,7 +613,9 @@ t.string "encrypted_recovery_code_digest_multi_region" t.datetime "second_mfa_reminder_dismissed_at" t.datetime "piv_cac_recommended_dismissed_at" + t.datetime "sign_in_new_device_at" t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + t.index ["sign_in_new_device_at"], name: "index_users_on_sign_in_new_device_at" t.index ["uuid"], name: "index_users_on_uuid", unique: true end diff --git a/docs/frontend.md b/docs/frontend.md index 29d6b16630a..6dd47a3eb65 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -209,8 +209,9 @@ Each component should have a name that is used consistently in its implementatio describes its purpose. This should be reflected in file names and the code itself. - ViewComponent classes should be named `[ExampleName]Component` -- ViewComponent files should be named `app/components/[example_name]_component.rb` -- Stylesheet files should be named `app/assets/stylesheets/components/_[example-name].scss` +- ViewComponent classes should be defined in `app/components/[example_name]_component.rb` +- ViewComponent stylesheets should be named `app/components/[example_name].scss` +- ViewComponent scripts should be named `app/components/[example_name].ts` - Stylesheet selectors should use `[example-name]` as the ["block name" in BEM](https://en.bem.info/methodology/naming-convention/#two-dashes-style) - React components should be named `<[ExampleName] />` - React component files should be named `app/javascript/packages/[example-name]/[example-name].tsx` @@ -220,8 +221,9 @@ describes its purpose. This should be reflected in file names and the code itsel For example, consider a **Password Input** component: - A ViewComponent implementation would be named `PasswordInputComponent` -- A ViewComponent file would be named `app/components/password_input_component.rb` -- A stylesheet file would be named `app/assets/stylesheets/componewnts/_password-input.scss` +- A ViewComponent classes would be defined in `app/components/password_input_component.rb` +- A ViewComponent stylesheet would be named `app/components/password_input_component.scss` +- A ViewComponent script would be named `app/components/password_input_component.ts` - A stylesheet selector would be named `.password-input`, with child elements prefixed as `.password-input__` - A React component would be named `