added authorization classes to test endpoints based in permissions
This commit is contained in:
parent
6817ed002c
commit
c70e65e58c
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { sendResponseAccessDenied } from '../utils/responses.js';
|
||||||
|
import { checkPermission, getDataFromToken, generateAccessToken} from '../services/authService.js';
|
||||||
|
import { getRolesHasPermissionsModel } from '../models/authorization/roles_has_permissionsModel.js';
|
||||||
|
import mysql from '../adapters/mysql.js';
|
||||||
|
import { error404, errorHandler } from '../utils/errors.js';
|
||||||
|
import { noResults } from '../validators/result-validators.js';
|
||||||
|
|
||||||
|
const obtainToken = (req, res) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const token = req.header('Authorization')?.replace('Bearer ', '');
|
||||||
|
if (token) {
|
||||||
|
resolve(token);
|
||||||
|
} else {
|
||||||
|
sendResponseAccessDenied(res, { message: 'No authorization provided. Access token required' });
|
||||||
|
reject(new Error('No authorization provided'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setToken = (result, req, res, next, config) => {
|
||||||
|
const { user, role } = result._data
|
||||||
|
const token = generateAccessToken({
|
||||||
|
payload: { user, role },
|
||||||
|
config
|
||||||
|
})
|
||||||
|
next({ user: { ...result, token } })
|
||||||
|
}
|
||||||
|
|
||||||
|
const authenticateToken = (req, res, next) => {
|
||||||
|
obtainToken(req, res)
|
||||||
|
.then((token) => getDataFromToken(token))
|
||||||
|
.then((decoded) => {
|
||||||
|
req.auth = decoded;
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error in authentication middleware:', error);
|
||||||
|
sendResponseAccessDenied(res, { message: 'Access denied. Invalid token.' });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const authorizePermission = (endpoint) => {
|
||||||
|
return (req, res, next, config) => {
|
||||||
|
obtainToken(req, res)
|
||||||
|
.then((token) => getDataFromToken(token)) //extract user data from the token
|
||||||
|
.then((decoded) => {
|
||||||
|
const roleName = decoded.payload.role
|
||||||
|
_getRolePermissionsByName(roleName, config)
|
||||||
|
.then((rolePermissions) => {
|
||||||
|
const action = req.method
|
||||||
|
//check if the user has the necessary permissions
|
||||||
|
return checkPermission(action, endpoint, rolePermissions)
|
||||||
|
.then(({ hasPermission }) => {
|
||||||
|
if (!hasPermission) {
|
||||||
|
sendResponseAccessDenied(res, {
|
||||||
|
message: `You don't have permission to ${action} on ${endpoint}`
|
||||||
|
});
|
||||||
|
throw new Error(`Permission denied for ${action} on ${endpoint}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//attach user and role to the request object
|
||||||
|
//req.auth.user = user;
|
||||||
|
//req.auth.role = role;
|
||||||
|
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error in authorization middleware:', error);
|
||||||
|
sendResponseAccessDenied(res, { message: 'Authorization error', error: error.message });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const _getRolePermissionsByName = (roleName, config) => {
|
||||||
|
const conn = mysql.start(config)
|
||||||
|
return getRolesHasPermissionsModel({ roleName, conn })
|
||||||
|
.then((response) => {
|
||||||
|
if (noResults(response)) {
|
||||||
|
const err = error404()
|
||||||
|
const error = errorHandler(err, config.environment)
|
||||||
|
return sendResponseNotFound(res, error)
|
||||||
|
}
|
||||||
|
console.log(response)
|
||||||
|
return response.map(({ permission_action, permission_endpoint }) => ({
|
||||||
|
permission_action,
|
||||||
|
permission_endpoint
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
const error = errorHandler(err, config.environment)
|
||||||
|
res.status(error.code).json(error)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
mysql.end(conn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
authenticateToken,
|
||||||
|
authorizePermission,
|
||||||
|
setToken
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes and verifies a JWT token.
|
||||||
|
* @param {string} token - The JWT token to verify.
|
||||||
|
* @returns {Promise<Object>} - Resolves with the decoded token payload.
|
||||||
|
*/
|
||||||
|
const getDataFromToken = (token) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const JWT_SECRET = process.env.JWT_SECRET;
|
||||||
|
jwt.verify(token, JWT_SECRET, (err, decoded) => {
|
||||||
|
err ? reject(err) : resolve(decoded);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new JWT token.
|
||||||
|
* @param {Object} payload - The payload to include in the token.
|
||||||
|
* @returns {string} - The generated JWT token.
|
||||||
|
*/
|
||||||
|
const generateAccessToken = (payload) => {
|
||||||
|
const JWT_SECRET = process.env.JWT_SECRET;
|
||||||
|
const JWT_TIME = parseInt(process.env.JWT_TIME, 10);
|
||||||
|
|
||||||
|
return jwt.sign(payload, JWT_SECRET, JWT_TIME ? { expiresIn: JWT_TIME } : {});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a user has permission to perform an action on a resource.
|
||||||
|
* @param {string} userId - The ID of the user.
|
||||||
|
* @param {string} action - The action to check (e.g., GET, POST, PUT, DELETE).
|
||||||
|
* @param {string} endpoin - The endpoint of the attack
|
||||||
|
* @param {Array} userPermissions - The list of permissions assigned to the user.
|
||||||
|
* @returns {Promise<Object>} - Resolves with an object containing permission details.
|
||||||
|
*/
|
||||||
|
const checkPermission = (action, endpoint, userPermissions) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const hasPermission = userPermissions.some(
|
||||||
|
(permission) =>
|
||||||
|
permission.permission_action === action && permission.permission_endpoint === endpoint
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasPermission) {
|
||||||
|
resolve({ hasPermission: true });
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Permission denied for ${action} on ${endpoint}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDataFromToken,
|
||||||
|
generateAccessToken,
|
||||||
|
checkPermission,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { sendErrorForbidden } from './errorUtils'
|
||||||
|
|
||||||
|
// Simplified version focusing on roles instead of parties
|
||||||
|
const getPermittedBusinessUnits = (req) => {
|
||||||
|
// Instead of extracting from parties_accesses,
|
||||||
|
// extract from user roles or permissions
|
||||||
|
const userRoles = req.auth.user.roles || []
|
||||||
|
|
||||||
|
// Example: Map roles to allowed business units
|
||||||
|
const businessUnitsByRole = {
|
||||||
|
'admin': ['all'], // Admin can access everything
|
||||||
|
'manager': ['specific-business-unit-uuid'], // Managers might have limited access
|
||||||
|
'viewer': ['read-only-business-unit-uuid']
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect business units based on user's roles
|
||||||
|
const allowedBusinessUnits = userRoles
|
||||||
|
.flatMap(role => businessUnitsByRole[role] || [])
|
||||||
|
.filter(bu => bu !== 'all')
|
||||||
|
|
||||||
|
return allowedBusinessUnits.length ? allowedBusinessUnits.join(',') : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPermittedCustomers = (req) => {
|
||||||
|
// Similar approach for customers
|
||||||
|
const userRoles = req.auth.user.roles || []
|
||||||
|
|
||||||
|
const customersByRole = {
|
||||||
|
'admin': ['all'],
|
||||||
|
'manager': ['specific-customer-uuid'],
|
||||||
|
'viewer': ['read-only-customer-uuid']
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedCustomers = userRoles
|
||||||
|
.flatMap(role => customersByRole[role] || [])
|
||||||
|
.filter(customer => customer !== 'all')
|
||||||
|
|
||||||
|
return allowedCustomers.length
|
||||||
|
? { mandatory: allowedCustomers[0] }
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
const prepareRequest = (req, type, customerResult, businessUnitResult) => {
|
||||||
|
return {
|
||||||
|
...req[type],
|
||||||
|
...(customerResult ? { customerUuid: customerResult.mandatory } : {}),
|
||||||
|
...(businessUnitResult ? { businessUnitUuid: businessUnitResult } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenPermissionsContentFilter = (req, res, next, config) => {
|
||||||
|
try {
|
||||||
|
const businessUnitUuid = getPermittedBusinessUnits(req)
|
||||||
|
const customerUuidResult = getPermittedCustomers(req)
|
||||||
|
|
||||||
|
req.body = prepareRequest(req, 'body', customerUuidResult, businessUnitUuid)
|
||||||
|
req.query = prepareRequest(req, 'query', customerUuidResult, businessUnitUuid)
|
||||||
|
|
||||||
|
return next()
|
||||||
|
} catch {
|
||||||
|
sendErrorForbidden({ res, config })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { tokenPermissionsContentFilter }
|
||||||
Loading…
Reference in New Issue