This is one of many problems with today's frameworks and stacks. They pile up tech, but leave the most important parts to the people who use them - most of the time, the people use them because they lack experience and won't be able to integrate those most important parts - most likely, they don't even know that they have to implement them. Well, that's just me talking, so let's get to your problem.
One easy way to secure the web presence against DDoS is to use a proxy in front of it. You might use NGINX in order to do so and have it handle https, then forward the http connection to your NodeJS instance on a different port. Here's an article on how to configure NGINX so that it limits the number of requests. Don't forget that putting anything in front of your NodeJS application means that you no longer have the possibility to use advanced HTTP/2 techniques.
As for "unintended call"s, I don't really understand what you mean by that. Each and every call to an important procedure (for example "delete user") should have a call to an authentication routine, which makes sure the current user has the necessary rights. You might implement user-right-management by defining users with the right, use access levels, use ACLs, or give out access keys for certain operations. Just... make sure your authentication system is very stable and secure.
// Define users
const usersWithDeleteRights = [
'eve',
'alice',
];
function deleteUser(requestState, userToDelete) {
if (usersWithDeleteRights.includes(requestState.session.userId)) {
database.users.deleteById(userToDelete);
return;
}
throw new Error('Insufficient rights!');
}
// Use access levels
const accessLevels = {
none: 0,
createUser: 1,
updateUser: 2,
deleteUser: 3,
};
function deleteUser(requestState, userToDelete) {
if (requestState.session.accessLevel >= accessLevel.deleteLevel) {
database.users.deleteById(userToDelete);
return;
}
throw new Error('Insufficient rights!');
}
// Use ACLs
const acl = {
alice: {
createUser: true,
updateUser: true,
deleteUser: true,
},
oscar: {
createUser: true,
updateUser: true,
deleteUser: false,
},
};
function deleteUser(requestState, userToDelete) {
if (acl[requestState.session.userId].deleteUser) {
database.users.deleteById(userToDelete);
return;
}
throw new Error('Insufficient rights!');
}
// Use Keys
const keys = {
K_CREATE_USER: 1,
K_UPDATE_USER: 2,
K_DELETE_USER: 3,
};
const keyRing = {
eve: [K_CREATE_USER, K_UPDATE_USER, K_DELETE_USER],
oscar: [K_CREATE_USER, K_UPDATE_USER],
};
function deleteUser(requestState, userToDelete) {
if (keyRing[requestState.session.userId].includes(K_DELETE_USER)) {
database.users.deleteById(userToDelete);
return;
}
throw new Error('Insufficient rights!');
}
Of course, above code snippets are just explanatory. Please use a database to store information, like the keys and who has which keys. Also, use proper type checks (for example with instanced objects of a named function and for type-checking using instanceof) and clean error propagation. I highly recommend using Monads, like Results, instead of throwing like a madman.
Oh, and don't forget that each and every method of the ones listed above have their advantages and disadvantages. You should take a bit of time and read up on them, then decide for the one which is most suitable for your project. Personally, I recommend using ACLs, though I have very good experience with keys, as they are very generic and extensible (however also a bit harder to implement and take up more resources to check, because they are so generic - tbh, they are a pain when you want to have groups, which usually makes a lot of sense when you have more than three keys and five users :D).