diff --git a/forge/auditLog/device.js b/forge/auditLog/device.js index b3978b0a1e..455633a403 100644 --- a/forge/auditLog/device.js +++ b/forge/auditLog/device.js @@ -22,6 +22,9 @@ module.exports = { } await log('device.unassigned', actionedBy, device.id, generateBody(bodyData)) }, + async startFailed (actionedBy, error, device) { + await log('device.start-failed', actionedBy || 0, device?.id, generateBody({ error })) + }, credentials: { async generated (actionedBy, error, device) { await log('device.credential.generated', actionedBy, device?.id, generateBody({ error, device })) diff --git a/forge/auditLog/formatters.js b/forge/auditLog/formatters.js index ae0ec9475a..3c9e48b2da 100644 --- a/forge/auditLog/formatters.js +++ b/forge/auditLog/formatters.js @@ -171,6 +171,19 @@ const formatLogEntry = (auditLogDbRow) => { formatted.body.context = { key: body.key, scope: body.scope, store: body.store } } + // format log entries for know Node-RED audit events + if (formatted.event === 'flows.set') { + formatted.body = formatted.body || {} + formatted.body.flowsSet = formatted.body?.flowsSet || { type: body.type } + } + // TODO: Add other known Node-RED audit events + // including: 'nodes.install', 'nodes.remove', 'library.set' + // this will permit audit viewer to access the details of the event + // via the body.xxx object and thus permit the UI to display the details + // instead of the current generic message + // e.g. to show _which_ module was installed or removed + // e.g. to show _which_ library was set + const roleObj = body?.role && roleObject(body.role) if (roleObj) { if (formatted.body?.user) { diff --git a/forge/routes/logging/index.js b/forge/routes/logging/index.js index 50ebf23222..0b7ec4791f 100644 --- a/forge/routes/logging/index.js +++ b/forge/routes/logging/index.js @@ -1,4 +1,5 @@ -const { getLoggers } = require('../../auditLog/project') +const { getLoggers: getDeviceLogger } = require('../../auditLog/device') +const { getLoggers: getProjectLogger } = require('../../auditLog/project') /** Node-RED Audit Logging backend * @@ -9,23 +10,36 @@ const { getLoggers } = require('../../auditLog/project') */ module.exports = async function (app) { - const logger = getLoggers(app) + const deviceAuditLogger = getDeviceLogger(app) + const projectAuditLogger = getProjectLogger(app) + /** @type {import('../../db/controllers/AuditLog')} */ + const auditLogController = app.db.controllers.AuditLog + app.addHook('preHandler', app.verifySession) - app.addHook('preHandler', async (request, response) => { - // The request has a valid token, but need to check the token is allowed - // to access the project - const id = request.params.projectId - // Check if the project exists first - const project = await app.db.models.Project.byId(id) - if (project && request.session.ownerType === 'project' && request.session.ownerId === id) { - // Project exists and the auth token is for this project - request.project = project - return + /** + * Post route for node-red _cloud_ instance audit log events + * @method POST + * @name /logging/:projectId/audit + * @memberof forge.routes.logging + */ + app.post('/:projectId/audit', { + preHandler: async (request, response) => { + // The request has a valid token, but need to check the token is allowed + // to access the project + + const id = request.params.projectId + // Check if the project exists first + const project = await app.db.models.Project.byId(id) + if (project && request.session.ownerType === 'project' && request.session.ownerId === id) { + // Project exists and the auth token is for this project + request.project = project + return + } + response.status(404).send({ code: 'not_found', error: 'Not Found' }) } - response.status(404).send({ code: 'not_found', error: 'Not Found' }) - }) - app.post('/:projectId/audit', async (request, response) => { + }, + async (request, response) => { const projectId = request.params.projectId const auditEvent = request.body const event = auditEvent.event @@ -34,7 +48,7 @@ module.exports = async function (app) { // first check to see if the event is a known structured event if (event === 'start-failed') { - await logger.project.startFailed(userId || 'system', error, { id: projectId }) + await projectAuditLogger.project.startFailed(userId || 'system', error, { id: projectId }) } else { // otherwise, just log it delete auditEvent.event @@ -42,7 +56,7 @@ module.exports = async function (app) { delete auditEvent.path delete auditEvent.timestamp - await app.db.controllers.AuditLog.projectLog( + await auditLogController.projectLog( projectId, userId, event, @@ -63,4 +77,52 @@ module.exports = async function (app) { response.status(200).send() }) + + /** + * Post route for node_red device audit log events + * @method POST + * @name /logging/device/:deviceId/audit + * @memberof forge.routes.logging + */ + app.post('/device/:deviceId/audit', { + preHandler: async (request, response) => { + // The request has a valid token, but need to check the token is allowed + // to access the device + const id = request.params.deviceId + // Check if the device exists first + const device = await app.db.models.Device.byId(id) + if (device && request.session.ownerType === 'device' && +request.session.ownerId === device.id) { + // device exists and the auth token is for this device + request.device = device + return + } + response.status(404).send({ code: 'not_found', error: 'Not Found' }) + } + }, async (request, response) => { + const deviceId = request.params.deviceId + const auditEvent = request.body + const event = auditEvent.event + const error = auditEvent.error + const userId = auditEvent.user ? app.db.models.User.decodeHashid(auditEvent.user) : undefined + + // first check to see if the event is a known structured event + if (event === 'start-failed') { + await deviceAuditLogger.device.startFailed(userId || 'system', error, { id: deviceId }) + } else { + // otherwise, just log it + delete auditEvent.event + delete auditEvent.user + delete auditEvent.path + delete auditEvent.timestamp + + await auditLogController.deviceLog( + request.device.id, + userId, + event, + auditEvent + ) + } + + response.status(200).send() + }) } diff --git a/frontend/src/components/audit-log/AuditEntryVerbose.vue b/frontend/src/components/audit-log/AuditEntryVerbose.vue index 33ef1edc2c..d91d355f84 100644 --- a/frontend/src/components/audit-log/AuditEntryVerbose.vue +++ b/frontend/src/components/audit-log/AuditEntryVerbose.vue @@ -151,7 +151,8 @@