From 0f4f066bc70b9074802b133682bb12159520b8d2 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 29 Oct 2025 00:59:01 -0230 Subject: [PATCH 1/5] WIP: feat(captcha): implement captcha validation hooks and integrate into registration process --- .../users/registrations_controller.rb | 26 ++++++++++++ .../_extra_registration_fields.html.erb | 3 ++ app/views/devise/registrations/new.html.erb | 3 ++ config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fr.yml | 1 + .../registrations_controller_hook_spec.rb | 42 +++++++++++++++++++ 7 files changed, 77 insertions(+) create mode 100644 app/views/devise/registrations/_extra_registration_fields.html.erb create mode 100644 spec/controllers/better_together/users/registrations_controller_hook_spec.rb diff --git a/app/controllers/better_together/users/registrations_controller.rb b/app/controllers/better_together/users/registrations_controller.rb index e5dedc419..ccc9269f6 100644 --- a/app/controllers/better_together/users/registrations_controller.rb +++ b/app/controllers/better_together/users/registrations_controller.rb @@ -80,6 +80,13 @@ def create return end + # Validate captcha if enabled by host application + unless validate_captcha_if_enabled + build_resource(sign_up_params) + handle_captcha_validation_failure(resource) + return + end + ActiveRecord::Base.transaction do super do |user| handle_user_creation(user) if user.persisted? @@ -104,6 +111,25 @@ def set_required_agreements @code_of_conduct_agreement = BetterTogether::Agreement.find_by(identifier: 'code_of_conduct') end + # Hook method for host applications to implement captcha validation + # Override this method in host applications to add Turnstile or other captcha validation + # @return [Boolean] true if captcha is valid or not enabled, false if validation fails + def validate_captcha_if_enabled + # Default implementation - no captcha validation + # Host applications should override this method to implement their captcha logic + true + end + + # Hook method for host applications to handle captcha validation failures + # Override this method in host applications to customize error handling + # @param resource [User] the user resource being created + def handle_captcha_validation_failure(resource) + # Default implementation - adds a generic error message + resource.errors.add(:base, I18n.t('better_together.registrations.captcha_validation_failed', + default: 'Security verification failed. Please try again.')) + respond_with resource + end + def after_sign_up_path_for(resource) # Redirect to event if signed up via event invitation return better_together.event_path(@event_invitation.event) if @event_invitation&.event diff --git a/app/views/devise/registrations/_extra_registration_fields.html.erb b/app/views/devise/registrations/_extra_registration_fields.html.erb new file mode 100644 index 000000000..11bb237e9 --- /dev/null +++ b/app/views/devise/registrations/_extra_registration_fields.html.erb @@ -0,0 +1,3 @@ +<%# Override this partial in your host app to add additional fields to the registration form %> +<%# Place custom form elements here (e.g., CAPTCHA, extra validation fields, etc.) %> +<%# Available locals: form (form builder), resource (user resource being created) %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 432aa3e1d..bae3d9acd 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -130,6 +130,9 @@ <% end %> <% end %> + + <%= render partial: 'devise/registrations/extra_registration_fields', locals: { form: f, resource: resource } %> +
<%= f.submit t('.sign_up'), class: 'btn btn-primary' %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 64effabb7..aada2c633 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1721,6 +1721,7 @@ en: agreements_must_accept: You must accept the Terms of Service and Privacy Policy to continue. agreements_required: You must accept the Privacy Policy and Terms of Service + captcha_validation_failed: Security verification failed. Please complete the security check and try again. code_of_conduct: label: I agree to the Code of Conduct email: diff --git a/config/locales/es.yml b/config/locales/es.yml index 671f420b3..7dd4e2249 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1737,6 +1737,7 @@ es: agreements_must_accept: You must accept the Terms of Service and Privacy Policy to continue. agreements_required: You must accept the Privacy Policy and Terms of Service + captcha_validation_failed: La verificación de seguridad falló. Por favor, completa la verificación de seguridad e inténtalo de nuevo. code_of_conduct: label: I agree to the Code of Conduct email: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5a0705485..343619ea0 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1745,6 +1745,7 @@ fr: agreements_must_accept: You must accept the Terms of Service and Privacy Policy to continue. agreements_required: You must accept the Privacy Policy and Terms of Service + captcha_validation_failed: La vérification de sécurité a échoué. Veuillez compléter la vérification de sécurité et réessayer. code_of_conduct: label: I agree to the Code of Conduct email: diff --git a/spec/controllers/better_together/users/registrations_controller_hook_spec.rb b/spec/controllers/better_together/users/registrations_controller_hook_spec.rb new file mode 100644 index 000000000..0b42d1d28 --- /dev/null +++ b/spec/controllers/better_together/users/registrations_controller_hook_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe BetterTogether::Users::RegistrationsController, :skip_host_setup, + type: :controller do + include BetterTogether::CapybaraFeatureHelpers + + routes { BetterTogether::Engine.routes } + + before do + configure_host_platform + end + + describe 'captcha hook methods', :no_auth do + describe '#validate_captcha_if_enabled' do + let(:controller_instance) { described_class.new } + + it 'returns true by default (no captcha validation)' do + expect(controller_instance.send(:validate_captcha_if_enabled)).to be true + end + end + + describe '#handle_captcha_validation_failure' do + let(:user) { build(:better_together_user) } + let(:controller_instance) { described_class.new } + + before do + allow(controller_instance).to receive(:respond_with) + end + + it 'adds error to resource and calls respond_with' do + controller_instance.send(:handle_captcha_validation_failure, user) + + expect(user.errors[:base]).to include('Security verification failed. Please try again.') + expect(controller_instance).to have_received(:respond_with).with(user) + end + end + end + + # Integration test for the complete captcha flow will be handled in feature specs +end From 2f0411a1b4a2af822049d8c0b6c989e7596c51c8 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 29 Oct 2025 12:52:25 -0230 Subject: [PATCH 2/5] fix(locales): update Spanish and French translations for agreements and policies --- config/locales/es.yml | 17 ++++++++--------- config/locales/fr.yml | 17 ++++++++--------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/config/locales/es.yml b/config/locales/es.yml index 7dd4e2249..0d839d5a8 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1732,14 +1732,13 @@ es: actual para confirmar los cambios new: agreements: - privacy_policy: I agree to the Privacy Policy - terms_of_service: I agree to the Terms of Service - agreements_must_accept: You must accept the Terms of Service and Privacy Policy - to continue. - agreements_required: You must accept the Privacy Policy and Terms of Service + privacy_policy: Acepto la Política de Privacidad + terms_of_service: Acepto los Términos de Servicio + agreements_must_accept: Debe aceptar los Términos de Servicio y la Política de Privacidad para continuar. + agreements_required: Debe aceptar la Política de Privacidad y los Términos de Servicio captcha_validation_failed: La verificación de seguridad falló. Por favor, completa la verificación de seguridad e inténtalo de nuevo. code_of_conduct: - label: I agree to the Code of Conduct + label: Acepto el Código de Conducta email: help: Por favor, introduzca una dirección de correo válida. label: Correo Electrónico @@ -1765,13 +1764,13 @@ es: name: Nombre name_hint: Por favor, proporcione su nombre completo. privacy_policy: - label: I agree to the Privacy Policy + label: Acepto la Política de Privacidad profile_details: Detalles del Perfil sign_up: Registrarse submit: Enviar terms_of_service: - label: I agree to the Terms of Service - view: View + label: Acepto los Términos de Servicio + view: Ver signed_up: Bienvenido. Tu cuenta fue creada. signed_up_but_inactive: Tu cuenta ha sido creada correctamente. Sin embargo, no hemos podido iniciar la sesión porque tu cuenta aún no está activada. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 343619ea0..6752c2902 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1740,14 +1740,13 @@ fr: votre mot de passe actuel pour valider ces modifications new: agreements: - privacy_policy: I agree to the Privacy Policy - terms_of_service: I agree to the Terms of Service - agreements_must_accept: You must accept the Terms of Service and Privacy Policy - to continue. - agreements_required: You must accept the Privacy Policy and Terms of Service + privacy_policy: J'accepte la Politique de Confidentialité + terms_of_service: J'accepte les Conditions d'Utilisation + agreements_must_accept: Vous devez accepter les Conditions d'Utilisation et la Politique de Confidentialité pour continuer. + agreements_required: Vous devez accepter la Politique de Confidentialité et les Conditions d'Utilisation captcha_validation_failed: La vérification de sécurité a échoué. Veuillez compléter la vérification de sécurité et réessayer. code_of_conduct: - label: I agree to the Code of Conduct + label: J'accepte le Code de Conduite email: help: Veuillez entrer une adresse email valide. label: Email @@ -1773,13 +1772,13 @@ fr: name: Nom name_hint: Veuillez fournir votre nom complet. privacy_policy: - label: I agree to the Privacy Policy + label: J'accepte la Politique de Confidentialité profile_details: Détails du Profil sign_up: Inscription submit: Soumettre terms_of_service: - label: I agree to the Terms of Service - view: View + label: J'accepte les Conditions d'Utilisation + view: Voir signed_up: Bienvenue ! Vous vous êtes bien enregistré(e). signed_up_but_inactive: Vous vous êtes bien enregistré(e). Cependant, nous n’avons pas pu vous connecter car votre compte n’a pas encore été activé. From aacb1deaf42b7982d1e1688cceb277b123b8b6da Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 29 Oct 2025 12:52:37 -0230 Subject: [PATCH 3/5] refactor(registration): enhance form structure and accessibility with improved IDs and grouping --- app/views/devise/registrations/new.html.erb | 98 +++++++++++---------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index bae3d9acd..4206b1e52 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -29,115 +29,119 @@ <% else %>
-

