/*Canopy - The next generation of stoner streaming software Copyright (C) 2024-2025 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 .*/ //NPM Imports const {mongoose} = require('mongoose'); //Local Imports const {userModel} = require('./userSchema'); const userBanSchema = new mongoose.Schema({ user: { type: mongoose.SchemaTypes.ObjectID, ref: "user" }, //To be used in future when ip-hashing/better session tracking is implemented ips: { type: [mongoose.SchemaTypes.String], required: false }, //To be used in future when alt-detection has been implemented alts: { type: mongoose.SchemaTypes.ObjectID, ref: "user" }, deletedNames: { type: [mongoose.SchemaTypes.String], required: false }, banDate: { type: mongoose.SchemaTypes.Date, required: true, default: new Date() }, expirationDays: { type: mongoose.SchemaTypes.Number, required: true, default: 30 }, //If true, then expiration date deletes associated accounts instead of deleting the ban record permanent: { type: mongoose.SchemaTypes.Boolean, required: true, default: false } }); userBanSchema.statics.checkBanByUserDoc = async function(userDB){ const banDB = await this.find({}); var foundBan = null; banDB.forEach((ban) => { if(ban.user != null){ if(ban.user.toString() == userDB._id.toString()){ foundBan = ban; } } }); return foundBan; } userBanSchema.statics.checkBan = async function(user){ const userDB = await userModel.findOne({user: user.user}); return this.checkBanByUserDoc(userDB); } userBanSchema.statics.checkProcessedBans = async function(user){ //Pull banlist and create empty variable to hold any found ban const banDB = await this.find({}); var foundBan = null; //For each ban in list banDB.forEach((ban)=>{ //For each deleted account associated with the ban ban.deletedNames.forEach((name)=>{ //If the banned name equals the name we're checking against if(name == user){ //We've found our ban foundBan = ban; } }) }); //Return any found associated ban return foundBan; } userBanSchema.statics.banByUserDoc = async function(userDB, permanent, expirationDays){ //Prevent missing users if(userDB == null){ throw new Error("User not found") } //Ensure the user isn't already banned if(await this.checkBanByUserDoc(userDB) != null){ throw new Error("User already banned"); } if(expirationDays < 0){ throw new Error("Expiration Days must be a positive integer!"); }else if(expirationDays < 30 && permanent){ throw new Error("Permanent bans must be given at least 30 days before automatic account deletion!"); }else if(expirationDays > 185){ throw new Error("Expiration/Deletion date cannot be longer than half a year out from the original ban date."); } //Log the user out if(permanent){ await userDB.killAllSessions(`Your account has been permanently banned, and will be nuked from the database in ${expirationDays} day(s).`); }else{ await userDB.killAllSessions(`Your account has been temporarily banned, and will be reinstated in: ${expirationDays} day(s).`); } //Add the ban to the database return await this.create({user: userDB._id, permanent, expirationDays}); } userBanSchema.statics.ban = async function(user, permanent, expirationDays){ const userDB = await userModel.findOne({user: user.user}); return this.banByUserDoc(userDB, permanent, expirationDays); } userBanSchema.statics.unbanByUserDoc = async function(userDB){ //Prevent missing users if(userDB == null){ throw new Error("User not found") } const banDB = await this.checkBanByUserDoc(userDB); if(!banDB){ throw new Error("User already un-banned"); } //Use _id in-case mongoose wants to be a cunt var oldBan = await this.deleteOne({_id: banDB._id}); return oldBan; } userBanSchema.statics.unbanDeleted = async function(user){ const banDB = await this.checkProcessedBans(user); if(!banDB){ throw new Error("User already un-banned"); } const oldBan = await this.deleteOne({_id: banDB._id}); return oldBan; } userBanSchema.statics.unban = async function(user){ //Find user in DB const userDB = await userModel.findOne({user: user.user}); //If user was deleted if(userDB == null){ //unban deleted user return await this.unbanDeleted(user.user); }else{ //unban by user doc return await this.unbanByUserDoc(userDB); } } userBanSchema.statics.getBans = async function(){ const banDB = await this.find({}).populate('user'); var bans = []; banDB.forEach((ban) => { //Calcualte expiration date var expirationDate = new Date(ban.banDate); expirationDate.setDate(expirationDate.getDate() + ban.expirationDays); //Make sure we're not about to read the properties of a null object if(ban.user != null){ var userObj = { id: ban.user.id, user: ban.user.user, img: ban.user.img, date: ban.user.date } } const banObj = { banDate: ban.banDate, expirationDays: ban.expirationDays, expirationDate: expirationDate, daysUntilExpiration: ban.getDaysUntilExpiration(), user: userObj, ips: ban.ips, alts: ban.alts, deletedNames: ban.deletedNames, permanent: ban.permanent } bans.push(banObj); }); return bans; } userBanSchema.statics.processExpiredBans = async function(){ const banDB = await this.find({}); banDB.forEach(async (ban) => { //This ban was already processed, and it's user has been deleted. There is no more to be done... if(ban.user == null){ return; } //If the ban hasn't been processed and it's got 0 or less days to go if(ban.getDaysUntilExpiration() <= 0){ //If the ban is permanent if(ban.permanent){ //Populate the user field await ban.populate('user'); //Add the name to our deleted names list ban.deletedNames.push(ban.user.user); //Hey hey hey, goodbye! await userModel.deleteOne({_id: ban.user._id}); //Empty out the reference ban.user = null; //Save the ban await ban.save(); }else{ //Otherwise, delete the ban and let our user back in :P await this.deleteOne({_id: ban._id}); } } }) } //methods userBanSchema.methods.getDaysUntilExpiration = function(){ //Get ban date const expirationDate = new Date(this.banDate); //Get expiration days and calculate expiration date expirationDate.setDate(expirationDate.getDate() + this.expirationDays); //Calculate and return days until ban expiration return ((expirationDate - new Date()) / (1000 * 60 * 60 * 24)).toFixed(1); } module.exports = mongoose.model("userBan", userBanSchema);