diff --git a/app/controllers/admin.js b/app/controllers/admin.js index 51c4449..4a4a387 100644 --- a/app/controllers/admin.js +++ b/app/controllers/admin.js @@ -76,6 +76,7 @@ class AdminController extends SiteController { const { coreNode: coreNodeService, dashboard: dashboardService, + logan: loganService, } = this.dtp.services; res.locals.stats = { @@ -86,6 +87,11 @@ class AdminController extends SiteController { res.locals.pageTitle = `Admin Dashbord for ${this.dtp.config.site.name}`; + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getHomeView', + }); + res.render('admin/index'); } } @@ -93,5 +99,6 @@ class AdminController extends SiteController { module.exports = { slug: 'admin', name: 'admin', + className: 'AdminController', create: async (dtp) => { return new AdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/announcement.js b/app/controllers/admin/announcement.js index cfcd3dc..5edb1d3 100644 --- a/app/controllers/admin/announcement.js +++ b/app/controllers/admin/announcement.js @@ -38,31 +38,76 @@ class AnnouncementAdminController extends SiteController { } async populateAnnouncementId (req, res, next, announcementId) { - const { announcement: announcementService } = this.dtp.services; + const { + announcement: announcementService, + logan: loganService, + } = this.dtp.services; try { res.locals.announcement = await announcementService.getById(announcementId); return next(); } catch (error) { + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'populateAnnouncementId', + message: `failed to populate announcement: ${error.message}`, + data: { announcementId, error }, + }); return next(error); } } async postUpdateAnnouncement (req, res, next) { - const { announcement: announcementService } = this.dtp.services; + const { + announcement: announcementService, + logan: loganService, + } = this.dtp.services; try { await announcementService.update(res.locals.announcement, req.body); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postUpdateAnnouncement', + message: 'announcement updated', + data: { + announcement: { + _id: res.locals.announcement._id, + }, + }, + }); res.redirect('/admin/announcement'); } catch (error) { + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postUpdateAnnouncement', + message: `failed to update announcement: ${error.message}`, + data: { error }, + }); return next(error); } } async postCreateAnnouncement (req, res, next) { - const { announcement: announcementService } = this.dtp.services; + const { + announcement: announcementService, + logan: loganService, + } = this.dtp.services; try { - await announcementService.create(req.body); + res.locals.announcement = await announcementService.create(req.body); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postCreateAnnouncement', + message: 'announcement created', + data: { + announcement: res.locals.announcement, + }, + }); res.redirect('/admin/announcement'); } catch (error) { + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postCreateAnnouncement', + message: `failed to create announcement: ${error.message}`, + data: { error }, + }); return next(error); } } @@ -83,14 +128,27 @@ class AnnouncementAdminController extends SiteController { } async deleteAnnouncement (req, res) { - const { announcement: announcementService } = this.dtp.services; + const { + announcement: announcementService, + logan: loganService, + } = this.dtp.services; try { const displayList = this.createDisplayList('delete-announcement'); await announcementService.remove(res.locals.announcement); displayList.reload(); res.status(200).json({ success: true, displayList }); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'deleteAnnouncement', + data: { announcement: { _id: res.locals.announcement._id } }, + }); } catch (error) { - this.log.error('failed to delete announcement', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'deleteAnnouncement', + message: `failed to delete announcement: ${error.message}`, + data: { error }, + }); res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -102,8 +160,6 @@ class AnnouncementAdminController extends SiteController { module.exports = { name: 'announcement', slug: 'announcement', - create: async (dtp) => { - let controller = new AnnouncementAdminController(dtp); - return controller; - }, + className: 'AnnouncementAdminController', + create: async (dtp) => { return new AnnouncementAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/content-report.js b/app/controllers/admin/content-report.js index fe47479..e7d0f2f 100644 --- a/app/controllers/admin/content-report.js +++ b/app/controllers/admin/content-report.js @@ -8,7 +8,7 @@ const express = require('express'); const { SiteController } = require('../../../lib/site-lib'); -class ContentReportController extends SiteController { +class ContentReportAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -89,5 +89,6 @@ class ContentReportController extends SiteController { module.exports = { name: 'adminContentReport', slug: 'admin-content-report', - create: async (dtp) => { return new ContentReportController(dtp); }, + className: 'ContentReportAdminController', + create: async (dtp) => { return new ContentReportAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/core-node.js b/app/controllers/admin/core-node.js index fd7cdc9..345be1b 100644 --- a/app/controllers/admin/core-node.js +++ b/app/controllers/admin/core-node.js @@ -9,7 +9,7 @@ const express = require('express'); const { SiteController, SiteError } = require('../../../lib/site-lib'); -class CoreNodeController extends SiteController { +class CoreNodeAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -161,5 +161,6 @@ class CoreNodeController extends SiteController { module.exports = { name: 'adminCoreNode', slug: 'admin-core-node', - create: async (dtp) => { return new CoreNodeController(dtp); }, + className: 'CoreNodeAdminController', + create: async (dtp) => { return new CoreNodeAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/core-user.js b/app/controllers/admin/core-user.js index 421c6fe..28d0366 100644 --- a/app/controllers/admin/core-user.js +++ b/app/controllers/admin/core-user.js @@ -9,7 +9,7 @@ const express = require('express'); const { SiteController, SiteError } = require('../../../lib/site-lib'); -class CoreUserController extends SiteController { +class CoreUserAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -90,5 +90,6 @@ class CoreUserController extends SiteController { module.exports = { name: 'adminCoreUser', slug: 'admin-core-user', - create: async (dtp) => { return new CoreUserController(dtp); }, + className: 'CoreUserAdminController', + create: async (dtp) => { return new CoreUserAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/host.js b/app/controllers/admin/host.js index daf9b26..979a6d5 100644 --- a/app/controllers/admin/host.js +++ b/app/controllers/admin/host.js @@ -12,7 +12,7 @@ const NetHostStats = mongoose.model('NetHostStats'); const { /*SiteError,*/ SiteController } = require('../../../lib/site-lib'); -class HostController extends SiteController { +class HostAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -118,5 +118,6 @@ class HostController extends SiteController { module.exports = { name: 'adminHost', slug: 'admin-host', - create: async (dtp) => { return new HostController(dtp); }, + className: 'HostAdminController', + create: async (dtp) => { return new HostAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/job-queue.js b/app/controllers/admin/job-queue.js index 8136d96..9f16c47 100644 --- a/app/controllers/admin/job-queue.js +++ b/app/controllers/admin/job-queue.js @@ -8,7 +8,7 @@ const express = require('express'); const { SiteController, SiteError } = require('../../../lib/site-lib'); -class JobQueueController extends SiteController { +class JobQueueAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -121,5 +121,6 @@ class JobQueueController extends SiteController { module.exports = { name: 'adminJobQueue', slug: 'admin-job-queue', - create: async (dtp) => { return new JobQueueController(dtp); }, + className: 'JobQueueAdminController', + create: async (dtp) => { return new JobQueueAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/log.js b/app/controllers/admin/log.js index 4e7bd34..a29a82e 100644 --- a/app/controllers/admin/log.js +++ b/app/controllers/admin/log.js @@ -8,7 +8,7 @@ const express = require('express'); const { SiteController } = require('../../../lib/site-lib'); -class LogController extends SiteController { +class LogAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -53,5 +53,6 @@ class LogController extends SiteController { module.exports = { name: 'adminLog', slug: 'admin-log', - create: async (dtp) => { return new LogController(dtp); }, + className: 'LogAdminController', + create: async (dtp) => { return new LogAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/newsletter.js b/app/controllers/admin/newsletter.js index 74afa24..a875661 100644 --- a/app/controllers/admin/newsletter.js +++ b/app/controllers/admin/newsletter.js @@ -8,7 +8,7 @@ const express = require('express'); const { SiteController, SiteError } = require('../../../lib/site-lib'); -class NewsletterController extends SiteController { +class NewsletterAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -169,8 +169,6 @@ class NewsletterController extends SiteController { module.exports = { name: 'adminNewsletter', slug: 'admin-newsletter', - create: async (dtp) => { - let controller = new NewsletterController(dtp); - return controller; - }, + className: 'NewsletterAdminController', + create: async (dtp) => { return new NewsletterAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/newsroom.js b/app/controllers/admin/newsroom.js index e2811e2..d014460 100644 --- a/app/controllers/admin/newsroom.js +++ b/app/controllers/admin/newsroom.js @@ -160,5 +160,6 @@ class NewsroomAdminController extends SiteController { module.exports = { name: 'newsroomAdmin', slug: 'newsroom-admin', + className: 'NewsroomAdminController', create: async (dtp) => { return new NewsroomAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/otp.js b/app/controllers/admin/otp.js index 0363aa9..9bdbd15 100644 --- a/app/controllers/admin/otp.js +++ b/app/controllers/admin/otp.js @@ -51,5 +51,6 @@ class OtpAdminController extends SiteController { module.exports = { name: 'adminOtp', slug: 'admin-opt', + className: 'OtpAdminController', create: async (dtp) => { return new OtpAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/service-node.js b/app/controllers/admin/service-node.js index 26f8c06..5519b44 100644 --- a/app/controllers/admin/service-node.js +++ b/app/controllers/admin/service-node.js @@ -8,7 +8,7 @@ const express = require('express'); const { SiteController, SiteError } = require('../../../lib/site-lib'); -class ServiceNodeController extends SiteController { +class ServiceNodeAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -130,5 +130,6 @@ class ServiceNodeController extends SiteController { module.exports = { name: 'adminServiceNode', slug: 'admin-service-node', - create: async (dtp) => { return new ServiceNodeController(dtp); }, + className: 'ServiceNodeAdminController', + create: async (dtp) => { return new ServiceNodeAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/settings.js b/app/controllers/admin/settings.js index 1c8b967..57ea30f 100644 --- a/app/controllers/admin/settings.js +++ b/app/controllers/admin/settings.js @@ -8,7 +8,7 @@ const express = require('express'); const { SiteController } = require('../../../lib/site-lib'); -class SettingsController extends SiteController { +class SettingsAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -100,5 +100,6 @@ class SettingsController extends SiteController { module.exports = { name: 'adminSettings', slug: 'admin-settings', - create: async (dtp) => { return new SettingsController(dtp); }, + className: 'SettingsAdminController', + create: async (dtp) => { return new SettingsAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/admin/user.js b/app/controllers/admin/user.js index fc1fd9d..27aab35 100644 --- a/app/controllers/admin/user.js +++ b/app/controllers/admin/user.js @@ -8,7 +8,7 @@ const express = require('express'); const { SiteController } = require('../../../lib/site-lib'); -class UserController extends SiteController { +class UserAdminController extends SiteController { constructor (dtp) { super(dtp, module.exports); @@ -79,5 +79,6 @@ class UserController extends SiteController { module.exports = { name: 'adminUser', slug: 'admin-user', - create: async (dtp) => { return new UserController(dtp); }, + className: 'UserAdminController', + create: async (dtp) => { return new UserAdminController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/announcement.js b/app/controllers/announcement.js index ea55350..c07ebbc 100644 --- a/app/controllers/announcement.js +++ b/app/controllers/announcement.js @@ -78,8 +78,6 @@ class AnnouncementController extends SiteController { module.exports = { slug: 'announcement', name: 'announcement', - create: async (dtp) => { - let controller = new AnnouncementController(dtp); - return controller; - }, + className: 'AnnouncementController', + create: async (dtp) => { return new AnnouncementController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/auth.js b/app/controllers/auth.js index 15caccb..57e80d0 100644 --- a/app/controllers/auth.js +++ b/app/controllers/auth.js @@ -84,7 +84,10 @@ class AuthController extends SiteController { } async postOtpEnable (req, res, next) { - const { otpAuth: otpAuthService } = this.dtp.services; + const { + logan: loganService, + otpAuth: otpAuthService, + } = this.dtp.services; const service = req.body['otp-service']; const secret = req.body['otp-secret']; @@ -95,17 +98,33 @@ class AuthController extends SiteController { this.log.info('enabling OTP protections', { service, secret, token }); res.locals.otpAccount = await otpAuthService.createOtpAccount(req, service, secret, token); res.locals.otpRedirectURL = otpRedirectURL; + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postOtpEnable', + data: { + user: { + _id: req.user._id, + username: req.user.username, + }, + }, + }); res.render('otp/new-account'); } catch (error) { - this.log.error('failed to enable OTP protections', { - service, error, + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postOtpEnable', + message: `failed to enable OTP account: ${error.message}`, + data: { service, error }, }); return next(error); } } async postOtpAuthenticate (req, res, next) { - const { otpAuth: otpAuthService } = this.dtp.services; + const { + logan: loganService, + otpAuth: otpAuthService, + } = this.dtp.services; if (!req.user) { return res.status(403).json({ @@ -129,36 +148,87 @@ class AuthController extends SiteController { } try { await otpAuthService.startOtpSession(req, service, passcode); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postOtpAuthenticate', + data: { + user: { + _id: req.user._id, + username: req.user.username, + }, + }, + }); return res.redirect(req.body['otp-redirect']); } catch (error) { - this.log.error('failed to verify one-time password for 2FA', { - service, error, - }, req.user); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postOtpAuthenticate', + message: `failed to verify one-time password: ${error.message}`, + data: { + user: { + _id: req.user._id, + username: req.user.username, + }, + error, + }, + }); return next(error); } } async postLogin (req, res, next) { + const { logan: loganService } = this.dtp.services; + const redirectUri = req.session.loginReturnTo || '/'; this.log.debug('starting passport.authenticate', { redirectUri }); + passport.authenticate('dtp-local', (error, user/*, info*/) => { if (error) { req.session.loginResult = error.toString(); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postLogin', + message: `login failed: ${error.message}`, + data: { error }, + }); return next(error); } if (!user) { req.session.loginResult = 'Username or email address is unknown.'; + loganService.sendRequestEvent(module.exports, req, { + level: 'alert', + event: 'postLogin', + message: 'username or email address is unknown', + }); return res.redirect('/welcome/login'); } this.log.info('user logging in', { user: user.username }); req.login(user, async (error) => { if (error) { + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postLogin', + message: `failed to start user session: ${error.message}`, + data: { error }, + }); return next(error); } // scrub login return URL from session delete req.session.loginReturnTo; + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postLogin', + message: 'session started for site member', + data: { + user: { + _id: user._id, + username: user.username, + }, + }, + }); + // redirect to whatever was wanted return res.redirect(redirectUri); }); @@ -166,20 +236,29 @@ class AuthController extends SiteController { } async getPersonalApiToken (req, res, next) { + const { + apiGuard: apiGuardService, + logan: loganService, + } = this.dtp.platform.services; try { - const { apiGuard: apiGuardService } = this.dtp.platform.services; res.locals.apiToken = await apiGuardService.createApiToken(req.user, [ 'account-read', // additional scopes go here ]); res.render('api-token/view'); } catch (error) { - this.log.error('failed to generate API token', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getPersonalApiToken', + message: `failed to generate API token: ${error.message}`, + data: { error }, + }); return next(error); } } async getSocketToken (req, res, next) { + const { logan: loganService } = this.dtp.services; try { const token = await ConnectToken.create({ created: new Date(), @@ -192,35 +271,68 @@ class AuthController extends SiteController { token: token.token }); } catch (error) { - this.log.error('failed to create Socket.io connect token', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getSocketToken', + message: `failed to create socket token: ${error.message}`, + data: { error }, + }); return next(error); } } async getCoreHome (req, res, next) { - const { coreNode: coreNodeService } = this.dtp.services; + const { + coreNode: coreNodeService, + logan: loganService, + } = this.dtp.services; try { res.locals.currentView = 'welcome'; res.locals.pagination = this.getPaginationParameters(req, 20); res.locals.connectedCores = await coreNodeService.getConnectedCores(res.locals.pagination); res.render('welcome/core-home'); } catch (error) { - this.log.error('failed to generate Core auth menu', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getCoreHome', + message: `failed to render view: ${error.message}`, + data: { error }, + }); return next(error); } } async getLogout (req, res, next) { + const { logan: loganService } = this.dtp.services; + if (!req.user) { return next(new SiteError(403, 'You are not signed in')); } - + + const user = { + _id: req.user._id, + username: req.user.username, + }; req.logout(); req.session.destroy((err) => { if (err) { this.log.error('failed to destroy browser session', { err }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getLogout', + message: 'failed to destroy browser session', + data: { error: err }, + }); return next(err); } + + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getLogout', + message: 'user terminated their browser session', + data: { user }, + }); + res.redirect('/'); }); } @@ -229,5 +341,6 @@ class AuthController extends SiteController { module.exports = { slug: 'auth', name: 'auth', + className: 'AuthController', create: async (dtp) => { return new AuthController(dtp); }, -}; +}; \ No newline at end of file diff --git a/app/controllers/chat.js b/app/controllers/chat.js index 8290275..82cf2a8 100644 --- a/app/controllers/chat.js +++ b/app/controllers/chat.js @@ -132,7 +132,10 @@ class ChatController extends SiteController { } async populateRoomId (req, res, next, roomId) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { res.locals.room = await chatService.getRoomById(roomId); if (!res.locals.room) { @@ -141,12 +144,21 @@ class ChatController extends SiteController { return next(); } catch (error) { this.log.error('failed to populate roomId', { roomId, error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'populateRoomId', + message: error.message, + data: { roomId, error }, + }); return next(error); } } async populateInviteId (req, res, next, inviteId) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { res.locals.invite = await chatService.getRoomInviteById(inviteId); if (!res.locals.invite) { @@ -155,12 +167,21 @@ class ChatController extends SiteController { return next(); } catch (error) { this.log.error('failed to populate inviteId', { inviteId, error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'populateInviteId', + message: error.message, + data: { inviteId, error }, + }); return next(error); } } async postRoomInviteAction (req, res) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { const { response } = req.body; const displayList = this.createDisplayList('room-invite-action'); @@ -168,11 +189,27 @@ class ChatController extends SiteController { switch (response) { case 'accept': await chatService.acceptRoomInvite(res.locals.invite); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postRoomInviteAction', + message: 'invitation accepted successfully', + data: { + invite: res.locals.invite, + }, + }); displayList.navigateTo(`/chat/room/${res.locals.invite.room._id}`); break; case 'reject': await chatService.rejectRoomInvite(res.locals.invite); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postRoomInviteAction', + message: 'invitation rejected successfully', + data: { + invite: res.locals.invite, + }, + }); displayList.showNotification( `Chat room invite rejected`, 'success', @@ -192,6 +229,12 @@ class ChatController extends SiteController { response: req.body.response, error, }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postRoomInviteAction', + message: `failed to execute room invite action: ${error.message}`, + data: { error }, + }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -200,7 +243,11 @@ class ChatController extends SiteController { } async postRoomInvite (req, res) { - const { chat: chatService, user: userService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + user: userService, + } = this.dtp.services; this.log.debug('room invite received', { invite: req.body }); if (!req.body.username || !req.body.username.length) { return res.status(400).json({ success: false, message: 'Please provide a username' }); @@ -223,7 +270,7 @@ class ChatController extends SiteController { throw new SiteError(400, "You can't invite yourself."); } - await chatService.sendRoomInvite(res.locals.room, member, req.body); + res.locals.invite = await chatService.sendRoomInvite(res.locals.room, member, req.body); const displayList = this.createDisplayList('invite create'); displayList.showNotification( @@ -232,9 +279,28 @@ class ChatController extends SiteController { 'top-left', 5000, ); + + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postRoomInvite', + data: { + room: { + _id: res.locals.room._id, + name: res.locals.room.name, + }, + invite: res.locals.invite, + }, + }); + res.status(200).json({ success: true, displayList }); } catch (error) { this.log.error('failed to create room invitation', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postRoomInvite', + message: `failed to create room invitation: ${error.message}`, + data: { error }, + }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -243,35 +309,78 @@ class ChatController extends SiteController { } async postRoomUpdate (req, res, next) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { res.locals.room = await chatService.updateRoom(res.locals.room, req.body); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postRoomUpdate', + data: { + room: { + _id: res.locals.room._id, + name: res.locals.room.name, + }, + }, + }); res.redirect(`/chat/room/${res.locals.room._id}`); } catch (error) { - this.log.error('failed to update chat room', { - // roomId: res.locals.room._id, - error, + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postRoomUpdate', + message: `failed to update chat room: ${error.message}`, + data: { error }, }); return next(error); } } async postRoomCreate (req, res, next) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { res.locals.room = await chatService.createRoom(req.user, req.body); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postRoomCreate', + message: 'chat room created', + data: { + room: res.locals.room, + }, + }); res.redirect(`/chat/room/${res.locals.room._id}`); } catch (error) { - this.log.error('failed to create chat room', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postRoomCreate', + message: `failed to create chat room: ${error.message}`, + data: { error }, + }); return next(error); } } async getRoomEditor (req, res) { + const { logan: loganService } = this.dtp.services; + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getRoomEditor', + data: { + room: { + _id: res.locals.room._id, + name: res.locals.room.name, + }, + }, + }); res.render('chat/room/editor'); } async getRoomForm (req, res, next) { + const { logan: loganService } = this.dtp.services; const validFormNames = [ 'invite-member', ]; @@ -280,65 +389,148 @@ class ChatController extends SiteController { return next(new SiteError(404, 'Form not found')); } try { + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getRoomForm', + data: { formName }, + }); res.render(`chat/room/form/${formName}`); } catch (error) { this.log.error('failed to render form', { formName, error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getRoomForm', + message: `failed to render form: ${error.message}`, + data: { formName, error }, + }); return next(error); } } async getRoomInviteView (req, res) { + const { logan: loganService } = this.dtp.services; + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getRoomInviteView', + data: { + invite: res.locals.invite, + }, + }); res.render('chat/room/invite/view'); } async getRoomInviteHome (req, res, next) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { res.locals.invites = { new: await chatService.getRoomInvites(res.locals.room, 'new'), accepted: await chatService.getRoomInvites(res.locals.room, 'accepted'), rejected: await chatService.getRoomInvites(res.locals.room, 'rejected'), }; + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getRoomInviteHome', + }); res.render('chat/room/invite'); } catch (error) { - this.log.error('failed to render the room invites view', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getRoomInviteHome', + message: `failed to render the view: ${error.message}`, + data: { error }, + }); return next(error); } } async getRoomSettings (req, res) { + const { logan: loganService } = this.dtp.services; + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getRoomSettings', + data: { + room: { + _id: res.locals.room._id, + name: res.locals.room.name, + }, + }, + }); res.render('chat/room/editor'); } async getRoomView (req, res, next) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { res.locals.pageTitle = res.locals.room.name; const pagination = { skip: 0, cpp: 20 }; res.locals.chatMessages = await chatService.getChannelHistory(res.locals.room, pagination); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getRoomView', + data: { + room: { + _id: res.locals.room._id, + name: res.locals.room.name, + }, + }, + }); + res.render('chat/room/view'); } catch (error) { this.log.error('failed to render chat room view', { roomId: req.params.roomId, error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getRoomView', + message: `failed to render the view: ${error.message}`, + data: { + room: { + _id: res.locals.room._id, + name: res.locals.room.name, + }, + error, + }, + }); return next(error); } } async getRoomHome (req, res, next) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { res.locals.pagination = this.getPaginationParameters(req, 20); res.locals.publicRooms = await chatService.getPublicRooms(req.user, res.locals.pagination); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getRoomHome', + }); res.render('chat/room/index'); } catch (error) { - this.log.error('failed to render room home', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getRoomHome', + message: `failed to render the view: ${error.message}`, + data: { error }, + }); return next(error); } } async getHome (req, res, next) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { res.locals.pageTitle = 'Chat Home'; @@ -349,15 +541,28 @@ class ChatController extends SiteController { res.locals.joinedChatRooms.forEach((room) => roomIds.push(room._id)); res.locals.timeline = await chatService.getMultiRoomTimeline(roomIds, res.locals.pagination); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getHome', + }); + res.render('chat/index'); } catch (error) { - this.log.error('failed to render chat home', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getHome', + message: `failed to render the view: ${error.message}`, + data: { error }, + }); return next(error); } } async deleteInvite (req, res, next) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { if (res.locals.room.owner._id.equals(req.user._id)) { throw new SiteError(403, 'This is not your invitiation'); @@ -374,15 +579,34 @@ class ChatController extends SiteController { 5000, ); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'deleteInvite', + message: 'room invitation deleted', + data: { + invite: { + _id: res.locals.invite._id, + }, + }, + }); + res.status(200).json({ success: true, displayList }); } catch (error) { - this.log.error('failed to delete chat room invite', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'deleteInvite', + message: `failed to delete chat room invite: ${error.message}`, + data: { error }, + }); return next(error); } } async deleteRoom (req, res, next) { - const { chat: chatService } = this.dtp.services; + const { + chat: chatService, + logan: loganService, + } = this.dtp.services; try { if (res.locals.room.owner._id.equals(req.user._id)) { throw new SiteError(403, 'This is not your chat room'); @@ -393,9 +617,26 @@ class ChatController extends SiteController { const displayList = this.createDisplayList('delete chat invite'); displayList.navigateTo('/chat'); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'deleteRoom', + message: 'chat room deleted', + data: { + room: { + _id: res.locals.room._id, + name: res.locals.room.name, + }, + }, + }); + res.status(200).json({ success: true, displayList }); } catch (error) { - this.log.error('failed to delete chat room invite', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'deleteRoom', + message: `failed to delete chat room: ${error.message}`, + data: { error }, + }); return next(error); } } @@ -404,5 +645,6 @@ class ChatController extends SiteController { module.exports = { slug: 'chat', name: 'chat', + className: 'ChatController', create: async (dtp) => { return new ChatController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/comment.js b/app/controllers/comment.js index 7935572..17d8005 100644 --- a/app/controllers/comment.js +++ b/app/controllers/comment.js @@ -153,5 +153,6 @@ class CommentController extends SiteController { module.exports = { slug: 'comment', name: 'comment', + className: 'CommentController', create: async (dtp) => { return new CommentController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/email.js b/app/controllers/email.js index b6078fd..b70d6b1 100644 --- a/app/controllers/email.js +++ b/app/controllers/email.js @@ -38,27 +38,43 @@ class EmailController extends SiteController { } async getEmailOptOut (req, res, next) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { await userService.emailOptOut(req.query.u, req.query.c); res.render('email/opt-out-success'); } catch (error) { - this.log.error('failed to opt-out from email', { - userId: req.query.t, - category: req.query.c, - error, + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getEmailOptOut', + message: 'failed to opt-out from email', + data: { + userId: req.query.u, + category: req.query.c, + error, + }, }); return next(error); } } async getEmailVerify (req, res, next) { - const { email: emailService } = this.dtp.services; + const { + email: emailService, + logan: loganService, + } = this.dtp.services; try { await emailService.verifyToken(req.query.t); res.render('email/verify-success'); } catch (error) { - this.log.error('failed to verify email', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getEmailVerify', + message: `failed to verify email: ${error.message}`, + data: { error }, + }); return next(error); } } @@ -67,5 +83,6 @@ class EmailController extends SiteController { module.exports = { slug: 'email', name: 'email', + className: 'EmailController', create: async (dtp) => { return new EmailController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/form.js b/app/controllers/form.js index d35e713..f4b8c31 100644 --- a/app/controllers/form.js +++ b/app/controllers/form.js @@ -66,5 +66,6 @@ class FormController extends SiteController { module.exports = { slug: 'form', name: 'form', + className: 'FormController', create: async (dtp) => { return new FormController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/hive.js b/app/controllers/hive.js index 68c2cc1..b02891e 100644 --- a/app/controllers/hive.js +++ b/app/controllers/hive.js @@ -59,5 +59,6 @@ class HiveController extends SiteController { module.exports = { slug: 'hive', name: 'hive', + className: 'HiveController', create: async (dtp) => { return new HiveController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/hive/kaleidoscope.js b/app/controllers/hive/kaleidoscope.js index b8c7d1b..3dcd8dd 100644 --- a/app/controllers/hive/kaleidoscope.js +++ b/app/controllers/hive/kaleidoscope.js @@ -95,5 +95,6 @@ class HiveKaleidoscopeController extends SiteController { module.exports = { name: 'hiveKaleidoscope', slug: 'hive-kaleidoscope', + className: 'HiveKaleidoscopeController', create: async (dtp) => { return new HiveKaleidoscopeController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/hive/user.js b/app/controllers/hive/user.js index fa19802..4b7ab59 100644 --- a/app/controllers/hive/user.js +++ b/app/controllers/hive/user.js @@ -151,8 +151,6 @@ class HiveUserController extends SiteController { module.exports = { name: 'hiveUser', slug: 'hive-user', - create: async (dtp) => { - let controller = new HiveUserController(dtp); - return controller; - }, + className: 'HiveUserController', + create: async (dtp) => { return new HiveUserController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/home.js b/app/controllers/home.js index 6c459cb..dee0497 100644 --- a/app/controllers/home.js +++ b/app/controllers/home.js @@ -63,24 +63,39 @@ class HomeController extends SiteController { } async getHome (req, res, next) { - const { announcement: announcementService, hive: hiveService } = this.dtp.services; + const { + announcement: announcementService, + hive: hiveService, + logan: loganService, + } = this.dtp.services; try { res.locals.announcements = await announcementService.getLatest(req.user); res.locals.pagination = this.getPaginationParameters(req, 20); res.locals.constellationTimeline = await hiveService.getConstellationTimeline(req.user, res.locals.pagination); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getHome', + }); + res.render('index'); } catch (error) { - this.log.error('failed to render home view', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getHome', + message: `failed to render the view: ${error.message}`, + data: { error }, + }); return next(error); } } } module.exports = { + isHome: true, slug: 'home', name: 'home', - isHome: true, + className: 'HomeController', create: async (dtp) => { return new HomeController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/image.js b/app/controllers/image.js index bc8bac5..83a38f9 100644 --- a/app/controllers/image.js +++ b/app/controllers/image.js @@ -134,5 +134,6 @@ class ImageController extends SiteController { module.exports = { slug: 'image', name: 'image', + className: 'ImageController', create: async (dtp) => { return new ImageController(dtp); }, }; diff --git a/app/controllers/manifest.js b/app/controllers/manifest.js index d55eb5a..d64b28d 100644 --- a/app/controllers/manifest.js +++ b/app/controllers/manifest.js @@ -66,5 +66,6 @@ class ManifestController extends SiteController { module.exports = { slug: 'manifest', name: 'manifest', + className: 'ManifestController', create: async (dtp) => { return new ManifestController(dtp); }, }; diff --git a/app/controllers/newsletter.js b/app/controllers/newsletter.js index bfaed75..112d5b9 100644 --- a/app/controllers/newsletter.js +++ b/app/controllers/newsletter.js @@ -94,8 +94,6 @@ class NewsletterController extends SiteController { module.exports = { slug: 'newsletter', name: 'newsletter', - create: async (dtp) => { - let controller = new NewsletterController(dtp); - return controller; - }, + className: 'NewsletterController', + create: async (dtp) => { return new NewsletterController(dtp); }, }; diff --git a/app/controllers/newsroom.js b/app/controllers/newsroom.js index 265e018..1e905c9 100644 --- a/app/controllers/newsroom.js +++ b/app/controllers/newsroom.js @@ -81,5 +81,6 @@ class NewsroomController extends SiteController { module.exports = { slug: 'newsroom', name: 'newsroom', + className: 'NewsroomController', create: (dtp) => { return new NewsroomController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/notification.js b/app/controllers/notification.js index 4711ed0..ae89916 100644 --- a/app/controllers/notification.js +++ b/app/controllers/notification.js @@ -74,5 +74,6 @@ class NotificationController extends SiteController { module.exports = { slug: 'notification', name: 'notification', + className: 'NotificationController', create: async (dtp) => { return new NotificationController(dtp); }, }; \ No newline at end of file diff --git a/app/controllers/user.js b/app/controllers/user.js index 897739d..bbcedf7 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -18,6 +18,7 @@ class UserController extends SiteController { async start ( ) { const { dtp } = this; const { + csrfToken: csrfTokenService, limiter: limiterService, otpAuth: otpAuthService, session: sessionService, @@ -97,6 +98,7 @@ class UserController extends SiteController { router.post( '/', limiterService.createMiddleware(limiterService.config.user.postCreate), + csrfTokenService.middleware({ name: 'user-create' }), this.postCreateUser.bind(this), ); @@ -155,7 +157,10 @@ class UserController extends SiteController { } async populateCoreUsername (req, res, next, coreUsername) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { res.locals.username = userService.filterUsername(coreUsername); res.locals.userProfileId = await userService.getCoreUserId(res.locals.username); @@ -166,12 +171,21 @@ class UserController extends SiteController { return this.populateCoreUserId(req, res, next, res.locals.userProfileId); } catch (error) { this.log.error('failed to populate core username', { coreUsername, error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'populateCoreUsername', + message: `failed to populate Core user: ${error.message}`, + data: { coreUsername, error }, + }); return next(error); } } async populateCoreUserId (req, res, next, coreUserId) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { res.locals.userProfileId = mongoose.Types.ObjectId(coreUserId); @@ -187,13 +201,21 @@ class UserController extends SiteController { return next(); } catch (error) { - this.log.error('failed to populate core user id', { coreUserId, error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'populateCoreUserId', + message: `failed to populate core user: ${error.message}`, + data: { coreUserId, error }, + }); return next(error); } } async populateLocalUsername (req, res, next, username) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { res.locals.username = userService.filterUsername(username); @@ -210,12 +232,21 @@ class UserController extends SiteController { return next(); } catch (error) { this.log.error('failed to populate local username', { username, error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'populateLocalUsername', + message: `failed to populate local user: ${error.message}`, + data: { username, error }, + }); return next(error); } } async populateLocalUserId (req, res, next, userId) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { res.locals.userProfileId = mongoose.Types.ObjectId(userId); @@ -231,13 +262,21 @@ class UserController extends SiteController { return next(); } catch (error) { - this.log.error('failed to populate local user id', { userId, error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'populateLocalUserId', + message: `failed to populate local user: ${error.message}`, + data: { userId, error }, + }); return next(error); } } async postCreateUser (req, res, next) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { // verify that the request has submitted a captcha if ((typeof req.body.captcha !== 'string') || req.body.captcha.length === 0) { @@ -255,11 +294,42 @@ class UserController extends SiteController { // create the user account res.locals.user = await userService.create(req.body); + const form = Object.assign(req.body); + if (form.password) { + delete form.password; + } + if (form.passwordv) { + delete form.passwordv; + } + + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postCreateUser', + data: { + form, + user: { + _id: res.locals.user._id, + username: res.locals.user.username, + }, + }, + }); + // log the user in req.login(res.locals.user, (error) => { if (error) { + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postCreateUser', + message: `failed to start user session: ${error.message}`, + data: { error }, + }); return next(error); } + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postCreateUser', + message: 'user session started', + }); res.redirect(`/user/${res.locals.user.username}`); }); } catch (error) { @@ -269,7 +339,10 @@ class UserController extends SiteController { } async postProfilePhoto (req, res) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { await userService.updatePhoto(req.user, req.file); @@ -280,9 +353,21 @@ class UserController extends SiteController { 'bottom-center', 2000, ); + + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postProfilePhoto', + message: 'profile photo updated', + }); + res.status(200).json({ success: true, displayList }); } catch (error) { - this.log.error('failed to update profile photo', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postProfilePhoto', + message: `failed to update profile photo: ${error.message}`, + data: { error }, + }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -291,7 +376,10 @@ class UserController extends SiteController { } async postHeaderImage (req, res) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { await userService.updateHeaderImage(req.user, req.file); @@ -302,9 +390,21 @@ class UserController extends SiteController { 'bottom-center', 2000, ); + + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postHeaderImage', + message: 'header image updated', + }); + res.status(200).json({ success: true, displayList }); } catch (error) { - this.log.error('failed to update header image', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postHeaderImage', + message: `failed to update header image: ${error.message}`, + data: { error }, + }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -313,15 +413,30 @@ class UserController extends SiteController { } async postUpdateCoreSettings (req, res) { - const { coreNode: coreNodeService } = this.dtp.services; + const { + coreNode: coreNodeService, + logan: loganService, + } = this.dtp.services; try { await coreNodeService.updateUserSettings(req.user, req.body); const displayList = this.createDisplayList('app-settings'); displayList.reload(); + + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postUpdateCoreSettings', + message: 'CoreUser settings updated', + }); + res.status(200).json({ success: true, displayList }); } catch (error) { - this.log.error('failed to update CoreUser settings', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postUpdateCoreSettings', + message: `failed to update CoreUser settings: ${error.message}`, + data: { error }, + }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -330,16 +445,30 @@ class UserController extends SiteController { } async postUpdateSettings (req, res) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { await userService.updateSettings(req.user, req.body); const displayList = this.createDisplayList('app-settings'); displayList.reload(); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'postUpdateSettings', + message: 'account settings updates', + }); + res.status(200).json({ success: true, displayList }); } catch (error) { - this.log.error('failed to update account settings', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'postUpdateSettings', + message: `failed to update account settings: ${error.message}`, + data: { error }, + }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -352,13 +481,26 @@ class UserController extends SiteController { } async getOtpDisable (req, res) { - const { otpAuth: otpAuthService } = this.dtp.services; + const { + logan: loganService, + otpAuth: otpAuthService, + } = this.dtp.services; try { await otpAuthService.destroyOtpSession(req, 'Account'); await otpAuthService.removeForUser(req.user, 'Account'); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getOtpDisable', + message: 'one-time passwords disabled', + }); res.render('user/otp-disabled'); } catch (error) { - this.log.error('failed to disable OTP service for Account', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getOtpDisable', + message: `failed to disable OTP: ${error.message}`, + data: { error }, + }); res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -367,43 +509,70 @@ class UserController extends SiteController { } async getCoreUserSettingsView (req, res, next) { - const { otpAuth: otpAuthService } = this.dtp.services; + const { + logan: loganService, + otpAuth: otpAuthService, + } = this.dtp.services; try { res.locals.hasOtpAccount = await otpAuthService.isUserProtected(req.user, 'Account'); res.locals.startTab = req.query.st || 'watch'; res.render('user/settings-core'); } catch (error) { - this.log.error('failed to render CoreUser settings view', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getCoreUserSettingsView', + message: `failed to render the view: ${error.message}`, + data: { error }, + }); return next(error); } } async getUserSettingsView (req, res, next) { - const { otpAuth: otpAuthService } = this.dtp.services; + const { + logan: loganService, + otpAuth: otpAuthService, + } = this.dtp.services; try { res.locals.hasOtpAccount = await otpAuthService.isUserProtected(req.user, 'Account'); res.locals.startTab = req.query.st || 'watch'; res.render('user/settings'); } catch (error) { - this.log.error('failed to render user settings view', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getUserSettingsView', + message: `failed to render the view: ${error.message}`, + data: { error }, + }); return next(error); } } async getUserView (req, res, next) { - const { comment: commentService } = this.dtp.services; + const { + comment: commentService, + logan: loganService, + } = this.dtp.services; try { res.locals.pagination = this.getPaginationParameters(req, 20); res.locals.commentHistory = await commentService.getForAuthor(req.user, res.locals.pagination); res.render('user/profile'); } catch (error) { - this.log.error('failed to produce user profile view', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getUserView', + message: `failed to render the view: ${error.message}`, + data: { error }, + }); return next(error); } } async deleteProfilePhoto (req, res) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { const displayList = this.createDisplayList('app-settings'); await userService.removePhoto(req.user); @@ -413,9 +582,19 @@ class UserController extends SiteController { 'bottom-center', 2000, ); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'deleteProfilePhoto', + message: 'profile photo removed', + }); res.status(200).json({ success: true, displayList }); } catch (error) { - this.log.error('failed to remove profile photo', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'deleteProfilePhoto', + message: `failed to remove profile photo: ${error.message}`, + data: { error }, + }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -424,7 +603,10 @@ class UserController extends SiteController { } async deleteHeaderImage (req, res) { - const { user: userService } = this.dtp.services; + const { + logan: loganService, + user: userService, + } = this.dtp.services; try { const displayList = this.createDisplayList('remove-header-image'); await userService.removeHeaderImage(req.user); @@ -434,9 +616,19 @@ class UserController extends SiteController { 'bottom-center', 2000, ); + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'deleteHeaderImage', + message: 'profile header image removed', + }); res.status(200).json({ success: true, displayList }); } catch (error) { - this.log.error('failed to remove header image', { error }); + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'deleteHeaderImage', + message: `failed to remove header image: ${error.message}`, + data: { error }, + }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, @@ -448,5 +640,6 @@ class UserController extends SiteController { module.exports = { slug: 'user', name: 'user', + className: 'UserController', create: async (dtp) => { return new UserController(dtp); }, }; diff --git a/app/controllers/welcome.js b/app/controllers/welcome.js index eb24032..d2d7cec 100644 --- a/app/controllers/welcome.js +++ b/app/controllers/welcome.js @@ -65,20 +65,55 @@ class WelcomeController extends SiteController { } async getSignupView (req, res) { + const { + csrfToken: csrfTokenService, + logan: loganService, + } = this.dtp.services; + req.csrfToken = await csrfTokenService.create(req, { + name: 'user-create', + expiresMinutes: 20, + }); req.session.captcha = req.session.captcha || { }; req.session.captcha.signup = captcha.randomText(4 + Math.floor(Math.random()*4)); + + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getSignupView', + message: 'serving new member signup view', + }); + res.render('welcome/signup'); } async getLoginView (req, res) { + const { logan: loganService } = this.dtp.services; + res.locals.loginResult = req.session.loginResult; + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getLoginView', + message: 'serving member login view', + }); + res.render('welcome/login'); } async getHomeView (req, res, next) { + const { logan: loganService } = this.dtp.services; try { + loganService.sendRequestEvent(module.exports, req, { + level: 'info', + event: 'getHomeView', + message: 'serving the Welcome home page', + }); res.render('welcome/index'); } catch (error) { + loganService.sendRequestEvent(module.exports, req, { + level: 'error', + event: 'getHomeView', + message: `failed to render the view: ${error.message}`, + data: { error }, + }); return next(error); } } @@ -87,5 +122,6 @@ class WelcomeController extends SiteController { module.exports = { slug: 'welcome', name: 'welcome', + className: 'WelcomeController', create: async (dtp) => { return new WelcomeController(dtp); }, }; diff --git a/app/services/announcement.js b/app/services/announcement.js index 786b901..5e7969e 100644 --- a/app/services/announcement.js +++ b/app/services/announcement.js @@ -118,5 +118,6 @@ class AnnouncementService extends SiteService { module.exports = { slug: 'announcement', name: 'announcement', + className: 'AnnouncementService', create: (dtp) => { return new AnnouncementService(dtp); }, }; \ No newline at end of file diff --git a/app/services/attachment.js b/app/services/attachment.js index 327938f..8c086f9 100644 --- a/app/services/attachment.js +++ b/app/services/attachment.js @@ -182,5 +182,6 @@ class AttachmentService extends SiteService { module.exports = { slug: 'attachment', name: 'attachment', + className: 'AttachmentService', create: (dtp) => { return new AttachmentService(dtp); }, }; \ No newline at end of file diff --git a/app/services/cache.js b/app/services/cache.js index 50186f2..418752c 100644 --- a/app/services/cache.js +++ b/app/services/cache.js @@ -59,5 +59,6 @@ class CacheService extends SiteService { module.exports = { slug: 'cache', name: 'cache', + className: 'CacheService', create: (dtp) => { return new CacheService(dtp); }, }; \ No newline at end of file diff --git a/app/services/chat.js b/app/services/chat.js index 112b2b9..c490dea 100644 --- a/app/services/chat.js +++ b/app/services/chat.js @@ -784,10 +784,75 @@ class ChatService extends SiteService { return reaction.toObject(); } + + async removeForUser (user) { + const { logan: loganService } = this.dtp.services; + + let roomCount = 0, messageCount = 0; + await ChatRoom + .find({ owner: user._id }) + .populate(this.populateChatRoom) + .cursor(async (room) => { + try { + await this.deleteRoom(room); + ++roomCount; + } catch (error) { + loganService.sendEvent(module.exports, { + level: 'error', + event: 'removeForUser', + message: `failed to remove chat room: ${error.message}`, + data: { + room: { + _id: room._id, + name: room.name, + }, + error, + }, + }); + // fall through + } + }); + + await ChatMessage + .find({ author: user._id }) + .cursor() + .eachAsync(async (message) => { + try { + await this.removeMessage(message); + ++messageCount; + } catch (error) { + loganService.sendEvent(module.exports, { + level: 'error', + event: 'removeForUser', + message: `failed to remove chat message: ${error.message}`, + data: { + message: { + _id: message._id, + }, + error, + }, + }); + // fall through + } + }); + + loganService.sendEvent(module.exports, { + level: 'info', + event: 'removeForUser', + data: { + user: { + _id: user._id, + username: user.username, + }, + roomCount, messageCount, + }, + }); + } } module.exports = { slug: 'chat', name: 'chat', + className: 'ChatService', create: (dtp) => { return new ChatService(dtp); }, }; \ No newline at end of file diff --git a/app/services/comment.js b/app/services/comment.js index 99ea171..8a3f115 100644 --- a/app/services/comment.js +++ b/app/services/comment.js @@ -320,10 +320,23 @@ class CommentService extends SiteService { await Comment.deleteOne({ _id: comment._id }); }, 4); } + + async removeForAuthor (author) { + await Comment + .find({ // index: 'comment_author_by_type' + authorType: author.type, + author: author._id, + }) + .cursor() + .eachAsync(async (comment) => { + await this.remove(comment); + }); + } } module.exports = { slug: 'comment', name: 'comment', + className: 'CommentService', create: (dtp) => { return new CommentService(dtp); }, }; \ No newline at end of file diff --git a/app/services/content-report.js b/app/services/content-report.js index dddd2d2..3d2d8dc 100644 --- a/app/services/content-report.js +++ b/app/services/content-report.js @@ -117,6 +117,10 @@ class ContentReportService extends SiteService { await ContentReport.deleteMany({ resource: resource._id }); } + async removeForUser (user) { + await ContentReport.deleteMany({ user: user._id }); + } + async removeReport (report) { await ContentReport.deleteOne({ _id: report._id }); } @@ -125,5 +129,6 @@ class ContentReportService extends SiteService { module.exports = { slug: 'content-report', name: 'contentReport', + className: 'ContentReportService', create: (dtp) => { return new ContentReportService(dtp); }, }; \ No newline at end of file diff --git a/app/services/content-vote.js b/app/services/content-vote.js index b384229..c2cdc04 100644 --- a/app/services/content-vote.js +++ b/app/services/content-vote.js @@ -95,10 +95,30 @@ class ContentVoteService extends SiteService { this.log.info('removing all votes for resource', { resourceId: resource._id }); await ContentVote.deleteMany({ resource: resource._id }); } + + async removeForUser (user) { + await ContentVote + .find({ user: user._id }) + .cursor() + .eachAsync(async (vote) => { + const updateOp = { $inc: { } }; + if (vote.vote === 'up') { + updateOp.$inc['resourceStats.upvoteCount'] = -1; + } else { + updateOp.$inc['resourceStats.downvoteCount'] = -1; + } + + const resourceModel = mongoose.model(vote.resourceType); + await resourceModel.updateOne({ _id: vote.resource }, updateOp); + }); + + await ContentVote.deleteMany({ user: user._id }); + } } module.exports = { slug: 'content-vote', name: 'contentVote', + className: 'ContentVoteService', create: (dtp) => { return new ContentVoteService(dtp); }, }; \ No newline at end of file diff --git a/app/services/core-node.js b/app/services/core-node.js index 29bce6b..a4b5776 100644 --- a/app/services/core-node.js +++ b/app/services/core-node.js @@ -805,5 +805,6 @@ class CoreNodeService extends SiteService { module.exports = { slug: 'core-node', name: 'coreNode', + className: 'CoreNodeService', create: (dtp) => { return new CoreNodeService(dtp); }, }; \ No newline at end of file diff --git a/app/services/crypto.js b/app/services/crypto.js index 8e3568c..d65f3bc 100644 --- a/app/services/crypto.js +++ b/app/services/crypto.js @@ -60,5 +60,6 @@ class CryptoService extends SiteService { module.exports = { slug: 'crypto', name: 'crypto', + className: 'CryptoService', create: (dtp) => { return new CryptoService(dtp); }, }; \ No newline at end of file diff --git a/app/services/csrf-token.js b/app/services/csrf-token.js index 4c6b72b..764a50b 100644 --- a/app/services/csrf-token.js +++ b/app/services/csrf-token.js @@ -69,10 +69,15 @@ class CsrfTokenService extends SiteService { csrfToken.name = `csrf-token-${options.name}`; return csrfToken; } + + async removeForUser (user) { + await CsrfToken.deleteMany({ user: user._id }); + } } module.exports = { slug: 'csrf-token', name: 'csrfToken', + className: 'CsrfTokenService', create: (dtp) => { return new CsrfTokenService(dtp); }, }; \ No newline at end of file diff --git a/app/services/dashboard.js b/app/services/dashboard.js index eaaa9ae..738df0a 100644 --- a/app/services/dashboard.js +++ b/app/services/dashboard.js @@ -272,5 +272,6 @@ class DashboardService extends SiteService { module.exports = { slug: 'dashboard', name: 'dashboard', + className: 'DashboardService', create: (dtp) => { return new DashboardService(dtp); }, }; \ No newline at end of file diff --git a/app/services/display-engine.js b/app/services/display-engine.js index 968416d..56296eb 100644 --- a/app/services/display-engine.js +++ b/app/services/display-engine.js @@ -156,5 +156,6 @@ class DisplayEngineService extends SiteService { module.exports = { slug: 'display-engine', name: 'displayEngine', + className: 'DisplayEngineService', create: (dtp) => { return new DisplayEngineService(dtp); }, }; \ No newline at end of file diff --git a/app/services/email.js b/app/services/email.js index 6cfbdcc..19aef2f 100644 --- a/app/services/email.js +++ b/app/services/email.js @@ -173,5 +173,6 @@ class EmailService extends SiteService { module.exports = { slug: 'email', name: 'email', + className: 'EmailService', create: (dtp) => { return new EmailService(dtp); }, }; \ No newline at end of file diff --git a/app/services/feed.js b/app/services/feed.js index ea9a684..9123673 100644 --- a/app/services/feed.js +++ b/app/services/feed.js @@ -178,5 +178,6 @@ class FeedService extends SiteService { module.exports = { slug: 'feed', name: 'feed', + className: 'FeedService', create: (dtp) => { return new FeedService(dtp); }, }; \ No newline at end of file diff --git a/app/services/hive.js b/app/services/hive.js index b6366a7..993899b 100644 --- a/app/services/hive.js +++ b/app/services/hive.js @@ -332,5 +332,6 @@ class HiveService extends SiteService { module.exports = { slug: 'hive', name: 'hive', + className: 'HiveService', create: (dtp) => { return new HiveService(dtp); }, }; \ No newline at end of file diff --git a/app/services/host-cache.js b/app/services/host-cache.js index f4eb246..08339f5 100644 --- a/app/services/host-cache.js +++ b/app/services/host-cache.js @@ -99,5 +99,6 @@ class HostCacheService extends SiteService { module.exports = { slug: 'host-cache', name: 'hostCache', + className: 'HostCacheService', create: (dtp) => { return new HostCacheService(dtp); }, }; \ No newline at end of file diff --git a/app/services/image.js b/app/services/image.js index 4547483..23eeab0 100644 --- a/app/services/image.js +++ b/app/services/image.js @@ -301,5 +301,6 @@ class ImageService extends SiteService { module.exports = { slug: 'image', name: 'image', + className: 'ImageService', create: (dtp) => { return new ImageService(dtp); }, }; \ No newline at end of file diff --git a/app/services/job-queue.js b/app/services/job-queue.js index f59510b..b710bff 100644 --- a/app/services/job-queue.js +++ b/app/services/job-queue.js @@ -64,5 +64,6 @@ class JobQueueService extends SiteService { module.exports = { slug: 'job-queue', name: 'jobQueue', + className: 'JobQueueService', create: (dtp) => { return new JobQueueService(dtp); }, }; \ No newline at end of file diff --git a/app/services/limiter.js b/app/services/limiter.js index b7a8259..8b79db8 100644 --- a/app/services/limiter.js +++ b/app/services/limiter.js @@ -71,5 +71,6 @@ class LimiterService extends SiteService { module.exports = { slug: 'limiter', name: 'limiter', + className: 'LimiterService', create: (dtp) => { return new LimiterService(dtp); }, }; \ No newline at end of file diff --git a/app/services/log.js b/app/services/log.js index 661d22f..e03c501 100644 --- a/app/services/log.js +++ b/app/services/log.js @@ -40,5 +40,6 @@ class SystemLogService extends SiteService { module.exports = { slug: 'log', name: 'log', + className: 'SystemLogService', create: (dtp) => { return new SystemLogService(dtp); }, }; \ No newline at end of file diff --git a/app/services/logan.js b/app/services/logan.js new file mode 100644 index 0000000..94e78ef --- /dev/null +++ b/app/services/logan.js @@ -0,0 +1,79 @@ +// logan.js +// Copyright (C) 2023 DTP Technologies, LLC +// License: Apache-2.0 + +'use strict'; + +const os = require('os'); + +const { SiteService, SiteError } = require('../../lib/site-lib'); + +class LoganService extends SiteService { + + constructor (dtp) { + super(dtp, module.exports); + } + + async start ( ) { + await super.start(); + } + + async sendRequestEvent (component, req, event) { + if (process.env.DTP_LOGAN !== 'enabled') { + return; + } + if (req.user) { + event.data = event.data || { }; + event.data.user = { + _id: req.user._id, + username: req.user.username, + }; + } + event.ip = req.ip; + return this.sendEvent(component, event); + } + + async sendEvent (component, event) { + if (process.env.DTP_LOGAN !== 'enabled') { + return; + } + try { + const loganScheme = process.env.DTP_LOGAN_SCHEME || 'http'; + const loganUrl = `${loganScheme}://${process.env.DTP_LOGAN_HOST}/api/event`; + + event.host = os.hostname(); + event['component.slug'] = component.slug; + event['component.name'] = component.className || component.name; + + this.log[event.level]('sending Logan event', { event }); + + const payload = JSON.stringify(event); + const response = await fetch(loganUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': payload.length, + 'X-LogAn-Auth': process.env.DTP_LOGAN_API_KEY, + }, + body: payload, + }); + + const json = await response.json(); + if (!json.success) { + throw new SiteError(500, json.message); + } + + return json; + } catch (error) { + this.log.error('failed to send LOGAN event', { event, error }); + // fall through + } + } +} + +module.exports = { + slug: 'logan', + name: 'logan', + className: 'LoganService', + create: (dtp) => { return new LoganService(dtp); }, +}; \ No newline at end of file diff --git a/app/services/markdown.js b/app/services/markdown.js index e7a5462..ee80cbf 100644 --- a/app/services/markdown.js +++ b/app/services/markdown.js @@ -34,5 +34,6 @@ class MarkdownService extends SiteService { module.exports = { slug: 'markdown', name: 'markdown', + className: 'MarkdownService', create: (dtp) => { return new MarkdownService(dtp); }, }; \ No newline at end of file diff --git a/app/services/media.js b/app/services/media.js index 0a58952..24cd7e1 100644 --- a/app/services/media.js +++ b/app/services/media.js @@ -96,5 +96,6 @@ class MediaService extends SiteService { module.exports = { slug: 'media', name: 'media', + className: 'MediaService', create: (dtp) => { return new MediaService(dtp); }, }; \ No newline at end of file diff --git a/app/services/minio.js b/app/services/minio.js index 79d09ae..d386cb2 100644 --- a/app/services/minio.js +++ b/app/services/minio.js @@ -100,5 +100,6 @@ class MinioService extends SiteService { module.exports = { slug: 'minio', name: 'minio', + className: 'MinioService', create: (dtp) => { return new MinioService(dtp); }, }; diff --git a/app/services/newsletter.js b/app/services/newsletter.js index efcbe1f..1010718 100644 --- a/app/services/newsletter.js +++ b/app/services/newsletter.js @@ -119,5 +119,6 @@ class NewsletterService extends SiteService { module.exports = { slug: 'newsletter', name: 'newsletter', + className: 'NewsletterService', create: (dtp) => { return new NewsletterService(dtp); }, }; diff --git a/app/services/oauth2.js b/app/services/oauth2.js index 4608d05..ae68dde 100644 --- a/app/services/oauth2.js +++ b/app/services/oauth2.js @@ -476,5 +476,6 @@ class OAuth2Service extends SiteService { module.exports = { slug: 'oauth2', name: 'oauth2', + className: 'OAuth2Service', create: (dtp) => { return new OAuth2Service(dtp); }, }; \ No newline at end of file diff --git a/app/services/otp-auth.js b/app/services/otp-auth.js index 69cfbe2..3369ceb 100644 --- a/app/services/otp-auth.js +++ b/app/services/otp-auth.js @@ -223,7 +223,10 @@ class OtpAuthService extends SiteService { } async removeForUser (user, serviceName) { - return await OtpAccount.findOneAndDelete({ user: user, service: serviceName }); + if (serviceName) { + return OtpAccount.findOneAndDelete({ user: user._id, service: serviceName }); + } + await OtpAccount.deleteMany({ user: user._id }); } async getBackupTokens (user, serviceName) { @@ -237,5 +240,6 @@ class OtpAuthService extends SiteService { module.exports = { slug: 'otp-auth', name: 'otpAuth', + className: 'OtpAuthService', create: (dtp) => { return new OtpAuthService(dtp); }, }; diff --git a/app/services/phone.js b/app/services/phone.js index 26bd321..923c1c1 100644 --- a/app/services/phone.js +++ b/app/services/phone.js @@ -57,5 +57,6 @@ class PhoneService extends SiteService { module.exports = { slug: 'phone', name: 'phone', + className: 'PhoneService', create: (dtp) => { return new PhoneService(dtp); }, }; \ No newline at end of file diff --git a/app/services/resource.js b/app/services/resource.js index a4f6d84..80ce1e0 100644 --- a/app/services/resource.js +++ b/app/services/resource.js @@ -120,5 +120,6 @@ class ResourceService extends SiteService { module.exports = { slug: 'resource', name: 'resource', + className: 'ResourceService', create: (dtp) => { return new ResourceService(dtp); }, }; diff --git a/app/services/session.js b/app/services/session.js index 5b89b66..d4a8cd3 100644 --- a/app/services/session.js +++ b/app/services/session.js @@ -99,7 +99,7 @@ class SessionService extends SiteService { break; } if (user && user.permissions && !user.permissions.canLogin) { - return done(null, null); // quietly destroys any login session they might have + return done(null, null); // destroy the session } return done(null, user); } catch (error) { @@ -112,5 +112,6 @@ class SessionService extends SiteService { module.exports = { slug: 'session', name: 'session', + className: 'SessionService', create: (dtp) => { return new SessionService(dtp); }, }; \ No newline at end of file diff --git a/app/services/sms.js b/app/services/sms.js index 10abfba..93c26c3 100644 --- a/app/services/sms.js +++ b/app/services/sms.js @@ -50,5 +50,6 @@ class SmsService extends SiteService { module.exports = { slug: 'sms', name: 'sms', + className: 'SmsService', create: (dtp) => { return new SmsService(dtp); }, }; \ No newline at end of file diff --git a/app/services/sticker.js b/app/services/sticker.js index 9f37bf7..581bb58 100644 --- a/app/services/sticker.js +++ b/app/services/sticker.js @@ -162,6 +162,16 @@ class StickerService extends SiteService { await this.queue.add('sticker-delete', { stickerId }); } + async removeForUser (user) { + await Sticker + .find({ ownerType: user.type, owner: user._id }) + .populate(this.populateSticker) + .cursor() + .eachAsync(async (sticker) => { + await this.removeSticker(sticker); + }); + } + async render (sticker, stickerOptions) { return this.stickerTemplate({ sticker, stickerOptions }); } @@ -200,5 +210,6 @@ class StickerService extends SiteService { module.exports = { slug: 'sticker', name: 'sticker', + className: 'StickerService', create: (dtp) => { return new StickerService(dtp); }, }; \ No newline at end of file diff --git a/app/services/user-notification.js b/app/services/user-notification.js index 9c1a4db..3b7c100 100644 --- a/app/services/user-notification.js +++ b/app/services/user-notification.js @@ -128,10 +128,15 @@ class UserNotificationService extends SiteService { .lean(); return notification; } + + async removeForUser (user) { + await UserNotification.deleteMany({ user: user._id }); + } } module.exports = { name: 'userNotification', slug: 'user-notification', + className: 'UserNotificationService', create: (dtp) => { return new UserNotificationService(dtp); }, }; \ No newline at end of file diff --git a/app/services/user.js b/app/services/user.js index 14210cf..28c1dea 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -778,10 +778,49 @@ class UserService extends SiteService { }, ); } + + async ban (user) { + const { + chat: chatService, + comment: commentService, + contentReport: contentReportService, + csrfToken: csrfTokenService, + otpAuth: otpAuthService, + sticker: stickerService, + userNotification: userNotificationService, + } = this.dtp.services; + + const userModel = mongoose.model(user.type); + await userModel.updateOne( + { _id: user._id }, + { + $set: { + 'flags.isAdmin': false, + 'flags.isModerator': false, + 'flags.isEmailVerified': false, + 'permissions.canLogin': false, + 'permissions.canChat': false, + 'permissions.canComment': false, + 'permissions.canReport': false, + 'optIn.system': false, + 'optIn.marketing': false, + }, + }, + ); + + await chatService.removeForUser(user); + await commentService.removeForAuthor(user); + await contentReportService.removeForUser(user); + await csrfTokenService.removeForUser(user); + await otpAuthService.removeForUser(user); + await stickerService.removeForUser(user); + await userNotificationService.removeForUser(user); + } } module.exports = { slug: 'user', name: 'user', + className: 'UserService', create: (dtp) => { return new UserService(dtp); }, }; \ No newline at end of file