diff --git a/nodejs/src/controller/web/brainController.js b/nodejs/src/controller/web/brainController.js index 1547865d..ea33985f 100644 --- a/nodejs/src/controller/web/brainController.js +++ b/nodejs/src/controller/web/brainController.js @@ -39,11 +39,53 @@ const deleteBrain = catchAsync(async (req, res) => { }) const deleteAllBrain = catchAsync(async (req, res) => { + // Feature flag check + if (process.env.BULK_DELETE_BRAINS_ENABLED !== 'true') { + return res.status(403).json({ error: 'Bulk delete is disabled by feature flag.' }); + } + + // Confirm param check + if (req.query.confirm !== 'true' && req.body.confirm !== true) { + return res.status(400).json({ error: 'Explicit confirmation required (confirm=true).' }); + } + + // Role/permission defense-in-depth + const allowedRoles = ['COMPANY', 'MANAGER']; + const userRoles = req.user.roles || []; + const userPermissions = req.user.permissions || []; + if (!userRoles.some(role => allowedRoles.includes(role)) || !userPermissions.includes('brain.delete_all')) { + return res.status(403).json({ error: 'Forbidden: insufficient permissions.' }); + } + + // Rate limiting (simple in-memory, replace with Redis for prod) + if (!global.bulkDeleteRateLimit) global.bulkDeleteRateLimit = {}; + const userId = req.userId; + const now = Date.now(); + const windowMs = 60 * 60 * 1000; // 1 hour + const maxDeletes = 2; + const userLimit = global.bulkDeleteRateLimit[userId] || { count: 0, last: 0 }; + if (userLimit.last + windowMs > now) { + if (userLimit.count >= maxDeletes) { + return res.status(429).json({ error: 'Rate limit exceeded for bulk delete.' }); + } + userLimit.count++; + } else { + userLimit.count = 1; + userLimit.last = now; + } + global.bulkDeleteRateLimit[userId] = userLimit; + + // Audit logging + const logger = require('../../utils/logger'); + logger.info(`Bulk delete brains requested by user ${userId} (${req.user.email}) at ${new Date().toISOString()}`); + const result = await brainService.deleteAllBrain(req); if (result) { + logger.info(`Bulk delete brains SUCCESS for user ${userId} (${req.user.email}) at ${new Date().toISOString()}`); res.message = _localize('module.delete', req, BRAIN); return util.successResponse(result, res); } + logger.error(`Bulk delete brains FAILURE for user ${userId} (${req.user.email}) at ${new Date().toISOString()}`); return util.failureResponse(_localize('module.deleteError', req, BRAIN), res); }) diff --git a/nodejs/src/middleware/authorization.js b/nodejs/src/middleware/authorization.js new file mode 100644 index 00000000..e4d8d413 --- /dev/null +++ b/nodejs/src/middleware/authorization.js @@ -0,0 +1,22 @@ +// Authorization middleware for role/permission checks +module.exports = function checkPermission(permission) { + return function (req, res, next) { + const user = req.user; + // Feature flag: disable endpoint if not enabled + if (process.env.BULK_DELETE_BRAINS_ENABLED !== 'true') { + return res.status(403).json({ error: 'Bulk delete is disabled by feature flag.' }); + } + // Check user and permissions + if (!user || !user.permissions || !user.roles) { + return res.status(403).json({ error: 'Unauthorized: missing user roles/permissions.' }); + } + // Only COMPANY or MANAGER roles allowed + const allowedRoles = ['COMPANY', 'MANAGER']; + const hasRole = user.roles.some(role => allowedRoles.includes(role)); + const hasPermission = user.permissions.includes(permission); + if (hasRole && hasPermission) { + return next(); + } + return res.status(403).json({ error: 'Forbidden: insufficient permissions.' }); + }; +}; diff --git a/nodejs/src/routes/web/brains.js b/nodejs/src/routes/web/brains.js index 0ec1f09b..2428d42c 100644 --- a/nodejs/src/routes/web/brains.js +++ b/nodejs/src/routes/web/brains.js @@ -4,13 +4,14 @@ const brainController = require('../../controller/web/brainController'); const { createBrainKeys, updateBrainKeys, shareBrainKeys, unshareBrainKeys, shareDocKeys } = require('../../utils/validations/brain'); const { partialUpdateKeys } = require('../../utils/validations/common'); const { authentication } = require('../../middleware/authentication'); +const checkPermission = require('../../middleware/authorization'); const { checkPromptLimit } = require('../../middleware/promptlimit'); router.post('/create', validate(createBrainKeys), authentication,checkPromptLimit, brainController.createBrain); router.put('/update/:id', validate(updateBrainKeys), authentication,checkPromptLimit, brainController.updateBrain); router.get('/:slug', authentication, brainController.getBrain); router.delete('/delete/:id', authentication, brainController.deleteBrain); -router.delete('/deleteall', authentication, brainController.deleteAllBrain); +router.delete('/deleteall', authentication, checkPermission('brain.delete_all'), brainController.deleteAllBrain); router.post('/list', authentication, checkPromptLimit, brainController.getAll); router.patch('/partial/:slug', validate(partialUpdateKeys), authentication, brainController.partialUpdate); router.post('/unshare', validate(unshareBrainKeys), authentication, brainController.unShareBrain);