integrating latest base and Logan; and user ban service

master
rob 1 year ago
parent 4dfa167d53
commit d5eb929cc2

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -160,5 +160,6 @@ class NewsroomAdminController extends SiteController {
module.exports = {
name: 'newsroomAdmin',
slug: 'newsroom-admin',
className: 'NewsroomAdminController',
create: async (dtp) => { return new NewsroomAdminController(dtp); },
};

@ -51,5 +51,6 @@ class OtpAdminController extends SiteController {
module.exports = {
name: 'adminOtp',
slug: 'admin-opt',
className: 'OtpAdminController',
create: async (dtp) => { return new OtpAdminController(dtp); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};
};

@ -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); },
};

@ -153,5 +153,6 @@ class CommentController extends SiteController {
module.exports = {
slug: 'comment',
name: 'comment',
className: 'CommentController',
create: async (dtp) => { return new CommentController(dtp); },
};

@ -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); },
};

@ -66,5 +66,6 @@ class FormController extends SiteController {
module.exports = {
slug: 'form',
name: 'form',
className: 'FormController',
create: async (dtp) => { return new FormController(dtp); },
};

@ -59,5 +59,6 @@ class HiveController extends SiteController {
module.exports = {
slug: 'hive',
name: 'hive',
className: 'HiveController',
create: async (dtp) => { return new HiveController(dtp); },
};

@ -95,5 +95,6 @@ class HiveKaleidoscopeController extends SiteController {
module.exports = {
name: 'hiveKaleidoscope',
slug: 'hive-kaleidoscope',
className: 'HiveKaleidoscopeController',
create: async (dtp) => { return new HiveKaleidoscopeController(dtp); },
};

@ -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); },
};

@ -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); },
};

@ -134,5 +134,6 @@ class ImageController extends SiteController {
module.exports = {
slug: 'image',
name: 'image',
className: 'ImageController',
create: async (dtp) => { return new ImageController(dtp); },
};

@ -66,5 +66,6 @@ class ManifestController extends SiteController {
module.exports = {
slug: 'manifest',
name: 'manifest',
className: 'ManifestController',
create: async (dtp) => { return new ManifestController(dtp); },
};

@ -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); },
};

@ -81,5 +81,6 @@ class NewsroomController extends SiteController {
module.exports = {
slug: 'newsroom',
name: 'newsroom',
className: 'NewsroomController',
create: (dtp) => { return new NewsroomController(dtp); },
};

@ -74,5 +74,6 @@ class NotificationController extends SiteController {
module.exports = {
slug: 'notification',
name: 'notification',
className: 'NotificationController',
create: async (dtp) => { return new NotificationController(dtp); },
};

@ -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); },
};

@ -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); },
};

