module.exports = function (app, io) { var Response = require('../lib/httpResponse'); var Audit = require('mongoose').model('Audit'); var acl = require('../lib/auth').acl; var reportGenerator = require('../lib/report-generator'); var _ = require('lodash'); var utils = require('../lib/utils'); var Settings = require('mongoose').model('Settings'); /* ### AUDITS LIST ### */ // Get audits list of user (all for admin) with regex filter on findings app.get('/api/audits', acl.hasPermission('audits:read'), function (req, res) { var getUsersRoom = function (room) { return utils.getSockets(io, room).map(s => s.username); }; var filters = {}; if (req.query.findingTitle) filters['findings.title'] = new RegExp( utils.escapeRegex(req.query.findingTitle), 'i', ); if (req.query.type && req.query.type === 'default') filters.$or = [{ type: 'default' }, { type: { $exists: false } }]; if (req.query.type && ['multi', 'retest'].includes(req.query.type)) filters.type = req.query.type; Audit.getAudits( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.decodedToken.id, filters, ) .then(msg => { var result = []; msg.forEach(audit => { var a = {}; a._id = audit._id; a.name = audit.name; a.language = audit.language; a.auditType = audit.auditType; a.creator = audit.creator; a.collaborators = audit.collaborators; a.company = audit.company; a.createdAt = audit.createdAt; a.ext = !!audit.template && !!audit.template.ext ? audit.template.ext : 'Template error'; a.reviewers = audit.reviewers; a.approvals = audit.approvals; a.state = audit.state; a.type = audit.type; a.parentId = audit.parentId; if (acl.isAllowed(req.decodedToken.role, 'audits:users-connected')) { a.connected = getUsersRoom(audit._id.toString()); } result.push(a); }); Response.Ok(res, result); }) .catch(err => Response.Internal(res, err)); }); // Create audit (default or multi) with name, auditType, language provided // parentId can be set only if type is default app.post( '/api/audits', acl.hasPermission('audits:create'), function (req, res) { if (!req.body.name || !req.body.language || !req.body.auditType) { Response.BadParameters( res, 'Missing some required parameters: name, language, auditType', ); return; } if (!utils.validFilename(req.body.language)) { Response.BadParameters(res, 'Invalid characters for language'); return; } var audit = {}; // Required params audit.name = req.body.name; audit.language = req.body.language; audit.auditType = req.body.auditType; audit.type = 'default'; // Optional params if (req.body.type && req.body.type === 'multi') audit.type = req.body.type; if (audit.type === 'default' && req.body.parentId) audit.parentId = req.body.parentId; Audit.create(audit, req.decodedToken.id) .then(inserted => Response.Created(res, { message: 'Audit created successfully', audit: inserted, }), ) .catch(err => Response.Internal(res, err)); }, ); // Get audits children app.get( '/api/audits/:auditId/children', acl.hasPermission('audits:read'), function (req, res) { var getUsersRoom = function (room) { return utils.getSockets(io, room).map(s => s.username); }; Audit.getAuditChildren( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(msg => { var result = []; msg.forEach(audit => { var a = {}; a._id = audit._id; a.name = audit.name; a.auditType = audit.auditType; a.approvals = audit.approvals; a.state = audit.state; if ( acl.isAllowed(req.decodedToken.role, 'audits:users-connected') ) { a.connected = getUsersRoom(audit._id.toString()); } result.push(a); }); Response.Ok(res, result); }) .catch(err => Response.Internal(res, err)); }, ); // Get audit retest with auditId app.get( '/api/audits/:auditId/retest', acl.hasPermission('audits:read'), function (req, res) { Audit.getRetest( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(msg => Response.Ok(res, msg)) .catch(err => Response.Internal(res, err)); }, ); // Create audit retest with auditId app.post( '/api/audits/:auditId/retest', acl.hasPermission('audits:create'), function (req, res) { if (!req.body.auditType) { Response.BadParameters( res, 'Missing some required parameters: auditType', ); return; } Audit.createRetest( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, req.body.auditType, ) .then(inserted => Response.Created(res, { message: 'Audit Retest created successfully', audit: inserted, }), ) .catch(err => Response.Internal(res, err)); }, ); // Delete audit if creator or admin app.delete( '/api/audits/:auditId', acl.hasPermission('audits:delete'), function (req, res) { Audit.delete( acl.isAllowed(req.decodedToken.role, 'audits:delete-all'), req.params.auditId, req.decodedToken.id, ) .then(msg => Response.Ok(res, msg)) .catch(err => Response.Internal(res, err)); }, ); /* ### AUDITS EDIT ### */ // Get Audit with ID app.get( '/api/audits/:auditId', acl.hasPermission('audits:read'), function (req, res) { Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(msg => Response.Ok(res, msg)) .catch(err => Response.Internal(res, err)); }, ); // Get audit general information app.get( '/api/audits/:auditId/general', acl.hasPermission('audits:read'), function (req, res) { Audit.getGeneral( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(msg => Response.Ok(res, msg)) .catch(err => Response.Internal(res, err)); }, ); // Update audit general information app.put( '/api/audits/:auditId/general', acl.hasPermission('audits:update'), async function (req, res) { var update = {}; var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); if (settings.reviews.enabled && audit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } if (req.body.reviewers) { if (req.body.reviewers.some(element => !element._id)) { Response.BadParameters(res, 'One or more reviewer is missing an _id'); return; } // Is the new reviewer the creator of the audit? if ( req.body.reviewers.some(element => element._id === audit.creator._id) ) { Response.BadParameters( res, 'A user cannot simultaneously be a reviewer and a collaborator/creator', ); return; } // Is the new reviewer one of the new collaborators that will override current collaborators? if (req.body.collaborators) { req.body.reviewers.forEach(reviewer => { if ( req.body.collaborators.some( element => !element._id || element._id === reviewer._id, ) ) { Response.BadParameters( res, 'A user cannot simultaneously be a reviewer and a collaborator/creator', ); return; } }); } // If no new collaborators are being set, is the new reviewer one of the current collaborators? else if (audit.collaborators) { req.body.reviewers.forEach(reviewer => { if ( audit.collaborators.some(element => element._id === reviewer._id) ) { Response.BadParameters( res, 'A user cannot simultaneously be a reviewer and a collaborator/creator', ); return; } }); } } if (req.body.collaborators) { if (req.body.collaborators.some(element => !element._id)) { Response.BadParameters( res, 'One or more collaborator is missing an _id', ); return; } // Are the new collaborators part of the current reviewers? req.body.collaborators.forEach(collaborator => { if ( audit.reviewers.some(element => element._id === collaborator._id) ) { Response.BadParameters( res, 'A user cannot simultaneously be a reviewer and a collaborator/creator', ); return; } }); // If the new collaborator already gave a review, remove said review, accept collaborator if (audit.approvals) { var newApprovals = audit.approvals.filter( approval => !req.body.collaborators.some( collaborator => approval.toString() === collaborator._id, ), ); update.approvals = newApprovals; } } // Optional parameters if (req.body.name) update.name = req.body.name; if (req.body.date) update.date = req.body.date; if (req.body.date_start) update.date_start = req.body.date_start; if (req.body.date_end) update.date_end = req.body.date_end; if (req.body.client !== undefined) update.client = req.body.client; if (req.body.company !== undefined) { update.company = {}; if (req.body.company && req.body.company._id) update.company._id = req.body.company._id; else if (req.body.company && req.body.company.name) update.company.name = req.body.company.name; else update.company = null; } if (req.body.collaborators) update.collaborators = req.body.collaborators; if (req.body.reviewers) update.reviewers = req.body.reviewers; if (req.body.language && utils.validFilename(req.body.language)) update.language = req.body.language; if (req.body.scope && typeof (req.body.scope === 'array')) { update.scope = req.body.scope.map(item => { return { name: item }; }); } if (req.body.template) update.template = req.body.template; if (req.body.customFields) update.customFields = req.body.customFields; if ( settings.reviews.enabled && settings.reviews.private.removeApprovalsUponUpdate ) update.approvals = []; Audit.updateGeneral( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, update, ) .then(msg => { io.to(req.params.auditId).emit('updateAudit'); Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); // Get audit network information app.get( '/api/audits/:auditId/network', acl.hasPermission('audits:read'), function (req, res) { Audit.getNetwork( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(msg => Response.Ok(res, msg)) .catch(err => Response.Internal(res, err)); }, ); // Update audit network information app.put( '/api/audits/:auditId/network', acl.hasPermission('audits:update'), async function (req, res) { var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); if (settings.reviews.enabled && audit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } var update = {}; // Optional parameters if (req.body.scope) update.scope = req.body.scope; if ( settings.reviews.enabled && settings.reviews.private.removeApprovalsUponUpdate ) update.approvals = []; Audit.updateNetwork( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, update, ) .then(msg => Response.Ok(res, msg)) .catch(err => Response.Internal(res, err)); }, ); // POST to export an encrypted PDF. app.post( '/api/audits/:auditId/generate/pdf', acl.hasPermission('audits:read'), function (req, res) { Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(async audit => { if ( ['ppt', 'pptx', 'doc', 'docx', 'docm'].includes(audit.template.ext) ) { let reportPdf; if (req.body.password) { reportPdf = await reportGenerator.generateEncryptedPdf( audit, req.body.password, ); } else { Response.BadParameters(res, 'No password included'); } if (reportPdf) { res.setHeader( 'Content-Disposition', `attachment; filename=${audit.name}.pdf`, ); res.setHeader('Content-Type', 'application/pdf'); res.send(reportPdf); } else { console.error('Error generating PDF'); Response.Internal(res, 'Error generating PDF'); } } else { Response.BadParameters( res, 'Template not in a Microsoft Word/Powerpoint format', ); } }) .catch(err => { console.log(err); if (err.code === 'ENOENT') Response.BadParameters(res, 'Template File not found'); else Response.Internal(res, err); }); }, ); // Generate report as PDF app.get( '/api/audits/:auditId/generate/pdf', acl.hasPermission('audits:read'), function (req, res) { Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(async audit => { if ( ['ppt', 'pptx', 'doc', 'docx', 'docm'].find( ext => ext === audit.template.ext, ) ) { var reportPdf = await reportGenerator.generatePdf(audit); Response.SendFile(res, `${audit.name}.pdf`, reportPdf); } else { Response.BadParameters( res, 'Template not in a Microsoft Word/Powerpoint format', ); } }) .catch(err => { console.log(err); if (err.code === 'ENOENT') Response.BadParameters(res, 'Template File not found'); else Response.Internal(res, err); }); }, ); // Generate Report as csv app.get( '/api/audits/:auditId/generate/csv', acl.hasPermission('audits:read'), function (req, res) { Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(async audit => { var reportCsv = await reportGenerator.generateCsv(audit); Response.SendFile(res, `${audit.name}.csv`, reportCsv); }) .catch(err => { console.log(err); Response.Internal(res, err); }); }, ); // Generate Report as json app.get( '/api/audits/:auditId/generate/json', acl.hasPermission('audits:read'), function (req, res) { Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(async audit => { Response.SendFile(res, `${audit.name}.json`, audit); }) .catch(err => { console.log(err); Response.Internal(res, err); }); }, ); // Add finding to audit app.post( '/api/audits/:auditId/findings', acl.hasPermission('audits:update'), async function (req, res) { var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); if (settings.reviews.enabled && audit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } if (!req.body.title) { Response.BadParameters(res, 'Missing some required parameters: title'); return; } var finding = {}; // Required parameters finding.title = req.body.title; // Optional parameters if (req.body.vulnType) finding.vulnType = req.body.vulnType; if (req.body.description) finding.description = req.body.description; if (req.body.observation) finding.observation = req.body.observation; if (req.body.remediation) finding.remediation = req.body.remediation; if (req.body.remediationComplexity) finding.remediationComplexity = req.body.remediationComplexity; if (req.body.priority) finding.priority = req.body.priority; if (req.body.references) finding.references = req.body.references; if (req.body.cwes) finding.cwes = req.body.cwes; if (req.body.cvssv3) finding.cvssv3 = req.body.cvssv3; if (req.body.poc) finding.poc = req.body.poc; if (req.body.scope) finding.scope = req.body.scope; if (req.body.status !== undefined) finding.status = req.body.status; if (req.body.category) finding.category = req.body.category; if (req.body.customFields) finding.customFields = req.body.customFields; if ( settings.reviews.enabled && settings.reviews.private.removeApprovalsUponUpdate ) { Audit.updateGeneral( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, { approvals: [] }, ); } Audit.createFinding( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, finding, ) .then(msg => { io.to(req.params.auditId).emit('updateAudit'); Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); // Get finding of audit app.get( '/api/audits/:auditId/findings/:findingId', acl.hasPermission('audits:read'), function (req, res) { Audit.getFinding( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, req.params.findingId, ) .then(msg => Response.Ok(res, msg)) .catch(err => Response.Internal(res, err)); }, ); // Update finding of audit app.put( '/api/audits/:auditId/findings/:findingId', acl.hasPermission('audits:update'), async function (req, res) { var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); if (settings.reviews.enabled && audit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } var finding = {}; // Optional parameters if (req.body.title) finding.title = req.body.title; if (req.body.vulnType) finding.vulnType = req.body.vulnType; if (!_.isNil(req.body.description)) finding.description = req.body.description; if (!_.isNil(req.body.observation)) finding.observation = req.body.observation; if (!_.isNil(req.body.remediation)) finding.remediation = req.body.remediation; if (req.body.remediationComplexity) finding.remediationComplexity = req.body.remediationComplexity; if (req.body.priority) finding.priority = req.body.priority; if (req.body.references) finding.references = req.body.references; if (req.body.cwes) finding.cwes = req.body.cwes; if (req.body.cvssv3) finding.cvssv3 = req.body.cvssv3; if (!_.isNil(req.body.poc)) finding.poc = req.body.poc; if (!_.isNil(req.body.scope)) finding.scope = req.body.scope; if (req.body.status !== undefined) finding.status = req.body.status; if (req.body.category) finding.category = req.body.category; if (req.body.customFields) finding.customFields = req.body.customFields; if (req.body.retestDescription) finding.retestDescription = req.body.retestDescription; if (req.body.retestStatus) finding.retestStatus = req.body.retestStatus; if ( settings.reviews.enabled && settings.reviews.private.removeApprovalsUponUpdate ) { Audit.updateGeneral( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, { approvals: [] }, ); } Audit.updateFinding( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, req.params.findingId, finding, ) .then(msg => { io.to(req.params.auditId).emit('updateAudit'); Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); // Delete finding of audit app.delete( '/api/audits/:auditId/findings/:findingId', acl.hasPermission('audits:update'), async function (req, res) { var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); if (settings.reviews.enabled && audit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } Audit.deleteFinding( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, req.params.findingId, ) .then(msg => { io.to(req.params.auditId).emit('updateAudit'); Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); // Get section of audit app.get( '/api/audits/:auditId/sections/:sectionId', acl.hasPermission('audits:read'), function (req, res) { Audit.getSection( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, req.params.sectionId, ) .then(msg => Response.Ok(res, msg)) .catch(err => Response.Internal(res, err)); }, ); // Update section of audit app.put( '/api/audits/:auditId/sections/:sectionId', acl.hasPermission('audits:update'), async function (req, res) { var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); if (settings.reviews.enabled && audit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } if (typeof req.body.customFields === 'undefined') { Response.BadParameters( res, 'Missing some required parameters: customFields', ); return; } var section = {}; // Mandatory parameters section.customFields = req.body.customFields; // For retrocompatibility with old section.text usage if (req.body.text) section.text = req.body.text; if ( settings.reviews.enabled && settings.reviews.private.removeApprovalsUponUpdate ) { Audit.updateGeneral( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, { approvals: [] }, ); } Audit.updateSection( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, req.params.sectionId, section, ) .then(msg => { Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); // Generate Report for specific audit app.get( '/api/audits/:auditId/generate', acl.hasPermission('audits:read'), function (req, res) { Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ) .then(async audit => { var settings = await Settings.getAll(); if ( settings.reviews.enabled && settings.reviews.public.mandatoryReview && audit.state !== 'APPROVED' ) { Response.Forbidden( res, 'Audit was not approved therefore cannot be exported.', ); return; } if (!audit.template) throw { fn: 'BadParameters', message: 'Template not defined' }; var reportDoc = await reportGenerator.generateDoc(audit); Response.SendFile( res, `${audit.name.replace(/[\\\/:*?"<>|]/g, '')}.${audit.template.ext || 'docx'}`, reportDoc, ); }) .catch(err => { if (err.code === 'ENOENT') Response.BadParameters(res, 'Template File not found'); else Response.Internal(res, err); }); }, ); // Update sort options of an audit app.put( '/api/audits/:auditId/sortfindings', acl.hasPermission('audits:update'), async function (req, res) { var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); if (settings.reviews.enabled && audit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } var update = {}; // Optional parameters if (req.body.sortFindings) update.sortFindings = req.body.sortFindings; if ( settings.reviews.enabled && settings.reviews.private.removeApprovalsUponUpdate ) update.approvals = []; Audit.updateSortFindings( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, update, ) .then(msg => { io.to(req.params.auditId).emit('updateAudit'); Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); // Update finding position (oldIndex -> newIndex) app.put( '/api/audits/:auditId/movefinding', acl.hasPermission('audits:update'), async function (req, res) { var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); if (settings.reviews.enabled && audit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } if ( typeof req.body.oldIndex === 'undefined' || typeof req.body.newIndex === 'undefined' ) { Response.BadParameters( res, 'Missing some required parameters: oldIndex, newIndex', ); return; } var move = {}; // Required parameters move.oldIndex = req.body.oldIndex; move.newIndex = req.body.newIndex; if ( settings.reviews.enabled && settings.reviews.private.removeApprovalsUponUpdate ) { Audit.updateGeneral( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, { approvals: [] }, ); } Audit.moveFindingPosition( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, move, ) .then(msg => { io.to(req.params.auditId).emit('updateAudit'); Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); // Give or remove a reviewer's approval to an audit app.put( '/api/audits/:auditId/toggleApproval', acl.hasPermission('audits:review'), async function (req, res) { const settings = await Settings.getAll(); if (!settings.reviews.enabled) { Response.Forbidden(res, 'Audit reviews are not enabled.'); return; } Audit.findById(req.params.auditId) .then(audit => { if (audit.state !== 'REVIEW' && audit.state !== 'APPROVED') { Response.Forbidden( res, 'The audit is not approvable in the current state.', ); return; } var hasApprovedBefore = false; var newApprovalsArray = []; if (audit.approvals) { audit.approvals.forEach(approval => { if (approval._id.toString() === req.decodedToken.id) { hasApprovedBefore = true; } else { newApprovalsArray.push(approval); } }); } if (!hasApprovedBefore) { newApprovalsArray.push({ _id: req.decodedToken.id, role: req.decodedToken.role, username: req.decodedToken.username, firstname: req.decodedToken.firstname, lastname: req.decodedToken.lastname, }); } var update = { approvals: newApprovalsArray }; Audit.updateApprovals( acl.isAllowed(req.decodedToken.role, 'audits:review-all'), req.params.auditId, req.decodedToken.id, update, ) .then(() => { io.to(req.params.auditId).emit('updateAudit'); Response.Ok(res, 'Approval updated successfully.'); }) .catch(err => { Response.Internal(res, err); }); }) .catch(err => { Response.Internal(res, err); }); }, ); // Sets the audit state to EDIT or REVIEW app.put( '/api/audits/:auditId/updateReadyForReview', acl.hasPermission('audits:update'), async function (req, res) { const settings = await Settings.getAll(); if (!settings.reviews.enabled) { Response.Forbidden(res, 'Audit reviews are not enabled.'); return; } var update = {}; var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); if (audit.state !== 'EDIT' && audit.state !== 'REVIEW') { Response.Forbidden( res, 'The audit is not in the proper state for this action.', ); return; } if ( req.body.state != undefined && (req.body.state === 'EDIT' || req.body.state === 'REVIEW') ) update.state = req.body.state; if (update.state === 'EDIT') { var newApprovalsArray = []; if (audit.approvals) { audit.approvals.forEach(approval => { if (approval._id.toString() !== req.decodedToken.id) { newApprovalsArray.push(approval); } }); update.approvals = newApprovalsArray; } } Audit.updateGeneral( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, update, ) .then(msg => { io.to(req.params.auditId).emit('updateAudit'); Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); // Update parentId of Audit app.put( '/api/audits/:auditId/updateParent', acl.hasPermission('audits:create'), async function (req, res) { var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.body.parentId, req.decodedToken.id, ); if (settings.reviews.enabled && audit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } if (!req.body.parentId) { Response.BadParameters( res, 'Missing some required parameters: parentId', ); return; } Audit.updateParent( acl.isAllowed(req.decodedToken.role, 'audits:update-all'), req.params.auditId, req.decodedToken.id, req.body.parentId, ) .then(msg => { io.to(req.body.parentId).emit('updateAudit'); Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); // Delete parentId of Audit app.delete( '/api/audits/:auditId/deleteParent', acl.hasPermission('audits:delete'), async function (req, res) { var settings = await Settings.getAll(); var audit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), req.params.auditId, req.decodedToken.id, ); var parentAudit = await Audit.getAudit( acl.isAllowed(req.decodedToken.role, 'audits:read-all'), audit.parentId, req.decodedToken.id, ); if (settings.reviews.enabled && parentAudit.state !== 'EDIT') { Response.Forbidden( res, 'The audit is not in the EDIT state and therefore cannot be edited.', ); return; } Audit.deleteParent( acl.isAllowed(req.decodedToken.role, 'audits:delete-all'), req.params.auditId, req.decodedToken.id, ) .then(msg => { if (msg.parentId) io.to(msg.parentId.toString()).emit('updateAudit'); Response.Ok(res, msg); }) .catch(err => Response.Internal(res, err)); }, ); };