Skip to content
This repository has been archived by the owner on Mar 6, 2024. It is now read-only.

Commit

Permalink
feat: validate emails before sending tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
darccio committed Apr 18, 2023
1 parent 3c2f620 commit e24a3d5
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 22 deletions.
17 changes: 17 additions & 0 deletions app/controllers/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ def self.included(clazz)
error(e.message)
redirect_back fallback_location: root_path
end

rescue_from InvalidEmailAddress do |e|
emails = e.emails.map { |email| email.inspect }

error("#{e.message} (#{emails.join(', ')})")
redirect_back fallback_location: root_path
end
end
end

Expand Down Expand Up @@ -119,4 +126,14 @@ def initialize(msg = I18n.t('errors.you_already_voted_for_this'), consultation:
@consultation = consultation
end
end

class InvalidEmailAddress < ActionController::BadRequest
attr_reader :emails

def initialize(msg = I18n.t('errors.invalid_email'), emails:)
super(msg)

@emails = emails
end
end
end
46 changes: 24 additions & 22 deletions app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,39 +40,25 @@ def next_question
def create_tokens
authorize event

params = create_token_params
role = params.fetch(:role, :voter).to_sym
multiple = params.fetch(:multiple, 'false') == 'true'

Token.transaction do
if multiple
lines = params[:value].open
CSV.new(lines).read.each do |line|
CreateToken.call(
identifier: line.first,
role:,
aliased: params.fetch(:aliased, '0').to_i == 1,
send_magic_link: params.fetch(:send, '0').to_i == 1,
event:
)
end
identifiers = CSV.new(lines).read.map(&:first)

result = BulkCreateTokens.result(identifiers:, role:, aliased:, send_magic_link:, event:)
raise result.error if result.failure?

success(I18n.t('events.tokens_created_or_enabled'))

result = RedirectBySession.call(Context.to_h)
redirect_to result.destination
else
identifier = params[:value].presence
send_magic_link = params.fetch(:send, '0').to_i == 1
send_magic_link ||= EmailValidator.valid?(identifier, mode: :strict) if consultation.config.distribution == 'email'

result = CreateToken.result(
identifier:,
role:,
aliased: params.fetch(:aliased, '0').to_i == 1,
send_magic_link:,
event:
)
send_magic_link = false
send_magic_link = EmailValidator.valid?(identifier, mode: :strict) if consultation.config.distribution == 'email'

result = CreateToken.result(identifier:, role:, aliased:, send_magic_link:, event:)

if result.skip
success(I18n.t('events.token_already_issued'))
Expand Down Expand Up @@ -143,6 +129,22 @@ def create_token_params
params.permit(:value, :aliased, :multiple, :send, :role)
end

def role
create_token_params.fetch(:role, :voter).to_sym
end

def multiple
create_token_params.fetch(:multiple, 'false') == 'true'
end

def aliased
create_token_params.fetch(:aliased, '0').to_i == 1
end

def send_magic_link
create_token_params.fetch(:send, '0').to_i == 1
end

def update_token_params
params.require(:status)
end
Expand Down
30 changes: 30 additions & 0 deletions app/interactors/bulk_create_tokens.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

class BulkCreateTokens < Actor
input :identifiers, type: Array
input :role, type: Symbol, in: Token.roles.keys.map(&:to_sym)
input :event, type: Event, allow_nil: true, default: nil
input :aliased, type: [TrueClass, FalseClass], default: false
input :send_magic_link, type: [TrueClass, FalseClass], default: false

def call
validate
create_tokens
end

private

def validate
return if identifiers.blank?
return unless send_magic_link

invalid_identifiers = identifiers.reject { |identifier| EmailValidator.valid?(identifier, mode: :strict) }
fail!(error: Errors::InvalidEmailAddress.new(emails: invalid_identifiers)) if invalid_identifiers.present?
end

def create_tokens
identifiers.each do |identifier|
CreateToken.call(identifier:, role:, aliased:, send_magic_link:, event:)
end
end
end
34 changes: 34 additions & 0 deletions test/controllers/events_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,40 @@ class EventsControllerTest < ActionDispatch::IntegrationTest
assert_equal 2, tokens.size
end

test 'should validate multiple tokens and fail' do
consultation = token.consultation
event = create(:event, consultation: token.consultation)

token.update!(role: :manager, event:)

post sessions_url, params: { session: { identifier: token.to_hash } }

file_content = <<~CSV
"mohana@coop.mail; "
kiran@coopanio.com
giang@coopanio.com
kelechi_9@coopanio.com
blessing@examp.le
masozi@coopanio.com
chika-dawa.wanangwa@examp.le
rathol@coopan.io
"s.hozan99@coop.mail;"
kelsey@coopanio.com
xia@coopanio.com
CSV
census = StringIO.new(file_content)

assert_emails 0 do
post "/events/#{event.id}/tokens", params: { value: Rack::Test::UploadedFile.new(census, 'text/csv', original_filename: 'census.csv'), multiple: 'true', send: '1' }
end

tokens = Token.where(event:, role: :voter)

assert_response :redirect
assert_equal 'Invalid email. ("mohana@coop.mail; ", "s.hozan99@coop.mail;")', flash[:alert].message
assert_equal 0, tokens.size
end

test 'should create an unaliased token and deliver it' do
consultation = token.consultation
event = create(:event, consultation: token.consultation)
Expand Down

0 comments on commit e24a3d5

Please sign in to comment.