diff --git a/src/utils/altchaUtils.js b/src/utils/altchaUtils.js
index 78e06d8..4c7754f 100644
--- a/src/utils/altchaUtils.js
+++ b/src/utils/altchaUtils.js
@@ -20,11 +20,21 @@ const config = require('../../config.json');
//NPM imports
const { createChallenge, verifySolution } = require('altcha-lib');
-//Create empty array to hold cache of spent payloades to protect against replay attacks
+/**
+ * Create empty array to hold cache of spent payloads to protect against replay attacks
+ */
const spent = [];
-//Captcha lifetime in minutes
+/**
+ * Captcha lifetime in minutes
+ */
const lifetime = 2;
+/**
+ * Generates captcha challenges to send down to the browser
+ * @param {Number} difficulty - Challange Difficulty (x100K internally)
+ * @param {String} uniqueSecret - Secret to salt the challange hash with
+ * @returns {String} Altcha Challenge hash
+ */
module.exports.genCaptcha = async function(difficulty = 2, uniqueSecret = ''){
//Set altcha expiration date
const expiration = new Date();
@@ -40,8 +50,14 @@ module.exports.genCaptcha = async function(difficulty = 2, uniqueSecret = ''){
});
}
+/**
+ * Verifies completed altcha challenges handed over from the user
+ * @param {String} payload - Completed Altcha Payload
+ * @param {String} uniqueSecret - Server-side Unique Secret to verify payload came from server-generated challenge
+ * @returns {boolean} True if payload is a valid and unique altcha challenge which originated from this server
+ */
module.exports.verify = async function(payload, uniqueSecret = ''){
- //If we already checked this payload
+ //If this payload is already spent
if(spent.indexOf(payload) != -1){
//Fuck off and die
return false;
diff --git a/src/utils/configCheck.js b/src/utils/configCheck.js
index d0f9c55..a705195 100644
--- a/src/utils/configCheck.js
+++ b/src/utils/configCheck.js
@@ -23,7 +23,10 @@ const loggerUtil = require('./loggerUtils');
//NPM Imports
const validator = require('validator');//We need validators for express-less code too!
-
+/**
+ * Basic security check which runs on startup.
+ * Warns server admin against unsafe config options.
+ */
module.exports.securityCheck = function(){
//Check Protocol
if(config.protocol.toLowerCase() != 'https'){
diff --git a/src/utils/hashUtils.js b/src/utils/hashUtils.js
index cc78d01..52616c6 100644
--- a/src/utils/hashUtils.js
+++ b/src/utils/hashUtils.js
@@ -23,15 +23,33 @@ const crypto = require('node:crypto');
//NPM Imports
const bcrypt = require('bcrypt');
+/**
+ * Sitewide function for hashing passwords
+ * @param {String} pass - Password to hash
+ * @returns {String} Hashed/Salted password
+ */
module.exports.hashPassword = function(pass){
const salt = bcrypt.genSaltSync();
return bcrypt.hashSync(pass, salt);
}
+/**
+ * Sitewide password for authenticating/comparing passwords agianst hashes
+ * @param {String} pass - Plaintext Password
+ * @param {String} hash - Salty Hash
+ * @returns {Boolean} True if authentication success
+ */
module.exports.comparePassword = function(pass, hash){
return bcrypt.compareSync(pass, hash);
}
+/**
+ * Site-wide IP hashing/salting function
+ *
+ * Provides a basic level of privacy by only logging salted hashes of IP's
+ * @param {String} ip - IP to hash
+ * @returns {String} Hashed/Salted IP Adress
+ */
module.exports.hashIP = function(ip){
//Create hash object
const hashObj = crypto.createHash('md5');
diff --git a/src/utils/linkUtils.js b/src/utils/linkUtils.js
index 9708ab7..6452db1 100644
--- a/src/utils/linkUtils.js
+++ b/src/utils/linkUtils.js
@@ -18,8 +18,16 @@ along with this program. If not, see .*/
const validator = require('validator');//No express here, so regular validator it is!
//Create link cache
+/**
+ * Basic RAM-Based cache of links, so we don't have to re-pull things after we get them
+ */
module.exports.cache = new Map();
+/**
+ * Validates links and returns a marked link object that can be returned to the client to format/embed accordingly
+ * @param {String} link - URL to Validate
+ * @returns {Object} Marked link object
+ */
module.exports.markLink = async function(link){
//Check link cache for the requested link
const cachedLink = module.exports.cache.get(link);
diff --git a/src/utils/loggerUtils.js b/src/utils/loggerUtils.js
index b6daae9..0189f05 100644
--- a/src/utils/loggerUtils.js
+++ b/src/utils/loggerUtils.js
@@ -17,6 +17,15 @@ along with this program. If not, see .*/
//Config
const config = require('../../config.json');
+/**
+ * Creates and returns a custom exception, tagged as a 'custom' exception, using the 'custom' boolean property.
+ * This is used to denote that this error was generated on purpose, with a human readable message, that can be securely sent to the client.
+ * Unexpected exceptions should only be logged internally, however, as they may contain sensitive data.
+ *
+ * @param {String} msg - Error message to send the client
+ * @param {String} type - Error type to send back to the client
+ * @returns {Error} The exception to smith
+ */
module.exports.exceptionSmith = function(msg, type){
//Create the new error with the given message
const exception = new Error(msg);
@@ -31,6 +40,14 @@ module.exports.exceptionSmith = function(msg, type){
return exception;
}
+/**
+ * Main error handling function
+ * @param {Express.Response} res - Response being sent out to the client who caused the issue
+ * @param {String} msg - Error message to send the client
+ * @param {String} type - Error type to send back to the client
+ * @param {Number} status - HTTP(s) Status Code to send back to the client
+ * @returns {Express.Response} If we have a usable Express Response object, return it back after it's been cashed
+ */
module.exports.errorHandler = function(res, msg, type = "Generic", status = 400){
//Some controllers do things after sending headers, for those, we should remain silent
if(!res.headersSent){
@@ -39,6 +56,10 @@ module.exports.errorHandler = function(res, msg, type = "Generic", status = 400)
}
}
+/**
+ * Handles local exceptions which where not directly created by user interaction
+ * @param {Error} err - Exception to handle
+ */
module.exports.localExceptionHandler = function(err){
//If we're being verbose
if(config.verbose){
@@ -47,6 +68,11 @@ module.exports.localExceptionHandler = function(err){
}
}
+/**
+ * Handles exceptions which where directly the fault of user action >:(
+ * @param {Express.Response} res - Express Response object to bitch at
+ * @param {Error} err - Error created by the jerk in question
+ */
module.exports.exceptionHandler = function(res, err){
//If this is a self-made problem
if(err.custom){
@@ -60,14 +86,27 @@ module.exports.exceptionHandler = function(res, err){
}
}
+/**
+ * Basic error-handling for socket.io so we don't just silently swallow errors.
+ * @param {Socket} socket - Socket error originated from
+ * @param {String} msg - Error message to send the client
+ * @param {String} type - Error type to send back to the client
+ * @returns {Boolean} - Passthrough from socket.emit
+ */
module.exports.socketErrorHandler = function(socket, msg, type = "Generic"){
return socket.emit("error", {errors: [{type, msg, date: new Date()}]});
}
+/**
+ * Generates error messages for simple errors generated by socket.io interaction
+ * @param {Socket} socket - Socket error originated from
+ * @param {Error} err - Error created by the jerk in question
+ * @returns {Boolean} - Passthrough from socket.emit
+ */
module.exports.socketExceptionHandler = function(socket, err){
//If this is a self made problem
if(err.custom){
- //at the browser for fucking up, and tell it what it did wrong.
+ //yell at the browser for fucking up, and tell it what it did wrong.
return module.exports.socketErrorHandler(socket, err.message, err.type);
}else{
//Locally handle the exception
@@ -78,6 +117,12 @@ module.exports.socketExceptionHandler = function(socket, err){
}
}
+/**
+ * Generates error messages and drops connection for critical errors caused by socket.io interaction
+ * @param {Socket} socket - Socket error originated from
+ * @param {Error} err - Error created by the jerk in question
+ * @returns {Boolean} - Passthrough from socket.disconnect
+ */
module.exports.socketCriticalExceptionHandler = function(socket, err){
//If this is a self made problem
if(err.custom){
@@ -93,11 +138,21 @@ module.exports.socketCriticalExceptionHandler = function(socket, err){
return socket.disconnect();
}
+/**
+ * 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);
}
-//Basic error-handling middleware to ensure we're not dumping stack traces
+/**
+ * Basic error-handling middleware to ensure we're not dumping stack traces to the client, as that would be insecure
+ * @param {Error} err - Error to handle
+ * @param {Express.Request} req - Express Request
+ * @param {Express.Response} res - Express Response
+ * @param {Function} next - Next function in the Express middleware chain (Not that it's getting called XP)
+ */
module.exports.errorMiddleware = function(err, req, res, next){
//Set generic error
var reason = "Server Error";
diff --git a/src/utils/mailUtils.js b/src/utils/mailUtils.js
index 29d744a..3df123b 100644
--- a/src/utils/mailUtils.js
+++ b/src/utils/mailUtils.js
@@ -21,6 +21,9 @@ const config = require('../../config.json');
const nodeMailer = require("nodemailer");
//Setup mail transport
+/**
+ * nodemailer transport object, generated from options specific in our config file
+ */
const transporter = nodeMailer.createTransport({
host: config.mail.host,
port: config.mail.port,
@@ -31,6 +34,14 @@ const transporter = nodeMailer.createTransport({
}
});
+/**
+ * Sends an email as tokebot to the requested user w/ the requested body and signature
+ * @param {String} to - String containing the email address to send to
+ * @param {String} subject - Subject line of the email to send
+ * @param {String} body - Body contents, either HTML or Plaintext
+ * @param {Boolean} htmlBody - Whether or not Body contents should be sent as HTML or Plaintext
+ * @returns {Object} Sent mail info
+ */
module.exports.mailem = async function(to, subject, body, htmlBody = false){
//Create mail object
const mailObj = {
@@ -56,6 +67,12 @@ module.exports.mailem = async function(to, subject, body, htmlBody = false){
return sentMail;
}
+/**
+ * Sends address verification email
+ * @param {Mongoose.Document} requestDB - DB Document Object for the current email change request token
+ * @param {Mongoose.Document} userDB - DB Document Object for the user we're verifying email against
+ * @param {String} newEmail - New email address to send to
+ */
module.exports.sendAddressVerification = async function(requestDB, userDB, newEmail){
//Send the reset url via email
await module.exports.mailem(
diff --git a/src/utils/media/internetArchiveUtils.js b/src/utils/media/internetArchiveUtils.js
index 71e2a7a..8f7debf 100644
--- a/src/utils/media/internetArchiveUtils.js
+++ b/src/utils/media/internetArchiveUtils.js
@@ -22,6 +22,12 @@ const media = require('../../app/channel/media/media.js');
const regexUtils = require('../regexUtils.js');
const loggerUtils = require('../loggerUtils.js')
+/**
+ * Pulls metadate for a given archive.org item
+ * @param {String} fullID - Full path of the requested upload
+ * @param {String} title - Title to add to media object
+ * @returns {Array} Generated list of media objects from given upload path
+ */
module.exports.fetchMetadata = async function(fullID, title){
//Split fullID by first slash
const [itemID, requestedPath] = decodeURIComponent(fullID).split(/\/(.*)/);
diff --git a/src/utils/media/yanker.js b/src/utils/media/yanker.js
index 1aaef04..5ff5c38 100644
--- a/src/utils/media/yanker.js
+++ b/src/utils/media/yanker.js
@@ -22,6 +22,12 @@ const validator = require('validator');//No express here, so regular validator i
const iaUtil = require('./internetArchiveUtils');
const ytdlpUtil = require('./ytdlpUtils');
+/**
+ * Checks a given URL and runs the proper metadata fetching function to create a media object from any supported URL
+ * @param {String} url - URL to yank media against
+ * @param {String} title - Title to apply to yanked media
+ * @returns
+ */
module.exports.yankMedia = async function(url, title){
//Get pull type
const pullType = await this.getMediaType(url);
@@ -50,6 +56,13 @@ module.exports.yankMedia = async function(url, title){
}
}
+/**
+ * Refreshes raw links on relevant media objects
+ *
+ * Useful for sources like youtube, who only provide expiring raw links
+ * @param {ScheduledMedia} mediaObj - Media Object to refresh
+ * @returns {ScheduledMedia} Refreshed media object
+ */
module.exports.refreshRawLink = async function(mediaObj){
switch(mediaObj.type){
case 'yt':
@@ -76,10 +89,16 @@ module.exports.refreshRawLink = async function(mediaObj){
//Return null to tell the calling function there is no refresh required for this media type
return null;
}
-
-//I'd be lying if this didn't take at least some inspiration/regex patterns from extractQueryParam() in cytube/forest's browser-side 'util.js'
-//Still this has some improvements like url pre-checks and the fact that it's handled serverside, recuing possibility of bad requests.
-//Some of the regex expressions for certain services have also been improved, such as youtube, and the fore.st-unique archive.org
+/**
+ * Detects media type by URL
+ *
+ * I'd be lying if this didn't take at least some inspiration/regex patterns from extractQueryParam() in cytube/forest's browser-side 'util.js'
+ * Still this has some improvements like url pre-checks and the fact that it's handled serverside, recuing possibility of bad requests.
+ * Some of the regex expressions for certain services have also been improved, such as youtube, and the fore.st-unique archive.org
+ *
+ * @param {String} url - URL to determine media type of
+ * @returns {Object} containing URL type and clipped ID string
+ */
module.exports.getMediaType = async function(url){
//Check if we have a valid url, encode it on the fly in case it's too humie-friendly
if(!validator.isURL(encodeURI(url))){
diff --git a/src/utils/media/ytdlpUtils.js b/src/utils/media/ytdlpUtils.js
index c487b86..075189a 100644
--- a/src/utils/media/ytdlpUtils.js
+++ b/src/utils/media/ytdlpUtils.js
@@ -29,6 +29,12 @@ const media = require('../../app/channel/media/media.js');
const regexUtils = require('../regexUtils.js');
const loggerUtils = require('../loggerUtils.js')
+/**
+ * Pulls metadata for a single youtube video via YT-DLP
+ * @param {String} id - Youtube Video ID
+ * @param {String} title - Title to add to the given media object
+ * @returns {Media} Media object containing relevant metadata
+ */
module.exports.fetchYoutubeMetadata = async function(id, title){
try{
//Try to pull media from youtube id
@@ -50,6 +56,12 @@ module.exports.fetchYoutubeMetadata = async function(id, title){
}
}
+/**
+ * Pulls metadata for a playlist of youtube videos via YT-DLP
+ * @param {String} id - Youtube Playlist ID
+ * @param {String} title - Title to add to the given media objects
+ * @returns {Array} Array of Media objects containing relevant metadata
+ */
module.exports.fetchYoutubePlaylistMetadata = async function(id, title){
try{
//Try to pull media from youtube id
@@ -71,15 +83,23 @@ module.exports.fetchYoutubePlaylistMetadata = async function(id, title){
}
}
+/* This requires HLS embeds which, in-turn, require daily motion to add us to their CORS exception list
+ * Not gonna happen, so we need to use their API for this, or proxy the video
module.exports.fetchDailymotionMetadata = async function(id, title){
//Pull media from dailymotion link
const media = await fetchVideoMetadata(`https://dailymotion.com/video/${id}`, title, 'dm');
//Return found media;
return media;
-}
+}*/
-//Generic single video YTDLP function meant to be used by service-sepecific fetchers which will then be used to fetch video metadata
+/**
+ * Generic single video YTDLP function meant to be used by service-sepecific fetchers which will then be used to fetch video metadata
+ * @param {String} link - Link to video in question
+ * @param {String} title - Title to add to the given media objects
+ * @param {String} type - Link type to attach to the resulting media object
+ * @returns {Array} Array of Media objects containing relevant metadata
+ */
async function fetchVideoMetadata(link, title, type, format = 'b'){
//Create media list
const mediaList = [];
@@ -110,6 +130,12 @@ async function fetchVideoMetadata(link, title, type, format = 'b'){
}*/
//Wrapper function for YT-DLP NPM package with pre-set cli-flags
+/**
+ * Basic async YT-DLP Fetch wrapper, ensuring config
+ * @param {String} link - Link to fetch using YT-DLP
+ * @param {String} format - Format string to hand YT-DLP, defaults to 'b'
+ * @returns {Object} Metadata dump from YT-DLP
+ */
async function ytdlpFetch(link, format = 'b'){
//return promise from ytdlp
return ytdlp(link, {
diff --git a/src/utils/regexUtils.js b/src/utils/regexUtils.js
index 09a50c4..64be99a 100644
--- a/src/utils/regexUtils.js
+++ b/src/utils/regexUtils.js
@@ -14,10 +14,15 @@ 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 .*/
+/**
+ * I won't lie this line was whole-sale ganked from stack overflow like a fucking skid
+ * In my defense I only did it because js-runtime-devs are taking fucking eons to implement RegExp.escape()
+ * This should be replaced once that function becomes available in mainline versions of node.js:
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/escape
+ *
+ * @param {String} string - Regex string to escape
+ * @returns {String} The Escaped String
+ */
module.exports.escapeRegex = function(string){
- /* I won't lie this line was whole-sale ganked from stack overflow like a fucking skid
- In my defense I only did it because js-runtime-devs are taking fucking eons to implement RegExp.escape()
- This should be replaced once that function becomes available in mainline versions of node.js:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/escape */
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
\ No newline at end of file
diff --git a/src/utils/scheduler.js b/src/utils/scheduler.js
index 44a8dd9..f1a09bc 100644
--- a/src/utils/scheduler.js
+++ b/src/utils/scheduler.js
@@ -26,6 +26,9 @@ const channelModel = require('../schemas/channel/channelSchema');
const sessionUtils = require('./sessionUtils');
const { email } = require('../validators/accountValidator');
+/**
+ * Schedules all timed jobs accross the server
+ */
module.exports.schedule = function(){
//Process hashed IP Records that haven't been recorded in a week or more
cron.schedule('0 0 * * *', ()=>{userModel.processAgedIPRecords()},{scheduled: true, timezone: "UTC"});
@@ -41,6 +44,9 @@ module.exports.schedule = function(){
cron.schedule('0 0 * * *', ()=>{emailChangeModel.processExpiredRequests()},{scheduled: true, timezone: "UTC"});
}
+/**
+ * Kicks off first run of scheduled functions before scheduling functions for regular callback
+ */
module.exports.kickoff = function(){
//Process Hashed IP Records that haven't been recorded in a week or more
userModel.processAgedIPRecords();
diff --git a/src/utils/sessionUtils.js b/src/utils/sessionUtils.js
index 818a1ae..6aeae3b 100644
--- a/src/utils/sessionUtils.js
+++ b/src/utils/sessionUtils.js
@@ -19,15 +19,33 @@ const config = require('../../config.json');
const {userModel} = require('../schemas/user/userSchema.js');
const userBanModel = require('../schemas/user/userBanSchema.js')
const altchaUtils = require('../utils/altchaUtils.js');
-const loggerUtils = require('../utils/loggerUtils.js')
+const loggerUtils = require('../utils/loggerUtils.js');
-//Create failed sign-in cache since it's easier and more preformant to implement it this way than adding extra burdon to the database
-//Server restarts are far and few between. It would take multiple during a single bruteforce attempt for this to become an issue.
+/**
+ * Create failed sign-in cache since it's easier and more preformant to implement it this way than adding extra burdon to the database
+ * Server restarts are far and few between. It would take multiple during a single bruteforce attempt for this to become an issue.
+ */
const failedAttempts = new Map();
+
+/**
+ * How many failed attempts required to throttle with altcha
+ */
const throttleAttempts = 5;
+
+/**
+ * How many attempts to lock user account out for the day
+ */
const maxAttempts = 200;
-//this module is good for keeping wrappers for userModel and other shit in that does more session handling than database access/modification.
+/**
+ * Sole and Singular Session Authentication method.
+ * All logins should happen through here, all other site-wide authentication should happen by sessions authenticated by this model.
+ * This is important, as reducing authentication endpoints reduces attack surface.
+ * @param {String} user - Username to login as
+ * @param {String} pass - Password to authenticat session with
+ * @param {express.Request} req - Express request object w/ session to authenticate
+ * @returns Username of authticated user upon success
+ */
module.exports.authenticateSession = async function(user, pass, req){
//Fuck you yoda
try{
@@ -137,15 +155,27 @@ module.exports.authenticateSession = async function(user, pass, req){
}
}
+/**
+ * Logs user out and destroys all server-side traces of a given session
+ * @param {express-session.session} session
+ */
module.exports.killSession = async function(session){
session.destroy();
}
+/**
+ * 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
+ * @returns {Number} of failed login attempts
+ */
module.exports.getLoginAttempts = function(user){
//Read the code, i'm not explaining this
return failedAttempts.get(user);
}
+/**
+ * Nightly Function Call which iterates through the failed login attempts map, removing any which haven't been attempted in over a da yeahy
+ */
module.exports.processExpiredAttempts = function(){
for(user of failedAttempts.keys()){
//Get attempt by user