From 661a7e5e2d3e7ec515ff697e5616be27c59872fd Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Sun, 7 Dec 2025 13:26:31 -0500 Subject: [PATCH] Validate email before creating identity during join code redemption Avoid Sentry exceptions when attackers try to stuff invalid emails. The browser performs form field validation that should normally prevent this from occurring, so we just return 422 without validation error messages. See similar change in #1996 for sign-in and sign-up --- app/controllers/join_codes_controller.rb | 25 +++++++++++++------ .../controllers/join_codes_controller_test.rb | 12 +++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/app/controllers/join_codes_controller.rb b/app/controllers/join_codes_controller.rb index 99298880c..8bd67c9dc 100644 --- a/app/controllers/join_codes_controller.rb +++ b/app/controllers/join_codes_controller.rb @@ -3,6 +3,7 @@ class JoinCodesController < ApplicationController before_action :set_join_code before_action :ensure_join_code_is_valid + before_action :set_identity, only: :create layout "public" @@ -10,25 +11,35 @@ class JoinCodesController < ApplicationController end def create - identity = Identity.find_or_create_by!(email_address: params.expect(:email_address)) + @join_code.redeem_if { |account| @identity.join(account) } + user = User.active.find_by!(account: @join_code.account, identity: @identity) - @join_code.redeem_if { |account| identity.join(account) } - user = User.active.find_by!(account: @join_code.account, identity: identity) - - if identity == Current.identity && user.setup? + if @identity == Current.identity && user.setup? redirect_to landing_url(script_name: @join_code.account.slug) - elsif identity == Current.identity + elsif @identity == Current.identity redirect_to new_users_verification_url(script_name: @join_code.account.slug) else terminate_session if Current.identity redirect_to_session_magic_link \ - identity.send_magic_link, + @identity.send_magic_link, return_to: new_users_verification_url(script_name: @join_code.account.slug) end end private + def set_identity + @identity = Identity.find_or_initialize_by(email_address: params.expect(:email_address)) + + if @identity.new_record? + if @identity.invalid? + head :unprocessable_entity + else + @identity.save! + end + end + end + def set_join_code @join_code ||= Account::JoinCode.find_by(code: params.expect(:code), account: Current.account) end diff --git a/test/controllers/join_codes_controller_test.rb b/test/controllers/join_codes_controller_test.rb index 9558b605a..415bde3e5 100644 --- a/test/controllers/join_codes_controller_test.rb +++ b/test/controllers/join_codes_controller_test.rb @@ -82,4 +82,16 @@ class JoinCodesControllerTest < ActionDispatch::IntegrationTest assert_redirected_to session_magic_link_url(script_name: nil) assert_not_predicate cookies[:session_token], :present? end + + test "create with invalid email address" do + # Avoid Sentry exceptions when attackers try to stuff invalid emails into the system + without_action_dispatch_exception_handling do + assert_no_difference -> { Identity.count } do + assert_no_difference -> { User.count } do + post join_path(code: @join_code.code, script_name: @account.slug), params: { email_address: "not-a-valid-email" } + end + end + assert_response :unprocessable_entity + end + end end