| What you want is to guard at the outset of each API call is the same thing -- whether the user is not suspended, active, any other number of things (including more things as you add more functionality), so you want to shift the entire validation logic to its own module. We therefore want to encapsulate the logic of all users being "qualified". I think the best way to realize this is to try to say in as few words as possible what we want. We want the user.toBe.qualified, but we can write that even more efficiently, user.isQualified(), so we can ASK at the top of each API `if !user.isQualified()` and then fail loudly and quickly if so, and then go do something afterwards otherwise. We presumably expect multiple users that will have a property of being qualified, so let's use a class: ```js class User {
constructor(data) {
this.isAuthenticated = data.isAuthenticated;
this.hasActiveLicense = data.hasActiveLicense;
\\ many more details...
} // Now encapsulate the boolean property of being qualified, for all Users, in one place isQualified() {
return (this.isAuthorized && this.hasActiveLicense && this.hasPaidInFull && this.hasAdminPermissions);
}
} // Now, we can use our isQualified() property at each of our API guard clause by checking if the user is NOT qualified: if (!user.isQualified()) {
return res.status(403).json({ "error": "Unauthorized access requested."});
} // Critically, the API guard clause remains identical even if we change the criteria for validation and what it means to be "qualified" ``` There are of course many other ways to solve this problem beyond this approach. For instance, you could export a function called isUserQualified(user) (or more likely userId) then call it somewhat similarly: ```js if (!isUserQualified(user)) {
return res.status(403).send("Error: Unauthorized access requested");
} ``` The other approach I like is to use a factory pattern to build a userSession with function arrow notation. That's really helpful when you are getting raw data back from a database call, say as a JSON object. That would look like this: ```js const createUserSession = (userData) => {
return {
...userData, // here we use the ...notation for convenience but don't trust this as secure)
isQualified() {
return (
this.isAuthorized && !this.isSuspended
);
}
};
}; // Then you instantiate a user session from the request like this: const user = createUserSession(res.session.user);
if (!user.isQualified()) {
return res.status(403).send("Error: Unauthorized");
} ``` There are actually loads other approaches too, like using class inheritance (define a parent User class and child QualifiedUser class, for instance), or types. I would say that readability here is less a concern than the issue that you have multiple APIs and if you refactor or change the logic for qualified users, now you have to refactor your most sensitive attack surface at multiple points. It's just safer to have that rewrite only happening once, wherever you put it, in my view. |