i have customer service application using in app browser call using nexmo and laravel as framework. I have some users and each users have their list phone numbers in table and each row has contact action button to call. The target of phone number is put in button like this :
<a href="#" class="btn btn-sm btn-info call-button" data-toggle="modal" data-number="6281299054899">Contact</a>
but user can do inspect element and edit those button then put another phone numbers they want. How can i prevent this cheat and alert user in example “You have not authorize to call this phone number.” when they do that action?
this my handler when .call-button is clicked:
dataTableAdminLeader.on('click', '.call-button', function(){ let id = $(this).data('id'); let master_number_id = $(this).data('master_numbers_id'); let target_number = $(this).data('number'); const trashButton = document.querySelector('.btn-trash'); const updateButton = document.querySelector(".btn-update"); trashButton.setAttribute('disabled', 'disabled'); updateButton.setAttribute('disabled', 'disabled'); $('#phone-call-modal').modal('show'); // input reset document.querySelector('#target-phone').value = target_number; document.querySelector('#id-edit').value = id; document.querySelector('#master-numbers-id-edit').value = master_number_id; document.querySelector('#member-id').value = $(this).data('id-member'); document.querySelector('#member-name').value = $(this).data('name'); trashButton.removeAttribute('disabled'); updateButton.removeAttribute('disabled'); // all constant needed for call const USER_JWT = "{{ $jwt_token }}"; const phoneNumberInput = document.querySelector("#cs-phone"); const statusElement = document.querySelector("#status-call"); // button object selector const callButton = document.querySelector(".btn-call"); callButton.setAttribute('disabled', 'disabled'); const hangupButton = document.querySelector(".btn-hangup"); const closeButton = document.querySelector('.btn-close'); closeButton.style.removeProperty('display'); const statusButton = document.querySelector('.btn-status'); // input object selector let campaign_result = document.querySelector('#campaignresult'); let note_contacted = document.querySelector('#note_contacted'); let note_container = document.querySelector('.note_container'); let nameContainer = document.querySelector('.name-container'); let campaignContainer = document.querySelector('.campaign-container'); let waContainer = document.querySelector('.wa-container'); let inputCallStatus = document.querySelector('#call-status'); // call status check let callStatusCompleted = false; let callStatusAnswered = false; // reset property campaign_result.value = ""; note_container.style = 'display: none'; nameContainer.style = 'display: none'; // sound object var sndAnswered = new Audio("{{ asset('storage/sounds/answered.wav') }}"); // listening to event campaign_result.addEventListener('change', function(){ if(campaign_result.value != ''){ note_container.style.removeProperty('display'); note_contacted.setAttribute('required', 'required'); }else{ note_container.style = 'display: none'; note_contacted.removeAttribute('required'); } }); // nexmo status reset statusElement.innerText = ''; inputCallStatus.value = ''; // nexmo call button reset callButton.style.display = "inline"; hangupButton.style.display = "none"; // timeouts set setTimeout(() => { callButton.removeAttribute('disabled'); }, 5000); // nexmo object start new NexmoClient({ debug: true }).login(USER_JWT).then(app => { callButton.addEventListener("click", event => { event.preventDefault(); let number = String(target_number); console.log(number); if (number !== ""){ app.callServer(number).catch(function(error){ console.log('debug: ',error); }); } else { statusElement.innerText = 'Please enter your phone number.'; } }); app.on("member:call", (member, call) => { // object selector reset callButton.style.display = 'none'; closeButton.style.display = 'none'; hangupButton.style.removeProperty('display'); statusButton.style.removeProperty('display'); $('#wa-valid').removeAttr('checked'); // event when hangup button clicked hangupButton.addEventListener("click", () => { call.hangUp(); }); }); app.on("call:status:changed",(call) => { console.log('Periodik : ', call.status); // animation call let statusAnimation = `<p class="saving">Call status: ${call.status}<span>.</span><span>.</span><span>.</span></p>`; // assign call animation to nexmo status display statusElement.innerHTML = statusAnimation; // filter nexmo status condition switch(call.status) { case call.CALL_STATUS.STARTED: console.log('Case call status: ', call.status); break; case call.CALL_STATUS.RINGING: console.log('Case call status: ', call.status); break; case call.CALL_STATUS.FAILED: inputCallStatus.value = call.status; callStatusAnswered = false; callButton.style.display = 'none'; hangupButton.style.display = 'none'; statusButton.style.removeProperty('display'); updateButton.style.removeProperty('display'); trashButton.style.removeProperty('display'); waContainer.style.removeProperty('display'); closeButton.style.removeProperty('dispaly'); nameContainer.style.removeProperty('display'); console.log('Case call status: ', call.status); break; case call.CALL_STATUS.CANCELLED: inputCallStatus.value = call.status; callStatusAnswered = false; callButton.style.removeProperty('display'); hangupButton.style.display = 'none'; statusButton.style.display = 'none'; nameContainer.style.removeProperty('display'); updateButton.style.removeProperty('display'); trashButton.style.removeProperty('display'); waContainer.style.removeProperty('display'); closeButton.style.removeProperty('dispaly'); console.log('Case call status: ', call.status); break; case call.CALL_STATUS.COMPLETED: callStatusCompleted = true; callButton.style.display = 'none'; hangupButton.style.display = 'none'; updateButton.style.removeProperty('display'); closeButton.style.display = 'none'; statusButton.style.display = 'none'; campaign_result.setAttribute('required', 'required'); nameContainer.style.removeProperty('display'); campaignContainer.style.removeProperty('display'); dataTableAdminLeader.ajax.reload(); console.log('Case call status: ', call.status); break; case call.CALL_STATUS.ANSWERED: // play sound sndAnswered.play(); inputCallStatus.value = call.status; callStatusAnswered = true; callButton.style.display = 'none'; hangupButton.style.removeProperty('display'); nameContainer.style.removeProperty('display'); closeButton.style.display = 'none'; statusButton.style.display = 'none'; console.log('Case call status: ', call.status); break; default: // BUSY // REJECTED // TIMEOUT // UNANSWERED inputCallStatus.value = call.status; callStatusAnswered = false; callButton.style.display = 'none'; hangupButton.style.display = 'none'; updateButton.style.removeProperty('display'); trashButton.style.removeProperty('display'); statusButton.style.display = 'none'; nameContainer.style.removeProperty('display'); waContainer.style.removeProperty('display'); closeButton.style.removeProperty('dispaly'); console.log('Case call status: ', call.status); console.log('Case call status default: ', call.status); break; } }); }).catch(function(){ alert('Network Problem, refresh page and try again later. Please contact dev team if this problem not fix in few minutes.'); console.error; $('#phone-call-modal').modal('hide'); }); });
Advertisement
Answer
There is no possible way for the Vonage (Nexmo) Voice API to validate whether your user should be able to call a phone number they have access to in your application. It sounds like you have provided users of your application the ability to initiate voice conversations with your Vonage API credentials. The validation logic for your users must rest in your application.
One solution you can consider is before initiating the new voice call, you can check against your database if the user has access or not. If they do not have access you can redirect them to another view in your application, and if they do have access you can initiate the call. Laravel has a entire suite of validation tooling that you may find helpful.