fix(whatsapp): Drop obsolete WABA scope check broken by Meta embedded signup (#14697)

## 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 <muhsinkeramam@gmail.com>
This commit is contained in:
Tanmay Deep Sharma
2026-06-10 13:22:30 +04:00
committed by GitHub
co-authored by Muhsin Keloth
parent 3dfb5061e1
commit f93f2067b6
4 changed files with 0 additions and 151 deletions
@@ -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(
@@ -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
@@ -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)
@@ -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