Migrated user related sechamas to src/schemas/user/
This commit is contained in:
parent
3eddd0ea5b
commit
3de4bff68d
26 changed files with 41 additions and 39 deletions
127
src/schemas/user/passwordResetSchema.js
Normal file
127
src/schemas/user/passwordResetSchema.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.*/
|
||||
|
||||
//You could make an argument for making this part of the userModel
|
||||
//However, this is so rarely used the preformance benefits aren't worth the extra clutter
|
||||
|
||||
//Config
|
||||
const config = require('../../../config.json');
|
||||
|
||||
//Node Imports
|
||||
const crypto = require("node:crypto");
|
||||
|
||||
//NPM Imports
|
||||
const {mongoose} = require('mongoose');
|
||||
|
||||
//Local Imports
|
||||
const hashUtil = require('../../utils/hashUtils');
|
||||
|
||||
const daysToExpire = 7;
|
||||
|
||||
const passwordResetSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.SchemaTypes.ObjectID,
|
||||
ref: "user",
|
||||
required: true
|
||||
},
|
||||
token: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
//Use a cryptographically secure algorythm to create a random hex string from 16 bytes as our reset token
|
||||
default: ()=>{return crypto.randomBytes(16).toString('hex')}
|
||||
},
|
||||
ipHash: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true
|
||||
},
|
||||
date: {
|
||||
type: mongoose.SchemaTypes.Date,
|
||||
required: true,
|
||||
default: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//Presave function
|
||||
passwordResetSchema.pre('save', async function (next){
|
||||
//If we're saving an ip
|
||||
if(this.isModified('ipHash')){
|
||||
//Hash that sunnuvabitch
|
||||
this.ipHash = hashUtil.hashIP(this.ipHash);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
//statics
|
||||
passwordResetSchema.statics.processExpiredRequests = async function(){
|
||||
//Pull all requests from the DB
|
||||
const requestDB = await this.find({});
|
||||
|
||||
//Fire em all off at once without waiting for the last one to complete since we don't fuckin' need to
|
||||
requestDB.forEach(async (request) => {
|
||||
//If the request hasn't been processed and it's been expired
|
||||
if(request.getDaysUntilExpiration() <= 0){
|
||||
//Delete the request
|
||||
await this.deleteOne({_id: request._id});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//methods
|
||||
passwordResetSchema.methods.consume = async function(pass, confirmPass){
|
||||
//Check confirmation pass
|
||||
if(pass != confirmPass){
|
||||
throw new Error("Confirmation password does not match!");
|
||||
}
|
||||
|
||||
//Populate the user reference
|
||||
await this.populate('user');
|
||||
|
||||
//Set the users password
|
||||
this.user.pass = pass;
|
||||
|
||||
//Save the user
|
||||
await this.user.save();
|
||||
|
||||
//Kill all authed sessions for security purposes
|
||||
await this.user.killAllSessions("Your password has been reset.");
|
||||
|
||||
//Delete the request token now that it has been consumed
|
||||
await this.deleteOne();
|
||||
}
|
||||
|
||||
passwordResetSchema.methods.getResetURL = function(){
|
||||
//Check for default port based on protocol
|
||||
if((config.protocol == 'http' && config.port == 80) || (config.protocol == 'https' && config.port == 443)){
|
||||
//Return path
|
||||
return `${config.protocol}://${config.domain}/passwordReset?token=${this.token}`;
|
||||
}else{
|
||||
//Return path
|
||||
return `${config.protocol}://${config.domain}:${config.port}/passwordReset?token=${this.token}`;
|
||||
}
|
||||
}
|
||||
|
||||
passwordResetSchema.methods.getDaysUntilExpiration = function(){
|
||||
//Get request date
|
||||
const expirationDate = new Date(this.date);
|
||||
//Get expiration days and calculate expiration date
|
||||
expirationDate.setDate(expirationDate.getDate() + daysToExpire);
|
||||
//Calculate and return days until request expiration
|
||||
return ((expirationDate - new Date()) / (1000 * 60 * 60 * 24)).toFixed(1);
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("passwordReset", passwordResetSchema);
|
||||
258
src/schemas/user/userBanSchema.js
Normal file
258
src/schemas/user/userBanSchema.js
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.*/
|
||||
|
||||
//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);
|
||||
638
src/schemas/user/userSchema.js
Normal file
638
src/schemas/user/userSchema.js
Normal file
|
|
@ -0,0 +1,638 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.*/
|
||||
|
||||
//Node Imports
|
||||
const { profile } = require('console');
|
||||
|
||||
//NPM Imports
|
||||
const {mongoose} = require('mongoose');
|
||||
|
||||
//local imports
|
||||
//server
|
||||
const server = require('../../server');
|
||||
//DB Models
|
||||
const statModel = require('../statSchema');
|
||||
const flairModel = require('../flairSchema');
|
||||
const permissionModel = require('../permissionSchema');
|
||||
const emoteModel = require('../emoteSchema');
|
||||
//Utils
|
||||
const hashUtil = require('../../utils/hashUtils');
|
||||
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
id: {
|
||||
type: mongoose.SchemaTypes.Number,
|
||||
required: true
|
||||
},
|
||||
user: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
maxLength: 22,
|
||||
required: true,
|
||||
},
|
||||
pass: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true
|
||||
},
|
||||
email: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
optional: true,
|
||||
default: ""
|
||||
},
|
||||
date: {
|
||||
type: mongoose.SchemaTypes.Date,
|
||||
required: true,
|
||||
default: new Date()
|
||||
},
|
||||
rank: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
enum: permissionModel.rankEnum,
|
||||
default: "user"
|
||||
},
|
||||
tokes: {
|
||||
type: mongoose.SchemaTypes.Map,
|
||||
required: true,
|
||||
default: new Map()
|
||||
},
|
||||
img: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
default: "/img/johnny.png"
|
||||
},
|
||||
bio: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
maxLength: 1000,
|
||||
default: "Bio not set!"
|
||||
},
|
||||
pronouns:{
|
||||
type: mongoose.SchemaTypes.String,
|
||||
optional: true,
|
||||
maxLength: 20,
|
||||
default: ""
|
||||
},
|
||||
signature: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
maxLength: 150,
|
||||
default: "Signature not set!"
|
||||
},
|
||||
highLevel: {
|
||||
type: mongoose.SchemaTypes.Number,
|
||||
required: true,
|
||||
min: 0,
|
||||
max: 10,
|
||||
default: 0
|
||||
},
|
||||
flair: {
|
||||
type: mongoose.SchemaTypes.ObjectID,
|
||||
default: null,
|
||||
ref: "flair"
|
||||
},
|
||||
//Not re-using the site-wide schema because post/pre save should call different functions
|
||||
emotes: [{
|
||||
name:{
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true
|
||||
},
|
||||
link:{
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true
|
||||
},
|
||||
type:{
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
enum: emoteModel.typeEnum,
|
||||
default: emoteModel.typeEnum[0]
|
||||
}
|
||||
}],
|
||||
recentIPs: [{
|
||||
ipHash: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true
|
||||
},
|
||||
firstLog: {
|
||||
type: mongoose.SchemaTypes.Date,
|
||||
required: true,
|
||||
default: new Date()
|
||||
},
|
||||
lastLog: {
|
||||
type: mongoose.SchemaTypes.Date,
|
||||
required: true,
|
||||
default: new Date()
|
||||
}
|
||||
}],
|
||||
alts:[{
|
||||
type: mongoose.SchemaTypes.ObjectID,
|
||||
ref: "user"
|
||||
}]
|
||||
});
|
||||
|
||||
//This is one of those places where you really DON'T want to use an arrow function over an anonymous one!
|
||||
userSchema.pre('save', async function (next){
|
||||
|
||||
//If the password was changed
|
||||
if(this.isModified("pass")){
|
||||
//Hash that sunnovabitch, no questions asked.
|
||||
this.pass = hashUtil.hashPassword(this.pass);
|
||||
}
|
||||
|
||||
//If the flair was changed
|
||||
if(this.isModified("flair")){
|
||||
//Get flair properties
|
||||
await this.populate('flair');
|
||||
|
||||
if(permissionModel.rankToNum(this.rank) < permissionModel.rankToNum(this.flair.rank)){
|
||||
throw new Error(`User '${this.user}' does not have a high enough rank for flair '${this.flair.displayName}'!`);
|
||||
}
|
||||
}
|
||||
|
||||
//Ensure we don't have empty flair
|
||||
if(this.flair == null){
|
||||
const flairDB = await flairModel.findOne({});
|
||||
this.flair = flairDB._id;
|
||||
}
|
||||
|
||||
//If rank was changed
|
||||
if(this.isModified("rank")){
|
||||
//force a full log-out
|
||||
await this.killAllSessions("Your site-wide rank has changed. Sign-in required.");
|
||||
}
|
||||
|
||||
//if emotes where modified
|
||||
if(this.isModified('emotes')){
|
||||
//Get the active Channel object from the application side of the house
|
||||
server.channelManager.crawlConnections(this.user, (conn)=>{
|
||||
//Send out emotes to each one
|
||||
conn.sendPersonalEmotes(this);
|
||||
});
|
||||
}
|
||||
|
||||
//All is good, continue on saving.
|
||||
next();
|
||||
});
|
||||
|
||||
//post-delete function (document not query)
|
||||
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");
|
||||
|
||||
//Populate alts
|
||||
await this.populate('alts');
|
||||
|
||||
//iterate through alts
|
||||
for(alt in this.alts){
|
||||
//Find the index of the alt entry for this user inside of the alt users array of alt users
|
||||
const altIndex = this.alts[alt].alts.indexOf(this._id);
|
||||
|
||||
//splice the entry for this user out of the alt users array of alt users
|
||||
this.alts[alt].alts.splice(altIndex,1);
|
||||
|
||||
//Save the alt user
|
||||
await this.alts[alt].save();
|
||||
}
|
||||
});
|
||||
|
||||
//statics
|
||||
userSchema.statics.register = async function(userObj, ip){
|
||||
//Pull values from user object
|
||||
const {user, pass, passConfirm, email} = userObj;
|
||||
|
||||
//Check password confirmation matches
|
||||
if(pass == passConfirm){
|
||||
//Look for a user (case insensitive)
|
||||
var userDB = await this.findOne({user: new RegExp(user, 'i')});
|
||||
|
||||
//If we didn't find a user and we submitted an email
|
||||
if(userDB == null && email != null){
|
||||
//Check to make sure no one's used this email address
|
||||
userDB = await this.findOne({email});
|
||||
}
|
||||
|
||||
//If the user is found or someones trying to impersonate tokeboi
|
||||
if(userDB || user.toLowerCase() == "tokebot"){
|
||||
throw new Error("User name/email already taken!");
|
||||
}else{
|
||||
//Increment the user count, pulling the id to tattoo to the user
|
||||
const id = await statModel.incrementUserCount();
|
||||
|
||||
//Create user document in the database
|
||||
const newUser = await this.create({id, user, pass, email});
|
||||
|
||||
//Tattoo the hashed IP used to register to the new user
|
||||
await newUser.tattooIPRecord(ip);
|
||||
}
|
||||
}else{
|
||||
throw new Error("Confirmation password doesn't match!");
|
||||
}
|
||||
}
|
||||
|
||||
userSchema.statics.authenticate = async function(user, pass){
|
||||
//check for missing pass
|
||||
if(!user || !pass){
|
||||
throw new Error("Missing user/pass.");
|
||||
}
|
||||
|
||||
//get the user if it exists
|
||||
const userDB = await this.findOne({ user });
|
||||
|
||||
//if not scream and shout
|
||||
if(!userDB){
|
||||
badLogin();
|
||||
}
|
||||
|
||||
//Check our password is correct
|
||||
if(userDB.checkPass(pass)){
|
||||
return userDB;
|
||||
}else{
|
||||
//if not scream and shout
|
||||
badLogin();
|
||||
}
|
||||
|
||||
//standardize bad login response so it's unknowin which is bad for security reasons.
|
||||
function badLogin(){
|
||||
throw new Error("Bad Username or Password.");
|
||||
}
|
||||
}
|
||||
|
||||
userSchema.statics.findProfile = async function(user){
|
||||
//If someone's looking for tokebot
|
||||
if(user.user.toLowerCase() == "tokebot"){
|
||||
//fake a profile hashtable for tokebot
|
||||
profile = {
|
||||
id: -420,
|
||||
user: "Tokebot",
|
||||
date: (await statModel.getStats()).firstLaunch,
|
||||
tokes: await statModel.getTokeCommandCounts(),
|
||||
tokeCount: await statModel.getTokeCount(),
|
||||
img: "/img/johnny.png",
|
||||
signature: "!TOKE",
|
||||
bio: "!TOKE OR DIE!"
|
||||
};
|
||||
|
||||
//return the faked profile
|
||||
return profile;
|
||||
}else{
|
||||
//find user
|
||||
const userDB = await this.findOne({user: user.user});
|
||||
|
||||
//If we don't find a user just return a null profile
|
||||
if(userDB == null){
|
||||
return null
|
||||
}
|
||||
|
||||
//return the profile
|
||||
return userDB.getProfile();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
userSchema.statics.tattooToke = function(tokemap){
|
||||
//For each toke, asynchronously:
|
||||
tokemap.forEach(async (toke, user) => {
|
||||
//get user
|
||||
const userDB = await this.findOne({user});
|
||||
|
||||
//Check that the user exists (might come in handy for future treez.one integration?)
|
||||
if(userDB != null){
|
||||
var tokeCount = userDB.tokes.get(toke);
|
||||
|
||||
//if this is the first time using this toke command
|
||||
if(tokeCount == null){
|
||||
//set toke count to one
|
||||
tokeCount = 1;
|
||||
//otherwise
|
||||
}else{
|
||||
//increment tokecount
|
||||
tokeCount++;
|
||||
}
|
||||
|
||||
//Set the toke count for the specific command
|
||||
userDB.tokes.set(toke, tokeCount);
|
||||
|
||||
//Save the user doc to the database
|
||||
await userDB.save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
userSchema.statics.getUserList = async function(fullList = false){
|
||||
var userList = [];
|
||||
//Get all of our users
|
||||
const users = await this.find({});
|
||||
|
||||
//Return empty if we don't find nuthin'
|
||||
if(users == null){
|
||||
return [];
|
||||
}
|
||||
|
||||
//For each user
|
||||
users.forEach((user)=>{
|
||||
//create a user object with limited properties (safe for public consumption)
|
||||
var userObj = {
|
||||
id: user.id,
|
||||
user: user.user,
|
||||
img: user.img,
|
||||
date: user.date
|
||||
}
|
||||
|
||||
//Put together a spicier version for admins when told so (permission checks should happen before this is called)
|
||||
if(fullList){
|
||||
userObj.rank = user.rank,
|
||||
userObj.email = user.email
|
||||
}
|
||||
|
||||
//Add user object to list
|
||||
userList.push(userObj);
|
||||
});
|
||||
|
||||
//return the userlist
|
||||
return userList;
|
||||
}
|
||||
|
||||
userSchema.statics.processAgedIPRecords = async function(){
|
||||
//Pull full userlist
|
||||
const users = await this.find({});
|
||||
|
||||
//for every user
|
||||
users.forEach((userDB) => {
|
||||
//For every recent ip within the user
|
||||
userDB.recentIPs.forEach((record, recordI) => {
|
||||
//Check how long it's been since we've last seen the IP
|
||||
const daysSinceLastUse = ((new Date() - record.lastLog) / (1000 * 60 * 60 * 24)).toFixed(1);
|
||||
|
||||
//If it's been more than a week
|
||||
if(daysSinceLastUse >= 7){
|
||||
//Splice out the IP record
|
||||
userDB.recentIPs.splice(recordI, 1);
|
||||
//No reason to wait on this since we're done with this user
|
||||
userDB.save();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
//methods
|
||||
userSchema.methods.checkPass = function(pass){
|
||||
return hashUtil.comparePassword(pass, this.pass)
|
||||
}
|
||||
|
||||
userSchema.methods.getAuthenticatedSessions = async function(){
|
||||
var returnArr = [];
|
||||
|
||||
//retrieve active sessions (they really need to implement this shit async already)
|
||||
return new Promise((resolve) => {
|
||||
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) => {
|
||||
//if a session matches the current user
|
||||
if(session.user.id == this.id){
|
||||
//we return it
|
||||
returnArr.push(session);
|
||||
}
|
||||
});
|
||||
|
||||
resolve(returnArr);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
userSchema.methods.getProfile = function(){
|
||||
//Create profile hashtable
|
||||
const profile = {
|
||||
id: this.id,
|
||||
user: this.user,
|
||||
date: this.date,
|
||||
tokes: this.tokes,
|
||||
tokeCount: this.getTokeCount(),
|
||||
img: this.img,
|
||||
signature: this.signature,
|
||||
bio: this.bio
|
||||
};
|
||||
|
||||
//return profile hashtable
|
||||
return profile;
|
||||
}
|
||||
|
||||
userSchema.methods.getAltProfiles = async function(){
|
||||
//Create an empty list to hold alt profiles
|
||||
const profileList = [];
|
||||
|
||||
//populate the users alt list
|
||||
await this.populate('alts');
|
||||
|
||||
//For every alt for the current user
|
||||
for(let alt of this.alts){
|
||||
//get the alts profile and push it to the profile list
|
||||
profileList.push(alt.getProfile());
|
||||
}
|
||||
|
||||
//return our generated profile list
|
||||
return profileList;
|
||||
}
|
||||
|
||||
userSchema.methods.setFlair = async function(flair){
|
||||
//Find flair by name
|
||||
const flairDB = await flairModel.findOne({name: flair});
|
||||
//Set the users flair ref to the found flairs _id
|
||||
this.flair = flairDB._id;
|
||||
//Save the user
|
||||
await this.save();
|
||||
//return the found flair
|
||||
return flairDB;
|
||||
}
|
||||
|
||||
userSchema.methods.getTokeCount = function(){
|
||||
//Set tokeCount to 0
|
||||
var tokeCount = 0;
|
||||
|
||||
//For each toke command the user has used
|
||||
this.tokes.forEach((commandCount) => {
|
||||
//Add the count for that specific command to the total
|
||||
tokeCount += commandCount;
|
||||
});
|
||||
|
||||
//Return the amount of tokes recorded
|
||||
return tokeCount;
|
||||
}
|
||||
|
||||
userSchema.methods.getEmotes = function(){
|
||||
//Create an empty array to hold our emote list
|
||||
const emoteList = [];
|
||||
|
||||
//For each channel emote
|
||||
this.emotes.forEach((emote) => {
|
||||
//Push an object with select information from the emote to the emote list
|
||||
emoteList.push({
|
||||
name: emote.name,
|
||||
link: emote.link,
|
||||
type: emote.type
|
||||
});
|
||||
});
|
||||
|
||||
//return the emote list
|
||||
return emoteList;
|
||||
}
|
||||
|
||||
userSchema.methods.deleteEmote = async function(name){
|
||||
//Get index by emote name
|
||||
const emoteIndex = this.emotes.findIndex(checkName);
|
||||
|
||||
//Splice out found emote
|
||||
this.emotes.splice(emoteIndex, 1);
|
||||
|
||||
//Save the user doc
|
||||
await this.save();
|
||||
|
||||
function checkName(emote){
|
||||
//return emotes
|
||||
return emote.name == name;
|
||||
}
|
||||
}
|
||||
|
||||
userSchema.methods.tattooIPRecord = async function(ip){
|
||||
//Hash the users ip
|
||||
const ipHash = hashUtil.hashIP(ip);
|
||||
|
||||
//Look for a pre-existing entry for this ipHash
|
||||
const foundIndex = this.recentIPs.findIndex(checkHash);
|
||||
|
||||
//If there is no entry
|
||||
if(foundIndex == -1){
|
||||
//Pull the entire userlist
|
||||
//TODO: update query to only pull users with recentIPs, so we aren't looping through inactive users
|
||||
const users = await this.model().find({});
|
||||
|
||||
//create record object
|
||||
const record = {
|
||||
ipHash: ipHash,
|
||||
firstLog: new Date(),
|
||||
lastLog: new Date()
|
||||
};
|
||||
|
||||
//We should really start using for loops and stop acting like its 2008
|
||||
//Though to be quite honest this bit would be particularly brutal without them
|
||||
//For every user in the userlist
|
||||
for(let curUser of users){
|
||||
//Ensure we're not checking the user against itself
|
||||
if(curUser._id != this._id){
|
||||
//For every IP record in the current user
|
||||
for(let curRecord of curUser.recentIPs){
|
||||
//If it matches the current ipHash
|
||||
if(curRecord.ipHash == ipHash){
|
||||
//Check if we've already marked the user as an alt
|
||||
const foundAlt = this.alts.indexOf(curUser._id);
|
||||
|
||||
//If these accounts aren't already marked as alts
|
||||
if(foundAlt == -1){
|
||||
//Add found user to this users alt list
|
||||
this.alts.push(curUser._id);
|
||||
|
||||
//add this user to found users alt list
|
||||
curUser.alts.push(this._id);
|
||||
|
||||
//Save changes to the found user, this user will save at the end of the function
|
||||
await curUser.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Pop it into place
|
||||
this.recentIPs.push(record);
|
||||
|
||||
//Save the user doc
|
||||
await this.save();
|
||||
//Otherwise, if we already have a record for this IP
|
||||
}else{
|
||||
//Update the last logged date for the found record
|
||||
this.recentIPs[foundIndex].lastLog = new Date();
|
||||
|
||||
//Save the user doc
|
||||
await this.save();
|
||||
}
|
||||
|
||||
//Look for matching ip record
|
||||
function checkHash(ipRecord){
|
||||
//return matching records
|
||||
return ipRecord.ipHash == ipHash;
|
||||
}
|
||||
}
|
||||
|
||||
//note: if you gotta call this from a request authenticated by it's user, make sure to kill that session first!
|
||||
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();
|
||||
|
||||
//crawl through and kill all sessions
|
||||
sessions.forEach((session) => {
|
||||
server.store.destroy(session.seshid);
|
||||
});
|
||||
|
||||
//Tell the application side of the house to kick the user out as well
|
||||
server.channelManager.kickConnections(this.user, reason);
|
||||
}
|
||||
|
||||
userSchema.methods.changePassword = async function(passChange){
|
||||
if(this.checkPass(passChange.oldPass)){
|
||||
if(passChange.newPass == passChange.confirmPass){
|
||||
//Note: We don't have to worry about hashing here because the schema is written to do it auto-magically
|
||||
this.pass = passChange.newPass;
|
||||
|
||||
//Save our password
|
||||
await this.save();
|
||||
|
||||
//Kill all authed sessions for security purposes
|
||||
await this.killAllSessions("Your password has been reset.");
|
||||
}else{
|
||||
//confirmation pass doesn't match
|
||||
throw new Error("Mismatched confirmation password!");
|
||||
}
|
||||
}else{
|
||||
//Old password wrong
|
||||
throw new Error("Incorrect Password!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
userSchema.methods.nuke = async function(pass){
|
||||
//Check we have a confirmation password
|
||||
if(pass == "" || pass == null){
|
||||
//scream and shout
|
||||
throw new Error("No confirmation password!");
|
||||
}
|
||||
|
||||
//Check that the password is correct
|
||||
if(this.checkPass(pass)){
|
||||
//delete the user
|
||||
var oldUser = await this.deleteOne();
|
||||
}else{
|
||||
//complain about a bad pass
|
||||
throw new Error("Bad pass.");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.userModel = mongoose.model("user", userSchema);
|
||||
Loading…
Add table
Add a link
Reference in a new issue