diff --git a/app/controllers/wise_items_controller.rb b/app/controllers/wise_items_controller.rb index ad8a7b86e..f92daec0b 100644 --- a/app/controllers/wise_items_controller.rb +++ b/app/controllers/wise_items_controller.rb @@ -36,11 +36,9 @@ class WiseItemsController < ApplicationController end session[:wise_pending_profiles] = profiles - @pending_profiles = profiles - @existing_profile_ids = Current.family.wise_items.pluck(:profile_id).map(&:to_s).to_set - @encrypted_pending_token = encrypt_pending_token(token) + session[:wise_pending_encrypted_token] = encrypt_pending_token(token) - render :select_profiles + redirect_to select_profiles_wise_items_path rescue Provider::Wise::WiseError => e @wise_item = Current.family.wise_items.build error_key = e.error_type == :unauthorized ? ".invalid_token" : ".connection_failed" @@ -51,9 +49,10 @@ class WiseItemsController < ApplicationController # Step 2: Show profile selection. def select_profiles @pending_profiles = session[:wise_pending_profiles] + @encrypted_pending_token = session[:wise_pending_encrypted_token] - if @pending_profiles.blank? - redirect_to new_wise_item_path, alert: t(".session_expired") and return + if @pending_profiles.blank? || @encrypted_pending_token.blank? + redirect_to settings_providers_path, alert: t(".session_expired") and return end @existing_profile_ids = Current.family.wise_items.pluck(:profile_id).map(&:to_s).to_set @@ -61,11 +60,11 @@ class WiseItemsController < ApplicationController # Step 3: Create one WiseItem per selected profile. def link_profiles - token = decrypt_pending_token(params[:encrypted_pending_token]) # pipelock:ignore + token = decrypt_pending_token(session[:wise_pending_encrypted_token]) # pipelock:ignore profiles = session[:wise_pending_profiles] if token.blank? || profiles.blank? - redirect_to new_wise_item_path, alert: t(".session_expired") and return + redirect_to settings_providers_path, alert: t(".session_expired") and return end selected_ids = Array(params[:profile_ids]).map(&:to_s).compact_blank @@ -92,6 +91,7 @@ class WiseItemsController < ApplicationController end session.delete(:wise_pending_profiles) + session.delete(:wise_pending_encrypted_token) if created.zero? redirect_to settings_providers_path, alert: t(".already_connected") diff --git a/test/controllers/wise_items_controller_test.rb b/test/controllers/wise_items_controller_test.rb index cfcec1b1b..b2073b607 100644 --- a/test/controllers/wise_items_controller_test.rb +++ b/test/controllers/wise_items_controller_test.rb @@ -14,15 +14,19 @@ class WiseItemsControllerTest < ActionDispatch::IntegrationTest ] end - # create renders select_profiles directly — token must NOT appear in the session + # create redirects to select_profiles (Turbo requires a redirect from a standard + # form submission) — the encrypted token travels via the session, not the response body. - test "create renders select_profiles and keeps token out of session" do + test "create redirects to select_profiles and keeps raw token out of the session" do Provider::Wise.any_instance.stubs(:get_profiles).returns(@valid_profiles) post wise_items_url, params: { wise_item: { token: "live_token_abc" } } - assert_response :success + assert_redirected_to select_profiles_wise_items_path assert_nil session[:wise_pending_token], "raw API token must not be stored in the session" + assert session[:wise_pending_encrypted_token].present? + + follow_redirect! assert_select "input[name='encrypted_pending_token']" end @@ -30,6 +34,7 @@ class WiseItemsControllerTest < ActionDispatch::IntegrationTest Provider::Wise.any_instance.stubs(:get_profiles).returns(@valid_profiles) post wise_items_url, params: { wise_item: { token: "live_token_abc" } } + follow_redirect! encrypted = css_select("input[name='encrypted_pending_token']").first["value"] assert encrypted.present?, "hidden encrypted_pending_token field must be present" @@ -55,44 +60,37 @@ class WiseItemsControllerTest < ActionDispatch::IntegrationTest assert_nil session[:wise_pending_token] end - # link_profiles uses the encrypted hidden field, not the session + # link_profiles reads the encrypted token from the session (set by create) — + # the client no longer needs to (and cannot) supply or tamper with it via params. - test "link_profiles creates WiseItems using the encrypted token" do + test "link_profiles creates WiseItems using the session-held encrypted token" do Provider::Wise.any_instance.stubs(:get_profiles).returns(@valid_profiles) post wise_items_url, params: { wise_item: { token: "live_token_abc" } } - encrypted = css_select("input[name='encrypted_pending_token']").first["value"] - assert_difference "WiseItem.count", 1 do - post link_profiles_wise_items_url, params: { - encrypted_pending_token: encrypted, - profile_ids: [ "99999999" ] - } + post link_profiles_wise_items_url, params: { profile_ids: [ "99999999" ] } end assert_redirected_to settings_providers_path assert_equal "live_token_abc", @family.wise_items.find_by!(profile_id: "99999999").token assert_nil session[:wise_pending_profiles] + assert_nil session[:wise_pending_encrypted_token] end - test "link_profiles redirects to new when encrypted token is missing" do - post link_profiles_wise_items_url, params: { - encrypted_pending_token: "", - profile_ids: [ "99999999" ] - } + test "link_profiles redirects to providers when there is no pending session" do + post link_profiles_wise_items_url, params: { profile_ids: [ "99999999" ] } - assert_redirected_to new_wise_item_path + assert_redirected_to settings_providers_path end - test "link_profiles redirects to new when encrypted token is tampered" do + test "link_profiles redirects to providers when the session token cannot be decrypted" do Provider::Wise.any_instance.stubs(:get_profiles).returns(@valid_profiles) post wise_items_url, params: { wise_item: { token: "live_token_abc" } } - post link_profiles_wise_items_url, params: { - encrypted_pending_token: "tampered_garbage_value", - profile_ids: [ "99999999" ] - } + session[:wise_pending_encrypted_token] = "corrupted_garbage_value" - assert_redirected_to new_wise_item_path + post link_profiles_wise_items_url, params: { profile_ids: [ "99999999" ] } + + assert_redirected_to settings_providers_path end end