diff --git a/src/schemas/statSchema.js b/src/schemas/statSchema.js index 0aed906..cdcd902 100644 --- a/src/schemas/statSchema.js +++ b/src/schemas/statSchema.js @@ -70,14 +70,6 @@ statSchema.statics.getStats = async function(){ var stats = await this.findOne({}); if(stats){ - //If the current listed version doesnt match current running version - if(stats.version != package.version){ - //Update stat version - stats.version = package.version; - //Save stat document - await stats.save(); - } - //If we found something then the statistics document exist and this is it, //So long as no one else has fucked with the database it should be the only one. (is this forshadowing for a future bug?) return stats; diff --git a/src/schemas/tokebot/tokeCommandSchema.js b/src/schemas/tokebot/tokeCommandSchema.js index 88b2709..3694afa 100644 --- a/src/schemas/tokebot/tokeCommandSchema.js +++ b/src/schemas/tokebot/tokeCommandSchema.js @@ -120,7 +120,7 @@ tokeCommandSchema.statics.loadDefaults = async function(){ if(toke != null){ loggerUtils.dumpError(err); }else{ - loggerUtils.consoleWarn("Error, null toke!"); + loggerUtils.consoleShout("Error, null toke!"); } } } diff --git a/src/schemas/user/migrationSchema.js b/src/schemas/user/migrationSchema.js index c516519..9092bf8 100644 --- a/src/schemas/user/migrationSchema.js +++ b/src/schemas/user/migrationSchema.js @@ -105,7 +105,7 @@ migrationSchema.statics.ingestLegacyDump = async function(){ //If we caught an error (most likely it's missing) }catch(err){ - loggerUtils.consoleWarn("No migration files detected! Pleas provide legacy migration files or disable migration from config.json!"); + loggerUtils.consoleShout("No migration files detected! Pleas provide legacy migration files or disable migration from config.json!"); //BAIL! return; } @@ -151,7 +151,7 @@ migrationSchema.statics.ingestLegacyDump = async function(){ //Pass toke logs over to the stat model for further ingestion await tokeModel.ingestLegacyTokes(tokeLogs); - loggerUtils.consoleWarn(`Legacy Server Migration Completed at: ${new Date().toLocaleString()}`); + loggerUtils.consoleShout(`Legacy Server Migration Completed at: ${new Date().toLocaleString()}`); }catch(err){ return loggerUtils.localExceptionHandler(err); } @@ -174,8 +174,8 @@ migrationSchema.statics.ingestLegacyUser = async function(rawProfile){ //If we have an invalid line if(profileMatches <= 0){ - loggerUtils.consoleWarn('Bad profile detected in legacy dump:'); - loggerUtils.consoleWarn(rawProfile); + loggerUtils.consoleShout('Bad profile detected in legacy dump:'); + loggerUtils.consoleShout(rawProfile); //BAIL! return; } @@ -199,8 +199,8 @@ migrationSchema.statics.ingestLegacyUser = async function(rawProfile){ //If profile array is the wrong length if(profileArray.length != 10){ - loggerUtils.consoleWarn('Bad profile detected in legacy dump:'); - loggerUtils.consoleWarn(profileArray); + loggerUtils.consoleShout('Bad profile detected in legacy dump:'); + loggerUtils.consoleShout(profileArray); //BAIL! return; } @@ -212,7 +212,7 @@ migrationSchema.statics.ingestLegacyUser = async function(rawProfile){ //If we found the user in the database if(foundMigration != null || foundUser != null){ //Scream - loggerUtils.consoleWarn(`Found legacy user ${profileArray[1]} in database, skipping migration!`); + loggerUtils.consoleShout(`Found legacy user ${profileArray[1]} in database, skipping migration!`); //BAIL! return; } diff --git a/src/server.js b/src/server.js index 4330a53..8e78e99 100644 --- a/src/server.js +++ b/src/server.js @@ -40,6 +40,7 @@ const configCheck = require('./utils/configCheck'); const scheduler = require('./utils/scheduler'); const {errorMiddleware} = require('./utils/loggerUtils'); const sessionUtils = require('./utils/sessionUtils'); +const upgradeUtils = require('./utils/upgradeUtils'); //Validator const accountValidator = require('./validators/accountValidator'); //DB Model @@ -231,6 +232,9 @@ async function asyncKickStart(){ //Load default emotes await emoteModel.loadDefaults(); + //Check for and handle any server upgrades + await upgradeUtils.upgradeCheck(); + //Kick off scheduled-jobs scheduler.kickoff(); diff --git a/src/utils/configCheck.js b/src/utils/configCheck.js index 84fd22d..d45e1c8 100644 --- a/src/utils/configCheck.js +++ b/src/utils/configCheck.js @@ -31,52 +31,52 @@ module.exports.securityCheck = function(){ //Check Protocol if(config.protocol.toLowerCase() != 'https'){ //If it's insecure then warn the admin - loggerUtil.consoleWarn("Starting in HTTP mode. This server should be used for development purposes only!"); + loggerUtil.consoleShout("Starting in HTTP mode. This server should be used for development purposes only!"); } //Check mail protocol if(!config.mail.secure){ //If it's insecure then warn the admin - loggerUtil.consoleWarn("Mail transport security disabled! This server should be used for development purposes only!"); + loggerUtil.consoleShout("Mail transport security disabled! This server should be used for development purposes only!"); } //check password pepper if(!validator.isStrongPassword(config.secrets.passwordSecret) || config.secrets.passwordSecret == "CHANGE_ME"){ - loggerUtil.consoleWarn("Insecure Password Secret! Change Password Secret!"); + loggerUtil.consoleShout("Insecure Password Secret! Change Password Secret!"); } //check RememberMe pepper if(!validator.isStrongPassword(config.secrets.rememberMeSecret) || config.secrets.rememberMeSecret == "CHANGE_ME"){ - loggerUtil.consoleWarn("Insecure RememberMe Secret! Change RememberMe Secret!"); + loggerUtil.consoleShout("Insecure RememberMe Secret! Change RememberMe Secret!"); } //check session secret if(!validator.isStrongPassword(config.secrets.sessionSecret) || config.secrets.sessionSecret == "CHANGE_ME"){ - loggerUtil.consoleWarn("Insecure Session Secret! Change Session Secret!"); + loggerUtil.consoleShout("Insecure Session Secret! Change Session Secret!"); } //check altcha secret if(!validator.isStrongPassword(config.secrets.altchaSecret) || config.secrets.altchaSecret == "CHANGE_ME"){ - loggerUtil.consoleWarn("Insecure Altcha Secret! Change Altcha Secret!"); + loggerUtil.consoleShout("Insecure Altcha Secret! Change Altcha Secret!"); } //check ipHash secret if(!validator.isStrongPassword(config.secrets.ipSecret) || config.secrets.ipSecret == "CHANGE_ME"){ - loggerUtil.consoleWarn("Insecure IP Hashing Secret! Change IP Hashing Secret!"); + loggerUtil.consoleShout("Insecure IP Hashing Secret! Change IP Hashing Secret!"); } //check DB pass if(!validator.isStrongPassword(config.db.pass) || config.db.pass == "CHANGE_ME" || config.db.pass == config.db.user){ - loggerUtil.consoleWarn("Insecure Database Password! Change Database password!"); + loggerUtil.consoleShout("Insecure Database Password! Change Database password!"); } //check email pass if(!validator.isStrongPassword(config.mail.pass) || config.mail.pass == "CHANGE_ME"){ - loggerUtil.consoleWarn("Insecure Email Password! Change Email password!"); + loggerUtil.consoleShout("Insecure Email Password! Change Email password!"); } //check debug mode if(config.debug){ - loggerUtil.consoleWarn("Debug mode enabled! Understand the risks and security implications before enabling on production servers!"); + loggerUtil.consoleShout("Debug mode enabled! Understand the risks and security implications before enabling on production servers!"); } } \ No newline at end of file diff --git a/src/utils/loggerUtils.js b/src/utils/loggerUtils.js index bdac8db..1835e58 100644 --- a/src/utils/loggerUtils.js +++ b/src/utils/loggerUtils.js @@ -142,12 +142,20 @@ module.exports.socketCriticalExceptionHandler = function(socket, err){ return socket.disconnect(); } +/** + * Shouts into the server console + * @param {String} string - String to shout into to the console + */ +module.exports.consoleShout = function(string){ + console.warn('\x1b[31m\x1b[4m%s\x1b[0m',string); +} + /** * Prints warning text to server console * @param {String} string - String to print to console */ module.exports.consoleWarn = function(string){ - console.warn('\x1b[31m\x1b[4m%s\x1b[0m',string); + console.warn('\x1b[33m%s\x1b[0m',string); } /** @@ -208,7 +216,7 @@ module.exports.dumpLog = async function(content, name, subDir = '/', muzzle = fa }catch(err){ if(!muzzle){ //Shout about it - module.exports.consoleWarn("Log folder missing, mking dir!") + module.exports.consoleShout("Log folder missing, mking dir!") } //Make it if doesn't @@ -223,17 +231,17 @@ module.exports.dumpLog = async function(content, name, subDir = '/', muzzle = fa if(!muzzle){ //Whine about the error - module.exports.consoleWarn(`Warning: Unexpected Server Crash gracefully dumped to '${path}'... SOMETHING MAY BE VERY BROKEN!!!!`); + module.exports.consoleShout(`Warning: Unexpected Server Crash gracefully dumped to '${path}'... SOMETHING MAY BE VERY BROKEN!!!!`); } //If somethine went really really wrong }catch(doubleErr){ if(!muzzle){ //Use humor to cope with the pain - module.exports.consoleWarn("Yo Dawg, I herd you like errors, so I put an error in your error dump, so you can dump while you dump:"); + module.exports.consoleShout("Yo Dawg, I herd you like errors, so I put an error in your error dump, so you can dump while you dump:"); //Dump the original error to console - module.exports.consoleWarn(err); + module.exports.consoleShout(err); //Dump the error we had saving that error to file to console - module.exports.consoleWarn(doubleErr); + module.exports.consoleShout(doubleErr); } } } diff --git a/src/utils/sessionUtils.js b/src/utils/sessionUtils.js index fd8606b..bb746a9 100644 --- a/src/utils/sessionUtils.js +++ b/src/utils/sessionUtils.js @@ -18,6 +18,7 @@ along with this program. If not, see .*/ const {validationResult, matchedData} = require('express-validator'); //Local Imports +const server = require('../server.js'); const config = require('../../config.json'); const {userModel} = require('../schemas/user/userSchema.js'); const userBanModel = require('../schemas/user/userBanSchema.js'); @@ -187,6 +188,22 @@ module.exports.killSession = async function(session){ session.destroy(); } +module.exports.killAllSessions = function(){ + //Grab the entire session store from the server + server.store.all((err, sessions) => { + //You guys ever hear of a 'not my' problem? Fucking y33tskies lmao, better use a try/catch + if(err){ + throw err; + } + + //crawl through active sessions + sessions.forEach((session) => { + //Fuckin' nukem! + server.store.destroy(session.seshid); + }); + }); +} + /** * Returns how many failed login attempts within the past day or so since the last login has occured for a given user * @param {String} user - User to check map against diff --git a/src/utils/upgradeUtils.js b/src/utils/upgradeUtils.js new file mode 100644 index 0000000..be6e52c --- /dev/null +++ b/src/utils/upgradeUtils.js @@ -0,0 +1,177 @@ + /*Canopy - The next generation of stoner streaming software +Copyright (C) 2024-2026 Rainbownapkin and the TTN Community + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see .*/ + +//Config Imports +const package = require('../../package.json'); + +//Local Imports +const loggerUtils = require('./loggerUtils'); +const sessionUtils = require('./sessionUtils'); +const statModel = require('../schemas/statSchema'); +const { userModel } = require('../schemas/user/userSchema'); +const channelModel = require('../schemas/channel/channelSchema'); +const archiveModel = require('../schemas/channel/media/archivedMediaSchema'); +const { channelPlaylistModel } = require('../schemas/channel/media/playlist/playlistSchema'); +const { userPlaylistModel } = require('../schemas/channel/media/playlist/playlistSchema'); +const archivedMedia = require('../app/channel/media/archivedMedia'); + +/** + * Checks server for recent upgrades upon boot + */ +module.exports.upgradeCheck = async function(){ + //Get the server statistics document from Database + const stats = await statModel.getStats(); + + //Throw this flag if an upgrade is initiated to automagically and silently initiate all upgrades after it + let upgradeStarted = false; + + //If we're upgrading from Panama Red (or older) + if(stats.version == "<0.2.0"){ + //Initiate upgrade procedure from Panama Red + await upgradePanamaRed(); + + //Throw upgrade started flag + upgradeStarted = true; + } + + //If we upgraded + if(upgradeStarted){ + //Update the statistics document to reflect the new version + stats.version = package.version; + //Write the statistics document to the DB + await stats.save(); + } + + loggerUtils.consoleShout(`Database Upgrading to ${package.canopyDisplayVersion} Complete!`); +} + +/** + * Upgrades server database from Panama Red + * @param {Boolean} quiet - tells upgrade logic to STFU! + */ +async function upgradePanamaRed(quiet = false){ + //If we're bein' loud + if(!quiet){ + upgradeWarn("0.1-Alpha (Panama Red)"); + } + + //Warn of impending session death + loggerUtils.consoleWarn("Killing all active sessions!"); + //Nuke existing sessions since the new version requires the user _id in there + sessionUtils.killAllSessions(); + + //Pull all channels into RAM + const channels = await channelModel.find(); + //Pull all users into RAM + const users = await userModel.find(); + + //For every channel on the server + for(channel of channels){ + //Create variables to track upgraded objects + let archived = 0; + let playlists = 0; + + //log the migration + loggerUtils.consoleWarn(`Upgrading Channel '${channel.name}'..`); + + //Iterate through the given channels media archive + for(entry of channel.media.archived){ + //Re-hydrate archived media object + const mediaObject = archivedMedia.fromQueuedMedia(entry.rehydrate()) + + //Tattoo channel name into media object + mediaObject.channel = channel.name; + + //Add media object to the site-wide media archive + await archiveModel.create(mediaObject); + + //We could just pull the archvied array count, but this feels more honest XP + archived++; + } + + //Iterate through the given channels playlists + for(playlist of channel.media.playlists){ + //Create channel playlist in site-wide collection + await channelPlaylistModel.create({ + name: playlist.name, + media: playlist.media, + defaultTitles: playlist.defaultTitles, + channel: channel._id + }) + + //We could just pull the playlists array count, but this feels more honest XP + playlists++; + } + + //If we did anything + if(playlists > 0 || archived > 0){ + //Clear out archived media from channel document + channel.media.archived = []; + + //Clear out playlists from channel document + channel.media.playlists = []; + + //Save updated channel document + await channel.save(); + + //log the migration + loggerUtils.consoleWarn(`Channel '${channel.name}; Upgrade Complete! Optimized ${playlists} Playlist(s), and ${archived} Media Object(s)!`); + } + } + + //For each registered user on the server + for(user of users){ + //Create variable to track upgraded playlist count + let playlists = 0; + + //Iterate through users playlists + for(playlist of user.playlists){ + //Create channel playlist in site-wide collection + await userPlaylistModel.create({ + name: playlist.name, + media: playlist.media, + defaultTitles: playlist.defaultTitles, + user: user._id + }) + + //We could just pull the playlists array count, but this feels more honest XP + playlists++; + } + + //If we did anything + if(playlists > 0){ + //Clear out playlists from channel document + user.playlists = []; + + //Save updated channel document + await user.save(); + + //log the migration + loggerUtils.consoleWarn(`User '${user.user}' Upgrade Complete! Optimized ${playlists} Playlist(s)!`); + } + } +} + +/** + * Warns server admin of impending Database upgrades + * @param {String} versionString - Version we're upgrading from + */ +function upgradeWarn(versionString){ + //Rise and Shout the ~~AlphaSmart's~~ upgrade's out! + //https://eeggs.com/images/items/2942.full.jpg + loggerUtils.consoleShout("Server Upgrade Detected!"); + loggerUtils.consoleShout(`Upgrading Database from ${versionString} to ${package.canopyDisplayVersion}`); +} \ No newline at end of file