<%= t('.sign_up') %>

+

<%= t('.sign_up') %>

- <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: 'card card-body needs-validation', novalidate: true }, data: { controller: 'better_together--form-validation', turbo: false }) do |f| %> + <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { id: 'registration-form', class: 'card card-body needs-validation registration-form', novalidate: true }, data: { controller: 'better_together--form-validation', turbo: false }) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> <% if @platform_invitation %> - <%= hidden_field_tag :invitation_code, @platform_invitation.token %> + <%= hidden_field_tag :invitation_code, @platform_invitation.token, id: 'invitation-code-field' %> <% end %> -
- <%= f.label :email, t('.email.label'), class: 'form-label' %> - <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form-control', required: true %> - <%= t('.email.help') %> +
+ <%= f.label :email, t('.email.label'), class: 'form-label', for: 'user_email' %> + <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form-control', required: true, id: 'user_email' %> + <%= t('.email.help') %>
-
- <%= f.label :password, t('.password.label'), class: 'form-label' %> +
+ <%= f.label :password, t('.password.label'), class: 'form-label', for: 'user_password' %> <% if @minimum_password_length %> - <%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %> + <%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %> <% end %>
- <%= f.password_field :password, autocomplete: "current-password", class: 'form-control', "data-target": "better_together--password-toggle.field", required: true, minlength: @minimum_password_length || 12 %> -
- <%= t('.password.help') %> + <%= t('.password.help') %>
-
- <%= f.label :password_confirmation, t('.password_confirmation.label'), class: 'form-label' %> +
+ <%= f.label :password_confirmation, t('.password_confirmation.label'), class: 'form-label', for: 'user_password_confirmation' %>
- <%= f.password_field :password_confirmation, autocomplete: "current-password", class: 'form-control', "data-target": "better_together--password-toggle.field", required: true, minlength: @minimum_password_length || 12 %> -
- <%= t('.password_confirmation.help') %> + <%= t('.password_confirmation.help') %>
-
-

