Channel Rank/Auth base backend functional

This commit is contained in:
rainbow napkin 2024-11-25 00:44:07 -05:00
parent 057537341a
commit 61fab57a6d
12 changed files with 318 additions and 83 deletions

View file

@ -16,11 +16,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//NPM Imports
const {mongoose} = require('mongoose');
const {validationResult, matchedData} = require('express-validator');
//Local Imports
const statModel = require('../statSchema.js');
const userModel = require('../userSchema.js');
const permissionModel = require('../permissionSchema.js');
const channelPermissionSchema = require('./channelPermissionSchema.js');
const { exceptionHandler } = require('../../utils/loggerUtils.js');
const channelSchema = new mongoose.Schema({
id: {
@ -118,6 +121,56 @@ channelSchema.statics.getChannelList = async function(includeHidden = false){
return chanGuide;
}
//Middleware for rank checks
//Man, it would be really nice if express middleware actually supported async functions, you know, as if it where't still 2015 >:(
//Also holy shit, sharing a function between two middleware functions is a nightmare
//I'd rather just have this check chanField for '/c/' to handle channels in URL, fuck me this was obnoxious to write
channelSchema.statics.reqPermCheck = function(perm, chanField = "chanName"){
return (req, res, next)=>{
try{
//Check validation result
const validResult = validationResult(req);
//if our chan field is set to '/c/', telling us to check the URL
if(chanField == '/c/'){
//Rip the chan name out of the URL
var chanName = (req.originalUrl.split('/c/')[1].replace('/settings',''));
}else if(validResult.isEmpty()){
//otherwise if our input is valid, use that
var chanName = matchedData(req)[chanField];
}else{
//We didn't get /c/ and we got a bad input, time for shit to hit the fan!
res.status(400);
return res.send({errors: validResult.array()})
}
//Find the related channel document, and handle it using a then() block
this.findOne({name: chanName}).then((chanDB) => {
//If we didnt find a channel
if(chanDB == null){
//FUCK
res.status(401);
return res.send({error:`Cannot perm check non-existant channel!.`});
}
//Run a perm check against the current user and permission
chanDB.permCheck(req.session.user, perm).then((permitted) => {
if(permitted){
//if we're permitted, go on to fulfill the request
next();
}else{
//If not, prevent the request from going through and tell them why
res.status(401);
return res.send({error:`You do not have a high enough rank to access this resource.`});
}
});
});
}catch(err){
return exceptionHandler(res, err);
}
}
}
//methods
channelSchema.methods.updateSettings = async function(settingsMap){
settingsMap.forEach((value, key) => {
@ -133,37 +186,140 @@ channelSchema.methods.updateSettings = async function(settingsMap){
return this.settings;
}
channelSchema.methods.updateChannelPerms = async function(permissionsMap){
permissionsMap.forEach((value, key) => {
if(this.permissions[key] == null){
throw new Error("Invalid channel permission.");
channelSchema.methods.rankCrawl = async function(userDB,cb){
//Crawl through channel rank list
//TODO: replace this with rank check function shared with setRank
this.rankList.forEach(async (rankObj, rankIndex) => {
//check against user ID to speed things up
if(rankObj.user.user == null){
if(rankObj.user.toString() == userDB._id.toString()){
//If we found a match, call back
cb(rankObj, rankIndex);
}
}else{
//in case someone populated the users
if(rankObj.user.user == userDB.user){
//If we found a match, call back
cb(rankObj, rankIndex);
}
}
this.permissions[key] = value;
})
await this.save();
return this.permissions;
});
}
channelSchema.methods.setUserRank = async function(userDB,rank){
//Create rank object based on input
const rankObj = {
user: userDB._id,
rank: rank
channelSchema.methods.setRank = async function(userDB,rank){
//Create variable to store found ranks
var foundRankIndex = null;
//Crawl through ranks to find matching index
this.rankCrawl(userDB,(rankObj, rankIndex)=>{foundRankIndex = rankIndex});
//If we found an existing rank object
if(foundRankIndex != null){
if(rank == "user"){
this.rankList.splice(foundRankIndex,1);
}else{
//otherwise, set the users rank
this.rankList[foundRankIndex].rank = rank;
}
}else if(rank != "user"){
//if the user rank object doesn't exist, and we're not setting to user
//Create rank object based on input
const rankObj = {
user: userDB._id,
rank: rank
}
//Add it to rank list
this.rankList.push(rankObj);
}
//Add it to rank list
this.rankList.push(rankObj);
//Save our channel and return rankList
await this.save();
return this.rankList;
}
channelSchema.methods.getChannelRankFromUser = async function(userDB){
channelSchema.methods.getRankList = async function(){
//Create an empty array to hold the user list
const rankList = [];
//Populate the user objects in our ranklist based off of their DB ID's
await this.populate('rankList.user');
//For each rank object in the rank list
this.rankList.forEach(async (rankObj) => {
//Create a new user object from rank object data
const userObj = {
id: rankObj.user.id,
user: rankObj.user.user,
rank: rankObj.rank
}
//Add our user object to the list
rankList.push(userObj);
});
//return userList
return rankList;
}
channelSchema.methods.channelPermCheck = async function(user, perm){
channelSchema.methods.getChannelRankByUserDoc = async function(userDB = null){
var foundRank = null;
//Check to make sure userDB exists before going forward
if(userDB == null){
//If so this user is probably not signed in
return "anon"
}
//Crawl through ranks to find matching rank
this.rankCrawl(userDB,(rankObj)=>{foundRank = rankObj});
//If we found an existing rank object
if(foundRank != null){
//return rank
return foundRank.rank;
}else{
//default to "user" for registered users, and "anon" for anonymous
if(userDB.rank == "anon"){
return "anon";
}else{
return "user";
}
}
}
channelSchema.methods.getChannelRank = async function(user){
const userDB = await userModel.findOne({user: user.user});
return await this.getChannelRankByUserDoc(userDB);
}
channelSchema.methods.permCheckByUserDoc = async function(userDB, perm){
//Get site-wide rank as number, default to anon for anonymous users
const rank = userDB ? permissionModel.rankToNum(userDB.rank) : permissionModel.rankToNum("anon");
//Get channel rank as number
const chanRank = permissionModel.rankToNum(await this.getChannelRankByUserDoc(userDB));
//Get channel permission rank requirement as number
const permRank = permissionModel.rankToNum(this.permissions[perm]);
//Get site-wide rank requirement to override as number
const overrideRank = permissionModel.rankToNum((await permissionModel.getPerms()).channelOverrides[perm]);
//Get channel perm check result
const permCheck = (chanRank >= permRank);
//Get site-wide override perm check result
const overrideCheck = (rank >= overrideRank);
return (permCheck || overrideCheck);
}
channelSchema.methods.permCheck = async function (user, perm){
//Set userDB to null if we wheren't passed a real user
if(user != null){
var userDB = await userModel.findOne({user: user.user});
}else{
var userDB = null;
}
return await this.permCheckByUserDoc(userDB, perm)
}
channelSchema.methods.nuke = async function(confirm){

View file

@ -43,18 +43,6 @@ const permissionSchema = new mongoose.Schema({
default: "admin",
required: true
},
manageChannel: {
type: mongoose.SchemaTypes.String,
enum: rankEnum,
default: "admin",
required: true
},
deleteChannel: {
type: mongoose.SchemaTypes.String,
enum: rankEnum,
default: "admin",
required: true
},
channelOverrides: {
type: channelPermissionSchema,
default: () => ({})