@ -118,5 +118,6 @@ class AnnouncementService extends SiteService {
module.exports = {
slug: 'announcement',
name: 'announcement',
className: 'AnnouncementService',
create: (dtp) => { return new AnnouncementService(dtp); },
};

@ -182,5 +182,6 @@ class AttachmentService extends SiteService {
module.exports = {
slug: 'attachment',
name: 'attachment',
className: 'AttachmentService',
create: (dtp) => { return new AttachmentService(dtp); },
};

@ -59,5 +59,6 @@ class CacheService extends SiteService {
module.exports = {
slug: 'cache',
name: 'cache',
className: 'CacheService',
create: (dtp) => { return new CacheService(dtp); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};

@ -805,5 +805,6 @@ class CoreNodeService extends SiteService {
module.exports = {
slug: 'core-node',
name: 'coreNode',
className: 'CoreNodeService',
create: (dtp) => { return new CoreNodeService(dtp); },
};

@ -60,5 +60,6 @@ class CryptoService extends SiteService {
module.exports = {
slug: 'crypto',
name: 'crypto',
className: 'CryptoService',
create: (dtp) => { return new CryptoService(dtp); },
};

@ -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); },
};

@ -272,5 +272,6 @@ class DashboardService extends SiteService {
module.exports = {
slug: 'dashboard',
name: 'dashboard',
className: 'DashboardService',
create: (dtp) => { return new DashboardService(dtp); },
};

@ -156,5 +156,6 @@ class DisplayEngineService extends SiteService {
module.exports = {
slug: 'display-engine',
name: 'displayEngine',
className: 'DisplayEngineService',
create: (dtp) => { return new DisplayEngineService(dtp); },
};

@ -173,5 +173,6 @@ class EmailService extends SiteService {
module.exports = {
slug: 'email',
name: 'email',
className: 'EmailService',
create: (dtp) => { return new EmailService(dtp); },
};

@ -178,5 +178,6 @@ class FeedService extends SiteService {
module.exports = {
slug: 'feed',
name: 'feed',
className: 'FeedService',
create: (dtp) => { return new FeedService(dtp); },
};

@ -332,5 +332,6 @@ class HiveService extends SiteService {
module.exports = {
slug: 'hive',
name: 'hive',
className: 'HiveService',
create: (dtp) => { return new HiveService(dtp); },
};

@ -99,5 +99,6 @@ class HostCacheService extends SiteService {
module.exports = {
slug: 'host-cache',
name: 'hostCache',
className: 'HostCacheService',
create: (dtp) => { return new HostCacheService(dtp); },
};

@ -301,5 +301,6 @@ class ImageService extends SiteService {
module.exports = {
slug: 'image',
name: 'image',
className: 'ImageService',
create: (dtp) => { return new ImageService(dtp); },
};

@ -64,5 +64,6 @@ class JobQueueService extends SiteService {
module.exports = {
slug: 'job-queue',
name: 'jobQueue',
className: 'JobQueueService',
create: (dtp) => { return new JobQueueService(dtp); },
};

@ -71,5 +71,6 @@ class LimiterService extends SiteService {
module.exports = {
slug: 'limiter',
name: 'limiter',
className: 'LimiterService',
create: (dtp) => { return new LimiterService(dtp); },
};

@ -40,5 +40,6 @@ class SystemLogService extends SiteService {
module.exports = {
slug: 'log',
name: 'log',
className: 'SystemLogService',
create: (dtp) => { return new SystemLogService(dtp); },
};

@ -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); },
};

@ -34,5 +34,6 @@ class MarkdownService extends SiteService {
module.exports = {
slug: 'markdown',
name: 'markdown',
className: 'MarkdownService',
create: (dtp) => { return new MarkdownService(dtp); },
};

@ -96,5 +96,6 @@ class MediaService extends SiteService {
module.exports = {
slug: 'media',
name: 'media',
className: 'MediaService',
create: (dtp) => { return new MediaService(dtp); },
};

@ -100,5 +100,6 @@ class MinioService extends SiteService {
module.exports = {
slug: 'minio',
name: 'minio',
className: 'MinioService',
create: (dtp) => { return new MinioService(dtp); },
};

@ -119,5 +119,6 @@ class NewsletterService extends SiteService {
module.exports = {
slug: 'newsletter',
name: 'newsletter',
className: 'NewsletterService',
create: (dtp) => { return new NewsletterService(dtp); },
};

@ -476,5 +476,6 @@ class OAuth2Service extends SiteService {
module.exports = {
slug: 'oauth2',
name: 'oauth2',
className: 'OAuth2Service',
create: (dtp) => { return new OAuth2Service(dtp); },
};

@ -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); },
};

@ -57,5 +57,6 @@ class PhoneService extends SiteService {
module.exports = {
slug: 'phone',
name: 'phone',
className: 'PhoneService',
create: (dtp) => { return new PhoneService(dtp); },
};

@ -120,5 +120,6 @@ class ResourceService extends SiteService {
module.exports = {
slug: 'resource',
name: 'resource',
className: 'ResourceService',
create: (dtp) => { return new ResourceService(dtp); },
};

@ -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); },
};

@ -50,5 +50,6 @@ class SmsService extends SiteService {
module.exports = {
slug: 'sms',
name: 'sms',
className: 'SmsService',
create: (dtp) => { return new SmsService(dtp); },
};

@ -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); },
};

@ -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); },
};

@ -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); },
};
Loading…
Cancel
Save