<%= t('.profile_details') %>

+
+

<%= t('.profile_details') %>

<%= f.fields_for :person do |person_form| %> -
- <%= person_form.label :name, t('.person.name'), class: 'form-label' %> - <%= person_form.text_field :name, class: "form-control", required: true %> - <%= t('.person.name_hint') %> +
+ <%= person_form.label :name, t('.person.name'), class: 'form-label', for: 'user_person_attributes_name' %> + <%= person_form.text_field :name, class: "form-control", required: true, id: 'user_person_attributes_name' %> + <%= t('.person.name_hint') %>
-
- <%= person_form.label :identifier, t('.person.identifier'), class: 'form-label' %> - <%= person_form.text_field :identifier, class: "form-control", required: true, minlength: 3 %> +
+ <%= person_form.label :identifier, t('.person.identifier'), class: 'form-label', for: 'user_person_attributes_identifier' %> + <%= person_form.text_field :identifier, class: "form-control", required: true, minlength: 3, id: 'user_person_attributes_identifier' %> - <%= t('.person.identifier_hint_html', platform: host_platform) %> + <%= t('.person.identifier_hint_html', platform: host_platform) %>
-
- <%= person_form.label :description, t('.person.description'), class: 'form-label' %> - <%= person_form.text_area :description, class: "form-control" %> - <%= t('.person.description_hint') %> +
+ <%= person_form.label :description, t('.person.description'), class: 'form-label', for: 'user_person_attributes_description' %> + <%= person_form.text_area :description, class: "form-control", id: 'user_person_attributes_description' %> + <%= t('.person.description_hint') %>
<% end %>
<% if @terms_of_service_agreement || @privacy_policy_agreement || @code_of_conduct_agreement %> +
<% if @terms_of_service_agreement %> -
- <%= check_box_tag :terms_of_service_agreement, '1', false, class: 'form-check-input agreement-checkbox', data: { agreement_identifier: 'terms_of_service' }, required: true, aria: { describedby: 'terms-of-service-summary', disabled: 'true' } %> - <%= label_tag :terms_of_service_agreement, t('.terms_of_service.label'), class: 'form-check-label' %> +
+ <%= check_box_tag :terms_of_service_agreement, '1', false, class: 'form-check-input agreement-checkbox', data: { agreement_identifier: 'terms_of_service' }, required: true, aria: { describedby: 'terms-of-service-summary', disabled: 'true' }, id: 'terms_of_service_agreement_checkbox' %> + <%= label_tag :terms_of_service_agreement, t('.terms_of_service.label'), class: 'form-check-label', for: 'terms_of_service_agreement_checkbox' %> <%= link_to t('.view', default: 'View'), agreement_path(@terms_of_service_agreement, locale: I18n.locale), class: 'ms-2 agreement-modal-link', role: 'button', data: { agreement_identifier: 'terms_of_service' } %> -

