@ferjssilva/fast-crud-api
Version:
A complete and fast crud API generator
103 lines (89 loc) • 3.16 kB
JavaScript
/**
* User-scoped resource middleware
* Automatically filters and secures resources by user ID
* @param {Object} model - Mongoose model
* @param {String} modelName - Model collection name
* @param {Array} userScopedResources - Array of resource names that should be user-scoped
*/
function createUserScopeHandler(model, modelName, userScopedResources = []) {
// Skip if userScoped is not provided, is null, or is empty
if (!userScopedResources || userScopedResources.length === 0) {
return null;
}
// Check if this model is in the userScoped list
if (!userScopedResources.includes(modelName)) {
return null;
}
// Return the preHandler hook
return async function userScopePreHandler(request, reply) {
const method = request.method;
// Check authentication for all user-scoped operations
if (!request.userId) {
reply.code(401).send({
error: 'Unauthorized',
message: 'Authentication required'
});
return;
}
// Handle GET requests
if (method === 'GET') {
// Check if user is trying to access other users' data via query params
if (request.query.userId && request.query.userId !== request.userId) {
reply.code(403).send({
error: 'Forbidden',
message: 'Cannot access other users\' data'
});
return;
}
// Automatically inject userId filter
request.query.userId = request.userId;
}
// Handle POST requests
if (method === 'POST') {
// Check if user is trying to set a different userId
if (request.body.userId && request.body.userId !== request.userId) {
reply.code(403).send({
error: 'Forbidden',
message: 'Cannot modify other users\' data'
});
return;
}
// Automatically inject userId
request.body.userId = request.userId;
}
// Handle PUT requests
if (method === 'PUT') {
// Check if user is trying to set a different userId
if (request.body.userId && request.body.userId !== request.userId) {
reply.code(403).send({
error: 'Forbidden',
message: 'Cannot modify other users\' data'
});
return;
}
// Automatically inject userId (will be used in the update)
request.body.userId = request.userId;
// Note: Ownership verification is handled atomically in the route handler
// to prevent race conditions (TOCTOU vulnerabilities)
}
// Handle DELETE requests
// Note: Ownership verification is handled atomically in the route handler
// to prevent race conditions. No additional checks needed here.
};
}
/**
* Check if a resource is user-scoped
* @param {String} modelName - Model collection name
* @param {Array} userScopedResources - Array of user-scoped resource names
* @returns {Boolean} True if the resource is user-scoped
*/
function isUserScoped(modelName, userScopedResources = []) {
if (!userScopedResources || userScopedResources.length === 0) {
return false;
}
return userScopedResources.includes(modelName);
}
module.exports = {
createUserScopeHandler,
isUserScoped
};