From f93f2067b6fed98a8b8b8e9b20b359314da548b7 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:52:30 +0530 Subject: [PATCH] fix(whatsapp): Drop obsolete WABA scope check broken by Meta embedded signup (#14697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Fixes WhatsApp embedded signup failing with "No WABA scope found in token." Meta recently changed which scopes embedded-signup tokens carry (whatsapp_business_manage_events instead of whatsapp_business_management), which tripped a brittle scope-string check. That check was redundant anyway — PhoneInfoService already verifies the token's access to the specific WABA by calling its /phone_numbers endpoint right before it. This removes the obsolete TokenValidationService and relies on the functional check. ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules Co-authored-by: Muhsin Keloth --- .../whatsapp/embedded_signup_service.rb | 5 - .../whatsapp/token_validation_service.rb | 42 -------- .../whatsapp/embedded_signup_service_spec.rb | 5 - .../whatsapp/token_validation_service_spec.rb | 99 ------------------- 4 files changed, 151 deletions(-) delete mode 100644 app/services/whatsapp/token_validation_service.rb delete mode 100644 spec/services/whatsapp/token_validation_service_spec.rb diff --git a/app/services/whatsapp/embedded_signup_service.rb b/app/services/whatsapp/embedded_signup_service.rb index 1a5b13c8f..2e8a5028e 100644 --- a/app/services/whatsapp/embedded_signup_service.rb +++ b/app/services/whatsapp/embedded_signup_service.rb @@ -13,7 +13,6 @@ class Whatsapp::EmbeddedSignupService access_token = exchange_code_for_token phone_info = fetch_phone_info(access_token) - validate_token_access(access_token) channel = create_or_reauthorize_channel(access_token, phone_info) # NOTE: We call setup_webhooks explicitly here instead of relying on after_commit callback because: @@ -42,10 +41,6 @@ class Whatsapp::EmbeddedSignupService Whatsapp::PhoneInfoService.new(@waba_id, @phone_number_id, access_token).perform end - def validate_token_access(access_token) - Whatsapp::TokenValidationService.new(access_token, @waba_id).perform - end - def create_or_reauthorize_channel(access_token, phone_info) if @inbox_id.present? Whatsapp::ReauthorizationService.new( diff --git a/app/services/whatsapp/token_validation_service.rb b/app/services/whatsapp/token_validation_service.rb deleted file mode 100644 index 863f9da51..000000000 --- a/app/services/whatsapp/token_validation_service.rb +++ /dev/null @@ -1,42 +0,0 @@ -class Whatsapp::TokenValidationService - def initialize(access_token, waba_id) - @access_token = access_token - @waba_id = waba_id - @api_client = Whatsapp::FacebookApiClient.new(access_token) - end - - def perform - validate_parameters! - validate_token_waba_access - end - - private - - def validate_parameters! - raise ArgumentError, 'Access token is required' if @access_token.blank? - raise ArgumentError, 'WABA ID is required' if @waba_id.blank? - end - - def validate_token_waba_access - token_debug_data = @api_client.debug_token(@access_token) - waba_scope = extract_waba_scope(token_debug_data) - verify_waba_authorization(waba_scope) - end - - def extract_waba_scope(token_data) - granular_scopes = token_data.dig('data', 'granular_scopes') - waba_scope = granular_scopes&.find { |scope| scope['scope'] == 'whatsapp_business_management' } - - raise 'No WABA scope found in token' unless waba_scope - - waba_scope - end - - def verify_waba_authorization(waba_scope) - authorized_waba_ids = waba_scope['target_ids'] || [] - - return if authorized_waba_ids.include?(@waba_id) - - raise "Token does not have access to WABA #{@waba_id}. Authorized WABAs: #{authorized_waba_ids}" - end -end diff --git a/spec/services/whatsapp/embedded_signup_service_spec.rb b/spec/services/whatsapp/embedded_signup_service_spec.rb index a20e36ac8..560b1993e 100644 --- a/spec/services/whatsapp/embedded_signup_service_spec.rb +++ b/spec/services/whatsapp/embedded_signup_service_spec.rb @@ -36,11 +36,6 @@ describe Whatsapp::EmbeddedSignupService do .with(params[:waba_id], params[:phone_number_id], access_token).and_return(phone_service) allow(phone_service).to receive(:perform).and_return(phone_info) - validation_service = instance_double(Whatsapp::TokenValidationService) - allow(Whatsapp::TokenValidationService).to receive(:new) - .with(access_token, params[:waba_id]).and_return(validation_service) - allow(validation_service).to receive(:perform) - channel_creation = instance_double(Whatsapp::ChannelCreationService) allow(Whatsapp::ChannelCreationService).to receive(:new) .with(account, { waba_id: params[:waba_id], business_name: 'Test Business' }, phone_info, access_token) diff --git a/spec/services/whatsapp/token_validation_service_spec.rb b/spec/services/whatsapp/token_validation_service_spec.rb deleted file mode 100644 index d9cf40257..000000000 --- a/spec/services/whatsapp/token_validation_service_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'rails_helper' - -describe Whatsapp::TokenValidationService do - let(:access_token) { 'test_access_token' } - let(:waba_id) { 'test_waba_id' } - let(:service) { described_class.new(access_token, waba_id) } - let(:api_client) { instance_double(Whatsapp::FacebookApiClient) } - - before do - allow(Whatsapp::FacebookApiClient).to receive(:new).with(access_token).and_return(api_client) - end - - describe '#perform' do - context 'when token has access to WABA' do - let(:debug_response) do - { - 'data' => { - 'granular_scopes' => [ - { - 'scope' => 'whatsapp_business_management', - 'target_ids' => [waba_id, 'another_waba_id'] - } - ] - } - } - end - - before do - allow(api_client).to receive(:debug_token).with(access_token).and_return(debug_response) - end - - it 'validates successfully' do - expect { service.perform }.not_to raise_error - end - end - - context 'when token does not have access to WABA' do - let(:debug_response) do - { - 'data' => { - 'granular_scopes' => [ - { - 'scope' => 'whatsapp_business_management', - 'target_ids' => ['different_waba_id'] - } - ] - } - } - end - - before do - allow(api_client).to receive(:debug_token).with(access_token).and_return(debug_response) - end - - it 'raises an error' do - expect { service.perform }.to raise_error(/Token does not have access to WABA/) - end - end - - context 'when no WABA scope is found' do - let(:debug_response) do - { - 'data' => { - 'granular_scopes' => [ - { - 'scope' => 'some_other_scope', - 'target_ids' => ['some_id'] - } - ] - } - } - end - - before do - allow(api_client).to receive(:debug_token).with(access_token).and_return(debug_response) - end - - it 'raises an error' do - expect { service.perform }.to raise_error('No WABA scope found in token') - end - end - - context 'when access_token is blank' do - let(:access_token) { '' } - - it 'raises ArgumentError' do - expect { service.perform }.to raise_error(ArgumentError, 'Access token is required') - end - end - - context 'when waba_id is blank' do - let(:waba_id) { '' } - - it 'raises ArgumentError' do - expect { service.perform }.to raise_error(ArgumentError, 'WABA ID is required') - end - end - end -end