<%= @terms_of_service_agreement.description %>

+ <%= @terms_of_service_agreement.description %>
<% end %> <% if @privacy_policy_agreement %> -
- <%= check_box_tag :privacy_policy_agreement, '1', false, class: 'form-check-input agreement-checkbox', data: { agreement_identifier: 'privacy_policy' }, required: true, aria: { describedby: 'privacy-policy-summary', disabled: 'true' } %> - <%= label_tag :privacy_policy_agreement, t('.privacy_policy.label'), class: 'form-check-label' %> +
+ <%= check_box_tag :privacy_policy_agreement, '1', false, class: 'form-check-input agreement-checkbox', data: { agreement_identifier: 'privacy_policy' }, required: true, aria: { describedby: 'privacy-policy-summary', disabled: 'true' }, id: 'privacy_policy_agreement_checkbox' %> + <%= label_tag :privacy_policy_agreement, t('.privacy_policy.label'), class: 'form-check-label', for: 'privacy_policy_agreement_checkbox' %> <%= link_to t('.view', default: 'View'), agreement_path(@privacy_policy_agreement, locale: I18n.locale), class: 'ms-2 agreement-modal-link', role: 'button', data: { agreement_identifier: 'privacy_policy' } %> -

<%= @privacy_policy_agreement.description %>

