diff --git a/src/schemas/tokebot/tokeCommandSchema.js b/src/schemas/tokebot/tokeCommandSchema.js index fffbe9a..164127f 100644 --- a/src/schemas/tokebot/tokeCommandSchema.js +++ b/src/schemas/tokebot/tokeCommandSchema.js @@ -21,6 +21,9 @@ const {mongoose} = require('mongoose'); const defaultTokes = require("../../../defaultTokes.json"); const server = require('../../server'); +/** + * Mongoose Schema representing a toke command + */ const tokeCommandSchema = new mongoose.Schema({ command:{ type: mongoose.SchemaTypes.String, @@ -28,8 +31,10 @@ const tokeCommandSchema = new mongoose.Schema({ } }); +/** + * Pre-Save middleware, ensures tokebot receives all new toke commands + */ tokeCommandSchema.pre('save', async function (next){ - //if the command was changed if(this.isModified("command")){ //Get server tokebot object @@ -44,6 +49,9 @@ tokeCommandSchema.pre('save', async function (next){ next(); }); +/** + * Pre-Delete middleware, ensures tokebot removes all old toke commands + */ tokeCommandSchema.pre('deleteOne', {document: true}, async function (next){ //Get server tokebot object (isn't this a fun dot crawler? Why hasn't anyone asked me to stop writing software yet?) const tokebot = server.channelManager.chatHandler.commandPreprocessor.tokebot; @@ -55,6 +63,10 @@ tokeCommandSchema.pre('deleteOne', {document: true}, async function (next){ next(); }); +/** + * Pulls command strings from DB and reports back + * @returns {Array} Array of toke commands pulled from the DB + */ tokeCommandSchema.statics.getCommandStrings = async function(){ //Get all toke commands in the DB const tokeDB = await this.find({}); @@ -71,6 +83,9 @@ tokeCommandSchema.statics.getCommandStrings = async function(){ return tokeArray; } +/** + * Loads default tokes into the DB from flat file upon launch + */ tokeCommandSchema.statics.loadDefaults = async function(){ //Make sure registerToke function is happy const _this = this; diff --git a/src/schemas/user/emailChangeSchema.js b/src/schemas/user/emailChangeSchema.js index 8ad104d..bffd13f 100644 --- a/src/schemas/user/emailChangeSchema.js +++ b/src/schemas/user/emailChangeSchema.js @@ -30,8 +30,14 @@ const {mongoose} = require('mongoose'); const hashUtil = require('../../utils/hashUtils'); const mailUtils = require('../../utils/mailUtils'); +/** + * Email change token retention time + */ const daysToExpire = 7; +/** + * DB Schema for Document representing a single email change request + */ const emailChangeSchema = new mongoose.Schema({ user: { type: mongoose.SchemaTypes.ObjectID, @@ -60,7 +66,9 @@ const emailChangeSchema = new mongoose.Schema({ }); -//Presave function +/** + * Pre-Save function, ensures IP's are hashed and previous requests are deleted upon request creation + */ emailChangeSchema.pre('save', async function (next){ //If we're saving an ip if(this.isModified('ipHash')){ @@ -76,7 +84,9 @@ emailChangeSchema.pre('save', async function (next){ next(); }); -//statics +/** + * Schedulable function for processing expired email change requests + */ emailChangeSchema.statics.processExpiredRequests = async function(){ //Pull all requests from the DB //Tested finding request by date, but mongoose kept throwing casting errors. @@ -96,7 +106,9 @@ emailChangeSchema.statics.processExpiredRequests = async function(){ } } -//methods +/** + * Consumes email change token, changing email address on a given user + */ emailChangeSchema.methods.consume = async function(){ //Populate the user reference await this.populate('user'); @@ -137,6 +149,10 @@ emailChangeSchema.methods.consume = async function(){ } +/** + * Generates email change URL from a given token + * @returns {String} Email change URL generated from token + */ emailChangeSchema.methods.getChangeURL = function(){ //Check for default port based on protocol if((config.protocol == 'http' && config.port == 80) || (config.protocol == 'https' && config.port == 443 || config.proxied)){ @@ -148,6 +164,10 @@ emailChangeSchema.methods.getChangeURL = function(){ } } +/** + * Calculates days until token expiration + * @returns {Number} Days until token expiration + */ emailChangeSchema.methods.getDaysUntilExpiration = function(){ //Get request date const expirationDate = new Date(this.date); diff --git a/src/schemas/user/passwordResetSchema.js b/src/schemas/user/passwordResetSchema.js index 6fad8fd..2822594 100644 --- a/src/schemas/user/passwordResetSchema.js +++ b/src/schemas/user/passwordResetSchema.js @@ -30,8 +30,14 @@ const {mongoose} = require('mongoose'); const hashUtil = require('../../utils/hashUtils.js'); const loggerUtils = require('../../utils/loggerUtils.js') +/** + * Password reset token retention time + */ const daysToExpire = 7; +/** + * DB Schema for documents containing a single expiring password reset token + */ const passwordResetSchema = new mongoose.Schema({ user: { type: mongoose.SchemaTypes.ObjectID, @@ -56,7 +62,9 @@ const passwordResetSchema = new mongoose.Schema({ }); -//Presave function +/** + * Pre-save function, ensures IP's are hashed before saving + */ passwordResetSchema.pre('save', async function (next){ //If we're saving an ip if(this.isModified('ipHash')){ @@ -67,7 +75,9 @@ passwordResetSchema.pre('save', async function (next){ next(); }); -//statics +/** + * Schedulable function for processing expired reset requests + */ passwordResetSchema.statics.processExpiredRequests = async function(){ //Pull all requests from the DB //Tested finding request by date, but mongoose kept throwing casting errors. @@ -88,6 +98,11 @@ passwordResetSchema.statics.processExpiredRequests = async function(){ } //methods +/** + * Resets password and consumes token + * @param {String} pass - New password to set + * @param {String} confirmPass - Confirmation String to ensure new pass is correct + */ passwordResetSchema.methods.consume = async function(pass, confirmPass){ //Check confirmation pass if(pass != confirmPass){ @@ -110,6 +125,10 @@ passwordResetSchema.methods.consume = async function(pass, confirmPass){ await this.deleteOne(); } +/** + * Generates password reset URL off of the token object + * @returns {String} Reset URL + */ passwordResetSchema.methods.getResetURL = function(){ //Check for default port based on protocol if((config.protocol == 'http' && config.port == 80) || (config.protocol == 'https' && config.port == 443) || config.proxied){ @@ -121,6 +140,10 @@ passwordResetSchema.methods.getResetURL = function(){ } } +/** + * Returns number of days until token expiration + * @returns {Number} Number of days until token expiration + */ passwordResetSchema.methods.getDaysUntilExpiration = function(){ //Get request date const expirationDate = new Date(this.date); diff --git a/src/schemas/user/userBanSchema.js b/src/schemas/user/userBanSchema.js index 88da80c..0c1db1c 100644 --- a/src/schemas/user/userBanSchema.js +++ b/src/schemas/user/userBanSchema.js @@ -22,6 +22,9 @@ const hashUtil = require('../../utils/hashUtils.js'); const {userModel} = require('./userSchema.js'); const loggerUtils = require('../../utils/loggerUtils.js'); +/** + * DB Schema for Documents representing a single user's ban + */ const userBanSchema = new mongoose.Schema({ user: { type: mongoose.SchemaTypes.ObjectID, @@ -64,6 +67,11 @@ const userBanSchema = new mongoose.Schema({ } }); +/** + * Checks ban by IP + * @param {String} ip - IP Address check for bans + * @returns {Mongoose.Document} Found ban Document if one exists. + */ userBanSchema.statics.checkBanByIP = async function(ip){ //Get hash of ip const ipHash = hashUtil.hashIP(ip); @@ -127,6 +135,11 @@ userBanSchema.statics.checkBanByIP = async function(ip){ return foundBan; } +/** + * Checks for bans by user DB doc + * @param {Mongoose.Document} userDB - User Doc to check + * @returns {Mongoose.Document} Found ban document for given user doc + */ userBanSchema.statics.checkBanByUserDoc = async function(userDB){ const banDB = await this.find({}); var foundBan = null; @@ -156,11 +169,21 @@ userBanSchema.statics.checkBanByUserDoc = async function(userDB){ return foundBan; } +/** + * Checks for ban by username + * @param {String} user - User to check for bans + * @returns {Mongoose.Document} Found User Ban DB Document + */ userBanSchema.statics.checkBan = async function(user){ const userDB = await userModel.findOne({user: user.user}); return this.checkBanByUserDoc(userDB); } +/** + * Looks through processed bans by user + * @param {String} user - user to check against for bans + * @returns {Mongoose.Document} Spent User Ban Document + */ userBanSchema.statics.checkProcessedBans = async function(user){ //Pull banlist and create empty variable to hold any found ban const banDB = await this.find({}); @@ -182,6 +205,14 @@ userBanSchema.statics.checkProcessedBans = async function(user){ return foundBan; } +/** + * Bans a given user by their user Document + * @param {Mongoose.Document} userDB - DB Doc of the user to ban + * @param {Boolean} permanent - Whether or not it's permanant + * @param {Number} expirationDays - Days to expire + * @param {Boolean} ipBan - Whether or not we're banning by IP + * @returns {Mongoose.Document} A freshly created User Ban DB Document :) + */ userBanSchema.statics.banByUserDoc = async function(userDB, permanent, expirationDays, ipBan = false){ //Prevent missing users if(userDB == null){ @@ -258,11 +289,24 @@ userBanSchema.statics.banByUserDoc = async function(userDB, permanent, expiratio } } +/** + * Bans user by username + * @param {String} user - Username of user to ban + * @param {Boolean} permanent - Whether or not it's permanant + * @param {Number} expirationDays - Days to expire + * @param {Boolean} ipBan - Whether or not we're banning by IP + * @returns {Mongoose.Document} A freshly created User Ban DB Document :) + */ userBanSchema.statics.ban = async function(user, permanent, expirationDays, ipBan){ const userDB = await userModel.findOne({user: user.user}); return this.banByUserDoc(userDB, permanent, expirationDays, ipBan); } +/** + * Unbans users by user doc + * @param {Mongoose.Document} userDB - User DB Document to unban + * @returns {Mongoose.Document} Old, deleted ban document + */ userBanSchema.statics.unbanByUserDoc = async function(userDB){ //Prevent missing users @@ -281,6 +325,12 @@ userBanSchema.statics.unbanByUserDoc = async function(userDB){ return oldBan; } +/** + * Unban deleted user + * Can't bring back accounts, but will re-allow re-use of old usernames, and new accounts/connections from banned IP's + * @param {String} user - Username of deleted account to unban + * @returns {Mongoose.Document} Old, deleted ban document + */ userBanSchema.statics.unbanDeleted = async function(user){ const banDB = await this.checkProcessedBans(user); @@ -292,6 +342,11 @@ userBanSchema.statics.unbanDeleted = async function(user){ return oldBan; } +/** + * Unbans user by username + * @param {String} user - Username of user to unban + * @returns Old, deleted ban document + */ userBanSchema.statics.unban = async function(user){ //Find user in DB const userDB = await userModel.findOne({user: user.user}); @@ -306,6 +361,10 @@ userBanSchema.statics.unban = async function(user){ } } +/** + * Generates Network-Friendly Browser-Digestable list of bans for the admin panel + * @returns {Object} Network-Friendly Browser-Digestable list of bans for the admin panel + */ userBanSchema.statics.getBans = async function(){ //Get the ban, populating users and alts const banDB = await this.find({}).populate('user').populate('alts'); @@ -352,6 +411,9 @@ userBanSchema.statics.getBans = async function(){ return bans; } +/** + * Scheduable function for processing expired user bans + */ userBanSchema.statics.processExpiredBans = async function(){ //Channel ban expirations may vary so there's no way to search for expired bans const banDB = await this.find({}); @@ -402,6 +464,10 @@ userBanSchema.statics.processExpiredBans = async function(){ } //methods +/** + * Calculates days until ban expiration + * @returns {Number} Days until ban expiration + */ userBanSchema.methods.getDaysUntilExpiration = function(){ //Get ban date const expirationDate = new Date(this.banDate); diff --git a/src/schemas/user/userSchema.js b/src/schemas/user/userSchema.js index 85d9f7b..40a3e6e 100644 --- a/src/schemas/user/userSchema.js +++ b/src/schemas/user/userSchema.js @@ -33,6 +33,9 @@ const mailUtil = require('../../utils/mailUtils'); const loggerUtils = require('../../utils/loggerUtils') +/** + * Mongoose Schema for a document representing a single canopy user + */ const userSchema = new mongoose.Schema({ id: { type: mongoose.SchemaTypes.Number, @@ -147,6 +150,9 @@ const userSchema = new mongoose.Schema({ }); //This is one of those places where you really DON'T want to use an arrow function over an anonymous one! +/** + * Pre-Save function for user document, handles password hashing, flair updates, emote refreshes, and kills sessions upon rank change + */ userSchema.pre('save', async function (next){ //If the password was changed @@ -190,7 +196,9 @@ userSchema.pre('save', async function (next){ next(); }); -//post-delete function (document not query) +/** + * Pre-Delete function for user accounts, drops connections and rips it self out from alt accounts + */ userSchema.post('deleteOne', {document: true}, async function (){ //Kill any active sessions await this.killAllSessions("If you're seeing this, your account has been deleted. So long, and thanks for all the fish! <3"); @@ -212,6 +220,11 @@ userSchema.post('deleteOne', {document: true}, async function (){ }); //statics +/** + * Registers a new user account with given information + * @param {Object} userObj - Object representing user to register, generated by the client + * @param {String} ip - IP Address of connection registering the account + */ userSchema.statics.register = async function(userObj, ip){ //Pull values from user object const {user, pass, passConfirm, email} = userObj; @@ -246,6 +259,13 @@ userSchema.statics.register = async function(userObj, ip){ } } +/** + * Authenticates a username and password pair + * @param {String} user - Username of account to login as + * @param {String} pass - Password to authenticat account + * @param {String} failLine - Line to paste into custom error upon login failure + * @returns {Mongoose.Document} - User DB Document upon success + */ userSchema.statics.authenticate = async function(user, pass, failLine = "Bad Username or Password."){ //check for missing pass if(!user || !pass){ @@ -274,6 +294,12 @@ userSchema.statics.authenticate = async function(user, pass, failLine = "Bad Use } } +/** + * Gets profile by username + * @param {String} user - name of user to find + * @param {Boolean} includeEmail - Whether or not to include email in the final result + * @returns {Object} A network-friendly browser-digestable Object representing a single user profile + */ userSchema.statics.findProfile = async function(user, includeEmail = false){ //Catch null users if(user == null || user.user == null){ @@ -309,6 +335,10 @@ userSchema.statics.findProfile = async function(user, includeEmail = false){ } +/** + * Tattoos a single toke callout to all the users within it + * @param {Map} tokemap - Map containing list of users and the toke command they used to join the toke + */ userSchema.statics.tattooToke = function(tokemap){ //For each toke, asynchronously: tokemap.forEach(async (toke, user) => { @@ -345,6 +375,11 @@ userSchema.statics.tattooToke = function(tokemap){ }); } +/** + * Acquires a list of the entire userbase + * @param {Boolean} fullList - Determines whether or not we're listing rank and email + * @returns {Array} Array of objects containing user metadata + */ userSchema.statics.getUserList = async function(fullList = false){ var userList = []; //Get all of our users @@ -379,6 +414,9 @@ userSchema.statics.getUserList = async function(fullList = false){ return userList; } +/** + * Process and Deletes Aged IP Records + */ userSchema.statics.processAgedIPRecords = async function(){ //Pull full userlist const users = await this.find({}); @@ -408,10 +446,19 @@ userSchema.statics.processAgedIPRecords = async function(){ //methods +/** + * Checks password against a user document + * @param {String} pass - Password to authenticate + * @returns {Boolean} True if authenticated + */ userSchema.methods.checkPass = function(pass){ return hashUtil.comparePassword(pass, this.pass) } +/** + * Lists authenticated sessions for a given user document + * @returns {Promise} Promise containing an array of active user sessions for a given user + */ userSchema.methods.getAuthenticatedSessions = async function(){ var returnArr = []; @@ -442,6 +489,11 @@ userSchema.methods.getAuthenticatedSessions = async function(){ } +/** + * Generates a network-friendly browser-digestable profile object for a sepcific user document + * @param {Boolean} includeEmail - Whether or not to include the email address in the complete profile object + * @returns {Object} Network-Friendly Browser-Digestable object containing profile metadata + */ userSchema.methods.getProfile = function(includeEmail = false){ //Create profile hashtable const profile = { @@ -465,6 +517,10 @@ userSchema.methods.getProfile = function(includeEmail = false){ return profile; } +/** + * Gets list of profiles of alt accounts related to the given user document + * @returns {Array} List of alternate profiles + */ userSchema.methods.getAltProfiles = async function(){ //Create an empty list to hold alt profiles const profileList = []; @@ -482,6 +538,11 @@ userSchema.methods.getAltProfiles = async function(){ return profileList; } +/** + * Sets flair for current user documetn + * @param {String} flair - flair to set + * @returns {Mongoose.Document} returns found flair document from DB + */ userSchema.methods.setFlair = async function(flair){ //Find flair by name const flairDB = await flairModel.findOne({name: flair}); @@ -493,6 +554,10 @@ userSchema.methods.setFlair = async function(flair){ return flairDB; } +/** + * Gets number of tokes for a given user document + * @returns {Number} Number of tokes recorded for the given user document + */ userSchema.methods.getTokeCount = function(){ //Set tokeCount to 0 var tokeCount = 0; @@ -507,6 +572,10 @@ userSchema.methods.getTokeCount = function(){ return tokeCount; } +/** + * Gets number of emotes for a given user document + * @returns {Array} Array of objects representing emotes for the given user document + */ userSchema.methods.getEmotes = function(){ //Create an empty array to hold our emote list const emoteList = []; @@ -525,6 +594,10 @@ userSchema.methods.getEmotes = function(){ return emoteList; } +/** + * Deletes an emote from the user Document + * @param {String} name - Name of emote to delete + */ userSchema.methods.deleteEmote = async function(name){ //Get index by emote name const emoteIndex = this.emotes.findIndex(checkName); @@ -541,6 +614,10 @@ userSchema.methods.deleteEmote = async function(name){ } } +/** + * Gets list of user playlists + * @returns {Array} Array of objects represnting a playlist containing media objects + */ userSchema.methods.getPlaylists = function(){ //Create an empty array to hold our emote list const playlists = []; @@ -555,6 +632,10 @@ userSchema.methods.getPlaylists = function(){ return playlists; } +/** + * Iterates through user playlists and calls a given callback function against them + * @param {Function} cb - Callback function to call against user playlists + */ userSchema.methods.playlistCrawl = function(cb){ for(let listIndex in this.playlists){ //Grab the associated playlist @@ -565,6 +646,11 @@ userSchema.methods.playlistCrawl = function(cb){ } } +/** + * Returns playlist by name + * @param {String} name - Playlist to get by name + * @returns {Object} Object representing the requested playlist + */ userSchema.methods.getPlaylistByName = function(name){ //Create null value to hold our found playlist let foundPlaylist = null; @@ -584,6 +670,10 @@ userSchema.methods.getPlaylistByName = function(name){ return foundPlaylist; } +/** + * Deletes a playlist from the user document by name + * @param {String} name - Name of playlist to delete + */ userSchema.methods.deletePlaylistByName = async function(name){ //Find the playlist const playlist = this.getPlaylistByName(name); @@ -595,6 +685,10 @@ userSchema.methods.deletePlaylistByName = async function(name){ await this.save(); } +/** + * Salts, Hashes and Tattoo's IP address to user document in a privacy respecting manner + * @param {String} ip - Plaintext IP Address to Salt, Hash and Tattoo + */ userSchema.methods.tattooIPRecord = async function(ip){ //Hash the users ip const ipHash = hashUtil.hashIP(ip); @@ -666,6 +760,10 @@ userSchema.methods.tattooIPRecord = async function(ip){ } //note: if you gotta call this from a request authenticated by it's user, make sure to kill that session first! +/** + * Kills all sessions for a given user + * @param {String} reason - Reason to kill user sessions + */ userSchema.methods.killAllSessions = async function(reason = "A full log-out from all devices was requested for your account."){ //get authenticated sessions var sessions = await this.getAuthenticatedSessions(); @@ -679,6 +777,10 @@ userSchema.methods.killAllSessions = async function(reason = "A full log-out fro server.channelManager.kickConnections(this.user, reason); } +/** + * Changes password for a given user document + * @param {Object} passChange - passChange object handed down from Browser + */ userSchema.methods.changePassword = async function(passChange){ if(this.checkPass(passChange.oldPass)){ if(passChange.newPass == passChange.confirmPass){ @@ -701,6 +803,11 @@ userSchema.methods.changePassword = async function(passChange){ } +/** + * Checks another user document against this one to see if they're alts + * @param {Mongoose.Document} userDB - User document to check for alts against + * @returns {Boolean} True if alt + */ userSchema.methods.altCheck = async function(userDB){ //Found alt flag let foundAlt = false; @@ -716,6 +823,10 @@ userSchema.methods.altCheck = async function(userDB){ return foundAlt; } +/** + * Nukes user account from the face of the planet upon said user's request + * @param {String} pass - Password to authenticate against before deleting + */ userSchema.methods.nuke = async function(pass){ //Check we have a confirmation password if(pass == "" || pass == null){