From ec8ea41da567a361a85c7a7535f274efa70709ee Mon Sep 17 00:00:00 2001 From: f Date: Wed, 30 Oct 2024 18:03:23 -0300 Subject: [PATCH 1/3] feat: prueba de antispam (cherry picked from commit 0ebd57811d43846f9eccb669f1472ef96f8a62d8) --- app/controllers/registrations_controller.rb | 20 ++++++++++++++++++++ app/views/devise/registrations/new.haml | 3 +++ config/routes.rb | 2 +- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 app/controllers/registrations_controller.rb diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb new file mode 100644 index 00000000..353d0c17 --- /dev/null +++ b/app/controllers/registrations_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Modificaciones locales al registro de usuaries +# +# @see {https://github.com/heartcombo/devise/wiki/How-To:-Use-Recaptcha-with-Devise} +class RegistrationsController < Devise::RegistrationsController + class SpambotError < StandardError; end + + prepend_before_action :anti_spambot_traps, only: %i[create] + + private + + # Detecta spambots simples + def anti_spambot_traps + raise SpambotError if params.dig(:usuarie, :name).blank? + rescue SpambotError => e + ExceptionNotifier.notify_exception(e, data: { params: params }) + nil + end +end diff --git a/app/views/devise/registrations/new.haml b/app/views/devise/registrations/new.haml index aabc0487..4c562fc3 100644 --- a/app/views/devise/registrations/new.haml +++ b/app/views/devise/registrations/new.haml @@ -12,6 +12,9 @@ as: resource_name, url: registration_path(resource_name, params: { locale: params[:locale] })) do |f| + .d-none + = f.text_field :name, autocomplete: 'off' + .form-group = f.label :email, class: 'sr-only' = f.email_field :email, autofocus: true, autocomplete: 'email', diff --git a/config/routes.rb b/config/routes.rb index 9d5c974a..abe2c36a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Rails.application.routes.draw do - devise_for :usuaries + devise_for :usuaries, controllers: { registrations: 'registrations' } get '/.well-known/change-password', to: redirect('/usuaries/edit') require 'que/web' From 93ad72f86d27aa55f26251304a07424dea89a117 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 31 Oct 2024 11:17:21 -0300 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20detecci=C3=B3n=20de=20spambots=20y?= =?UTF-8?q?=20reporte=20an=C3=B3nimo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/registrations_controller.rb | 27 ++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 353d0c17..c9f21b53 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -10,11 +10,32 @@ class RegistrationsController < Devise::RegistrationsController private - # Detecta spambots simples + # Condiciones bajo las que consideramos que un registro viene de unx + # spambot + # + # @return [Bool] + def spambot? + @spambot ||= params.dig(:usuarie, :name).present? + end + + # Detecta e informa spambots muy simples + # + # @return [nil] def anti_spambot_traps - raise SpambotError if params.dig(:usuarie, :name).blank? + raise SpambotError if spambot? rescue SpambotError => e - ExceptionNotifier.notify_exception(e, data: { params: params }) + ExceptionNotifier.notify_exception(e, data: { params: anonymized_params }) nil end + + # Devuelve parámetros anonimizados para prevenir filtrar la contraseña + # de falsos positivos. + # + # @return [Hash] + def anonymized_params + params.except(:authenticity_token).permit!.to_h.tap do |p| + p['usuarie'].delete 'password' + p['usuarie'].delete 'password_confirmation' + end + end end From f00e3b823030920738245eb763a33338c9153350 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 31 Oct 2024 11:18:21 -0300 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20bloquear=20spambots=20despu=C3=A9s?= =?UTF-8?q?=20de=20un=20minuto=20#17722?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/registrations_controller.rb | 22 +++++++++++++++++++++ app/jobs/lock_usuarie_job.rb | 19 ++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 app/jobs/lock_usuarie_job.rb diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index c9f21b53..4ba0b7c7 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -7,6 +7,7 @@ class RegistrationsController < Devise::RegistrationsController class SpambotError < StandardError; end prepend_before_action :anti_spambot_traps, only: %i[create] + prepend_after_action :lock_spambots, only: %i[create] private @@ -18,6 +19,15 @@ class RegistrationsController < Devise::RegistrationsController @spambot ||= params.dig(:usuarie, :name).present? end + # Bloquea las cuentas de spam dentro de un minuto, para hacerles creer + # que la cuenta se creó correctamente. + def lock_spambots + return unless spambot? + return unless current_usuarie + + LockUsuarieJob.set(wait: 1.minute).perform_later(usuarie: current_usuarie) + end + # Detecta e informa spambots muy simples # # @return [nil] @@ -38,4 +48,16 @@ class RegistrationsController < Devise::RegistrationsController p['usuarie'].delete 'password_confirmation' end end + + # Si le usuarie es considerade spambot, no enviamos el correo de + # confirmación al crear la cuenta. + def sign_up_params + if spambot? + params[:usuarie][:confirmed_at] = Time.now.utc + + devise_parameter_sanitizer.permit(:sign_up, keys: %i[confirmed_at]) + end + + super + end end diff --git a/app/jobs/lock_usuarie_job.rb b/app/jobs/lock_usuarie_job.rb new file mode 100644 index 00000000..94640857 --- /dev/null +++ b/app/jobs/lock_usuarie_job.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Bloquea el acceso a une usuarie +class LockUsuarieJob < ApplicationJob + # Cambiamos la contraseña, aplicamos un bloqueo y cerramos la sesión + # para que no pueda volver a entrar hasta que siga las instrucciones + # de desbloqueo. + # + # @param :usuarie [Usuarie] + # @return [nil] + def perform(usuarie:) + password = SecureRandom.base36 + + usuarie.skip_password_change_notification! + usuarie.update(password: password, password_confirmation: password, remember_created_at: nil, locked_at: Time.utc.now) + + nil + end +end