+ <%= @privacy_policy_agreement.description %>
<% end %> <% if @code_of_conduct_agreement %> -
- <%= check_box_tag :code_of_conduct_agreement, '1', false, class: 'form-check-input agreement-checkbox', data: { agreement_identifier: 'code_of_conduct' }, required: true, aria: { describedby: 'code-of-conduct-summary', disabled: 'true' } %> - <%= label_tag :code_of_conduct_agreement, t('.code_of_conduct.label'), class: 'form-check-label' %> +
+ <%= check_box_tag :code_of_conduct_agreement, '1', false, class: 'form-check-input agreement-checkbox', data: { agreement_identifier: 'code_of_conduct' }, required: true, aria: { describedby: 'code-of-conduct-summary', disabled: 'true' }, id: 'code_of_conduct_agreement_checkbox' %> + <%= label_tag :code_of_conduct_agreement, t('.code_of_conduct.label'), class: 'form-check-label', for: 'code_of_conduct_agreement_checkbox' %> <%= link_to t('.view', default: 'View'), agreement_path(@code_of_conduct_agreement, locale: I18n.locale), class: 'ms-2 agreement-modal-link', role: 'button', data: { agreement_identifier: 'code_of_conduct' } %> -

<%= @code_of_conduct_agreement.description %>

