diff --git a/app/controllers/home.js b/app/controllers/home.js index ad6064d..1a3a190 100644 --- a/app/controllers/home.js +++ b/app/controllers/home.js @@ -6,13 +6,16 @@ import express from 'express'; -export default class HomeController { +import { SiteController } from '../../lib/site-controller.js'; + +export default class HomeController extends SiteController { static get isHome ( ) { return true; } static get slug ( ) { return 'home'; } static get className ( ) { return 'HomeController'; } constructor (dtp) { + super(dtp, HomeController.slug); this.dtp = dtp; } diff --git a/app/models/image.js b/app/models/image.js new file mode 100644 index 0000000..3da65ca --- /dev/null +++ b/app/models/image.js @@ -0,0 +1,49 @@ +// image.js +// Copyright (C) 2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import mongoose from 'mongoose'; +const Schema = mongoose.Schema; + +const ImageSchema = new Schema({ + created: { type: Date, default: Date.now, required: true, index: -1 }, + owner: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, + caption: { type: String, maxLength: 300 }, + flags: { + isSensitive: { type: Boolean, default: false, required: true }, + isPendingAttachment: { type: Boolean, default: false, required: true }, + }, + type: { type: String, required: true }, + size: { type: Number, required: true }, + file: { + bucket: { type: String, required: true }, + key: { type: String, required: true }, + etag: { type: String, required: true, index: true }, + }, + metadata: { + format: { type: String }, + size: { type: Number }, + width: { type: Number }, + height: { type: Number }, + space: { type: String }, + channels: { type: Number }, + depth: { type: String }, + density: { type: Number }, + hasAlpha: { type: Boolean }, + orientation: { type: Number }, + }, +}); + +ImageSchema.index({ + 'flags.isPendingAttachment': 1, + created: -1, +}, { + partialFilterExpression: { + 'flags.isPendingAttachment': true, + }, + name: 'img_pendattach_idx', +}); + +export default mongoose.model('Image', ImageSchema); \ No newline at end of file diff --git a/app/models/log.js b/app/models/log.js new file mode 100644 index 0000000..c8927c5 --- /dev/null +++ b/app/models/log.js @@ -0,0 +1,35 @@ +// log.js +// Copyright (C) 2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import mongoose from 'mongoose'; +const Schema = mongoose.Schema; + +const LOG_LEVEL_LIST = [ + 'debug', + 'info', + 'warn', + 'alert', + 'error', + 'crit', + 'fatal', +]; + +const LogSchema = new Schema({ + created: { type: Date, default: Date.now, required: true, index: -1, expires: '7d' }, + componentName: { type: String, required: true, index: 1 }, + level: { type: String, enum: LOG_LEVEL_LIST, required: true, index: true }, + message: { type: String }, + metadata: { type: Schema.Types.Mixed }, +}); + +LogSchema.index({ + level: 1, + componentName: 1, +}, { + name: 'log_level_component_idx', +}); + +export default mongoose.model('Log', LogSchema); \ No newline at end of file diff --git a/app/models/user.js b/app/models/user.js new file mode 100644 index 0000000..6dde484 --- /dev/null +++ b/app/models/user.js @@ -0,0 +1,69 @@ +// user.js +// Copyright (C) 2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import mongoose from "mongoose"; +const Schema = mongoose.Schema; + +const UserFlagsSchema = new Schema({ + isAdmin: { type: Boolean, default: false, required: true }, + isModerator: { type: Boolean, default: false, required: true }, + isEmailVerified: { type: Boolean, default: false, required: true }, + isCloaked: { type: Boolean, default: false, required: true, select: false }, + hasPublicProfile: { type: Boolean, default: true, required: true }, + hasPublicNetwork: { type: Boolean, default: true, required: true }, + requireFollowRequest: { type: Boolean, default: false, required: true }, +}); + +const UserPermissionsSchema = new Schema({ + canLogin: { type: Boolean, default: true, required: true }, + canChat: { type: Boolean, default: true, required: true }, + canComment: { type: Boolean, default: true, required: true }, + canReport: { type: Boolean, default: true, required: true }, + canPostStatus: { type: Boolean, default: true, required: true }, + canShareLinks: { type: Boolean, default: true, required: true }, + canEnablePublicProfile: { type: Boolean, default: true, required: true }, +}); + +const UserOptInSchema = new Schema({ + system: { type: Boolean, default: true, required: true }, + marketing: { type: Boolean, default: true, required: true }, +}); + +const UserSchema = new Schema({ + created: { type: Date, default: Date.now, required: true, index: -1 }, + email: { type: String, required: true, lowercase: true, unique: true }, + username: { type: String, maxLength: 40, required: true }, + username_lc: { type: String, maxLength: 40, required: true, lowercase: true, unique: true, index: 1 }, + passwordSalt: { type: String, required: true }, + password: { type: String, required: true }, + displayName: { type: String, maxLength: 60 }, + picture: { + large: { type: Schema.ObjectId, ref: 'Image' }, + small: { type: Schema.ObjectId, ref: 'Image' }, + }, + bio: { type: String }, + flags: { type: UserFlagsSchema, default: { }, required: true, select: false }, + permissions: { type: UserPermissionsSchema, default: { }, required: true, select: false }, + optIn: { type: UserOptInSchema, default: { }, required: true, select: false }, + lastAnnouncement: { type: Date }, + membership: { type: Schema.ObjectId, index: 1, ref: 'Membership' }, + paymentTokens: { + handcash: { type: String, select: false }, + stripe: { type: String, select: false }, + }, + balances: { + tokens: { type: Number, default: 0, min: 0, max: 500, required: true }, + }, + blockedUsers: { type: [Schema.ObjectId], select: false, ref: 'User' }, + notes: { type: String, select: false }, + history: { + username: { type: [String] }, + displayName: { type: [String] }, + bio: { type: [String] }, + }, +}); + +export default mongoose.model('User', UserSchema); \ No newline at end of file diff --git a/config/tripwire.js b/config/tripwire.js new file mode 100644 index 0000000..e0d7822 --- /dev/null +++ b/config/tripwire.js @@ -0,0 +1,36 @@ +// tripwire.js +// Copyright (C) 2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +export default [ + '/.env', + '/.well-known', + '/?a=fetch&content=die(@md5(HelloThinkCMF))', + '/?XDEBUG_SESSION_START', + '/actuator/health', + '/api/v1', + '/base', + '/api/pleroma', + '/backend', + '/cgi-bin', + '/conf', + '/core', + '/database', + '/kind-u', + '/laravel', + '/newsite', + '/old-index.php', + '/protected', + '/shell', + '/sites', + '/src', + '/storage', + '/vendor/php', + '/web', + '/wp-admin', + '/wp-login', + '/wp-load', + '/www', +]; \ No newline at end of file diff --git a/dtp-chat.js b/dtp-chat.js index e665478..3bec118 100644 --- a/dtp-chat.js +++ b/dtp-chat.js @@ -14,13 +14,23 @@ import { createRequire } from 'module'; const require = createRequire(import.meta.url); // jshint ignore:line import * as glob from 'glob'; +import * as rfs from 'rotating-file-stream'; import webpack from 'webpack'; import webpackDevMiddleware from 'webpack-dev-middleware'; import WEBPACK_CONFIG from './webpack.config.js'; +import mongoose from 'mongoose'; +import { Redis } from 'ioredis'; + +import { SiteLog } from './lib/site-lib.js'; +import { SiteTripwire } from './lib/site-tripwire.js'; + import express from 'express'; +import morgan from 'morgan'; + +import { Emitter } from '@socket.io/redis-emitter'; const APP_CONFIG = { pkg: require('./package.json'), @@ -32,9 +42,139 @@ class Harness { this.config = { root: __dirname, }; + this.log = new SiteLog(this, 'Harness'); + this.models = [ ]; } async start ( ) { + this.log.info('loading SiteTripwire (known-malicious request protection)'); + this.tripwire = new SiteTripwire(this); + await this.tripwire.start(); + + await this.startMongoDB(); + await this.loadModels(); + + await this.connectRedis(); + + await this.startExpressJS(); + } + + async startMongoDB ( ) { + try { + this.log.info('starting MongoDB'); + + /* + * For some time, strictQuery=true was the Mongoose default. It's being + * changed. The option has to be set manually now. + */ + mongoose.set('strictQuery', true); + + this.log.info('connecting to MongoDB database', { + pid: process.pid, + host: process.env.MONGODB_HOST, + database: process.env.MONGODB_DATABASE, + }); + const mongoConnectUri = `mongodb://${process.env.DTP_MONGODB_HOST}/${process.env.DTP_MONGODB_DATABASE}`; + await mongoose.connect(mongoConnectUri, { + socketTimeoutMS: 0, + dbName: process.env.MONGODB_DATABASE, + }); + this.db = mongoose.connection; + this.log.info('connected to MongoDB'); + } catch (error) { + this.log.error('failed to connect to database', { error }); + throw error; + } + } + + async loadModels ( ) { + const modelScripts = glob.sync(path.join(this.config.root, 'app', 'models', '*.js')); + this.log.info('loading models', { count: modelScripts.length }); + for (const modelScript of modelScripts) { + const model = (await import(modelScript)).default; + if (this.models[model.modelName]) { + this.log.error('model name collision', { name: model.modelName }); + process.exit(-1); + } + this.models.push(model); + this.log.info('model loaded', { name: model.modelName}); + } + } + + async resetIndexes (target) { + if (target === 'all') { + for (const model of this.models) { + await this.resetIndex(model); + } + return; + } + + const model = this.models.find((model) => model.modelName === target); + if (!model) { + throw new Error(`requested Mongoose model does not exist: ${target}`); + } + + return this.resetIndex(model); + } + + async resetIndex (model) { + return new Promise(async (resolve, reject) => { + this.log.info('dropping model indexes', { model: model.modelName }); + model.collection.dropIndexes((err) => { + if (err) { + return reject(err); + } + this.log.info('creating model indexes', { model: model.modelName }); + model.ensureIndexes((err) => { + if (err) { + return reject(err); + } + return resolve(model); + }); + }); + }); + } + + async connectRedis ( ) { + try { + const options = { + host: process.env.DTP_REDIS_HOST, + port: parseInt(process.env.DTP_REDIS_PORT || '6379', 10), + password: process.env.DTP_REDIS_PASSWORD, + keyPrefix: process.env.DTP_REDIS_KEY_PREFIX, + lazyConnect: false, + }; + this.log.info('connecting to Redis', { + host: options.host, + port: options.port, + prefix: options.keyPrefix || 'dtp', + }); + + this.redis = new Redis(options); + this.redis.setMaxListeners(64); // prevents warnings/errors with Bull Queue + + this.log.info('creating Socket.io Emitter'); + this.emitter = new Emitter(this.redis); + + this.log.info('Redis connected'); + } catch (error) { + this.log.error('failed to connect to Redis', error); + throw new Error('failed to connect to Redis', { cause: error }); + } + } + + async getRedisKeys (pattern) { + return new Promise((resolve, reject) => { + return this.redis.keys(pattern, (err, response) => { + if (err) { + return reject(err); + } + return resolve(response); + }); + }); + } + + async startExpressJS ( ) { this.app = express(); this.app.locals.config = APP_CONFIG; @@ -43,13 +183,47 @@ class Harness { this.app.set('view engine', 'pug'); this.app.set('views', path.join(__dirname, 'app', 'views')); - - this.app.use('/static', express.static(path.join(__dirname, 'client', 'static'))); - this.app.use('/fontawesome', express.static(path.join(__dirname, 'node_modules', '@fortawesome', 'fontawesome-free'))); - this.app.use('/uikit', express.static(path.join(__dirname, 'node_modules', 'uikit'))); - this.app.use('/pretty-checkbox', express.static(path.join(__dirname, 'node_modules', 'pretty-checkbox', 'dist'))); + this.app.set('x-powered-by', false); + + this.app.use(this.tripwire.guard.bind(this.tripwire)); + + /* + * HTTP request logging + */ + if (process.env.DTP_LOG_FILE === 'enabled') { + const httpLogStream = rfs.createStream(process.env.DTP_LOG_FILE_NAME_HTTP || 'dtp-sites-access.log', { + interval: '1d', + path: process.env.DTP_LOG_FILE_PATH || '/tmp', + compress: 'gzip', + }); + this.app.use(morgan(process.env.DTP_LOG_HTTP_FORMAT || 'combined', { stream: httpLogStream })); + } - this.app.use('/dist', express.static(path.join(__dirname, 'dist'))); + function cacheOneDay (req, res, next) { + res.set('Cache-Control', 'public, maxage=86400, s-maxage=86400, immutable'); + return next(); + } + function serviceWorkerAllowed (req, res, next) { + res.set('Service-Worker-Allowed', '/'); + return next(); + } + + /* + * Static content and file services + */ + const staticOptions = { + cacheControl: true, + immutable: true, + maxAge: '4h', + dotfiles: 'ignore', + }; + + this.app.use('/fontawesome', cacheOneDay, express.static(path.join(__dirname, 'node_modules', '@fortawesome', 'fontawesome-free'))); + this.app.use('/uikit', cacheOneDay, express.static(path.join(__dirname, 'node_modules', 'uikit'))); + this.app.use('/pretty-checkbox', cacheOneDay, express.static(path.join(__dirname, 'node_modules', 'pretty-checkbox', 'dist'))); + + this.app.use('/dist', cacheOneDay, serviceWorkerAllowed, express.static(path.join(__dirname, 'dist'))); + this.app.use('/static', cacheOneDay, serviceWorkerAllowed, express.static(path.join(__dirname, 'client', 'static'), staticOptions)); /* * Webpack integration @@ -77,9 +251,9 @@ class Harness { */ const host = process.env.DTP_HTTP_HOST || '127.0.0.1'; const port = parseInt(process.env.DTP_HTTP_PORT || '3000', 10); - console.log('Starting application server', { host, port }); + this.log.info('Starting application server', { host, port }); this.app.listen(port, host, ( ) => { - console.log(`${APP_CONFIG.pkg.name} online.`); + this.log.info(`${APP_CONFIG.pkg.name} online.`); }); } @@ -92,7 +266,7 @@ class Harness { for await (const script of scripts) { try { const file = path.parse(script); - console.log('loading controller', { name: file.base }); + this.log.info('loading controller', { name: file.base }); let controller = await import(script); controller = controller.default; @@ -101,7 +275,7 @@ class Harness { this.controllers[controller.slug] = controller; inits.push(controller); } catch (error) { - console.error('failed to load controller', { error }); + this.log.error('failed to load controller', { error }); throw new Error('failed to load controller', { cause: error }); } } @@ -118,6 +292,7 @@ class Harness { */ await this.controllers.home.instance.start(); } + } (async ( ) => { @@ -126,7 +301,7 @@ class Harness { const harness = new Harness(); await harness.start(); } catch (error) { - console.log('failed to start application harness', error); + console.error('failed to start application harness', error); } })(); \ No newline at end of file diff --git a/lib/site-async.js b/lib/site-async.js new file mode 100644 index 0000000..dd776fa --- /dev/null +++ b/lib/site-async.js @@ -0,0 +1,39 @@ +// site-async.js +// Copyright (C) 2022,2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +export class SiteAsync { + + static each (sourceItems, callback, concurrent = 1) { + if (!Array.isArray(sourceItems)) { + throw new Error('each requires an array of objects to be processed'); + } + if (sourceItems.length === 0) { + return Promise.resolve(); + } + + var items = sourceItems.slice(); + var running = 0; + + return new Promise((resolve, reject) => { + function next ( ) { + let item = items.shift(); + if (!item) { + return; + } + ++running; + callback(item).then(next).catch(reject).finally(( ) => { + if (--running === 0) { + resolve(); + } + }); + } + while (concurrent && items.length) { + next(); + --concurrent; + } + }); + } +} diff --git a/lib/site-common.js b/lib/site-common.js new file mode 100644 index 0000000..d8c5d9e --- /dev/null +++ b/lib/site-common.js @@ -0,0 +1,66 @@ +// site-common.js +// Copyright (C) 2022,2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import path from 'path'; +import pug from 'pug'; + +import EventEmitter from 'events'; + +export class SiteCommon extends EventEmitter { + + constructor (dtp) { + super(); + this.dtp = dtp; + this.appTemplateRoot = path.join(this.dtp.config.root, 'app', 'templates'); + } + + saveSession (req) { + return new Promise((resolve, reject) => { + req.session.save((err) => { + if (err) { + return reject(err); + } + resolve(); + }); + }); + } + + isValidString (text) { + return text && (typeof text === 'string') && (text.length > 0); + } + + loadAppTemplate (type, name) { + return pug.compileFile(path.join(this.appTemplateRoot, type, name)); + } + + loadViewTemplate (filename) { + const scriptFile = path.join(this.dtp.config.root, 'app', 'views', filename); + this.log.debug('loading view template', { filename }); + return pug.compileFile(scriptFile); + } + + async renderTemplate (templateFn, templateModel) { + const { cache: cacheService } = this.dtp.services; + const { config, pkg } = this.dtp; + + const settingsKey = `settings:${config.site.domainKey}:site`; + const adminSettings = await cacheService.getObject(settingsKey); + + if (this.dtp.app && this.dtp.app.locals) { + templateModel = Object.assign(templateModel, this.dtp.app.locals); // global app objects + } + templateModel.pkg = pkg; + templateModel.site = Object.assign(templateModel.site || { }, config.site); // defaults and .env + templateModel.site = Object.assign(templateModel.site, adminSettings); // admin overrides + + return templateFn(templateModel); + } + + createDisplayList (name) { + const { displayEngine: displayEngineService } = this.dtp.services; + return displayEngineService.createDisplayList(name); + } +} \ No newline at end of file diff --git a/lib/site-controller.js b/lib/site-controller.js new file mode 100644 index 0000000..019edb5 --- /dev/null +++ b/lib/site-controller.js @@ -0,0 +1,65 @@ +// site-controller.js +// Copyright (C) 2022,2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import multer from 'multer'; +import * as slugTool from 'slug'; + +import { SiteCommon } from './site-common.js'; +import { SiteLog } from './site-log.js'; + +export class SiteController extends SiteCommon { + + constructor (dtp, name) { + super(dtp); + this.name = name; + this.log = new SiteLog(dtp, `ctrl:${name}`); + + this.children = { }; + } + + async loadChild (filename) { + let child = await require(filename); + this.children[child.slug] = child; + + let instance = child.create(this.dtp); + + return await instance.start(); + } + + getPaginationParameters (req, maxPerPage, pageParamName = 'p', cppParamName = 'cpp') { + const pagination = { + p: parseInt(req.query[pageParamName] || '1', 10), + cpp: parseInt(req.query[cppParamName] || maxPerPage.toString(), 10), + }; + if (pagination.p < 1) { + pagination.p = 1; + } + if (pagination.cpp > maxPerPage) { + pagination.cpp = maxPerPage; + } + pagination.skip = (pagination.p - 1) * pagination.cpp; + return pagination; + } + + createMulter (slug, options) { + if (!!slug && (typeof slug === 'object')) { + options = slug; + slug = this.name; + } else { + slug = slug || this.name; + } + slug = slugTool(slug).toLowerCase(); + options = Object.assign({ + dest: `/tmp/${this.dtp.config.site.domainKey}/${slug}`, + }, options || { }); + return multer(options); + } + + async createCsrfToken (req, name) { + const { csrfToken } = this.dtp.platform.services; + return csrfToken.create(req, { name }); + } +} \ No newline at end of file diff --git a/lib/site-error.js b/lib/site-error.js new file mode 100644 index 0000000..ff354ed --- /dev/null +++ b/lib/site-error.js @@ -0,0 +1,17 @@ +// site-error.js +// Copyright (C) 2022,2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +export class SiteError extends Error { + + constructor (statusCode, message, options) { + super(message, options); + this.name = 'SiteError'; + if (!this.code) { + this.code = statusCode; + } + this.statusCode = statusCode; + } +} \ No newline at end of file diff --git a/lib/site-lib.js b/lib/site-lib.js new file mode 100644 index 0000000..adba697 --- /dev/null +++ b/lib/site-lib.js @@ -0,0 +1,13 @@ +// site-lib.js +// Copyright (C) 2022,2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +export { SiteCommon } from './site-common.js'; +// export * from './site-platform.js'; +export { SiteAsync } from './site-async.js'; +export { SiteError } from './site-error.js'; +export { SiteLog } from './site-log.js'; +export { SiteController } from './site-controller.js'; +export { SiteService } from './site-service.js'; \ No newline at end of file diff --git a/lib/site-log.js b/lib/site-log.js new file mode 100644 index 0000000..0419883 --- /dev/null +++ b/lib/site-log.js @@ -0,0 +1,128 @@ +// site-log.js +// Copyright (C) 2022,2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import * as util from 'node:util'; + +import moment from 'moment'; +import * as rfs from 'rotating-file-stream'; + +import color from 'ansicolor'; + +var LogModel, LogStream; + +if (process.env.DTP_LOG_FILE === 'enabled') { + LogStream = rfs.createStream(process.env.DTP_LOG_FILE_NAME_APP || 'site-app.log', { + path: process.env.DTP_LOG_FILE_PATH || '/tmp', + size: '10M', + interval: '1d', + compress: 'gzip', + }); +} + +export class SiteLog { + + /** + * Sets the Mongoose model to be used for writing log entries to the database. + * This is managed like this so we don't have to connect to the database and + * load the db models *before* we can write log entries to the console. + * @param {Model} model the Mongoose model to be used for writing logs. + */ + static setModel (model) { LogModel = model; } + + constructor (dtp, componentName) { + this.dtp = dtp; + this.componentName = componentName; + } + + async debug (message, metadata) { + if (process.env.DTP_LOG_DEBUG !== 'enabled') { + return; + } + return this.writeLog('debug', message, metadata); + } + + async info (message, metadata) { + if (process.env.DTP_LOG_INFO !== 'enabled') { + return; + } + return this.writeLog('info', message, metadata); + } + + async warn (message, metadata) { + if (process.env.DTP_LOG_WARN !== 'enabled') { + return; + } + return this.writeLog('warn', message, metadata); + } + + async alert (message, metadata) { + return this.writeLog('alert', message, metadata); + } + + async error (message, metadata) { + return this.writeLog('error', message, metadata); + } + + async crit (message, metadata) { + return this.writeLog('crit', message, metadata); + } + + async fatal (message, metadata) { + this.writeLog('fatal', message, metadata); + } + + async writeLog (level, message, metadata) { + const NOW = new Date(); + const { componentName } = this; + + if (process.env.DTP_LOG_CONSOLE === 'enabled') { + let clevel = level.padEnd(5); + switch (level) { + case 'debug': + clevel = color.darkGray(clevel); + break; + case 'info': + clevel = color.green(clevel); + break; + case 'warn': + clevel = color.yellow(clevel); + break; + case 'alert': + clevel = color.red(clevel); + break; + case 'error': + clevel = color.bgRed.white(clevel); + break; + case 'crit': + clevel = color.bgRed.yellow(clevel); + break; + case 'fatal': + clevel = color.bgRed.darkGray(clevel); + break; + } + + const ctimestamp = color.darkGray(moment(NOW).format('YYYY-MM-DD HH:mm:ss.SSS')); + const ccomponentName = color.cyan(componentName); + const cmessage = color.darkGray(message); + if (metadata) { + console.log(`${ctimestamp} ${clevel} ${ccomponentName} ${cmessage}`, util.inspect(metadata, false, Infinity, true)); + } else { + console.log(`${ctimestamp} ${clevel} ${ccomponentName} ${cmessage}`); + } + } + + if (LogModel && (process.env.DTP_LOG_MONGODB === 'enabled')) { + await LogModel.create({ created: NOW, level, componentName, message, metadata }); + } + + if (LogStream && (process.env.DTP_LOG_FILE === 'enabled')) { + const logEntry = { + t: NOW, c: componentName, l: level, m: message, d: metadata, + }; + LogStream.write(`${JSON.stringify(logEntry)}\n`); + } + } +} \ No newline at end of file diff --git a/lib/site-service.js b/lib/site-service.js new file mode 100644 index 0000000..40da681 --- /dev/null +++ b/lib/site-service.js @@ -0,0 +1,26 @@ +// site-service.js +// Copyright (C) 2022,2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import { SiteCommon } from './site-common.js'; +import { SiteLog } from './site-log.js'; + +export class SiteService extends SiteCommon { + + constructor (dtp, definition) { + super(dtp); + this.slug = definition.slug; + this.name = definition.name; + this.log = new SiteLog(dtp, `svc:${this.slug}`); + } + + async start ( ) { + this.log.debug(`starting ${this.name} service`); + } + + async stop ( ) { + this.log.debug(`stopping ${this.name} service`); + } +} \ No newline at end of file diff --git a/lib/site-tripwire.js b/lib/site-tripwire.js new file mode 100644 index 0000000..37c5a4d --- /dev/null +++ b/lib/site-tripwire.js @@ -0,0 +1,32 @@ +// site-tripwire.js +// Copyright (C) 2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import path from 'node:path'; +import { SiteLog } from './site-log.js'; + +export class SiteTripwire { + + constructor (dtp) { + this.dtp = dtp; + this.log = new SiteLog(this, 'Harness'); + } + + async start ( ) { + this.blockedPaths = (await import(path.join(this.dtp.config.root, 'config', 'tripwire.js'))).default; + } + + async guard (req, res, next) { + // Tripwire looks for known-bad URLs, malicious URLs, and requests that indicate + // the client is "snooping" and shuts them down. + const path = this.blockedPaths.find((path) => req.path.startsWith(path)); + if (!path) { + return next(); + } + + this.log.alert('tripwire path requested', { path, ip: req.ip }); + return res.status(403).end(); + } +} \ No newline at end of file diff --git a/package.json b/package.json index 653d519..383d148 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,24 @@ "private": true, "dependencies": { "@fortawesome/fontawesome-free": "^6.5.1", + "@socket.io/redis-adapter": "^8.3.0", + "@socket.io/redis-emitter": "^5.1.0", + "ansicolor": "^2.0.3", "dotenv": "^16.4.5", "express": "^4.19.2", "glob": "^10.3.10", + "ioredis": "^5.3.2", "marked": "^12.0.1", - "mongoose": "^8.2.3", + "mediasoup": "^3.13.24", + "moment": "^2.30.1", + "mongoose": "^8.2.4", + "morgan": "^1.10.0", "multer": "^1.4.5-lts.1", "numeral": "^2.0.6", "pretty-checkbox": "^3.0.3", "pug": "^3.0.2", + "rotating-file-stream": "^3.2.1", + "slug": "^9.0.0", "socket.io": "^4.7.5", "striptags": "^3.2.0" }, diff --git a/webpack.config.js b/webpack.config.js index 8b3a239..1f0ec00 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,11 +12,10 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import BrowserSyncPlugin from 'browser-sync-webpack-plugin'; const webpackMode = (process.env.NODE_ENV === 'production') ? 'production' : 'development'; -console.log('Webpack mode:', webpackMode); -const plugins = [ ]; - -plugins.push(new MiniCssExtractPlugin()); +const plugins = [ + new MiniCssExtractPlugin(), +]; if (webpackMode === 'development') { plugins.push( diff --git a/yarn.lock b/yarn.lock index 2788922..76696cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -961,6 +961,11 @@ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz#55cc8410abf1003b726324661ce5b0d1c10de258" integrity sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw== +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -1067,6 +1072,24 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== +"@socket.io/redis-adapter@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@socket.io/redis-adapter/-/redis-adapter-8.3.0.tgz#bdce1e8f34c07df4a8baf98170bf24dc84eaed4a" + integrity sha512-ly0cra+48hDmChxmIpnESKrc94LjRL80TEmZVscuQ/WWkRP81nNj8W8cCGMqbI4L6NCuAaPRSzZF1a9GlAxxnA== + dependencies: + debug "~4.3.1" + notepack.io "~3.0.1" + uid2 "1.0.0" + +"@socket.io/redis-emitter@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/redis-emitter/-/redis-emitter-5.1.0.tgz#faab6dc363135e44a5a37b5ef5d4f723c80e0db1" + integrity sha512-QQUFPBq6JX7JIuM/X1811ymKlAfwufnQ8w6G2/59Jaqp09hdF1GJ/+e8eo/XdcmT0TqkvcSa2TT98ggTXa5QYw== + dependencies: + debug "~4.3.1" + notepack.io "~3.0.1" + socket.io-parser "~4.2.1" + "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053" @@ -1089,6 +1112,13 @@ dependencies: "@types/node" "*" +"@types/debug@^4.1.12": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + "@types/eslint-scope@^3.7.3": version "3.7.7" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" @@ -1115,11 +1145,21 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/ini@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@types/ini/-/ini-4.1.0.tgz#20e7327b3133627f84304210670d6406cceaba25" + integrity sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w== + "@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/ms@*": + version "0.7.34" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" + integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== + "@types/node@*", "@types/node@>=10.0.0": version "20.11.30" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f" @@ -1419,6 +1459,11 @@ ansi-wrap@0.1.0, ansi-wrap@^0.1.0: resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" integrity sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw== +ansicolor@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/ansicolor/-/ansicolor-2.0.3.tgz#ec4448ae5baf8c2d62bf2dad52eac06ba0b5ea21" + integrity sha512-pzusTqk9VHrjgMCcTPDTTvfJfx6Q3+L5tQ6yKC8Diexmoit4YROTFIkxFvRTNL9y5s0Q8HrSrgerCD5bIC+Kiw== + anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1554,6 +1599,13 @@ base64id@2.0.0, base64id@~2.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== +basic-auth@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -1764,6 +1816,11 @@ chokidar@^3.5.1, chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" @@ -1819,6 +1876,11 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -2005,6 +2067,11 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + data-view-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" @@ -2044,7 +2111,7 @@ debug@2.6.9, debug@^2.2.0: dependencies: ms "2.0.0" -debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: +debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -2081,7 +2148,12 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -depd@2.0.0: +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + +depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -2496,6 +2568,14 @@ fastest-levenshtein@^1.0.12: resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + filelist@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" @@ -2549,6 +2629,11 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +flatbuffers@^24.3.7: + version "24.3.25" + resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-24.3.25.tgz#e2f92259ba8aa53acd0af7844afb7c7eb95e7089" + integrity sha512-3HDgPbgiwWMI9zVB7VYBHaMrbOO7Gm0v+yD2FV/sCKj+9NDeVL7BOBYUuhWAQGKWOzBo8S9WdMvV0eixO233XQ== + follow-redirects@^1.0.0: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" @@ -2569,6 +2654,13 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -2598,6 +2690,13 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^2.0.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2722,6 +2821,14 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +h264-profile-level-id@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/h264-profile-level-id/-/h264-profile-level-id-2.0.0.tgz#b7ea45badbac8f5dbb9583d34b06db09764f2535" + integrity sha512-X4CLryVbVA0CtjTExS4G5U1gb2Z4wa32AF8ukVmFuLdw2JRq2aHisor7SY5SYTUUrUSqq0KdPIO18sql6IWIQw== + dependencies: + "@types/debug" "^4.1.12" + debug "^4.3.4" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -2874,6 +2981,11 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== +ini@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.2.tgz#7f646dbd9caea595e61f88ef60bfff8b01f8130a" + integrity sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw== + internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" @@ -2888,6 +3000,21 @@ interpret@^3.1.1: resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== +ioredis@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" + integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -3303,6 +3430,16 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + lodash.isfinite@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz#fb89b65a9a80281833f0b7478b3a5104f898ebb3" @@ -3367,6 +3504,20 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +mediasoup@^3.13.24: + version "3.13.24" + resolved "https://registry.yarnpkg.com/mediasoup/-/mediasoup-3.13.24.tgz#c18e327263529a7992eb75a8b16b35b66e38c19a" + integrity sha512-7kAJP/4YIoK+UaioekQfyLEzJF7ngcVgog4TI3E08QOYS3WPR8Njx0Xsde7bkQZ/NoAy+6diTFN1vuC3GztCnQ== + dependencies: + "@types/ini" "^4.1.0" + debug "^4.3.4" + flatbuffers "^24.3.7" + h264-profile-level-id "^2.0.0" + ini "^4.1.2" + node-fetch "^3.3.2" + supports-color "^9.4.0" + tar "^6.2.0" + memfs@^4.6.0: version "4.8.0" resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.8.0.tgz#0ea1ecb137219883c2e7c5139f4fa109935f7e39" @@ -3473,11 +3624,31 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0": version "7.0.4" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mitt@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.2.0.tgz#cb24e6569c806e31bd4e3995787fe38a04fdf90d" @@ -3490,6 +3661,16 @@ mkdirp@^0.5.4: dependencies: minimist "^1.2.6" +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +moment@^2.30.1: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + mongodb-connection-string-url@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz#b4f87f92fd8593f3b9365f592515a06d304a1e9c" @@ -3507,10 +3688,10 @@ mongodb@6.3.0: bson "^6.2.0" mongodb-connection-string-url "^3.0.0" -mongoose@^8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.2.3.tgz#26c2074b0d65fa83fa2fd899d3327a2a820fd4c8" - integrity sha512-ZB8K8AgbVgLCcqjtmZMxaQBEztwEEZCtAIPMx2Q56Uo4WWKmwf5Nu/EEIFo8d/17P946X0z6xzxwIqCxUMKxrA== +mongoose@^8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.2.4.tgz#785234f928aeabc1b1859b555c97fc18adc6ff8c" + integrity sha512-da/r6zpG+2eAXuhBGUnL6jcBd03zlytoCc5/wq+LyTsmrY9hhPQmSpnugwnfqldtBmUOhB6iMLoV4hNtHRq+ww== dependencies: bson "^6.2.0" kareem "2.5.1" @@ -3520,6 +3701,17 @@ mongoose@^8.2.3: ms "2.1.3" sift "16.0.1" +morgan@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" + integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ== + dependencies: + basic-auth "~2.0.1" + debug "2.6.9" + depd "~2.0.0" + on-finished "~2.3.0" + on-headers "~1.0.2" + mpath@0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904" @@ -3583,6 +3775,20 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-releases@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" @@ -3624,6 +3830,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +notepack.io@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/notepack.io/-/notepack.io-3.0.1.tgz#2c2c9de1bd4e64a79d34e33c413081302a0d4019" + integrity sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg== + numeral@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" @@ -3668,6 +3879,11 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -4053,6 +4269,18 @@ rechoir@^0.8.0: dependencies: resolve "^1.20.0" +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + regenerate-unicode-properties@^10.1.0: version "10.1.1" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" @@ -4177,6 +4405,11 @@ rollup@^2.43.1: optionalDependencies: fsevents "~2.3.2" +rotating-file-stream@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/rotating-file-stream/-/rotating-file-stream-3.2.1.tgz#1d0a536d75884eedc3a677f5b0871fdc69f97d22" + integrity sha512-n2B18CJb+n2VA5Tdle+1NP2toEcRv68CjAOBjHmwcyswNwMVsrN3gVRZ9ymH3sapaiGY8jc9OhhV5b6I5rAeiA== + rx@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" @@ -4192,16 +4425,16 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-buffer@5.2.1, safe-buffer@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" @@ -4425,6 +4658,11 @@ simple-update-notifier@^2.0.0: dependencies: semver "^7.5.3" +slug@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/slug/-/slug-9.0.0.tgz#68f968a79ce5156c6606b7b2e233ed0ffab94bdf" + integrity sha512-ixytnHlpHPWM56heaGgYe/M8tDAcpJcsg/zBuyElbFDOORzMGOeP3Te6iJBRVYu3WQEiWLQPb70Gh9ig/sZgGQ== + socket.io-adapter@~2.5.2: version "2.5.4" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz#4fdb1358667f6d68f25343353bd99bd11ee41006" @@ -4443,7 +4681,7 @@ socket.io-client@^4.4.1: engine.io-client "~6.5.2" socket.io-parser "~4.2.4" -socket.io-parser@~4.2.4: +socket.io-parser@~4.2.1, socket.io-parser@~4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== @@ -4506,6 +4744,11 @@ sparse-bitfield@^3.0.3: dependencies: memory-pager "^1.0.2" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -4681,6 +4924,11 @@ supports-color@^8.0.0, supports-color@^8.1.1: dependencies: has-flag "^4.0.0" +supports-color@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954" + integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw== + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -4691,6 +4939,18 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tar@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + temp-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" @@ -4852,6 +5112,11 @@ ua-parser-js@^1.0.33: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f" integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ== +uid2@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-1.0.0.tgz#ef8d95a128d7c5c44defa1a3d052eecc17a06bfb" + integrity sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ== + uikit@^3.19.2: version "3.19.2" resolved "https://registry.yarnpkg.com/uikit/-/uikit-3.19.2.tgz#e2b7c3af3124835db38804a8e1e07f93eb97a015" @@ -4982,6 +5247,11 @@ watchpack@^2.4.1: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"