From c7f58dc8f2ab06099e4829e355b0c32c289aa995 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 2 Mar 2023 22:17:49 -0600 Subject: [PATCH 01/16] add explict authentication when running in production --- .env.default | 4 ++++ lib/site-platform.js | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.env.default b/.env.default index 4286e79..c40424f 100644 --- a/.env.default +++ b/.env.default @@ -62,6 +62,10 @@ MAILGUN_DOMAIN= MONGODB_HOST=localhost:27017 MONGODB_DATABASE=dtp-webapp +# For when NODE_ENV=production +MONGODB_USERNAME=mongo-user +MONGODB_PASSWORD=change-me! +MONGODB_OPTIONS= # # Redis configuration diff --git a/lib/site-platform.js b/lib/site-platform.js index 7847d65..8bb8994 100644 --- a/lib/site-platform.js +++ b/lib/site-platform.js @@ -34,12 +34,22 @@ module.connectDatabase = async (/*dtp*/) => { host: process.env.MONGODB_HOST, database: process.env.MONGODB_DATABASE, }); - const mongoConnectUri = `mongodb://${process.env.MONGODB_HOST}/${process.env.MONGODB_DATABASE}`; + const mongoConnectionInfo = { + host: process.env.MONGODB_HOST, + db: process.env.MONGODB_DATABASE, + username: encodeURIComponent(process.env.MONGODB_USERNAME), + password: encodeURIComponent(process.env.MONGODB_PASSWORD), + options: process.env.MONGODB_OPTIONS || '', + }; + let mongoConnectUri = `mongodb://${process.env.MONGODB_HOST}/${process.env.MONGODB_DATABASE}`; + if (process.env.NODE_ENV === 'production'){ + mongoConnectUri = `mongodb://${mongoConnectionInfo.username}:${mongoConnectionInfo.password}@${mongoConnectionInfo.host}/${mongoConnectionInfo.options}`; + } module.db = await mongoose.connect(mongoConnectUri, { socketTimeoutMS: 0, keepAlive: true, keepAliveInitialDelay: 300000, - dbName: process.env.MONGODB_DATABASE, + dbName: mongoConnectionInfo.db, }); module.log.info('connected to MongoDB'); } catch (error) { From 98853ced334ce23608ff32237c220de757144aa0 Mon Sep 17 00:00:00 2001 From: rob Date: Mon, 22 May 2023 17:05:02 -0400 Subject: [PATCH 02/16] Core vs. Local user updates (there will be more) --- README.md | 9 ++ app/controllers/admin/user.js | 2 +- app/controllers/hive/user.js | 2 +- app/controllers/user.js | 99 +++++++++---- app/models/user.js | 7 +- app/services/chat.js | 15 +- app/services/session.js | 3 +- app/services/user.js | 213 ++++++++++++++++++---------- app/views/admin/core-user/form.pug | 2 +- app/views/admin/user/form.pug | 2 +- app/views/components/navbar.pug | 2 +- app/views/components/off-canvas.pug | 2 +- lib/site-ioserver.js | 10 +- 13 files changed, 245 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index cf9c353..b052c3f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ The base project from which all others are forked when working with my framework and system. You don't have to start from this project at all. BUT, it can save you a lot of time by simply being a 100% compatible base on which you can build your apps and sites. +## Host Preparation +The following commands must be exeucted on any host expected to run DTP Framework applications. + +```sh +apt -y update && apt -y upgrade +apt -y install linux-headers-generic linux-headers-virtual linux-image-virtual linux-virtual +apt -y install build-essential ffmpeg supervisor +``` + ## Install Data Tier Components You will need MongoDB and MinIO installed and running before you can start DTP Base web services. diff --git a/app/controllers/admin/user.js b/app/controllers/admin/user.js index 214431c..eb471d4 100644 --- a/app/controllers/admin/user.js +++ b/app/controllers/admin/user.js @@ -68,7 +68,7 @@ class UserController extends SiteController { const { user: userService } = this.dtp.services; try { res.locals.pagination = this.getPaginationParameters(req, 10); - res.locals.userAccounts = await userService.getUserAccounts(res.locals.pagination, req.query.u); + res.locals.userAccounts = await userService.searchLocalUserAccounts(res.locals.pagination, req.query.u); res.locals.totalUserCount = await userService.getTotalCount(); res.render('admin/user/index'); } catch (error) { diff --git a/app/controllers/hive/user.js b/app/controllers/hive/user.js index 7bee4e9..fa19802 100644 --- a/app/controllers/hive/user.js +++ b/app/controllers/hive/user.js @@ -78,7 +78,7 @@ class HiveUserController extends SiteController { throw new SiteError(406, 'Must include search term'); } - res.locals.q = await userService.filterUsername(req.query.q); + res.locals.q = userService.filterUsername(req.query.q); res.locals.pagination = this.getPaginationParameters(req, 20); res.locals.userProfiles = await userService.getUserAccounts(res.locals.pagination, res.locals.q); res.locals.userProfiles = res.locals.userProfiles.map((user) => { diff --git a/app/controllers/user.js b/app/controllers/user.js index dbdbeaa..8dffd71 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -60,8 +60,10 @@ class UserController extends SiteController { return next(); } - router.param('username', this.populateUsername.bind(this)); - router.param('userId', this.populateUserId.bind(this)); + router.param('localUsername', this.populateLocalUsername.bind(this)); + router.param('coreUsername', this.populateCoreUsername.bind(this)); + + router.param('localUserId', this.populateLocalUserId.bind(this)); router.param('coreUserId', this.populateCoreUserId.bind(this)); router.post( @@ -73,7 +75,7 @@ class UserController extends SiteController { ); router.post( - '/:userId/profile-photo', + '/:localUserId/profile-photo', limiterService.createMiddleware(limiterService.config.user.postProfilePhoto), checkProfileOwner, upload.single('imageFile'), @@ -81,7 +83,7 @@ class UserController extends SiteController { ); router.post( - '/:userId/settings', + '/:localUserId/settings', limiterService.createMiddleware(limiterService.config.user.postUpdateSettings), checkProfileOwner, upload.none(), @@ -124,7 +126,7 @@ class UserController extends SiteController { ); router.get( - '/:userId/settings', + '/:localUsername/settings', limiterService.createMiddleware(limiterService.config.user.getSettings), authRequired, otpMiddleware, @@ -132,7 +134,7 @@ class UserController extends SiteController { this.getUserSettingsView.bind(this), ); router.get( - '/:username', + '/:localUsername', limiterService.createMiddleware(limiterService.config.user.getUserProfile), authRequired, otpMiddleware, @@ -148,48 +150,84 @@ class UserController extends SiteController { ); } - async populateUsername (req, res, next, username) { + async populateCoreUsername (req, res, next, coreUsername) { const { user: userService } = this.dtp.services; try { - res.locals.userProfile = await userService.getPublicProfile('User', username); - if (!res.locals.userProfile) { - throw new SiteError(404, 'Member not found'); + res.locals.username = userService.filterUsername(coreUsername); + res.locals.userProfileId = await userService.getCoreUserId(res.locals.username); + if (!res.locals.userProfileId) { + throw new SiteError(404, 'Core member not found'); } - return next(); + // manually chain over to the ID parameter resolver + return this.populateCoreUserId(req, res, next, res.locals.userProfileId); } catch (error) { - this.log.error('failed to populate username with public profile', { username, error }); + this.log.error('failed to populate core username', { coreUsername, error }); return next(error); } } - async populateUserId (req, res, next, userId) { + async populateCoreUserId (req, res, next, coreUserId) { const { user: userService } = this.dtp.services; try { - userId = mongoose.Types.ObjectId(userId); - } catch (error) { - return next(new SiteError(406, 'Invalid User')); - } - try { - res.locals.userProfile = await userService.getUserAccount(userId); + res.locals.userProfileId = mongoose.Types.ObjectId(coreUserId); + + if (req.user && (req.user.type === 'CoreUser') && req.user._id.equals(res.locals.userProfileId)) { + res.locals.userProfile = await userService.getCoreUserAccount(res.locals.userProfileId); + } else { + res.locals.userProfile = await userService.getCoreUserProfile(res.locals.userProfileId); + } + + if (!res.locals.userProfile) { + throw new SiteError(404, 'Core member not found'); + } + return next(); } catch (error) { - this.log.error('failed to populate userId', { userId, error }); + this.log.error('failed to populate core user id', { coreUserId, error }); return next(error); } } - async populateCoreUserId (req, res, next, coreUserId) { - const { coreNode: coreNodeService } = this.dtp.services; + async populateLocalUsername (req, res, next, username) { + const { user: userService } = this.dtp.services; try { - coreUserId = mongoose.Types.ObjectId(coreUserId); + res.locals.username = userService.filterUsername(username); + + res.locals.userProfileId = await userService.getLocalUserId(res.locals.username); + if (!res.locals.userProfileId) { + throw new SiteError(404, 'Local member not found'); + } + + if (req.user && (req.user.type === 'User') && req.user._id.equals(res.locals.userProfileId)) { + res.locals.userProfile = await userService.getLocalUserAccount(res.locals.userProfileId); + } else { + res.locals.userProfile = await userService.getLocalUserProfile(res.locals.userProfileId); + } + return next(); } catch (error) { - return next(new SiteError(406, 'Invalid User')); + this.log.error('failed to populate local username', { username, error }); + return next(error); } + } + + async populateLocalUserId (req, res, next, userId) { + const { user: userService } = this.dtp.services; try { - res.locals.userProfile = await coreNodeService.getUserByLocalId(coreUserId); + res.locals.userProfileId = mongoose.Types.ObjectId(userId); + + if (req.user && (req.user.type === 'User') && req.user._id.equals(res.locals.userProfileId)) { + res.locals.userProfile = await userService.getLocalUserAccount(res.locals.userProfileId); + } else { + res.locals.userProfile = await userService.getLocalUserProfile(res.locals.userProfileId); + } + + if (!res.locals.userProfile) { + throw new SiteError(404, 'Local member not found'); + } + return next(); } catch (error) { - this.log.error('failed to populate coreUserId', { coreUserId, error }); + this.log.error('failed to populate local user id', { userId, error }); return next(error); } } @@ -229,8 +267,9 @@ class UserController extends SiteController { async postProfilePhoto (req, res) { const { user: userService } = this.dtp.services; try { - const displayList = this.createDisplayList('profile-photo'); await userService.updatePhoto(req.user, req.file); + + const displayList = this.createDisplayList('profile-photo'); displayList.showNotification( 'Profile photo updated successfully.', 'success', @@ -250,8 +289,9 @@ class UserController extends SiteController { async postHeaderImage (req, res) { const { user: userService } = this.dtp.services; try { - const displayList = this.createDisplayList('header-image'); await userService.updateHeaderImage(req.user, req.file); + + const displayList = this.createDisplayList('header-image'); displayList.showNotification( 'Header image updated successfully.', 'success', @@ -271,10 +311,9 @@ class UserController extends SiteController { async postUpdateCoreSettings (req, res) { const { coreNode: coreNodeService } = this.dtp.services; try { - const displayList = this.createDisplayList('app-settings'); - await coreNodeService.updateUserSettings(req.user, req.body); + const displayList = this.createDisplayList('app-settings'); displayList.reload(); res.status(200).json({ success: true, displayList }); } catch (error) { diff --git a/app/models/user.js b/app/models/user.js index adb39a9..897e571 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -22,17 +22,18 @@ const { const UserSchema = new Schema({ created: { type: Date, default: Date.now, required: true, index: -1 }, - email: { type: String, required: true, lowercase: true, unique: true }, + email: { type: String, required: true, lowercase: true, unique: true, select: false }, username: { type: String, required: true }, username_lc: { type: String, required: true, lowercase: true, unique: true, index: 1 }, - passwordSalt: { type: String, required: true }, - password: { type: String, required: true }, + passwordSalt: { type: String, required: true, select: false }, + password: { type: String, required: true, select: false }, displayName: { type: String }, bio: { type: String, maxlength: 300 }, picture: { large: { type: Schema.ObjectId, ref: 'Image' }, small: { type: Schema.ObjectId, ref: 'Image' }, }, + header: { type: Schema.ObjectId, ref: 'Image' }, badges: { type: [String] }, flags: { type: UserFlagsSchema, select: false }, permissions: { type: UserPermissionsSchema, select: false }, diff --git a/app/services/chat.js b/app/services/chat.js index 8a66e26..112b2b9 100644 --- a/app/services/chat.js +++ b/app/services/chat.js @@ -473,7 +473,12 @@ class ChatService extends SiteService { async createMessage (author, messageDefinition) { const { sticker: stickerService, user: userService } = this.dtp.services; - author = await userService.getUserAccount(author._id); + this.log.alert('user record', { author }); + if (author.type === 'User') { + author = await userService.getLocalUserAccount(author._id); + } else { + author = await userService.getCoreUserAccount(author._id); + } if (!author || !author.permissions || !author.permissions.canChat) { throw new SiteError(403, `You are not permitted to chat at all on ${this.dtp.config.site.name}`); } @@ -744,7 +749,13 @@ class ChatService extends SiteService { const { user: userService } = this.dtp.services; const NOW = new Date(); - const userCheck = await userService.getUserAccount(user._id); + let userCheck; + if (user.type === '') { + userCheck = await userService.getLocalUserAccount(user._id); + } else { + userCheck = await userService.getCoreUserAccount(user._id); + } + if (!userCheck || !userCheck.permissions || !userCheck.permissions.canChat) { throw new SiteError(403, 'You are not permitted to chat'); } diff --git a/app/services/session.js b/app/services/session.js index 177878e..016a90e 100644 --- a/app/services/session.js +++ b/app/services/session.js @@ -92,8 +92,9 @@ class SessionService extends SiteService { delete user.stats._id; delete user.optIn._id; break; + case 'local': - user = await userService.getUserAccount(userId); + user = await userService.getLocalUserAccount(userId); user.type = 'User'; break; } diff --git a/app/services/user.js b/app/services/user.js index d3f83ea..9c4e63d 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -20,6 +20,12 @@ const uuidv4 = require('uuid').v4; const { SiteError, SiteService } = require('../../lib/site-lib'); +/* + * The entire concept of "get a user" is in flux right now. It's best to just + * ignore what's happening in this service right now, and focus on other + * features in the sytem. + */ + class UserService extends SiteService { constructor (dtp) { @@ -174,7 +180,7 @@ class UserService extends SiteService { async emailOptOut (userId, category) { userId = mongoose.Types.ObjectId(userId); - const user = await this.getUserAccount(userId); + const user = await this.getLocalUserAccount(userId); if (!user) { throw new SiteError(406, 'Invalid opt-out token'); } @@ -199,7 +205,6 @@ class UserService extends SiteService { throw SiteError(403, 'Invalid user account operation'); } - // strip characters we don't want to allow in username userDefinition.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); const username_lc = userDefinition.username.toLowerCase(); @@ -221,7 +226,6 @@ class UserService extends SiteService { } async updateForAdmin (user, userDefinition) { - // strip characters we don't want to allow in username userDefinition.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); const username_lc = userDefinition.username.toLowerCase(); @@ -265,7 +269,6 @@ class UserService extends SiteService { const updateOp = { $set: { }, $unset: { } }; - // strip characters we don't want to allow in username updateOp.$set.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); if (!updateOp.$set.username || (updateOp.$set.username.length === 0)) { throw new SiteError(400, 'Must include a username'); @@ -303,7 +306,7 @@ class UserService extends SiteService { }, options); const accountEmail = account.username.trim().toLowerCase(); - const accountUsername = await this.filterUsername(accountEmail); + const accountUsername = this.filterUsername(accountEmail); this.log.debug('locating user record', { accountEmail, accountUsername }); const user = await User @@ -393,28 +396,23 @@ class UserService extends SiteService { ); } - filterUserObject (user) { - const filteredUser = { - _id: user._id, - created: user.created, - displayName: user.displayName, - username: user.username, - username_lc: user.username_lc, - bio: user.bio, - flags: user.flags, - permissions: user.permissions, - picture: user.picture, - }; - if (filteredUser.flags && filteredUser.flags._id) { - delete filteredUser.flags._id; + async getLocalUserId (username) { + const user = await User.findOne({ username_lc: username }).select('_id').lean(); + if (!user) { + return; // undefined } - if (filteredUser.permissions && filteredUser.permissions._id) { - delete filteredUser.permissions._id; + return user._id; + } + + async getCoreUserId (username) { + const user = await CoreUser.findOne({ username_lc: username }).select('_id').lean(); + if (!user) { + return; // undefined } - return filteredUser; + return user._id; } - async getUserAccount (userId) { + async getLocalUserAccount (userId) { const user = await User .findById(userId) .select('+email +flags +permissions +optIn +picture') @@ -427,11 +425,47 @@ class UserService extends SiteService { return user; } - async getUserAccounts (pagination, username) { + async getCoreUserAccount (userId) { + const user = await User + .findById(userId) + .select('+email +flags +permissions +optIn +picture') + .populate(this.populateUser) + .lean(); + if (!user) { + throw new SiteError(404, 'Core member account not found'); + } + user.type = 'CoreUser'; + return user; + } + + async getLocalUserProfile (userId) { + const user = await User + .findById(userId) + .select('+email +flags +settings') + .populate(this.populateUser) + .lean(); + user.type = 'User'; + return user; + } + + async getCoreUserProfile (userId) { + const user = await CoreUser + .findById(userId) + .select('+core +flags +settings') + .populate(this.populateUser) + .lean(); + user.type = 'CoreUser'; + return user; + } + + async searchLocalUserAccounts (pagination, username) { let search = { }; + if (username) { + username = this.filterUsername(username); search.username_lc = { $regex: `^${username.toLowerCase().trim()}` }; } + const users = await User .find(search) .sort({ username_lc: 1 }) @@ -444,60 +478,24 @@ class UserService extends SiteService { return users.map((user) => { user.type = 'User'; return user; }); } - async getUserProfile (userId) { - let user; - try { - userId = mongoose.Types.ObjectId(userId); // will throw if invalid format - user = User.findById(userId); - } catch (error) { - user = User.findOne({ username: userId }); - } - user = await user - .select('+email +flags +settings') - .populate(this.populateUser) - .lean(); - return user; - } - - async getPublicProfile (type, username) { - if (!username || (typeof username !== 'string')) { - throw new SiteError(406, 'Invalid username'); - } + async searchCoreUserAccounts (pagination, username) { + let search = { }; - username = username.trim().toLowerCase(); - if (username.length === 0) { - throw new SiteError(406, 'Invalid username'); + username = this.filterUsername(username); + if (username) { + search.username_lc = { $regex: `^${username.toLowerCase().trim()}` }; } - let user; - switch (type) { - case 'CoreUser': - user = await CoreUser - .findOne({ username_lc: username }) - .select('_id created username username_lc displayName bio picture header core') - .populate(this.populateUser) - .lean(); - if (user) { - user.type = 'CoreUser'; - } - break; - - case 'User': - user = await User - .findOne({ username_lc: username }) - .select('_id created username username_lc displayName bio picture header') - .populate(this.populateUser) - .lean(); - if (user) { - user.type = 'User'; - } - break; - - default: - throw new SiteError(400, 'Invalid user account type'); - } + const users = await CoreUser + .find(search) + .sort({ username_lc: 1 }) + .select('+core +coreUserId +flags +permissions +optIn') + .skip(pagination.skip) + .limit(pagination.cpp) + .lean() + ; - return user; + return users.map((user) => { user.type = 'CoreUser'; return user; }); } async getRecent (maxCount = 3) { @@ -579,10 +577,6 @@ class UserService extends SiteService { return actions; } - async filterUsername (username) { - return striptags(username.trim().toLowerCase()).replace(/\W/g, ''); - } - async checkUsername (username) { if (!username || (typeof username !== 'string') || (username.length === 0)) { throw new SiteError(406, 'Invalid username'); @@ -598,6 +592,34 @@ class UserService extends SiteService { } } + filterUsername (username) { + while (username[0] === '@') { + username = username.slice(1); + } + return striptags(username.trim().toLowerCase()).replace(/\W/g, ''); + } + + filterUserObject (user) { + const filteredUser = { + _id: user._id, + created: user.created, + displayName: user.displayName, + username: user.username, + username_lc: user.username_lc, + bio: user.bio, + flags: user.flags, + permissions: user.permissions, + picture: user.picture, + }; + if (filteredUser.flags && filteredUser.flags._id) { + delete filteredUser.flags._id; + } + if (filteredUser.permissions && filteredUser.permissions._id) { + delete filteredUser.permissions._id; + } + return filteredUser; + } + async recordProfileView (user, req) { const { resource: resourceService } = this.dtp.services; await resourceService.recordView(req, 'User', user._id); @@ -653,6 +675,41 @@ class UserService extends SiteService { await User.updateOne({ _id: user._id }, { $unset: { 'picture': '' } }); } + async updateHeaderImage (user, file) { + const { image: imageService } = this.dtp.services; + + await this.removeHeaderImage(user.header); + + const images = [ + { + width: 1400, + height: 400, + format: 'jpeg', + formatParameters: { + quality: 80, + }, + }, + ]; + await imageService.processImageFile(user, file, images); + await User.updateOne( + { _id: user._id }, + { + $set: { + 'header': images[0].image._id, + }, + }, + ); + } + + async removeHeaderImage (user) { + const { image: imageService } = this.dtp.services; + user = await this.getUserAccount(user._id); + if (user.header) { + await imageService.deleteImage(user.header); + } + await User.updateOne({ _id: user._id }, { $unset: { 'header': '' } }); + } + async blockUser (user, blockedUser) { if (user._id.equals(blockedUser._id)) { throw new SiteError(406, "You can't block yourself"); diff --git a/app/views/admin/core-user/form.pug b/app/views/admin/core-user/form.pug index 9b989e8..8badbea 100644 --- a/app/views/admin/core-user/form.pug +++ b/app/views/admin/core-user/form.pug @@ -13,7 +13,7 @@ block content if userAccount.displayName .uk-text-large= userAccount.displayName div - a(href=`/user/${userAccount._id}`) @#{userAccount.username} + a(href=`/user/${userAccount.username}`) @#{userAccount.username} .uk-card-body .uk-margin diff --git a/app/views/admin/user/form.pug b/app/views/admin/user/form.pug index 9f2a735..83b7b9a 100644 --- a/app/views/admin/user/form.pug +++ b/app/views/admin/user/form.pug @@ -20,7 +20,7 @@ block content .uk-width-auto a(href=`mailto:${userAccount.email}`)= userAccount.email .uk-width-auto - a(href=`/user/${userAccount._id}`) @#{userAccount.username} + a(href=`/user/${userAccount.username}`) @#{userAccount.username} .uk-card-body .uk-margin diff --git a/app/views/components/navbar.pug b/app/views/components/navbar.pug index 3b0025b..5c5bbbf 100644 --- a/app/views/components/navbar.pug +++ b/app/views/components/navbar.pug @@ -52,7 +52,7 @@ nav(uk-navbar).uk-navbar-container.uk-position-fixed.uk-position-top i.fas.fa-user span Profile li - a(href= user.core ? `/user/core/${user._id}/settings` : `/user/${user._id}/settings`) + a(href= user.core ? `/user/core/${user.username}/settings` : `/user/${user.username}/settings`) span.nav-item-icon i.fas.fa-cog span Settings diff --git a/app/views/components/off-canvas.pug b/app/views/components/off-canvas.pug index f20c996..a1d1fd5 100644 --- a/app/views/components/off-canvas.pug +++ b/app/views/components/off-canvas.pug @@ -48,7 +48,7 @@ mixin renderMenuItem (iconClass, label) .uk-width-expand Profile li(class={ "uk-active": (currentView === 'user-settings') }) - a(href=`/user/${user._id}/settings`).uk-display-block + a(href=`/user/${user.username}/settings`).uk-display-block div(uk-grid).uk-grid-collapse .uk-width-auto .app-menu-icon diff --git a/lib/site-ioserver.js b/lib/site-ioserver.js index eea3471..bfcbae1 100644 --- a/lib/site-ioserver.js +++ b/lib/site-ioserver.js @@ -15,12 +15,12 @@ const ConnectToken = mongoose.model('ConnectToken'); const marked = require('marked'); const { SiteLog } = require(path.join(__dirname, 'site-log')); +const { SiteCommon } = require(path.join(__dirname, 'site-common')); -const Events = require('events'); -class SiteIoServer extends Events { +class SiteIoServer extends SiteCommon { constructor (dtp) { - super(); + super(dtp, { name: 'ioServer', slug: 'io-server' }); this.dtp = dtp; this.log = new SiteLog(dtp, DTP_COMPONENT); } @@ -74,6 +74,10 @@ class SiteIoServer extends Events { } async stop ( ) { + if (this.io) { + this.io.close(); + delete this.io; + } } From 857acbb980c74ffaccb64b3388495315b616601f Mon Sep 17 00:00:00 2001 From: rob Date: Mon, 22 May 2023 17:08:58 -0400 Subject: [PATCH 03/16] v0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d3a51a..adce19c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dtp-base", - "version": "0.6.3", + "version": "0.7.0", "description": "Open source web app engine for the Digital Telepresence Platform.", "main": "dtp-webapp.js", "author": "DTP Technologies, LLC", From e96233d00195afb2fea6fb84830eea1b32ee2f64 Mon Sep 17 00:00:00 2001 From: rob Date: Mon, 22 May 2023 18:19:40 -0400 Subject: [PATCH 04/16] change model loaders to accept the Mongoose connection instead of using global --- app/models/announcement.js | 4 +++- app/models/attachment.js | 4 +++- app/models/chat-message.js | 4 +++- app/models/chat-room-invite.js | 4 +++- app/models/chat-room.js | 4 +++- app/models/comment.js | 4 +++- app/models/connect-token.js | 4 +++- app/models/content-report.js | 4 +++- app/models/content-vote.js | 4 +++- app/models/core-node-connect.js | 4 +++- app/models/core-node-request.js | 4 +++- app/models/core-node.js | 4 +++- app/models/core-user.js | 4 +++- app/models/csrf-token.js | 4 +++- app/models/email-blacklist.js | 4 +++- app/models/email-body.js | 4 +++- app/models/email-log.js | 4 +++- app/models/email-verify.js | 4 +++- app/models/email.js | 4 +++- app/models/emoji-reaction.js | 4 +++- app/models/feed-entry.js | 4 +++- app/models/feed.js | 4 +++- app/models/image.js | 4 +++- app/models/kaleidoscope-event.js | 4 +++- app/models/log.js | 4 +++- app/models/media-router.js | 4 +++- app/models/media-worker.js | 4 +++- app/models/net-host-stats.js | 4 +++- app/models/net-host.js | 4 +++- app/models/newsletter-recipient.js | 4 +++- app/models/newsletter.js | 4 +++- app/models/oauth2-authorization-code.js | 4 +++- app/models/oauth2-client.js | 4 +++- app/models/oauth2-token.js | 4 +++- app/models/otp-account.js | 4 +++- app/models/resource-view.js | 4 +++- app/models/resource-visit.js | 4 +++- app/models/sticker.js | 4 +++- app/models/user-block.js | 4 +++- app/models/user-notification.js | 4 +++- app/models/user-subscription.js | 4 +++- app/models/user.js | 4 +++- lib/site-platform.js | 6 +++--- 43 files changed, 129 insertions(+), 45 deletions(-) diff --git a/app/models/announcement.js b/app/models/announcement.js index d7d8b51..7438408 100644 --- a/app/models/announcement.js +++ b/app/models/announcement.js @@ -29,4 +29,6 @@ const AnnouncementSchema = new Schema({ resourceStats: { type: ResourceStats, default: ResourceStatsDefaults, required: true }, }); -module.exports = mongoose.model('Announcement', AnnouncementSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Announcement', AnnouncementSchema); +}; \ No newline at end of file diff --git a/app/models/attachment.js b/app/models/attachment.js index 71c3b89..5ac8e3d 100644 --- a/app/models/attachment.js +++ b/app/models/attachment.js @@ -61,4 +61,6 @@ AttachmentSchema.index({ name: 'attachment_item_idx', }); -module.exports = mongoose.model('Attachment', AttachmentSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Attachment', AttachmentSchema); +}; \ No newline at end of file diff --git a/app/models/chat-message.js b/app/models/chat-message.js index ba963ca..7ccf67e 100644 --- a/app/models/chat-message.js +++ b/app/models/chat-message.js @@ -28,4 +28,6 @@ const ChatMessageSchema = new Schema({ attachments: { type: [Schema.ObjectId], ref: 'Attachment' }, }); -module.exports = mongoose.model('ChatMessage', ChatMessageSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ChatMessage', ChatMessageSchema); +}; \ No newline at end of file diff --git a/app/models/chat-room-invite.js b/app/models/chat-room-invite.js index f1d8fe6..dbe6eaf 100644 --- a/app/models/chat-room-invite.js +++ b/app/models/chat-room-invite.js @@ -27,4 +27,6 @@ ChatRoomInviteSchema.index({ name: 'chatroom_invite_unique_idx', }); -module.exports = mongoose.model('ChatRoomInvite', ChatRoomInviteSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ChatRoomInvite', ChatRoomInviteSchema); +}; \ No newline at end of file diff --git a/app/models/chat-room.js b/app/models/chat-room.js index 6d3e2ff..502647d 100644 --- a/app/models/chat-room.js +++ b/app/models/chat-room.js @@ -41,4 +41,6 @@ ChatRoomSchema.index({ name: 'chatroom_public_open_idx', }); -module.exports = mongoose.model('ChatRoom', ChatRoomSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ChatRoom', ChatRoomSchema); +}; \ No newline at end of file diff --git a/app/models/comment.js b/app/models/comment.js index 2aaa3de..8bd6558 100644 --- a/app/models/comment.js +++ b/app/models/comment.js @@ -72,4 +72,6 @@ CommentSchema.index({ name: 'comment_replies', }); -module.exports = mongoose.model('Comment', CommentSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Comment', CommentSchema); +}; \ No newline at end of file diff --git a/app/models/connect-token.js b/app/models/connect-token.js index 7f22340..c9ef76f 100644 --- a/app/models/connect-token.js +++ b/app/models/connect-token.js @@ -16,4 +16,6 @@ const ConnectTokenSchema = new Schema({ claimed: { type: Date }, }); -module.exports = mongoose.model('ConnectToken', ConnectTokenSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ConnectToken', ConnectTokenSchema); +}; \ No newline at end of file diff --git a/app/models/content-report.js b/app/models/content-report.js index ffad674..bb1860e 100644 --- a/app/models/content-report.js +++ b/app/models/content-report.js @@ -28,4 +28,6 @@ ContentReportSchema.index({ name: 'unique_user_content_report', }); -module.exports = mongoose.model('ContentReport', ContentReportSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ContentReport', ContentReportSchema); +}; \ No newline at end of file diff --git a/app/models/content-vote.js b/app/models/content-vote.js index 490b098..28b21df 100644 --- a/app/models/content-vote.js +++ b/app/models/content-vote.js @@ -23,4 +23,6 @@ ContentVoteSchema.index({ name: 'unique_user_content_vote', }); -module.exports = mongoose.model('ContentVote', ContentVoteSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ContentVote', ContentVoteSchema); +}; \ No newline at end of file diff --git a/app/models/core-node-connect.js b/app/models/core-node-connect.js index 599503e..355ca5b 100644 --- a/app/models/core-node-connect.js +++ b/app/models/core-node-connect.js @@ -30,4 +30,6 @@ const CoreNodeConnectSchema = new Schema({ }, }); -module.exports = mongoose.model('CoreNodeConnect', CoreNodeConnectSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CoreNodeConnect', CoreNodeConnectSchema); +}; \ No newline at end of file diff --git a/app/models/core-node-request.js b/app/models/core-node-request.js index df526f4..730d117 100644 --- a/app/models/core-node-request.js +++ b/app/models/core-node-request.js @@ -35,4 +35,6 @@ const CoreNodeRequestSchema = new Schema({ }, }); -module.exports = mongoose.model('CoreNodeRequest', CoreNodeRequestSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CoreNodeRequest', CoreNodeRequestSchema); +}; \ No newline at end of file diff --git a/app/models/core-node.js b/app/models/core-node.js index 81eda79..3a037af 100644 --- a/app/models/core-node.js +++ b/app/models/core-node.js @@ -45,4 +45,6 @@ CoreNodeSchema.index({ name: 'core_address_idx', }); -module.exports = mongoose.model('CoreNode', CoreNodeSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CoreNode', CoreNodeSchema); +}; \ No newline at end of file diff --git a/app/models/core-user.js b/app/models/core-user.js index 04e9e46..f91a347 100644 --- a/app/models/core-user.js +++ b/app/models/core-user.js @@ -52,4 +52,6 @@ CoreUserSchema.index({ name: 'core_username_lc_unique', }); -module.exports = mongoose.model('CoreUser', CoreUserSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CoreUser', CoreUserSchema); +}; \ No newline at end of file diff --git a/app/models/csrf-token.js b/app/models/csrf-token.js index 81d75df..2337094 100644 --- a/app/models/csrf-token.js +++ b/app/models/csrf-token.js @@ -17,4 +17,6 @@ const CsrfTokenSchema = new Schema({ ip: { type: String, required: true }, }); -module.exports = mongoose.model('CsrfToken', CsrfTokenSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CsrfToken', CsrfTokenSchema); +}; \ No newline at end of file diff --git a/app/models/email-blacklist.js b/app/models/email-blacklist.js index ff03a3a..50281c6 100644 --- a/app/models/email-blacklist.js +++ b/app/models/email-blacklist.js @@ -31,4 +31,6 @@ EmailBlacklistSchema.index({ }, }); -module.exports = mongoose.model('EmailBlacklist', EmailBlacklistSchema); +module.exports = (conn) => { + return conn.model('EmailBlacklist', EmailBlacklistSchema); +}; \ No newline at end of file diff --git a/app/models/email-body.js b/app/models/email-body.js index e8e3824..c0ceb57 100644 --- a/app/models/email-body.js +++ b/app/models/email-body.js @@ -12,4 +12,6 @@ const EmailBodySchema = new Schema({ body: { type: String, required: true }, }); -module.exports = mongoose.model('EmailBody', EmailBodySchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('EmailBody', EmailBodySchema); +}; \ No newline at end of file diff --git a/app/models/email-log.js b/app/models/email-log.js index b15fdec..d196d85 100644 --- a/app/models/email-log.js +++ b/app/models/email-log.js @@ -16,4 +16,6 @@ const EmailLogSchema = new Schema({ messageId: { type: String }, }); -module.exports = mongoose.model('EmailLog', EmailLogSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('EmailLog', EmailLogSchema); +}; \ No newline at end of file diff --git a/app/models/email-verify.js b/app/models/email-verify.js index 9b86900..e1ebd49 100644 --- a/app/models/email-verify.js +++ b/app/models/email-verify.js @@ -15,4 +15,6 @@ const EmailVerifySchema = new Schema({ token: { type: String, required: true }, }); -module.exports = mongoose.model('EmailVerify', EmailVerifySchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('EmailVerify', EmailVerifySchema); +}; \ No newline at end of file diff --git a/app/models/email.js b/app/models/email.js index f76bcae..b745c5b 100644 --- a/app/models/email.js +++ b/app/models/email.js @@ -17,4 +17,6 @@ const EmailSchema = new Schema({ content: { type: Schema.ObjectId, required: true, index: true, refPath: 'contentType' }, }); -module.exports = mongoose.model('Email', EmailSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Email', EmailSchema); +}; \ No newline at end of file diff --git a/app/models/emoji-reaction.js b/app/models/emoji-reaction.js index 6ce84a2..ce87469 100644 --- a/app/models/emoji-reaction.js +++ b/app/models/emoji-reaction.js @@ -36,4 +36,6 @@ const EmojiReactionSchema = new Schema({ timestamp: { type: Number }, }); -module.exports = mongoose.model('EmojiReaction', EmojiReactionSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('EmojiReaction', EmojiReactionSchema); +}; \ No newline at end of file diff --git a/app/models/feed-entry.js b/app/models/feed-entry.js index 676888a..fa708f8 100644 --- a/app/models/feed-entry.js +++ b/app/models/feed-entry.js @@ -23,4 +23,6 @@ FeedEntrySchema.index({ name: 'feed_entry_by_feed_idx', }); -module.exports = mongoose.model('FeedEntry', FeedEntrySchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('FeedEntry', FeedEntrySchema); +}; \ No newline at end of file diff --git a/app/models/feed.js b/app/models/feed.js index 84bc39e..4d2524c 100644 --- a/app/models/feed.js +++ b/app/models/feed.js @@ -18,4 +18,6 @@ const FeedSchema = new Schema({ published: { type: Date }, }); -module.exports = mongoose.model('Feed', FeedSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Feed', FeedSchema); +}; \ No newline at end of file diff --git a/app/models/image.js b/app/models/image.js index d31e6da..c2511c1 100644 --- a/app/models/image.js +++ b/app/models/image.js @@ -32,4 +32,6 @@ const ImageSchema = new Schema({ }, }); -module.exports = mongoose.model('Image', ImageSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Image', ImageSchema); +}; \ No newline at end of file diff --git a/app/models/kaleidoscope-event.js b/app/models/kaleidoscope-event.js index b76f33b..949547f 100644 --- a/app/models/kaleidoscope-event.js +++ b/app/models/kaleidoscope-event.js @@ -58,4 +58,6 @@ KaleidoscopeEventSchema.index({ name: 'evtsrc_site_author_index', }); -module.exports = mongoose.model('KaleidoscopeEvent', KaleidoscopeEventSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('KaleidoscopeEvent', KaleidoscopeEventSchema); +}; \ No newline at end of file diff --git a/app/models/log.js b/app/models/log.js index b2462f6..b74d682 100644 --- a/app/models/log.js +++ b/app/models/log.js @@ -29,4 +29,6 @@ const LogSchema = new Schema({ metadata: { type: Schema.Types.Mixed }, }); -module.exports = mongoose.model('Log', LogSchema); +module.exports = (conn) => { + return conn.model('Log', LogSchema); +}; \ No newline at end of file diff --git a/app/models/media-router.js b/app/models/media-router.js index c8dc32c..0416002 100644 --- a/app/models/media-router.js +++ b/app/models/media-router.js @@ -50,4 +50,6 @@ const MediaRouterSchema = new Schema({ } }); -module.exports = mongoose.model('MediaRouter', MediaRouterSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('MediaRouter', MediaRouterSchema); +}; \ No newline at end of file diff --git a/app/models/media-worker.js b/app/models/media-worker.js index feebc87..51b5e4c 100644 --- a/app/models/media-worker.js +++ b/app/models/media-worker.js @@ -40,4 +40,6 @@ const MediaWorkerSchema = new Schema({ } }); -module.exports = mongoose.model('MediaWorker', MediaWorkerSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('MediaWorker', MediaWorkerSchema); +}; \ No newline at end of file diff --git a/app/models/net-host-stats.js b/app/models/net-host-stats.js index 084068e..76f315a 100644 --- a/app/models/net-host-stats.js +++ b/app/models/net-host-stats.js @@ -72,4 +72,6 @@ const NetHostStatsSchema = new Schema({ network: { type: [NetworkInterfaceStatsSchema], required: true }, }); -module.exports = mongoose.model('NetHostStats', NetHostStatsSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('NetHostStats', NetHostStatsSchema); +}; \ No newline at end of file diff --git a/app/models/net-host.js b/app/models/net-host.js index 0646b2e..c8d482c 100644 --- a/app/models/net-host.js +++ b/app/models/net-host.js @@ -42,4 +42,6 @@ const NetHostSchema = new Schema({ network: { type: [NetworkInterfaceSchema] }, }); -module.exports = mongoose.model('NetHost', NetHostSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('NetHost', NetHostSchema); +}; \ No newline at end of file diff --git a/app/models/newsletter-recipient.js b/app/models/newsletter-recipient.js index da76741..2acfb65 100644 --- a/app/models/newsletter-recipient.js +++ b/app/models/newsletter-recipient.js @@ -18,4 +18,6 @@ const NewsletterRecipientSchema = new Schema({ }, }); -module.exports = mongoose.model('NewsletterRecipient', NewsletterRecipientSchema); +module.exports = (conn) => { + return conn.model('NewsletterRecipient', NewsletterRecipientSchema); +}; \ No newline at end of file diff --git a/app/models/newsletter.js b/app/models/newsletter.js index b036fdb..0f11675 100644 --- a/app/models/newsletter.js +++ b/app/models/newsletter.js @@ -28,4 +28,6 @@ const NewsletterSchema = new Schema({ }, }); -module.exports = mongoose.model('Newsletter', NewsletterSchema); +module.exports = (conn) => { + return conn.model('Newsletter', NewsletterSchema); +}; \ No newline at end of file diff --git a/app/models/oauth2-authorization-code.js b/app/models/oauth2-authorization-code.js index 2017ea7..4278c90 100644 --- a/app/models/oauth2-authorization-code.js +++ b/app/models/oauth2-authorization-code.js @@ -16,4 +16,6 @@ const OAuth2AuthorizationCodeSchema = new Schema({ scopes: { type: [String], required: true }, }); -module.exports = mongoose.model('OAuth2AuthorizationCode', OAuth2AuthorizationCodeSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('OAuth2AuthorizationCode', OAuth2AuthorizationCodeSchema); +}; \ No newline at end of file diff --git a/app/models/oauth2-client.js b/app/models/oauth2-client.js index de56e24..e9d9877 100644 --- a/app/models/oauth2-client.js +++ b/app/models/oauth2-client.js @@ -40,4 +40,6 @@ OAuth2ClientSchema.index({ unique: true, }); -module.exports = mongoose.model('OAuth2Client', OAuth2ClientSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('OAuth2Client', OAuth2ClientSchema); +}; \ No newline at end of file diff --git a/app/models/oauth2-token.js b/app/models/oauth2-token.js index f8133dd..692e279 100644 --- a/app/models/oauth2-token.js +++ b/app/models/oauth2-token.js @@ -24,4 +24,6 @@ OAuth2TokenSchema.index({ name: 'oauth2_token_unique', }); -module.exports = mongoose.model('OAuth2Token', OAuth2TokenSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('OAuth2Token', OAuth2TokenSchema); +}; \ No newline at end of file diff --git a/app/models/otp-account.js b/app/models/otp-account.js index fcaea75..1bc1287 100644 --- a/app/models/otp-account.js +++ b/app/models/otp-account.js @@ -33,4 +33,6 @@ OtpAccountSchema.index({ name: 'otp_user_svc_uniq_idx', }); -module.exports = mongoose.model('OtpAccount', OtpAccountSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('OtpAccount', OtpAccountSchema); +}; \ No newline at end of file diff --git a/app/models/resource-view.js b/app/models/resource-view.js index d59ad0a..c2cfbd2 100644 --- a/app/models/resource-view.js +++ b/app/models/resource-view.js @@ -29,4 +29,6 @@ ResourceViewSchema.index({ name: 'res_view_daily_unique', }); -module.exports = mongoose.model('ResourceView', ResourceViewSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ResourceView', ResourceViewSchema); +}; \ No newline at end of file diff --git a/app/models/resource-visit.js b/app/models/resource-visit.js index 770ce64..1c36baf 100644 --- a/app/models/resource-visit.js +++ b/app/models/resource-visit.js @@ -26,4 +26,6 @@ ResourceVisitSchema.index({ name: 'resource_visits_for_user', }); -module.exports = mongoose.model('ResourceVisit', ResourceVisitSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ResourceVisit', ResourceVisitSchema); +}; \ No newline at end of file diff --git a/app/models/sticker.js b/app/models/sticker.js index 5e33840..55dd4fe 100644 --- a/app/models/sticker.js +++ b/app/models/sticker.js @@ -43,4 +43,6 @@ const StickerSchema = new Schema({ encoded: { type: StickerMediaSchema }, }); -module.exports = mongoose.model('Sticker', StickerSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Sticker', StickerSchema); +}; \ No newline at end of file diff --git a/app/models/user-block.js b/app/models/user-block.js index f6396e1..842c233 100644 --- a/app/models/user-block.js +++ b/app/models/user-block.js @@ -14,4 +14,6 @@ const UserBlockSchema = new Schema({ blockedMembers: { type: [DtpUserSchema] }, }); -module.exports = mongoose.model('UserBlock', UserBlockSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('UserBlock', UserBlockSchema); +}; \ No newline at end of file diff --git a/app/models/user-notification.js b/app/models/user-notification.js index 544de5f..fa5a0ff 100644 --- a/app/models/user-notification.js +++ b/app/models/user-notification.js @@ -24,4 +24,6 @@ const UserNotificationSchema = new Schema({ event: { type: Schema.ObjectId, required: true, ref: 'KaleidoscopeEvent' }, }); -module.exports = mongoose.model('UserNotification', UserNotificationSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('UserNotification', UserNotificationSchema); +}; \ No newline at end of file diff --git a/app/models/user-subscription.js b/app/models/user-subscription.js index 7200eea..d7bea99 100644 --- a/app/models/user-subscription.js +++ b/app/models/user-subscription.js @@ -24,4 +24,6 @@ const UserSubscriptionSchema = new Schema({ subscriptions: { type: [SubscriptionSchema] }, }); -module.exports = mongoose.model('UserSubscription', UserSubscriptionSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('UserSubscription', UserSubscriptionSchema); +}; \ No newline at end of file diff --git a/app/models/user.js b/app/models/user.js index 897e571..92f7949 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -43,4 +43,6 @@ const UserSchema = new Schema({ lastAnnouncement: { type: Date }, }); -module.exports = mongoose.model('User', UserSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('User', UserSchema); +}; \ No newline at end of file diff --git a/lib/site-platform.js b/lib/site-platform.js index 8bb8994..cc6ba90 100644 --- a/lib/site-platform.js +++ b/lib/site-platform.js @@ -58,12 +58,12 @@ module.connectDatabase = async (/*dtp*/) => { } }; - module.loadModels = async (dtp) => { dtp.models = module.models = [ ]; const modelScripts = glob.sync(path.join(dtp.config.root, 'app', 'models', '*.js')); modelScripts.forEach((modelScript) => { - const model = require(modelScript); + const instance = require(modelScript); + const model = instance(module.db); if (module.models[model.modelName]) { module.log.error('model name collision', { name: model.modelName }); process.exit(-1); @@ -206,7 +206,7 @@ module.exports.startPlatform = async (dtp) => { await module.connectRedis(dtp); await module.loadModels(dtp); - SiteLog.setModel(mongoose.model('Log')); + SiteLog.setModel(module.db.model('Log')); await module.loadServices(dtp); From 98f07769d66e7fabaae85ebaa36701c8710cff69 Mon Sep 17 00:00:00 2001 From: rob Date: Mon, 22 May 2023 18:20:03 -0400 Subject: [PATCH 05/16] v0.7.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index adce19c..2dcfd92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dtp-base", - "version": "0.7.0", + "version": "0.7.1", "description": "Open source web app engine for the Digital Telepresence Platform.", "main": "dtp-webapp.js", "author": "DTP Technologies, LLC", From 8548919a84922cea27d31aaca0cb36a86bc64b01 Mon Sep 17 00:00:00 2001 From: rob Date: Mon, 22 May 2023 23:35:19 -0400 Subject: [PATCH 06/16] added a user not found error for permission grants --- dtp-webapp-cli.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dtp-webapp-cli.js b/dtp-webapp-cli.js index c7dbf25..99983a4 100644 --- a/dtp-webapp-cli.js +++ b/dtp-webapp-cli.js @@ -34,6 +34,10 @@ module.grantPermission = async (target, permission) => { const User = mongoose.model('User'); try { const user = await User.findOne({ email: target }).select('+permissions +flags'); + if (!user) { + throw new Error(`User not found (email: ${target})`); + } + switch (permission) { case 'admin': user.flags.isAdmin = true; From 4a62a3796268be37e47da32f4d26e117e2a81302 Mon Sep 17 00:00:00 2001 From: rob Date: Mon, 22 May 2023 23:35:29 -0400 Subject: [PATCH 07/16] v0.7.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2dcfd92..53059b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dtp-base", - "version": "0.7.1", + "version": "0.7.2", "description": "Open source web app engine for the Digital Telepresence Platform.", "main": "dtp-webapp.js", "author": "DTP Technologies, LLC", From af5b8aa5cfbb011fe900839e2ecabeb480ac12f9 Mon Sep 17 00:00:00 2001 From: rob Date: Tue, 23 May 2023 00:03:16 -0400 Subject: [PATCH 08/16] update local user admin --- app/controllers/admin/user.js | 17 ++++++++--------- app/services/user.js | 2 +- app/views/admin/user/form.pug | 2 +- app/views/admin/user/index.pug | 4 ++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/controllers/admin/user.js b/app/controllers/admin/user.js index eb471d4..fc1fd9d 100644 --- a/app/controllers/admin/user.js +++ b/app/controllers/admin/user.js @@ -22,37 +22,36 @@ class UserController extends SiteController { return next(); }); - router.param('userId', this.populateUserId.bind(this)); + router.param('localUserId', this.populateLocalUserId.bind(this)); + router.post('/local/:localUserId', this.postUpdateLocalUser.bind(this)); + router.get('/local/:localUserId', this.getLocalUserView.bind(this)); - router.post('/:userId', this.postUpdateUser.bind(this)); - - router.get('/:userId', this.getUserView.bind(this)); router.get('/', this.getHomeView.bind(this)); return router; } - async populateUserId (req, res, next, userId) { + async populateLocalUserId (req, res, next, localUserId) { const { user: userService } = this.dtp.services; try { - res.locals.userAccount = await userService.getUserAccount(userId); + res.locals.userAccount = await userService.getLocalUserAccount(localUserId); return next(); } catch (error) { return next(error); } } - async postUpdateUser (req, res, next) { + async postUpdateLocalUser (req, res, next) { const { user: userService } = this.dtp.services; try { - await userService.updateForAdmin(res.locals.userAccount, req.body); + await userService.updateLocalForAdmin(res.locals.userAccount, req.body); res.redirect('/admin/user'); } catch (error) { return next(error); } } - async getUserView (req, res, next) { + async getLocalUserView (req, res, next) { const { comment: commentService } = this.dtp.services; try { res.locals.pagination = this.getPaginationParameters(req, 20); diff --git a/app/services/user.js b/app/services/user.js index 9c4e63d..81a96fb 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -225,7 +225,7 @@ class UserService extends SiteService { ); } - async updateForAdmin (user, userDefinition) { + async updateLocalForAdmin (user, userDefinition) { userDefinition.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); const username_lc = userDefinition.username.toLowerCase(); diff --git a/app/views/admin/user/form.pug b/app/views/admin/user/form.pug index 83b7b9a..279bc32 100644 --- a/app/views/admin/user/form.pug +++ b/app/views/admin/user/form.pug @@ -5,7 +5,7 @@ block content div(uk-grid).uk-grid-small div(class="uk-width-1-1 uk-width-2-3@l") - form(method="POST", action=`/admin/user/${userAccount._id}`).uk-form + form(method="POST", action=`/admin/user/local/${userAccount._id}`).uk-form input(type="hidden", name="username", value= userAccount.username) input(type="hidden", name="displayName", value= userAccount.displayName) .uk-card.uk-card-default.uk-card-small diff --git a/app/views/admin/user/index.pug b/app/views/admin/user/index.pug index 096bd13..66c17c2 100644 --- a/app/views/admin/user/index.pug +++ b/app/views/admin/user/index.pug @@ -22,10 +22,10 @@ block content each userAccount in userAccounts tr td - a(href=`/admin/user/${userAccount._id}`)= userAccount.username + a(href=`/admin/user/local/${userAccount._id}`)= userAccount.username td if userAccount.displayName - a(href=`/admin/user/${userAccount._id}`)= userAccount.displayName + a(href=`/admin/user/local/${userAccount._id}`)= userAccount.displayName else .uk-text-muted N/A td= moment(userAccount.created).format('YYYY-MM-DD hh:mm a') From 620581ada39ee8019e5abaa4a9b25ed711bedadf Mon Sep 17 00:00:00 2001 From: rob Date: Tue, 23 May 2023 00:03:23 -0400 Subject: [PATCH 09/16] v0.7.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 53059b5..bcad11c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dtp-base", - "version": "0.7.2", + "version": "0.7.3", "description": "Open source web app engine for the Digital Telepresence Platform.", "main": "dtp-webapp.js", "author": "DTP Technologies, LLC", From df1a10c25f43ab803327ed8ce3bb1b7bda19dd25 Mon Sep 17 00:00:00 2001 From: rob Date: Tue, 23 May 2023 00:48:57 -0400 Subject: [PATCH 10/16] log controller names while loading them --- lib/site-platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/site-platform.js b/lib/site-platform.js index cc6ba90..3fa145f 100644 --- a/lib/site-platform.js +++ b/lib/site-platform.js @@ -156,7 +156,7 @@ module.loadControllers = async (dtp) => { await SiteAsync.each(scripts, async (script) => { const controller = await require(script); controller.instance = await controller.create(dtp); - + module.log.info('controller loaded', { name: controller.name, slug: controller.slug }); dtp.controllers[controller.name] = controller; inits.push(controller); From 9a06b424c5599492ccf4fedc6e07a5c38e5a4c9f Mon Sep 17 00:00:00 2001 From: rob Date: Tue, 23 May 2023 01:06:39 -0400 Subject: [PATCH 11/16] clean up the shutdown of webapp --- dtp-webapp.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dtp-webapp.js b/dtp-webapp.js index 1dd4cc1..3787943 100644 --- a/dtp-webapp.js +++ b/dtp-webapp.js @@ -30,7 +30,7 @@ module.config = { module.log = new SiteLog(module, module.config.component); module.shutdown = async ( ) => { - await SitePlatform.shutdown(); + return await SitePlatform.shutdown(); }; (async ( ) => { @@ -50,8 +50,8 @@ module.shutdown = async ( ) => { process.once('SIGINT', async ( ) => { module.log.info('SIGINT received'); module.log.info('requesting shutdown...'); - await module.shutdown(); - const exitCode = await SitePlatform.shutdown(); + const exitCode = await module.shutdown(); + process.nextTick(( ) => { process.exit(exitCode); }); From 0125add6f21ab919cddf814c3dd0aeafdf17e184 Mon Sep 17 00:00:00 2001 From: rob Date: Wed, 24 May 2023 18:48:38 -0400 Subject: [PATCH 12/16] added a basic markdown block to clean up marked output --- client/less/site/markdown.less | 9 +++++++++ client/less/style.common.less | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 client/less/site/markdown.less diff --git a/client/less/site/markdown.less b/client/less/site/markdown.less new file mode 100644 index 0000000..5a9c621 --- /dev/null +++ b/client/less/site/markdown.less @@ -0,0 +1,9 @@ +.markdown-block { + font-size: @global-font-size; + line-height: @global-line-height; + color: @global-color; + + p:last-of-type { + margin-bottom: 0; + } +} \ No newline at end of file diff --git a/client/less/style.common.less b/client/less/style.common.less index 86c5642..9e2764e 100644 --- a/client/less/style.common.less +++ b/client/less/style.common.less @@ -10,13 +10,14 @@ @import "site/kaleidoscope-event.less"; @import "site/nav.less"; +@import "site/button.less"; @import "site/content.less"; @import "site/core-node.less"; @import "site/dashboard.less"; -@import "site/site.less"; @import "site/form.less"; -@import "site/button.less"; -@import "site/sidebar.less"; +@import "site/markdown.less"; @import "site/section.less"; +@import "site/sidebar.less"; +@import "site/site.less"; @import "site/chat.less"; \ No newline at end of file From 3d4f9a1c2137d5e53547eef3cb01b9736ce0a328 Mon Sep 17 00:00:00 2001 From: rob Date: Wed, 24 May 2023 22:12:46 -0400 Subject: [PATCH 13/16] added options for multer --- app/controllers/user.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/user.js b/app/controllers/user.js index 8dffd71..897739d 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -23,7 +23,11 @@ class UserController extends SiteController { session: sessionService, } = dtp.services; - const upload = this.createMulter(); + const upload = this.createMulter('user', { + limits: { + fileSize: 1024 * 1000 * 5, // 5MB + }, + }); const router = express.Router(); dtp.app.use('/user', router); From 5e58e98bd8c80aa813aa40a826f7e722e9fcd846 Mon Sep 17 00:00:00 2001 From: rob Date: Sun, 11 Jun 2023 00:05:42 -0400 Subject: [PATCH 14/16] fix vuln for elevated privileges at create UserService.create was adapted a while back to accomodate imports, and was honoring a field named `isAdmin` and/or `isModerator` during create to set the value of `flags.isAdmin` and `flags.isModerator`. This change breaks that importer but stops people from being able to "sign up" with Admin privileges. --- app/services/user.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/services/user.js b/app/services/user.js index 81a96fb..14210cf 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -106,21 +106,21 @@ class UserService extends SiteService { user.password = maskedPassword; user.flags = { - isAdmin: userDefinition.isAdmin || false, - isModerator: userDefinition.isModerator || false, - isEmailVerified: userDefinition.isEmailVerified || false, + isAdmin: false, + isModerator: false, + isEmailVerified: false, }; user.permissions = { - canLogin: userDefinition.canLogin || true, - canChat: userDefinition.canChat || true, - canComment: userDefinition.canComment || true, - canReport: userDefinition.canReport || true, + canLogin: true, + canChat: true, + canComment: true, + canReport: true, }; user.optIn = { - system: userDefinition.optInSystem || true, - marketing: userDefinition.optInMarketing || false, + system: true, + marketing: false, }; this.log.info('creating new user account', { email: userDefinition.email }); From 71c644083674ce9f8f151aa6fa9bff3ba186d5a2 Mon Sep 17 00:00:00 2001 From: rob Date: Sun, 11 Jun 2023 00:05:51 -0400 Subject: [PATCH 15/16] comment updates --- lib/client/js/dtp-log.js | 2 +- lib/client/js/dtp-socket.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client/js/dtp-log.js b/lib/client/js/dtp-log.js index 6ee4c4f..1746cc6 100644 --- a/lib/client/js/dtp-log.js +++ b/lib/client/js/dtp-log.js @@ -1,4 +1,4 @@ -// dtpweb-log.js +// dtp-log.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 diff --git a/lib/client/js/dtp-socket.js b/lib/client/js/dtp-socket.js index 95a4a88..be63d47 100644 --- a/lib/client/js/dtp-socket.js +++ b/lib/client/js/dtp-socket.js @@ -1,4 +1,4 @@ -// dtpweb-socket.js +// dtp-socket.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 From 022f674e36f432b9674e2c84fbdb6cfd28e520c2 Mon Sep 17 00:00:00 2001 From: rob Date: Sun, 11 Jun 2023 00:06:22 -0400 Subject: [PATCH 16/16] v0.7.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bcad11c..6980a51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dtp-base", - "version": "0.7.3", + "version": "0.7.4", "description": "Open source web app engine for the Digital Telepresence Platform.", "main": "dtp-webapp.js", "author": "DTP Technologies, LLC",