+ <%= @code_of_conduct_agreement.description %>
<% end %> +
<% end %> <%= render partial: 'devise/registrations/extra_registration_fields', locals: { form: f, resource: resource } %> -
- <%= f.submit t('.sign_up'), class: 'btn btn-primary' %> +
+ <%= f.submit t('.sign_up'), class: 'btn btn-primary', id: 'registration-submit-btn' %> - <%= render "devise/shared/links" %> +
<% end %>
From d21c4b42fcde667e7ae9ecc60213a3ad315e3183 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 29 Oct 2025 12:57:30 -0230 Subject: [PATCH 4/5] Rubocop fixes --- .../better_together/users/registrations_controller.rb | 6 +++--- .../users/registrations_controller_hook_spec.rb | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/controllers/better_together/users/registrations_controller.rb b/app/controllers/better_together/users/registrations_controller.rb index ccc9269f6..256c727b7 100644 --- a/app/controllers/better_together/users/registrations_controller.rb +++ b/app/controllers/better_together/users/registrations_controller.rb @@ -74,14 +74,14 @@ def new end end - def create + def create # rubocop:todo Metrics/MethodLength unless agreements_accepted? handle_agreements_not_accepted return end # Validate captcha if enabled by host application - unless validate_captcha_if_enabled + unless validate_captcha_if_enabled? build_resource(sign_up_params) handle_captcha_validation_failure(resource) return @@ -114,7 +114,7 @@ def set_required_agreements # Hook method for host applications to implement captcha validation # Override this method in host applications to add Turnstile or other captcha validation # @return [Boolean] true if captcha is valid or not enabled, false if validation fails - def validate_captcha_if_enabled + def validate_captcha_if_enabled? # Default implementation - no captcha validation # Host applications should override this method to implement their captcha logic true diff --git a/spec/controllers/better_together/users/registrations_controller_hook_spec.rb b/spec/controllers/better_together/users/registrations_controller_hook_spec.rb index 0b42d1d28..e62d8039d 100644 --- a/spec/controllers/better_together/users/registrations_controller_hook_spec.rb +++ b/spec/controllers/better_together/users/registrations_controller_hook_spec.rb @@ -2,8 +2,7 @@ require 'rails_helper' -RSpec.describe BetterTogether::Users::RegistrationsController, :skip_host_setup, - type: :controller do +RSpec.describe BetterTogether::Users::RegistrationsController, :skip_host_setup do include BetterTogether::CapybaraFeatureHelpers routes { BetterTogether::Engine.routes } @@ -13,11 +12,11 @@ end describe 'captcha hook methods', :no_auth do - describe '#validate_captcha_if_enabled' do + describe '#validate_captcha_if_enabled?' do let(:controller_instance) { described_class.new } it 'returns true by default (no captcha validation)' do - expect(controller_instance.send(:validate_captcha_if_enabled)).to be true + expect(controller_instance.send(:validate_captcha_if_enabled?)).to be true end end From b5b6bd967400b2f0c8240178484f69cc52686569 Mon Sep 17 00:00:00 2001 From: Robert Smith Date: Wed, 29 Oct 2025 13:01:03 -0230 Subject: [PATCH 5/5] Improve I18n health --- config/locales/en.yml | 5 ++++- config/locales/es.yml | 12 +++++++++--- config/locales/fr.yml | 11 ++++++++--- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index aada2c633..6a49a1955 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1418,6 +1418,8 @@ en: updated_on_short: Updated view_post: View Post primary: Primary + registrations: + captcha_validation_failed: Security verification failed. Please try again. remove: Remove resource_permissions: index: @@ -1721,7 +1723,8 @@ en: agreements_must_accept: You must accept the Terms of Service and Privacy Policy to continue. agreements_required: You must accept the Privacy Policy and Terms of Service - captcha_validation_failed: Security verification failed. Please complete the security check and try again. + captcha_validation_failed: Security verification failed. Please complete the + security check and try again. code_of_conduct: label: I agree to the Code of Conduct email: diff --git a/config/locales/es.yml b/config/locales/es.yml index 0d839d5a8..31edccb05 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1428,6 +1428,9 @@ es: updated_on_short: Actualizado view_post: Ver publicación primary: Primario + registrations: + captcha_validation_failed: La verificación de seguridad falló. Por favor, inténtelo + de nuevo. remove: Eliminar resource_permissions: index: @@ -1734,9 +1737,12 @@ es: agreements: privacy_policy: Acepto la Política de Privacidad terms_of_service: Acepto los Términos de Servicio - agreements_must_accept: Debe aceptar los Términos de Servicio y la Política de Privacidad para continuar. - agreements_required: Debe aceptar la Política de Privacidad y los Términos de Servicio - captcha_validation_failed: La verificación de seguridad falló. Por favor, completa la verificación de seguridad e inténtalo de nuevo. + agreements_must_accept: Debe aceptar los Términos de Servicio y la Política + de Privacidad para continuar. + agreements_required: Debe aceptar la Política de Privacidad y los Términos + de Servicio + captcha_validation_failed: La verificación de seguridad falló. Por favor, + completa la verificación de seguridad e inténtalo de nuevo. code_of_conduct: label: Acepto el Código de Conducta email: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6752c2902..60a48b25e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1435,6 +1435,8 @@ fr: updated_on_short: Mis à jour view_post: Voir la publication primary: Principal + registrations: + captcha_validation_failed: La vérification de sécurité a échoué. Veuillez réessayer. remove: Supprimer resource_permissions: index: @@ -1742,9 +1744,12 @@ fr: agreements: privacy_policy: J'accepte la Politique de Confidentialité terms_of_service: J'accepte les Conditions d'Utilisation - agreements_must_accept: Vous devez accepter les Conditions d'Utilisation et la Politique de Confidentialité pour continuer. - agreements_required: Vous devez accepter la Politique de Confidentialité et les Conditions d'Utilisation - captcha_validation_failed: La vérification de sécurité a échoué. Veuillez compléter la vérification de sécurité et réessayer. + agreements_must_accept: Vous devez accepter les Conditions d'Utilisation et + la Politique de Confidentialité pour continuer. + agreements_required: Vous devez accepter la Politique de Confidentialité et + les Conditions d'Utilisation + captcha_validation_failed: La vérification de sécurité a échoué. Veuillez + compléter la vérification de sécurité et réessayer. code_of_conduct: label: J'accepte le Code de Conduite email: