From dc25bd7de72aa574767876341e5792733c2ee0e0 Mon Sep 17 00:00:00 2001 From: Jannik Schönartz Date: Sun, 1 Dec 2019 15:03:55 +0000 Subject: [server/log] Add logging to all modules Logging with snapshots: Client: create / edit / delete / added to group / removed from group Group: create / edit / delete / added to group / removed from group Logging without snapshot: Wake-on-lan: wakup Ipxe-Builder: build / clear / cancel / script save IP-Ranges: create / edit / delete Logging: with info in description: User: create / edit / delete / grant role / revoke role Event: create / edit / delete Permission-Manager-Role: create / edit / delete Registration-Hook: create / delete / edit / change order Ipxe Configuration: create / delete / edit Backend: create / edit / delete --- server/api/backends.js | 109 +++++++++++++++++- server/api/clients.js | 78 ++++++++++++- server/api/events.js | 99 +++++++++++++++- server/api/groups.js | 282 ++++++++++++++++++++++++++++++++++++++++++--- server/api/ipranges.js | 62 +++++++++- server/api/ipxe.js | 44 ++++++- server/api/ipxeconfigs.js | 135 +++++++++++++++++++++- server/api/registration.js | 73 ++++++++++-- server/api/roles.js | 78 ++++++++++++- server/api/systemlog.js | 2 +- server/api/users.js | 251 +++++++++++++++++++++++++++++++++++++--- server/api/wakerequests.js | 6 + server/lib/log.js | 13 ++- server/lib/wolhelper.js | 16 +++ 14 files changed, 1178 insertions(+), 70 deletions(-) (limited to 'server') diff --git a/server/api/backends.js b/server/api/backends.js index 32c7cc7..b75634f 100644 --- a/server/api/backends.js +++ b/server/api/backends.js @@ -6,6 +6,8 @@ var express = require('express') const { decorateApp } = require('@awaitjs/express') var router = decorateApp(express.Router()) var noAuthRouter = decorateApp(express.Router()) +const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse')) +const log = require(path.join(__appdir, 'lib', 'log')) // GET requests. @@ -384,8 +386,31 @@ router.postAsync('/:id/connection', async (req, res) => { const b = new ExternalBackends() const instance = b.getInstance(backend.type) const response = await instance.checkConnection(backend.credentials) - if (response.error) return res.status(500).send(response) - else if (response) res.status(200).send() + + if (response.error) { + console.log(response) + log({ + category: 'ERROR_BACKEND_CHECKCONNECTION', + description: '[' + backend.id + '] ' + backend.name + ': Connection error. [' + response.error + ']\n' + + 'ID: ' + backend.id + '\n' + + 'Name: ' + backend.name + '\n' + + 'Type: ' + backend.type + '\n' + + 'Error: ' + response.error + '\n' + + 'Message: ' + response.message, + userId: req.user.id + }) + return res.status(500).send(response) + } else if (response) { + log({ + category: 'BACKEND_CHECKCONNECTION', + description: '[' + backend.id + '] ' + backend.name + ': Connection successfull.\n' + + 'ID: ' + backend.id + '\n' + + 'Name: ' + backend.name + '\n' + + 'Type: ' + backend.type, + userId: req.user.id + }) + res.status(200).send() + } }) // PUT requests @@ -408,6 +433,14 @@ router.putAsync('/:id', async (req, res) => { // Insert new backend in the db. const credentialString = JSON.stringify(backend.credentials) db.backend.create({ name: backend.name, type: backend.type, credentials: credentialString }) + log({ + category: 'BACKEND_CREATE', + description: '[' + id + '] ' + backend.name + ': Backend successfully created.\n' + + 'ID: ' + id + '\n' + + 'Name: ' + backend.name + '\n' + + 'Type: ' + backend.type, + userId: req.user.id + }) } else { const backendDb = await db.backend.findOne({ where: { id: id } }) if (!backendDb) return res.status(404).send({ error: 'BACKEND_NOT_FOUND', message: 'No backend was found with the provided backend id.' }) @@ -426,6 +459,14 @@ router.putAsync('/:id', async (req, res) => { // Update an existing backend in the db. db.backend.update({ name: backend.name, type: backend.type, credentials: JSON.stringify(credentials) }, { where: { id: id } }) + log({ + category: 'BACKEND_EDIT', + description: '[' + id + '] ' + backend.name + ': Backend successfully edited.\n' + + 'ID: ' + id + '\n' + + 'Name: ' + backend.name + '\n' + + 'Type: ' + backend.type, + userId: req.user.id + }) } res.status(200).send('success') @@ -446,6 +487,16 @@ router.put('/:id/syncsettings', (req, res) => { const sync = req.body.sync db.backend.findOne({ where: { id: id } }).then(backend => { db.backend.update({ groupTypes: groups, clientTypes: clients, sync: sync }, { where: { id: id } }).then(() => { + log({ + category: 'BACKEND_SYNCSETTINGS_EDIT', + description: '[' + backend.id + '] ' + backend.name + ': Sync settings successfully edited.\n' + + 'ID: ' + backend.id + '\n' + + 'Name: ' + backend.name + '\n' + + 'Type: ' + backend.type + '\n' + + 'Groups: ' + req.body.groups + '\n' + + 'Clients: ' + req.body.clients, + userId: req.user.id + }) res.status(200).send() }) }) @@ -458,10 +509,58 @@ router.put('/:id/syncsettings', (req, res) => { * Deletes the backend to the given id. */ router.postAsync('/delete', async (req, res) => { - const backendIds = req.body.id + req.body.ids = req.body.id + // await db.backend.destroy({ where: { id: backendIds } }) + // res.status(200).send('success') + + const user = await db.user.findOne({ where: { id: req.user.id } }) + // Only need to log batch request if there is more than one backend to delete. + if (req.body.ids.length > 1) { + await log({ + category: 'BACKEND_BATCH_DELETE', + description: 'Batch deletion of ' + req.body.ids.length + ' backends initiated by user.', + user, + userId: req.user.id + }) + } - await db.backend.destroy({ where: { id: backendIds } }) - res.status(200).send('success') + let deletionCounter = 0 + // Delete every backend on its own, to get a better log + for (let index in req.body.ids) { + const backend = await db.backend.findOne({ where: { id: req.body.ids[index] } }) + const count = await db.backend.destroy({ where: { id: req.body.ids[index] } }) + if (count !== 1) { + await log({ + category: 'ERROR_BACKEND_DELETE', + description: '[' + backend.id + '] ' + backend.name + ': Backend could not be deleted.\n' + + 'ID: ' + backend.id + '\n' + + 'Name: ' + backend.name + '\n' + + 'Type: ' + backend.type, + user, + userId: req.user.id + }) + } else { + await log({ + category: 'BACKEND_DELETE', + description: '[' + backend.id + '] ' + backend.name + ': Backend successfully deleted.\n' + + 'ID: ' + backend.id + '\n' + + 'Name: ' + backend.name + '\n' + + 'Type: ' + backend.type, + user, + userId: req.user.id + }) + deletionCounter++ + } + } + if (req.body.ids.length > 1) { + log({ + category: 'BACKEND_BATCH_DELETE', + description: deletionCounter + '/' + req.body.ids.length + ' backends successfully deleted.', + user, + userId: req.user.id + }) + } + HttpResponse.successBatch('deleted', 'backend', deletionCounter).send(res) }) module.exports.router = router diff --git a/server/api/clients.js b/server/api/clients.js index d72207b..15e3b8d 100644 --- a/server/api/clients.js +++ b/server/api/clients.js @@ -7,6 +7,7 @@ var express = require('express') const { decorateApp } = require('@awaitjs/express') var router = decorateApp(express.Router()) const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse')) +const log = require(path.join(__appdir, 'lib', 'log')) // ############################################################################ // ########################### GET requests ################################# @@ -30,8 +31,51 @@ router.postAsync(['', '/:id'], async (req, res) => { if (req.query.delete !== undefined && req.query.delete !== 'false') { if (!Array.isArray(req.body.ids)) return HttpResponse.invalidBodyValue('ids', 'an array').send(res) await backendHelper.deleteClients(req.body.ids, req.body.backendIds) - const count = await db.client.destroy({ where: { id: req.body.ids } }) - HttpResponse.successBatch('deleted', 'client', count).send(res) + + const user = await db.user.findOne({ where: { id: req.user.id } }) + // Only need to log batch request if there is more than one client to delete. + if (req.body.ids.length > 1) { + await log({ + category: 'CLIENT_BATCH_DELETE', + description: 'Batch deletion of ' + req.body.ids.length + ' clients initiated by user.', + user, + userId: req.user.id + }) + } + + let deletionCounter = 0 + // Delete every client on its own, to get a better log + for (let index in req.body.ids) { + const client = await db.client.findOne({ where: { id: req.body.ids[index] } }) + const count = await db.client.destroy({ where: { id: req.body.ids[index] } }) + if (count !== 1) { + await log({ + category: 'ERROR_CLIENT_DELETE', + description: 'Client could not be deleted.', + client, + user, + userId: req.user.id + }) + } else { + await log({ + category: 'CLIENT_DELETE', + description: 'Client successfully deleted.', + client, + user, + userId: req.user.id + }) + deletionCounter++ + } + } + if (req.body.ids.length > 1) { + log({ + category: 'CLIENT_BATCH_DELETE', + description: deletionCounter + '/' + req.body.ids.length + ' clients successfully deleted.', + user, + userId: req.user.id + }) + } + HttpResponse.successBatch('deleted', 'client', deletionCounter).send(res) } else { let client let action = 'updated' @@ -51,6 +95,12 @@ router.postAsync(['', '/:id'], async (req, res) => { client = await db.client.create(req.body.data) action = 'created' io.in('broadcast newClient').emit('notifications newAlert', { type: 'info', text: 'New client!' }) + log({ + category: 'CLIENT_CREATE', + description: 'Client successfully created.', + clientId: client.id, + userId: req.user.id + }) // Add client to the backends backendClient.id = client.id @@ -58,7 +108,15 @@ router.postAsync(['', '/:id'], async (req, res) => { } else if (req.params.id > 0) { client = await db.client.findOne({ where: { id: req.params.id } }) if (!client) return HttpResponse.notFound(req.params.id).send(res) - else await client.update(req.body.data) + else { + await client.update(req.body.data) + log({ + category: 'CLIENT_EDIT', + description: 'Client successfully edited.', + clientId: client.id, + userId: req.user.id + }) + } backendHelper.updateClient(backendClient) } else { return HttpResponse.invalidId().send(res) @@ -71,10 +129,18 @@ router.postAsync(['', '/:id'], async (req, res) => { // ############################################################################ // ########################## DELETE requests ############################### -router.delete('/:id', async (req, res) => { +router.deleteAsync('/:id', async (req, res) => { + const client = db.client.findOne({ where: { id: req.params.id } }) const count = await db.client.destroy({ where: { id: req.params.id } }) - if (count) HttpResponse.success('deleted', 'client', req.params.id).send(res) - else HttpResponse.notFound(req.params.id).send(res) + if (count) { + log({ + category: 'CLIENT_DELETE', + description: 'Client successfully deleted.', + client, + userId: req.user.id + }) + HttpResponse.success('deleted', 'client', req.params.id).send(res) + } else HttpResponse.notFound(req.params.id).send(res) }) // ############################################################################ diff --git a/server/api/events.js b/server/api/events.js index 6ec37c4..7e330e5 100644 --- a/server/api/events.js +++ b/server/api/events.js @@ -8,6 +8,8 @@ var router = decorateApp(express.Router()) const zmq = require('zeromq') const socket = zmq.socket('push') socket.connect('ipc:///tmp/bas_zeromq_events') +const log = require(path.join(__appdir, 'lib', 'log')) +const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse')) // ############################################################################ // ########################### GET requests ################################# @@ -47,7 +49,65 @@ router.postAsync('/blacklist', async (req, res) => { // Create, Update or Delete POST router.postAsync(['', '/:id'], async (req, res) => { if (req.query.delete !== undefined && req.query.delete !== 'false') { - await db.event.destroy({ where: { id: req.params.id || req.body.ids } }).then(count => { res.send({ count }) }) + const user = await db.user.findOne({ where: { id: req.user.id } }) + + // Only need to log batch request if there is more than one event to delete. + if (req.body.ids.length > 1) { + await log({ + category: 'EVENT_BATCH_DELETE', + description: 'Event batch deletion of ' + req.body.ids.length + ' events initiated by user.', + user, + userId: req.user.id + }) + } + + let deletionCounter = 0 + // Delete every event on its own, to get a better log + for (let index in req.body.ids) { + const event = await db.event.findOne({ where: { id: req.body.ids[index] } }) + const count = await db.event.destroy({ where: { id: req.body.ids[index] } }) + if (count !== 1) { + await log({ + category: 'ERROR_EVENT_DELETE', + description: '[' + event.id + '] ' + event.name + ': Event could not be deleted.\n' + + 'ID: ' + event.id + '\n' + + 'Name: ' + event.name + '\n' + + 'Description: ' + event.description + '\n' + + 'Times: ' + event.times + '\n' + + 'Important: ' + event.important + '\n' + + 'Wake-on-Lan: ' + event.wakeonlan + '\n' + + 'Config ID: ' + event.configId, + user, + userId: req.user.id + }) + } else { + await log({ + category: 'EVENT_DELETE', + description: '[' + event.id + '] ' + event.name + ': Event successfully deleted.\n' + + 'ID: ' + event.id + '\n' + + 'Name: ' + event.name + '\n' + + 'Description: ' + event.description + '\n' + + 'Times: ' + event.times + '\n' + + 'Important: ' + event.important + '\n' + + 'Wake-on-Lan: ' + event.wakeonlan + '\n' + + 'Config ID: ' + event.configId, + user, + userId: req.user.id + }) + deletionCounter++ + } + } + if (req.body.ids.length > 1) { + log({ + category: 'EVENT_BATCH_DELETE', + description: deletionCounter + '/' + req.body.ids.length + ' events successfully deleted.', + user, + userId: req.user.id + }) + } + HttpResponse.successBatch('deleted', 'event', deletionCounter).send(res) + + await db.event.destroy({ where: { id: req.body.ids } }).then(count => { res.send({ count }) }) req.body.ids.forEach(id => { socket.send(id) }) @@ -58,7 +118,7 @@ router.postAsync(['', '/:id'], async (req, res) => { if (req.body.config.length !== 1) req.body.config = null if (req.body.times.length === 0) req.body.times = null if (req.params.id > 0) { - // Update existing role + // Update existing event eventDb = await db.event.findOne({ where: { id: req.params.id } }) if (eventDb !== null) { promises.push(eventDb.update({ name: req.body.name, description: req.body.description, times: req.body.times, important: req.body.important, wakeonlan: req.body.wakeonlan, configId: req.body.config || null })) @@ -68,13 +128,30 @@ router.postAsync(['', '/:id'], async (req, res) => { promisesBlacklist.push(eventDb.addGroups(req.body.blacklistGroups, { through: { blacklist: 1 } })) promisesBlacklist.push(eventDb.addClients(req.body.blacklistClients, { through: { blacklist: 1 } })) await Promise.all(promisesBlacklist) + log({ + category: 'EVENT_EDIT', + description: '[' + eventDb.id + '] ' + eventDb.name + ': Event successfully edited.\n' + + 'ID: ' + eventDb.id + '\n' + + 'Name: ' + eventDb.name + '\n' + + 'Description: ' + eventDb.description + '\n' + + 'Times: ' + eventDb.times + '\n' + + 'Important: ' + eventDb.important + '\n' + + 'Wake-on-Lan: ' + eventDb.wakeonlan + '\n' + + 'Config ID: ' + eventDb.configId + '\n' + + 'Groups: ' + req.body.groups + '\n' + + 'Clients: ' + req.body.clients + '\n' + + 'Blacklist-Groups: ' + req.body.blacklistGroups + '\n' + + 'Blacklist-Clients: ' + req.body.blacklistClients, + userId: req.user.id + }) + socket.send(eventDb.id) res.send({ id: req.params.id }) } else { res.status(404).end() } } else if (req.params.id === undefined) { - // Create new role + // Create new event eventDb = await db.event.create({ name: req.body.name, description: req.body.description, times: req.body.times, important: req.body.important, wakeonlan: req.body.wakeonlan, configId: req.body.config || null }) promises.push(eventDb.setGroups(req.body.groups, { through: { blacklist: 0 } })) promises.push(eventDb.setClients(req.body.clients, { through: { blacklist: 0 } })) @@ -82,6 +159,22 @@ router.postAsync(['', '/:id'], async (req, res) => { promisesBlacklist.push(eventDb.addGroups(req.body.blacklistGroups, { through: { blacklist: 1 } })) promisesBlacklist.push(eventDb.addClients(req.body.blacklistClients, { through: { blacklist: 1 } })) await Promise.all(promisesBlacklist) + log({ + category: 'EVENT_CREATE', + description: '[' + eventDb.id + '] ' + eventDb.name + ': Event successfully created.\n' + + 'ID: ' + eventDb.id + '\n' + + 'Name: ' + eventDb.name + '\n' + + 'Description: ' + eventDb.description + '\n' + + 'Times: ' + eventDb.times + '\n' + + 'Important: ' + eventDb.important + '\n' + + 'Wake-on-Lan: ' + eventDb.wakeonlan + '\n' + + 'Config ID: ' + eventDb.configId + '\n' + + 'Groups: ' + req.body.groups + '\n' + + 'Clients: ' + req.body.clients + '\n' + + 'Blacklist-Groups: ' + req.body.blacklistGroups + '\n' + + 'Blacklist-Clients: ' + req.body.blacklistClients, + userId: req.user.id + }) socket.send(eventDb.id) res.send({ id: req.body.id }) } diff --git a/server/api/groups.js b/server/api/groups.js index 633b63d..bdc560b 100644 --- a/server/api/groups.js +++ b/server/api/groups.js @@ -8,6 +8,7 @@ const { decorateApp } = require('@awaitjs/express') const router = decorateApp(express.Router()) const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse')) const backendHelper = require(path.join(__appdir, 'lib', 'external-backends', 'backendhelper')) +const log = require(path.join(__appdir, 'lib', 'log')) // ############################################################################ // ########################### GET requests ################################# @@ -49,18 +50,74 @@ router.getAsync('/:id', async (req, res) => { router.postAsync(['', '/:id'], async (req, res) => { if (req.query.delete !== undefined && req.query.delete !== 'false') { if (!Array.isArray(req.body.ids)) return HttpResponse.invalidBodyValue('ids', 'an array').send(res) - const count = await db.group.destroy({ where: { id: req.body.ids } }) - HttpResponse.successBatch('deleted', 'group', count).send(res) + + const user = await db.user.findOne({ where: { id: req.user.id } }) + // Only need to log batch request if there is more than one client to delete. + if (req.body.ids.length > 1) { + await log({ + category: 'GROUP_BATCH_DELETE', + description: 'Batch deletion of ' + req.body.ids.length + ' groups initiated by user.', + user, + userId: req.user.id + }) + } + let deletionCounter = 0 + // Delete every group on its own, to get a better log + for (let index in req.body.ids) { + const group = await db.group.findOne({ where: { id: req.body.ids[index] } }) + const count = await db.group.destroy({ where: { id: req.body.ids[index] } }) + if (count !== 1) { + await log({ + category: 'ERROR_GROUP_DELETE', + description: 'Group could not be deleted.', + group, + user, + userId: req.user.id + }) + } else { + await log({ + category: 'GROUP_DELETE', + description: 'Group successfully deleted.', + group, + user, + userId: req.user.id + }) + deletionCounter++ + } + } + if (req.body.ids.length > 1) { + log({ + category: 'GROUP_BATCH_DELETE', + description: deletionCounter + '/' + req.body.ids.length + ' groups successfully deleted.', + user, + userId: req.user.id + }) + } + HttpResponse.successBatch('deleted', 'group', deletionCounter).send(res) } else { let group let action = 'updated' if (req.params.id === undefined) { group = await db.group.create(req.body.data) action = 'created' + log({ + category: 'GROUP_CREATE', + description: 'Group successfully created.', + groupId: group.id, + userId: req.user.id + }) } else if (req.params.id > 0) { group = await db.group.findOne({ where: { id: req.params.id }, include: ['ipranges'] }) if (!group) return HttpResponse.notFound(req.params.id).send(res) - else await group.update(req.body.data) + else { + await group.update(req.body.data) + log({ + category: 'GROUP_EDIT', + description: 'Group successfully edited.', + groupId: group.id, + userId: req.user.id + }) + } } else { return HttpResponse.invalidId().send(res) } @@ -104,13 +161,107 @@ router.postAsync(['', '/:id'], async (req, res) => { router.postAsync('/:id/subgroups', async (req, res) => { if (!(req.params.id > 0)) return HttpResponse.invalidId().send(res) const group = await db.group.findOne({ where: { id: req.params.id } }) + if (group) { + let deletionCounter = 0 + const user = await db.user.findOne({ where: { id: req.user.id } }) + if (req.query.delete !== undefined && req.query.delete !== 'false') { - const count = await group.removeSubgroups(req.body.ids) - HttpResponse.successBatch('removed', 'subgroup', count).send(res) + // Remove method for subgroups + if (req.body.ids.length > 1) { + await log({ + category: 'GROUP_BATCH_REMOVE_SUBGROUP', + description: 'Group batch removal of ' + req.body.ids.length + ' subgroups initiated by user.', + user, + userId: req.user.id, + group, + groupId: group.id + }) + } + + for (let index in req.body.ids) { + const count = await group.removeSubgroups(req.body.ids[index]) + if (count !== 1) { + await log({ + category: 'ERROR_GROUP_REMOVE_SUBGROUP', + description: 'Subgroup [' + req.body.ids[index] + '] could not be removed from group [' + group.id + '] ' + group.name, + user, + userId: req.user.id, + group, + groupId: group.id + }) + } else { + await log({ + category: 'GROUP_REMOVE_SUBGROUP', + description: 'Subgroup [' + req.body.ids[index] + '] successfully removed from group [' + group.id + '] ' + group.name, + user, + userId: req.user.id, + group, + groupId: group.id + }) + deletionCounter++ + } + } + + if (req.body.ids.length > 1) { + log({ + category: 'GROUP_BATCH_REMOVE_SUBGROUP', + description: deletionCounter + '/' + req.body.ids.length + ' subgroups successfully removed.', + user, + userId: req.user.id, + group, + groupId: group.id + }) + } + HttpResponse.successBatch('removed', 'subgroup', deletionCounter).send(res) } else { - const count = await group.addSubgroups(req.body.ids) - HttpResponse.successBatch('added', 'subgroup', count).send(res) + // Add method for subgroups + if (req.body.ids.length > 1) { + await log({ + category: 'GROUP_BATCH_ADD_SUBGROUP', + description: 'Group batch addition of ' + req.body.ids.length + ' subgroups initiated by user.', + user, + userId: req.user.id, + group, + groupId: group.id + }) + } + + for (let index in req.body.ids) { + const count = await group.addSubgroups(req.body.ids[index]) + if (count.length !== 1) { + await log({ + category: 'ERROR_GROUP_ADD_SUBGROUP', + description: 'Subgroup [' + req.body.ids[index] + '] could not be added to group [' + group.id + '] ' + group.name, + user, + userId: req.user.id, + group, + groupId: group.id + }) + } else { + await log({ + category: 'GROUP_ADD_SUBGROUP', + description: 'Subgroup [' + req.body.ids[index] + '] successfully added to group [' + group.id + '] ' + group.name, + user, + userId: req.user.id, + group, + groupId: group.id + }) + deletionCounter++ + } + } + + if (req.body.ids.length > 1) { + log({ + category: 'GROUP_BATCH_ADD_SUBGROUP', + description: deletionCounter + '/' + req.body.ids.length + ' subgroups successfully added.', + user, + userId: req.user.id, + group, + groupId: group.id + }) + } + HttpResponse.successBatch('added', 'subgroup', deletionCounter).send(res) } } else { HttpResponse.notFound(req.params.id).send(res) @@ -121,13 +272,108 @@ router.postAsync('/:id/clients', async (req, res) => { if (!(req.params.id > 0)) return HttpResponse.invalidId().send(res) const group = await db.group.findOne({ where: { id: req.params.id } }) if (group) { + let deletionCounter = 0 + const user = await db.user.findOne({ where: { id: req.user.id } }) let groupid = null if (req.query.delete !== undefined && req.query.delete !== 'false') { - const count = await group.removeClients(req.body.ids) - HttpResponse.successBatch('removed', 'client', count).send(res) + // Remove method for clients to groups + if (req.body.ids.length > 1) { + await log({ + category: 'GROUP_BATCH_REMOVE_CLIENT', + description: 'Group batch removal of ' + req.body.ids.length + ' clients initiated by user.', + user, + userId: req.user.id, + group, + groupId: group.id + }) + } + + for (let index in req.body.ids) { + const count = await group.removeClients(req.body.ids[index]) + if (count !== 1) { + await log({ + category: 'ERROR_GROUP_REMOVE_CLIENT', + description: 'Client [' + req.body.ids[index] + '] could not be removed from group [' + group.id + '] ' + group.name, + user, + userId: req.user.id, + group, + groupId: group.id + }) + } else { + await log({ + category: 'GROUP_REMOVE_CLIENT', + description: 'Client [' + req.body.ids[index] + '] successfully removed from group [' + group.id + '] ' + group.name, + user, + userId: req.user.id, + group, + groupId: group.id, + clientId: req.body.ids[index] + }) + deletionCounter++ + } + } + + if (req.body.ids.length > 1) { + log({ + category: 'GROUP_BATCH_REMOVE_CLIENT', + description: deletionCounter + '/' + req.body.ids.length + ' clients successfully deleted.', + user, + userId: req.user.id, + group, + groupId: group.id + }) + } + HttpResponse.successBatch('removed', 'client', deletionCounter).send(res) } else { - const count = await group.addClients(req.body.ids) - HttpResponse.successBatch('added', 'client', count).send(res) + // Add method for clients to groups + + if (req.body.ids.length > 1) { + await log({ + category: 'GROUP_BATCH_ADD_CLIENT', + description: 'Group batch addition of ' + req.body.ids.length + ' clients initiated by user.', + user, + userId: req.user.id, + group, + groupId: group.id + }) + } + + for (let index in req.body.ids) { + const count = await group.addClients(req.body.ids[index]) + if (count.length !== 1) { + await log({ + category: 'ERROR_GROUP_ADD_CLIENT', + description: 'Client [' + req.body.ids[index] + '] could not be added to group [' + group.id + '] ' + group.name, + user, + userId: req.user.id, + group, + groupId: group.id + }) + } else { + await log({ + category: 'GROUP_ADD_CLIENT', + description: 'Client [' + req.body.ids[index] + '] successfully added to group [' + group.id + '] ' + group.name, + user, + userId: req.user.id, + group, + groupId: group.id, + clientId: req.body.ids[index] + }) + deletionCounter++ + } + } + + if (req.body.ids.length > 1) { + log({ + category: 'GROUP_BATCH_ADD_CLIENT', + description: deletionCounter + '/' + req.body.ids.length + ' clients successfully added.', + user, + userId: req.user.id, + group, + groupId: group.id + }) + } + HttpResponse.successBatch('added', 'client', deletionCounter).send(res) groupid = group.id } @@ -144,11 +390,19 @@ router.postAsync('/:id/clients', async (req, res) => { // ############################################################################ // ########################## DELETE requests ############################### -router.delete('/:id', async (req, res) => { +router.deleteAsync('/:id', async (req, res) => { if (!(req.params.id > 0)) return HttpResponse.invalidId().send(res) + const group = db.group.findOne({ where: { id: req.params.id } }) const count = await db.group.destroy({ where: { id: req.params.id } }) - if (count) HttpResponse.success('deleted', 'group', req.params.id).send(res) - else HttpResponse.notFound(req.params.id).send(res) + if (count) { + log({ + category: 'GROUP_DELETE', + description: 'Group successfully deleted.', + group, + userId: req.user.id + }) + HttpResponse.success('deleted', 'group', req.params.id).send(res) + } else HttpResponse.notFound(req.params.id).send(res) }) // ############################################################################ diff --git a/server/api/ipranges.js b/server/api/ipranges.js index 2a40763..fe66fd5 100644 --- a/server/api/ipranges.js +++ b/server/api/ipranges.js @@ -6,6 +6,7 @@ const { decorateApp } = require('@awaitjs/express') var router = decorateApp(express.Router()) const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse')) const iphelper = require(path.join(__appdir, 'lib', 'iphelper')) +const log = require(path.join(__appdir, 'lib', 'log')) // ############################################################################ // ########################### GET requests ################################# @@ -35,8 +36,51 @@ router.getAsync('/:id', async (req, res) => { router.postAsync(['', '/:id'], async (req, res) => { if (req.query.delete !== undefined && req.query.delete !== 'false') { if (!Array.isArray(req.body.ids)) return HttpResponse.invalidBodyValue('ids', 'an array').send(res) - const count = await db.iprange.destroy({ where: { id: req.body.ids } }) - return HttpResponse.successBatch('deleted', 'client', count).send(res) + + const user = await db.user.findOne({ where: { id: req.user.id } }) + + // Only need to log batch request if there is more than one client to delete. + if (req.body.ids.length > 1) { + await log({ + category: 'IPRANGE_BATCH_DELETE', + description: 'IP range batch deletion of ' + req.body.ids.length + ' ip ranges initiated by user.', + user, + userId: req.user.id + }) + } + + let deletionCounter = 0 + // Delete every iprange on its own, to get a better log + for (let index in req.body.ids) { + const iprange = await db.iprange.findOne({ where: { id: req.body.ids[index] } }) + const count = await db.iprange.destroy({ where: { id: req.body.ids[index] } }) + if (count !== 1) { + await log({ + category: 'ERROR_IPRANGE_DELETE', + description: '[' + iprange.id + '] IP range from ' + iphelper.toIPv4(iprange.startIp) + ' to ' + iphelper.toIPv4(iprange.endIp) + ' could not be deleted.', + user, + userId: req.user.id + }) + } else { + await log({ + category: 'IPRANGE_DELETE', + description: '[' + iprange.id + '] IP range from ' + iphelper.toIPv4(iprange.startIp) + ' to ' + iphelper.toIPv4(iprange.endIp) + ' successfully deleted.', + user, + userId: req.user.id + }) + deletionCounter++ + } + } + if (req.body.ids.length > 1) { + log({ + category: 'IPRANGE_BATCH_DELETE', + description: deletionCounter + '/' + req.body.ids.length + ' ip ranges successfully deleted.', + user, + userId: req.user.id + }) + } + + return HttpResponse.successBatch('deleted', 'client', deletionCounter).send(res) } let iprange let action = 'updated' @@ -44,11 +88,23 @@ router.postAsync(['', '/:id'], async (req, res) => { req.body.endIp = iphelper.toDecimal(req.body.endIp) if (req.params.id === undefined) { iprange = await db.iprange.create(req.body) + await log({ + category: 'IPRANGE_CREATE', + description: 'IP range from ' + iphelper.toIPv4(req.body.startIp) + ' to ' + iphelper.toIPv4(req.body.endIp) + ' successfully created', + userId: req.user.id + }) action = 'created' } else if (req.params.id > 0) { iprange = await db.iprange.findOne({ where: { id: req.params.id } }) if (!iprange) return HttpResponse.notFound(req.params.id).send(res) - else await iprange.update(req.body) + else { + await iprange.update(req.body) + await log({ + category: 'IPRANGE_EDIT', + description: '[' + iprange.id + '] IP range successfully edited from ' + iphelper.toIPv4(req.body.startIp) + ' to ' + iphelper.toIPv4(req.body.endIp), + userId: req.user.id + }) + } } else { return HttpResponse.invalidId().send(res) } diff --git a/server/api/ipxe.js b/server/api/ipxe.js index 7b5ad85..b7e1970 100644 --- a/server/api/ipxe.js +++ b/server/api/ipxe.js @@ -5,6 +5,7 @@ var shell = require(path.join(__appdir, 'lib', 'shell')) var express = require('express') var router = express.Router() var noAuthRouter = express.Router() +const log = require(path.join(__appdir, 'lib', 'log')) // GET requests. router.get('/script', (req, res) => { @@ -72,22 +73,45 @@ router.post('/config', (req, res) => { router.put('/:filename', (req, res) => { var filepath = null + let filename = '' + // Set the file path if (req.params.filename === 'script') { filepath = path.join(__appdir, 'ipxe', 'embedded.ipxe') + filename = 'embedded.ipxe' } else if (req.params.filename === 'console' || req.params.filename === 'general') { filepath = path.join(__appdir, 'ipxe', req.params.filename + '.h') + filename = req.params.filename + '.h' } else if (req.params.filename === 'certificate') { filepath = path.join(__appdir, 'bin', 'fullchain.pem') + filename = 'fullchain.pem' } else { + log({ + category: 'ERROR_IPXEBUILDER_SAVE', + description: filename + ' cannot be saved. Filename unknown', + userId: req.user.id + }) res.status(500).send({ status: 'UNKNOWN_FILENAME', error: 'The provided filename is unknown' }) return } // Write File fs.writeFile(filepath, req.body.data, err => { - if (err) res.status(500).send({ status: 'WRITE_FILE_ERROR', error: err }) - else res.status(200).send({ status: 'SUCCESS' }) + if (err) { + log({ + category: 'ERROR_IPXEBUILDER_SAVE', + description: 'Error while saving ' + filename + '. ' + err, + userId: req.user.id + }) + res.status(500).send({ status: 'WRITE_FILE_ERROR', error: err }) + } else { + log({ + category: 'IPXEBUILDER_SAVE', + description: 'User has saved ' + filename, + userId: req.user.id + }) + res.status(200).send({ status: 'SUCCESS' }) + } }) }) @@ -95,6 +119,12 @@ router.put('/:filename', (req, res) => { * @return: Rebuild the ipxe. */ router.get('/build', async (req, res) => { + log({ + category: 'IPXEBUILDER_BUILD', + description: 'User initiated the ipxe building process.', + userId: req.user.id + }) + delete require.cache[require.resolve(path.join(__appdir, 'config', 'ipxe'))] const config = require(path.join(__appdir, 'config', 'ipxe')) const build = await shell.buildIpxe(config.targets.build.join(' '), config.repository, config.branch) @@ -102,11 +132,21 @@ router.get('/build', async (req, res) => { }) router.get('/cancel', async (req, res) => { + log({ + category: 'IPXEBUILDER_CANCEL', + description: 'User canceled the ipxe building process.', + userId: req.user.id + }) const result = await shell.cancelBuilding(req, res) res.send(result) }) router.get('/clean', async (req, res) => { + log({ + category: 'IPXEBUILDER_CLEAN', + description: 'User initiated ipxe cleaning process.', + userId: req.user.id + }) const result = await shell.clean() res.send(result) }) diff --git a/server/api/ipxeconfigs.js b/server/api/ipxeconfigs.js index 64f2c37..3c6f6eb 100644 --- a/server/api/ipxeconfigs.js +++ b/server/api/ipxeconfigs.js @@ -6,6 +6,7 @@ var express = require('express') const { decorateApp } = require('@awaitjs/express') var router = decorateApp(express.Router()) const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse')) +const log = require(path.join(__appdir, 'lib', 'log')) // ############################################################################ // ########################### GET requests ################################# @@ -62,18 +63,96 @@ router.getAsync('/:id/clients', async (req, res) => { router.postAsync(['', '/:id'], async (req, res) => { if (req.query.delete !== undefined && req.query.delete !== 'false') { if (!Array.isArray(req.body.ids)) return HttpResponse.invalidBodyValue('ids', 'an array').send(res) - const count = await db.config.destroy({ where: { id: req.body.ids } }) - HttpResponse.successBatch('deleted', 'ipxe config', count).send(res) + + const user = await db.user.findOne({ where: { id: req.user.id } }) + // Only need to log batch request if there is more than one ipxeconfig to delete. + if (req.body.ids.length > 1) { + await log({ + category: 'IPXECONFIG_BATCH_DELETE', + description: 'Batch deletion of ' + req.body.ids.length + ' ipxe configs initiated by user.', + user, + userId: req.user.id + }) + } + + let deletionCounter = 0 + // Delete every config on its own, to get a better log + for (let index in req.body.ids) { + const config = await db.config.findOne({ where: { id: req.body.ids[index] } }) + const count = await db.config.destroy({ where: { id: req.body.ids[index] } }) + if (count !== 1) { + await log({ + category: 'ERROR_IPXECONFIG_DELETE', + description: '[' + config.id + '] ' + config.name + ': Ipxe config could not be deleted.\n' + + 'ID: ' + config.id + '\n' + + 'Name: ' + config.name + '\n' + + 'Description: ' + config.description + '\n' + + 'Default Entry: ' + config.defaultEntry + '\n' + + 'Timeout: ' + config.timeout + '\n' + + 'Default Config: ' + config.isDefault, + user, + userId: req.user.id + }) + } else { + await log({ + category: 'IPXECONFIG_DELETE', + description: '[' + config.id + '] ' + config.name + ': Ipxe config successfully deleted.\n' + + 'ID: ' + config.id + '\n' + + 'Name: ' + config.name + '\n' + + 'Description: ' + config.description + '\n' + + 'Default Entry: ' + config.defaultEntry + '\n' + + 'Timeout: ' + config.timeout + '\n' + + 'Default Config: ' + config.isDefault, + user, + userId: req.user.id + }) + deletionCounter++ + } + } + if (req.body.ids.length > 1) { + log({ + category: 'IPXECONFIG_BATCH_DELETE', + description: deletionCounter + '/' + req.body.ids.length + ' ipxe configs successfully deleted.', + user, + userId: req.user.id + }) + } + + HttpResponse.successBatch('deleted', 'ipxe config', deletionCounter).send(res) } else { let config let action = 'updated' if (req.params.id === undefined) { config = await db.config.create(req.body.data) + log({ + category: 'IPXECONFIG_CREATE', + description: '[' + config.id + '] ' + config.name + ': Ipxe config successfully created.\n' + + 'ID: ' + config.id + '\n' + + 'Name: ' + config.name + '\n' + + 'Description: ' + config.description + '\n' + + 'Default Entry: ' + config.defaultEntry + '\n' + + 'Timeout: ' + config.timeout + '\n' + + 'Default Config: ' + config.isDefault, + userId: req.user.id + }) action = 'created' } else if (req.params.id > 0) { config = await db.config.findOne({ where: { id: req.params.id } }) if (!config) return HttpResponse.notFound(req.params.id).send(res) - else await config.update(req.body.data) + else { + await config.update(req.body.data) + log({ + category: 'IPXECONFIG_EDIT', + description: '[' + config.id + '] ' + config.name + ': Ipxe config successfully edited.\n' + + 'ID: ' + config.id + '\n' + + 'Name: ' + config.name + '\n' + + 'Description: ' + config.description + '\n' + + 'Default Entry: ' + config.defaultEntry + '\n' + + 'Timeout: ' + config.timeout + '\n' + + 'Default Config: ' + config.isDefault, + userId: req.user.id + }) + } } else { return HttpResponse.invalidId().send(res) } @@ -99,6 +178,18 @@ router.putAsync('/:id/groups', async (req, res) => { if (config) { await config.setGroups(req.body.ids) HttpResponse.success('set', 'clients of ipxe config', config.id).send(res) + log({ + category: 'IPXECONFIG_SET_GROUPS', + description: '[' + config.id + '] ' + config.name + ': ' + req.body.ids.length + ' groups successfully set.\n' + + 'ID: ' + config.id + '\n' + + 'Name: ' + config.name + '\n' + + 'Description: ' + config.description + '\n' + + 'Default Entry: ' + config.defaultEntry + '\n' + + 'Timeout: ' + config.timeout + '\n' + + 'Default Config: ' + config.isDefault + '\n' + + 'Groups: ' + req.body.ids, + userId: req.user.id + }) } else { HttpResponse.notFound(req.params.id).send(res) } @@ -110,6 +201,18 @@ router.putAsync('/:id/clients', async (req, res) => { const config = await db.config.findOne({ where: { id: req.params.id } }) if (config) { await config.setClients(req.body.ids) + log({ + category: 'IPXECONFIG_SET_CLIENTS', + description: '[' + config.id + '] ' + config.name + ': ' + req.body.ids.length + ' clients successfully set.\n' + + 'ID: ' + config.id + '\n' + + 'Name: ' + config.name + '\n' + + 'Description: ' + config.description + '\n' + + 'Default Entry: ' + config.defaultEntry + '\n' + + 'Timeout: ' + config.timeout + '\n' + + 'Default Config: ' + config.isDefault + '\n' + + 'Clients: ' + req.body.ids, + userId: req.user.id + }) HttpResponse.success('set', 'clients of ipxe config', config.id).send(res) } else { HttpResponse.notFound(req.params.id).send(res) @@ -119,9 +222,17 @@ router.putAsync('/:id/clients', async (req, res) => { router.putAsync('/:id/default', async (req, res) => { if (!(req.params.id > 0)) return HttpResponse.invalidId().send(res) const config = await db.config.findOne({ where: { id: req.params.id } }) + const oldDefault = await db.config.findOne({ where: { isDefault: true } }) if (config) { await db.config.update({ isDefault: false }, { where: { isDefault: true } }) await config.update({ isDefault: true }) + log({ + category: 'IPXECONFIG_SET_DEFAULT', + description: '[' + config.id + '] ' + config.name + ': Config successfully set as default.\n' + + 'Old Default: [' + oldDefault.id + '] ' + oldDefault.name + '\n' + + 'New Default: [' + config.id + '] ' + config.name, + userId: req.user.id + }) HttpResponse.success('set as default:', 'config', config.id).send(res) } else { HttpResponse.notFound(req.params.id).send(res) @@ -133,9 +244,23 @@ router.putAsync('/:id/default', async (req, res) => { router.deleteAsync('/:id', async (req, res) => { if (!(req.params.id > 0)) return HttpResponse.invalidId().send(res) + const config = await db.config.findOne({ where: { id: req.params.id } }) const count = await db.config.destroy({ where: { id: req.params.id } }) - if (count) res.status(200).end() - else HttpResponse.notFound(req.params.id).send(res) + + if (count) { + log({ + category: 'IPXECONFIG_DELETE', + description: '[' + config.id + '] ' + config.name + ': Ipxe config successfully deleted.\n' + + 'ID: ' + config.id + '\n' + + 'Name: ' + config.name + '\n' + + 'Description: ' + config.description + '\n' + + 'Default Entry: ' + config.defaultEntry + '\n' + + 'Timeout: ' + config.timeout + '\n' + + 'Default Config: ' + config.isDefault, + userId: req.user.id + }) + HttpResponse.success('deleted', 'config', req.params.id).send(res) + } else HttpResponse.notFound(req.params.id).send(res) }) // ############################################################################ diff --git a/server/api/registration.js b/server/api/registration.js index 202d836..5744369 100644 --- a/server/api/registration.js +++ b/server/api/registration.js @@ -11,6 +11,7 @@ const config = require(path.join(__appdir, 'config', 'config')) // Ipxe needs the url without the port because ipxe can't handle port requests const url = config.https.host // + ':' + config.https.port const log = require(path.join(__appdir, 'lib', 'log')) +const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse')) // GET requests. @@ -36,11 +37,19 @@ router.postAsync('/hookorder', async (req, res) => { idSortvalueMap[id] = index }) var hooks = await db.registrationhook.findAll() + const oldOrder = hooks.sort((a, b) => (a.sortvalue > b.sortvalue)) var promises = [] hooks.forEach(hook => { promises.push(hook.update({ sortvalue: idSortvalueMap[hook.id] })) }) await Promise.all(promises) + log({ + category: 'REGISTATIONHOOK_EDIT_ORDER', + description: 'Registration hook order successfully edited.\n' + + 'Old-Order: ' + '\n\t' + oldOrder.map(x => { return '[' + x.id + '] ' + x.name }).toString().replace(/,/g, '\n\t') + '\n' + + 'New-Order: ' + '\n\t' + req.body.ids.map(x => { return '[' + x + '] ' + oldOrder.filter(y => y.id === x)[0].name }).toString().replace(/,/g, '\n\t') + }) + res.end() }) @@ -54,11 +63,35 @@ router.postAsync(['/hooks', '/hooks/:id'], async (req, res) => { var hook = null if (req.params.id > 0) { hook = await db.registrationhook.findOne({ where: { id: req.params.id } }) - if (hook) await hook.update(item) + if (hook) { + await hook.update(item) + log({ + category: 'REGISTATIONHOOK_EDIT', + description: '[' + hook.id + '] ' + hook.name + ': Registration hook successfully edited.\n' + + 'ID: ' + hook.id + '\n' + + 'Name: ' + hook.name + '\n' + + 'Type: ' + hook.type + '\n' + + 'Description: ' + hook.description + '\n' + + 'Sortvalue: ' + hook.sortvalue + '\n' + + 'Groups: ' + req.body.groups, + userId: req.user.id + }) + } } else { var maxSortvalue = await db.registrationhook.max('sortvalue') item.sortvalue = maxSortvalue ? maxSortvalue + 1 : 1 hook = await db.registrationhook.create(item) + log({ + category: 'REGISTATIONHOOK_CREATE', + description: '[' + hook.id + '] ' + hook.name + ': Registration hook successfully created.\n' + + 'ID: ' + hook.id + '\n' + + 'Name: ' + hook.name + '\n' + + 'Type: ' + hook.type + '\n' + + 'Description: ' + hook.description + '\n' + + 'Sortvalue: ' + hook.sortvalue + '\n' + + 'Groups: ' + req.body.groups, + userId: req.user.id + }) } if (hook) { hook.setGroups(req.body.groups) @@ -67,12 +100,31 @@ router.postAsync(['/hooks', '/hooks/:id'], async (req, res) => { res.end() }) -// DELETE requests. - -router.delete(['/hooks', '/hooks/:id'], (req, res) => { - db.registrationhook.destroy({ where: { id: req.params.id || req.body.ids } }).then(count => { res.send({ count }) }) +// ############################################################################ +// ########################## DELETE requests ############################### + +router.deleteAsync('/hooks/:id', async (req, res) => { + if (!(req.params.id > 0)) return HttpResponse.invalidId().send(res) + const hook = await db.registrationhook.findOne({ where: { id: req.params.id } }) + const count = await db.registrationhook.destroy({ where: { id: req.params.id } }) + + if (count) { + log({ + category: 'REGISTATIONHOOK_DELETE', + description: 'Registration hook successfully deleted.\n' + + 'ID: ' + hook.id + '\n' + + 'Name: ' + hook.name + '\n' + + 'Type: ' + hook.type + '\n' + + 'Sortvalue: ' + hook.sortvalue, + userId: req.user.id + }) + HttpResponse.success('deleted', 'hook', req.params.id).send(res) + } else HttpResponse.notFound(req.params.id).send(res) }) +// ############################################################################ +// ############################################################################ + module.exports.router = router // GET requests. @@ -164,7 +216,10 @@ noAuthRouter.postAsync('/clients', async (req, res) => { // Client ip set successfully client.networks[0].ip = setIp.ip } else { - log({ category: 'ERROR_DHCP', description: `[${dhcp.backend.id}] Error setting ip ${network.ip} for mac ${network.mac}\nError: ${setIp.msg}` }) + log({ + category: 'ERROR_DHCP', + description: `[${dhcp.backend.id}] Error setting ip ${network.ip} for mac ${network.mac}\nError: ${setIp.msg}` + }) } } } @@ -227,7 +282,11 @@ noAuthRouter.postAsync('/clients', async (req, res) => { // Add groups to the client. if (client.parents.length === 0) client.parents = await ipHelper.getGroups(client.networks[0].ip) client.parents.forEach(pid => { newClient.addGroup(pid) }) - log({ category: 'REGISTRATION', description: 'Client added successfully.', clientId: newClient.id }) + log({ + category: 'REGISTRATION', + description: 'Client added successfully.', + clientId: newClient.id + }) // Add the client to the backends. const result = backendHelper.addClient(client) diff --git a/server/api/roles.js b/server/api/roles.js index f4803cf..5b878f0 100644 --- a/server/api/roles.js +++ b/server/api/roles.js @@ -4,6 +4,8 @@ var db = require(path.join(__appdir, 'lib', 'sequelize')) var express = require('express') const { decorateApp } = require('@awaitjs/express') var router = decorateApp(express.Router()) +const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse')) +const log = require(path.join(__appdir, 'lib', 'log')) /* * / @@ -44,8 +46,58 @@ router.postAsync(['', '/:id'], async (req, res) => { // ?delete Delete the roles if (req.query.delete !== undefined && req.query.delete !== 'false') { - await db.role.destroy({ where: { id: req.body.ids } }) - res.status(200).send('success') + const user = await db.user.findOne({ where: { id: req.user.id } }) + + // Only need to log batch request if there is more than one event to delete. + if (req.body.ids.length > 1) { + await log({ + category: 'ROLE_BATCH_DELETE', + description: 'Role batch deletion of ' + req.body.ids.length + ' roles initiated by user.', + user, + userId: req.user.id + }) + } + + let deletionCounter = 0 + // Delete every event on its own, to get a better log + for (let index in req.body.ids) { + const role = await db.role.findOne({ where: { id: req.body.ids[index] } }) + const count = await db.role.destroy({ where: { id: req.body.ids[index] } }) + + if (count !== 1) { + await log({ + category: 'ERROR_ROLE_DELETE', + description: '[' + role.id + '] ' + role.name + ': Role could not be deleted.\n' + + 'ID: ' + role.id + '\n' + + 'Name: ' + role.name + '\n' + + 'Description: ' + role.descr + '\n', + user, + userId: req.user.id + }) + } else { + await log({ + category: 'ROLE_DELETE', + description: '[' + role.id + '] ' + role.name + ': Role successfully deleted.\n' + + 'ID: ' + role.id + '\n' + + 'Name: ' + role.name + '\n' + + 'Description: ' + role.descr + '\n', + user, + userId: req.user.id + }) + deletionCounter++ + } + } + if (req.body.ids.length > 1) { + log({ + category: 'ROLE_BATCH_DELETE', + description: deletionCounter + '/' + req.body.ids.length + ' roles successfully deleted.', + user, + userId: req.user.id + }) + } + HttpResponse.successBatch('deleted', 'role', deletionCounter).send(res) + + // res.status(200).send('success') } else { var promises = [] var roleDb @@ -56,6 +108,17 @@ router.postAsync(['', '/:id'], async (req, res) => { promises.push(roleDb.addGroups(req.body.groups, { through: { blacklist: 0 } })) promises.push(roleDb.addGroups(req.body.blacklist, { through: { blacklist: 1 } })) await Promise.all(promises) + log({ + category: 'ROLE_CREATE', + description: '[' + roleDb.id + '] ' + roleDb.name + ': Event successfully created.\n' + + 'ID: ' + roleDb.id + '\n' + + 'Name: ' + roleDb.name + '\n' + + 'Description: ' + roleDb.descr + '\n' + + 'Permissions: ' + req.body.permissions + '\n' + + 'Groups: ' + req.body.groups + '\n' + + 'Blacklist: ' + req.body.blacklist, + userId: req.user.id + }) res.send({ id: req.body.id }) } else if (req.params.id > 0) { // Update existing role @@ -66,6 +129,17 @@ router.postAsync(['', '/:id'], async (req, res) => { promises.push(roleDb.setGroups(req.body.groups, { through: { blacklist: 0 } })) promises.push(roleDb.addGroups(req.body.blacklist, { through: { blacklist: 1 } })) await Promise.all(promises) + log({ + category: 'ROLE_EDIT', + description: '[' + roleDb.id + '] ' + roleDb.name + ': Role successfully edited.\n' + + 'ID: ' + roleDb.id + '\n' + + 'Name: ' + roleDb.name + '\n' + + 'Description: ' + roleDb.descr + '\n' + + 'Permissions: ' + req.body.permissions + '\n' + + 'Groups: ' + req.body.groups + '\n' + + 'Blacklist: ' + req.body.blacklist, + userId: req.user.id + }) res.send({ id: req.params.id }) } else { res.status(404).end() diff --git a/server/api/systemlog.js b/server/api/systemlog.js index d2bd10c..4d7a69a 100644 --- a/server/api/systemlog.js +++ b/server/api/systemlog.js @@ -19,7 +19,7 @@ router.getAsync('', async (req, res) => { if (req.query.clients) where.clientId = req.query.clients.split(',') if (req.query.groups) where.groupId = req.query.groups.split(',') if (req.query.users) where.userId = req.query.users.split(',') - const log = await db.log.findAll({ where, include: ['client', 'group', 'user'], order: [['timestamp', 'DESC']] }) + const log = await db.log.findAll({ where, include: ['client', 'group', 'user'], order: [['id', 'DESC']] }) log.forEach(entry => { if (entry.clientSnapshot) entry.clientSnapshot = JSON.parse(entry.clientSnapshot) if (entry.groupSnapshot) entry.groupSnapshot = JSON.parse(entry.groupSnapshot) diff --git a/server/api/users.js b/server/api/users.js index 33ad3d3..d69c776 100644 --- a/server/api/users.js +++ b/server/api/users.js @@ -5,6 +5,7 @@ var express = require('express') const { decorateApp } = require('@awaitjs/express') var router = decorateApp(express.Router()) var authentication = require(path.join(__appdir, 'lib', 'authentication')) +const log = require(path.join(__appdir, 'lib', 'log')) // ############################################################################ // ########################### GET requests ################################# @@ -39,12 +40,44 @@ router.postAsync('/roles', async (req, res) => { const userIds = req.body.users const roleIds = req.body.roles - const users = await db.user.findAll({ where: { id: userIds } }) + const users = await db.user.findAll({ where: { id: userIds }, include: ['roles'] }) + const userDb = await db.user.findOne({ where: { id: req.user.id } }) if (users) { if (req.query.delete !== undefined && req.query.delete !== 'false') { - users.forEach(user => { user.removeRoles(roleIds) }) + for (let index in users) { + const user = users[index] + const roles = user.roles + const count = await user.removeRoles(roleIds) + let roleString = 'role' + if (count > 1) roleString += 's' + log({ + category: 'USER_REVOKE_ROLE', + description: '[' + user.id + '] ' + user.name + ': Successfully removed ' + count + ' ' + roleString + '.\n' + + 'ID: ' + user.id + '\n' + + 'Name: ' + user.name + '\n' + + 'Removed Roles: ' + roleIds.filter(y => { return roles.map(x => x.id).includes(y) }), + userDb, + userId: req.user.id + }) + } } else { - users.forEach(user => { user.addRoles(roleIds) }) + for (let index in users) { + const user = users[index] + const count = await user.addRoles(roleIds) + if (count.length > 0) { + let roleString = 'role' + if (count[0].length > 1) roleString += 's' + log({ + category: 'USER_GRANT_ROLE', + description: '[' + user.id + '] ' + user.name + ': Successfully added ' + count[0].length + ' ' + roleString + '.\n' + + 'ID: ' + user.id + '\n' + + 'Name: ' + user.name + '\n' + + 'Added Roles: ' + count[0].map(x => x.roleId), + userDb, + userId: req.user.id + }) + } + } } res.status(200).end() } else { @@ -59,24 +92,116 @@ router.postAsync(['/', '/:id'], async (req, res) => { // TODO: Check for permission to delete / create / update user } + // Delete request if (req.query.delete !== undefined && req.query.delete !== 'false') { - const count = await db.user.destroy({ where: { id: body.ids } }) - return res.send({ count }) + const user = await db.user.findOne({ where: { id: req.user.id } }) + + // Only need to log batch request if there is more than one user to delete. + if (req.body.ids.length > 1) { + await log({ + category: 'USER_BATCH_DELETE', + description: 'User batch deletion of ' + req.body.ids.length + ' users initiated by user.', + user, + userId: req.user.id + }) + } + + let deletionCounter = 0 + let selfdeletion = false + + // Delete every user on its own, to get a better log + for (let index in req.body.ids) { + // We can't set the userId in the log if we delete ourselfs. + if (req.body.ids[index] === req.user.id) selfdeletion = true + const userDb = await db.user.findOne({ where: { id: req.body.ids[index] } }) + const count = await db.user.destroy({ where: { id: req.body.ids[index] } }) + if (count !== 1) { + await log({ + category: 'ERROR_USER_DELETE', + description: '[' + userDb.id + '] ' + userDb.username + ': User could not be deleted.\n' + + 'ID: ' + userDb.id + '\n' + + 'Username: ' + userDb.username + '\n' + + 'Name: ' + userDb.name + '\n' + + 'E-Mail: ' + userDb.email + '\n', + user, + userId: selfdeletion ? undefined : req.user.id + }) + } else { + await log({ + category: 'USER_DELETE', + description: '[' + userDb.id + '] ' + userDb.username + ': User successfully deleted.\n' + + 'ID: ' + userDb.id + '\n' + + 'Username: ' + userDb.username + '\n' + + 'Name: ' + userDb.name + '\n' + + 'E-Mail: ' + userDb.email + '\n', + user, + userId: selfdeletion ? undefined : req.user.id + }) + deletionCounter++ + } + } + if (req.body.ids.length > 1) { + log({ + category: 'USER_BATCH_DELETE', + description: deletionCounter + '/' + req.body.ids.length + ' users successfully deleted.', + user, + userId: selfdeletion ? undefined : req.user.id + }) + } + + return res.send({ deletionCounter }) } if (req.params.id === undefined) { const result = await authentication.signup(body) const code = result.code + const user = await db.user.findOne({ where: { id: result.id } }) + + // Create a log entry for the user creation + if (code === 200) { + log({ + category: 'USER_CREATE', + description: '[' + user.id + '] ' + user.username + ': User successfully created.\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + userId: req.user.id + }) + } else { + log({ + category: 'ERROR_USER_CREATE', + description: '[' + code + '][' + result.error + '] ' + result.message + '\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + userId: req.user.id + }) + } + delete result.code return res.status(code).send(result) } else { const id = req.params.id === 'current' ? req.user.id : req.params.id + let user = await db.user.findOne({ where: { id: id } }) let email = req.body.email - if (!authentication.validateEmail(req.body.email)) return res.status(500).send({ error: 'EMAIL_INVALID', message: 'The provided email is invalid.' }) - - let user - user = await db.user.findOne({ where: { id: id } }) + if (!authentication.validateEmail(req.body.email)) { + log({ + category: 'ERROR_USER_EDIT', + description: '[' + user.id + '] ' + user.username + ': User could not be updated. The E-Mail is invalid.\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + userId: req.user.id + }) + return res.status(500).send({ + error: 'EMAIL_INVALID', + message: 'The provided email is invalid.' + }) + } if (user) { let userinfo = { @@ -87,20 +212,74 @@ router.postAsync(['/', '/:id'], async (req, res) => { // Check if the username is set and if it's valid. let username = body.username if (username && req.params.id !== 'current') { - if (!authentication.validateUsername(username)) return res.status(400).send({ error: 'INVALID_USERNAME', message: 'Username does not fullfill the requirements. (No whitespaces)' }) + if (!authentication.validateUsername(username)) { + log({ + category: 'ERROR_USER_EDIT', + description: '[' + user.id + '] ' + user.username + ': User could not be updated. The username does not fullfull the requirements. (No whitespaces)\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + userId: req.user.id + }) + return res.status(400).send({ error: 'INVALID_USERNAME', message: 'Username does not fullfill the requirements. (No whitespaces)' }) + } // Check if the username already exists. let userDb = await db.user.findOne({ where: { username: username, id: { [db.Op.not]: id } } }) - if (userDb) return res.status(400).send({ error: 'USER_ALREADY_EXISTS', message: 'The provided username already exists.' }) + if (userDb) { + log({ + category: 'ERROR_USER_EDIT', + description: '[' + user.id + '] ' + user.username + ': User could not be updated. The username already exists.\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + userId: req.user.id + }) + return res.status(400).send({ error: 'USER_ALREADY_EXISTS', message: 'The provided username already exists.' }) + } userinfo.username = username } // Update the user. await user.update(userinfo) + log({ + category: 'USER_EDIT', + description: '[' + user.id + '] ' + user.username + ': User successfully updated.\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + userId: req.user.id + }) if (body.password) { const result = await authentication.changePassword(id, body.password, body.passwordCurrent) const code = result.code delete result.code + + if (code === 200) { + log({ + category: 'USER_EDIT_PASSWORD', + description: '[' + user.id + '] ' + user.username + ': Password successfully updated.\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + userId: req.user.id + }) + } else { + log({ + category: 'ERROR_USER_EDIT_PASSWORD', + description: '[' + user.id + '] ' + user.username + ': Password could not be updated. Code ' + code + '\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + userId: req.user.id + }) + } + res.status(code).send(result) } } @@ -118,22 +297,58 @@ router.postAsync('/:id/password', async (req, res) => { const result = await authentication.changePassword(id, body.password, body.passwordCurrent) const code = result.code delete result.code + + if (code === 200) { + log({ + category: 'USER_EDIT_PASSWORD', + description: '[' + id + '] Password changed.', + userId: req.user.id + }) + } else { + log({ + category: 'ERROR_USER_EDIT_PASSWORD', + description: '[' + id + '] Password could not be changed. Code ' + code, + userId: req.user.id + }) + } res.status(code).send(result) } else res.status(400).send({ error: 'PASSWORD_MISSING', message: 'This service requires the current and the new password.' }) }) // Function for deleting a single user -router.delete('/:id/', (req, res) => { - // Check if the user has the permission for chaning those userdata. Else return. +router.deleteAsync('/:id/', async (req, res) => { if (req.params.id !== 'current') { - return res.status(500).end() + // Check if the user has the permission for changing those userdata. Else return. + // return res.status(500).end() } const id = req.params.id === 'current' ? req.user.id : req.params.id - + const user = await db.user.findOne({ where: { id: id } }) // Every user can delete his own account. - db.user.destroy({ where: { id } }).then(() => { - res.status(200).end() - }) + const count = await db.user.destroy({ where: { id } }) + if (count !== 1) { + log({ + category: 'ERROR_USER_DELETE', + description: '[' + user.id + '] ' + user.username + ': User could not be deleted.\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + user, + userId: req.user.id + }) + } else { + log({ + category: 'USER_DELETE', + description: '[' + user.id + '] ' + user.username + ': User successfully deleted.\n' + + 'ID: ' + user.id + '\n' + + 'Username: ' + user.username + '\n' + + 'Name: ' + user.name + '\n' + + 'E-Mail: ' + user.email + '\n', + user, + userId: req.params.id === 'current' ? undefined : req.user.id + }) + } + res.status(200).end() }) // ############################################################################ diff --git a/server/api/wakerequests.js b/server/api/wakerequests.js index 9791d41..811fea9 100644 --- a/server/api/wakerequests.js +++ b/server/api/wakerequests.js @@ -5,9 +5,15 @@ const wolHelper = require(path.join(__appdir, 'lib', 'wolhelper')) var express = require('express') const { decorateApp } = require('@awaitjs/express') var router = decorateApp(express.Router()) +const log = require(path.join(__appdir, 'lib', 'log')) router.postAsync('', async (req, res) => { const clients = await db.client.findAll({ where: { id: req.body.clients } }) + await log({ + category: 'WAKE_ON_LAN', + description: 'Wake on Lan signal to ' + clients.length + ' clients initiated by user.', + userId: req.user.id + }) wolHelper.wakeUp(clients) res.status(200).end() }) diff --git a/server/lib/log.js b/server/lib/log.js index fede874..782c34f 100644 --- a/server/lib/log.js +++ b/server/lib/log.js @@ -2,7 +2,7 @@ const path = require('path') var db = require(path.join(__appdir, 'lib', 'sequelize')) -async function log ({ category, description, groupId, clientId, userId }) { +async function log ({ category, description, clientId, userId, groupId, group, client, user }) { const entry = db.log.build({ category, description, @@ -11,9 +11,14 @@ async function log ({ category, description, groupId, clientId, userId }) { clientId, userId }) - if (groupId) entry.groupSnapshot = JSON.stringify(await db.group.findOne({ where: { id: groupId } })) - if (clientId) entry.clientSnapshot = JSON.stringify(await db.client.findOne({ where: { id: clientId } })) - if (userId) entry.userSnapshot = JSON.stringify(await db.user.findOne({ where: { id: userId } })) + + if (group) entry.groupSnapshot = JSON.stringify(group) + else if (groupId) entry.groupSnapshot = JSON.stringify(await db.group.findOne({ where: { id: groupId } })) + if (client) entry.clientSnapshot = JSON.stringify(client) + else if (clientId) entry.clientSnapshot = JSON.stringify(await db.client.findOne({ where: { id: clientId } })) + if (user) entry.userSnapshot = JSON.stringify(user) + else if (userId) entry.userSnapshot = JSON.stringify(await db.user.findOne({ where: { id: userId } })) + await entry.save() return entry } diff --git a/server/lib/wolhelper.js b/server/lib/wolhelper.js index 97c0958..c840e44 100644 --- a/server/lib/wolhelper.js +++ b/server/lib/wolhelper.js @@ -1,3 +1,6 @@ +/* global __appdir */ +var path = require('path') +const log = require(path.join(__appdir, 'lib', 'log')) const wol = require('node-wol') function wakeUp (clients) { @@ -8,6 +11,19 @@ function wakeUp (clients) { if (client.mac !== null && client.ip !== null) { console.log('Waking up: ' + client.name + ' (' + client.mac + ')') wol.wake(client.mac, { address: client.ip.slice(0, client.ip.lastIndexOf('.') + 1) + '255' }, err => { if (err) console.log(err) }) + log({ + category: 'WAKE_ON_LAN', + description: 'Wake on Lan signal sent to client.', + client, + clientId: client.id + }) + } else { + log({ + category: 'ERROR_WAKE_ON_LAN', + description: 'Client is missing ip or mac address.', + client, + clientId: client.id + }) } i++ if (i < clients.length) loop() -- cgit v1.2.3-55-g7522