From a498258fabf9a8b91c4ee86b050e7a4455aabd31 Mon Sep 17 00:00:00 2001 From: Quentin Champenois <26109239+Quentinchampenois@users.noreply.github.com> Date: Thu, 30 Nov 2023 20:14:50 +0100 Subject: [PATCH 01/19] fix: Remove Deface precompile on production (#76) * feat: Add Docker on local with https * fix: Deactivate Deface procompile on local * fix: Deactivate Deface precompile on prod * lint: Fix rubocop offenses * fix: Enable letter_opener on production mode * fix: Override after_confirmation_path * fix: Add env var in .env-example * revert: "fix: Override after_confirmation_path" This reverts commit f15c8b211b414fb416e0eac0002a21e24c268acd. --------- Co-authored-by: Pauline Bessoles <52420208+paulinebessoles@users.noreply.github.com> --- .env-example | 12 ++- .gitignore | 1 + Dockerfile | 3 +- Dockerfile.local | 55 ++++++++++ Gemfile | 2 +- Makefile | 101 +++++------------- config/puma.rb | 13 +-- config/routes.rb | 2 +- ...ompose.dev.yml => docker-compose.local.yml | 43 ++++++-- 9 files changed, 135 insertions(+), 97 deletions(-) create mode 100644 Dockerfile.local rename docker-compose.dev.yml => docker-compose.local.yml (56%) diff --git a/.env-example b/.env-example index 8b91cc13..4f38a5e5 100644 --- a/.env-example +++ b/.env-example @@ -29,4 +29,14 @@ # ENABLE_RACK_ATTACK=1 # Deface is already precompiled in Dockerfile, default is false to prevent duplicates -# DEFACE_ENABLED=false \ No newline at end of file +# DEFACE_ENABLED=false + +DECIDIM_ADMIN_PASSWORD_STRONG="false" +# Puma server configuration +# PUMA_MIN_THREADS=5 +# PUMA_MAX_THREADS=5 +# PUMA_WORKERS=0 +# PUMA_PRELOAD_APP=false + +# Override after confirmation path with custom route +# AH_REDIRECT_AFTER_CONFIRMATION="/initiatives" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7299a799..09433732 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,4 @@ vendor/cache yarn-debug.log* .yarn-integrity *.rubocop-https* +certificate-https-local/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index abf4be32..4b9765b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,8 +24,7 @@ RUN yarn install --frozen-lockfile COPY . . RUN bundle exec bootsnap precompile --gemfile app/ lib/ config/ bin/ db/ && \ - bundle exec rails assets:precompile && \ - bundle exec rails deface:precompile + bundle exec rails assets:precompile RUN rm -rf node_modules tmp/cache vendor/bundle spec \ && rm -rf /usr/local/bundle/cache/*.gem \ diff --git a/Dockerfile.local b/Dockerfile.local new file mode 100644 index 00000000..ad1dd5c7 --- /dev/null +++ b/Dockerfile.local @@ -0,0 +1,55 @@ +# Builder Stage +FROM ruby:3.0.6-slim as builder + +ENV RAILS_ENV=production \ + SECRET_KEY_BASE=dummy + +WORKDIR /app + +RUN apt-get update -q && \ + apt-get install -yq libpq-dev curl git libicu-dev build-essential openssl && \ + curl https://deb.nodesource.com/setup_16.x | bash && \ + apt-get install -y nodejs && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + npm install --global yarn && \ + gem install bundler:2.4.9 + +COPY Gemfile Gemfile.lock ./ +RUN bundle config set --local without 'development test' && \ + bundle install -j"$(nproc)" + +COPY package.json yarn.lock ./ +COPY packages packages +RUN yarn install --frozen-lock + +COPY . . + +RUN bundle exec bootsnap precompile --gemfile app/ lib/ config/ bin/ db/ && \ + bundle exec rails assets:precompile + +run mkdir certificate-https-local +RUN openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=FR/ST=France/L=Paris/O=decidim/CN=decidim.eu" -keyout ./certificate-https-local/key.pem -out ./certificate-https-local/cert.pem; + +# Runner Stage +FROM ruby:3.0.6-slim as runner + +ENV RAILS_ENV=production \ + SECRET_KEY_BASE=dummy \ + RAILS_LOG_TO_STDOUT=true \ + LD_PRELOAD="libjemalloc.so.2" \ + MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:5000,muzzy_decay_ms:5000,narenas:2" + +WORKDIR /app + +RUN apt-get update -q && \ + apt-get install -yq postgresql-client imagemagick libproj-dev proj-bin libjemalloc2 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + gem install bundler:2.4.9 + +COPY --from=builder /usr/local/bundle /usr/local/bundle +COPY --from=builder /app /app + +EXPOSE 3000 +CMD ["bundle", "exec", "rails", "server", "-b", "ssl://0.0.0.0:3000?key=/app/certificate-https-local/key.pem&cert=/app/certificate-https-local/cert.pem"] \ No newline at end of file diff --git a/Gemfile b/Gemfile index f9fa0647..088f60db 100644 --- a/Gemfile +++ b/Gemfile @@ -36,13 +36,13 @@ gem "deface" gem "faker", "~> 2.14" gem "fog-aws" gem "foundation_rails_helper", git: "https://github.com/sgruhier/foundation_rails_helper.git" +gem "letter_opener_web", "~> 2.0" gem "omniauth-rails_csrf_protection", "~> 1.0" gem "puma", ">= 5.6.2" gem "rack-attack" gem "sys-filesystem" group :development do - gem "letter_opener_web", "~> 2.0" gem "listen", "~> 3.1" gem "rubocop-faker" gem "spring", "~> 2.0" diff --git a/Makefile b/Makefile index 929119ff..36fc450e 100644 --- a/Makefile +++ b/Makefile @@ -1,89 +1,36 @@ -#### Terraform | Scaleway provider -init-scw: - terraform -chdir=deploy/providers/scaleway init - -plan-scw: - @make init-scw - terraform -chdir=deploy/providers/scaleway plan - -deploy-scw: - @make init-scw - terraform -chdir=deploy/providers/scaleway apply - -destroy-scw: - terraform -chdir=deploy/providers/scaleway destroy - -### Docker usage +run: up + @make create-seeds -# Docker images commands +up: + docker-compose -f docker-compose.local.yml up --build -d + @make setup-database -REGISTRY := rg.fr-par.scw.cloud -NAMESPACE := decidim-app -VERSION := latest -IMAGE_NAME := decidim-app -TAG := $(REGISTRY)/$(NAMESPACE)/$(IMAGE_NAME):$(VERSION) +# Stops containers and remove volumes +teardown: + docker-compose -f docker-compose.local.yml down -v --rmi all -login: - docker login $(REGISTRY) -u nologin -p $(SCW_SECRET_TOKEN) +create-database: + docker-compose -f docker-compose.local.yml exec app /bin/bash -c 'DISABLE_DATABASE_ENVIRONMENT_CHECK=1 /usr/local/bundle/bin/bundle exec rake db:create' -build-classic: - docker build -t $(IMAGE_NAME):$(VERSION) . -build-scw: - docker build -t $(TAG) . -push: - @make build-scw - @make login - docker push $(TAG) -pull: - @make build-scw - docker pull $(TAG) +setup-database: create-database + docker-compose -f docker-compose.local.yml exec app /bin/bash -c 'DISABLE_DATABASE_ENVIRONMENT_CHECK=1 /usr/local/bundle/bin/bundle exec rake db:migrate' -# Bundle commands -create-database: - docker-compose run app bundle exec rails db:create -run-migrations: - docker-compose run app bundle exec rails db:migrate +# Create seeds create-seeds: - docker-compose run app bundle exec rails db:seed + docker-compose -f docker-compose.local.yml exec app /bin/bash -c 'DISABLE_DATABASE_ENVIRONMENT_CHECK=1 /usr/local/bundle/bin/bundle exec rake db:schema:load db:seed' -# Database commands +# Restore dump restore-dump: - bundle exec rake restore_dump + bundle exec rake restore_dump -# Start commands seperated by context -start: - docker-compose up - -start-dumped-decidim: - @make create-database - @make -i restore-dump - @make run-migrations - @make start -start-seeded-decidim: - @make create-database - @make run-migrations - @make create-seeds - @make start -start-clean-decidim: - @make create-database - @make run-migrations - @make start +shell: + docker-compose -f docker-compose.local.yml exec app /bin/bash -# Utils commands -rails-console: - docker exec -it decidim-app_app_1 rails c -connect-app: - docker exec -it decidim-app_app_1 bash +restart: + docker-compose -f docker-compose.local.yml up -d -# Stop and delete commands -stop: - docker-compose down -delete: - @make stop - docker volume prune +status: + docker-compose -f docker-compose.local.yml ps -local-dev: - docker-compose -f docker-compose.dev.yml up -d - @make create-database - @make run-migrations - @make create-seeds \ No newline at end of file +logs: + docker-compose -f docker-compose.local.yml logs app \ No newline at end of file diff --git a/config/puma.rb b/config/puma.rb index a8adec5d..8e29df4d 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -6,8 +6,9 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) -threads threads_count, threads_count +min_threads_count = ENV.fetch("PUMA_MIN_THREADS", 5).to_i +max_threads_count = ENV.fetch("PUMA_MAX_THREADS", 5).to_i +threads min_threads_count, max_threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # @@ -22,15 +23,15 @@ # the concurrency of the application would be max `threads` * `workers`. # Workers do not work on JRuby or Windows (both of which do not support # processes). -# -# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +workers_count = ENV.fetch("PUMA_WORKERS", -1).to_i +workers workers_count if workers_count.positive? # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write # process behavior so workers use less memory. -# -# preload_app! +preload_app! if ENV.fetch("PUMA_PRELOAD_APP", "false") == "true" # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb index f7164ae2..a87f61ad 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,7 +8,7 @@ mount Sidekiq::Web => "/sidekiq" end - mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? + mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? || ENV.fetch("ENABLE_LETTER_OPENER", "0") == "1" mount Decidim::Core::Engine => "/" # mount Decidim::Map::Engine => '/map' diff --git a/docker-compose.dev.yml b/docker-compose.local.yml similarity index 56% rename from docker-compose.dev.yml rename to docker-compose.local.yml index 506541df..c0708fa8 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.local.yml @@ -19,34 +19,59 @@ services: sidekiq: build: context: . + dockerfile: Dockerfile.local command: [ "bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml" ] environment: - - REDIS_URL=redis://redis:6379 - - MEMCACHE_SERVERS=memcached:11211 - DATABASE_HOST=database - DATABASE_USERNAME=postgres + - DECIDIM_HOST=localhost + - REDIS_URL=redis://redis:6379 + - MEMCACHE_SERVERS=memcached:11211 + - RAILS_SERVE_STATIC_FILES=true + - RAILS_LOG_TO_STDOUT=true + - ASSET_HOST=localhost:3000 + - FORCE_SSL=1 + - ENABLE_LETTER_OPENER=1 + - SEED=true + - DEFACE_ENABLED=true + - QUESTION_CAPTCHA_HOST= + - ENABLE_RACK_ATTACK=0 + - PUMA_MIN_THREADS=5 + - PUMA_MAX_THREADS=5 + - PUMA_WORKERS=-1 + - PUMA_PRELOAD_APP=false depends_on: - app + volumes: + - shared-volume:/app links: - database - redis app: build: context: . - volumes: - - .:/app - - node_modules:/app/node_modules + dockerfile: Dockerfile.local environment: - DATABASE_HOST=database - DATABASE_USERNAME=postgres - - DECIDIM_HOST=0.0.0.0 + - DECIDIM_HOST=localhost - REDIS_URL=redis://redis:6379 - MEMCACHE_SERVERS=memcached:11211 - RAILS_SERVE_STATIC_FILES=true - RAILS_LOG_TO_STDOUT=true - - FORCE_SSL="0" - - LETTER_OPENER_ENABLED="true" + - ASSET_HOST=localhost:3000 + - FORCE_SSL=1 + - ENABLE_LETTER_OPENER=1 - SEED=true + - DEFACE_ENABLED=true + - QUESTION_CAPTCHA_HOST= + - ENABLE_RACK_ATTACK=0 + - PUMA_MIN_THREADS=5 + - PUMA_MAX_THREADS=5 + - PUMA_WORKERS=-1 + - PUMA_PRELOAD_APP=false + volumes: + - shared-volume:/app ports: - 3000:3000 depends_on: @@ -55,6 +80,6 @@ services: - memcached volumes: - node_modules: { } + shared-volume: { } pg-data: { } redis-data: { } \ No newline at end of file From 38bff622378fdb50fd031fa5170061c09cdfb3df Mon Sep 17 00:00:00 2001 From: moustachu Date: Mon, 4 Dec 2023 17:33:37 +0100 Subject: [PATCH 02/19] fix: Enable Devise unconfirmed access (#83) --- config/initializers/decidim.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/initializers/decidim.rb b/config/initializers/decidim.rb index b85dca96..8437e449 100644 --- a/config/initializers/decidim.rb +++ b/config/initializers/decidim.rb @@ -19,6 +19,10 @@ # Timeout session config.expire_session_after = ENV.fetch("DECIDIM_SESSION_TIMEOUT", 180).to_i.minutes + # Devise unconfirmed access + # see also config/initializers/devise.rb line 27 + config.unconfirmed_access_for = ENV.fetch("DECIDIM_UNCONFIRMED_ACCESS", 0).to_i.days + config.maximum_attachment_height_or_width = 6000 # Whether SSL should be forced or not (only in production). From a80c7fc7b69d31b3b0480f15b63fd3cb15263c1d Mon Sep 17 00:00:00 2001 From: Quentin Champenois <26109239+Quentinchampenois@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:34:21 +0100 Subject: [PATCH 03/19] feat: custo signup form (#79) * fix: Override Devise registration form * fix: Update with extended socio demo ah * fix: Add JS to format birth date field * fix: Add translations for AH signup form * refactor: Move acceptance checkbox * fix: Add translations * fix: Override create registration command * fix: Add missing translations * lint: Refactor i18n keys * fix: Refactor signup form view * feat: add make rebuild command * fix: Remove newsletter modal * fix: command specs * fix: Deactivate newsletter by default * lint: Fix rubocop offenses * fix: Signup form JS (#84) * fix: Update authentication system specs * fix: Remove newsletter and fix some issues * fix: Fix the modification of user_registrations * fix: Remove useless help_message * fix: Remove unused locales --------- Co-authored-by: Quentin Bernigaud <55503640+Quentin-Bernigaud@users.noreply.github.com> Co-authored-by: AyakorK --- Makefile | 5 +- app/commands/decidim/create_registration.rb | 65 +++++++++ app/forms/decidim/registration_form.rb | 77 +++++++++++ app/packs/entrypoints/application.js | 2 + app/packs/entrypoints/application.scss | 1 + app/packs/src/decidim/user_registrations.js | 24 ++++ app/packs/src/signup_form.js | 57 ++++++++ app/packs/stylesheets/signup_form.scss | 13 ++ .../decidim/devise/registrations/new.html.erb | 125 ++++++++++++++++++ config/i18n-tasks.yml | 4 + config/initializers/extends.rb | 1 - config/locales/en.yml | 13 ++ config/locales/fr.yml | 13 ++ .../decidim/create_registration_extends.rb | 25 ---- .../decidim/create_registration_spec.rb | 24 +++- spec/system/authentication_spec.rb | 47 +++++-- 16 files changed, 454 insertions(+), 42 deletions(-) create mode 100644 app/commands/decidim/create_registration.rb create mode 100644 app/forms/decidim/registration_form.rb create mode 100644 app/packs/entrypoints/application.scss create mode 100644 app/packs/src/decidim/user_registrations.js create mode 100644 app/packs/src/signup_form.js create mode 100644 app/packs/stylesheets/signup_form.scss create mode 100644 app/views/decidim/devise/registrations/new.html.erb delete mode 100644 lib/extends/commands/decidim/create_registration_extends.rb diff --git a/Makefile b/Makefile index 36fc450e..69259cec 100644 --- a/Makefile +++ b/Makefile @@ -29,8 +29,11 @@ shell: restart: docker-compose -f docker-compose.local.yml up -d +rebuild: + docker-compose -f docker-compose.local.yml up --build -d + status: docker-compose -f docker-compose.local.yml ps logs: - docker-compose -f docker-compose.local.yml logs app \ No newline at end of file + docker-compose -f docker-compose.local.yml logs app diff --git a/app/commands/decidim/create_registration.rb b/app/commands/decidim/create_registration.rb new file mode 100644 index 00000000..e6878773 --- /dev/null +++ b/app/commands/decidim/create_registration.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module Decidim + # A command with all the business logic to create a user through the sign up form. + class CreateRegistration < Decidim::Command + # Public: Initializes the command. + # + # form - A form object with the params. + def initialize(form) + @form = form + end + + # Executes the command. Broadcasts these events: + # + # - :ok when everything is valid. + # - :invalid if the form wasn't valid and we couldn't proceed. + # + # Returns nothing. + def call + if form.invalid? + user = User.has_pending_invitations?(form.current_organization.id, form.email) + user.invite!(user.invited_by) if user + return broadcast(:invalid) + end + + create_user + + broadcast(:ok, @user) + rescue ActiveRecord::RecordInvalid + broadcast(:invalid) + end + + private + + attr_reader :form + + def create_user + @user = User.create!( + email: form.email, + name: form.name, + nickname: form.nickname, + password: form.password, + password_confirmation: form.password_confirmation, + password_updated_at: Time.current, + organization: form.current_organization, + tos_agreement: form.tos_agreement, + newsletter_notifications_at: nil, + accepted_tos_version: form.current_organization.tos_version, + locale: form.current_locale, + notifications_sending_frequency: :none, + extended_data: extended_data + ) + end + + def extended_data + { + birth_date: form.birth_date, + address: form.address, + postal_code: form.postal_code, + city: form.city, + certification: form.certification + } + end + end +end diff --git a/app/forms/decidim/registration_form.rb b/app/forms/decidim/registration_form.rb new file mode 100644 index 00000000..e29a9c91 --- /dev/null +++ b/app/forms/decidim/registration_form.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module Decidim + # A form object used to handle user registrations + class RegistrationForm < Form + mimic :user + + attribute :name, String + attribute :nickname, String + attribute :email, String + attribute :password, String + attribute :password_confirmation, String + attribute :newsletter, Boolean + attribute :tos_agreement, Boolean + attribute :current_locale, String + + # Extended socio demographic attributes + attribute :birth_date, Date + attribute :address, String + attribute :postal_code, String + attribute :city, String + attribute :certification, Boolean + + validates :name, presence: true, format: { with: Decidim::User::REGEXP_NAME } + validates :nickname, presence: true, format: { with: Decidim::User::REGEXP_NICKNAME }, length: { maximum: Decidim::User.nickname_max_length } + validates :email, presence: true, "valid_email_2/email": { disposable: true } + validates :password, confirmation: true + validates :password, password: { name: :name, email: :email, username: :nickname } + validates :password_confirmation, presence: true + validates :tos_agreement, allow_nil: false, acceptance: true + + # Extended socio demographic validations + validates :birth_date, presence: true + validates :address, presence: true + validates :postal_code, numericality: { only_integer: true }, presence: true, length: { is: 5 } + validates :city, presence: true + validates :certification, acceptance: true, presence: true + validate :over_16? + + validate :email_unique_in_organization + validate :nickname_unique_in_organization + validate :no_pending_invitations_exist + + def newsletter_at + return nil unless newsletter? + + Time.current + end + + private + + def email_unique_in_organization + errors.add :email, :taken if valid_users.find_by(email: email, organization: current_organization).present? + end + + def nickname_unique_in_organization + return false unless nickname + + errors.add :nickname, :taken if valid_users.find_by("LOWER(nickname)= ? AND decidim_organization_id = ?", nickname.downcase, current_organization.id).present? + end + + def valid_users + UserBaseEntity.where(invitation_token: nil) + end + + def no_pending_invitations_exist + errors.add :base, I18n.t("devise.failure.invited") if User.has_pending_invitations?(current_organization.id, email) + end + + def over_16? + return if birth_date.blank? + return if 16.years.ago.to_date > birth_date + + errors.add :base, I18n.t("decidim.devise.registrations.form.errors.messages.over_16") + end + end +end diff --git a/app/packs/entrypoints/application.js b/app/packs/entrypoints/application.js index fc8ab3b0..26794cee 100644 --- a/app/packs/entrypoints/application.js +++ b/app/packs/entrypoints/application.js @@ -17,3 +17,5 @@ // Activate Active Storage // import * as ActiveStorage from "@rails/activestorage" // ActiveStorage.start() + +import "src/signup_form.js" \ No newline at end of file diff --git a/app/packs/entrypoints/application.scss b/app/packs/entrypoints/application.scss new file mode 100644 index 00000000..640ccfeb --- /dev/null +++ b/app/packs/entrypoints/application.scss @@ -0,0 +1 @@ +@import "stylesheets/signup_form"; diff --git a/app/packs/src/decidim/user_registrations.js b/app/packs/src/decidim/user_registrations.js new file mode 100644 index 00000000..90352351 --- /dev/null +++ b/app/packs/src/decidim/user_registrations.js @@ -0,0 +1,24 @@ +$(() => { + const $userRegistrationForm = $("#register-form"); + const $userGroupFields = $userRegistrationForm.find(".user-group-fields"); + const inputSelector = 'input[name="user[sign_up_as]"]'; + const newsletterSelector = 'input[type="checkbox"][name="user[newsletter]"]'; + const $newsletterModal = $("#sign-up-newsletter-modal"); + + + const setGroupFieldsVisibility = (value) => { + if (value === "user") { + $userGroupFields.hide(); + } else { + $userGroupFields.show(); + } + } + + setGroupFieldsVisibility($userRegistrationForm.find(`${inputSelector}:checked`).val()); + + $userRegistrationForm.on("change", inputSelector, (event) => { + const value = event.target.value; + + setGroupFieldsVisibility(value); + }); +}); \ No newline at end of file diff --git a/app/packs/src/signup_form.js b/app/packs/src/signup_form.js new file mode 100644 index 00000000..83bac911 --- /dev/null +++ b/app/packs/src/signup_form.js @@ -0,0 +1,57 @@ +$(document).ready(() => { + $("label[for='registration_user_birth_date'] select").wrapAll('
'); + + // Sélection des éléments du DOM + const passwordInput = document.getElementById('registration_user_password'); + const confirmPasswordInput = document.getElementById('registration_user_password_confirmation'); + const userNameInput = document.getElementById('registration_user_name'); + const userNicknameInput = document.getElementById('registration_user_nickname'); + const userNicknameField = document.querySelector('.user-nickname'); + const fieldElements = document.querySelectorAll('.field'); + + // Masquer la classe "user-nickname" + userNicknameField.style.display = 'none'; + + // Masquer la classe "field" contenant la classe "registration_user_password_confirmation" + fieldElements.forEach(field => { + const confirmPasswordField = field.querySelector('#registration_user_password_confirmation'); + if (confirmPasswordField !== null) { + field.style.display = 'none'; + } + }); + + // // Écouteur d'événement sur le champ du mot de passe + passwordInput.addEventListener('input', function() { + // If there is a form-error behind the password field, remove it + const passwordField = passwordInput.parentElement; + const passwordError = passwordField.querySelector('.form-error'); + if (passwordError !== null) { + changeMessage(passwordError) + } + + confirmPasswordInput.value = passwordInput.value; + }); + + function changeMessage(passwordError) { + const password = passwordInput.value; + const passwordLength = password.length; + + if (passwordLength < 10) { + passwordError.textContent = 'Le mot de passe doit contenir au moins 10 caractères.'; + } + } + + // Génération automatique du surnom à partir du champ du nom + userNameInput.addEventListener('input', function() { + const userName = userNameInput.value.toLowerCase().replace(/\s/g, '_').replace(/[^\w\s]/gi, ''); // Remplacement des espaces par des underscores, suppression des caractères spéciaux et mise en minuscules + const randomNum = Math.floor(100000 + Math.random() * 900000); // Génération d'un nombre aléatoire à 6 chiffres + const generatedNickname = userName + '_' + randomNum; + + // Remplissage automatique du champ du surnom + userNicknameInput.value = generatedNickname.substr(0, 20); // Limite le surnom à 20 caractères + + // Cacher les champs générés automatiquement + confirmPasswordInput.style.display = 'none'; // Cacher le champ de confirmation du mot de passe + userNicknameInput.style.display = 'none'; // Cacher le champ du surnom + }); +}) diff --git a/app/packs/stylesheets/signup_form.scss b/app/packs/stylesheets/signup_form.scss new file mode 100644 index 00000000..4eb7fdda --- /dev/null +++ b/app/packs/stylesheets/signup_form.scss @@ -0,0 +1,13 @@ +#register-form { + .select-date-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin: 0 20px; + } + + .select-date-container .columns { + padding-right: 1.4rem; + } +} diff --git a/app/views/decidim/devise/registrations/new.html.erb b/app/views/decidim/devise/registrations/new.html.erb new file mode 100644 index 00000000..fcf6164f --- /dev/null +++ b/app/views/decidim/devise/registrations/new.html.erb @@ -0,0 +1,125 @@ +<% add_decidim_page_title(t(".sign_up")) %> + +<% content_for :devise_links do %> + <%= render "decidim/devise/shared/links" %> +<% end %> + +
+
+
+
+

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

+

+ <%= t(".subtitle") %> +

+

+ <%= t(".already_have_an_account?") %> + <%= link_to t(".sign_in"), new_user_session_path %> +

+
+
+ + <% cache current_organization do %> + <%= render "decidim/devise/shared/omniauth_buttons" %> + <% end %> + +
+
+ <%= decidim_form_for(@form, namespace: "registration", as: resource_name, url: registration_path(resource_name), html: { class: "register-form new_user", id: "register-form" }) do |f| %> + <%= invisible_captcha %> +
+
+ <%= form_required_explanation %> + +
+
+ <%= f.text_field :name, help_text: t(".username_help"), autocomplete: "name" %> +
+
+ +
+
+ <%= f.text_field :nickname, help_text: t(".nickname_help", organization: current_organization.name), prefix: { value: "@", small: 1, large: 1 }, autocomplete: "nickname" %> +
+
+ +
+ <%= f.email_field :email, autocomplete: "email" %> +
+ +
+ <%= f.password_field :password, password_field_options_for(:user) %> +
+ +
+ <%= f.password_field :password_confirmation, password_field_options_for(:user).except(:help_text) %> +
+
+
+ +
+
+

<%= t(".personal_data_step") %>

+
+
+ <%= f.date_select :birth_date, { label: t(".birth_date"), start_year: Date.today.year - 100, end_year: Date.today.year }, { class: "columns medium-3" } %> +
+
+ <%= t(".birth_date_help") %> +
+
+ +
+ <%= f.text_field :address, autocomplete: "street-address", label: t(".address") %> +
+ +
+
+
+ <%= f.text_field :postal_code, pattern: "^[0-9]+$", minlength: 5, maxlength: 5, autocomplete: "postal-code", label: t(".code") %> +
+
+
+
+ <%= f.text_field :city, label: t(".city") %> +
+
+
+ +
+ <%= f.check_box :certification, label: t(".certification") %> +
+
+
+ +
+
+

<%= t(".tos_title") %>

+ +

+ <%= strip_tags(translated_attribute(terms_and_conditions_page.content)) %> +

+ +
+ <%= f.check_box :tos_agreement, label: t(".tos_agreement", link: link_to(t(".terms"), page_path("terms-and-conditions"))) %> +
+
+
+ +
+
+
+ <%= f.submit t("devise.registrations.new.sign_up"), class: "button expanded" %> +
+ <%= yield :devise_links %> +
+
+ <% end %> +
+
+
+
+ +<%= render "decidim/devise/shared/newsletter_modal" %> +<%= javascript_pack_tag "application" %> +<%= stylesheet_pack_tag "application" %> diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 83c0c7b5..295ae1c7 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -101,6 +101,10 @@ ignore_missing: - decidim.initiatives.initiatives.show.illegal.{title,description} - decidim.initiatives.create_initiative.select_initiative_type.* - decidim.admin.organization_appearance.form.images.* + - decidim.devise.registrations.new.* + - devise.failure.invited + - devise.registrations.new.sign_up + # Consider these keys used: ignore_unused: diff --git a/config/initializers/extends.rb b/config/initializers/extends.rb index ded55eaa..b6d49a6e 100644 --- a/config/initializers/extends.rb +++ b/config/initializers/extends.rb @@ -2,7 +2,6 @@ require "extends/controllers/decidim/meetings/meetings_controller_extends" require "extends/commands/decidim/initiatives/create_initiative_extends" -require "extends/commands/decidim/create_registration_extends" require "extends/controllers/decidim/initiatives/create_initiative_controller_extends" require "extends/controllers/decidim/initiatives/initiatives_controller_extends" require "extends/helpers/decidim/initiatives/initiative_helper_extends" diff --git a/config/locales/en.yml b/config/locales/en.yml index 54f8c703..5defc3c6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -51,6 +51,19 @@ en: osp_authorization_workflow: name: Authorization procedure devise: + registrations: + form: + errors: + messages: + over_16: You must be over 16 years old to access this service. + new: + address: Address + birth_date: Date of birth + birth_date_help: You must be over 16 years old to access this service. + certification: Certification CESE + city: City + code: Postal code + personal_data_step: Personal data sessions: new: sign_in_disabled: Sign in disabled diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 9a7b44db..b93baa3e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -51,6 +51,19 @@ fr: osp_authorization_workflow: name: Procédure d'autorisation devise: + registrations: + form: + errors: + messages: + over_16: Vous devez avoir plus de 16 ans pour accéder à ce service. + new: + address: Addresse + birth_date: Date de naissance + birth_date_help: Vous devez avoir plus de 16 ans pour avoir accès à ce service. + certification: Je certifie l'exactitude de ces informations. + city: Ville + code: Code postal + personal_data_step: Informations personnelles sessions: new: sign_in_disabled: Vous pouvez accéder avec un compte externe diff --git a/lib/extends/commands/decidim/create_registration_extends.rb b/lib/extends/commands/decidim/create_registration_extends.rb deleted file mode 100644 index 00d1b1bf..00000000 --- a/lib/extends/commands/decidim/create_registration_extends.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module CreateRegistrationExtends - private - - def create_user - @user = Decidim::User.create!( - email: form.email, - name: form.name, - nickname: form.nickname, - password: form.password, - password_confirmation: form.password_confirmation, - organization: form.current_organization, - tos_agreement: form.tos_agreement, - newsletter_notifications_at: form.newsletter_at, - accepted_tos_version: form.current_organization.tos_version, - locale: form.current_locale, - notifications_sending_frequency: :none - ) - end -end - -Decidim::CreateRegistration.class_eval do - prepend(CreateRegistrationExtends) -end diff --git a/spec/commands/decidim/create_registration_spec.rb b/spec/commands/decidim/create_registration_spec.rb index 92483b11..68638b45 100644 --- a/spec/commands/decidim/create_registration_spec.rb +++ b/spec/commands/decidim/create_registration_spec.rb @@ -14,7 +14,7 @@ module Comments let(:password) { "Y1fERVzL2F" } let(:password_confirmation) { password } let(:tos_agreement) { "1" } - let(:newsletter) { "1" } + let(:newsletter) { nil } let(:current_locale) { "fr" } let(:form_params) do @@ -26,7 +26,12 @@ module Comments "password" => password, "password_confirmation" => password_confirmation, "tos_agreement" => tos_agreement, - "newsletter_at" => newsletter + "newsletter_at" => newsletter, + "birth_date" => "1980-01-01", + "address" => "Carrer de la Llibertat, 47", + "postal_code" => "08012", + "city" => "Barcelona", + "certification" => "1" } } end @@ -87,17 +92,30 @@ module Comments email: form.email, password: form.password, password_confirmation: form.password_confirmation, + password_updated_at: an_instance_of(ActiveSupport::TimeWithZone), tos_agreement: form.tos_agreement, newsletter_notifications_at: form.newsletter_at, organization: organization, accepted_tos_version: organization.tos_version, locale: form.current_locale, - notifications_sending_frequency: :none + notifications_sending_frequency: :none, + extended_data: { + birth_date: Date.new(1980, 0o1, 0o1), + address: "Carrer de la Llibertat, 47", + postal_code: "08012", + city: "Barcelona", + certification: true + } ).and_call_original expect { command.call }.to change(User, :count).by(1) end + it "sets the password_updated_at to the current time" do + expect { command.call }.to broadcast(:ok) + expect(User.last.password_updated_at).to be_between(2.seconds.ago, Time.current) + end + describe "when user keeps the newsletter unchecked" do let(:newsletter) { "0" } diff --git a/spec/system/authentication_spec.rb b/spec/system/authentication_spec.rb index 3888b23b..e3f02073 100644 --- a/spec/system/authentication_spec.rb +++ b/spec/system/authentication_spec.rb @@ -19,12 +19,19 @@ within ".new_user" do fill_in :registration_user_email, with: "user@example.org" fill_in :registration_user_name, with: "Responsible Citizen" - fill_in :registration_user_nickname, with: "responsible" fill_in :registration_user_password, with: "DfyvHn425mYAy2HL" - fill_in :registration_user_password_confirmation, with: "DfyvHn425mYAy2HL" + + select "1997", from: :registration_user_birth_date_1i + select "March", from: :registration_user_birth_date_2i + select "1", from: :registration_user_birth_date_3i + fill_in :registration_user_postal_code, with: "08080" + fill_in :registration_user_city, with: "Barcelona" + fill_in :registration_user_address, with: "Carrer de la Ciutat" + check :registration_user_certification check :registration_user_tos_agreement - check :registration_user_newsletter + find("*[type=submit]").click + page.save_screenshot("screenshot.png") end expect(page).to have_content("confirmation link") @@ -44,11 +51,17 @@ within ".new_user" do fill_in :registration_user_email, with: "user@example.org" fill_in :registration_user_name, with: "Responsible Citizen" - fill_in :registration_user_nickname, with: "responsible" fill_in :registration_user_password, with: "DfyvHn425mYAy2HL" - fill_in :registration_user_password_confirmation, with: "DfyvHn425mYAy2HL" + + select "1997", from: :registration_user_birth_date_1i + select "décembre", from: :registration_user_birth_date_2i + select "1", from: :registration_user_birth_date_3i + fill_in :registration_user_postal_code, with: "08080" + fill_in :registration_user_city, with: "Barcelona" + fill_in :registration_user_address, with: "Carrer de la Ciutat, 1" + check :registration_user_certification check :registration_user_tos_agreement - check :registration_user_newsletter + find("*[type=submit]").click end @@ -65,11 +78,17 @@ page.execute_script("$($('.new_user > div > input')[0]).val('Ima robot :D')") fill_in :registration_user_email, with: "user@example.org" fill_in :registration_user_name, with: "Responsible Citizen" - fill_in :registration_user_nickname, with: "responsible" fill_in :registration_user_password, with: "DfyvHn425mYAy2HL" - fill_in :registration_user_password_confirmation, with: "DfyvHn425mYAy2HL" + + select "1997", from: :registration_user_birth_date_1i + select "March", from: :registration_user_birth_date_2i + select "1", from: :registration_user_birth_date_3i + fill_in :registration_user_postal_code, with: "08080" + fill_in :registration_user_city, with: "Barcelona" + fill_in :registration_user_address, with: "Carrer de la Ciutat, 1" + check :registration_user_certification check :registration_user_tos_agreement - check :registration_user_newsletter + find("*[type=submit]").click end @@ -580,11 +599,15 @@ within ".new_user" do fill_in :registration_user_email, with: user.email fill_in :registration_user_name, with: "Responsible Citizen" - fill_in :registration_user_nickname, with: "responsible" fill_in :registration_user_password, with: "DfyvHn425mYAy2HL" - fill_in :registration_user_password_confirmation, with: "DfyvHn425mYAy2HL" + select "1997", from: :registration_user_birth_date_1i + select "March", from: :registration_user_birth_date_2i + select "1", from: :registration_user_birth_date_3i + fill_in :registration_user_postal_code, with: "08080" + fill_in :registration_user_city, with: "Barcelona" + fill_in :registration_user_address, with: "Carrer de la Ciutat, 1" + check :registration_user_certification check :registration_user_tos_agreement - check :registration_user_newsletter find("*[type=submit]").click end From 1ecb4865333ecd49b43cb38fd4f103888e29da19 Mon Sep 17 00:00:00 2001 From: moustachu Date: Wed, 6 Dec 2023 17:47:33 +0100 Subject: [PATCH 04/19] feat: Automatically vote initiatives after sign up / in when the modal is used (#85) * feat: Automatically vote initiatives after sign up / in when the modal is used - add new request param for auto sign when the modal is activated - manage new param for standard sign in - forward and manage new param for sign up - forward and manage new param for france connect sign in - forward and manage new param for france connect sign up with tos aggreement - fix post logout redirect for france connect sign out :fire: - add flash message to notify that the initiative was voted * fix: reload initiative for TOS after action --- ...iauth_registrations_controller_override.rb | 26 ++++++ .../registrations_controller_override.rb | 16 ++++ .../decidim/after_sign_in_action_helper.rb | 53 ++++++++++++ .../initiative_helper_vote_modal_override.rb | 31 +++++++ .../decidim/append_after_action_to_modals.js | 81 +++++++++++++++++++ app/packs/src/decidim/decidim_application.js | 2 + .../decidim/initiatives/permissions.rb | 2 + config/application.rb | 10 +++ config/i18n-tasks.yml | 2 + config/locales/en.yml | 5 +- config/locales/fr.yml | 3 + .../devise/sessions_controller_extends.rb | 11 ++- .../decidim/homepage_controller_extends.rb | 21 +++++ .../initiatives_controller_extends.rb | 12 +++ .../strategies/france_connect_extends.rb | 17 ++++ spec/system/initiative_spec.rb | 4 +- 16 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 app/controllers/decidim/omniauth_registrations_controller_override.rb create mode 100644 app/controllers/decidim/registrations_controller_override.rb create mode 100644 app/helpers/decidim/after_sign_in_action_helper.rb create mode 100644 app/helpers/decidim/initiatives/initiative_helper_vote_modal_override.rb create mode 100644 app/packs/src/decidim/append_after_action_to_modals.js create mode 100644 lib/extends/controllers/decidim/homepage_controller_extends.rb create mode 100644 lib/extends/omniauth/strategies/france_connect_extends.rb diff --git a/app/controllers/decidim/omniauth_registrations_controller_override.rb b/app/controllers/decidim/omniauth_registrations_controller_override.rb new file mode 100644 index 00000000..3a7848a7 --- /dev/null +++ b/app/controllers/decidim/omniauth_registrations_controller_override.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Decidim + module OmniauthRegistrationsControllerOverride + extend ActiveSupport::Concern + + included do + include Decidim::AfterSignInActionHelper + + def after_sign_in_path_for(user) + after_sign_in_action_for(user, request.params[:after_action]) if request.params[:after_action].present? + + if user.present? && user.blocked? + check_user_block_status(user) + elsif user.present? && !user.tos_accepted? && request.params[:after_action].present? + session["tos_after_action"] = request.params[:after_action] + super + elsif !pending_redirect?(user) && first_login_and_not_authorized?(user) + decidim_verifications.authorizations_path + else + super + end + end + end + end +end diff --git a/app/controllers/decidim/registrations_controller_override.rb b/app/controllers/decidim/registrations_controller_override.rb new file mode 100644 index 00000000..3c6e0c1d --- /dev/null +++ b/app/controllers/decidim/registrations_controller_override.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Decidim + module RegistrationsControllerOverride + extend ActiveSupport::Concern + + included do + include Decidim::AfterSignInActionHelper + + def after_sign_in_path_for(user) + after_sign_in_action_for(user, request.params[:after_action]) if request.params[:after_action].present? + super + end + end + end +end diff --git a/app/helpers/decidim/after_sign_in_action_helper.rb b/app/helpers/decidim/after_sign_in_action_helper.rb new file mode 100644 index 00000000..d23d9c45 --- /dev/null +++ b/app/helpers/decidim/after_sign_in_action_helper.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Decidim + module AfterSignInActionHelper + extend ActiveSupport::Concern + include Decidim::FormFactory + + included do + def default_url_options + url_options = {} + url_options[:locale] = current_locale unless current_locale == default_locale.to_s + url_options[:after_action] = request.params[:after_action] if request.params[:after_action].present? + url_options + end + end + + def after_sign_in_action_for(user, action) + return if user.blank? + return unless action == "vote-initiative" && (scan = %r{/initiatives/i-(\d+)(\?.*)?}.match(read_stored_location_for(user))) + + initiative = Decidim::Initiative.find(scan[1]) + + return unless allowed_to? :vote, :initiative, initiative: initiative, user: user, chain: permission_class_chain.push(Decidim::Initiatives::Permissions) + + form = form(Decidim::Initiatives::VoteForm).from_params( + initiative: initiative, + signer: user + ) + + Decidim::Initiatives::VoteInitiative.call(form) do + on(:ok) do + after_action_flash_message!(:secondary, "initiative_votes.create.success", "decidim.initiatives") + end + + on(:invalid) do + after_action_flash_message!(:error, "initiative_votes.create.error", "decidim.initiatives") + end + end + end + + def read_stored_location_for(resource_or_scope) + store_location_for(resource_or_scope, stored_location_for(resource_or_scope)) + end + + def after_action_flash_message!(level, key, scope) + if is_a? DeviseController + set_flash_message! level, key, { scope: scope } + else + flash.now[level] = t(key, scope: scope) + end + end + end +end diff --git a/app/helpers/decidim/initiatives/initiative_helper_vote_modal_override.rb b/app/helpers/decidim/initiatives/initiative_helper_vote_modal_override.rb new file mode 100644 index 00000000..23048e01 --- /dev/null +++ b/app/helpers/decidim/initiatives/initiative_helper_vote_modal_override.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Decidim + module Initiatives + module InitiativeHelperVoteModalOverride + extend ActiveSupport::Concern + + included do + def authorized_vote_modal_button(initiative, html_options, &block) + return if current_user && action_authorized_to("vote", resource: initiative, permissions_holder: initiative.type).ok? + + tag = "button" + html_options ||= {} + + html_options["data-after-action"] = "vote-initiative" + + if current_user + html_options["data-open"] = "authorizationModal" + html_options["data-open-url"] = authorization_sign_modal_initiative_path(initiative) + else + html_options["data-open"] = "loginModal" + end + + html_options["onclick"] = "event.preventDefault();" + + send("#{tag}_to", "", html_options, &block) + end + end + end + end +end diff --git a/app/packs/src/decidim/append_after_action_to_modals.js b/app/packs/src/decidim/append_after_action_to_modals.js new file mode 100644 index 00000000..dcc62a40 --- /dev/null +++ b/app/packs/src/decidim/append_after_action_to_modals.js @@ -0,0 +1,81 @@ +/* eslint-disable multiline-ternary, no-ternary */ + +/* + * Triggered when adding data-after-action to a link or button + * + * This is used to add an after action after sign in. + * + * When a button or link trigger a login modal we capture + * the event and inject the after action to be done + * after sign in (the after_action param). + * + * The code is injected to any form or link in the modal + * and when the modal is closed we remove the injected + * code. + * + * In order for this to work the button or link must have + * a data-open attribute with the ID of the modal to open + * and a data-after-action attribute the action to be done. + * If any of this is missing no code will be injected. + */ +$(() => { + const removeAfterActionParameter = (url, parameter) => { + const urlParts = url.split("?"); + + if (urlParts.length >= 2) { + // Get first part, and remove from array + const urlBase = urlParts.shift(); + + // Join it back up + const queryString = urlParts.join("?"); + + const prefix = `${encodeURIComponent(parameter)}=`; + const parts = queryString.split(/[&;]/g); + + // Reverse iteration as may be destructive + for (let index = parts.length - 1; index >= 0; index -= 1) { + // Idiom for string.startsWith + if (parts[index].lastIndexOf(prefix, 0) !== -1) { + parts.splice(index, 1); + } + } + + if (parts.length === 0) { + return urlBase; + } + + return `${urlBase}?${parts.join("&")}`; + } + + return url; + } + + $(document).on("click.zf.trigger", (event) => { + const target = `#${$(event.target).data("open")}`; + const afterAction = $(event.target).data("afterAction"); + + if (target && afterAction) { + $(""). + attr("id", "after_action"). + attr("name", "after_action"). + attr("value", afterAction). + appendTo(`${target} form`); + + $(`${target} a`).attr("href", (index, href) => { + const querystring = jQuery.param({"after_action": afterAction}); + return href + (href.match(/\?/) ? "&" : "?") + querystring; + }); + } + }); + + $(document).on("closed.zf.reveal", (event) => { + $("#after_action", event.target).remove(); + $("a", event.target).attr("href", (index, href) => { + if (href && href.indexOf("after_action") !== -1) { + return removeAfterActionParameter(href, "after_action"); + } + + return href; + }); + }); +}); diff --git a/app/packs/src/decidim/decidim_application.js b/app/packs/src/decidim/decidim_application.js index 5d5dcf59..8ad5beb0 100644 --- a/app/packs/src/decidim/decidim_application.js +++ b/app/packs/src/decidim/decidim_application.js @@ -1,5 +1,7 @@ // This file is compiled inside Decidim core pack. Code can be added here and will be executed // as part of that pack +import "src/decidim/append_after_action_to_modals" + // Load images require.context("../../images", true) diff --git a/app/permissions/decidim/initiatives/permissions.rb b/app/permissions/decidim/initiatives/permissions.rb index a94f3ba5..621248c2 100644 --- a/app/permissions/decidim/initiatives/permissions.rb +++ b/app/permissions/decidim/initiatives/permissions.rb @@ -151,6 +151,7 @@ def unvote_initiative? can_unvote = initiative.accepts_online_unvotes? && initiative.organization&.id == user.organization&.id && initiative.votes.where(author: user).any? && + user.tos_accepted? && authorized?(:vote, resource: initiative, permissions_holder: initiative.type) toggle_allow(can_unvote) @@ -181,6 +182,7 @@ def can_vote? initiative.votes_enabled? && initiative.organization&.id == user.organization&.id && initiative.votes.where(author: user).empty? && + user.tos_accepted? && authorized?(:vote, resource: initiative, permissions_holder: initiative.type) end diff --git a/config/application.rb b/config/application.rb index 94c88fff..bc76d56f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -47,12 +47,22 @@ class Application < Rails::Application config.after_initialize do require "extends/forms/decidim/initiatives/initiative_form_extends" require "extends/controllers/decidim/devise/sessions_controller_extends" + require "extends/controllers/decidim/homepage_controller_extends" require "extends/forms/decidim/admin/organization_appearance_form_extends" + require "extends/omniauth/strategies/france_connect_extends" end initializer "session cookie domain", after: "Expire sessions" do Rails.application.config.session_store :active_record_store, key: "_decidim_session", expire_after: Decidim.config.expire_session_after ActiveRecord::SessionStore::Session.serializer = :hybrid end + + initializer "decidim_app.overrides", after: "decidim.action_controller" do + config.to_prepare do + Decidim::Initiatives::InitiativeHelper.include(Decidim::Initiatives::InitiativeHelperVoteModalOverride) + Decidim::Devise::RegistrationsController.include(Decidim::RegistrationsControllerOverride) + Decidim::Devise::OmniauthRegistrationsController.include(Decidim::OmniauthRegistrationsControllerOverride) + end + end end end diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 295ae1c7..1f828248 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -137,3 +137,5 @@ ignore_unused: - activemodel.attributes.confirmation.* - activemodel.attributes.mobile_phone.* - decidim.admin.participatory_space_private_users.create.* + - decidim.admin.participatory_space_private_users.create.* + - decidim.initiatives.initiative_votes.create.success \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 5defc3c6..78341c21 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -104,9 +104,12 @@ en: decidim_user_group_id_help: It's not possible to change initiative authorship after creation select_initiative_type: verification_required: Verify your account to promote this initiative + initiative_votes: + create: + success: Congratulations! The initiative has been successfully signed initiatives: show: - print: Voir le récépissé + print: Show receipt modal: not_authorized: authorizations_page: View authorizations diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b93baa3e..5b186747 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -93,6 +93,9 @@ fr: email_subject: Un utilisateur a tenté de se faire vérifier avec les données d'un utilisateur représenté notification_title: Le participant %{resource_title} a tenté de se faire vérifier avec les données de l'utilisateur représenté %{managed_user_name}. initiatives: + initiative_votes: + create: + success: Toutes nos félicitations ! La pétition a été signée correctement initiatives: show: print: Voir le récépissé diff --git a/lib/extends/controllers/decidim/devise/sessions_controller_extends.rb b/lib/extends/controllers/decidim/devise/sessions_controller_extends.rb index c7ec5047..ec6cc048 100644 --- a/lib/extends/controllers/decidim/devise/sessions_controller_extends.rb +++ b/lib/extends/controllers/decidim/devise/sessions_controller_extends.rb @@ -4,10 +4,13 @@ module SessionControllerExtends extend ActiveSupport::Concern included do + include Decidim::AfterSignInActionHelper + def destroy + after_sign_out_url = after_sign_out_path_for(current_user) current_user.invalidate_all_sessions! if active_france_connect_session? - destroy_france_connect_session(session["omniauth.france_connect.end_session_uri"]) + destroy_france_connect_session(session["omniauth.france_connect.end_session_uri"], after_sign_out_url) elsif params[:translation_suffix].present? super { set_flash_message! :notice, params[:translation_suffix], { scope: "decidim.devise.sessions" } } else @@ -16,6 +19,8 @@ def destroy end def after_sign_in_path_for(user) + after_sign_in_action_for(user, request.params[:after_action]) if request.params[:after_action].present? + if user.present? && user.blocked? check_user_block_status(user) elsif !skip_first_login_authorization? && (first_login_and_not_authorized?(user) && !user.admin? && !pending_redirect?(user)) @@ -33,13 +38,13 @@ def skip_first_login_authorization? end end - def destroy_france_connect_session(fc_logout_path) + def destroy_france_connect_session(fc_logout_path, post_logout_redirect_uri) signed_out = (::Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)) if signed_out set_flash_message! :notice, :signed_out session.delete("omniauth.france_connect.end_session_uri") end - + session["omniauth.france_connect.post_logout_redirect_uri"] = post_logout_redirect_uri redirect_to fc_logout_path end diff --git a/lib/extends/controllers/decidim/homepage_controller_extends.rb b/lib/extends/controllers/decidim/homepage_controller_extends.rb new file mode 100644 index 00000000..89bbc300 --- /dev/null +++ b/lib/extends/controllers/decidim/homepage_controller_extends.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module HomepageControllerExtends + extend ActiveSupport::Concern + + included do + before_action :france_connect_after_sign_out, only: [:show] + + def france_connect_after_sign_out + if session["omniauth.france_connect.post_logout_redirect_uri"].present? + post_logout_redirect_uri = session["omniauth.france_connect.post_logout_redirect_uri"] + session.delete("omniauth.france_connect.post_logout_redirect_uri") + redirect_to post_logout_redirect_uri + end + end + end +end + +Decidim::HomepageController.class_eval do + include(HomepageControllerExtends) +end diff --git a/lib/extends/controllers/decidim/initiatives/initiatives_controller_extends.rb b/lib/extends/controllers/decidim/initiatives/initiatives_controller_extends.rb index 572e6647..d3fa8223 100644 --- a/lib/extends/controllers/decidim/initiatives/initiatives_controller_extends.rb +++ b/lib/extends/controllers/decidim/initiatives/initiatives_controller_extends.rb @@ -4,11 +4,23 @@ module InitiativesControllerExtends extend ActiveSupport::Concern included do + include Decidim::AfterSignInActionHelper + helper Decidim::Initiatives::SignatureTypeOptionsHelper helper Decidim::Initiatives::InitiativePrintHelper helper_method :available_initiative_types + def show + enforce_permission_to :read, :initiative, initiative: current_initiative + if session["tos_after_action"].present? && URI.parse(request.referer).path == tos_path.split("?")&.first + tos_after_action = session["tos_after_action"] + session.delete("tos_after_action") + after_sign_in_action_for(current_user, tos_after_action) + current_initiative.reload + end + end + def print enforce_permission_to :print, :initiative, initiative: current_initiative end diff --git a/lib/extends/omniauth/strategies/france_connect_extends.rb b/lib/extends/omniauth/strategies/france_connect_extends.rb new file mode 100644 index 00000000..dadc7b6a --- /dev/null +++ b/lib/extends/omniauth/strategies/france_connect_extends.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module FranceConnectExtends + extend ActiveSupport::Concern + + included do + private + + def redirect_uri + "#{omniauth_callback_url}?#{params.slice("redirect_uri", "after_action").to_query}" + end + end +end + +OmniAuth::Strategies::FranceConnect.class_eval do + include(FranceConnectExtends) +end diff --git a/spec/system/initiative_spec.rb b/spec/system/initiative_spec.rb index a7ecbe7f..5b4cbdb8 100644 --- a/spec/system/initiative_spec.rb +++ b/spec/system/initiative_spec.rb @@ -46,13 +46,13 @@ shared_examples_for "initiative shows recepisse link" do it "shows recepisse link" do - expect(page).to have_link("Voir le récépissé") + expect(page).to have_link("Show receipt") end end shared_examples_for "initiative does not show recepisse link" do it "does not show recepisse link" do - expect(page).not_to have_link("Voir le récépissé") + expect(page).not_to have_link("Show receipt") end end From 8c1507300aa06f20f2fe1d0a40a92095ec5b8cd6 Mon Sep 17 00:00:00 2001 From: Quentin Champenois <26109239+Quentinchampenois@users.noreply.github.com> Date: Wed, 6 Dec 2023 19:04:27 +0100 Subject: [PATCH 05/19] feat: Unconfirmed votes cleaner (#86) * feat: Create Confirmation Reminder Event * feat: Create confirmation reminder job * fix: Docker-compose local change database_name * lint: Fix rubocop offenses * feat: Create confirmation email * feat: Clear unconfirmed votes * fix: Locales * fix: Allow to configure delays * fix: ConfirmationReminderJob * feat: Define Sidekiq crons * fix: Remove unused events * revert: Remove AnswerInitiativeEvent * lint: Fix rubocop offense * fix: Remove unused locales --------- Co-authored-by: AyakorK --- app/jobs/decidim/confirmation_reminder_job.rb | 17 +++++++ .../decidim/unconfirmed_votes_cleaner_job.rb | 26 ++++++++++ .../decidim/confirmation_reminder_mailer.rb | 22 ++++++++ .../decidim/unconfirmed_votes_clear_mailer.rb | 22 ++++++++ .../send_reminder.html.erb | 20 ++++++++ .../send_resume.html.erb | 22 ++++++++ config/locales/en.yml | 15 ++++++ config/locales/fr.yml | 15 ++++++ config/secrets.yml | 3 ++ config/sidekiq.yml | 8 +++ docker-compose.local.yml | 2 + .../decidim/confirmation_reminder_job_spec.rb | 36 +++++++++++++ .../unconfirmed_votes_cleaner_job_spec.rb | 31 ++++++++++++ .../confirmation_reminder_mailer_spec.rb | 41 +++++++++++++++ .../unconfirmed_votes_clear_mailer_spec.rb | 50 +++++++++++++++++++ 15 files changed, 330 insertions(+) create mode 100644 app/jobs/decidim/confirmation_reminder_job.rb create mode 100644 app/jobs/decidim/unconfirmed_votes_cleaner_job.rb create mode 100644 app/mailers/decidim/confirmation_reminder_mailer.rb create mode 100644 app/mailers/decidim/unconfirmed_votes_clear_mailer.rb create mode 100644 app/views/decidim/confirmation_reminder_mailer/send_reminder.html.erb create mode 100644 app/views/decidim/unconfirmed_votes_clear_mailer/send_resume.html.erb create mode 100644 spec/jobs/decidim/confirmation_reminder_job_spec.rb create mode 100644 spec/jobs/decidim/unconfirmed_votes_cleaner_job_spec.rb create mode 100644 spec/mailers/confirmation_reminder_mailer_spec.rb create mode 100644 spec/mailers/unconfirmed_votes_clear_mailer_spec.rb diff --git a/app/jobs/decidim/confirmation_reminder_job.rb b/app/jobs/decidim/confirmation_reminder_job.rb new file mode 100644 index 00000000..9ae35ffa --- /dev/null +++ b/app/jobs/decidim/confirmation_reminder_job.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Decidim + class ConfirmationReminderJob < ApplicationJob + def perform + unconfirmed_users.each do |user| + Decidim::ConfirmationReminderMailer.send_reminder(user).deliver_now + end + end + + private + + def unconfirmed_users + @unconfirmed_users ||= Decidim::User.not_confirmed.where("DATE(created_at) = ?", Rails.application.secrets.dig(:decidim, :reminder, :unconfirmed_email, :days).days.ago) + end + end +end diff --git a/app/jobs/decidim/unconfirmed_votes_cleaner_job.rb b/app/jobs/decidim/unconfirmed_votes_cleaner_job.rb new file mode 100644 index 00000000..bbb8a6dc --- /dev/null +++ b/app/jobs/decidim/unconfirmed_votes_cleaner_job.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Decidim + class UnconfirmedVotesCleanerJob < ApplicationJob + def perform + unconfirmed_users.each do |user| + votes = Decidim::InitiativesVote.includes(:initiative) + .where(author: user) + next if votes.blank? + + initiatives = votes.map(&:initiative) + votes.destroy_all + Decidim::UnconfirmedVotesClearMailer.send_resume(user, initiatives).deliver_now + end + end + + private + + def unconfirmed_users + @unconfirmed_users ||= Decidim::User.not_confirmed + .where("DATE(decidim_users.created_at) = ?", Decidim.unconfirmed_access_for.ago.to_date) + .joins("JOIN decidim_initiatives_votes ON decidim_users.id = decidim_initiatives_votes.decidim_author_id") + .distinct + end + end +end diff --git a/app/mailers/decidim/confirmation_reminder_mailer.rb b/app/mailers/decidim/confirmation_reminder_mailer.rb new file mode 100644 index 00000000..0a9eb8bf --- /dev/null +++ b/app/mailers/decidim/confirmation_reminder_mailer.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Decidim + class ConfirmationReminderMailer < ApplicationMailer + helper Decidim::SanitizeHelper + helper Decidim::TranslationsHelper + + def send_reminder(user) + return if user&.email.blank? + + @organization = user.organization + @user = user + root_url = decidim.root_url(host: @organization.host)[0..-2] + @confirmation_link = "#{root_url}#{decidim.user_confirmation_path(confirmation_token: user.confirmation_token)}" + with_user(user) do + @subject = I18n.t("subject", scope: "decidim.confirmation_reminder_mailer.send_reminder") + + mail(to: "#{user.name} <#{user.email}>", subject: @subject) + end + end + end +end diff --git a/app/mailers/decidim/unconfirmed_votes_clear_mailer.rb b/app/mailers/decidim/unconfirmed_votes_clear_mailer.rb new file mode 100644 index 00000000..8d389070 --- /dev/null +++ b/app/mailers/decidim/unconfirmed_votes_clear_mailer.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Decidim + class UnconfirmedVotesClearMailer < ApplicationMailer + helper Decidim::SanitizeHelper + helper Decidim::TranslationsHelper + + def send_resume(user, initiatives) + return if user&.email.blank? + + @organization = user.organization + @user = user + @initiatives = initiatives + + with_user(user) do + @subject = I18n.t("subject", scope: "decidim.unconfirmed_votes_clear_mailer.send_resume") + + mail(to: "#{user.name} <#{user.email}>", subject: @subject) + end + end + end +end diff --git a/app/views/decidim/confirmation_reminder_mailer/send_reminder.html.erb b/app/views/decidim/confirmation_reminder_mailer/send_reminder.html.erb new file mode 100644 index 00000000..1e5c33fe --- /dev/null +++ b/app/views/decidim/confirmation_reminder_mailer/send_reminder.html.erb @@ -0,0 +1,20 @@ + +

+ <%== t "title", scope: "decidim.confirmation_reminder_mailer.send_reminder.body" %> +

+ +

+ <%== t "body", scope: "decidim.confirmation_reminder_mailer.send_reminder.body" %> +

+ +
+ <%= link_to t("confirmation_link", scope: "decidim.confirmation_reminder_mailer.send_reminder.body"), @confirmation_link %> +
+ +

+ <%== t "warning", scope: "decidim.confirmation_reminder_mailer.send_reminder.body" %> +

+ +<% content_for :note do %> + <%== t "subject", scope: "decidim.confirmation_reminder_mailer.send_reminder", organization_name: h(@organization.name), link: decidim.notifications_settings_url(host: @organization.host) %> +<% end %> diff --git a/app/views/decidim/unconfirmed_votes_clear_mailer/send_resume.html.erb b/app/views/decidim/unconfirmed_votes_clear_mailer/send_resume.html.erb new file mode 100644 index 00000000..460d1980 --- /dev/null +++ b/app/views/decidim/unconfirmed_votes_clear_mailer/send_resume.html.erb @@ -0,0 +1,22 @@ + +

+ <%== t "title", scope: "decidim.unconfirmed_votes_clear_mailer.send_resume.body" %> +

+ +
    + <% @initiatives.each do |initiative| %> +
  • + <%== t("vote", scope: "decidim.unconfirmed_votes_clear_mailer.send_resume.body", initiative_title: translated_attribute(initiative.title)) %> +
  • + <% end %> +
+ + +

+ <%== t "confirm", scope: "decidim.unconfirmed_votes_clear_mailer.send_resume.body" %> +

+ + +<% content_for :note do %> + <%== t "subject", scope: "decidim.confirmation_reminder_mailer.send_reminder", organization_name: h(@organization.name), link: decidim.notifications_settings_url(host: @organization.host) %> +<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 78341c21..bfc37803 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -50,6 +50,14 @@ en: name: Identity Verification Form osp_authorization_workflow: name: Authorization procedure + confirmation_reminder_mailer: + send_reminder: + body: + body: Please confirm your email address to persist your votes. + confirmation_link: Link to confirmation + title: You have created an account there is 2 days ago and you haven't confirmed your email yet. + warning: If you don't confirm your email address, your votes will be deleted. + subject: Please confirm your email address devise: registrations: form: @@ -204,6 +212,13 @@ en: client_id: Client ID client_secret: Client secret site_url: Site URL + unconfirmed_votes_clear_mailer: + send_resume: + body: + confirm: You can still confirm your account and vote again on initiatives. + title: You did not confirm your account, existing votes have been deleted + vote: Your vote on %{initiative_title} has been deleted. + subject: You did not confirm your account, existing votes have been deleted verifications: authorizations: create: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5b186747..3389350c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -50,6 +50,14 @@ fr: name: Formulaire de vérification d'identité osp_authorization_workflow: name: Procédure d'autorisation + confirmation_reminder_mailer: + send_reminder: + body: + body: Veuillez confirmer votre compte en cliquant sur le lien ci-dessous. + confirmation_link: Lien de confirmation + title: Vous avez créé un compte il y a 2 jours mais vous ne l'avez pas encore confirmé. + warning: L'ensemble de vos votes seront supprimés dans 6 jours si votre compte n'est pas confirmé dans ce délai. + subject: Veuillez confirmer votre compte devise: registrations: form: @@ -200,6 +208,13 @@ fr: client_id: Client ID client_secret: Client secret site_url: Site URL + unconfirmed_votes_clear_mailer: + send_resume: + body: + confirm: Vous pouvez toujours confirmer votre compte et voter à nouveau sur les pétitions. + title: Vous n'avez pas confirmé votre compte, les votes que vous avez fait ont été supprimés + vote: Votre vote sur la pétition '%{initiative_title}' a été supprimé. + subject: Vous n'avez pas confirmé votre compte, vos votes ont été supprimés verifications: authorizations: create: diff --git a/config/secrets.yml b/config/secrets.yml index 63de95b4..c41ca0e8 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -13,6 +13,9 @@ default: &default asset_host: <%= ENV["ASSET_HOST"] %> decidim: + reminder: + unconfirmed_email: + days: <%= ENV["DECIDIM_REMINDER_UNCONFIRMED_EMAIL_DAYS"]&.to_i || 2 %> verifications: sms_gateway_service: username: <%= ENV["SMS_GATEWAY_USERNAME"].presence %> diff --git a/config/sidekiq.yml b/config/sidekiq.yml index ddce4644..8811244a 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -24,6 +24,14 @@ cron: '0 0 1 * * *' # Run at 01:00 class: PreloadOpenDataJob queue: scheduled + ConfirmationReminderJob: + cron: '0 0 9 * * *' # Run at 09:00 + class: Decidim::ConfirmationReminderJob + queue: scheduled + UnconfirmedVotesCleanerJob: + cron: '0 0 9 * * *' # Run at 09:00 + class: Decidim::UnconfirmedVotesCleanerJob + queue: scheduled DetectSpamUsers: cron: '0 <%= Random.rand(0..59) %> <%= Random.rand(6..8) %> * * *' # Run randomly between 06:00 and 08:59 class: Decidim::SpamDetection::MarkUsersJob diff --git a/docker-compose.local.yml b/docker-compose.local.yml index c0708fa8..7f78d6a1 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -24,6 +24,7 @@ services: environment: - DATABASE_HOST=database - DATABASE_USERNAME=postgres + - DATABASE_NAME=decidim_cese - DECIDIM_HOST=localhost - REDIS_URL=redis://redis:6379 - MEMCACHE_SERVERS=memcached:11211 @@ -53,6 +54,7 @@ services: dockerfile: Dockerfile.local environment: - DATABASE_HOST=database + - DATABASE_NAME=decidim_cese - DATABASE_USERNAME=postgres - DECIDIM_HOST=localhost - REDIS_URL=redis://redis:6379 diff --git a/spec/jobs/decidim/confirmation_reminder_job_spec.rb b/spec/jobs/decidim/confirmation_reminder_job_spec.rb new file mode 100644 index 00000000..88eaa770 --- /dev/null +++ b/spec/jobs/decidim/confirmation_reminder_job_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::ConfirmationReminderJob do + subject { described_class } + + let!(:unconfirmed_users) { create_list(:user, 2, created_at: 2.days.ago) } + + before do + create(:user, created_at: 3.days.ago) + create(:user, :confirmed, created_at: 2.days.ago) + create(:user, created_at: 1.day.ago) + end + + it "sends a reminder to unconfirmed users" do + expect { subject.new.perform }.to change { ActionMailer::Base.deliveries.count }.by(2) + end + + context "when confirmation reminder is set to 3 days" do + before do + allow(Rails.application.secrets).to receive(:dig).and_call_original + allow(Rails.application.secrets).to receive(:dig).with(:decidim, :reminder, :unconfirmed_email, :days).and_return(3) + end + + it "send a unique email to user created there is 3 days ago" do + expect { subject.new.perform }.to change { ActionMailer::Base.deliveries.count }.by(1) + end + end + + describe "#unconfirmed_users" do + it "returns the unconfirmed users" do + expect(subject.new.send(:unconfirmed_users)).to match_array(unconfirmed_users) + end + end +end diff --git a/spec/jobs/decidim/unconfirmed_votes_cleaner_job_spec.rb b/spec/jobs/decidim/unconfirmed_votes_cleaner_job_spec.rb new file mode 100644 index 00000000..df90ec30 --- /dev/null +++ b/spec/jobs/decidim/unconfirmed_votes_cleaner_job_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::UnconfirmedVotesCleanerJob do + subject { described_class } + + let!(:unconfirmed_users) { create_list(:user, 2, created_at: 7.days.ago) } + let!(:confirmed_user) { create(:user, :confirmed, created_at: 7.days.ago) } + let!(:unconfirmed_votes) { create_list(:initiative_user_vote, 3, author: unconfirmed_users.first) } + let!(:confirmed_votes) { create_list(:initiative_user_vote, 3, author: confirmed_user) } + + before do + allow(Decidim.unconfirmed_access_for).to receive(:ago).and_return(7.days.ago) + create(:user, created_at: 30.days.ago) + create(:user, :confirmed, created_at: 2.days.ago) + create(:user, created_at: 1.day.ago) + end + + it "sends a reminder to unconfirmed users" do + expect { subject.new.perform }.to change { ActionMailer::Base.deliveries.count }.by(1) + end + + describe "#unconfirmed_users" do + it "returns the unconfirmed user with initiatives votes" do + expect(subject.new.send(:unconfirmed_users).count).to eq(1) + expect(subject.new.send(:unconfirmed_users)).to include(unconfirmed_users.first) + expect(subject.new.send(:unconfirmed_users)).not_to include(unconfirmed_users.last) + end + end +end diff --git a/spec/mailers/confirmation_reminder_mailer_spec.rb b/spec/mailers/confirmation_reminder_mailer_spec.rb new file mode 100644 index 00000000..08a5ad16 --- /dev/null +++ b/spec/mailers/confirmation_reminder_mailer_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + describe ConfirmationReminderMailer, type: :mailer do + let(:user) { create(:user, name: "Sarah Connor", organization: organization) } + let(:organization) { create(:organization) } + + describe "#send_reminder" do + let(:mail) { described_class.send_reminder(user) } + + it "parses the subject" do + expect(mail.subject).to eq("Please confirm your email address") + end + + it "parses the body" do + expect(email_body(mail)).to include("You have created an account there is 2 days ago and you haven't confirmed your email yet.") + expect(email_body(mail)).to include("Please confirm your email address to persist your votes.") + expect(email_body(mail)).to include("If you don't confirm your email address, your votes will be deleted.") + end + + context "when the user has a different locale" do + before do + user.locale = "fr" + user.save! + end + + it "parses the subject in the user's locale" do + expect(mail.subject).to eq("Veuillez confirmer votre compte") + end + + it "parses the body in the user's locale" do + expect(email_body(mail)).to include("Vous avez créé un compte il y a 2 jours mais vous ne l'avez pas encore confirmé.") + expect(email_body(mail)).to include("Veuillez confirmer votre compte en cliquant sur le lien ci-dessous.") + expect(email_body(mail)).to include("L'ensemble de vos votes seront supprimés dans 6 jours si votre compte n'est pas confirmé dans ce délai.") + end + end + end + end +end diff --git a/spec/mailers/unconfirmed_votes_clear_mailer_spec.rb b/spec/mailers/unconfirmed_votes_clear_mailer_spec.rb new file mode 100644 index 00000000..410fa262 --- /dev/null +++ b/spec/mailers/unconfirmed_votes_clear_mailer_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + describe UnconfirmedVotesClearMailer, type: :mailer do + let(:organization) { create(:organization) } + let(:user) { create(:user, name: "Sarah Connor", organization: organization) } + let(:unconfirmed_votes) { create_list(:initiative_user_vote, 3, author: user) } + let(:initiatives) { unconfirmed_votes.map(&:initiative) } + + let(:confirmed_user) { create(:user, :confirmed, organization: organization) } + let(:confirmed_votes) { create_list(:initiative_user_vote, 3, author: confirmed_user) } + + describe "#send_resume" do + let(:mail) { described_class.send_resume(user, initiatives) } + + it "parses the subject" do + expect(mail.subject).to eq("You did not confirm your account, existing votes have been deleted") + end + + it "parses the body" do + expect(email_body(mail)).to include("You did not confirm your account, existing votes have been deleted") + expect(email_body(mail)).to include("You can still confirm your account and vote again on initiatives.") + initiatives.each do |initiative| + expect(email_body(mail)).to include("Your vote on #{translated(initiative.title)} has been deleted.") + end + end + + context "when the user has a different locale" do + before do + user.locale = "fr" + user.save! + end + + it "parses the subject in the user's locale" do + expect(mail.subject).to eq("Vous n'avez pas confirmé votre compte, vos votes ont été supprimés") + end + + it "parses the body in the user's locale" do + expect(email_body(mail)).to include("Vous n'avez pas confirmé votre compte, les votes que vous avez fait ont été supprimés") + expect(email_body(mail)).to include("Vous pouvez toujours confirmer votre compte et voter à nouveau sur les pétitions.") + initiatives.each do |initiative| + expect(email_body(mail)).to include("Votre vote sur la pétition '#{translated(initiative.title)}' a été supprimé.") + end + end + end + end + end +end From 1c87cbd52617954a5486240938463cfb3ab16ae9 Mon Sep 17 00:00:00 2001 From: quentinchampenois <26109239+Quentinchampenois@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:09:59 +0100 Subject: [PATCH 06/19] fix: Omniauth registration form --- app/packs/entrypoints/application.js | 21 +---- app/packs/src/omniauth_registration.js | 3 + app/packs/src/signup_form.js | 2 + .../omniauth_registrations/new.html.erb | 85 +++++++++++++++++++ config/application.rb | 1 + .../omniauth_registration_form_extend.rb | 37 ++++++++ 6 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 app/packs/src/omniauth_registration.js create mode 100644 app/views/decidim/devise/omniauth_registrations/new.html.erb create mode 100644 lib/extends/forms/decidim/omniauth_registration_form_extend.rb diff --git a/app/packs/entrypoints/application.js b/app/packs/entrypoints/application.js index 26794cee..7517a595 100644 --- a/app/packs/entrypoints/application.js +++ b/app/packs/entrypoints/application.js @@ -1,21 +1,2 @@ -/* eslint no-console:0 */ -// This file is automatically compiled by Webpack, along with any other files -// present in this directory. You're encouraged to place your actual application logic in -// a relevant structure within app/packs and only use these pack files to reference -// that code so it'll be compiled. -// -// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate -// layout file, like app/views/layouts/application.html.erb +import "src/signup_form.js" -// Uncomment to copy all static images under ../images to the output folder and reference -// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) -// or the `imagePath` JavaScript helper below. -// -// const images = require.context('../images', true) -// const imagePath = (name) => images(name, true) - -// Activate Active Storage -// import * as ActiveStorage from "@rails/activestorage" -// ActiveStorage.start() - -import "src/signup_form.js" \ No newline at end of file diff --git a/app/packs/src/omniauth_registration.js b/app/packs/src/omniauth_registration.js new file mode 100644 index 00000000..b0d27b45 --- /dev/null +++ b/app/packs/src/omniauth_registration.js @@ -0,0 +1,3 @@ +$(() => { + $("#new_user label[for='user_birth_date'] select").wrapAll('
'); +}); \ No newline at end of file diff --git a/app/packs/src/signup_form.js b/app/packs/src/signup_form.js index 83bac911..f8563ada 100644 --- a/app/packs/src/signup_form.js +++ b/app/packs/src/signup_form.js @@ -1,5 +1,7 @@ $(document).ready(() => { $("label[for='registration_user_birth_date'] select").wrapAll('
'); + // Omniauth registration form + $("#new_user label[for='user_birth_date'] select").wrapAll('
'); // Sélection des éléments du DOM const passwordInput = document.getElementById('registration_user_password'); diff --git a/app/views/decidim/devise/omniauth_registrations/new.html.erb b/app/views/decidim/devise/omniauth_registrations/new.html.erb new file mode 100644 index 00000000..9e49a1ca --- /dev/null +++ b/app/views/decidim/devise/omniauth_registrations/new.html.erb @@ -0,0 +1,85 @@ +
+
+ + <% if @form.errors[:minimum_age].present? %> +
+
+
+ <%= @form.errors[:minimum_age].join %> +
+
+
+
+
+
+
+ <%= link_to t("decidim.errors.not_found.back_home"), root_path, class: "button hollow expanded" %> +
+
+ <% else %> +
+
+

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

+

+ <%= t(".subtitle") %> +

+
+
+ +
+
+
+ <%== t(".registration_info") %> +
+ +
+
+ <%= decidim_form_for(@form, as: resource_name, url: omniauth_registrations_path(resource_name), html: { class: "register-form new_user" }) do |f| %> +
+

<%= t(".personal_data_step") %>

+ +
+
+ <%= f.date_select :birth_date, { label: t(".birth_date"), start_year: Date.today.year - 116, end_year: Date.today.year - 16 }, { class: "columns medium-3" } %> +
+
+ <%= t(".birth_date_help") %> +
+
+ +
+ <%= f.text_field :address, autocomplete: "street-address", label: t(".address") %> +
+ +
+
+
+ <%= f.text_field :postal_code, pattern: "^[0-9]+$", minlength: 5, maxlength: 5, autocomplete: "postal-code", label: t(".code") %> +
+
+
+
+ <%= f.text_field :city, label: t(".city") %> +
+
+
+
+ + <%= f.hidden_field :email %> + <%= f.hidden_field :uid %> + <%= f.hidden_field :name %> + <%= f.hidden_field :provider %> + <%= f.hidden_field :oauth_signature %> +
+ <%= f.submit t(".complete_profile"), class: "button expanded" %> +
+ <% end %> +
+
+
+
+ <% end %> +
+
+ +<%= javascript_pack_tag "application" %> \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index bc76d56f..65f60ac4 100644 --- a/config/application.rb +++ b/config/application.rb @@ -50,6 +50,7 @@ class Application < Rails::Application require "extends/controllers/decidim/homepage_controller_extends" require "extends/forms/decidim/admin/organization_appearance_form_extends" require "extends/omniauth/strategies/france_connect_extends" + require "extends/forms/decidim/omniauth_registration_form_extend" end initializer "session cookie domain", after: "Expire sessions" do diff --git a/lib/extends/forms/decidim/omniauth_registration_form_extend.rb b/lib/extends/forms/decidim/omniauth_registration_form_extend.rb new file mode 100644 index 00000000..4800cd49 --- /dev/null +++ b/lib/extends/forms/decidim/omniauth_registration_form_extend.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "active_support/concern" + +module OmniauthRegistrationFormExtend + extend ActiveSupport::Concern + + included do + attribute :birth_date, Date + attribute :address, String + attribute :postal_code, String + attribute :city, String + + validates :email, 'valid_email_2/email': { mx: true } + validates :postal_code, + :birth_date, + :city, + :address, + presence: true + + validate :over_16? + + private + + def over_16? + return if birth_date.blank? + return if 16.years.ago.to_date > birth_date + + errors.add :base, I18n.t("decidim.devise.registrations.form.errors.messages.over_16") + end + end +end + +Decidim::OmniauthRegistrationForm.class_eval do + clear_validators! + include OmniauthRegistrationFormExtend +end \ No newline at end of file From ee9265f916ac246264068947999b13fbe5a569d2 Mon Sep 17 00:00:00 2001 From: quentinchampenois <26109239+Quentinchampenois@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:26:26 +0100 Subject: [PATCH 07/19] fix: Birthdate field format --- app/packs/src/signup_form.js | 46 +++++++++++-------- app/packs/stylesheets/signup_form.scss | 4 +- .../omniauth_registrations/new.html.erb | 3 +- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/app/packs/src/signup_form.js b/app/packs/src/signup_form.js index f8563ada..3daf02d1 100644 --- a/app/packs/src/signup_form.js +++ b/app/packs/src/signup_form.js @@ -12,7 +12,9 @@ $(document).ready(() => { const fieldElements = document.querySelectorAll('.field'); // Masquer la classe "user-nickname" - userNicknameField.style.display = 'none'; + if (userNicknameField !== null) { + userNicknameField.style.display = 'none'; + } // Masquer la classe "field" contenant la classe "registration_user_password_confirmation" fieldElements.forEach(field => { @@ -23,16 +25,18 @@ $(document).ready(() => { }); // // Écouteur d'événement sur le champ du mot de passe - passwordInput.addEventListener('input', function() { - // If there is a form-error behind the password field, remove it - const passwordField = passwordInput.parentElement; - const passwordError = passwordField.querySelector('.form-error'); - if (passwordError !== null) { - changeMessage(passwordError) - } + if (passwordInput !== null) { + passwordInput.addEventListener('input', function() { + // If there is a form-error behind the password field, remove it + const passwordField = passwordInput.parentElement; + const passwordError = passwordField.querySelector('.form-error'); + if (passwordError !== null) { + changeMessage(passwordError) + } - confirmPasswordInput.value = passwordInput.value; - }); + confirmPasswordInput.value = passwordInput.value; + }); + } function changeMessage(passwordError) { const password = passwordInput.value; @@ -44,16 +48,18 @@ $(document).ready(() => { } // Génération automatique du surnom à partir du champ du nom - userNameInput.addEventListener('input', function() { - const userName = userNameInput.value.toLowerCase().replace(/\s/g, '_').replace(/[^\w\s]/gi, ''); // Remplacement des espaces par des underscores, suppression des caractères spéciaux et mise en minuscules - const randomNum = Math.floor(100000 + Math.random() * 900000); // Génération d'un nombre aléatoire à 6 chiffres - const generatedNickname = userName + '_' + randomNum; + if (userNameInput !== null) { + userNameInput.addEventListener('input', function() { + const userName = userNameInput.value.toLowerCase().replace(/\s/g, '_').replace(/[^\w\s]/gi, ''); // Remplacement des espaces par des underscores, suppression des caractères spéciaux et mise en minuscules + const randomNum = Math.floor(100000 + Math.random() * 900000); // Génération d'un nombre aléatoire à 6 chiffres + const generatedNickname = userName + '_' + randomNum; - // Remplissage automatique du champ du surnom - userNicknameInput.value = generatedNickname.substr(0, 20); // Limite le surnom à 20 caractères + // Remplissage automatique du champ du surnom + userNicknameInput.value = generatedNickname.substr(0, 20); // Limite le surnom à 20 caractères - // Cacher les champs générés automatiquement - confirmPasswordInput.style.display = 'none'; // Cacher le champ de confirmation du mot de passe - userNicknameInput.style.display = 'none'; // Cacher le champ du surnom - }); + // Cacher les champs générés automatiquement + confirmPasswordInput.style.display = 'none'; // Cacher le champ de confirmation du mot de passe + userNicknameInput.style.display = 'none'; // Cacher le champ du surnom + }); + } }) diff --git a/app/packs/stylesheets/signup_form.scss b/app/packs/stylesheets/signup_form.scss index 4eb7fdda..388efd79 100644 --- a/app/packs/stylesheets/signup_form.scss +++ b/app/packs/stylesheets/signup_form.scss @@ -1,4 +1,4 @@ -#register-form { +#register-form, #new_user { .select-date-container { display: flex; flex-direction: row; @@ -10,4 +10,4 @@ .select-date-container .columns { padding-right: 1.4rem; } -} +} \ No newline at end of file diff --git a/app/views/decidim/devise/omniauth_registrations/new.html.erb b/app/views/decidim/devise/omniauth_registrations/new.html.erb index 9e49a1ca..587e33e7 100644 --- a/app/views/decidim/devise/omniauth_registrations/new.html.erb +++ b/app/views/decidim/devise/omniauth_registrations/new.html.erb @@ -82,4 +82,5 @@
-<%= javascript_pack_tag "application" %> \ No newline at end of file +<%= javascript_pack_tag "application" %> +<%= stylesheet_pack_tag "application" %> \ No newline at end of file From c7ded196d6bcdd0708329115da3cb83a5fdad120 Mon Sep 17 00:00:00 2001 From: quentinchampenois <26109239+Quentinchampenois@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:44:55 +0100 Subject: [PATCH 08/19] fix: Save Extended data for omniauth registrations --- .../decidim/create_omniauth_registration.rb | 147 ++++++++++++++++++ .../omniauth_registrations/new.html.erb | 6 +- .../omniauth_registration_form_extend.rb | 8 +- 3 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 app/commands/decidim/create_omniauth_registration.rb diff --git a/app/commands/decidim/create_omniauth_registration.rb b/app/commands/decidim/create_omniauth_registration.rb new file mode 100644 index 00000000..36a79b96 --- /dev/null +++ b/app/commands/decidim/create_omniauth_registration.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +module Decidim + # A command with all the business logic to create a user from omniauth + class CreateOmniauthRegistration < Decidim::Command + # Public: Initializes the command. + # + # form - A form object with the params. + def initialize(form, verified_email = nil) + @form = form + @verified_email = verified_email + end + + # Executes the command. Broadcasts these events: + # + # - :ok when everything is valid. + # - :invalid if the form wasn't valid and we couldn't proceed. + # + # Returns nothing. + def call + verify_oauth_signature! + + begin + if existing_identity + user = existing_identity.user + verify_user_confirmed(user) + + return broadcast(:ok, user) + end + return broadcast(:invalid) if form.invalid? + + transaction do + create_or_find_user + @identity = create_identity + end + trigger_omniauth_registration + + broadcast(:ok, @user) + rescue ActiveRecord::RecordInvalid => e + broadcast(:error, e.record) + end + end + + private + + attr_reader :form, :verified_email + + def create_or_find_user + generated_password = SecureRandom.hex + + @user = User.find_or_initialize_by( + email: verified_email, + organization: organization + ) + + if @user.persisted? + # If user has left the account unconfirmed and later on decides to sign + # in with omniauth with an already verified account, the account needs + # to be marked confirmed. + @user.skip_confirmation! if !@user.confirmed? && @user.email == verified_email + else + @user.email = (verified_email || form.email) + @user.name = form.name + @user.nickname = form.normalized_nickname + @user.newsletter_notifications_at = nil + @user.password = generated_password + @user.password_confirmation = generated_password + if form.avatar_url.present? + url = URI.parse(form.avatar_url) + filename = File.basename(url.path) + file = url.open + @user.avatar.attach(io: file, filename: filename) + end + @user.extended_data = extended_data + @user.skip_confirmation! if verified_email + end + + @user.tos_agreement = "1" + @user.save! + end + + def create_identity + @user.identities.create!( + provider: form.provider, + uid: form.uid, + organization: organization + ) + end + + def organization + @form.current_organization + end + + def existing_identity + @existing_identity ||= Identity.find_by( + user: organization.users, + provider: form.provider, + uid: form.uid + ) + end + + def verify_user_confirmed(user) + return true if user.confirmed? + return false if user.email != verified_email + + user.skip_confirmation! + user.save! + end + + def verify_oauth_signature! + raise InvalidOauthSignature, "Invalid oauth signature: #{form.oauth_signature}" unless signature_valid? + end + + def signature_valid? + signature = OmniauthRegistrationForm.create_signature(form.provider, form.uid) + form.oauth_signature == signature + end + + def trigger_omniauth_registration + ActiveSupport::Notifications.publish( + "decidim.user.omniauth_registration", + user_id: @user.id, + identity_id: @identity.id, + provider: form.provider, + uid: form.uid, + email: form.email, + name: form.name, + nickname: form.normalized_nickname, + avatar_url: form.avatar_url, + raw_data: form.raw_data + ) + end + + def extended_data + { + birth_date: form.birth_date, + address: form.address, + postal_code: form.postal_code, + city: form.city, + certification: form&.certification + } + end + end + + class InvalidOauthSignature < StandardError + end +end diff --git a/app/views/decidim/devise/omniauth_registrations/new.html.erb b/app/views/decidim/devise/omniauth_registrations/new.html.erb index 587e33e7..764f58a3 100644 --- a/app/views/decidim/devise/omniauth_registrations/new.html.erb +++ b/app/views/decidim/devise/omniauth_registrations/new.html.erb @@ -40,7 +40,7 @@
- <%= f.date_select :birth_date, { label: t(".birth_date"), start_year: Date.today.year - 116, end_year: Date.today.year - 16 }, { class: "columns medium-3" } %> + <%= f.date_select :birth_date, { label: t(".birth_date"), start_year: Date.today.year - 100, end_year: Date.today.year }, { class: "columns medium-3" } %>
<%= t(".birth_date_help") %> @@ -63,6 +63,10 @@
+ +
+ <%= f.check_box :certification, label: t(".certification") %> +
<%= f.hidden_field :email %> diff --git a/lib/extends/forms/decidim/omniauth_registration_form_extend.rb b/lib/extends/forms/decidim/omniauth_registration_form_extend.rb index 4800cd49..9939679d 100644 --- a/lib/extends/forms/decidim/omniauth_registration_form_extend.rb +++ b/lib/extends/forms/decidim/omniauth_registration_form_extend.rb @@ -6,18 +6,22 @@ module OmniauthRegistrationFormExtend extend ActiveSupport::Concern included do + attribute :certification, ::ActiveModel::Type::Boolean attribute :birth_date, Date attribute :address, String attribute :postal_code, String attribute :city, String - validates :email, 'valid_email_2/email': { mx: true } + validates :email, "valid_email_2/email": { mx: true } validates :postal_code, :birth_date, :city, :address, + :certification, presence: true + validates :postal_code, numericality: { only_integer: true }, length: { is: 5 } + validates :certification, acceptance: true validate :over_16? private @@ -34,4 +38,4 @@ def over_16? Decidim::OmniauthRegistrationForm.class_eval do clear_validators! include OmniauthRegistrationFormExtend -end \ No newline at end of file +end From bfd90b1f5e6fc7d06bb3cb5265f5c92c14728c0d Mon Sep 17 00:00:00 2001 From: AyakorK Date: Wed, 6 Dec 2023 18:46:59 +0100 Subject: [PATCH 09/19] fix: Add missing locales --- config/locales/en.yml | 16 ++++++++++++++++ config/locales/fr.yml | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index bfc37803..c2daf185 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -59,6 +59,19 @@ en: warning: If you don't confirm your email address, your votes will be deleted. subject: Please confirm your email address devise: + omniauth_registrations: + new: + address: Address + birth_date: Date of birth + birth_date_help: You must be over 16 years old to access this service. + certification: CESE Certification + city: City + code: Postal code + complete_profile: Complete your profile + personal_data_step: Personal data + registration_info: Please fill your registration data + sign_up: Sign up + subtitle: Create your account registrations: form: errors: @@ -75,6 +88,9 @@ en: sessions: new: sign_in_disabled: Sign in disabled + errors: + not_found: + back_home: Back to home events: budgets: pending_order: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 3389350c..5f1b871c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -59,6 +59,19 @@ fr: warning: L'ensemble de vos votes seront supprimés dans 6 jours si votre compte n'est pas confirmé dans ce délai. subject: Veuillez confirmer votre compte devise: + omniauth_registrations: + new: + address: Addresse + birth_date: Date de naissance + birth_date_help: Vous devez avoir plus de 16 ans pour avoir accès à ce service. + certification: Certification CESE + city: Ville + code: Code postal + complete_profile: Complétez votre profil + personal_data_step: Informations personnelles + registration_info: Renseignez vos informations personnelles + sign_up: Renseignez vos informations personnelles + subtitle: Vous êtes sur le point de créer un compte sur la plateforme de pétitions du CESE. registrations: form: errors: @@ -75,6 +88,9 @@ fr: sessions: new: sign_in_disabled: Vous pouvez accéder avec un compte externe + errors: + not_found: + back_home: Retour à l'accueil events: budgets: pending_order: From 6525fb1cd77227329e11f5d9c39174dc46868cff Mon Sep 17 00:00:00 2001 From: quentinchampenois <26109239+Quentinchampenois@users.noreply.github.com> Date: Wed, 6 Dec 2023 19:17:45 +0100 Subject: [PATCH 10/19] fix: Remove system omniauth specs --- spec/system/authentication_spec.rb | 194 +---------------------------- 1 file changed, 1 insertion(+), 193 deletions(-) diff --git a/spec/system/authentication_spec.rb b/spec/system/authentication_spec.rb index e3f02073..35a19f4c 100644 --- a/spec/system/authentication_spec.rb +++ b/spec/system/authentication_spec.rb @@ -38,7 +38,7 @@ end end - context "when using another langage" do + context "when using another language" do before do within_language_menu do click_link "Français" @@ -96,155 +96,6 @@ end end - context "when using facebook" do - let(:omniauth_hash) do - OmniAuth::AuthHash.new( - provider: "facebook", - uid: "123545", - info: { - email: "user@from-facebook.com", - name: "Facebook User" - } - ) - end - - before do - OmniAuth.config.test_mode = true - OmniAuth.config.mock_auth[:facebook] = omniauth_hash - OmniAuth.config.add_camelization "facebook", "FaceBook" - OmniAuth.config.request_validation_phase = ->(env) {} if OmniAuth.config.respond_to?(:request_validation_phase) - end - - after do - OmniAuth.config.test_mode = false - OmniAuth.config.mock_auth[:facebook] = nil - OmniAuth.config.camelizations.delete("facebook") - end - - context "when the user has confirmed the email in facebook" do - it "creates a new User without sending confirmation instructions" do - find(".sign-up-link").click - - click_link "Sign in with Facebook" - - expect(page).to have_content("Successfully") - expect_user_logged - end - end - end - - context "when using twitter" do - let(:email) { nil } - let(:omniauth_hash) do - OmniAuth::AuthHash.new( - provider: "twitter", - uid: "123545", - info: { - name: "Twitter User", - nickname: "twitter_user", - email: email - } - ) - end - - before do - OmniAuth.config.test_mode = true - OmniAuth.config.mock_auth[:twitter] = omniauth_hash - - OmniAuth.config.add_camelization "twitter", "Twitter" - OmniAuth.config.request_validation_phase = ->(env) {} if OmniAuth.config.respond_to?(:request_validation_phase) - end - - after do - OmniAuth.config.test_mode = false - OmniAuth.config.mock_auth[:twitter] = nil - OmniAuth.config.camelizations.delete("twitter") - end - - context "when the response doesn't include the email" do - it "redirects the user to a finish signup page" do - find(".sign-up-link").click - - click_link "Sign in with Twitter" - - expect(page).to have_content("Successfully") - expect(page).to have_content("Please complete your profile") - - within ".new_user" do - fill_in :registration_user_email, with: "user@from-twitter.com" - find("*[type=submit]").click - end - end - - context "and a user already exists with the given email" do - it "doesn't allow it" do - create(:user, :confirmed, email: "user@from-twitter.com", organization: organization) - find(".sign-up-link").click - - click_link "Sign in with Twitter" - - expect(page).to have_content("Successfully") - expect(page).to have_content("Please complete your profile") - - within ".new_user" do - fill_in :registration_user_email, with: "user@from-twitter.com" - find("*[type=submit]").click - end - - expect(page).to have_content("Please complete your profile") - expect(page).to have_content("Another account is using the same email address") - end - end - end - - context "when the response includes the email" do - let(:email) { "user@from-twitter.com" } - - it "creates a new User" do - find(".sign-up-link").click - - click_link "Sign in with Twitter" - - expect_user_logged - end - end - end - - context "when using google" do - let(:omniauth_hash) do - OmniAuth::AuthHash.new( - provider: "google_oauth2", - uid: "123545", - info: { - name: "Google User", - email: "user@from-google.com" - } - ) - end - - before do - OmniAuth.config.test_mode = true - OmniAuth.config.mock_auth[:google_oauth2] = omniauth_hash - - OmniAuth.config.add_camelization "google_oauth2", "GoogleOauth" - OmniAuth.config.request_validation_phase = ->(env) {} if OmniAuth.config.respond_to?(:request_validation_phase) - end - - after do - OmniAuth.config.test_mode = false - OmniAuth.config.mock_auth[:google_oauth2] = nil - OmniAuth.config.camelizations.delete("google_oauth2") - end - - it "creates a new User" do - find(".sign-up-link").click - - click_link "Sign in with Google" - - expect_user_logged - end - end - context "when sign up is disabled" do let(:organization) { create(:organization, users_registration_mode: :existing) } @@ -617,49 +468,6 @@ end end - context "when a user is already registered in another organization with the same fb account" do - let(:user) { create(:user, :confirmed) } - let(:identity) { create(:identity, user: user, provider: "facebook", uid: "12345") } - - let(:omniauth_hash) do - OmniAuth::AuthHash.new( - provider: identity.provider, - uid: identity.uid, - info: { - email: user.email, - name: "Facebook User", - verified: true - } - ) - end - - before do - OmniAuth.config.test_mode = true - OmniAuth.config.mock_auth[:facebook] = omniauth_hash - OmniAuth.config.add_camelization "facebook", "FaceBook" - OmniAuth.config.request_validation_phase = ->(env) {} if OmniAuth.config.respond_to?(:request_validation_phase) - end - - after do - OmniAuth.config.test_mode = false - OmniAuth.config.mock_auth[:facebook] = nil - OmniAuth.config.camelizations.delete("facebook") - end - - describe "Sign Up" do - context "when the user has confirmed the email in facebook" do - it "creates a new User without sending confirmation instructions" do - find(".sign-up-link").click - - click_link "Sign in with Facebook" - - expect(page).to have_content("Successfully") - expect_user_logged - end - end - end - end - context "when a user with the same email is already registered in another organization" do let(:organization2) { create(:organization) } From 8c897758af17d0aaf22220052f5147fa9f03237a Mon Sep 17 00:00:00 2001 From: AyakorK Date: Wed, 6 Dec 2023 19:20:42 +0100 Subject: [PATCH 11/19] fix: Fix locales --- config/locales/en.yml | 6 +++--- config/locales/fr.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index c2daf185..802d1bc6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -67,9 +67,9 @@ en: certification: CESE Certification city: City code: Postal code - complete_profile: Complete your profile - personal_data_step: Personal data - registration_info: Please fill your registration data + complete_profile: Complete profile + personal_data_step: Complete your profile + registration_info: Please complete your profile sign_up: Sign up subtitle: Create your account registrations: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5f1b871c..8f700c37 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -68,7 +68,7 @@ fr: city: Ville code: Code postal complete_profile: Complétez votre profil - personal_data_step: Informations personnelles + personal_data_step: Complétez votre profil registration_info: Renseignez vos informations personnelles sign_up: Renseignez vos informations personnelles subtitle: Vous êtes sur le point de créer un compte sur la plateforme de pétitions du CESE. From 713e4664aebc51d110d5c71b846694b60aa18e04 Mon Sep 17 00:00:00 2001 From: quentinchampenois <26109239+Quentinchampenois@users.noreply.github.com> Date: Wed, 6 Dec 2023 19:53:29 +0100 Subject: [PATCH 12/19] fix: Omniauth registration form --- .../forms/decidim/omniauth_registration_form_extend.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/extends/forms/decidim/omniauth_registration_form_extend.rb b/lib/extends/forms/decidim/omniauth_registration_form_extend.rb index 9939679d..785007a6 100644 --- a/lib/extends/forms/decidim/omniauth_registration_form_extend.rb +++ b/lib/extends/forms/decidim/omniauth_registration_form_extend.rb @@ -18,10 +18,10 @@ module OmniauthRegistrationFormExtend :city, :address, :certification, - presence: true + presence: true, unless: ->(form) { form.certification.blank? } - validates :postal_code, numericality: { only_integer: true }, length: { is: 5 } - validates :certification, acceptance: true + validates :postal_code, numericality: { only_integer: true }, length: { is: 5 }, unless: ->(form) { form.certification.blank? } + validates :certification, acceptance: true, presence: true validate :over_16? private @@ -31,6 +31,7 @@ def over_16? return if 16.years.ago.to_date > birth_date errors.add :base, I18n.t("decidim.devise.registrations.form.errors.messages.over_16") + errors.add :birth_date, I18n.t("decidim.devise.registrations.form.errors.messages.over_16") end end end From db81f7e44737015639908af7b43f0e459b0b36aa Mon Sep 17 00:00:00 2001 From: moustachu Date: Wed, 6 Dec 2023 22:14:26 +0100 Subject: [PATCH 13/19] feat: Remove decidim-extended_socio_demographic_authorization_handler gem --- .gitignore | 4 +++- Gemfile | 2 -- Gemfile.lock | 9 --------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 09433732..20ec71bf 100644 --- a/.gitignore +++ b/.gitignore @@ -95,4 +95,6 @@ vendor/cache yarn-debug.log* .yarn-integrity *.rubocop-https* -certificate-https-local/ \ No newline at end of file +certificate-https-local/ + +.DS_Store \ No newline at end of file diff --git a/Gemfile b/Gemfile index 088f60db..000f9128 100644 --- a/Gemfile +++ b/Gemfile @@ -17,8 +17,6 @@ gem "decidim-initiatives", "~> #{DECIDIM_VERSION}.0" # External Decidim gems gem "decidim-blog_author_petition", git: "https://github.com/OpenSourcePolitics/decidim-module-blog_author_petition.git", branch: "main" gem "decidim-decidim_awesome", git: "https://github.com/decidim-ice/decidim-module-decidim_awesome.git", branch: "main" -gem "decidim-extended_socio_demographic_authorization_handler", git: "https://github.com/OpenSourcePolitics/decidim-module-extended_socio_demographic_authorization_handler.git", - branch: "cese" gem "decidim-initiative_status", git: "https://github.com/OpenSourcePolitics/decidim-module-initiative_status.git", branch: "main" gem "decidim-spam_detection" gem "decidim-term_customizer", git: "https://github.com/armandfardeau/decidim-module-term_customizer.git", branch: "fix/precompile-on-docker-0.27" diff --git a/Gemfile.lock b/Gemfile.lock index 54f9adb0..fcab453c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,14 +6,6 @@ GIT decidim-blog_author_petition (0.1) decidim-core (~> 0.27) -GIT - remote: https://github.com/OpenSourcePolitics/decidim-module-extended_socio_demographic_authorization_handler.git - revision: f51e6314e014fe89156b1115dbd7fe14ecaa5565 - branch: cese - specs: - decidim-extended_socio_demographic_authorization_handler (0.26.1) - decidim-core (~> 0.27) - GIT remote: https://github.com/OpenSourcePolitics/decidim-module-initiative_status.git revision: eae4e5440fc84adac72097afb6e06ec172795dd4 @@ -1032,7 +1024,6 @@ DEPENDENCIES decidim-blog_author_petition! decidim-decidim_awesome! decidim-dev (~> 0.27.0) - decidim-extended_socio_demographic_authorization_handler! decidim-initiative_status! decidim-initiatives (~> 0.27.0) decidim-spam_detection From 44435e315c31cc9b93d0f779cc53abcf6fffed5d Mon Sep 17 00:00:00 2001 From: moustachu Date: Thu, 7 Dec 2023 00:45:11 +0100 Subject: [PATCH 14/19] add migration for decidim-extended_socio_demographic_authorization_handler data --- ...ization_handler_settings_on_initiatives.rb | 21 ++++++ ..._demographic_authorization_handler_data.rb | 21 ++++++ db/schema.rb | 66 ++++++++++++++++++- 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20231206212031_delete_extended_socio_demographic_authorization_handler_settings_on_initiatives.rb create mode 100644 db/migrate/20231206230804_migrate_extended_socio_demographic_authorization_handler_data.rb diff --git a/db/migrate/20231206212031_delete_extended_socio_demographic_authorization_handler_settings_on_initiatives.rb b/db/migrate/20231206212031_delete_extended_socio_demographic_authorization_handler_settings_on_initiatives.rb new file mode 100644 index 00000000..01869589 --- /dev/null +++ b/db/migrate/20231206212031_delete_extended_socio_demographic_authorization_handler_settings_on_initiatives.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class DeleteExtendedSocioDemographicAuthorizationHandlerSettingsOnInitiatives < ActiveRecord::Migration[6.1] + def change + Decidim::InitiativesType.where(document_number_authorization_handler: "extended_socio_demographic_authorization_handler").each do |initiatives_type| + initiatives_type.update!(document_number_authorization_handler: nil) + end + + Decidim::ResourcePermission.all.each do |resource_permission| + next if resource_permission.permissions.blank? + + resource_permission.permissions.each do |action, authorization| + if authorization.has_key?("authorization_handlers") && authorization["authorization_handlers"].has_key?("extended_socio_demographic_authorization_handler") + resource_permission.permissions.delete(action) + end + end + + resource_permission.save! if resource_permission.changed? + end + end +end diff --git a/db/migrate/20231206230804_migrate_extended_socio_demographic_authorization_handler_data.rb b/db/migrate/20231206230804_migrate_extended_socio_demographic_authorization_handler_data.rb new file mode 100644 index 00000000..11ba7b92 --- /dev/null +++ b/db/migrate/20231206230804_migrate_extended_socio_demographic_authorization_handler_data.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class MigrateExtendedSocioDemographicAuthorizationHandlerData < ActiveRecord::Migration[6.1] + def change + Decidim::Authorization.where(name: "extended_socio_demographic_authorization_handler").each do |authorization| + next if authorization.user.deleted? + + # We don't migrate the email because we don't want to confirm it if needed + authorization.user.name = "#{authorization.metadata["first_mane"]} #{authorization.metadata["first_mane"]}" + authorization.user.extended_data = { + city: authorization.metadata["city"], + address: authorization.metadata["address"], + birth_date: authorization.metadata["birth_date"], + postal_code: authorization.metadata["postal_code"], + certification: authorization.metadata["certification"] + } + authorization.user.save + authorization.delete + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 8275c50a..66c651fd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_05_12_092845) do +ActiveRecord::Schema.define(version: 2023_12_06_230804) do # These are extensions that must be enabled in order to support this database enable_extension "ltree" @@ -577,6 +577,56 @@ t.index ["follows_count"], name: "index_decidim_debates_debates_on_follows_count" end + create_table "decidim_dummy_resources_coauthorable_dummy_resources", force: :cascade do |t| + t.jsonb "translatable_text" + t.string "title" + t.string "body" + t.text "address" + t.float "latitude" + t.float "longitude" + t.datetime "published_at" + t.integer "coauthorships_count", default: 0, null: false + t.integer "endorsements_count", default: 0, null: false + t.integer "comments_count", default: 0, null: false + t.bigint "decidim_component_id" + t.bigint "decidim_category_id" + t.bigint "decidim_scope_id" + t.string "reference" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + + create_table "decidim_dummy_resources_dummy_resources", force: :cascade do |t| + t.jsonb "translatable_text" + t.jsonb "title" + t.string "body" + t.text "address" + t.float "latitude" + t.float "longitude" + t.datetime "published_at" + t.integer "coauthorships_count", default: 0, null: false + t.integer "endorsements_count", default: 0, null: false + t.integer "comments_count", default: 0, null: false + t.integer "follows_count", default: 0, null: false + t.bigint "decidim_component_id" + t.integer "decidim_author_id" + t.string "decidim_author_type" + t.integer "decidim_user_group_id" + t.bigint "decidim_category_id" + t.bigint "decidim_scope_id" + t.string "reference" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + + create_table "decidim_dummy_resources_nested_dummy_resources", force: :cascade do |t| + t.jsonb "translatable_text" + t.string "title" + t.bigint "dummy_resource_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + create_table "decidim_editor_images", force: :cascade do |t| t.bigint "decidim_author_id", null: false t.bigint "decidim_organization_id", null: false @@ -591,7 +641,7 @@ t.bigint "resource_id" t.string "decidim_author_type" t.bigint "decidim_author_id" - t.integer "decidim_user_group_id" + t.integer "decidim_user_group_id", default: 0 t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["decidim_author_type", "decidim_author_id"], name: "idx_endorsements_authors" @@ -1681,6 +1731,18 @@ t.index ["reset_password_token"], name: "index_decidim_system_admins_on_reset_password_token", unique: true end + create_table "decidim_templates_templates", force: :cascade do |t| + t.integer "decidim_organization_id", null: false + t.string "templatable_type" + t.bigint "templatable_id" + t.jsonb "name", null: false + t.jsonb "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["decidim_organization_id"], name: "index_decidim_templates_organization" + t.index ["templatable_type", "templatable_id"], name: "index_decidim_templates_templatable" + end + create_table "decidim_term_customizer_constraints", force: :cascade do |t| t.bigint "decidim_organization_id", null: false t.string "subject_type" From f73358888edd5221277ae43476b2cf79800eeea7 Mon Sep 17 00:00:00 2001 From: moustachu Date: Thu, 7 Dec 2023 19:20:53 +0100 Subject: [PATCH 15/19] Delete ResourcePermission when resource does not exist --- ...ographic_authorization_handler_settings_on_initiatives.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/db/migrate/20231206212031_delete_extended_socio_demographic_authorization_handler_settings_on_initiatives.rb b/db/migrate/20231206212031_delete_extended_socio_demographic_authorization_handler_settings_on_initiatives.rb index 01869589..5fd05b71 100644 --- a/db/migrate/20231206212031_delete_extended_socio_demographic_authorization_handler_settings_on_initiatives.rb +++ b/db/migrate/20231206212031_delete_extended_socio_demographic_authorization_handler_settings_on_initiatives.rb @@ -9,6 +9,11 @@ def change Decidim::ResourcePermission.all.each do |resource_permission| next if resource_permission.permissions.blank? + if resource_permission.resource.blank? + resource_permission.delete + next + end + resource_permission.permissions.each do |action, authorization| if authorization.has_key?("authorization_handlers") && authorization["authorization_handlers"].has_key?("extended_socio_demographic_authorization_handler") resource_permission.permissions.delete(action) From da549c64e74d8f5769964597fb21d8fe145c37fc Mon Sep 17 00:00:00 2001 From: Guillaume MORET <90462045+AyakorK@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:33:34 +0100 Subject: [PATCH 16/19] fix: Typo in french locales (#90) --- config/locales/fr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 8f700c37..dce1e809 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -61,7 +61,7 @@ fr: devise: omniauth_registrations: new: - address: Addresse + address: Adresse birth_date: Date de naissance birth_date_help: Vous devez avoir plus de 16 ans pour avoir accès à ce service. certification: Certification CESE @@ -78,7 +78,7 @@ fr: messages: over_16: Vous devez avoir plus de 16 ans pour accéder à ce service. new: - address: Addresse + address: Adresse birth_date: Date de naissance birth_date_help: Vous devez avoir plus de 16 ans pour avoir accès à ce service. certification: Je certifie l'exactitude de ces informations. From 1a5f09b29a18339f60da13d366f5de43799856a9 Mon Sep 17 00:00:00 2001 From: Guillaume MORET <90462045+AyakorK@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:27:56 +0100 Subject: [PATCH 17/19] fix: Validations were not visually updating when interacting (#91) --- app/packs/entrypoints/application.js | 1 + app/packs/src/confirmation_registration.js | 28 ++++++++++++++++++++++ config/locales/fr.yml | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 app/packs/src/confirmation_registration.js diff --git a/app/packs/entrypoints/application.js b/app/packs/entrypoints/application.js index 7517a595..f11f8716 100644 --- a/app/packs/entrypoints/application.js +++ b/app/packs/entrypoints/application.js @@ -1,2 +1,3 @@ import "src/signup_form.js" +import "src/confirmation_registration.js" diff --git a/app/packs/src/confirmation_registration.js b/app/packs/src/confirmation_registration.js new file mode 100644 index 00000000..88fc7e30 --- /dev/null +++ b/app/packs/src/confirmation_registration.js @@ -0,0 +1,28 @@ +$(document).ready(() => { + $("#user_certification").on("change", function(e) { + const certificationField = event.target.parentNode.parentNode; + const certificationError = certificationField.querySelector('.form-error'); + + if (certificationError) { + certificationError.remove(); + } + }); + + $(".select-date-container").on("change", function(e) { + const dateField = event.target.parentNode.parentNode; + const dateError = dateField.querySelectorAll('.form-error'); + const invalidFields = dateField.querySelectorAll('.is-invalid-input'); + + if (dateError) { + dateError.forEach(error => { + error.remove(); + }); + } + + if (invalidFields) { + invalidFields.forEach(field => { + field.classList.remove('is-invalid-input'); + }); + } + }); +}); \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index dce1e809..9a086a7a 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -64,7 +64,7 @@ fr: address: Adresse birth_date: Date de naissance birth_date_help: Vous devez avoir plus de 16 ans pour avoir accès à ce service. - certification: Certification CESE + certification: Je certifie l'exactitude de ces informations. city: Ville code: Code postal complete_profile: Complétez votre profil From 0ce4a36cab6b966f192ddf495d198d0892f58677 Mon Sep 17 00:00:00 2001 From: Guillaume MORET <90462045+AyakorK@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:52:21 +0100 Subject: [PATCH 18/19] feat: Add TOS to Omniauth Registration Form (#92) * feat: Add tos agreement to the omniauth registration form * feat: Adapt JavaScript to the addition of TOS * fix: Readd a line to force tos_agreement to be accepted --- .../decidim/create_omniauth_registration.rb | 2 ++ app/packs/src/confirmation_registration.js | 11 +++++++++++ .../devise/omniauth_registrations/new.html.erb | 14 ++++++++++++++ config/locales/en.yml | 3 +++ config/locales/fr.yml | 3 +++ .../decidim/omniauth_registration_form_extend.rb | 9 ++++++--- 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/commands/decidim/create_omniauth_registration.rb b/app/commands/decidim/create_omniauth_registration.rb index 36a79b96..d51d82f1 100644 --- a/app/commands/decidim/create_omniauth_registration.rb +++ b/app/commands/decidim/create_omniauth_registration.rb @@ -62,6 +62,8 @@ def create_or_find_user @user.email = (verified_email || form.email) @user.name = form.name @user.nickname = form.normalized_nickname + @user.tos_agreement = form.tos_agreement + @user.accepted_tos_version = form.current_organization.tos_version @user.newsletter_notifications_at = nil @user.password = generated_password @user.password_confirmation = generated_password diff --git a/app/packs/src/confirmation_registration.js b/app/packs/src/confirmation_registration.js index 88fc7e30..7842b8f7 100644 --- a/app/packs/src/confirmation_registration.js +++ b/app/packs/src/confirmation_registration.js @@ -8,6 +8,17 @@ $(document).ready(() => { } }); + $("#user_tos_agreement").on("change", function(e) { + const tosField = event.target.parentNode.parentNode; + const tosError = tosField.querySelectorAll('.form-error'); + + if (tosError) { + tosError.forEach(error => { + error.remove(); + }); + } + }); + $(".select-date-container").on("change", function(e) { const dateField = event.target.parentNode.parentNode; const dateError = dateField.querySelectorAll('.form-error'); diff --git a/app/views/decidim/devise/omniauth_registrations/new.html.erb b/app/views/decidim/devise/omniauth_registrations/new.html.erb index 764f58a3..6180d1ea 100644 --- a/app/views/decidim/devise/omniauth_registrations/new.html.erb +++ b/app/views/decidim/devise/omniauth_registrations/new.html.erb @@ -69,6 +69,20 @@
+
+
+

<%= t(".tos_title") %>

+ +

+ <%= strip_tags(translated_attribute(Decidim::StaticPage.find_by(slug: "terms-and-conditions", organization: current_organization).content)) %> +

+ +
+ <%= f.check_box :tos_agreement, label: t(".tos_agreement", link: link_to(t(".terms"), page_path("terms-and-conditions"))) %> +
+
+
+ <%= f.hidden_field :email %> <%= f.hidden_field :uid %> <%= f.hidden_field :name %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 802d1bc6..72a5ce56 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -72,6 +72,9 @@ en: registration_info: Please complete your profile sign_up: Sign up subtitle: Create your account + terms: the terms and conditions of use + tos_agreement: By signing up you agree to %{link}. + tos_title: Terms of Service registrations: form: errors: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 9a086a7a..373f5496 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -72,6 +72,9 @@ fr: registration_info: Renseignez vos informations personnelles sign_up: Renseignez vos informations personnelles subtitle: Vous êtes sur le point de créer un compte sur la plateforme de pétitions du CESE. + terms: les termes et conditions d'utilisation + tos_agreement: En vous créant un compte, vous acceptez %{link}. + tos_title: Conditions d'utilisation registrations: form: errors: diff --git a/lib/extends/forms/decidim/omniauth_registration_form_extend.rb b/lib/extends/forms/decidim/omniauth_registration_form_extend.rb index 785007a6..7d7084d1 100644 --- a/lib/extends/forms/decidim/omniauth_registration_form_extend.rb +++ b/lib/extends/forms/decidim/omniauth_registration_form_extend.rb @@ -11,6 +11,7 @@ module OmniauthRegistrationFormExtend attribute :address, String attribute :postal_code, String attribute :city, String + attribute :tos_agreement, ::ActiveModel::Type::Boolean validates :email, "valid_email_2/email": { mx: true } validates :postal_code, @@ -18,10 +19,12 @@ module OmniauthRegistrationFormExtend :city, :address, :certification, - presence: true, unless: ->(form) { form.certification.blank? } + :tos_agreement, + presence: true, unless: ->(form) { form.tos_agreement.blank? } - validates :postal_code, numericality: { only_integer: true }, length: { is: 5 }, unless: ->(form) { form.certification.blank? } - validates :certification, acceptance: true, presence: true + validates :postal_code, numericality: { only_integer: true }, length: { is: 5 }, unless: ->(form) { form.tos_agreement.blank? } + validates :certification, acceptance: true, presence: true, unless: ->(form) { form.tos_agreement.blank? } + validates :tos_agreement, acceptance: true, presence: true validate :over_16? private From 09263b376463968b9c7ea77773c5011c0eedab09 Mon Sep 17 00:00:00 2001 From: moustachu Date: Fri, 8 Dec 2023 20:52:15 +0100 Subject: [PATCH 19/19] fix: omniauth users are confirmed directly at sign up --- ...omniauth_registrations_controller_override.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/controllers/decidim/omniauth_registrations_controller_override.rb b/app/controllers/decidim/omniauth_registrations_controller_override.rb index 3a7848a7..d80a25e1 100644 --- a/app/controllers/decidim/omniauth_registrations_controller_override.rb +++ b/app/controllers/decidim/omniauth_registrations_controller_override.rb @@ -21,6 +21,22 @@ def after_sign_in_path_for(user) super end end + + private + + def verified_email + @verified_email ||= find_verified_email + end + + def find_verified_email + if oauth_data.present? + session["oauth_data.verified_email"] = oauth_data.dig(:info, :email) + else + email_from_session = session["oauth_data.verified_email"] + session.delete("oauth_data.verified_email") + email_from_session + end + end end end end