Anti-spam measures for volunteering
This commit is contained in:
parent
e461ec504f
commit
b8ae2b6b0e
|
@ -0,0 +1,20 @@
|
|||
module Public
|
||||
class VolunteerConfirmationsController < Public::ApplicationController
|
||||
def create
|
||||
@volunteer = Volunteer.find_by!(unique_id: params[:id])
|
||||
|
||||
if ActiveSupport::SecurityUtils.secure_compare(@volunteer.confirmation_token, params[:confirmation_token])
|
||||
@volunteer.transaction do
|
||||
@volunteer.touch(:confirmed_at)
|
||||
@volunteer.update(confirmation_token: nil)
|
||||
end
|
||||
|
||||
@volunteer.send_notification_to_volunteer
|
||||
|
||||
redirect_to edit_volunteer_path(@volunteer.unique_id), notice: I18n.t("views.volunteers.email_confirmed_successfully")
|
||||
else
|
||||
redirect_to root_path, alert: I18n.t("views.volunteers.email_confirmation_error")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
module Public
|
||||
class VolunteersController < Public::ApplicationController
|
||||
before_action :check_honey_pot, only: [:create, :edit]
|
||||
def new
|
||||
@volunteer = current_conference.volunteers.build
|
||||
end
|
||||
|
@ -30,6 +31,10 @@ module Public
|
|||
|
||||
private
|
||||
|
||||
def check_honey_pot
|
||||
head :unauthorized unless params.dig(:volunteer_ht, :full_name).blank?
|
||||
end
|
||||
|
||||
def volunteer_params
|
||||
params.require(:volunteer).permit(
|
||||
:name, :picture, :email, :phone, :tshirt_size, :tshirt_cut,
|
||||
|
|
|
@ -13,8 +13,19 @@ class VolunteerMailer < ActionMailer::Base
|
|||
I18n.locale = @volunteer.language
|
||||
mail(to: @volunteer.email,
|
||||
reply_to: @volunteer.conference.email,
|
||||
from: "no-reply@openfest.org",
|
||||
from: "cfp@openfest.org",
|
||||
subject: I18n.t("volunteer_mailer.success_notification.subject",
|
||||
conference_name: @volunteer.conference.title))
|
||||
end
|
||||
|
||||
def volunteer_email_confirmation(new_volunteer)
|
||||
@volunteer = new_volunteer
|
||||
I18n.locale = @volunteer.language
|
||||
mail(to: @volunteer.email,
|
||||
reply_to: @volunteer.conference.email,
|
||||
from: "cfp@openfest.org",
|
||||
subject: I18n.t("volunteer_mailer.email_confirmation.subject",
|
||||
conference_name: @volunteer.conference.title))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ class Volunteer < ActiveRecord::Base
|
|||
validates :tshirt_size, inclusion: {in: TSHIRT_SIZES.map(&:to_s)}
|
||||
validates :tshirt_cut, inclusion: {in: TSHIRT_CUTS.map(&:to_s)}
|
||||
validates :food_preferences, inclusion: {in: FOOD_PREFERENCES.map(&:to_s)}
|
||||
validates :email, format: {with: /\A[^@]+@[^@]+\z/}, presence: true
|
||||
validates :email, format: {with: /\A[^@]+@[^@]+\z/}, presence: true, uniqueness: {scope: :conference_id}
|
||||
validates :phone, presence: true, format: {with: /\A[+\- \(\)0-9]+\z/}
|
||||
validates :volunteer_team, presence: true
|
||||
validate :volunteer_teams_belong_to_conference
|
||||
|
@ -22,7 +22,12 @@ class Volunteer < ActiveRecord::Base
|
|||
|
||||
before_create :ensure_main_volunteer_team_is_part_of_additional_volunteer_teams
|
||||
before_create :assign_unique_id
|
||||
after_create :send_notification_to_volunteer
|
||||
before_create :assign_confirmation_token
|
||||
after_commit :send_email_confirmation_to_volunteer, on: [:create]
|
||||
|
||||
def send_notification_to_volunteer
|
||||
VolunteerMailer.volunteer_notification(self).deliver_later
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
@ -34,8 +39,12 @@ class Volunteer < ActiveRecord::Base
|
|||
self.unique_id = Digest::SHA256.hexdigest(SecureRandom.uuid)
|
||||
end
|
||||
|
||||
def send_notification_to_volunteer
|
||||
VolunteerMailer.volunteer_notification(self).deliver_later
|
||||
def assign_confirmation_token
|
||||
self.confirmation_token = Digest::SHA256.hexdigest(SecureRandom.uuid)
|
||||
end
|
||||
|
||||
def send_email_confirmation_to_volunteer
|
||||
VolunteerMailer.volunteer_email_confirmation(self).deliver_later
|
||||
end
|
||||
|
||||
def volunteer_teams_belong_to_conference
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Здравейте,
|
||||
|
||||
Моля, потвърдете e-mail адреса си от следния линк:
|
||||
|
||||
<%= confirm_volunteer_url(@volunteer.unique_id, confirmation_token: @volunteer.confirmation_token, host: @volunteer.conference.host_name, protocol: 'https') %>
|
|
@ -0,0 +1,5 @@
|
|||
Hello,
|
||||
|
||||
Please confirm your e-mail address by clicking the following link:
|
||||
|
||||
<%= confirm_volunteer_url(@volunteer.unique_id, confirmation_token: @volunteer.confirmation_token, host: @volunteer.conference.host_name, protocol: 'https') %>
|
|
@ -86,9 +86,14 @@ Rails.application.configure do
|
|||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||
# config.action_mailer.raise_delivery_errors = false
|
||||
|
||||
config.action_mailer.delivery_method = :sendmail
|
||||
config.action_mailer.default_options = {from: "no-reply@openfest.org"}
|
||||
config.action_mailer.delivery_method = :smtp
|
||||
config.action_mailer.default_options = {from: "cfp@openfest.org"}
|
||||
config.action_mailer.default_url_options = {host: "cfp.openfest.org"}
|
||||
config.action_mailer.smtp_settings = {
|
||||
address: "host.containers.internal",
|
||||
enable_starttls_auto: false,
|
||||
enable_starttls: false
|
||||
}
|
||||
|
||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||
# the I18n.default_locale when a translation cannot be found).
|
||||
|
|
|
@ -12,7 +12,7 @@ Devise.setup do |config|
|
|||
# Configure the e-mail address which will be shown in Devise::Mailer,
|
||||
# note that it will be overwritten if you use your own mailer class
|
||||
# with default "from" parameter.
|
||||
config.mailer_sender = "no-reply@openfest.org"
|
||||
config.mailer_sender = "cfp@openfest.org"
|
||||
|
||||
# Configure the class responsible to send e-mails.
|
||||
# config.mailer = 'Devise::Mailer'
|
||||
|
|
|
@ -242,6 +242,8 @@ bg:
|
|||
volunteer_mailer:
|
||||
success_notification:
|
||||
subject: "Кандидатурата Ви за доброволец за %{conference_name} беше получена"
|
||||
email_confirmation:
|
||||
subject: "Потвърдете e-mail адреса си, за да се включите в %{conference_name}"
|
||||
event_states:
|
||||
approved:
|
||||
one: "Одобрено"
|
||||
|
@ -412,6 +414,9 @@ bg:
|
|||
welcome:
|
||||
submit_event: "Предложи %{event_type}"
|
||||
volunteers:
|
||||
email_not_confirmed: Вашият e-mail адрес не е потвърден. Моля, проверете електронната си поща и кликнете на линка от полученото писмо за потвърждение.
|
||||
email_confirmed_successfully: Успешно потвърдихте e-mail адреса си!
|
||||
email_confirmation_error: Възникна грешка при опит за потвърждаване на e-mail адреса Ви.
|
||||
new_volunteer_title: Кандидатствай за доброволец
|
||||
edit_volunteer_title: "Кандидатура за доброволец на %{name}"
|
||||
apply: Кандидатствай за доброволец
|
||||
|
|
|
@ -242,6 +242,8 @@ en:
|
|||
volunteer_mailer:
|
||||
success_notification:
|
||||
subject: "Your application for \"volunteership\" on %{conference_name} was received"
|
||||
email_confirmation:
|
||||
subject: "Confirm your e-mail address to participate in %{conference_name}"
|
||||
event_states:
|
||||
approved:
|
||||
one: "Approved"
|
||||
|
@ -412,6 +414,9 @@ en:
|
|||
welcome:
|
||||
submit_event: "Submit %{event_type}"
|
||||
volunteers:
|
||||
email_not_confirmed: Your e-mail address has not been confirmed yet. Please check your e-mail and click on the link from the confirmation message you received.
|
||||
email_confirmed_successfully: You have successfully confirmed your e-mail address!
|
||||
email_confirmation_error: There was an error during the attempt to confirm your e-mail address.
|
||||
new_volunteer_title: Apply for a volunteer
|
||||
edit_volunteer_title: "Volunteership application of %{name}"
|
||||
apply: Apply for a volunteer
|
||||
|
|
|
@ -11,7 +11,11 @@ Rails.application.routes.draw do
|
|||
get :confirm
|
||||
end
|
||||
end
|
||||
resources :volunteers
|
||||
resources :volunteers do
|
||||
member do
|
||||
get :confirm, to: "volunteer_confirmations#create"
|
||||
end
|
||||
end
|
||||
resources :volunteer_teams, only: [:index]
|
||||
resources :feedback, as: "conference_feedbacks", controller: "conference_feedbacks", only: [:new, :create, :index]
|
||||
end
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
class AddConfirmationAndApprovalToVolunteers < ActiveRecord::Migration[7.1]
|
||||
class Volunteer < ActiveRecord::Base; end
|
||||
|
||||
def change
|
||||
add_column :volunteers, :confirmation_token, :string
|
||||
add_index :volunteers, :confirmation_token
|
||||
add_column :volunteers, :confirmed_at, :timestamptz
|
||||
add_index :volunteers, :confirmed_at
|
||||
add_column :volunteers, :approved_at, :timestamptz
|
||||
add_index :volunteers, :approved_at
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
execute "UPDATE volunteers SET confirmed_at = created_at, approved_at = created_at;"
|
||||
end
|
||||
|
||||
dir.down do
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
#flash_messages {
|
||||
.flash_messages {
|
||||
border: 1px solid #CCC;
|
||||
background-color: #F1F1F1;
|
||||
padding: 10px;
|
||||
|
|
|
@ -162,3 +162,14 @@
|
|||
.btn-link-red:active {
|
||||
background: #D27A59;
|
||||
}
|
||||
|
||||
.special-form-field {
|
||||
transform: scale(0.00069420);
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
div#flash_messages
|
||||
div#flash_messages.flash_messages
|
||||
- flash.each do |key, value|
|
||||
= content_tag :div, value, class: "flash #{key}"
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
= f.hidden_field :picture, value: @volunteer.picture.signed_id if @volunteer.picture.attached?
|
||||
= f.input :picture, as: :file, required: false, direct: true, wrapper: false, input_html: {direct_upload: true}
|
||||
|
||||
= text_field :volunteer_ht, :full_name, class: 'special-form-field', tabindex: "-1", "aria-hidden": true
|
||||
= label :volunteer_ht, :full_name, 'Full Name', class: 'special-form-field', "aria-hidden": true
|
||||
|
||||
= f.input :name, autofocus: true
|
||||
= f.input :email
|
||||
= f.input :phone, input_html: {value: @volunteer.phone.try(:phony_formatted, format: :international)}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
- content_for(:title) { t 'views.volunteers.edit_volunteer_title', name: @volunteer.name }
|
||||
|
||||
- if !@volunteer.new_record? && !@volunteer.confirmed_at
|
||||
div.flash_messages
|
||||
div.flash.alert = t 'views.volunteers.email_not_confirmed'
|
||||
|
||||
h1 = t 'views.volunteers.edit_volunteer_title', name: @volunteer.name
|
||||
|
||||
= render 'form'
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require "rails_helper"
|
||||
|
||||
feature "Volunteering" do
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
before do
|
||||
Rails.application.load_seed
|
||||
sign_in_as_admin
|
||||
|
@ -12,12 +14,29 @@ feature "Volunteering" do
|
|||
visit root_path
|
||||
click_on I18n.t("views.volunteers.apply")
|
||||
|
||||
perform_enqueued_jobs do
|
||||
fill_in_volunteer_profile
|
||||
expect(page).to have_content I18n.t("views.volunteers.successful_application")
|
||||
expect(page).to have_content I18n.t("views.volunteers.email_not_confirmed")
|
||||
end
|
||||
|
||||
perform_enqueued_jobs do
|
||||
visit link_from_last_email
|
||||
end
|
||||
|
||||
expect(page).not_to have_content I18n.t("views.volunteers.email_not_confirmed")
|
||||
|
||||
expect(ActionMailer::Base.deliveries.last.subject).to eq(I18n.t("volunteer_mailer.success_notification.subject", conference_name: "FooConf #{1.year.from_now.year}"))
|
||||
|
||||
sign_in_as_admin
|
||||
click_on_first_conference_in_management_root
|
||||
click_on I18n.t("activerecord.models.volunteership", count: 2).capitalize
|
||||
expect(page).to have_content "Volunteer Foo"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def link_from_last_email
|
||||
ActionMailer::Base.deliveries.last.body.to_s.strip[%r{(?<=https://www\.example\.com).*?(?=$)}]
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue