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');
});
});
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.